feat: multiple implementations of history entry creations

This commit is contained in:
trafficlunar 2025-02-09 12:08:08 +00:00
parent 44609671a9
commit 0fc7497f10
9 changed files with 124 additions and 22 deletions

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
@ -7,6 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { cn } from "@/lib/utils";
import { numberToVersion, versionToNumber } from "@/utils/version";
import { HistoryContext } from "@/context/History";
const versions = [
"1.21.4",
@ -31,14 +32,29 @@ const versions = [
interface Props {
version: number;
setVersion: React.Dispatch<React.SetStateAction<number>>;
// If both variables above are from the context
isContext?: boolean;
}
function VersionCombobox({ version, setVersion }: Props) {
function VersionCombobox({ version, setVersion, isContext }: Props) {
const { addHistory } = useContext(HistoryContext);
const [comboboxOpen, setComboboxOpen] = useState(false);
const [comboboxValue, setComboboxValue] = useState(numberToVersion(version));
useEffect(() => {
setVersion(versionToNumber(comboboxValue));
setVersion((prev) => {
if (isContext) {
const oldVersion = prev;
addHistory(
"Set Version",
() => setVersion(versionToNumber(comboboxValue)),
() => setVersion(oldVersion)
);
}
return versionToNumber(comboboxValue);
});
}, [comboboxValue]);
return (

View file

@ -287,7 +287,16 @@ function Canvas() {
}
}
setSelectionCoords(newSelection);
setSelectionCoords((prev) => {
const prevSelection = [...prev];
addHistory(
"Select All",
() => setSelectionCoords(newSelection),
() => setSelectionCoords(prevSelection)
);
return newSelection;
});
break;
}
case "z":

View file

@ -4,6 +4,7 @@ import { useDropzone } from "react-dropzone";
import { CircleAlertIcon, LinkIcon, UploadIcon } from "lucide-react";
import { CanvasContext } from "@/context/Canvas";
import { HistoryContext } from "@/context/History";
import { LoadingContext } from "@/context/Loading";
import { Button } from "@/components/ui/button";
@ -20,11 +21,13 @@ import { Toggle } from "@/components/ui/toggle";
import { useBlockData } from "@/hooks/useBlockData";
import BlockSelector from "./open-image/BlockSelector";
import VersionCombobox from "../VersionCombobox";
import VersionCombobox from "@/components/VersionCombobox";
import { findBlockFromRgb } from "@/utils/findBlockFromRgb";
function OpenImage({ close }: DialogProps) {
const { version, setBlocks, setVersion, centerCanvas } = useContext(CanvasContext);
const { blocks, version, setBlocks, setVersion, centerCanvas } = useContext(CanvasContext);
const { addHistory } = useContext(HistoryContext);
const { setLoading } = useContext(LoadingContext);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
@ -98,6 +101,8 @@ function OpenImage({ close }: DialogProps) {
const onSubmit = async () => {
if (image) {
const oldBlocks = [...blocks];
setIsFinished(false);
setLoading(true);
// Wait for loading indicator to appear
@ -131,6 +136,11 @@ function OpenImage({ close }: DialogProps) {
}
setBlocks(newBlocks);
addHistory(
"Open Image",
() => setBlocks(newBlocks),
() => setBlocks(oldBlocks)
);
}
setLoading(false);
@ -318,7 +328,7 @@ function OpenImage({ close }: DialogProps) {
</div>
<DialogFooter className="!justify-between">
<VersionCombobox version={version} setVersion={setVersion} />
<VersionCombobox version={version} setVersion={setVersion} isContext />
<div className="flex gap-2">
<Button variant="outline" onClick={close}>

View file

@ -5,6 +5,7 @@ import { UploadIcon } from "lucide-react";
import * as nbt from "nbtify";
import { CanvasContext } from "@/context/Canvas";
import { HistoryContext } from "@/context/History";
import { LoadingContext } from "@/context/Loading";
import * as varint from "@/utils/varint";
@ -47,7 +48,8 @@ interface SpongeNBT extends nbt.ListTagLike {
}
function OpenSchematic({ close }: DialogProps) {
const { setBlocks, setVersion } = useContext(CanvasContext);
const { blocks, setBlocks, setVersion } = useContext(CanvasContext);
const { addHistory } = useContext(HistoryContext);
const { setLoading } = useContext(LoadingContext);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
@ -67,6 +69,8 @@ function OpenSchematic({ close }: DialogProps) {
const bytes = await file.arrayBuffer();
const data = await nbt.read(bytes);
const oldBlocks = [...blocks];
if (fileExtension == "litematic") {
const litematicData = (data as nbt.NBTData<LitematicNBT>).data;
const litematicRegionData = litematicData.Regions.Image;
@ -144,6 +148,11 @@ function OpenSchematic({ close }: DialogProps) {
}
setBlocks(blocks);
addHistory(
"Open Schematic",
() => setBlocks(blocks),
() => setBlocks(oldBlocks)
);
} else if (fileExtension == "schem") {
const spongeData = (data as nbt.NBTData<SpongeNBT>).data.Schematic;
const schematicVersion = Object.keys(versionData).find((key) => versionData[key] == spongeData.DataVersion);
@ -192,6 +201,11 @@ function OpenSchematic({ close }: DialogProps) {
}
setBlocks(blocks);
addHistory(
"Open Schematic",
() => setBlocks(blocks),
() => setBlocks(oldBlocks)
);
}
}

View file

@ -1,18 +1,28 @@
import { useContext, useState } from "react";
import { CanvasContext } from "@/context/Canvas";
import { HistoryContext } from "@/context/History";
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import VersionCombobox from "../VersionCombobox";
import VersionCombobox from "@/components/VersionCombobox";
function SetVersion({ close }: DialogProps) {
const { version: contextVersion, setVersion: setContextVersion } = useContext(CanvasContext);
const { addHistory } = useContext(HistoryContext);
const [version, setVersion] = useState(contextVersion);
const onSubmit = () => {
const oldVersion = contextVersion;
setContextVersion(version);
addHistory(
"Set Version",
() => setContextVersion(version),
() => setContextVersion(oldVersion)
);
close();
};

View file

@ -34,7 +34,7 @@ function Menubar() {
<ViewMenu />
<div className="absolute right-3 flex items-center gap-1">
<ThemeIcon inApp={true} />
<ThemeIcon inApp />
<a href="https://github.com/trafficlunar/blockmatic" className="w-5">
<GithubIcon fill={isDark ? "white" : "black"} />
</a>

View file

@ -1,17 +1,38 @@
import { useContext, useEffect, useRef } from "react";
import {
BombIcon,
EraserIcon,
FileIcon,
ImageIcon,
LassoIcon,
PaintBucketIcon,
PencilIcon,
PresentationIcon,
ReplaceIcon,
SlidersHorizontalIcon,
SquareDashedIcon,
Trash2Icon,
WandIcon,
} from "lucide-react";
import { HistoryContext } from "@/context/History";
import { ScrollArea } from "@/components/ui/scroll-area";
import { BombIcon, EraserIcon, LassoIcon, PencilIcon, PresentationIcon, SquareDashedIcon, Trash2Icon, WandIcon } from "lucide-react";
const iconMap = {
"New Canvas": PresentationIcon,
Pencil: PencilIcon,
Eraser: EraserIcon,
"Rectangle Select": SquareDashedIcon,
Lasso: LassoIcon,
"Magic Wand": WandIcon,
"Clear All": BombIcon,
Delete: Trash2Icon,
Eraser: EraserIcon,
Lasso: LassoIcon,
"Magic Wand": WandIcon,
"New Canvas": PresentationIcon,
"Open Image": ImageIcon,
"Open Schematic": FileIcon,
"Paint Bucket": PaintBucketIcon,
Pencil: PencilIcon,
Replace: ReplaceIcon,
"Rectangle Select": SquareDashedIcon,
"Select All": SquareDashedIcon,
"Set Version": SlidersHorizontalIcon,
};
function History() {

View file

@ -2,6 +2,7 @@ import { useContext, useEffect, useState } from "react";
import { Container, Sprite, Stage } from "@pixi/react";
import { CanvasContext } from "@/context/Canvas";
import { HistoryContext } from "@/context/History";
import { SelectionContext } from "@/context/Selection";
import { ToolContext } from "@/context/Tool";
import { TexturesContext } from "@/context/Textures";
@ -13,6 +14,7 @@ import { useTextures } from "@/hooks/useTextures";
function Replace() {
const { version, setBlocks } = useContext(CanvasContext);
const { addHistory } = useContext(HistoryContext);
const { isInSelection } = useContext(SelectionContext);
const { selectedBlock, tool, setTool } = useContext(ToolContext);
const { missingTexture } = useContext(TexturesContext);
@ -32,8 +34,10 @@ function Replace() {
const onClickReplace = () => {
// If block2 name is air, delete the block instead.
setBlocks((prev) =>
prev
setBlocks((prev) => {
const oldBlocks = [...prev];
const replacedBlocks = prev
.map((block) => {
if (isInSelection(block.x, block.y)) {
if (block.name === block1) {
@ -45,8 +49,15 @@ function Replace() {
return block;
})
// Remove all blocks that are null
.filter((block) => block !== null)
);
.filter((block) => block !== null);
addHistory(
"Replace",
() => setBlocks(replacedBlocks),
() => setBlocks(oldBlocks)
);
return replacedBlocks;
});
};
useEffect(() => {

View file

@ -2,9 +2,11 @@ import { useContext } from "react";
import { CanvasContext } from "@/context/Canvas";
import { ToolContext } from "@/context/Tool";
import { HistoryContext } from "@/context/History";
export function usePaintBucketTool(mouseCoords: Position) {
const { blocks, canvasSize, setBlocks } = useContext(CanvasContext);
const { addHistory } = useContext(HistoryContext);
const { selectedBlock } = useContext(ToolContext);
// Directions for adjacent blocks (up, down, left, right)
@ -16,6 +18,9 @@ export function usePaintBucketTool(mouseCoords: Position) {
];
const use = () => {
// Create a copy
const oldBlocks = blocks.map((block) => ({ ...block }));
const visited = new Set<string>();
const startBlock = blocks.find((block) => block.x === mouseCoords.x && block.y === mouseCoords.y);
const startName = startBlock ? startBlock.name : "air";
@ -51,7 +56,13 @@ export function usePaintBucketTool(mouseCoords: Position) {
}
floodFill(mouseCoords.x, mouseCoords.y);
setBlocks(() => [...blocks]);
setBlocks([...blocks]);
addHistory(
"Paint Bucket",
() => setBlocks([...blocks]),
() => setBlocks(oldBlocks)
);
};
return { use };