mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-15 19:17:42 +01:00
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:
@@ -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
30
pnpm-lock.yaml
generated
@@ -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
70
src-tauri/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
5
src-tauri/capabilities/zustand.json
Normal file
5
src-tauri/capabilities/zustand.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"identifier": "zustand",
|
||||
"windows": ["*"],
|
||||
"permissions": ["zustand:default", "core:event:default"]
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -119,10 +119,6 @@ export default function SelectionWindow() {
|
||||
};
|
||||
}, [autoHideMs]);
|
||||
|
||||
useEffect(() => {
|
||||
useSelectionStore.getState().initSync();
|
||||
}, []);
|
||||
|
||||
const close = async () => {
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -130,4 +130,4 @@ export const useAppStore = create<IAppStore>()(
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user