feat: app-store 适配 PathEntry — 新增 togglePath、loadPaths 合并禁用状态、savePaths 过滤 disabled

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:49:26 +08:00
parent 611a36fb98
commit e646a84291
+73 -21
View File
@@ -3,16 +3,17 @@ import { invoke } from '@tauri-apps/api/core';
import i18n from '@/i18n'; import i18n from '@/i18n';
import { UndoRedoManager, OperationType, TargetType } from '@/core/undo-redo'; import { UndoRedoManager, OperationType, TargetType } from '@/core/undo-redo';
import { pathClean } from '@/core/path-manager'; import { pathClean } from '@/core/path-manager';
import type { PathEntry } from '@/core/path-entry';
import appConfig from '@/config/default.json'; import appConfig from '@/config/default.json';
export type TabId = 'system' | 'user' | 'merged'; export type TabId = 'system' | 'user' | 'merged';
interface AppState { interface AppState {
sysPaths: string[]; sysPaths: PathEntry[];
userPaths: string[]; userPaths: PathEntry[];
undoRedo: UndoRedoManager; undoRedo: UndoRedoManager;
_savedSys: string[]; // 上次保存时的快照,用于 isModified 判断 _savedSys: PathEntry[]; // 上次保存时的快照,用于 isModified 判断
_savedUser: string[]; _savedUser: PathEntry[];
activeTab: TabId; activeTab: TabId;
searchQuery: string; searchQuery: string;
@@ -37,6 +38,8 @@ interface AppState {
replacePaths: (target: TargetType, newPaths: string[]) => void; replacePaths: (target: TargetType, newPaths: string[]) => void;
clearPaths: (target: TargetType) => void; clearPaths: (target: TargetType) => void;
togglePath: (index: number, target: TargetType) => void;
undo: () => void; undo: () => void;
redo: () => void; redo: () => void;
@@ -46,8 +49,8 @@ interface AppState {
} }
function arraysEqual(a: readonly string[], b: readonly string[]): boolean { function arraysEqual(a: readonly PathEntry[], b: readonly PathEntry[]): boolean {
return a.length === b.length && a.every((v, i) => v === b[i]); return a.length === b.length && a.every((v, i) => v.path === b[i].path && v.enabled === b[i].enabled);
} }
export const useAppStore = create<AppState>((set, get) => { export const useAppStore = create<AppState>((set, get) => {
@@ -80,10 +83,11 @@ export const useAppStore = create<AppState>((set, get) => {
addPath: (path, target) => { addPath: (path, target) => {
const state = get(); const state = get();
const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths; const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths;
const newList = [...list, path]; const entry: PathEntry = { path, enabled: true };
const newList = [...list, entry];
state.undoRedo.push({ state.undoRedo.push({
type: OperationType.ADD, target, index: newList.length - 1, count: 1, type: OperationType.ADD, target, index: newList.length - 1, count: 1,
oldPaths: [], newPaths: [path], oldPaths: [], newPaths: [entry],
}); });
if (target === TargetType.SYSTEM) set({ sysPaths: newList }); if (target === TargetType.SYSTEM) set({ sysPaths: newList });
else set({ userPaths: newList }); else set({ userPaths: newList });
@@ -93,14 +97,15 @@ export const useAppStore = create<AppState>((set, get) => {
editPath: (index, newPath, target) => { editPath: (index, newPath, target) => {
const state = get(); const state = get();
const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths; const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths;
const oldPath = list[index]; const oldEntry = list[index];
if (oldPath === undefined) return; if (!oldEntry) return;
const newEntry: PathEntry = { path: newPath, enabled: oldEntry.enabled };
state.undoRedo.push({ state.undoRedo.push({
type: OperationType.EDIT, target, index, count: 1, type: OperationType.EDIT, target, index, count: 1,
oldPaths: [oldPath], newPaths: [newPath], oldPaths: [oldEntry], newPaths: [newEntry],
}); });
const newList = [...list]; const newList = [...list];
newList[index] = newPath; newList[index] = newEntry;
if (target === TargetType.SYSTEM) set({ sysPaths: newList }); if (target === TargetType.SYSTEM) set({ sysPaths: newList });
else set({ userPaths: newList }); else set({ userPaths: newList });
markDirty(); markDirty();
@@ -171,21 +176,22 @@ export const useAppStore = create<AppState>((set, get) => {
markDirty(); markDirty();
} }
return removed; return removed.map(e => e.path);
}, },
replacePaths: (target, newPaths) => { replacePaths: (target, newPaths) => {
if (newPaths.length === 0) return; if (newPaths.length === 0) return;
const state = get(); const state = get();
const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths; const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths;
const entries: PathEntry[] = newPaths.map(p => ({ path: p, enabled: true }));
state.undoRedo.push({ state.undoRedo.push({
type: OperationType.IMPORT, target, index: 0, count: newPaths.length, type: OperationType.IMPORT, target, index: 0, count: entries.length,
oldPaths: [...list], newPaths: [...newPaths], oldPaths: [...list], newPaths: [...entries],
}); });
if (target === TargetType.SYSTEM) set({ sysPaths: [...newPaths], selectedIndices: [] }); if (target === TargetType.SYSTEM) set({ sysPaths: [...entries], selectedIndices: [] });
else set({ userPaths: [...newPaths], selectedIndices: [] }); else set({ userPaths: [...entries], selectedIndices: [] });
markDirty(); markDirty();
}, },
@@ -204,6 +210,32 @@ export const useAppStore = create<AppState>((set, get) => {
markDirty(); markDirty();
}, },
togglePath: (index, target) => {
const state = get();
const list = target === TargetType.SYSTEM ? state.sysPaths : state.userPaths;
const oldEntry = list[index];
if (!oldEntry) return;
const newEntry: PathEntry = { path: oldEntry.path, enabled: !oldEntry.enabled };
state.undoRedo.push({
type: OperationType.TOGGLE, target, index, count: 1,
oldPaths: [oldEntry], newPaths: [newEntry],
});
const newList = [...list];
newList[index] = newEntry;
if (target === TargetType.SYSTEM) set({ sysPaths: newList });
else set({ userPaths: newList });
markDirty();
// 即时保存禁用状态
const { sysPaths: sys, userPaths: usr } = get();
const sysDisabled = sys.filter(e => !e.enabled).map(e => e.path);
const usrDisabled = usr.filter(e => !e.enabled).map(e => e.path);
invoke('save_disabled_state', { system: sysDisabled, user: usrDisabled })
.catch(() => {});
},
undo: () => { undo: () => {
const { undoRedo, sysPaths, userPaths, _savedSys, _savedUser } = get(); const { undoRedo, sysPaths, userPaths, _savedSys, _savedUser } = get();
const result = undoRedo.undo(sysPaths, userPaths); const result = undoRedo.undo(sysPaths, userPaths);
@@ -235,9 +267,27 @@ export const useAppStore = create<AppState>((set, get) => {
invoke<string[]>('load_system_paths'), invoke<string[]>('load_system_paths'),
invoke<string[]>('load_user_paths'), invoke<string[]>('load_user_paths'),
]); ]);
// 加载禁用状态(文件不存在时返回空)
let sysDisabled: string[] = [];
let usrDisabled: string[] = [];
try {
const result = await invoke<[string[], string[]]>('load_disabled_state');
sysDisabled = result[0];
usrDisabled = result[1];
} catch {
// 文件不存在或损坏,忽略
}
const sysSet = new Set(sysDisabled);
const usrSet = new Set(usrDisabled);
const sysEntries: PathEntry[] = sysArr.map(p => ({ path: p, enabled: !sysSet.has(p) }));
const usrEntries: PathEntry[] = userArr.map(p => ({ path: p, enabled: !usrSet.has(p) }));
set({ set({
sysPaths: sysArr, userPaths: userArr, sysPaths: sysEntries, userPaths: usrEntries,
_savedSys: [...sysArr], _savedUser: [...userArr], _savedSys: [...sysEntries], _savedUser: [...usrEntries],
undoRedo: new UndoRedoManager(appConfig.undo.maxHistory), undoRedo: new UndoRedoManager(appConfig.undo.maxHistory),
isLoading: false, isModified: false, isLoading: false, isModified: false,
statusMessage: i18n.t('status.loaded', { sysCount: sysArr.length, userCount: userArr.length }), statusMessage: i18n.t('status.loaded', { sysCount: sysArr.length, userCount: userArr.length }),
@@ -252,7 +302,9 @@ export const useAppStore = create<AppState>((set, get) => {
if (state.isSaving) return; if (state.isSaving) return;
set({ isSaving: true, statusMessage: i18n.t('status.saving') }); set({ isSaving: true, statusMessage: i18n.t('status.saving') });
const { sysPaths, userPaths } = state; // 只保存 enabled 的路径到注册表
const sysPaths = state.sysPaths.filter(e => e.enabled).map(e => e.path);
const userPaths = state.userPaths.filter(e => e.enabled).map(e => e.path);
const sysJoined = sysPaths.join(';'); const sysJoined = sysPaths.join(';');
const userJoined = userPaths.join(';'); const userJoined = userPaths.join(';');
@@ -275,7 +327,7 @@ export const useAppStore = create<AppState>((set, get) => {
if (sysOk && userOk) { if (sysOk && userOk) {
invoke('broadcast_env_change').catch(() => {}); invoke('broadcast_env_change').catch(() => {});
const savedSys = [...sysPaths], savedUser = [...userPaths]; const savedSys = [...state.sysPaths], savedUser = [...state.userPaths];
set({ isModified: false, isSaving: false, statusMessage: i18n.t('status.saved'), _savedSys: savedSys, _savedUser: savedUser }); set({ isModified: false, isSaving: false, statusMessage: i18n.t('status.saved'), _savedSys: savedSys, _savedUser: savedUser });
} else { } else {
const reason = (!sysOk && sysResult.status === 'rejected') ? String(sysResult.reason) : const reason = (!sysOk && sysResult.status === 'rejected') ? String(sysResult.reason) :