mirror of
https://github.com/trafficlunar/blockmatic.git
synced 2026-06-28 06:34:13 +00:00
feat: history/undo & redo
This commit is contained in:
parent
f02977b096
commit
ac9ac3d454
9 changed files with 237 additions and 29 deletions
|
|
@ -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
86
src/context/History.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue