mirror of
https://github.com/infinilabs/coco-app.git
synced 2025-12-16 19:47:43 +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-shell": "^2.2.1",
|
||||||
"@tauri-apps/plugin-updater": "github:infinilabs/tauri-plugin-updater#v2",
|
"@tauri-apps/plugin-updater": "github:infinilabs/tauri-plugin-updater#v2",
|
||||||
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
"@tauri-apps/plugin-window": "2.0.0-alpha.1",
|
||||||
|
"@tauri-store/zustand": "^1.1.0",
|
||||||
"@wavesurfer/react": "^1.0.11",
|
"@wavesurfer/react": "^1.0.11",
|
||||||
"ahooks": "^3.8.4",
|
"ahooks": "^3.8.4",
|
||||||
"axios": "^1.12.0",
|
"axios": "^1.12.0",
|
||||||
|
|||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -59,6 +59,9 @@ importers:
|
|||||||
'@tauri-apps/plugin-window':
|
'@tauri-apps/plugin-window':
|
||||||
specifier: 2.0.0-alpha.1
|
specifier: 2.0.0-alpha.1
|
||||||
version: 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':
|
'@wavesurfer/react':
|
||||||
specifier: ^1.0.11
|
specifier: ^1.0.11
|
||||||
version: 1.0.11(react@18.3.1)(wavesurfer.js@7.11.1)
|
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':
|
'@tauri-apps/plugin-window@2.0.0-alpha.1':
|
||||||
resolution: {integrity: sha512-dFOAgal/3Txz3SQ+LNQq0AK1EPC+acdaFlwPVB/6KXUZYmaFleIlzgxDVoJCQ+/xOhxvYrdQaFLefh0I/Kldbg==}
|
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':
|
'@tootallnate/quickjs-emscripten@0.23.0':
|
||||||
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
||||||
|
|
||||||
@@ -2161,6 +2170,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-toolkit@1.42.0:
|
||||||
|
resolution: {integrity: sha512-SLHIyY7VfDJBM8clz4+T2oquwTQxEzu263AyhVK4jREOAwJ+8eebaa4wM3nlvnAqhDrMm2EsA6hWHaQsMPQ1nA==}
|
||||||
|
|
||||||
esbuild@0.21.5:
|
esbuild@0.21.5:
|
||||||
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -4863,6 +4875,22 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api': 2.0.0-alpha.6
|
'@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': {}
|
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||||
|
|
||||||
'@types/babel__core@7.20.5':
|
'@types/babel__core@7.20.5':
|
||||||
@@ -5683,6 +5711,8 @@ snapshots:
|
|||||||
has-tostringtag: 1.0.2
|
has-tostringtag: 1.0.2
|
||||||
hasown: 2.0.2
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
es-toolkit@1.42.0: {}
|
||||||
|
|
||||||
esbuild@0.21.5:
|
esbuild@0.21.5:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@esbuild/aix-ppc64': 0.21.5
|
'@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-store",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tauri-plugin-windows-version",
|
"tauri-plugin-windows-version",
|
||||||
|
"tauri-plugin-zustand",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -1577,6 +1578,20 @@ dependencies = [
|
|||||||
"syn 2.0.111",
|
"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]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.9.0"
|
version = "2.9.0"
|
||||||
@@ -7460,6 +7475,18 @@ dependencies = [
|
|||||||
"windows-version",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
@@ -7512,6 +7539,49 @@ dependencies = [
|
|||||||
"wry",
|
"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]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ path-clean = "1.0.1"
|
|||||||
actix-files = "0.6.8"
|
actix-files = "0.6.8"
|
||||||
actix-web = "4.11.0"
|
actix-web = "4.11.0"
|
||||||
tauri-plugin-clipboard-manager = "2"
|
tauri-plugin-clipboard-manager = "2"
|
||||||
|
tauri-plugin-zustand = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.23.0"
|
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_windows_version::init())
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
|
.plugin(tauri_plugin_zustand::init())
|
||||||
.plugin(prevent_default::init());
|
.plugin(prevent_default::init());
|
||||||
|
|
||||||
// Conditional compilation for macOS
|
// Conditional compilation for macOS
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export const AssistantFetcher = ({
|
|||||||
lastServerId.current = currentService?.id;
|
lastServerId.current = currentService?.id;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
total: response.hits.total.value,
|
total: response?.hits?.total?.value ?? 0,
|
||||||
list: assistantList,
|
list: assistantList,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -126,10 +126,6 @@ const SelectionSettings = () => {
|
|||||||
const iconsOnly = useSelectionStore((state) => state.iconsOnly);
|
const iconsOnly = useSelectionStore((state) => state.iconsOnly);
|
||||||
const setIconsOnly = useSelectionStore((state) => state.setIconsOnly);
|
const setIconsOnly = useSelectionStore((state) => state.setIconsOnly);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
useSelectionStore.getState().initSync();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Initialize from global store; write back on change for multi-window sync
|
// Initialize from global store; write back on change for multi-window sync
|
||||||
const toolbarConfig = useSelectionStore((s) => s.toolbarConfig);
|
const toolbarConfig = useSelectionStore((s) => s.toolbarConfig);
|
||||||
const setToolbarConfig = useSelectionStore((s) => s.setToolbarConfig);
|
const setToolbarConfig = useSelectionStore((s) => s.setToolbarConfig);
|
||||||
|
|||||||
@@ -119,10 +119,6 @@ export default function SelectionWindow() {
|
|||||||
};
|
};
|
||||||
}, [autoHideMs]);
|
}, [autoHideMs]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
useSelectionStore.getState().initSync();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const close = async () => {
|
const close = async () => {
|
||||||
if (timerRef.current) {
|
if (timerRef.current) {
|
||||||
clearTimeout(timerRef.current);
|
clearTimeout(timerRef.current);
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ import { useModifierKeyPress } from "@/hooks/useModifierKeyPress";
|
|||||||
import { useIconfontScript } from "@/hooks/useScript";
|
import { useIconfontScript } from "@/hooks/useScript";
|
||||||
import { Extension } from "@/components/Settings/Extensions";
|
import { Extension } from "@/components/Settings/Extensions";
|
||||||
import { useExtensionsStore } from "@/stores/extensionsStore";
|
import { useExtensionsStore } from "@/stores/extensionsStore";
|
||||||
|
import { useSelectionStore } from "@/stores/selectionStore";
|
||||||
import { useServers } from "@/hooks/useServers";
|
import { useServers } from "@/hooks/useServers";
|
||||||
import { useDeepLinkManager } from "@/hooks/useDeepLinkManager";
|
import { useDeepLinkManager } from "@/hooks/useDeepLinkManager";
|
||||||
import { useSelectionWindow } from "../hooks/useSelectionWindow";
|
import { useSelectionWindow } from "../hooks/useSelectionWindow";
|
||||||
import { useSelectionStore } from "@/stores/selectionStore";
|
|
||||||
|
|
||||||
export default function LayoutOutlet() {
|
export default function LayoutOutlet() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -27,6 +27,9 @@ export default function LayoutOutlet() {
|
|||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const { activeTheme, isDark, setIsDark, setTheme } = useThemeStore();
|
const { activeTheme, isDark, setIsDark, setTheme } = useThemeStore();
|
||||||
|
|
||||||
|
// Initialize selection store synchronization
|
||||||
|
useSelectionStore();
|
||||||
|
|
||||||
// init servers isTauri
|
// init servers isTauri
|
||||||
useServers();
|
useServers();
|
||||||
// init deep link manager
|
// init deep link manager
|
||||||
|
|||||||
@@ -1,65 +1,63 @@
|
|||||||
import { create } from "zustand";
|
import { create } from 'zustand';
|
||||||
import { persist, subscribeWithSelector } from "zustand/middleware";
|
import { createTauriStore } from '@tauri-store/zustand';
|
||||||
import platformAdapter from "@/utils/platformAdapter";
|
|
||||||
|
|
||||||
export type ISelectionStore = {
|
// Types adapted from Selection/index.tsx to ensure compatibility
|
||||||
// whether selection is enabled
|
export type LucideIconName =
|
||||||
selectionEnabled: boolean;
|
| "Search"
|
||||||
setSelectionEnabled: (selectionEnabled: boolean) => void;
|
| "Bot"
|
||||||
// toolbar buttons configuration for selection window
|
| "Languages"
|
||||||
toolbarConfig: any[];
|
| "FileText"
|
||||||
setToolbarConfig: (toolbarConfig: any[]) => void;
|
| "Copy"
|
||||||
// whether to show icons only (hide labels) in selection window
|
| "Volume2";
|
||||||
iconsOnly: boolean;
|
|
||||||
setIconsOnly: (iconsOnly: boolean) => void;
|
type IconConfig =
|
||||||
// initialize cross-window sync listeners once
|
| { type: "lucide"; name: LucideIconName; color?: string }
|
||||||
initSync: () => Promise<void>;
|
| { 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>()(
|
type SelectionStore = {
|
||||||
subscribeWithSelector(
|
iconsOnly: boolean;
|
||||||
persist(
|
setIconsOnly: (iconsOnly: boolean) => void;
|
||||||
(set) => ({
|
toolbarConfig: ButtonConfig[];
|
||||||
selectionEnabled: false,
|
setToolbarConfig: (config: ButtonConfig[]) => void;
|
||||||
setSelectionEnabled(selectionEnabled) {
|
selectionEnabled: boolean;
|
||||||
set({ selectionEnabled });
|
setSelectionEnabled: (enabled: boolean) => void;
|
||||||
},
|
}
|
||||||
toolbarConfig: [],
|
|
||||||
setToolbarConfig(toolbarConfig) {
|
// A Zustand store, like any other.
|
||||||
return set({ toolbarConfig });
|
export const useSelectionStore = create<SelectionStore>((set) => ({
|
||||||
},
|
|
||||||
iconsOnly: false,
|
iconsOnly: false,
|
||||||
setIconsOnly(iconsOnly) {
|
setIconsOnly: (iconsOnly) => set({ iconsOnly }),
|
||||||
set({ iconsOnly });
|
toolbarConfig: [],
|
||||||
// broadcast to other windows
|
setToolbarConfig: (toolbarConfig) => set({ toolbarConfig }),
|
||||||
try {
|
selectionEnabled: true,
|
||||||
platformAdapter.emitEvent("selection-icons-only", { value: iconsOnly });
|
setSelectionEnabled: (selectionEnabled) => set({ selectionEnabled }),
|
||||||
} catch {}
|
}));
|
||||||
},
|
|
||||||
initSync: async () => {
|
// A handle to the Tauri plugin.
|
||||||
// ensure listener only initialized once per window context
|
// We will need this to start the store.
|
||||||
const hasInit = (window as any).__selectionIconsOnlyInit__;
|
export const tauriHandler = createTauriStore('selection-store', useSelectionStore, {
|
||||||
if (hasInit) return;
|
saveOnChange: true,
|
||||||
(window as any).__selectionIconsOnlyInit__ = true;
|
autoStart: 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,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user