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
This commit is contained in:
BiggerRain
2025-11-28 17:29:36 +08:00
committed by GitHub
parent ff7721d17f
commit 73ca224ad8
12 changed files with 174 additions and 73 deletions

View File

@@ -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",

30
pnpm-lock.yaml generated
View File

@@ -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

70
src-tauri/Cargo.lock generated
View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,5 @@
{
"identifier": "zustand",
"windows": ["*"],
"permissions": ["zustand:default", "core:event:default"]
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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);

View File

@@ -119,10 +119,6 @@ export default function SelectionWindow() {
};
}, [autoHideMs]);
useEffect(() => {
useSelectionStore.getState().initSync();
}, []);
const close = async () => {
if (timerRef.current) {
clearTimeout(timerRef.current);

View File

@@ -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

View File

@@ -130,4 +130,4 @@ export const useAppStore = create<IAppStore>()(
}
)
)
);
);

View File

@@ -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<void>;
// 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<ISelectionStore>()(
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,
}),
}
)
)
);
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<SelectionStore>((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,
});