Introduced Refresh-Resistant Session Persistence

This commit is contained in:
2025-05-06 05:27:42 -06:00
parent 1c9873dedd
commit ca1348fcb8

View File

@ -57,12 +57,10 @@ if (!window.BorealisSocket) {
}); });
} }
// Global Node Update Timer Variable
if (!window.BorealisUpdateRate) { if (!window.BorealisUpdateRate) {
window.BorealisUpdateRate = 200; window.BorealisUpdateRate = 200;
} }
// Dynamically load all node components via Vite
const modules = import.meta.glob('./nodes/**/*.jsx', { eager: true }); const modules = import.meta.glob('./nodes/**/*.jsx', { eager: true });
const nodeTypes = {}; const nodeTypes = {};
const categorizedNodes = {}; const categorizedNodes = {};
@ -72,11 +70,8 @@ Object.entries(modules).forEach(([path, mod]) => {
if (!comp) return; if (!comp) return;
const { type, component } = comp; const { type, component } = comp;
if (!type || !component) return; if (!type || !component) return;
// derive category folder name from path: "./nodes/<Category>/File.jsx"
const parts = path.replace('./nodes/', '').split('/'); const parts = path.replace('./nodes/', '').split('/');
const category = parts[0]; const category = parts[0];
if (!categorizedNodes[category]) { if (!categorizedNodes[category]) {
categorizedNodes[category] = []; categorizedNodes[category] = [];
} }
@ -112,6 +107,7 @@ const darkTheme = createTheme({
} }
}); });
const LOCAL_STORAGE_KEY = "borealis_persistent_state";
export default function App() { export default function App() {
const [tabs, setTabs] = useState([ const [tabs, setTabs] = useState([
@ -134,6 +130,29 @@ export default function App() {
const [tabMenuTabId, setTabMenuTabId] = useState(null); const [tabMenuTabId, setTabMenuTabId] = useState(null);
const fileInputRef = useRef(null); const fileInputRef = useRef(null);
useEffect(() => {
const saved = localStorage.getItem(LOCAL_STORAGE_KEY);
if (saved) {
try {
const parsed = JSON.parse(saved);
if (Array.isArray(parsed.tabs) && parsed.activeTabId) {
setTabs(parsed.tabs);
setActiveTabId(parsed.activeTabId);
}
} catch (err) {
console.warn("Failed to parse saved state:", err);
}
}
}, []);
useEffect(() => {
const timeout = setTimeout(() => {
const data = JSON.stringify({ tabs, activeTabId });
localStorage.setItem(LOCAL_STORAGE_KEY, data);
}, 1000);
return () => clearTimeout(timeout);
}, [tabs, activeTabId]);
const handleSetNodes = useCallback( const handleSetNodes = useCallback(
(callbackOrArray, tId) => { (callbackOrArray, tId) => {
const targetId = tId || activeTabId; const targetId = tId || activeTabId;
@ -232,10 +251,8 @@ export default function App() {
setTabs((old) => { setTabs((old) => {
const idx = old.findIndex((t) => t.id === tabMenuTabId); const idx = old.findIndex((t) => t.id === tabMenuTabId);
if (idx === -1) return old; if (idx === -1) return old;
const newList = [...old]; const newList = [...old];
newList.splice(idx, 1); newList.splice(idx, 1);
if (tabMenuTabId === activeTabId && newList.length > 0) { if (tabMenuTabId === activeTabId && newList.length > 0) {
setActiveTabId(newList[0].id); setActiveTabId(newList[0].id);
} else if (newList.length === 0) { } else if (newList.length === 0) {
@ -270,7 +287,6 @@ export default function App() {
const handleExportFlow = async () => { const handleExportFlow = async () => {
const activeTab = tabs.find((x) => x.id === activeTabId); const activeTab = tabs.find((x) => x.id === activeTabId);
if (!activeTab) return; if (!activeTab) return;
const data = JSON.stringify( const data = JSON.stringify(
{ {
nodes: activeTab.nodes, nodes: activeTab.nodes,
@ -283,7 +299,6 @@ export default function App() {
const blob = new Blob([data], { type: "application/json" }); const blob = new Blob([data], { type: "application/json" });
const sanitizedTabName = activeTab.tab_name.replace(/\s+/g, "_").toLowerCase(); const sanitizedTabName = activeTab.tab_name.replace(/\s+/g, "_").toLowerCase();
const suggestedFilename = sanitizedTabName + "_workflow.json"; const suggestedFilename = sanitizedTabName + "_workflow.json";
if (window.showSaveFilePicker) { if (window.showSaveFilePicker) {
try { try {
const fileHandle = await window.showSaveFilePicker({ const fileHandle = await window.showSaveFilePicker({
@ -295,7 +310,6 @@ export default function App() {
} }
] ]
}); });
const writable = await fileHandle.createWritable(); const writable = await fileHandle.createWritable();
await writable.write(blob); await writable.write(blob);
await writable.close(); await writable.close();
@ -328,7 +342,6 @@ export default function App() {
const file = await fileHandle.getFile(); const file = await fileHandle.getFile();
const text = await file.text(); const text = await file.text();
const json = JSON.parse(text); const json = JSON.parse(text);
const newId = "flow_" + (tabs.length + 1); const newId = "flow_" + (tabs.length + 1);
setTabs((prev) => [ setTabs((prev) => [
...prev, ...prev,
@ -354,7 +367,6 @@ export default function App() {
try { try {
const text = await file.text(); const text = await file.text();
const json = JSON.parse(text); const json = JSON.parse(text);
const newId = "flow_" + (tabs.length + 1); const newId = "flow_" + (tabs.length + 1);
setTabs((prev) => [ setTabs((prev) => [
...prev, ...prev,
@ -374,7 +386,6 @@ export default function App() {
return ( return (
<ThemeProvider theme={darkTheme}> <ThemeProvider theme={darkTheme}>
<CssBaseline /> <CssBaseline />
<Box sx={{ width: "100vw", height: "100vh", display: "flex", flexDirection: "column", overflow: "hidden" }}> <Box sx={{ width: "100vw", height: "100vh", display: "flex", flexDirection: "column", overflow: "hidden" }}>
<AppBar position="static" sx={{ bgcolor: "#16191d" }}> <AppBar position="static" sx={{ bgcolor: "#16191d" }}>
<Toolbar sx={{ minHeight: "36px" }}> <Toolbar sx={{ minHeight: "36px" }}>
@ -399,7 +410,6 @@ export default function App() {
</Menu> </Menu>
</Toolbar> </Toolbar>
</AppBar> </AppBar>
<Box sx={{ display: "flex", flexGrow: 1, overflow: "hidden" }}> <Box sx={{ display: "flex", flexGrow: 1, overflow: "hidden" }}>
<NodeSidebar <NodeSidebar
categorizedNodes={categorizedNodes} categorizedNodes={categorizedNodes}
@ -409,7 +419,6 @@ export default function App() {
fileInputRef={fileInputRef} fileInputRef={fileInputRef}
onFileInputChange={handleFileInputChange} onFileInputChange={handleFileInputChange}
/> />
<Box sx={{ display: "flex", flexDirection: "column", flexGrow: 1, overflow: "hidden" }}> <Box sx={{ display: "flex", flexDirection: "column", flexGrow: 1, overflow: "hidden" }}>
<FlowTabs <FlowTabs
tabs={tabs} tabs={tabs}
@ -418,7 +427,6 @@ export default function App() {
onAddTab={createNewTab} onAddTab={createNewTab}
onTabRightClick={handleTabRightClick} onTabRightClick={handleTabRightClick}
/> />
<Box sx={{ flexGrow: 1, position: "relative" }}> <Box sx={{ flexGrow: 1, position: "relative" }}>
{tabs.map((tab) => ( {tabs.map((tab) => (
<Box <Box
@ -434,7 +442,7 @@ export default function App() {
> >
<ReactFlowProvider id={tab.id}> <ReactFlowProvider id={tab.id}>
<FlowEditor <FlowEditor
flowId={tab.id} //Used to Fix Grid Issues Across Multiple Flow Tabs flowId={tab.id}
nodes={tab.nodes} nodes={tab.nodes}
edges={tab.edges} edges={tab.edges}
setNodes={(val) => handleSetNodes(val, tab.id)} setNodes={(val) => handleSetNodes(val, tab.id)}
@ -448,10 +456,8 @@ export default function App() {
</Box> </Box>
</Box> </Box>
</Box> </Box>
<StatusBar /> <StatusBar />
</Box> </Box>
<CloseAllDialog <CloseAllDialog
open={confirmCloseOpen} open={confirmCloseOpen}
onClose={handleCloseDialog} onClose={handleCloseDialog}