Files
SpineParticlesWeb/src/editor/systemSettings.ts
T
2026-10-24 18:52:25 +08:00

130 lines
5.0 KiB
TypeScript

import { watch, type WatchStopHandle } from 'vue'
import type { SettingsState, useParticleStore } from '../store/particleStore'
import { translate } from '../i18n'
type EditorStore = ReturnType<typeof useParticleStore>
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<SettingsState,
| 'parameterPanelOnRight'
| 'floatingMenuEnabled'
| 'undoLimit'
| 'showEmitter'
| 'showBones'
| 'bonePointDrawSize'
| 'boneAxisDrawSize'
| 'showSkinMesh'
| 'tickEnabled'
| 'axisFontSize'
| 'tickWidth'
| 'showAxisLabels'
| 'unsavedChangesPrompt'
| 'showProjectSize'
| 'axisColor'
>
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<SystemSettingsState>, current: SettingsState): SystemSettingsState {
const booleanValue = <K extends keyof SystemSettingsState>(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<SystemSettingsFile>
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<SystemSettingsState>) {
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 },
)
}