feat: part 2 of redesign of open image dialog
This commit is contained in:
parent
9833f8a63c
commit
6996c9a310
6 changed files with 73 additions and 24 deletions
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue