feat: load blocks from previous session
also remove annoying unsaved changes popup
This commit is contained in:
parent
7a115e751d
commit
9b0cdfdc9b
5 changed files with 62 additions and 18 deletions
|
|
@ -34,6 +34,7 @@
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"lucide-react": "^0.544.0",
|
"lucide-react": "^0.544.0",
|
||||||
"nbtify": "^2.2.0",
|
"nbtify": "^2.2.0",
|
||||||
|
"pako": "^2.1.0",
|
||||||
"pixi.js": "^8.13.2",
|
"pixi.js": "^8.13.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-device-detect": "^2.2.3",
|
"react-device-detect": "^2.2.3",
|
||||||
|
|
@ -46,6 +47,7 @@
|
||||||
"@eslint/js": "^9.36.0",
|
"@eslint/js": "^9.36.0",
|
||||||
"@tailwindcss/postcss": "^4.1.14",
|
"@tailwindcss/postcss": "^4.1.14",
|
||||||
"@types/node": "^24.6.1",
|
"@types/node": "^24.6.1",
|
||||||
|
"@types/pako": "^2.0.4",
|
||||||
"@types/react": "^19.1.17",
|
"@types/react": "^19.1.17",
|
||||||
"@types/react-dom": "^19.1.11",
|
"@types/react-dom": "^19.1.11",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,9 @@ importers:
|
||||||
nbtify:
|
nbtify:
|
||||||
specifier: ^2.2.0
|
specifier: ^2.2.0
|
||||||
version: 2.2.0
|
version: 2.2.0
|
||||||
|
pako:
|
||||||
|
specifier: ^2.1.0
|
||||||
|
version: 2.1.0
|
||||||
pixi.js:
|
pixi.js:
|
||||||
specifier: ^8.13.2
|
specifier: ^8.13.2
|
||||||
version: 8.13.2
|
version: 8.13.2
|
||||||
|
|
@ -111,6 +114,9 @@ importers:
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^24.6.1
|
specifier: ^24.6.1
|
||||||
version: 24.6.1
|
version: 24.6.1
|
||||||
|
'@types/pako':
|
||||||
|
specifier: ^2.0.4
|
||||||
|
version: 2.0.4
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.1.17
|
specifier: ^19.1.17
|
||||||
version: 19.1.17
|
version: 19.1.17
|
||||||
|
|
@ -1270,6 +1276,9 @@ packages:
|
||||||
'@types/node@24.6.1':
|
'@types/node@24.6.1':
|
||||||
resolution: {integrity: sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==}
|
resolution: {integrity: sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==}
|
||||||
|
|
||||||
|
'@types/pako@2.0.4':
|
||||||
|
resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==}
|
||||||
|
|
||||||
'@types/react-dom@19.1.11':
|
'@types/react-dom@19.1.11':
|
||||||
resolution: {integrity: sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w==}
|
resolution: {integrity: sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
@ -2090,6 +2099,9 @@ packages:
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
pako@2.1.0:
|
||||||
|
resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
@ -3479,6 +3491,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.13.0
|
undici-types: 7.13.0
|
||||||
|
|
||||||
|
'@types/pako@2.0.4': {}
|
||||||
|
|
||||||
'@types/react-dom@19.1.11(@types/react@19.1.17)':
|
'@types/react-dom@19.1.11(@types/react@19.1.17)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 19.1.17
|
'@types/react': 19.1.17
|
||||||
|
|
@ -4347,6 +4361,8 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
|
pako@2.1.0: {}
|
||||||
|
|
||||||
parent-module@1.0.1:
|
parent-module@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
callsites: 3.1.0
|
callsites: 3.1.0
|
||||||
|
|
|
||||||
|
|
@ -456,18 +456,6 @@ function Canvas() {
|
||||||
return () => resizeObserver.disconnect();
|
return () => resizeObserver.disconnect();
|
||||||
}, [loading, setStageSize]);
|
}, [loading, setStageSize]);
|
||||||
|
|
||||||
// Window events handler
|
|
||||||
useEffect(() => {
|
|
||||||
const onBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("beforeunload", onBeforeUnload);
|
|
||||||
return () => {
|
|
||||||
window.removeEventListener("beforeunload", onBeforeUnload);
|
|
||||||
};
|
|
||||||
}, [onKeyDown, onKeyUp]);
|
|
||||||
|
|
||||||
useGesture(
|
useGesture(
|
||||||
{
|
{
|
||||||
onPinch: ({ offset: [d] }) => {
|
onPinch: ({ offset: [d] }) => {
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import {
|
||||||
SquareDashedIcon,
|
SquareDashedIcon,
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
WandIcon,
|
WandIcon,
|
||||||
|
SaveIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { HistoryContext } from "@/context/History";
|
import { HistoryContext } from "@/context/History";
|
||||||
|
|
@ -32,6 +33,7 @@ const iconMap = {
|
||||||
"Magic Wand": WandIcon,
|
"Magic Wand": WandIcon,
|
||||||
"Move Selection": MoveIcon,
|
"Move Selection": MoveIcon,
|
||||||
"New Canvas": PresentationIcon,
|
"New Canvas": PresentationIcon,
|
||||||
|
"Load Previous Session": SaveIcon,
|
||||||
"Open Image": ImageIcon,
|
"Open Image": ImageIcon,
|
||||||
"Open Schematic": FileIcon,
|
"Open Schematic": FileIcon,
|
||||||
"Paint Bucket": PaintBucketIcon,
|
"Paint Bucket": PaintBucketIcon,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
import React, { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||||
|
import pako from "pako";
|
||||||
|
|
||||||
import { HistoryContext } from "./History";
|
import { HistoryContext } from "./History";
|
||||||
import welcomeBlocksData from "@/data/welcome.json";
|
import welcomeBlocksData from "@/data/welcome.json";
|
||||||
|
|
@ -89,14 +90,49 @@ export const CanvasProvider = ({ children }: Props) => {
|
||||||
}, [canvasSize, stageSize]);
|
}, [canvasSize, stageSize]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Load blocks from previous session (if any)
|
||||||
|
const localStorageBlocks = localStorage.getItem("blocks");
|
||||||
|
if (localStorageBlocks) {
|
||||||
|
try {
|
||||||
|
// Convert stored string (Base64) back to bytes
|
||||||
|
const compressedData = Uint8Array.from(atob(localStorageBlocks), (c) => c.charCodeAt(0));
|
||||||
|
const decompressed = pako.inflate(compressedData, { to: "string" });
|
||||||
|
const parsedBlocks = JSON.parse(decompressed);
|
||||||
|
|
||||||
|
setBlocks(parsedBlocks);
|
||||||
|
addHistory(
|
||||||
|
"Load Previous Session",
|
||||||
|
() => setBlocks(parsedBlocks),
|
||||||
|
() => setBlocks(welcomeBlocksData)
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load blocks from localStorage:", err);
|
||||||
|
localStorage.removeItem("blocks");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Add history entry for new canvas
|
||||||
addHistory(
|
addHistory(
|
||||||
"New Canvas",
|
"New Canvas",
|
||||||
() => setBlocks(welcomeBlocksData),
|
() => setBlocks(welcomeBlocksData),
|
||||||
() => setBlocks([])
|
() => setBlocks([])
|
||||||
);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Set blocks to localStorage for session persistence
|
||||||
|
useEffect(() => {
|
||||||
|
// If blocks are the same as the starting welcome blocks, return
|
||||||
|
if (JSON.stringify(blocks) === JSON.stringify(welcomeBlocksData)) return;
|
||||||
|
|
||||||
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode(JSON.stringify(blocks));
|
||||||
|
const compressed = pako.deflate(data);
|
||||||
|
|
||||||
|
// Store compressed data as Base64 string
|
||||||
|
const base64 = btoa(String.fromCharCode(...compressed));
|
||||||
|
localStorage.setItem("blocks", base64);
|
||||||
|
}, [blocks]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CanvasContext.Provider
|
<CanvasContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue