feat(editor): preserve in-progress workspace across a login redirect
The Sign-in links navigate with a full page load (they mount in a separate React root without Router context), which wipes the in-memory Zustand workspace. New utils/workspaceDraft stashes the whole workspace (reusing the lossless .vlx serialisation) to sessionStorage before that navigation and restores it once when the editor remounts after login — so a user who was building a circuit and signs in lands back on their work instead of the empty starter board. Strictly scoped to the login round-trip by a one-shot restore flag (not a general autosave), and skipped when a named project is already loaded so it never clobbers one. EditorPage calls restoreStashedWorkspace() on mount; the pro overlay's auth links call stashWorkspaceForAuth() before navigating.
This commit is contained in:
parent
9826737831
commit
108d330efd
|
|
@ -6,6 +6,7 @@ import React, { useRef, useState, useCallback, useEffect, lazy, Suspense } from
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { startSimulation } from '../simulation/spice/start';
|
||||
import { useSEO } from '../utils/useSEO';
|
||||
import { restoreStashedWorkspace } from '../utils/workspaceDraft';
|
||||
import { CodeEditor } from '../components/editor/CodeEditor';
|
||||
import { EditorToolbar } from '../components/editor/EditorToolbar';
|
||||
import { FileExplorer } from '../components/editor/FileExplorer';
|
||||
|
|
@ -100,6 +101,13 @@ export const EditorPage: React.FC = () => {
|
|||
return startSimulation();
|
||||
}, []);
|
||||
|
||||
// Restore an in-progress workspace stashed before a login redirect, so a
|
||||
// user who was building something and signed in lands back on their circuit
|
||||
// (not the empty starter board). One-shot; see utils/workspaceDraft.
|
||||
useEffect(() => {
|
||||
restoreStashedWorkspace();
|
||||
}, []);
|
||||
|
||||
// ── GitHub star prompt (show twice at most: 2nd visit OR after 3 min) ──────
|
||||
// Three localStorage flags drive this:
|
||||
// velxio_star_prompted → dismissed the first ask
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Workspace draft — keep an anonymous user's in-progress circuit + code across
|
||||
* a full-page navigation to /login and back.
|
||||
*
|
||||
* The Sign-in links mount in a separate React root without Router context, so
|
||||
* they navigate with `window.location.assign` (full page load), which wipes
|
||||
* the in-memory Zustand stores. Before that navigation we stash the whole
|
||||
* workspace (reusing the lossless `.vlx` serialisation) into sessionStorage;
|
||||
* when the editor remounts after login it restores the stash. sessionStorage
|
||||
* is same-tab and survives the reload, and is discarded when the tab closes.
|
||||
*
|
||||
* Scoped strictly to the login round-trip via a one-shot restore flag — it is
|
||||
* NOT a general autosave, so a normal reload never resurrects an old draft.
|
||||
*/
|
||||
|
||||
import type { BoardInstance } from '../types/board';
|
||||
import { useProjectStore } from '../store/useProjectStore';
|
||||
import { useSimulatorStore } from '../store/useSimulatorStore';
|
||||
import { buildVlxPayload } from './vlxFile';
|
||||
|
||||
const DRAFT_KEY = 'velxio_ws_draft';
|
||||
const RESTORE_FLAG = 'velxio_ws_restore';
|
||||
|
||||
/** True when the canvas holds something worth preserving (a real build, not
|
||||
* just the empty starter board). */
|
||||
export function workspaceHasWork(): boolean {
|
||||
const sim = useSimulatorStore.getState();
|
||||
return sim.components.length > 0 || sim.wires.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Snapshot the current workspace and mark it for restore on the next editor
|
||||
* mount. Call right before a full-page navigation to /login. No-op when there
|
||||
* is nothing worth keeping or storage is unavailable.
|
||||
*/
|
||||
export function stashWorkspaceForAuth(): void {
|
||||
try {
|
||||
if (!workspaceHasWork()) return;
|
||||
sessionStorage.setItem(DRAFT_KEY, JSON.stringify(buildVlxPayload()));
|
||||
sessionStorage.setItem(RESTORE_FLAG, '1');
|
||||
} catch {
|
||||
// storage full / disabled — degrade to losing the draft, never throw.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a stash is pending (set before the login redirect), load it into the
|
||||
* stores and clear it. Runs once on editor mount. Skips when a named project
|
||||
* is already loaded so it never clobbers one. Returns whether it restored.
|
||||
*/
|
||||
export function restoreStashedWorkspace(): boolean {
|
||||
let raw: string | null = null;
|
||||
try {
|
||||
if (sessionStorage.getItem(RESTORE_FLAG) !== '1') return false;
|
||||
sessionStorage.removeItem(RESTORE_FLAG);
|
||||
raw = sessionStorage.getItem(DRAFT_KEY);
|
||||
sessionStorage.removeItem(DRAFT_KEY);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
if (!raw) return false;
|
||||
if (useProjectStore.getState().currentProject) return false;
|
||||
try {
|
||||
const payload = JSON.parse(raw);
|
||||
useSimulatorStore.getState().loadProjectState({
|
||||
boards: payload.boards as unknown as BoardInstance[],
|
||||
fileGroups: payload.fileGroups,
|
||||
components: payload.components,
|
||||
wires: payload.wires,
|
||||
activeBoardId: payload.activeBoardId,
|
||||
});
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue