From ff43b9f4f265bf9fc35d43b786d9220c39747e75 Mon Sep 17 00:00:00 2001 From: rojasadrian012 Date: Fri, 24 Jan 2025 18:45:38 -0300 Subject: [PATCH 1/4] close #662 --- editor.html | 5 -- src/core/providers/canvas/canvas.model.ts | 1 + src/core/providers/canvas/canvas.provider.tsx | 47 ++++++++++++++----- .../components/save-button/save-button.tsx | 9 +++- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/editor.html b/editor.html index 8941296d..5a1cc85c 100644 --- a/editor.html +++ b/editor.html @@ -34,10 +34,5 @@
- diff --git a/src/core/providers/canvas/canvas.model.ts b/src/core/providers/canvas/canvas.model.ts index 606f5e82..2119189a 100644 --- a/src/core/providers/canvas/canvas.model.ts +++ b/src/core/providers/canvas/canvas.model.ts @@ -124,6 +124,7 @@ export interface CanvasContextModel { updateColorSlot: (color: string, index: number) => void; dropRef: React.MutableRefObject; setDropRef: (dropRef: React.MutableRefObject) => void; + setIsDirty: (dirty: boolean) => void; } export const APP_CONSTANTS = { diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 917f37db..7523feb9 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -52,8 +52,28 @@ export const CanvasProvider: React.FC = props => { const selectionInfo = useSelection(document, setDocument); + const [isDirty, setIsDirty] = React.useState(false); + + React.useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (isDirty) { + e.preventDefault(); + } + }; + window.addEventListener('beforeunload', handleBeforeUnload); + return () => window.removeEventListener('beforeunload', handleBeforeUnload); + }, [isDirty]); + + const setDocumentAndMarkDirtyState = ( + updater: DocumentModel | ((prev: DocumentModel) => DocumentModel), + isDirty = true + ) => { + setDocument(updater); + setIsDirty(isDirty); + }; + const addNewPage = () => { - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { const newActiveIndex = draft.pages.length; draft.pages.push({ @@ -75,7 +95,7 @@ export const CanvasProvider: React.FC = props => { } ); - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { const newPage = { id: uuidv4(), @@ -95,7 +115,7 @@ export const CanvasProvider: React.FC = props => { ? document.pages[pageIndex + 1].id // If it's not the last page, select the next one : document.pages[pageIndex - 1].id; // Otherwise, select the previous one - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { draft.pages = draft.pages.filter( currentPage => document.pages[pageIndex].id !== currentPage.id @@ -118,7 +138,7 @@ export const CanvasProvider: React.FC = props => { selectionInfo.clearSelection(); selectionInfo.shapeRefs.current = {}; - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { const pageIndex = draft.pages.findIndex(page => page.id === pageId); if (pageIndex !== -1) { @@ -129,7 +149,7 @@ export const CanvasProvider: React.FC = props => { }; const editPageTitle = (pageIndex: number, newName: string) => { - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { draft.pages[pageIndex].name = newName; }) @@ -137,7 +157,7 @@ export const CanvasProvider: React.FC = props => { }; const swapPages = (id1: string, id2: string) => { - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { const index1 = draft.pages.findIndex(page => page.id === id1); const index2 = draft.pages.findIndex(page => page.id === id2); @@ -157,7 +177,7 @@ export const CanvasProvider: React.FC = props => { }); if (isPageIndexValid(document)) { - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { draft.pages[lastDocument.activePageIndex].shapes.push(...newShapes); }) @@ -196,13 +216,13 @@ export const CanvasProvider: React.FC = props => { ); const createNewFullDocument = () => { - setDocument(createDefaultDocumentModel()); + setDocumentAndMarkDirtyState(createDefaultDocumentModel(), false); setFileName(''); }; const deleteSelectedShapes = () => { if (isPageIndexValid(document)) { - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { draft.pages[lastDocument.activePageIndex].shapes = removeShapesFromList( @@ -227,7 +247,7 @@ export const CanvasProvider: React.FC = props => { const newShape = createShape({ x, y }, type, otherProps); - setDocument(lastDocument => + setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { draft.pages[lastDocument.activePageIndex].shapes.push(newShape); }) @@ -257,7 +277,7 @@ export const CanvasProvider: React.FC = props => { }); }); } else { - setDocument(fullDocument => { + setDocumentAndMarkDirtyState(fullDocument => { return produce(fullDocument, draft => { draft.pages[document.activePageIndex].shapes = draft.pages[ document.activePageIndex @@ -271,7 +291,7 @@ export const CanvasProvider: React.FC = props => { const updateShapePosition = (id: string, { x, y }: Coord) => { if (isPageIndexValid(document)) { - setDocument(fullDocument => { + setDocumentAndMarkDirtyState(fullDocument => { return produce(fullDocument, draft => { draft.pages[document.activePageIndex].shapes = draft.pages[ document.activePageIndex @@ -304,7 +324,7 @@ export const CanvasProvider: React.FC = props => { }; const loadDocument = (document: DocumentModel) => { - setDocument(document); + setDocumentAndMarkDirtyState(document, false); setHowManyLoadedDocuments(numberOfDocuments => numberOfDocuments + 1); setCustomColors(document.customColors); }; @@ -369,6 +389,7 @@ export const CanvasProvider: React.FC = props => { updateColorSlot, dropRef, setDropRef, + setIsDirty, }} > {children} diff --git a/src/pods/toolbar/components/save-button/save-button.tsx b/src/pods/toolbar/components/save-button/save-button.tsx index 61294a91..b1b5d9c5 100644 --- a/src/pods/toolbar/components/save-button/save-button.tsx +++ b/src/pods/toolbar/components/save-button/save-button.tsx @@ -2,13 +2,20 @@ import { SaveIcon } from '@/common/components/icons/save-icon.component'; import classes from '@/pods/toolbar/toolbar.pod.module.css'; import { ToolbarButton } from '../toolbar-button'; import { useLocalDisk } from '@/core/local-disk'; +import { useCanvasContext } from '@/core/providers'; export const SaveButton: React.FC = () => { const { handleSave } = useLocalDisk(); + const { setIsDirty } = useCanvasContext(); + + const handleSaveLocal = () => { + handleSave(); + setIsDirty(false); + }; return ( } label="Save" From d7020e5afbf9a6f98addd2d78ee89a619f9818f3 Mon Sep 17 00:00:00 2001 From: rojasadrian012 Date: Sun, 26 Jan 2025 11:41:11 -0300 Subject: [PATCH 2/4] close #662 --- src/core/providers/canvas/canvas.provider.tsx | 4 ++-- src/core/providers/canvas/use-selection.hook.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index 7523feb9..2176f463 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -50,10 +50,10 @@ export const CanvasProvider: React.FC = props => { addSnapshot ); - const selectionInfo = useSelection(document, setDocument); - const [isDirty, setIsDirty] = React.useState(false); + const selectionInfo = useSelection(document, setDocument, setIsDirty); + React.useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { if (isDirty) { diff --git a/src/core/providers/canvas/use-selection.hook.ts b/src/core/providers/canvas/use-selection.hook.ts index 1ae9c249..83b83bee 100644 --- a/src/core/providers/canvas/use-selection.hook.ts +++ b/src/core/providers/canvas/use-selection.hook.ts @@ -8,7 +8,8 @@ import { produce } from 'immer'; export const useSelection = ( document: DocumentModel, - setDocument: React.Dispatch> + setDocument: React.Dispatch>, + markAsDirtyDocument?: (dirty: boolean) => void ): SelectionInfo => { const transformerRef = useRef(null); const shapeRefs = useRef({}); @@ -225,13 +226,14 @@ export const useSelection = ( if (selectedShapesIds.length === 1) { const selectedShapeId = selectedShapesIds[0]; updateOtherPropsOnSelectedSingleShape(selectedShapeId, key, value); - + markAsDirtyDocument?.(true); return; } // Multiple selection case if (multipleSelection) { updateOtherPropsOnSelectedMutlipleShapes(key, value); + markAsDirtyDocument?.(true); } }; From 5165c91f9f62354ae92b46b26d6f741a0019a93b Mon Sep 17 00:00:00 2001 From: rojasadrian012 Date: Sun, 26 Jan 2025 16:53:23 -0300 Subject: [PATCH 3/4] close #662 --- src/core/providers/canvas/canvas.provider.tsx | 22 +++++++++++-------- .../providers/canvas/use-selection.hook.ts | 13 ++++++----- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/core/providers/canvas/canvas.provider.tsx b/src/core/providers/canvas/canvas.provider.tsx index d10aaac1..d94b9768 100644 --- a/src/core/providers/canvas/canvas.provider.tsx +++ b/src/core/providers/canvas/canvas.provider.tsx @@ -53,7 +53,19 @@ export const CanvasProvider: React.FC = props => { const [isDirty, setIsDirty] = React.useState(false); - const selectionInfo = useSelection(document, setDocument, setIsDirty); + const setDocumentAndMarkDirtyState = ( + updater: DocumentModel | ((prev: DocumentModel) => DocumentModel), + isDirty = true + ) => { + setDocument(updater); + setIsDirty(isDirty); + }; + + const selectionInfo = useSelection( + document, + setDocument, + setDocumentAndMarkDirtyState + ); React.useEffect(() => { const handleBeforeUnload = (e: BeforeUnloadEvent) => { @@ -65,14 +77,6 @@ export const CanvasProvider: React.FC = props => { return () => window.removeEventListener('beforeunload', handleBeforeUnload); }, [isDirty]); - const setDocumentAndMarkDirtyState = ( - updater: DocumentModel | ((prev: DocumentModel) => DocumentModel), - isDirty = true - ) => { - setDocument(updater); - setIsDirty(isDirty); - }; - const addNewPage = () => { setDocumentAndMarkDirtyState(lastDocument => produce(lastDocument, draft => { diff --git a/src/core/providers/canvas/use-selection.hook.ts b/src/core/providers/canvas/use-selection.hook.ts index 83b83bee..995a5a72 100644 --- a/src/core/providers/canvas/use-selection.hook.ts +++ b/src/core/providers/canvas/use-selection.hook.ts @@ -9,7 +9,10 @@ import { produce } from 'immer'; export const useSelection = ( document: DocumentModel, setDocument: React.Dispatch>, - markAsDirtyDocument?: (dirty: boolean) => void + setDocumentAndMarkDirtyState: ( + updater: DocumentModel | ((prev: DocumentModel) => DocumentModel), + isDirty?: boolean + ) => void ): SelectionInfo => { const transformerRef = useRef(null); const shapeRefs = useRef({}); @@ -166,7 +169,7 @@ export const useSelection = ( } const selectedShapeId = selectedShapesIds[0]; - setDocument(prevDocument => + setDocumentAndMarkDirtyState(prevDocument => produce(prevDocument, draft => { draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ prevDocument.activePageIndex @@ -182,7 +185,7 @@ export const useSelection = ( key: K, value: OtherProps[K] ) => { - setDocument(prevDocument => + setDocumentAndMarkDirtyState(prevDocument => produce(prevDocument, draft => { draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ prevDocument.activePageIndex @@ -199,7 +202,7 @@ export const useSelection = ( key: K, value: OtherProps[K] ) => { - setDocument(prevDocument => + setDocumentAndMarkDirtyState(prevDocument => produce(prevDocument, draft => { draft.pages[prevDocument.activePageIndex].shapes = draft.pages[ prevDocument.activePageIndex @@ -226,14 +229,12 @@ export const useSelection = ( if (selectedShapesIds.length === 1) { const selectedShapeId = selectedShapesIds[0]; updateOtherPropsOnSelectedSingleShape(selectedShapeId, key, value); - markAsDirtyDocument?.(true); return; } // Multiple selection case if (multipleSelection) { updateOtherPropsOnSelectedMutlipleShapes(key, value); - markAsDirtyDocument?.(true); } }; From 15e7ea077bc33f949700953158aaf43e9dfff03d Mon Sep 17 00:00:00 2001 From: Leticia de la Osa Date: Tue, 28 Jan 2025 13:36:03 +0100 Subject: [PATCH 4/4] Not available on mobile --- index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 093d0723..d8affb1c 100644 --- a/index.html +++ b/index.html @@ -357,7 +357,8 @@

- Launch QuickMock + START DESIGN +

Now, only available on desktop.