@@ -11,6 +11,8 @@ interface AppState {
sysPaths : string [ ] ;
userPaths : string [ ] ;
undoRedo : UndoRedoManager ;
_savedSys : string [ ] ; // 上次保存时的快照,用于 isModified 判断
_savedUser : string [ ] ;
activeTab : TabId ;
searchQuery : string ;
@@ -19,6 +21,7 @@ interface AppState {
statusMessage : string ;
isModified : boolean ;
isLoading : boolean ;
isSaving : boolean ;
setActiveTab : ( tab : TabId ) = > void ;
setSearchQuery : ( query : string ) = > void ;
@@ -41,14 +44,21 @@ interface AppState {
loadPaths : ( ) = > Promise < void > ;
savePaths : ( ) = > Promise < void > ;
initialize : ( ) = > Promise < void > ;
_markDirty : ( ) = > void ;
}
function arraysEqual ( a : readonly string [ ] , b : readonly string [ ] ) : boolean {
return a . length === b . length && a . every ( ( v , i ) = > v === b [ i ] ) ;
}
export const useAppStore = create < AppState > ( ( set , get ) = > ( {
sysPaths : [ ] ,
userPaths : [ ] ,
undoRedo : new UndoRedoManager ( appConfig . undo . maxHistory ) ,
_savedSys : [ ] ,
_savedUser : [ ] ,
activeTab : 'system' ,
searchQuery : '' ,
@@ -57,6 +67,7 @@ export const useAppStore = create<AppState>((set, get) => ({
statusMessage : '' ,
isModified : false ,
isLoading : true ,
isSaving : false ,
setActiveTab : ( tab ) = > set ( { activeTab : tab } ) ,
setSearchQuery : ( query ) = > set ( { searchQuery : query } ) ,
@@ -71,8 +82,9 @@ export const useAppStore = create<AppState>((set, get) => ({
type : OperationType . ADD , target , index : newList.length - 1 , count : 1 ,
oldPaths : [ ] , newPaths : [ path ] ,
} ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , isModified : true } ) ;
else set ( { userPaths : newList , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList } ) ;
else set ( { userPaths : newList } ) ;
get ( ) . _markDirty ( ) ;
} ,
editPath : ( index , newPath , target ) = > {
@@ -86,8 +98,9 @@ export const useAppStore = create<AppState>((set, get) => ({
} ) ;
const newList = [ . . . list ] ;
newList [ index ] = newPath ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , isModified : true } ) ;
else set ( { userPaths : newList , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList } ) ;
else set ( { userPaths : newList } ) ;
get ( ) . _markDirty ( ) ;
} ,
deletePaths : ( indices , target ) = > {
@@ -95,18 +108,20 @@ export const useAppStore = create<AppState>((set, get) => ({
const state = get ( ) ;
const list = target === TargetType . SYSTEM ? state.sysPaths : state.userPaths ;
const sorted = [ . . . indices ] . sort ( ( a , b ) = > b - a ) ;
const oldPaths = sorted . map ( ( i ) = > list [ i ] ) ;
for ( const idx of sorted ) {
state . undoRedo . push ( {
type : OperationType . DELETE , target , index : idx , count : 1 ,
oldPaths : [ list [ idx ] ] , newPaths : [ ] ,
} ) ;
}
// 单条撤销记录覆盖全部删除
state . undoRedo . push ( {
type : OperationType . DELETE , target ,
index : sorted [ sorted . length - 1 ] , count : sorted.length ,
oldPaths , newPaths : [ ] ,
} ) ;
const toRemove = new Set ( sorted ) ;
const newList = list . filter ( ( _ , i ) = > ! toRemove . has ( i ) ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , selectedIndices : [ ] , isModified : true } ) ;
else set ( { userPaths : newList , selectedIndices : [ ] , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , selectedIndices : [ ] } ) ;
else set ( { userPaths : newList , selectedIndices : [ ] } ) ;
get ( ) . _markDirty ( ) ;
} ,
moveUp : ( index , target ) = > {
@@ -114,13 +129,13 @@ export const useAppStore = create<AppState>((set, get) => ({
const state = get ( ) ;
const list = target === TargetType . SYSTEM ? state.sysPaths : state.userPaths ;
state . undoRedo . push ( {
type : OperationType . MOVE_UP , target , index , count : 1 ,
oldPaths : [ ] , newPaths : [ ] ,
type : OperationType . MOVE_UP , target , index , count : 1 , oldPaths : [ ] , newPaths : [ ] ,
} ) ;
const newList = [ . . . list ] ;
[ newList [ index - 1 ] , newList [ index ] ] = [ newList [ index ] , newList [ index - 1 ] ] ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , selectedIndices : [ index - 1 ] , isModified : true } ) ;
else set ( { userPaths : newList , selectedIndices : [ index - 1 ] , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , selectedIndices : [ index - 1 ] } ) ;
else set ( { userPaths : newList , selectedIndices : [ index - 1 ] } ) ;
get ( ) . _markDirty ( ) ;
} ,
moveDown : ( index , target ) = > {
@@ -128,13 +143,13 @@ export const useAppStore = create<AppState>((set, get) => ({
const list = target === TargetType . SYSTEM ? state.sysPaths : state.userPaths ;
if ( index >= list . length - 1 ) return ;
state . undoRedo . push ( {
type : OperationType . MOVE_DOWN , target , index , count : 1 ,
oldPaths : [ ] , newPaths : [ ] ,
type : OperationType . MOVE_DOWN , target , index , count : 1 , oldPaths : [ ] , newPaths : [ ] ,
} ) ;
const newList = [ . . . list ] ;
[ newList [ index ] , newList [ index + 1 ] ] = [ newList [ index + 1 ] , newList [ index ] ] ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , selectedIndices : [ index + 1 ] , isModified : true } ) ;
else set ( { userPaths : newList , selectedIndices : [ index + 1 ] , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : newList , selectedIndices : [ index + 1 ] } ) ;
else set ( { userPaths : newList , selectedIndices : [ index + 1 ] } ) ;
get ( ) . _markDirty ( ) ;
} ,
cleanPaths : ( target , validateFn ) = > {
@@ -147,8 +162,9 @@ export const useAppStore = create<AppState>((set, get) => ({
type : OperationType . CLEAN , target , index : 0 , count : removed.length ,
oldPaths : [ . . . list ] , newPaths : kept ,
} ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : kept , selectedIndices : [ ] , isModified : true } ) ;
else set ( { userPaths : kept , selectedIndices : [ ] , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : kept , selectedIndices : [ ] } ) ;
else set ( { userPaths : kept , selectedIndices : [ ] } ) ;
get ( ) . _markDirty ( ) ;
}
return removed ;
@@ -158,15 +174,15 @@ export const useAppStore = create<AppState>((set, get) => ({
if ( importPaths . length === 0 ) return ;
const state = get ( ) ;
const list = target === TargetType . SYSTEM ? state.sysPaths : state.userPaths ;
const copied = [ . . . importPaths ] ;
state . undoRedo . push ( {
type : OperationType . IMPORT , target , index : 0 , count : copied.length ,
oldPaths : [ . . . list ] , newPaths : copied ,
type : OperationType . IMPORT , target , index : 0 , count : importPaths.length ,
oldPaths : [ . . . list ] , newPaths : [ . . . importPaths ] ,
} ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : copied , selectedIndices : [ ] , isModified : true } ) ;
else set ( { userPaths : copied , selectedIndices : [ ] , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : [ . . . importPaths ] , selectedIndices : [ ] } ) ;
else set ( { userPaths : [ . . . importPaths ] , selectedIndices : [ ] } ) ;
get ( ) . _markDirty ( ) ;
} ,
clearPaths : ( target ) = > {
@@ -179,20 +195,36 @@ export const useAppStore = create<AppState>((set, get) => ({
oldPaths : [ . . . list ] , newPaths : [ ] ,
} ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : [ ] , isModified : true } ) ;
else set ( { userPaths : [ ] , isModified : true } ) ;
if ( target === TargetType . SYSTEM ) set ( { sysPaths : [ ] } ) ;
else set ( { userPaths : [ ] } ) ;
get ( ) . _markDirty ( ) ;
} ,
undo : ( ) = > {
const { undoRedo , sysPaths , userPaths } = get ( ) ;
const { undoRedo , sysPaths , userPaths , _savedSys , _savedUser } = get ( ) ;
const result = undoRedo . undo ( sysPaths , userPaths ) ;
if ( result ) set ( { sysPaths : result [ 0 ] , userPaths : result [ 1 ] , isModified : true , selectedIndices : [ ] } ) ;
if ( result ) {
set ( {
sysPaths : result [ 0 ] , userPaths : result [ 1 ] , selectedIndices : [ ] ,
isModified : ! ( arraysEqual ( result [ 0 ] , _savedSys ) && arraysEqual ( result [ 1 ] , _savedUser ) ) ,
} ) ;
}
} ,
redo : ( ) = > {
const { undoRedo , sysPaths , userPaths } = get ( ) ;
const { undoRedo , sysPaths , userPaths , _savedSys , _savedUser } = get ( ) ;
const result = undoRedo . redo ( sysPaths , userPaths ) ;
if ( result ) set ( { sysPaths : result [ 0 ] , userPaths : result [ 1 ] , isModified : true , selectedIndices : [ ] } ) ;
if ( result ) {
set ( {
sysPaths : result [ 0 ] , userPaths : result [ 1 ] , selectedIndices : [ ] ,
isModified : ! ( arraysEqual ( result [ 0 ] , _savedSys ) && arraysEqual ( result [ 1 ] , _savedUser ) ) ,
} ) ;
}
} ,
_markDirty : ( ) = > {
const { _savedSys , _savedUser , sysPaths , userPaths } = get ( ) ;
set ( { isModified : ! ( arraysEqual ( sysPaths , _savedSys ) && arraysEqual ( userPaths , _savedUser ) ) } ) ;
} ,
canUndo : ( ) = > get ( ) . undoRedo . canUndo ( ) ,
@@ -206,11 +238,10 @@ export const useAppStore = create<AppState>((set, get) => ({
invoke < string [ ] > ( 'load_user_paths' ) ,
] ) ;
set ( {
sysPaths : sysArr ,
userPaths : userArr ,
sysPaths : sysArr , userPaths : userArr ,
_savedSys : [ . . . sysArr ] , _savedUser : [ . . . userArr ] ,
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 } ) ,
} ) ;
} catch ( e ) {
@@ -219,21 +250,23 @@ export const useAppStore = create<AppState>((set, get) => ({
} ,
savePaths : async ( ) = > {
const { sysPaths , userPaths } = get ( ) ;
const state = get ( ) ;
if ( state . isSaving ) return ;
set ( { isSaving : true , statusMessage : i18n.t ( 'status.saving' ) } ) ;
const { sysPaths , userPaths } = state ;
const sysJoined = sysPaths . join ( ';' ) ;
const userJoined = userPaths . join ( ';' ) ;
const { maxSystemLength , maxUserLength , maxCombinedLength } = appConfig . path ;
if ( sysJoined . length > maxSystemLength || userJoined . length > maxUserLength || ( sysJoined + userJoined ) . length > maxCombinedLength ) {
if ( ! window . confirm ( ` ${ i18n . t ( 'status.error' ) } : PATH 长度超过建议值,是否继续? ` ) ) return ;
if ( ! window . confirm ( ' PATH 长度超过建议值,是否继续保存?' ) ) { set ( { isSaving : false } ) ; return ; }
}
set ( { statusMessage : i18n.t ( 'status.saving' ) } ) ;
// 备份(失败时通知用户)
invoke ( 'backup_registry' , { customDir : null , sysPaths , userPaths } )
. catch ( ( ) = > set ( { statusMessage : i18n.t ( 'status.warning_backup' ) } ) ) ;
// 备份(不阻塞保存)
invoke ( 'backup_registry' , { customDir : null , sysPaths , userPaths } ) . catch ( ( ) = > { } ) ;
// 并行保存
const [ sysResult , userResult ] = await Promise . allSettled ( [
invoke ( 'save_system_paths' , { paths : sysPaths } ) ,
invoke ( 'save_user_paths' , { paths : userPaths } ) ,
@@ -244,13 +277,13 @@ export const useAppStore = create<AppState>((set, get) => ({
if ( sysOk && userOk ) {
invoke ( 'broadcast_env_change' ) . catch ( ( ) = > { } ) ;
set ( { isModified : false , statusMessage : i18n.t ( 'status.saved' ) } ) ;
} else if ( sysOk ) {
set ( { statusMessage : '用户 PATH 保存失败,系统 PATH 已保存' } ) ;
} else if ( userOk ) {
set ( { statusMessage : '系统 PATH 保存失败,用户 PATH 已保存' } ) ;
const savedSys = [ . . . sysPaths ] , savedUser = [ . . . userPaths ] ;
set ( { isModified : false , isSaving : false , statusMessage : i18n.t ( 'status.saved' ) , _savedSys : savedSys , _savedUser : savedUser } ) ;
} else {
set ( { statusMessage : ` ${ i18n . t ( 'status.error' ) } : 保存失败 ` } ) ;
const reason = ( ! sysOk && sysResult . status === 'rejected' ) ? String ( sysResult . reason ) :
( ! userOk && userResult . status === 'rejected' ) ? String ( userResult . reason ) : '' ;
const msg = sysOk ? '用户 PATH 保存失败' : userOk ? '系统 PATH 保存失败' : ` 保存失败: ${ reason } ` ;
set ( { isSaving : false , statusMessage : msg } ) ;
}
} ,