Zum Inhalt

src/host-web/persistence.ts

// localStorage-backed settings store for the standalone Web-Host.
//
// Holds a mutable settings object in memory and persists it on
// saveSettings(). The plugin-shim adapts this onto a Plugin-shaped object
// for the engine controller (see plugin-shim.ts).

import type { ScreensaverSettings } from '../engine/data/defaults';
import { DEFAULT_SCREENSAVER } from '../engine/data/defaults';

const STORAGE_KEY = 'kuro-screensaver:settings';
const LEGACY_KEY = 'kuro-animation:settings';   // pre-rename (commit dcf4b7f) — migrated on load/save

export function deepMerge<T extends object>(base: T, overrides: any): T {
  if (!overrides || typeof overrides !== 'object') return base;
  const out: any = Array.isArray(base) ? [...(base as any)] : { ...base };
  for (const k of Object.keys(overrides)) {
    const bv = (base as any)[k];
    const ov = overrides[k];
    if (bv && typeof bv === 'object' && !Array.isArray(bv) && ov && typeof ov === 'object' && !Array.isArray(ov)) {
      out[k] = deepMerge(bv, ov);
    } else {
      out[k] = ov;
    }
  }
  return out as T;
}

function loadFromStorage(): ScreensaverSettings {
  try {
    const raw = localStorage.getItem(STORAGE_KEY) ?? localStorage.getItem(LEGACY_KEY);
    if (!raw) return structuredClone(DEFAULT_SCREENSAVER);
    const parsed = JSON.parse(raw);
    // Merge user overrides over defaults so newly-added settings always get a value.
    return deepMerge(structuredClone(DEFAULT_SCREENSAVER), parsed);
  } catch {
    return structuredClone(DEFAULT_SCREENSAVER);
  }
}

export interface WebHostOptions {
  vaultKanji?: string;        // e.g. '黒' for the HUD
  activePreset?: string;      // for colorMode 'kuro-auto'
  overrides?: Partial<ScreensaverSettings>;  // applied once at startup
}

export class WebHost {
  private settings: ScreensaverSettings;

  constructor(private options: WebHostOptions = {}) {
    this.settings = loadFromStorage();
    if (options.overrides) {
      this.settings = deepMerge(this.settings, options.overrides);
    }
  }

  getSettings(): ScreensaverSettings {
    return this.settings;
  }

  saveSettings(settings: ScreensaverSettings): void {
    this.settings = settings;
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
      localStorage.removeItem(LEGACY_KEY);   // one-time migration off the old project name
    } catch {
      // Quota exceeded or storage disabled — fail silently. Stats are non-critical.
    }
  }

  getActivePreset(): string | undefined {
    return this.options.activePreset;
  }

  getVaultKanji(): string {
    return this.options.vaultKanji ?? '';
  }
}