161 lines
5.2 KiB
TypeScript
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;
|
|
}
|
|
|