diff --git a/packages/editor-sample/src/App/TemplatePanel/Redo/index.tsx b/packages/editor-sample/src/App/TemplatePanel/Redo/index.tsx new file mode 100644 index 0000000..2471911 --- /dev/null +++ b/packages/editor-sample/src/App/TemplatePanel/Redo/index.tsx @@ -0,0 +1,33 @@ +import React, { useEffect } from 'react'; + +import { RedoOutlined as RedoIcon } from '@mui/icons-material'; +import { IconButton, Tooltip } from '@mui/material'; + +import { redo, useCanRedo } from '../../../documents/editor/EditorContext'; + +export default function Redo() { + const canRedo = useCanRedo(); + useEffect(() => { + function handle(event: KeyboardEvent) { + if (event.ctrlKey && event.key.toLowerCase() === 'y') { + event.preventDefault(); + redo(); + } + } + + document.addEventListener('keydown', handle); + return () => document.removeEventListener('keydown', handle); + }, []); + + return ( + <> + + + + + + + + + ); +} \ No newline at end of file diff --git a/packages/editor-sample/src/App/TemplatePanel/Undo/index.tsx b/packages/editor-sample/src/App/TemplatePanel/Undo/index.tsx new file mode 100644 index 0000000..8f2eed6 --- /dev/null +++ b/packages/editor-sample/src/App/TemplatePanel/Undo/index.tsx @@ -0,0 +1,33 @@ +import React, { useEffect } from 'react'; + +import { UndoOutlined as UndoIcon } from '@mui/icons-material'; +import { IconButton, Tooltip } from '@mui/material'; + +import { undo, useCanUndo } from '../../../documents/editor/EditorContext'; + +export default function Undo() { + const canUndo = useCanUndo(); + useEffect(() => { + function handle(event: KeyboardEvent) { + if (event.ctrlKey && event.key.toLowerCase() === 'z') { + event.preventDefault(); + undo(); + } + } + + document.addEventListener('keydown', handle); + return () => document.removeEventListener('keydown', handle); + }, []); + + return ( + <> + + + + + + + + + ); +} \ No newline at end of file diff --git a/packages/editor-sample/src/App/TemplatePanel/index.tsx b/packages/editor-sample/src/App/TemplatePanel/index.tsx index 0340cad..54c08f8 100644 --- a/packages/editor-sample/src/App/TemplatePanel/index.tsx +++ b/packages/editor-sample/src/App/TemplatePanel/index.tsx @@ -20,6 +20,8 @@ import ImportJson from './ImportJson'; import JsonPanel from './JsonPanel'; import MainTabsGroup from './MainTabsGroup'; import ShareButton from './ShareButton'; +import Undo from './Undo'; +import Redo from './Redo'; export default function TemplatePanel() { const document = useDocument(); @@ -97,6 +99,8 @@ export default function TemplatePanel() { + + diff --git a/packages/editor-sample/src/documents/editor/EditorContext.tsx b/packages/editor-sample/src/documents/editor/EditorContext.tsx index 0ac60e6..e2e02d2 100644 --- a/packages/editor-sample/src/documents/editor/EditorContext.tsx +++ b/packages/editor-sample/src/documents/editor/EditorContext.tsx @@ -1,5 +1,5 @@ import { create } from 'zustand'; - +import debounce from '@mui/material/utils/debounce'; import getConfiguration from '../../getConfiguration'; import { TEditorConfiguration } from './core'; @@ -7,6 +7,9 @@ import { TEditorConfiguration } from './core'; type TValue = { document: TEditorConfiguration; + documentHistory: Array, + documentHistoryIndex: number, + selectedBlockId: string | null; selectedSidebarTab: 'block-configuration' | 'styles'; selectedMainTab: 'editor' | 'preview' | 'json' | 'html'; @@ -18,6 +21,8 @@ type TValue = { const editorStateStore = create(() => ({ document: getConfiguration(window.location.hash), + documentHistory: [], + documentHistoryIndex: 0, selectedBlockId: null, selectedSidebarTab: 'styles', selectedMainTab: 'editor', @@ -76,7 +81,25 @@ export function setSidebarTab(selectedSidebarTab: TValue['selectedSidebarTab']) return editorStateStore.setState({ selectedSidebarTab }); } +const addDocumentToHistory = debounce(function (document: TValue['document']) { + let documentHistory = editorStateStore.getState().documentHistory; + const currentIndex = editorStateStore.getState().documentHistoryIndex; + if (currentIndex < documentHistory.length - 1) { + documentHistory = documentHistory.slice(0, currentIndex + 1); + } + documentHistory.push(document); + if (documentHistory.length > 254) { + documentHistory = documentHistory.slice(1); + } + + editorStateStore.setState({ + documentHistory, + documentHistoryIndex: documentHistory.length - 1, + }); +}, 500); + export function resetDocument(document: TValue['document']) { + addDocumentToHistory(document); return editorStateStore.setState({ document, selectedSidebarTab: 'styles', @@ -86,11 +109,47 @@ export function resetDocument(document: TValue['document']) { export function setDocument(document: TValue['document']) { const originalDocument = editorStateStore.getState().document; + const mergedDocument = { + ...originalDocument, + ...document, + }; + addDocumentToHistory(mergedDocument); return editorStateStore.setState({ - document: { - ...originalDocument, - ...document, - }, + document: mergedDocument, + }); +} + +export function useCanUndo() { + return editorStateStore(s => s.documentHistory.length > 0 && s.documentHistoryIndex > 0); +} + +export function useCanRedo() { + return editorStateStore(s => s.documentHistory.length > 0 && s.documentHistoryIndex < s.documentHistory.length - 1); +} + +export function undo() { + const documentHistory = editorStateStore.getState().documentHistory; + const currentIndex = editorStateStore.getState().documentHistoryIndex; + if (documentHistory.length <= 0 || currentIndex <= 0) { + return; + } + const changeToIndex = currentIndex - 1; + editorStateStore.setState({ + document: documentHistory[changeToIndex], + documentHistoryIndex: changeToIndex, + }); +} + +export function redo() { + const documentHistory = editorStateStore.getState().documentHistory; + const currentIndex = editorStateStore.getState().documentHistoryIndex; + if (documentHistory.length <= 0 || currentIndex >= documentHistory.length - 1) { + return; + } + const changeToIndex = currentIndex + 1; + editorStateStore.setState({ + document: documentHistory[changeToIndex], + documentHistoryIndex: changeToIndex, }); }