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>
107 lines
3.4 KiB
TypeScript
Executable File
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;
|
|
}
|
|
}
|