feat: multiple implementations of history entry creations
This commit is contained in:
parent
44609671a9
commit
0fc7497f10
9 changed files with 124 additions and 22 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
|
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -7,6 +7,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { numberToVersion, versionToNumber } from "@/utils/version";
|
import { numberToVersion, versionToNumber } from "@/utils/version";
|
||||||
|
import { HistoryContext } from "@/context/History";
|
||||||
|
|
||||||
const versions = [
|
const versions = [
|
||||||
"1.21.4",
|
"1.21.4",
|
||||||
|
|
@ -31,14 +32,29 @@ const versions = [
|
||||||
interface Props {
|
interface Props {
|
||||||
version: number;
|
version: number;
|
||||||
setVersion: React.Dispatch<React.SetStateAction<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 [comboboxOpen, setComboboxOpen] = useState(false);
|
||||||
const [comboboxValue, setComboboxValue] = useState(numberToVersion(version));
|
const [comboboxValue, setComboboxValue] = useState(numberToVersion(version));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setVersion(versionToNumber(comboboxValue));
|
setVersion((prev) => {
|
||||||
|
if (isContext) {
|
||||||
|
const oldVersion = prev;
|
||||||
|
addHistory(
|
||||||
|
"Set Version",
|
||||||
|
() => setVersion(versionToNumber(comboboxValue)),
|
||||||
|
() => setVersion(oldVersion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionToNumber(comboboxValue);
|
||||||
|
});
|
||||||
}, [comboboxValue]);
|
}, [comboboxValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -287,7 +287,16 @@ function Canvas() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectionCoords(newSelection);
|
setSelectionCoords((prev) => {
|
||||||
|
const prevSelection = [...prev];
|
||||||
|
addHistory(
|
||||||
|
"Select All",
|
||||||
|
() => setSelectionCoords(newSelection),
|
||||||
|
() => setSelectionCoords(prevSelection)
|
||||||
|
);
|
||||||
|
|
||||||
|
return newSelection;
|
||||||
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "z":
|
case "z":
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useDropzone } from "react-dropzone";
|
||||||
import { CircleAlertIcon, LinkIcon, UploadIcon } from "lucide-react";
|
import { CircleAlertIcon, LinkIcon, UploadIcon } from "lucide-react";
|
||||||
|
|
||||||
import { CanvasContext } from "@/context/Canvas";
|
import { CanvasContext } from "@/context/Canvas";
|
||||||
|
import { HistoryContext } from "@/context/History";
|
||||||
import { LoadingContext } from "@/context/Loading";
|
import { LoadingContext } from "@/context/Loading";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
@ -20,11 +21,13 @@ import { Toggle } from "@/components/ui/toggle";
|
||||||
import { useBlockData } from "@/hooks/useBlockData";
|
import { useBlockData } from "@/hooks/useBlockData";
|
||||||
|
|
||||||
import BlockSelector from "./open-image/BlockSelector";
|
import BlockSelector from "./open-image/BlockSelector";
|
||||||
import VersionCombobox from "../VersionCombobox";
|
import VersionCombobox from "@/components/VersionCombobox";
|
||||||
|
|
||||||
import { findBlockFromRgb } from "@/utils/findBlockFromRgb";
|
import { findBlockFromRgb } from "@/utils/findBlockFromRgb";
|
||||||
|
|
||||||
function OpenImage({ close }: DialogProps) {
|
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 { setLoading } = useContext(LoadingContext);
|
||||||
|
|
||||||
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
||||||
|
|
@ -98,6 +101,8 @@ function OpenImage({ close }: DialogProps) {
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
if (image) {
|
if (image) {
|
||||||
|
const oldBlocks = [...blocks];
|
||||||
|
|
||||||
setIsFinished(false);
|
setIsFinished(false);
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
// Wait for loading indicator to appear
|
// Wait for loading indicator to appear
|
||||||
|
|
@ -131,6 +136,11 @@ function OpenImage({ close }: DialogProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setBlocks(newBlocks);
|
setBlocks(newBlocks);
|
||||||
|
addHistory(
|
||||||
|
"Open Image",
|
||||||
|
() => setBlocks(newBlocks),
|
||||||
|
() => setBlocks(oldBlocks)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
@ -318,7 +328,7 @@ function OpenImage({ close }: DialogProps) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<DialogFooter className="!justify-between">
|
<DialogFooter className="!justify-between">
|
||||||
<VersionCombobox version={version} setVersion={setVersion} />
|
<VersionCombobox version={version} setVersion={setVersion} isContext />
|
||||||
|
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" onClick={close}>
|
<Button variant="outline" onClick={close}>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { UploadIcon } from "lucide-react";
|
||||||
import * as nbt from "nbtify";
|
import * as nbt from "nbtify";
|
||||||
|
|
||||||
import { CanvasContext } from "@/context/Canvas";
|
import { CanvasContext } from "@/context/Canvas";
|
||||||
|
import { HistoryContext } from "@/context/History";
|
||||||
import { LoadingContext } from "@/context/Loading";
|
import { LoadingContext } from "@/context/Loading";
|
||||||
|
|
||||||
import * as varint from "@/utils/varint";
|
import * as varint from "@/utils/varint";
|
||||||
|
|
@ -47,7 +48,8 @@ interface SpongeNBT extends nbt.ListTagLike {
|
||||||
}
|
}
|
||||||
|
|
||||||
function OpenSchematic({ close }: DialogProps) {
|
function OpenSchematic({ close }: DialogProps) {
|
||||||
const { setBlocks, setVersion } = useContext(CanvasContext);
|
const { blocks, setBlocks, setVersion } = useContext(CanvasContext);
|
||||||
|
const { addHistory } = useContext(HistoryContext);
|
||||||
const { setLoading } = useContext(LoadingContext);
|
const { setLoading } = useContext(LoadingContext);
|
||||||
|
|
||||||
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
||||||
|
|
@ -67,6 +69,8 @@ function OpenSchematic({ close }: DialogProps) {
|
||||||
const bytes = await file.arrayBuffer();
|
const bytes = await file.arrayBuffer();
|
||||||
const data = await nbt.read(bytes);
|
const data = await nbt.read(bytes);
|
||||||
|
|
||||||
|
const oldBlocks = [...blocks];
|
||||||
|
|
||||||
if (fileExtension == "litematic") {
|
if (fileExtension == "litematic") {
|
||||||
const litematicData = (data as nbt.NBTData<LitematicNBT>).data;
|
const litematicData = (data as nbt.NBTData<LitematicNBT>).data;
|
||||||
const litematicRegionData = litematicData.Regions.Image;
|
const litematicRegionData = litematicData.Regions.Image;
|
||||||
|
|
@ -144,6 +148,11 @@ function OpenSchematic({ close }: DialogProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setBlocks(blocks);
|
setBlocks(blocks);
|
||||||
|
addHistory(
|
||||||
|
"Open Schematic",
|
||||||
|
() => setBlocks(blocks),
|
||||||
|
() => setBlocks(oldBlocks)
|
||||||
|
);
|
||||||
} else if (fileExtension == "schem") {
|
} else if (fileExtension == "schem") {
|
||||||
const spongeData = (data as nbt.NBTData<SpongeNBT>).data.Schematic;
|
const spongeData = (data as nbt.NBTData<SpongeNBT>).data.Schematic;
|
||||||
const schematicVersion = Object.keys(versionData).find((key) => versionData[key] == spongeData.DataVersion);
|
const schematicVersion = Object.keys(versionData).find((key) => versionData[key] == spongeData.DataVersion);
|
||||||
|
|
@ -192,6 +201,11 @@ function OpenSchematic({ close }: DialogProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setBlocks(blocks);
|
setBlocks(blocks);
|
||||||
|
addHistory(
|
||||||
|
"Open Schematic",
|
||||||
|
() => setBlocks(blocks),
|
||||||
|
() => setBlocks(oldBlocks)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,28 @@
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
|
|
||||||
import { CanvasContext } from "@/context/Canvas";
|
import { CanvasContext } from "@/context/Canvas";
|
||||||
|
import { HistoryContext } from "@/context/History";
|
||||||
|
|
||||||
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import VersionCombobox from "../VersionCombobox";
|
import VersionCombobox from "@/components/VersionCombobox";
|
||||||
|
|
||||||
function SetVersion({ close }: DialogProps) {
|
function SetVersion({ close }: DialogProps) {
|
||||||
const { version: contextVersion, setVersion: setContextVersion } = useContext(CanvasContext);
|
const { version: contextVersion, setVersion: setContextVersion } = useContext(CanvasContext);
|
||||||
|
const { addHistory } = useContext(HistoryContext);
|
||||||
|
|
||||||
const [version, setVersion] = useState(contextVersion);
|
const [version, setVersion] = useState(contextVersion);
|
||||||
|
|
||||||
const onSubmit = () => {
|
const onSubmit = () => {
|
||||||
|
const oldVersion = contextVersion;
|
||||||
|
|
||||||
setContextVersion(version);
|
setContextVersion(version);
|
||||||
|
addHistory(
|
||||||
|
"Set Version",
|
||||||
|
() => setContextVersion(version),
|
||||||
|
() => setContextVersion(oldVersion)
|
||||||
|
);
|
||||||
|
|
||||||
close();
|
close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ function Menubar() {
|
||||||
<ViewMenu />
|
<ViewMenu />
|
||||||
|
|
||||||
<div className="absolute right-3 flex items-center gap-1">
|
<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">
|
<a href="https://github.com/trafficlunar/blockmatic" className="w-5">
|
||||||
<GithubIcon fill={isDark ? "white" : "black"} />
|
<GithubIcon fill={isDark ? "white" : "black"} />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,38 @@
|
||||||
import { useContext, useEffect, useRef } from "react";
|
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 { HistoryContext } from "@/context/History";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import { BombIcon, EraserIcon, LassoIcon, PencilIcon, PresentationIcon, SquareDashedIcon, Trash2Icon, WandIcon } from "lucide-react";
|
|
||||||
|
|
||||||
const iconMap = {
|
const iconMap = {
|
||||||
"New Canvas": PresentationIcon,
|
|
||||||
Pencil: PencilIcon,
|
|
||||||
Eraser: EraserIcon,
|
|
||||||
"Rectangle Select": SquareDashedIcon,
|
|
||||||
Lasso: LassoIcon,
|
|
||||||
"Magic Wand": WandIcon,
|
|
||||||
"Clear All": BombIcon,
|
"Clear All": BombIcon,
|
||||||
Delete: Trash2Icon,
|
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() {
|
function History() {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useContext, useEffect, useState } from "react";
|
||||||
import { Container, Sprite, Stage } from "@pixi/react";
|
import { Container, Sprite, Stage } from "@pixi/react";
|
||||||
|
|
||||||
import { CanvasContext } from "@/context/Canvas";
|
import { CanvasContext } from "@/context/Canvas";
|
||||||
|
import { HistoryContext } from "@/context/History";
|
||||||
import { SelectionContext } from "@/context/Selection";
|
import { SelectionContext } from "@/context/Selection";
|
||||||
import { ToolContext } from "@/context/Tool";
|
import { ToolContext } from "@/context/Tool";
|
||||||
import { TexturesContext } from "@/context/Textures";
|
import { TexturesContext } from "@/context/Textures";
|
||||||
|
|
@ -13,6 +14,7 @@ import { useTextures } from "@/hooks/useTextures";
|
||||||
|
|
||||||
function Replace() {
|
function Replace() {
|
||||||
const { version, setBlocks } = useContext(CanvasContext);
|
const { version, setBlocks } = useContext(CanvasContext);
|
||||||
|
const { addHistory } = useContext(HistoryContext);
|
||||||
const { isInSelection } = useContext(SelectionContext);
|
const { isInSelection } = useContext(SelectionContext);
|
||||||
const { selectedBlock, tool, setTool } = useContext(ToolContext);
|
const { selectedBlock, tool, setTool } = useContext(ToolContext);
|
||||||
const { missingTexture } = useContext(TexturesContext);
|
const { missingTexture } = useContext(TexturesContext);
|
||||||
|
|
@ -32,8 +34,10 @@ function Replace() {
|
||||||
|
|
||||||
const onClickReplace = () => {
|
const onClickReplace = () => {
|
||||||
// If block2 name is air, delete the block instead.
|
// If block2 name is air, delete the block instead.
|
||||||
setBlocks((prev) =>
|
setBlocks((prev) => {
|
||||||
prev
|
const oldBlocks = [...prev];
|
||||||
|
|
||||||
|
const replacedBlocks = prev
|
||||||
.map((block) => {
|
.map((block) => {
|
||||||
if (isInSelection(block.x, block.y)) {
|
if (isInSelection(block.x, block.y)) {
|
||||||
if (block.name === block1) {
|
if (block.name === block1) {
|
||||||
|
|
@ -45,8 +49,15 @@ function Replace() {
|
||||||
return block;
|
return block;
|
||||||
})
|
})
|
||||||
// Remove all blocks that are null
|
// Remove all blocks that are null
|
||||||
.filter((block) => block !== null)
|
.filter((block) => block !== null);
|
||||||
);
|
|
||||||
|
addHistory(
|
||||||
|
"Replace",
|
||||||
|
() => setBlocks(replacedBlocks),
|
||||||
|
() => setBlocks(oldBlocks)
|
||||||
|
);
|
||||||
|
return replacedBlocks;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,11 @@ import { useContext } from "react";
|
||||||
|
|
||||||
import { CanvasContext } from "@/context/Canvas";
|
import { CanvasContext } from "@/context/Canvas";
|
||||||
import { ToolContext } from "@/context/Tool";
|
import { ToolContext } from "@/context/Tool";
|
||||||
|
import { HistoryContext } from "@/context/History";
|
||||||
|
|
||||||
export function usePaintBucketTool(mouseCoords: Position) {
|
export function usePaintBucketTool(mouseCoords: Position) {
|
||||||
const { blocks, canvasSize, setBlocks } = useContext(CanvasContext);
|
const { blocks, canvasSize, setBlocks } = useContext(CanvasContext);
|
||||||
|
const { addHistory } = useContext(HistoryContext);
|
||||||
const { selectedBlock } = useContext(ToolContext);
|
const { selectedBlock } = useContext(ToolContext);
|
||||||
|
|
||||||
// Directions for adjacent blocks (up, down, left, right)
|
// Directions for adjacent blocks (up, down, left, right)
|
||||||
|
|
@ -16,6 +18,9 @@ export function usePaintBucketTool(mouseCoords: Position) {
|
||||||
];
|
];
|
||||||
|
|
||||||
const use = () => {
|
const use = () => {
|
||||||
|
// Create a copy
|
||||||
|
const oldBlocks = blocks.map((block) => ({ ...block }));
|
||||||
|
|
||||||
const visited = new Set<string>();
|
const visited = new Set<string>();
|
||||||
const startBlock = blocks.find((block) => block.x === mouseCoords.x && block.y === mouseCoords.y);
|
const startBlock = blocks.find((block) => block.x === mouseCoords.x && block.y === mouseCoords.y);
|
||||||
const startName = startBlock ? startBlock.name : "air";
|
const startName = startBlock ? startBlock.name : "air";
|
||||||
|
|
@ -51,7 +56,13 @@ export function usePaintBucketTool(mouseCoords: Position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
floodFill(mouseCoords.x, mouseCoords.y);
|
floodFill(mouseCoords.x, mouseCoords.y);
|
||||||
setBlocks(() => [...blocks]);
|
setBlocks([...blocks]);
|
||||||
|
|
||||||
|
addHistory(
|
||||||
|
"Paint Bucket",
|
||||||
|
() => setBlocks([...blocks]),
|
||||||
|
() => setBlocks(oldBlocks)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { use };
|
return { use };
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue