mirror of
https://github.com/bunny-lab-io/Borealis.git
synced 2025-07-27 03:48:29 -06:00
Introduced Refresh-Resistant Session Persistence
This commit is contained in:
@ -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}
|
||||||
|
Reference in New Issue
Block a user