Skip to content

Commit 50a19f2

Browse files
committed
refactor(state): migrate config store to zustand
1 parent ccea3ae commit 50a19f2

File tree

33 files changed

+457
-446
lines changed

33 files changed

+457
-446
lines changed

packages/client/src/App.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import React, { Suspense, useLayoutEffect } from 'react';
33
import { Route, Switch } from 'wouter';
44

55
import { ModalWrapperProvider } from '$hooks/useModal';
6-
import { mainStore } from '$stores/MainStore';
6+
import { useConfigStore } from '$store/config';
7+
import { useUIStore } from '$store/ui';
78
import { setupStore } from '$stores/SetupStore';
89
import { withoutTransitions } from '$utils/css';
910
import { lazyPreload } from '$utils/react';
@@ -17,8 +18,11 @@ const Setup = lazyPreload(() => import('./views/setup'));
1718
const ModelManager = lazyPreload(() => import('./views/models'));
1819

1920
export const App: React.FC = observer(() => {
20-
const theme = mainStore.theme;
21-
const fontSize = mainStore.config.data?.ui.fontSize || 16;
21+
const config = useConfigStore(state => state.data);
22+
const systemTheme = useUIStore(state => state.systemTheme);
23+
const configTheme = config?.ui.theme || 'dark';
24+
const theme = configTheme === 'system' ? systemTheme : configTheme;
25+
const fontSize = config?.ui.fontSize || 16;
2226
const isSetup = setupStore?.status !== 'done';
2327

2428
useLayoutEffect(() => {

packages/client/src/common/hooks/useHotkey.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { DependencyList, useCallback, useEffect, useState } from 'react';
22

3-
import { mainStore } from '$stores/MainStore';
3+
import { defaultHotkeys } from '$data/hotkeys';
4+
import { useConfigStore } from '$store/config';
45
import { IS_MAC } from '$utils/config';
56

67
let disableHotkeys = false;
@@ -130,11 +131,30 @@ const isHotkeyMatchingKeyboardEvent = (
130131
return false;
131132
};
132133

134+
let parsedHotkeys: Record<string, ParsedHotkey> = {};
135+
136+
useConfigStore.subscribe(state => {
137+
const overrides = state.data?.app?.hotkeys;
138+
const hotkeys = { ...defaultHotkeys };
139+
140+
if (overrides) {
141+
for (const [key, value] of Object.entries(overrides)) {
142+
if (value && key in hotkeys) {
143+
hotkeys[key] = value;
144+
}
145+
}
146+
}
147+
148+
parsedHotkeys = Object.fromEntries(
149+
Object.entries(hotkeys).map(([id, keys]) => [id, parseHotkey(keys)]),
150+
);
151+
});
152+
133153
export function matchHotkey(
134154
e: KeyboardEvent | React.KeyboardEvent,
135155
group?: string,
136156
) {
137-
for (const [id, parsed] of Object.entries(mainStore.hotkeys)) {
157+
for (const [id, parsed] of Object.entries(parsedHotkeys)) {
138158
if (group && !id.startsWith(`${group}_`)) {
139159
continue;
140160
}
@@ -240,7 +260,7 @@ export function useHotkey(
240260
dependencies?: DependencyList,
241261
) {
242262
useEffect(() => {
243-
if (!mainStore.hotkeys[id]) {
263+
if (!defaultHotkeys[id]) {
244264
console.warn(`Unregistered hotkey ID: ${id}`);
245265
}
246266

packages/client/src/common/utils/units.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { mainStore } from '$stores/MainStore';
1+
import { useConfigStore } from '$store/config';
22

33
export function formatTemperature(value: number) {
4-
if (mainStore.config.data?.ui.units.temperature === 'F') {
4+
const config = useConfigStore.getState().data;
5+
if (config?.ui.units.temperature === 'F') {
56
return `${Math.floor(32 + value * 1.8)}°F`;
67
}
78

packages/client/src/components/modelBrowser/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { TreeBrowser } from '$components/treeBrowser';
2727
import { useModalWrapperContext } from '$hooks/useModal';
2828
import { ModelDelete } from '$modals/model/delete';
2929
import { ModelEdit } from '$modals/model/edit';
30-
import { mainStore } from '$stores/MainStore';
30+
import { useSearchFn } from '$store/config/hooks';
3131
import { modelStore } from '$stores/ModelStore';
3232
import { IS_ELECTRON } from '$utils/config';
3333
import { removeFileExtension, stringToColor } from '$utils/string';
@@ -53,6 +53,7 @@ export const ModelBrowser: React.FC<Props> = observer(
5353
architecture,
5454
allowReset,
5555
}) => {
56+
const searchFn = useSearchFn();
5657
const modalWrapper = useModalWrapperContext();
5758
const [compatibleOnly, setCompatibleOnly] = useState(true);
5859
const types = Array.isArray(type) ? type : [type];
@@ -194,7 +195,7 @@ export const ModelBrowser: React.FC<Props> = observer(
194195
</>
195196
}
196197
quickFilter={(models, search) =>
197-
mainStore.searchFn(
198+
searchFn(
198199
models,
199200
search,
200201
item =>

packages/client/src/i18n.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ init({
44
ns: ['civitai', 'model', 'project', 'task', 'setup'],
55
defaultNS: 'model',
66
load: 'languageOnly',
7-
loadPath: `./locales/{{lng}}/{{ns}}.json`,
7+
loadPath: `/locales/{{lng}}/{{ns}}.json`,
88
fallbackLng: 'en',
99
});

packages/client/src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import { createRoot } from 'react-dom/client';
44
import { Router } from 'wouter';
55

66
import { API, TRPC } from '$api';
7+
import { refreshAll } from '$store/actions.ts';
78
import { App } from './App.tsx';
89
import './onError.ts';
910
import './i18n.ts';
1011

12+
refreshAll();
13+
1114
const queryClient = new QueryClient();
1215

1316
const root = createRoot(document.getElementById('root')!);

packages/client/src/modals/instance/backendError/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import { API } from '$api';
44
import { Button } from '$components/button';
55
import { Log } from '$components/log';
66
import { Modal, ModalActions } from '$components/modal';
7-
import { mainStore } from '$stores/MainStore';
7+
import { useBackendStore } from '$store/backend';
88

99
export const InstanceBackendError: React.FC = () => {
10+
const log = useBackendStore(state => state.log);
11+
1012
return (
1113
<Modal
1214
title="Backend error"
1315
size="small"
1416
onSubmit={() => API.instance.restart.mutate()}
1517
>
1618
<div>Unable to start backend. Details:</div>
17-
<Log items={mainStore.backendLog} />
19+
<Log items={log} />
1820
<ModalActions cancelText="Close">
1921
<Button variant="primary">Restart</Button>
2022
</ModalActions>
Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
11
import { useConfigStore } from './config';
2+
import { useInstanceStore } from './instance';
3+
import { useUIStore } from './ui';
24
import { useUpdateStore } from './update';
35

46
export async function refreshAll() {
5-
await Promise.all([
6-
useConfigStore.getState().refresh(),
7-
useUpdateStore.getState().refresh(),
8-
]);
7+
try {
8+
await useInstanceStore.getState().refresh();
9+
10+
await Promise.all([
11+
useConfigStore.getState().refresh(),
12+
useUpdateStore.getState().refresh(),
13+
]);
14+
} catch {
15+
//
16+
}
17+
}
18+
19+
export function notify(title: string, body: string, onClick?: () => void) {
20+
const config = useConfigStore.getState().data;
21+
22+
if (Notification.permission !== 'granted' || !config?.ui?.notifications) {
23+
return;
24+
}
25+
26+
if (useUIStore.getState().isFocused) {
27+
return;
28+
}
29+
30+
const notification = new Notification(title, { body });
31+
if (onClick) {
32+
notification.addEventListener('click', onClick);
33+
}
934
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { useInstanceStore } from '$store/instance';
2+
import { useBackendStore } from '.';
3+
4+
export function useStatus() {
5+
const connected = useInstanceStore(state => state.connected);
6+
const status = useBackendStore(state => state.status);
7+
8+
if (!connected) {
9+
return 'connecting';
10+
}
11+
12+
return status;
13+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { BackendStatus, LogItem } from '@metastable/types';
2+
import { create } from 'zustand';
3+
4+
import { API, linkManager } from '$api';
5+
import { combineUnsubscribables } from '$utils/trpc';
6+
7+
const MAX_LOG_ITEMS = 100;
8+
9+
type State = {
10+
log: LogItem[];
11+
status: BackendStatus;
12+
};
13+
14+
type Actions = {
15+
appendLog: (items: LogItem[]) => void;
16+
};
17+
18+
export const useBackendStore = create<State & Actions>((set, get) => ({
19+
log: [],
20+
status: 'starting',
21+
appendLog: items => {
22+
const log = get().log;
23+
set({ log: [...log, ...items].slice(-1 * MAX_LOG_ITEMS) });
24+
},
25+
}));
26+
27+
linkManager.subscribe(
28+
combineUnsubscribables(() => [
29+
API.instance.onBackendLog.subscribe(undefined, {
30+
onData: items => {
31+
useBackendStore.getState().appendLog(items);
32+
},
33+
}),
34+
API.instance.onBackendStatus.subscribe(undefined, {
35+
onData: status => {
36+
useBackendStore.setState({ status });
37+
},
38+
}),
39+
]),
40+
);

0 commit comments

Comments
 (0)