Files
layonara-forge/packages/frontend/src/hooks/useEditorState.ts
T

129 lines
3.1 KiB
TypeScript

import { useState, useCallback, useEffect, useRef } from "react";
const STORAGE_KEY = "forge-editor-state";
interface PersistedState {
openTabs: string[];
activeTab: string | null;
}
function loadPersistedState(): PersistedState {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
return {
openTabs: Array.isArray(parsed.openTabs) ? parsed.openTabs : [],
activeTab:
typeof parsed.activeTab === "string" ? parsed.activeTab : null,
};
}
} catch {
// corrupted storage — start fresh
}
return { openTabs: [], activeTab: null };
}
function persistState(state: PersistedState) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} catch {
// quota exceeded — silently ignore
}
}
export function useEditorState() {
const [openTabs, setOpenTabs] = useState<string[]>(
() => loadPersistedState().openTabs,
);
const [activeTab, setActiveTab] = useState<string | null>(
() => loadPersistedState().activeTab,
);
const [dirtyFiles, setDirtyFiles] = useState<Set<string>>(new Set());
const fileContents = useRef<Map<string, string>>(new Map());
useEffect(() => {
persistState({ openTabs, activeTab });
}, [openTabs, activeTab]);
const openFile = useCallback(
(path: string, content?: string) => {
if (content !== undefined) {
fileContents.current.set(path, content);
}
setOpenTabs((prev) => {
if (prev.includes(path)) return prev;
return [...prev, path];
});
setActiveTab(path);
},
[],
);
const closeFile = useCallback(
(path: string) => {
setOpenTabs((prev) => {
const next = prev.filter((p) => p !== path);
setActiveTab((current) => {
if (current !== path) return current;
const idx = prev.indexOf(path);
return next[Math.min(idx, next.length - 1)] ?? null;
});
return next;
});
setDirtyFiles((prev) => {
const next = new Set(prev);
next.delete(path);
return next;
});
fileContents.current.delete(path);
},
[],
);
const selectTab = useCallback((path: string) => {
setActiveTab(path);
}, []);
const markDirty = useCallback((path: string) => {
setDirtyFiles((prev) => {
if (prev.has(path)) return prev;
return new Set(prev).add(path);
});
}, []);
const markClean = useCallback((path: string) => {
setDirtyFiles((prev) => {
if (!prev.has(path)) return prev;
const next = new Set(prev);
next.delete(path);
return next;
});
}, []);
const updateContent = useCallback(
(path: string, content: string) => {
fileContents.current.set(path, content);
markDirty(path);
},
[markDirty],
);
const getContent = useCallback((path: string): string | undefined => {
return fileContents.current.get(path);
}, []);
return {
openTabs,
activeTab,
dirtyFiles,
openFile,
closeFile,
selectTab,
markDirty,
markClean,
updateContent,
getContent,
};
}