feat: history/undo & redo

This commit is contained in:
trafficlunar 2025-02-07 20:17:30 +00:00
parent f02977b096
commit ac9ac3d454
9 changed files with 237 additions and 29 deletions

View file

@ -1,5 +1,6 @@
import React, { createContext, ReactNode, useMemo, useState } from "react";
import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from "react";
import { HistoryContext } from "./History";
import welcomeBlocksData from "@/data/welcome.json";
interface Context {
@ -24,6 +25,8 @@ interface Props {
export const CanvasContext = createContext<Context>({} as Context);
export const CanvasProvider = ({ children }: Props) => {
const { addHistory } = useContext(HistoryContext);
const [stageSize, setStageSize] = useState<Dimension>({ width: 0, height: 0 });
const [blocks, setBlocks] = useState<Block[]>(welcomeBlocksData);
const [coords, setCoords] = useState<Position>({ x: 0, y: 0 });
@ -84,9 +87,30 @@ export const CanvasProvider = ({ children }: Props) => {
setCoords({ x: newX, y: newY });
};
useEffect(() => {
addHistory(
"New Canvas",
() => setBlocks(welcomeBlocksData),
() => setBlocks([])
);
}, []);
return (
<CanvasContext.Provider
value={{ stageSize, canvasSize, blocks, coords, scale, version, setStageSize, setBlocks, setCoords, setScale, setVersion, centerCanvas }}
value={{
stageSize,
canvasSize,
blocks,
coords,
scale,
version,
setStageSize,
setBlocks,
setCoords,
setScale,
setVersion,
centerCanvas,
}}
>
{children}
</CanvasContext.Provider>

86
src/context/History.tsx Normal file
View file

@ -0,0 +1,86 @@
import { createContext, ReactNode, useState } from "react";
interface Context {
history: HistoryEntry[];
currentIndex: number;
isUndoAvailable: boolean;
isRedoAvailable: boolean;
addHistory: (name: string, apply: () => void, revert: () => void) => void;
undo: () => void;
redo: () => void;
jumpTo: (index: number) => void;
}
interface Props {
children: ReactNode;
}
export const HistoryContext = createContext<Context>({} as Context);
export const HistoryProvider = ({ children }: Props) => {
const [history, setHistory] = useState<HistoryEntry[]>([]);
const [currentIndex, setCurrentIndex] = useState(-1);
const isUndoAvailable = currentIndex > 0;
const isRedoAvailable = currentIndex < history.length - 1;
const MAX_HISTORY = 20;
const addHistory = (name: string, apply: () => void, revert: () => void) => {
const newHistory = history.slice(0, currentIndex + 1);
newHistory.push({ name, apply, revert });
if (newHistory.length > MAX_HISTORY) {
newHistory.shift();
setCurrentIndex((prev) => Math.max(prev - 1, 0));
} else {
setCurrentIndex(newHistory.length - 1);
}
setHistory(newHistory);
};
const undo = () => {
if (!isUndoAvailable) return;
history[currentIndex].revert();
setCurrentIndex(currentIndex - 1);
};
const redo = () => {
if (!isRedoAvailable) return;
history[currentIndex + 1].apply();
setCurrentIndex(currentIndex + 1);
};
const jumpTo = (index: number) => {
if (index == currentIndex) return;
if (index > currentIndex) {
for (let i = currentIndex + 1; i <= index; i++) {
history[i].apply();
}
} else {
for (let i = currentIndex; i > index; i--) {
history[i].revert();
}
}
setCurrentIndex(index);
};
return (
<HistoryContext.Provider
value={{
history,
currentIndex,
isUndoAvailable,
isRedoAvailable,
addHistory,
undo,
redo,
jumpTo,
}}
>
{children}
</HistoryContext.Provider>
);
};

View file

@ -12,8 +12,9 @@ interface Props {
const defaultSettings: Settings = {
grid: true,
canvasBorder: false,
historyPanel: true,
colorPicker: false,
blockReplacer: true,
blockReplacer: false,
radiusChanger: true,
blockSelector: true,
};