import { watch, type WatchStopHandle } from 'vue' import type { SettingsState, useParticleStore } from '../store/particleStore' import { translate } from '../i18n' type EditorStore = ReturnType export const SYSTEM_SETTINGS_FORMAT = 'SpineParticle.SystemSettings' export const SYSTEM_SETTINGS_VERSION = 1 const SYSTEM_SETTINGS_CACHE_KEY = 'SpineParticle.SystemSettings.v1' export type SystemSettingsState = Pick export interface SystemSettingsFile { format: typeof SYSTEM_SETTINGS_FORMAT version: typeof SYSTEM_SETTINGS_VERSION savedAt: string settings: SystemSettingsState } function clamp(value: unknown, min: number, max: number, fallback: number) { const number = Number(value) return Math.min(max, Math.max(min, Number.isFinite(number) ? number : fallback)) } export function captureSystemSettings(settings: SettingsState): SystemSettingsState { return { parameterPanelOnRight: settings.parameterPanelOnRight, floatingMenuEnabled: settings.floatingMenuEnabled, undoLimit: settings.undoLimit, showEmitter: settings.showEmitter, showBones: settings.showBones, bonePointDrawSize: settings.bonePointDrawSize, boneAxisDrawSize: settings.boneAxisDrawSize, showSkinMesh: settings.showSkinMesh, tickEnabled: settings.tickEnabled, axisFontSize: settings.axisFontSize, tickWidth: settings.tickWidth, showAxisLabels: settings.showAxisLabels, unsavedChangesPrompt: settings.unsavedChangesPrompt, showProjectSize: settings.showProjectSize, axisColor: settings.axisColor, } } function normalizedSystemSettings(value: Partial, current: SettingsState): SystemSettingsState { const booleanValue = (key: K) => value[key] == null ? Boolean(current[key]) : value[key] === true const axisColor = typeof value.axisColor === 'string' && /^#[0-9a-f]{6}$/i.test(value.axisColor) ? value.axisColor : current.axisColor return { parameterPanelOnRight: booleanValue('parameterPanelOnRight'), floatingMenuEnabled: booleanValue('floatingMenuEnabled'), undoLimit: Math.round(clamp(value.undoLimit, 1, 200, current.undoLimit)), showEmitter: booleanValue('showEmitter'), showBones: booleanValue('showBones'), bonePointDrawSize: clamp(value.bonePointDrawSize, 0.1, 10, current.bonePointDrawSize), boneAxisDrawSize: clamp(value.boneAxisDrawSize, 0.1, 10, current.boneAxisDrawSize), showSkinMesh: booleanValue('showSkinMesh'), tickEnabled: booleanValue('tickEnabled'), axisFontSize: Math.round(clamp(value.axisFontSize, 8, 40, current.axisFontSize)), tickWidth: clamp(value.tickWidth, 1, 5, current.tickWidth), showAxisLabels: booleanValue('showAxisLabels'), unsavedChangesPrompt: booleanValue('unsavedChangesPrompt'), showProjectSize: booleanValue('showProjectSize'), axisColor, } } function parseSystemSettingsCache(text: string): SystemSettingsFile { let parsed: unknown try { parsed = JSON.parse(text) } catch { throw new Error(translate('errors.settingsInvalidJson')) } const candidate = parsed as Partial if (candidate.format !== SYSTEM_SETTINGS_FORMAT) throw new Error(translate('errors.settingsFormat')) if (candidate.version !== SYSTEM_SETTINGS_VERSION) throw new Error(translate('errors.settingsVersion', { version: candidate.version ?? translate('export.unknown') })) if (!candidate.settings || typeof candidate.settings !== 'object') throw new Error(translate('errors.settingsMissing')) return candidate as SystemSettingsFile } export function applySystemSettings(store: EditorStore, settings: Partial) { Object.assign(store.settings, normalizedSystemSettings(settings, store.settings)) } let stopCacheSync: WatchStopHandle | null = null /** 启动时读取浏览器缓存,之后将系统设置的每次修改自动同步回缓存。 */ export function installSystemSettingsCache(store: EditorStore) { stopCacheSync?.() try { const cached = localStorage.getItem(SYSTEM_SETTINGS_CACHE_KEY) if (cached) applySystemSettings(store, parseSystemSettingsCache(cached).settings) } catch { try { localStorage.removeItem(SYSTEM_SETTINGS_CACHE_KEY) } catch { /* 浏览器禁用本地存储时使用当前会话默认值。 */ } } stopCacheSync = watch( () => captureSystemSettings(store.settings), (settings) => { const file: SystemSettingsFile = { format: SYSTEM_SETTINGS_FORMAT, version: SYSTEM_SETTINGS_VERSION, savedAt: new Date().toISOString(), settings, } try { localStorage.setItem(SYSTEM_SETTINGS_CACHE_KEY, JSON.stringify(file)) } catch { /* 缓存不可用不影响编辑器运行。 */ } }, { deep: true }, ) }