improve profile system

This commit is contained in:
Boof2015
2026-03-31 15:48:34 -04:00
parent 3a8a6dae75
commit d76abe38a8
11 changed files with 574 additions and 93 deletions
+75 -17
View File
@@ -83,6 +83,10 @@ function canUseElectronAPI(): boolean {
return typeof window !== 'undefined' && typeof window.electronAPI !== 'undefined'
}
function canUseBrowserStorage(): boolean {
return typeof window !== 'undefined' && typeof window.localStorage !== 'undefined'
}
function getErrorMessage(error: unknown, fallback: string): string {
return error instanceof Error && error.message
? error.message
@@ -90,9 +94,20 @@ function getErrorMessage(error: unknown, fallback: string): string {
}
function loadFromStorage(): Partial<PersistedSettingsState> {
if (!canUseBrowserStorage()) {
return {}
}
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (raw) return JSON.parse(raw) as Partial<PersistedSettingsState>
if (!raw) {
return {}
}
const parsed = JSON.parse(raw) as unknown
if (typeof parsed === 'object' && parsed !== null) {
return parsed as Partial<PersistedSettingsState>
}
} catch {
// Ignore localStorage read failures.
}
@@ -100,6 +115,10 @@ function loadFromStorage(): Partial<PersistedSettingsState> {
}
function saveToStorage(state: WorkingSettingsState): void {
if (!canUseBrowserStorage()) {
return
}
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({
themeId: state.themeId,
@@ -116,9 +135,17 @@ function saveToStorage(state: WorkingSettingsState): void {
}
function persistWorkingState(state: WorkingSettingsState): void {
if (!canUseElectronAPI()) {
saveToStorage(state)
}
saveToStorage(state)
}
function hasPersistedWorkingState(state: Partial<PersistedSettingsState>): boolean {
return 'themeId' in state
|| 'scopeOrder' in state
|| 'hiddenScopes' in state
|| 'widthWeights' in state
|| 'scopeSettings' in state
|| 'scopePopouts' in state
|| 'windowBounds' in state
}
function loadLegacyProfileMigrationPayload(): LegacyProfileMigrationPayload | null {
@@ -178,6 +205,20 @@ function createWorkingStateFromProfile(profile: Profile): WorkingSettingsState {
}
}
function createWorkingStateFromPersistedState(state: Partial<PersistedSettingsState>): WorkingSettingsState {
return {
themeId: typeof state.themeId === 'string' && state.themeId.trim()
? state.themeId.trim()
: null,
scopeOrder: normalizeScopeOrder(state.scopeOrder),
hiddenScopes: new Set<ScopeKind>(normalizeHiddenScopes(state.hiddenScopes)),
widthWeights: normalizeWidthWeights(state.widthWeights),
scopeSettings: mergeScopeSettings(state.scopeSettings),
scopePopouts: normalizeScopePopouts(state.scopePopouts),
windowBounds: state.windowBounds,
}
}
function getActiveProfileName(state: Pick<SettingsState, 'activeProfileId' | 'profiles'>): string | null {
if (!state.activeProfileId) {
return null
@@ -454,18 +495,8 @@ async function restoreSavedProfileBaseline(
syncCurrentMainWindowBounds(set)
}
const stored = canUseElectronAPI() ? {} : loadFromStorage()
const initialWorkingState: WorkingSettingsState = {
themeId: typeof stored.themeId === 'string' && stored.themeId.trim()
? stored.themeId.trim()
: null,
scopeOrder: normalizeScopeOrder(stored.scopeOrder),
hiddenScopes: new Set<ScopeKind>(normalizeHiddenScopes(stored.hiddenScopes)),
widthWeights: normalizeWidthWeights(stored.widthWeights),
scopeSettings: mergeScopeSettings(stored.scopeSettings),
scopePopouts: normalizeScopePopouts(stored.scopePopouts),
windowBounds: stored.windowBounds,
}
const stored = loadFromStorage()
const initialWorkingState = createWorkingStateFromPersistedState(stored)
export const useSettingsStore = create<SettingsState>((set, get) => ({
...initialWorkingState,
@@ -496,7 +527,34 @@ export const useSettingsStore = create<SettingsState>((set, get) => ({
}
}
applyProfileSnapshot(set, snapshot, { loadActiveProfile: true })
// If localStorage has a working state from a previous session, preserve it so the
// user picks up exactly where they left off (dirty or not). The saved profile becomes
// the baseline for dirty-state comparison but the working values are left as-is.
// On first launch (no stored state) we load the profile normally.
const storedWorkingState = loadFromStorage()
const hasStoredWorkingState = hasPersistedWorkingState(storedWorkingState)
applyProfileSnapshot(set, snapshot, { loadActiveProfile: !hasStoredWorkingState })
if (hasStoredWorkingState) {
set((state) => commitWorkingState(
state,
createWorkingStateFromPersistedState(storedWorkingState),
state.savedProfileBaseline,
))
// Restore window position: use the working-state bounds (last known position,
// possibly dirty) or fall back to the saved profile's bounds if none exist.
const state = get()
const boundsToApply = state.windowBounds ?? state.savedProfileBaseline?.windowBounds ?? null
if (boundsToApply) {
window.electronAPI.setWindowBounds(boundsToApply)
}
syncCurrentMainWindowBounds(set)
const { themeId } = state
const { themes, loadTheme, activeThemeId } = useThemeStore.getState()
if (themeId && themeId !== activeThemeId && themes[themeId]) {
void loadTheme(themeId)
}
}
},
applyExternalProfileSnapshot: (snapshot: ProfileLibrarySnapshot) => {