dd-timer/src/infrastructure/persistence/LocalStorageRepository.ts
POL Mickaël 3e485fd09b chore: normalise fins de ligne CRLF → LF dans tout le repo
Applique .gitattributes sur tous les fichiers existants.
Élimine les différences fantômes entre WSL et Windows.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 08:55:10 +02:00

107 lines
3.4 KiB
TypeScript
Executable File

import type { AppState, StateRepository } from '@domain/ports/StateRepository';
import { DEFAULT_TARGETS } from '@domain/value-objects/GaugeType';
interface ElectronAPI {
saveData: (json: string) => void;
loadData: () => Promise<string | null>;
}
function getElectronAPI(): ElectronAPI | null {
if (typeof window !== 'undefined' && (window as any).electronAPI) {
return (window as any).electronAPI;
}
return null;
}
export class LocalStorageRepository implements StateRepository {
private readonly storageKey = 'dd3v3';
async load(): Promise<AppState | null> {
try {
let raw: string | null = null;
const api = getElectronAPI();
if (api) raw = await api.loadData();
if (!raw && typeof localStorage !== 'undefined') {
raw = localStorage.getItem(this.storageKey) || localStorage.getItem('dd3v2');
}
if (!raw) return null;
return this.deserialize(raw);
} catch {
return null;
}
}
save(state: AppState): void {
try {
const d = JSON.parse(JSON.stringify(state));
// Reset runtime state before persisting
d.enclos.forEach((e: any) => {
e.timer.running = false;
e.alerted = {};
});
const json = JSON.stringify(d);
const api = getElectronAPI();
if (api) api.saveData(json);
if (typeof localStorage !== 'undefined') {
localStorage.setItem(this.storageKey, json);
}
} catch (e) {
console.error('Erreur sauvegarde:', (e as Error).message);
}
}
private deserialize(raw: string): AppState {
const d = JSON.parse(raw);
const state: AppState = {
enclos: d.enclos || [],
activeId: d.activeId ?? null,
nextEnclosId: d.nextEnclosId || 1,
alarmSound: d.alarmSound || 'arpege',
notifsEnabled: d.notifsEnabled !== undefined ? d.notifsEnabled : true,
ntfyTopic: d.ntfyTopic || '',
archivedStats: d.archivedStats || [],
inventaire: d.inventaire || {},
workflows: d.workflows || [],
accouplements: d.accouplements || [],
};
// Migration: old ntfyUrl format → ntfyTopic
if (!state.ntfyTopic && d.ntfyUrl) {
const m = d.ntfyUrl.match(/\/([^\/]+)$/);
if (m) state.ntfyTopic = m[1];
}
// Migrate enclos data
state.enclos.forEach((enc: any) => {
enc.timer = enc.timer || { running: false, startTime: null, pausedAt: null, pausedMs: 0, snapGauges: {}, snapStats: {} };
enc.timer.running = false;
enc.alerted = {};
if (enc.gaugeLevels.mangeoire === undefined) enc.gaugeLevels.mangeoire = 0;
enc.dragodindes.forEach((dd: any) => {
if (dd.stats.xp === undefined) dd.stats.xp = 1;
// Migration: old serenite target → gauge-based targets
if (dd.targets.serenite !== undefined && dd.targets.baffeur === undefined) {
const old = { ...dd.targets };
dd.targets = {
baffeur: old.serenite ?? -5000,
caresseur: Math.max(0, old.serenite ?? 40),
foudroyeur: old.endurance ?? 20000,
abreuvoir: old.maturite ?? 20000,
dragofesse: old.amour ?? 20000,
mangeoire: 100,
};
}
Object.keys(DEFAULT_TARGETS).forEach(k => {
if (dd.targets[k] === undefined) dd.targets[k] = (DEFAULT_TARGETS as any)[k];
});
});
});
if (!state.activeId && state.enclos.length) {
state.activeId = state.enclos[0].id;
}
return state;
}
}