aboutsummaryrefslogtreecommitdiff
path: root/src/store.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/store.ts')
-rw-r--r--src/store.ts226
1 files changed, 226 insertions, 0 deletions
diff --git a/src/store.ts b/src/store.ts
new file mode 100644
index 0000000..d4f87d3
--- /dev/null
+++ b/src/store.ts
@@ -0,0 +1,226 @@
+/*
+ * Defines a global store for UI settings, palette colors, etc
+ */
+
+
+import {defineStore} from 'pinia';
+import {Action} from './lib';
+import {LayoutOptions} from './layout';
+import {getBreakpoint, Breakpoint, getScrollBarWidth, onTouchDevice} from './util';
+
+export type StoreState = {
+ // Device info
+ touchDevice: boolean,
+ breakpoint: Breakpoint,
+ scrollGap: number, // Size of scroll bar, in px
+ // Tree display
+ tree: 'trimmed' | 'images' | 'picked',
+ lytOpts: LayoutOptions,
+ ancestryBarBreadth: number, // px (fixed value needed for transitions)
+ tutPaneSz: number, // px (fixed value needed for transitions)
+ // Search related
+ searchSuggLimit: number, // Max number of search suggestions
+ searchJumpMode: boolean,
+ // Tutorial related
+ tutorialSkip: boolean,
+ disabledActions: Set<Action>,
+ // Coloring
+ color: {
+ text: string, // CSS color
+ textAlt: string,
+ bg: string,
+ bgLight: string,
+ bgDark: string,
+ bgLight2: string,
+ bgDark2: string,
+ bgAlt: string,
+ bgAltDark: string,
+ alt: string,
+ altDark: string,
+ accent: string,
+ },
+ childQtyColors: [number, string][],
+ // Specifies, for an increasing sequence of minimum-child-quantity values, CSS colors to use
+ //eg: [[1, 'green'], [10, 'orange'], [100, 'red']]
+ nonleafBgColors: string[],
+ // Specifies CSS colors to use at various tree depths
+ // With N strings, tiles at depth M use the color at index M % N
+ nonleafHeaderColor: string, // CSS color
+ ancestryBarBgColor: string,
+ // More styling
+ borderRadius: number, // CSS border-radius value, in px
+ shadowNormal: string, // CSS box-shadow value
+ shadowHovered: string,
+ shadowFocused: string,
+ // Timing
+ clickHoldDuration: number, // Time after mousedown when a click-and-hold is recognised, in ms
+ transitionDuration: number, // ms
+ animationDelay: number, // Time between updates during transitions/resizes/etc, in ms
+ autoActionDelay: number, // Time between auto-mode actions (incl transitions), in ms
+ // Other
+ disableShortcuts: boolean,
+ autoHide: boolean, // If true, leaf-click failure results in hiding an ancestor and trying again
+};
+function getDefaultState(): StoreState {
+ const breakpoint = getBreakpoint();
+ const scrollGap = getScrollBarWidth();
+ const tileSpacing = breakpoint == 'sm' ? 6 : 9;
+ const color = { // Note: For scrollbar colors on chrome, edit ./index.css
+ text: '#fafaf9',
+ textAlt: '#1c1917',
+ bg: '#292524',
+ bgLight: '#44403c',
+ bgDark: '#1c1917',
+ bgLight2: '#57534e',
+ bgDark2: '#0e0c0b',
+ bgAlt: '#f5f5f4',
+ bgAltDark: '#d6d3d1',
+ alt: '#a3e623',
+ altDark: '#65a30d',
+ accent: '#f59e0b',
+ };
+ return {
+ // Device related
+ touchDevice: onTouchDevice(),
+ breakpoint: breakpoint,
+ scrollGap,
+ // Tree display
+ tree: 'images',
+ lytOpts: {
+ tileSpacing, //px
+ headerSz: 22, // px
+ minTileSz: breakpoint == 'sm' ? 50 : 80, // px
+ maxTileSz: 200, // px
+ // Layout-algorithm related
+ layoutType: 'sweep', // 'sqr' | 'rect' | 'sweep'
+ rectMode: 'auto first-row', // 'horz' | 'vert' | 'linear' | 'auto' | 'auto first-row'
+ rectSensitivity: 0.9, // Between 0 and 1
+ sweepMode: 'left', // 'left' | 'top' | 'shorter' | 'auto'
+ sweptNodesPrio: 'sqrt', // 'linear' | 'sqrt' | 'pow-2/3'
+ sweepToParent: breakpoint == 'sm' ? 'prefer' : 'fallback', // 'none' | 'prefer' | 'fallback'
+ },
+ ancestryBarBreadth: (breakpoint == 'sm' ? 80 : 100) + tileSpacing*2, // px
+ tutPaneSz: 180, // px
+ // Search related
+ searchSuggLimit: 10,
+ searchJumpMode: false,
+ // Tutorial related
+ tutorialSkip: false,
+ disabledActions: new Set() as Set<Action>,
+ // Coloring
+ color,
+ childQtyColors: [[1, 'greenyellow'], [10, 'orange'], [100, 'red']],
+ nonleafBgColors: [color.bgLight, color.bgLight2],
+ nonleafHeaderColor: color.bgDark,
+ ancestryBarBgColor: color.bgLight,
+ // More styling
+ borderRadius: 5, // px
+ shadowNormal: '0 0 2px black',
+ shadowHovered: '0 0 1px 2px ' + color.alt,
+ shadowFocused: '0 0 1px 2px ' + color.accent,
+ // Timing
+ clickHoldDuration: 400, // ms
+ transitionDuration: 300, // ms
+ animationDelay: 100, // ms
+ autoActionDelay: 1000, // ms
+ // Other
+ disableShortcuts: false,
+ autoHide: true,
+ };
+}
+// Gets 'composite keys' which have the form 'key1' or 'key1.key2' (usable to specify properties of store objects)
+function getCompositeKeys(state: StoreState){
+ const compKeys = [];
+ for (const key of Object.getOwnPropertyNames(state) as (keyof StoreState)[]){
+ if (typeof state[key] != 'object'){
+ compKeys.push(key);
+ } else {
+ for (const subkey of Object.getOwnPropertyNames(state[key])){
+ compKeys.push(`${key}.${subkey}`);
+ }
+ }
+ }
+ return compKeys;
+}
+const STORE_COMP_KEYS = getCompositeKeys(getDefaultState());
+// For getting/setting values in store
+function getStoreVal(state: StoreState, compKey: string): any {
+ if (compKey in state){
+ return state[compKey as keyof StoreState];
+ }
+ const [s1, s2] = compKey.split('.', 2);
+ if (s1 in state){
+ const key1 = s1 as keyof StoreState;
+ if (typeof state[key1] == 'object' && s2 in (state[key1] as any)){
+ return (state[key1] as any)[s2];
+ }
+ }
+ return null;
+}
+function setStoreVal(state: StoreState, compKey: string, val: any): void {
+ if (compKey in state){
+ (state[compKey as keyof StoreState] as any) = val;
+ return;
+ }
+ const [s1, s2] = compKey.split('.', 2);
+ if (s1 in state){
+ const key1 = s1 as keyof StoreState;
+ if (typeof state[key1] == 'object' && s2 in (state[key1] as any)){
+ (state[key1] as any)[s2] = val;
+ return;
+ }
+ }
+}
+// For loading settings into [initial] store state
+function loadFromLocalStorage(state: StoreState){
+ for (const key of STORE_COMP_KEYS){
+ const item = localStorage.getItem(key)
+ if (item != null){
+ setStoreVal(state, key, JSON.parse(item));
+ }
+ }
+}
+
+export const useStore = defineStore('store', {
+ state: () => {
+ const state = getDefaultState();
+ loadFromLocalStorage(state);
+ return state;
+ },
+ actions: {
+ reset(): void {
+ Object.assign(this, getDefaultState());
+ },
+ resetOne(key: string){
+ const val = getStoreVal(this, key);
+ if (val != null){
+ const val2 = getStoreVal(getDefaultState(), key);
+ if (val != val2){
+ setStoreVal(this, key, val2);
+ }
+ }
+ },
+ save(key: string){
+ if (STORE_COMP_KEYS.includes(key)){
+ localStorage.setItem(key, JSON.stringify(getStoreVal(this, key)));
+ }
+ },
+ load(): void {
+ loadFromLocalStorage(this);
+ },
+ clear(): void {
+ for (const key of STORE_COMP_KEYS){
+ localStorage.removeItem(key);
+ }
+ },
+ softReset(): void { // Like reset(), but keeps saved values
+ const defaultState = getDefaultState();
+ for (const key of STORE_COMP_KEYS){
+ const defaultVal = getStoreVal(defaultState, key);
+ if (getStoreVal(this, key) != defaultState && localStorage.getItem(key) == null){
+ setStoreVal(this, key, defaultVal)
+ }
+ }
+ },
+ },
+});