Skip to content

Commit

Permalink
Merge pull request #49 from KirillSerg/history
Browse files Browse the repository at this point in the history
feat: added history for the undo/redo
  • Loading branch information
KirillSerg authored Aug 29, 2024
2 parents f437d36 + 10a08da commit 9d4ef26
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 37 deletions.
6 changes: 5 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Inspector from './components/Inspector';
import Toolbar from './components/Toolbar';
import Zoom from './components/Zoom';
import { isDrawingAtom, onKeyPressAtom } from './store/store';
import HistoryControls from './components/HistoryControls';

const App = () => {
const [, onKeyPress] = useAtom(onKeyPressAtom);
Expand All @@ -30,7 +31,10 @@ const App = () => {
<Canvas />
<Inspector />
{/* <Layers /> */}
<Zoom />
<div className="w-full flex justify-end px-5">
<Zoom />
<HistoryControls />
</div>
</div>
);
};
Expand Down
56 changes: 56 additions & 0 deletions src/components/HistoryControls.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useAtom } from 'jotai';
import { useHistoryAtom } from '../store/store';

const HistoryControls = () => {
const [, doRedo] = useAtom(useHistoryAtom);

return (
// <div className="w-full flex justify-end px-5">
<div className="h-fit w-fit px-2 fixed bottom-3 right-[20%] flex justify-center items-center gap-4 border-[1px] border-black">
<button
// id=""
className="h-7 w-7"
onClick={() => doRedo(1)}
>
<svg
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
width={'100%'}
height={'100%'}
>
<path
d="M7.5 10.833 4.167 7.5 7.5 4.167M4.167 7.5h9.166a3.333 3.333 0 0 1 0 6.667H12.5"
strokeWidth="1.25"
></path>
</svg>
</button>

<button
// id=""
className="h-7 w-7"
onClick={() => doRedo(-1)}
>
<svg
viewBox="0 0 20 20"
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
width={'100%'}
height={'100%'}
>
<path
d="M12.5 10.833 15.833 7.5 12.5 4.167M15.833 7.5H6.667a3.333 3.333 0 1 0 0 6.667H7.5"
strokeWidth="1.25"
></path>
</svg>
</button>
</div>
// </div>
);
};

export default HistoryControls;
2 changes: 1 addition & 1 deletion src/components/SingleElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const SingleElement = ({ element }: Props) => {
onMouseDown={() => onDragStart(element)}
onMouseUp={onMouseUp}
>
{element.type === 'foreignObject' && <Textarea element={element} />}
{element.type_name === 'text' && <Textarea element={element} />}
</element.type>

{isSelected && !isDrawing && <SelectingFrame element={element} />}
Expand Down
60 changes: 30 additions & 30 deletions src/components/Zoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,39 @@ const Zoom = () => {
const [, zoomCanvas] = useAtom(zoomCanvasAtom);

return (
<div className="w-full flex justify-end px-5">
<div className="h-fit w-fit px-2 fixed bottom-3 flex justify-center items-center gap-4 border-[1px] border-black">
<button
id="zoomdown"
className="font-bold text-xl leading-none text-start"
onClick={() => {
zoomCanvas(ZoomCanvasFn.ZOOMDOWN);
}}
>
-
</button>
// <div className="w-full flex justify-end px-5">
<div className="h-fit w-fit px-2 fixed bottom-3 flex justify-center items-center gap-4 border-[1px] border-black">
<button
id="zoomdown"
className="font-bold text-xl leading-none text-start"
onClick={() => {
zoomCanvas(ZoomCanvasFn.ZOOMDOWN);
}}
>
-
</button>

<button
id="zoomreset"
className="w-10"
onClick={() => {
zoomCanvas(ZoomCanvasFn.ZOOMRESET);
}}
>
{`${canvasViewBox.percentage}%`}
</button>
<button
id="zoomreset"
className="w-10"
onClick={() => {
zoomCanvas(ZoomCanvasFn.ZOOMRESET);
}}
>
{`${canvasViewBox.percentage}%`}
</button>

<button
id="zoomup"
className="font-bold text-lg"
onClick={() => {
zoomCanvas(ZoomCanvasFn.ZOOMUP);
}}
>
+
</button>
</div>
<button
id="zoomup"
className="font-bold text-lg"
onClick={() => {
zoomCanvas(ZoomCanvasFn.ZOOMUP);
}}
>
+
</button>
</div>
// </div>
);
};

Expand Down
8 changes: 7 additions & 1 deletion src/components/elements/textElement/Textarea.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useRef } from 'react';
import { useAtom, useAtomValue } from 'jotai';
import { isDrawingAtom, updateElementsAtom } from '../../../store/store';
import {
isDrawingAtom,
setHistoryAtom,
updateElementsAtom,
} from '../../../store/store';
import { Element } from '../../../types/CommonTypes';

type Props = {
Expand All @@ -9,6 +13,7 @@ type Props = {

const Textarea = ({ element }: Props) => {
const [, updateElements] = useAtom(updateElementsAtom);
const [, setHistory] = useAtom(setHistoryAtom);
const isDrawing = useAtomValue(isDrawingAtom);

const textareaRef = useRef<HTMLTextAreaElement>(null);
Expand All @@ -31,6 +36,7 @@ const Textarea = ({ element }: Props) => {
ref={textareaRef}
rows={1}
autoFocus={isDrawing}
onBlur={setHistory}
style={{
width: '100%',
height: '100%',
Expand Down
50 changes: 46 additions & 4 deletions src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ const initialCanvasViewBox = {

export const creationInitialElementAtom = atom<Element>(initialElement)

export const historyAtom = atomWithStorage<Element[][]>("history", [[]])

export const currentHistoryIndexAtom = atomWithStorage<number>("historyIndex", 0)

export const elementsAtom = atomWithStorage<Element[]>("elements", [])

export const selectedElementAtom = atom<Element[] | []>([])
Expand Down Expand Up @@ -164,9 +168,15 @@ export const updateElementsAtom = atom(
&& !get(isDraggingAtom)
&& !get(isDrawingAtom)
&& !get(resizeVectorAtom)
) set(selectedElementAtom, (prev) => {
return prev?.map(el => el.id === updatedElement.id ? updatedElement : el) || null
})
) {
set(selectedElementAtom, (prev) => {
return prev?.map(el => el.id === updatedElement.id ? updatedElement : el) || null
})
if (updatedElement.type_name !== 'text') {
set(setHistoryAtom)
}
}

// if drawing with pencil we need to put fresh d
if (updatedElement.type_name === "pencil" && get(isDrawingAtom)) {
set(selectedElementAtom, (prev) => {
Expand All @@ -182,6 +192,7 @@ export const deleteElementsAtom = atom(
const selectedElement = get(selectedElementAtom)
set(elementsAtom, (prev) => prev.filter((el) => !selectedElement.find((selectedEl) => selectedEl.id === el.id)))
set(selectedElementAtom, [])
set(setHistoryAtom)
}
)

Expand Down Expand Up @@ -397,6 +408,7 @@ export const onMouseUpAtom = atom(
set(resizeVectorAtom, "")
set(isDraggingAtom, false)
set(selectingAreaAtom, null)
set(setHistoryAtom)

const isNeedtoResetCreatinType = creationInitialElement.type_name === "pencil" || creationInitialElement.type_name === "grab"
if (!isNeedtoResetCreatinType) {
Expand Down Expand Up @@ -448,6 +460,36 @@ export const duplicateAtom = atom(

set(elementsAtom, prev => [...prev, duplicatedElemment])
set(selectedElementAtom, prev => [...prev, duplicatedElemment])
set(setHistoryAtom)
})
}
)

export const setHistoryAtom = atom(
null,
(get, set) => {
set(historyAtom, (prev) => {
// it's need to cut newest and continue build history chain from some previous snapshot if user was clicked undo btn
const previousHistoryFromCurrentIndex = prev.slice(get(currentHistoryIndexAtom))
if (get(elementsAtom) !== previousHistoryFromCurrentIndex[0] && !!get(elementsAtom).length) {
return [get(elementsAtom), ...previousHistoryFromCurrentIndex]
} else { return prev }
})
set(currentHistoryIndexAtom, 0)
}
)
)

export const useHistoryAtom = atom(
// this call of history (and current history index) is need to enable localStorage and use this history after refresh page when clicking hystory control btn
(get) => { get(historyAtom), get(currentHistoryIndexAtom) },
(get, set, direction: number) => {
const history = get(historyAtom)
const newIndex = get(currentHistoryIndexAtom) + direction
const isEndOfHistory = history.length - 1 < newIndex || newIndex < 0
if (!isEndOfHistory) {
const snapshot = history[newIndex]
set(elementsAtom, snapshot)
set(currentHistoryIndexAtom, newIndex)
}
}
)

0 comments on commit 9d4ef26

Please sign in to comment.