161 lines
5.2 KiB
TypeScript

import { writable } from "svelte/store";
import { get } from "svelte/store";
import { invoke } from "$lib/runtime/invoke";
const defaultTags = ["Personal", "Work", "Ideas", "Journal"];
const defaultFragmentTypes = ["Quote", "Snippet", "Reference"];
const startupViews = ["entries", "calendar", "fragments", "todos", "lists"] as const;
const defaultStartupView = "entries";
export type StartupView = typeof startupViews[number];
export const settingsTags = writable<string[]>([...defaultTags]);
export const settingsFragmentTypes = writable<string[]>([...defaultFragmentTypes]);
export const settingsDefaultStartupView = writable<StartupView>(defaultStartupView);
let hydrationComplete = false;
let hydrating = false;
type UiSettingsPayload = {
tags?: string[];
fragmentTypes?: string[];
defaultStartupView?: string;
};
function normalize(value: string): string {
return value.trim().toLowerCase();
}
function hasDuplicate(values: string[], candidate: string, excludeIndex?: number): boolean {
const normalized = normalize(candidate);
return values.some((value, index) => index !== excludeIndex && normalize(value) === normalized);
}
function normalizeValues(values: string[], fallback: string[]): string[] {
const seen = new Set<string>();
const result: string[] = [];
for (const value of values) {
const trimmed = value.trim();
if (!trimmed) continue;
const key = normalize(trimmed);
if (seen.has(key)) continue;
seen.add(key);
result.push(trimmed);
}
return result.length ? result : [...fallback];
}
function normalizeStartupView(value: string | undefined): StartupView {
const normalized = (value ?? "").trim().toLowerCase();
if (startupViews.includes(normalized as StartupView)) {
return normalized as StartupView;
}
return defaultStartupView;
}
export async function hydrateUiSettings(): Promise<void> {
if (hydrating || hydrationComplete) return;
hydrating = true;
try {
const payload = await invoke<UiSettingsPayload>("get_ui_settings");
settingsTags.set(normalizeValues(payload.tags ?? [], defaultTags));
settingsFragmentTypes.set(normalizeValues(payload.fragmentTypes ?? [], defaultFragmentTypes));
settingsDefaultStartupView.set(normalizeStartupView(payload.defaultStartupView));
} catch (error) {
console.error("[settings] hydrate failed", error);
} finally {
hydrating = false;
hydrationComplete = true;
}
}
export async function persistUiSettings(): Promise<void> {
const tags = normalizeValues(get(settingsTags), defaultTags);
const fragmentTypes = normalizeValues(get(settingsFragmentTypes), defaultFragmentTypes);
const startupView = normalizeStartupView(get(settingsDefaultStartupView));
settingsTags.set(tags);
settingsFragmentTypes.set(fragmentTypes);
settingsDefaultStartupView.set(startupView);
await invoke("set_ui_settings", {
tags,
fragmentTypes,
fragment_types: fragmentTypes,
defaultStartupView: startupView,
default_startup_view: startupView
});
}
function queuePersist(): void {
if (!hydrationComplete || hydrating) return;
void persistUiSettings().catch((error) => {
console.error("[settings] persist failed", error);
});
}
export function addSettingsTag(value: string): boolean {
const next = value.trim();
if (!next) return false;
const tags = get(settingsTags);
if (hasDuplicate(tags, next)) return false;
settingsTags.set([...tags, next]);
queuePersist();
return true;
}
export function updateSettingsTag(index: number, value: string): boolean {
const next = value.trim();
if (!next) return false;
const tags = get(settingsTags);
if (index < 0 || index >= tags.length) return false;
if (hasDuplicate(tags, next, index)) return false;
settingsTags.set(tags.map((tag, idx) => (idx === index ? next : tag)));
queuePersist();
return true;
}
export function removeSettingsTag(index: number): boolean {
const tags = get(settingsTags);
if (index < 0 || index >= tags.length) return false;
settingsTags.set(tags.filter((_, idx) => idx !== index));
queuePersist();
return true;
}
export function addFragmentType(value: string): boolean {
const next = value.trim();
if (!next) return false;
const types = get(settingsFragmentTypes);
if (hasDuplicate(types, next)) return false;
settingsFragmentTypes.set([...types, next]);
queuePersist();
return true;
}
export function updateFragmentType(index: number, value: string): boolean {
const next = value.trim();
if (!next) return false;
const types = get(settingsFragmentTypes);
if (index < 0 || index >= types.length) return false;
if (hasDuplicate(types, next, index)) return false;
settingsFragmentTypes.set(types.map((type, idx) => (idx === index ? next : type)));
queuePersist();
return true;
}
export function removeFragmentType(index: number): boolean {
const types = get(settingsFragmentTypes);
if (index < 0 || index >= types.length) return false;
settingsFragmentTypes.set(types.filter((_, idx) => idx !== index));
queuePersist();
return true;
}
export function setDefaultStartupView(value: string): boolean {
const next = normalizeStartupView(value);
if (get(settingsDefaultStartupView) === next) return false;
settingsDefaultStartupView.set(next);
queuePersist();
return true;
}