From 9b0cdfdc9b1c81f93444491d37d2b6e1298b26b7 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Wed, 8 Oct 2025 17:31:11 +0100 Subject: [PATCH] feat: load blocks from previous session also remove annoying unsaved changes popup --- package.json | 2 ++ pnpm-lock.yaml | 16 ++++++++++ src/components/canvas/Canvas.tsx | 12 -------- src/components/sidebar/History.tsx | 2 ++ src/context/Canvas.tsx | 48 ++++++++++++++++++++++++++---- 5 files changed, 62 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 8207ca0..a0ff1a5 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "embla-carousel-react": "^8.6.0", "lucide-react": "^0.544.0", "nbtify": "^2.2.0", + "pako": "^2.1.0", "pixi.js": "^8.13.2", "react": "^19.1.1", "react-device-detect": "^2.2.3", @@ -46,6 +47,7 @@ "@eslint/js": "^9.36.0", "@tailwindcss/postcss": "^4.1.14", "@types/node": "^24.6.1", + "@types/pako": "^2.0.4", "@types/react": "^19.1.17", "@types/react-dom": "^19.1.11", "@vitejs/plugin-react": "^5.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8a85ecb..7d5402c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: nbtify: specifier: ^2.2.0 version: 2.2.0 + pako: + specifier: ^2.1.0 + version: 2.1.0 pixi.js: specifier: ^8.13.2 version: 8.13.2 @@ -111,6 +114,9 @@ importers: '@types/node': specifier: ^24.6.1 version: 24.6.1 + '@types/pako': + specifier: ^2.0.4 + version: 2.0.4 '@types/react': specifier: ^19.1.17 version: 19.1.17 @@ -1270,6 +1276,9 @@ packages: '@types/node@24.6.1': resolution: {integrity: sha512-ljvjjs3DNXummeIaooB4cLBKg2U6SPI6Hjra/9rRIy7CpM0HpLtG9HptkMKAb4HYWy5S7HUvJEuWgr/y0U8SHw==} + '@types/pako@2.0.4': + resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + '@types/react-dom@19.1.11': resolution: {integrity: sha512-3BKc/yGdNTYQVVw4idqHtSOcFsgGuBbMveKCOgF8wQ5QtrYOc3jDIlzg3jef04zcXFIHLelyGlj0T+BJ8+KN+w==} peerDependencies: @@ -2090,6 +2099,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + pako@2.1.0: + resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -3479,6 +3491,8 @@ snapshots: dependencies: undici-types: 7.13.0 + '@types/pako@2.0.4': {} + '@types/react-dom@19.1.11(@types/react@19.1.17)': dependencies: '@types/react': 19.1.17 @@ -4347,6 +4361,8 @@ snapshots: dependencies: p-limit: 3.1.0 + pako@2.1.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 diff --git a/src/components/canvas/Canvas.tsx b/src/components/canvas/Canvas.tsx index c51c813..e9248b8 100644 --- a/src/components/canvas/Canvas.tsx +++ b/src/components/canvas/Canvas.tsx @@ -456,18 +456,6 @@ function Canvas() { return () => resizeObserver.disconnect(); }, [loading, setStageSize]); - // Window events handler - useEffect(() => { - const onBeforeUnload = (e: BeforeUnloadEvent) => { - e.preventDefault(); - }; - - window.addEventListener("beforeunload", onBeforeUnload); - return () => { - window.removeEventListener("beforeunload", onBeforeUnload); - }; - }, [onKeyDown, onKeyUp]); - useGesture( { onPinch: ({ offset: [d] }) => { diff --git a/src/components/sidebar/History.tsx b/src/components/sidebar/History.tsx index a9387ce..8ecba7a 100644 --- a/src/components/sidebar/History.tsx +++ b/src/components/sidebar/History.tsx @@ -17,6 +17,7 @@ import { SquareDashedIcon, Trash2Icon, WandIcon, + SaveIcon, } from "lucide-react"; import { HistoryContext } from "@/context/History"; @@ -32,6 +33,7 @@ const iconMap = { "Magic Wand": WandIcon, "Move Selection": MoveIcon, "New Canvas": PresentationIcon, + "Load Previous Session": SaveIcon, "Open Image": ImageIcon, "Open Schematic": FileIcon, "Paint Bucket": PaintBucketIcon, diff --git a/src/context/Canvas.tsx b/src/context/Canvas.tsx index fa1e1fa..6d23cb9 100644 --- a/src/context/Canvas.tsx +++ b/src/context/Canvas.tsx @@ -1,4 +1,5 @@ import React, { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from "react"; +import pako from "pako"; import { HistoryContext } from "./History"; import welcomeBlocksData from "@/data/welcome.json"; @@ -89,14 +90,49 @@ export const CanvasProvider = ({ children }: Props) => { }, [canvasSize, stageSize]); useEffect(() => { - addHistory( - "New Canvas", - () => setBlocks(welcomeBlocksData), - () => setBlocks([]) - ); - // eslint-disable-next-line react-hooks/exhaustive-deps + // 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( + "New Canvas", + () => setBlocks(welcomeBlocksData), + () => setBlocks([]) + ); + } }, []); + // 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 (