From b589ef886b6a40f3f129833a1cbc732e0d36241a Mon Sep 17 00:00:00 2001 From: hayzedd2 Date: Wed, 29 Apr 2026 10:31:23 +0100 Subject: [PATCH 1/2] feat: Add "clear canvas" functionality --- frontend/package-lock.json | 48 +++++++++---------- .../src/components/editor-clear-canvas.tsx | 36 ++++++++++++++ frontend/src/components/scene-editor.tsx | 10 +++- frontend/src/routes/create.tsx | 2 + 4 files changed, 70 insertions(+), 26 deletions(-) create mode 100644 frontend/src/components/editor-clear-canvas.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4132a6e..f6143ec 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -140,7 +140,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -508,7 +507,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -557,11 +555,31 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -1141,7 +1159,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz", "integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -2130,8 +2147,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tailwindcss/node": { "version": "4.2.2", @@ -2609,7 +2625,6 @@ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.168.22.tgz", "integrity": "sha512-W2LyfkfJtDCf//jOjZeUBWwOVl8iDRVTECpGHa2M28MT3T5/VVnjgicYNHR/ax0Filk1iU67MRjcjHheTYvK1Q==", "license": "MIT", - "peer": true, "dependencies": { "@tanstack/history": "1.161.6", "@tanstack/react-store": "^0.9.3", @@ -2820,7 +2835,6 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2982,7 +2996,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -3002,7 +3015,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -3013,7 +3025,6 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -3497,7 +3508,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4173,7 +4183,6 @@ "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -4791,7 +4800,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -6927,8 +6935,7 @@ "url": "https://github.com/sponsors/sxzz" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/query-selector-shadow-dom": { "version": "1.0.1", @@ -6981,7 +6988,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -6991,7 +6997,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -7428,7 +7433,6 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.2.tgz", "integrity": "sha512-xcRN39BdsnO9Tf+VzsE7b3JyTJASItIV1FVFewJKCFcW4s4haIKS3e6vj8PGB9qBwC7tnuOywQMdv5N4qkzi7Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -7742,8 +7746,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.2", @@ -8026,7 +8029,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8268,7 +8270,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -8545,7 +8546,6 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/frontend/src/components/editor-clear-canvas.tsx b/frontend/src/components/editor-clear-canvas.tsx new file mode 100644 index 0000000..6d4028c --- /dev/null +++ b/frontend/src/components/editor-clear-canvas.tsx @@ -0,0 +1,36 @@ +import { RefreshIcon } from "@hugeicons/core-free-icons" +import { HugeiconsIcon } from "@hugeicons/react" + +// similar class as the adjacent export button next to it +const triggerClass = [ + "inline-flex h-9 shrink-0 items-center justify-center gap-1.5 rounded-full border border-black/[0.08] px-4 text-sm font-medium sm:h-10 sm:px-5", + "bg-gradient-to-br from-[#fafaf9] via-[#f2f0f3] to-[#ebe7f3]", + "text-[var(--text)] shadow-[0_1px_2px_rgba(0,0,0,0.04)]", + "outline-none transition-[background,box-shadow,filter] duration-200", + "hover:from-[#f5f4f2] hover:via-[#eceaf1] hover:to-[#e5e0f2] hover:shadow-[0_2px_8px_rgba(0,0,0,0.06)]", + "focus-visible:ring-2 focus-visible:ring-[var(--accent)]/50 focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--surface)]", + "disabled:pointer-events-none disabled:opacity-40", +].join(" "); + +type Props = { + onClearCanvas?: () => void; +}; + +export default function EditorClearCanvas({ onClearCanvas }: Props) { + return ( + + ) +} \ No newline at end of file diff --git a/frontend/src/components/scene-editor.tsx b/frontend/src/components/scene-editor.tsx index 0612ccb..5935947 100644 --- a/frontend/src/components/scene-editor.tsx +++ b/frontend/src/components/scene-editor.tsx @@ -150,6 +150,7 @@ export type SceneEditorHandle = { exportImage: (opts?: ExportImageOptions) => void saveDocument: () => void loadDocument: (file: File) => Promise + clearCanvas: () => void } type SceneEditorProps = { @@ -1290,10 +1291,15 @@ const SceneEditor = forwardRef( [doc, vectorBoardDocs], ) + const clearCanvas = useCallback(() => { + setDoc((prev) => ({ ...prev, objects: [] })) + setSelectedIds([]) + }, [setDoc, setSelectedIds]) + useImperativeHandle( ref, - () => ({ exportImage, saveDocument, loadDocument }), - [exportImage, saveDocument, loadDocument], + () => ({ exportImage, saveDocument, loadDocument, clearCanvas }), + [exportImage, saveDocument, loadDocument, clearCanvas], ) const onZoomSliderChange = useCallback((pct: number) => { diff --git a/frontend/src/routes/create.tsx b/frontend/src/routes/create.tsx index 57ce5c8..e81f4b2 100644 --- a/frontend/src/routes/create.tsx +++ b/frontend/src/routes/create.tsx @@ -14,6 +14,7 @@ import { idbMigrateLegacyDocument, idbSetDocumentName, } from "../lib/avnac-editor-idb"; +import EditorClearCanvas from "#/components/editor-clear-canvas"; type CreateSearch = { id?: string; @@ -183,6 +184,7 @@ function CreatePage() { />
+ editorRef.current?.clearCanvas()} /> editorRef.current?.exportImage(opts)} From 8062d7624b26e667e0bc71afcd14292e73f7e1f0 Mon Sep 17 00:00:00 2001 From: hayzedd2 Date: Wed, 29 Apr 2026 11:00:25 +0100 Subject: [PATCH 2/2] fix: break out of active edit mode when clearing canvas --- frontend/src/components/scene-editor.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/scene-editor.tsx b/frontend/src/components/scene-editor.tsx index 5935947..bb97dd2 100644 --- a/frontend/src/components/scene-editor.tsx +++ b/frontend/src/components/scene-editor.tsx @@ -1294,6 +1294,7 @@ const SceneEditor = forwardRef( const clearCanvas = useCallback(() => { setDoc((prev) => ({ ...prev, objects: [] })) setSelectedIds([]) + setTextEditingId(null) }, [setDoc, setSelectedIds]) useImperativeHandle(