From 73ca224ad8ad1e209671dfd70ffddab3c3724ec0 Mon Sep 17 00:00:00 2001 From: BiggerRain <15911122312@163.COM> Date: Fri, 28 Nov 2025 17:29:36 +0800 Subject: [PATCH] refactor: the selection store using @tauri-store/zustand (#994) * chore: add @tauri-store/zustand plugin * refactor: the selection store using @tauri-store/zustand * fix: data error * fix: data error * chore: remove config --- package.json | 1 + pnpm-lock.yaml | 30 +++++ src-tauri/Cargo.lock | 70 ++++++++++ src-tauri/Cargo.toml | 1 + src-tauri/capabilities/zustand.json | 5 + src-tauri/src/lib.rs | 1 + src/components/Assistant/AssistantFetcher.tsx | 2 +- .../Advanced/components/Selection/index.tsx | 4 - src/pages/selection/index.tsx | 4 - src/routes/outlet.tsx | 5 +- src/stores/appStore.ts | 2 +- src/stores/selectionStore.ts | 122 +++++++++--------- 12 files changed, 174 insertions(+), 73 deletions(-) create mode 100644 src-tauri/capabilities/zustand.json diff --git a/package.json b/package.json index da6f1407..aa6d4231 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@tauri-apps/plugin-shell": "^2.2.1", "@tauri-apps/plugin-updater": "github:infinilabs/tauri-plugin-updater#v2", "@tauri-apps/plugin-window": "2.0.0-alpha.1", + "@tauri-store/zustand": "^1.1.0", "@wavesurfer/react": "^1.0.11", "ahooks": "^3.8.4", "axios": "^1.12.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26529e98..ea55e69b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: '@tauri-apps/plugin-window': specifier: 2.0.0-alpha.1 version: 2.0.0-alpha.1 + '@tauri-store/zustand': + specifier: ^1.1.0 + version: 1.1.0(@types/react@18.3.26)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) '@wavesurfer/react': specifier: ^1.0.11 version: 1.0.11(react@18.3.1)(wavesurfer.js@7.11.1) @@ -1364,6 +1367,12 @@ packages: '@tauri-apps/plugin-window@2.0.0-alpha.1': resolution: {integrity: sha512-dFOAgal/3Txz3SQ+LNQq0AK1EPC+acdaFlwPVB/6KXUZYmaFleIlzgxDVoJCQ+/xOhxvYrdQaFLefh0I/Kldbg==} + '@tauri-store/shared@0.10.2': + resolution: {integrity: sha512-hnEBbe/m9UG5ATSBp4yJOosLkX5QLblVsGIvz7mtBAqmzeWFfjP4+8X/T81L+bCH6+ref/0vsR9Ekex138kAfg==} + + '@tauri-store/zustand@1.1.0': + resolution: {integrity: sha512-893TmbuiS+uX1KUNzALxnY9FRU9CUpOfZwl8tf7Wxoq4UU2ZabtZqWzLRVBGbs9rejuHGFMJeRE5U/U6tfzz7g==} + '@tootallnate/quickjs-emscripten@0.23.0': resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} @@ -2161,6 +2170,9 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-toolkit@1.42.0: + resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==} + esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} @@ -4863,6 +4875,22 @@ snapshots: dependencies: '@tauri-apps/api': 2.0.0-alpha.6 + '@tauri-store/shared@0.10.2': + dependencies: + '@tauri-apps/api': 2.9.0 + es-toolkit: 1.42.0 + + '@tauri-store/zustand@1.1.0(@types/react@18.3.26)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1))': + dependencies: + '@tauri-apps/api': 2.9.0 + '@tauri-store/shared': 0.10.2 + zustand: 5.0.8(@types/react@18.3.26)(immer@10.2.0)(react@18.3.1)(use-sync-external-store@1.5.0(react@18.3.1)) + transitivePeerDependencies: + - '@types/react' + - immer + - react + - use-sync-external-store + '@tootallnate/quickjs-emscripten@0.23.0': {} '@types/babel__core@7.20.5': @@ -5683,6 +5711,8 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 + es-toolkit@1.42.0: {} + esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1ca41a03..e0e34518 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1214,6 +1214,7 @@ dependencies = [ "tauri-plugin-store", "tauri-plugin-updater", "tauri-plugin-windows-version", + "tauri-plugin-zustand", "tempfile", "thiserror 1.0.69", "tokio", @@ -1577,6 +1578,20 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.9.0" @@ -7460,6 +7475,18 @@ dependencies = [ "windows-version", ] +[[package]] +name = "tauri-plugin-zustand" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3aad14c7a1bf457a697c9b6805af27aff127928e59330ea7850d868381e5ef25" +dependencies = [ + "serde", + "tauri", + "tauri-plugin", + "tauri-store", +] + [[package]] name = "tauri-runtime" version = "2.9.1" @@ -7512,6 +7539,49 @@ dependencies = [ "wry", ] +[[package]] +name = "tauri-store" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aee4e469de686b5ca47838ca6dd99c1b98cbf574848fb4f987eecd11d7c965f1" +dependencies = [ + "dashmap", + "futures", + "itertools 0.14.0", + "semver", + "serde", + "serde_json", + "tauri", + "tauri-store-macros", + "tauri-store-utils", + "thiserror 2.0.17", + "tokio", +] + +[[package]] +name = "tauri-store-macros" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c61be15c39f0abf599c33946f97fdd736346719fc3bbdd4c8acf196f110e06" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "tauri-store-utils" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "855375aae57c8c81bb89c847716e4ec1130fab7e0628310cfd173136f4382080" +dependencies = [ + "parking_lot", + "semver", + "tauri", + "thiserror 2.0.17", + "tokio", +] + [[package]] name = "tauri-utils" version = "2.8.0" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index eabbc175..53361046 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -120,6 +120,7 @@ path-clean = "1.0.1" actix-files = "0.6.8" actix-web = "4.11.0" tauri-plugin-clipboard-manager = "2" +tauri-plugin-zustand = "1" [dev-dependencies] tempfile = "3.23.0" diff --git a/src-tauri/capabilities/zustand.json b/src-tauri/capabilities/zustand.json new file mode 100644 index 00000000..91b524f6 --- /dev/null +++ b/src-tauri/capabilities/zustand.json @@ -0,0 +1,5 @@ +{ + "identifier": "zustand", + "windows": ["*"], + "permissions": ["zustand:default", "core:event:default"] +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 59a5a014..06a898e3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -110,6 +110,7 @@ pub fn run() { ) .plugin(tauri_plugin_windows_version::init()) .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_zustand::init()) .plugin(prevent_default::init()); // Conditional compilation for macOS diff --git a/src/components/Assistant/AssistantFetcher.tsx b/src/components/Assistant/AssistantFetcher.tsx index 7917d9b8..e2b87b55 100644 --- a/src/components/Assistant/AssistantFetcher.tsx +++ b/src/components/Assistant/AssistantFetcher.tsx @@ -67,7 +67,7 @@ export const AssistantFetcher = ({ lastServerId.current = currentService?.id; return { - total: response.hits.total.value, + total: response?.hits?.total?.value ?? 0, list: assistantList, }; } catch (error) { diff --git a/src/components/Settings/Advanced/components/Selection/index.tsx b/src/components/Settings/Advanced/components/Selection/index.tsx index 91ec5538..425d87f5 100644 --- a/src/components/Settings/Advanced/components/Selection/index.tsx +++ b/src/components/Settings/Advanced/components/Selection/index.tsx @@ -126,10 +126,6 @@ const SelectionSettings = () => { const iconsOnly = useSelectionStore((state) => state.iconsOnly); const setIconsOnly = useSelectionStore((state) => state.setIconsOnly); - useEffect(() => { - useSelectionStore.getState().initSync(); - }, []); - // Initialize from global store; write back on change for multi-window sync const toolbarConfig = useSelectionStore((s) => s.toolbarConfig); const setToolbarConfig = useSelectionStore((s) => s.setToolbarConfig); diff --git a/src/pages/selection/index.tsx b/src/pages/selection/index.tsx index dc3e43eb..a9a8851a 100644 --- a/src/pages/selection/index.tsx +++ b/src/pages/selection/index.tsx @@ -119,10 +119,6 @@ export default function SelectionWindow() { }; }, [autoHideMs]); - useEffect(() => { - useSelectionStore.getState().initSync(); - }, []); - const close = async () => { if (timerRef.current) { clearTimeout(timerRef.current); diff --git a/src/routes/outlet.tsx b/src/routes/outlet.tsx index 12b5e5b6..edf29959 100644 --- a/src/routes/outlet.tsx +++ b/src/routes/outlet.tsx @@ -15,10 +15,10 @@ import { useModifierKeyPress } from "@/hooks/useModifierKeyPress"; import { useIconfontScript } from "@/hooks/useScript"; import { Extension } from "@/components/Settings/Extensions"; import { useExtensionsStore } from "@/stores/extensionsStore"; +import { useSelectionStore } from "@/stores/selectionStore"; import { useServers } from "@/hooks/useServers"; import { useDeepLinkManager } from "@/hooks/useDeepLinkManager"; import { useSelectionWindow } from "../hooks/useSelectionWindow"; -import { useSelectionStore } from "@/stores/selectionStore"; export default function LayoutOutlet() { const location = useLocation(); @@ -27,6 +27,9 @@ export default function LayoutOutlet() { const { i18n } = useTranslation(); const { activeTheme, isDark, setIsDark, setTheme } = useThemeStore(); + // Initialize selection store synchronization + useSelectionStore(); + // init servers isTauri useServers(); // init deep link manager diff --git a/src/stores/appStore.ts b/src/stores/appStore.ts index f226c44a..f4c43773 100644 --- a/src/stores/appStore.ts +++ b/src/stores/appStore.ts @@ -130,4 +130,4 @@ export const useAppStore = create()( } ) ) -); +); \ No newline at end of file diff --git a/src/stores/selectionStore.ts b/src/stores/selectionStore.ts index 435b924f..67d624a2 100644 --- a/src/stores/selectionStore.ts +++ b/src/stores/selectionStore.ts @@ -1,65 +1,63 @@ -import { create } from "zustand"; -import { persist, subscribeWithSelector } from "zustand/middleware"; -import platformAdapter from "@/utils/platformAdapter"; +import { create } from 'zustand'; +import { createTauriStore } from '@tauri-store/zustand'; -export type ISelectionStore = { - // whether selection is enabled - selectionEnabled: boolean; - setSelectionEnabled: (selectionEnabled: boolean) => void; - // toolbar buttons configuration for selection window - toolbarConfig: any[]; - setToolbarConfig: (toolbarConfig: any[]) => void; - // whether to show icons only (hide labels) in selection window - iconsOnly: boolean; - setIconsOnly: (iconsOnly: boolean) => void; - // initialize cross-window sync listeners once - initSync: () => Promise; +// Types adapted from Selection/index.tsx to ensure compatibility +export type LucideIconName = + | "Search" + | "Bot" + | "Languages" + | "FileText" + | "Copy" + | "Volume2"; + +type IconConfig = + | { type: "lucide"; name: LucideIconName; color?: string } + | { type: "custom"; dataUrl: string; color?: string }; + +type ActionType = + | "search" + | "ask_ai" + | "translate" + | "summary" + | "copy" + | "speak" + | "custom"; + +export type ButtonConfig = { + id: string; + label: string; + icon: IconConfig; + action: { + type: ActionType; + assistantId?: string; + assistantServerId?: string; + eventName?: string; + }; + labelKey?: string; }; -export const useSelectionStore = create()( - subscribeWithSelector( - persist( - (set) => ({ - selectionEnabled: false, - setSelectionEnabled(selectionEnabled) { - set({ selectionEnabled }); - }, - toolbarConfig: [], - setToolbarConfig(toolbarConfig) { - return set({ toolbarConfig }); - }, - iconsOnly: false, - setIconsOnly(iconsOnly) { - set({ iconsOnly }); - // broadcast to other windows - try { - platformAdapter.emitEvent("selection-icons-only", { value: iconsOnly }); - } catch {} - }, - initSync: async () => { - // ensure listener only initialized once per window context - const hasInit = (window as any).__selectionIconsOnlyInit__; - if (hasInit) return; - (window as any).__selectionIconsOnlyInit__ = true; - try { - await platformAdapter.listenEvent( - "selection-icons-only", - ({ payload }: any) => { - const next = Boolean(payload?.value); - // apply without re-broadcast to avoid echo - set({ iconsOnly: next }); - } - ); - } catch {} - }, - }), - { - name: "selection-store", - partialize: (state) => ({ - toolbarConfig: state.toolbarConfig, - iconsOnly: state.iconsOnly, - }), - } - ) - ) -); \ No newline at end of file +type SelectionStore = { + iconsOnly: boolean; + setIconsOnly: (iconsOnly: boolean) => void; + toolbarConfig: ButtonConfig[]; + setToolbarConfig: (config: ButtonConfig[]) => void; + selectionEnabled: boolean; + setSelectionEnabled: (enabled: boolean) => void; +} + +// A Zustand store, like any other. +export const useSelectionStore = create((set) => ({ + iconsOnly: false, + setIconsOnly: (iconsOnly) => set({ iconsOnly }), + toolbarConfig: [], + setToolbarConfig: (toolbarConfig) => set({ toolbarConfig }), + selectionEnabled: true, + setSelectionEnabled: (selectionEnabled) => set({ selectionEnabled }), +})); + +// A handle to the Tauri plugin. +// We will need this to start the store. +export const tauriHandler = createTauriStore('selection-store', useSelectionStore, { + saveOnChange: true, + autoStart: true, +});