diff --git a/src/lib/presets.ts b/src/lib/presets.ts new file mode 100644 index 0000000..ab0b49a --- /dev/null +++ b/src/lib/presets.ts @@ -0,0 +1,12 @@ +import { parseTemplate } from './data/parse'; + +const templateModules = import.meta.glob('../../data/templates/*.xml', { + query: '?raw', + import: 'default', + eager: true +}); + +export const presets = Object.entries(templateModules).map(([path, xml]) => { + const filename = path.split('/').pop()!.replace('.xml', ''); + return parseTemplate(xml as string, `preset:${filename}`); +}); diff --git a/src/lib/state.svelte.ts b/src/lib/state.svelte.ts new file mode 100644 index 0000000..0b7d615 --- /dev/null +++ b/src/lib/state.svelte.ts @@ -0,0 +1,88 @@ +import { getAllCharacters, saveCharacter, deleteCharacter } from './storage'; +import { isBlankCharacter } from './utils/blank'; +import type { Character, Template } from './types'; + +let characters = $state([]); +let activeId = $state(null); +let saveStatus = $state<'idle' | 'saving' | 'saved'>('idle'); +let saveTimer: ReturnType | null = null; +let statusTimer: ReturnType | null = null; + +export const roster = { + get characters() { return characters; }, + get active() { return characters.find((c) => c.id === activeId) ?? null; }, + get saveStatus() { return saveStatus; }, + + async load() { + const all = await getAllCharacters(); + const kept: Character[] = []; + for (const char of all) { + if (isBlankCharacter(char)) { + await deleteCharacter(char.id); + } else { + kept.push(char); + } + } + characters = kept; + + const stored = localStorage.getItem('activeCharacterId'); + if (stored && characters.some((c) => c.id === stored)) { + activeId = stored; + } else if (characters.length) { + activeId = characters[0].id; + } + }, + + async create(template: Template) { + const char: Character = { + id: crypto.randomUUID(), + template: $state.snapshot(template), + data: {} + }; + characters.push(char); + activeId = char.id; + localStorage.setItem('activeCharacterId', char.id); + await saveCharacter($state.snapshot(char)); + return char; + }, + + async remove(id: string) { + characters = characters.filter((c) => c.id !== id); + await deleteCharacter(id); + if (activeId === id) { + activeId = characters[0]?.id ?? null; + if (activeId) localStorage.setItem('activeCharacterId', activeId); + else localStorage.removeItem('activeCharacterId'); + } + }, + + async duplicate(id: string) { + const source = characters.find((c) => c.id === id); + if (!source) return; + const copy: Character = { + id: crypto.randomUUID(), + template: $state.snapshot(source.template), + data: $state.snapshot(source.data) + }; + characters.push(copy); + activeId = copy.id; + localStorage.setItem('activeCharacterId', copy.id); + await saveCharacter($state.snapshot(copy)); + }, + + setActive(id: string) { + activeId = id; + localStorage.setItem('activeCharacterId', id); + }, + + scheduleSave(char: Character) { + if (saveTimer) clearTimeout(saveTimer); + saveTimer = setTimeout(async () => { + saveStatus = 'saving'; + await saveCharacter($state.snapshot(char)); + saveStatus = 'saved'; + if (statusTimer) clearTimeout(statusTimer); + statusTimer = setTimeout(() => { saveStatus = 'idle'; }, 1500); + }, 300); + } +};