feat: part 2 of redesign of open image dialog

This commit is contained in:
trafficlunar 2024-12-28 21:46:52 +00:00
parent 9833f8a63c
commit 6996c9a310
6 changed files with 73 additions and 24 deletions

View file

@ -16,6 +16,7 @@ interface Props {
solidTextures: Record<string, PIXI.Texture>; solidTextures: Record<string, PIXI.Texture>;
image: HTMLImageElement | undefined; image: HTMLImageElement | undefined;
imageDimensions: Dimension; imageDimensions: Dimension;
usableBlocks: string[];
coords: Position; coords: Position;
scale: number; scale: number;
version: number; version: number;
@ -25,7 +26,20 @@ interface Props {
// Lifts 16,000 tiles limit // Lifts 16,000 tiles limit
settings.use32bitIndex = true; settings.use32bitIndex = true;
function Blocks({ blocks, setBlocks, missingTexture, textures, solidTextures, image, imageDimensions, coords, scale, version, setLoading }: Props) { function Blocks({
blocks,
setBlocks,
missingTexture,
textures,
solidTextures,
image,
imageDimensions,
usableBlocks,
coords,
scale,
version,
setLoading,
}: Props) {
const app = useApp(); const app = useApp();
const tilemapRef = useRef<CompositeTilemap>(); const tilemapRef = useRef<CompositeTilemap>();
@ -84,7 +98,7 @@ function Blocks({ blocks, setBlocks, missingTexture, textures, solidTextures, im
const newBlocks: Block[] = []; const newBlocks: Block[] = [];
for (let i = 0; i < imageData.data.length; i += 4) { for (let i = 0; i < imageData.data.length; i += 4) {
const block = findBlockFromRgb(blockData, imageData.data[i], imageData.data[i + 1], imageData.data[i + 2], imageData.data[i + 3]); const block = findBlockFromRgb(usableBlocks, imageData.data[i], imageData.data[i + 1], imageData.data[i + 2], imageData.data[i + 3]);
if (block == "air") continue; if (block == "air") continue;
const x = Math.floor((i / 4) % imageData.width); const x = Math.floor((i / 4) % imageData.width);

View file

@ -26,7 +26,7 @@ PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
function Canvas() { function Canvas() {
const { stageSize, canvasSize, blocks, coords, scale, version, setStageSize, setBlocks, setCoords, setScale } = useContext(CanvasContext); const { stageSize, canvasSize, blocks, coords, scale, version, setStageSize, setBlocks, setCoords, setScale } = useContext(CanvasContext);
const { image, imageDimensions } = useContext(ImageContext); const { image, imageDimensions, usableBlocks } = useContext(ImageContext);
const { setLoading } = useContext(LoadingContext); const { setLoading } = useContext(LoadingContext);
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
const { missingTexture, textures, solidTextures } = useContext(TexturesContext); const { missingTexture, textures, solidTextures } = useContext(TexturesContext);
@ -308,6 +308,7 @@ function Canvas() {
solidTextures={solidTextures} solidTextures={solidTextures}
image={image} image={image}
imageDimensions={imageDimensions} imageDimensions={imageDimensions}
usableBlocks={usableBlocks}
coords={coords} coords={coords}
scale={scale} scale={scale}
version={version} version={version}

View file

@ -21,11 +21,12 @@ import { Toggle } from "@/components/ui/toggle";
import { getBlockData } from "@/utils/getBlockData"; import { getBlockData } from "@/utils/getBlockData";
import BlockSelector from "./open-image/BlockSelector"; import BlockSelector from "./open-image/BlockSelector";
import VersionCombobox from "../VersionCombobox";
function OpenImage({ close }: DialogProps) { function OpenImage({ close }: DialogProps) {
const { version } = useContext(CanvasContext); const { version, setVersion } = useContext(CanvasContext);
const { setLoading } = useContext(LoadingContext); const { setLoading } = useContext(LoadingContext);
const { setImage: setContextImage, setImageDimensions: setContextImageDimensions } = useContext(ImageContext); const { setImage: setContextImage, setImageDimensions: setContextImageDimensions, setUsableBlocks } = useContext(ImageContext);
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
accept: { accept: {
@ -33,7 +34,9 @@ function OpenImage({ close }: DialogProps) {
}, },
}); });
const blockData = getBlockData(version);
const divRef = useRef<HTMLDivElement>(null); const divRef = useRef<HTMLDivElement>(null);
const userModifiedBlocks = useRef(false);
const [image, setImage] = useState<HTMLImageElement>(); const [image, setImage] = useState<HTMLImageElement>();
const [imageDimensions, setImageDimensions] = useState<Dimension>({ width: 0, height: 0 }); const [imageDimensions, setImageDimensions] = useState<Dimension>({ width: 0, height: 0 });
@ -43,15 +46,13 @@ function OpenImage({ close }: DialogProps) {
const [searchInput, setSearchInput] = useState(""); const [searchInput, setSearchInput] = useState("");
const [stageWidth, setStageWidth] = useState(0); const [stageWidth, setStageWidth] = useState(0);
const [selectedBlocks, setSelectedBlocks] = useState<string[]>(["stone"]); const [selectedBlocks, setSelectedBlocks] = useState<string[]>(Object.keys(blockData));
const [blockTypeCheckboxesChecked, setBlockTypeCheckboxesChecked] = useState({ const [blockTypeCheckboxesChecked, setBlockTypeCheckboxesChecked] = useState({
creative: false, creative: false,
tile_entity: false, tile_entity: false,
fallable: false, fallable: false,
}); });
const blockData = getBlockData(version);
useEffect(() => { useEffect(() => {
if (acceptedFiles[0]) { if (acceptedFiles[0]) {
const img = new Image(); const img = new Image();
@ -89,9 +90,15 @@ function OpenImage({ close }: DialogProps) {
setBlockTypeCheckboxesChecked((prev) => ({ ...prev, [property]: checked })); setBlockTypeCheckboxesChecked((prev) => ({ ...prev, [property]: checked }));
}; };
const onBlockSelectionChange = (newValue: string[]) => {
userModifiedBlocks.current = true;
setSelectedBlocks(newValue);
};
const onSubmit = () => { const onSubmit = () => {
if (image) { if (image) {
setLoading(true); setLoading(true);
setUsableBlocks(selectedBlocks);
// Wait for loading indicator to appear // Wait for loading indicator to appear
setTimeout(() => { setTimeout(() => {
@ -113,6 +120,12 @@ function OpenImage({ close }: DialogProps) {
}); });
}, [selectedBlocks]); }, [selectedBlocks]);
useEffect(() => {
if (!userModifiedBlocks.current) {
setSelectedBlocks(Object.keys(blockData));
}
}, [version]);
useEffect(() => { useEffect(() => {
if (!divRef.current) return; if (!divRef.current) return;
setStageWidth(divRef.current.clientWidth); setStageWidth(divRef.current.clientWidth);
@ -237,10 +250,10 @@ function OpenImage({ close }: DialogProps) {
</div> </div>
<div className="grid grid-rows-2 gap-1"> <div className="grid grid-rows-2 gap-1">
<Button className="h-8" onClick={() => setSelectedBlocks(Object.keys(blockData))}> <Button className="h-8" onClick={() => onBlockSelectionChange(Object.keys(blockData))}>
Check all blocks Check all blocks
</Button> </Button>
<Button className="h-8" onClick={() => setSelectedBlocks([])}> <Button className="h-8" onClick={() => onBlockSelectionChange([])}>
Uncheck all blocks Uncheck all blocks
</Button> </Button>
</div> </div>
@ -256,21 +269,24 @@ function OpenImage({ close }: DialogProps) {
searchInput={searchInput} searchInput={searchInput}
selectedBlocks={selectedBlocks} selectedBlocks={selectedBlocks}
setSelectedBlocks={setSelectedBlocks} setSelectedBlocks={setSelectedBlocks}
userModifiedBlocks={userModifiedBlocks}
/> />
</ScrollArea> </ScrollArea>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>
<DialogFooter> <DialogFooter className="!justify-between">
{/* todo: add version selector here */} <VersionCombobox version={version} setVersion={setVersion} />
<div className="flex gap-2">
<Button variant="outline" onClick={close}> <Button variant="outline" onClick={close}>
Cancel Cancel
</Button> </Button>
<Button type="submit" onClick={onSubmit}> <Button type="submit" onClick={onSubmit}>
Open Open
</Button> </Button>
</div>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
); );

View file

@ -12,9 +12,10 @@ interface Props {
searchInput: string; searchInput: string;
selectedBlocks: string[]; selectedBlocks: string[];
setSelectedBlocks: React.Dispatch<React.SetStateAction<string[]>>; setSelectedBlocks: React.Dispatch<React.SetStateAction<string[]>>;
userModifiedBlocks: React.MutableRefObject<boolean>;
} }
function BlockSelector({ stageWidth, searchInput, selectedBlocks, setSelectedBlocks }: Props) { function BlockSelector({ stageWidth, searchInput, selectedBlocks, setSelectedBlocks, userModifiedBlocks }: Props) {
const { version } = useContext(CanvasContext); const { version } = useContext(CanvasContext);
const { missingTexture, textures } = useContext(TexturesContext); const { missingTexture, textures } = useContext(TexturesContext);
const { isDark } = useContext(ThemeContext); const { isDark } = useContext(ThemeContext);
@ -27,6 +28,8 @@ function BlockSelector({ stageWidth, searchInput, selectedBlocks, setSelectedBlo
const blocksPerColumn = Math.floor(stageWidth / (32 + 2)); const blocksPerColumn = Math.floor(stageWidth / (32 + 2));
const onClick = (block: string) => { const onClick = (block: string) => {
userModifiedBlocks.current = true;
if (selectedBlocks.includes(block)) { if (selectedBlocks.includes(block)) {
setSelectedBlocks((prev) => prev.filter((blockName) => blockName !== block)); setSelectedBlocks((prev) => prev.filter((blockName) => blockName !== block));
} else { } else {

View file

@ -3,8 +3,10 @@ import { createContext, ReactNode, useState } from "react";
interface Context { interface Context {
image: HTMLImageElement | undefined; image: HTMLImageElement | undefined;
imageDimensions: Dimension; imageDimensions: Dimension;
usableBlocks: string[];
setImage: React.Dispatch<React.SetStateAction<HTMLImageElement | undefined>>; setImage: React.Dispatch<React.SetStateAction<HTMLImageElement | undefined>>;
setImageDimensions: React.Dispatch<React.SetStateAction<Dimension>>; setImageDimensions: React.Dispatch<React.SetStateAction<Dimension>>;
setUsableBlocks: React.Dispatch<React.SetStateAction<string[]>>;
} }
interface Props { interface Props {
@ -16,6 +18,11 @@ export const ImageContext = createContext<Context>({} as Context);
export const ImageProvider = ({ children }: Props) => { export const ImageProvider = ({ children }: Props) => {
const [image, setImage] = useState<HTMLImageElement>(); const [image, setImage] = useState<HTMLImageElement>();
const [imageDimensions, setImageDimensions] = useState<Dimension>({ width: 0, height: 0 }); const [imageDimensions, setImageDimensions] = useState<Dimension>({ width: 0, height: 0 });
const [usableBlocks, setUsableBlocks] = useState<string[]>([]);
return <ImageContext.Provider value={{ image, imageDimensions, setImage, setImageDimensions }}>{children}</ImageContext.Provider>; return (
<ImageContext.Provider value={{ image, imageDimensions, usableBlocks, setImage, setImageDimensions, setUsableBlocks }}>
{children}
</ImageContext.Provider>
);
}; };

View file

@ -1,8 +1,16 @@
export const findBlockFromRgb = (data: BlockData, r: number, g: number, b: number, a: number): string => { import _blockData from "@/data/blocks/data.json";
return Object.entries(data).reduce( const blockData: BlockData = _blockData;
(closestBlock, [block, data]) => {
export const findBlockFromRgb = (data: BlockData | string[], r: number, g: number, b: number, a: number): string => {
const source = Array.isArray(data) ? Object.entries(blockData).filter(([block]) => data.includes(block)) : Object.entries(data);
return source.reduce(
(closestBlock, [block, blockData]) => {
const distance = Math.sqrt( const distance = Math.sqrt(
Math.pow(r - data.color[0], 2) + Math.pow(g - data.color[1], 2) + Math.pow(b - data.color[2], 2) + Math.pow(a - data.color[3], 2) Math.pow(r - blockData.color[0], 2) +
Math.pow(g - blockData.color[1], 2) +
Math.pow(b - blockData.color[2], 2) +
Math.pow(a - blockData.color[3], 2)
); );
return distance < closestBlock.distance ? { block, distance } : closestBlock; return distance < closestBlock.distance ? { block, distance } : closestBlock;
}, },