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>;
|
||||
image: HTMLImageElement | undefined;
|
||||
imageDimensions: Dimension;
|
||||
usableBlocks: string[];
|
||||
coords: Position;
|
||||
scale: number;
|
||||
version: number;
|
||||
|
|
@ -25,7 +26,20 @@ interface Props {
|
|||
// Lifts 16,000 tiles limit
|
||||
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 tilemapRef = useRef<CompositeTilemap>();
|
||||
|
||||
|
|
@ -84,7 +98,7 @@ function Blocks({ blocks, setBlocks, missingTexture, textures, solidTextures, im
|
|||
const newBlocks: Block[] = [];
|
||||
|
||||
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;
|
||||
|
||||
const x = Math.floor((i / 4) % imageData.width);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.NEAREST;
|
|||
|
||||
function Canvas() {
|
||||
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 { settings } = useContext(SettingsContext);
|
||||
const { missingTexture, textures, solidTextures } = useContext(TexturesContext);
|
||||
|
|
@ -308,6 +308,7 @@ function Canvas() {
|
|||
solidTextures={solidTextures}
|
||||
image={image}
|
||||
imageDimensions={imageDimensions}
|
||||
usableBlocks={usableBlocks}
|
||||
coords={coords}
|
||||
scale={scale}
|
||||
version={version}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ import { Toggle } from "@/components/ui/toggle";
|
|||
import { getBlockData } from "@/utils/getBlockData";
|
||||
|
||||
import BlockSelector from "./open-image/BlockSelector";
|
||||
import VersionCombobox from "../VersionCombobox";
|
||||
|
||||
function OpenImage({ close }: DialogProps) {
|
||||
const { version } = useContext(CanvasContext);
|
||||
const { version, setVersion } = useContext(CanvasContext);
|
||||
const { setLoading } = useContext(LoadingContext);
|
||||
const { setImage: setContextImage, setImageDimensions: setContextImageDimensions } = useContext(ImageContext);
|
||||
const { setImage: setContextImage, setImageDimensions: setContextImageDimensions, setUsableBlocks } = useContext(ImageContext);
|
||||
|
||||
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
||||
accept: {
|
||||
|
|
@ -33,7 +34,9 @@ function OpenImage({ close }: DialogProps) {
|
|||
},
|
||||
});
|
||||
|
||||
const blockData = getBlockData(version);
|
||||
const divRef = useRef<HTMLDivElement>(null);
|
||||
const userModifiedBlocks = useRef(false);
|
||||
|
||||
const [image, setImage] = useState<HTMLImageElement>();
|
||||
const [imageDimensions, setImageDimensions] = useState<Dimension>({ width: 0, height: 0 });
|
||||
|
|
@ -43,15 +46,13 @@ function OpenImage({ close }: DialogProps) {
|
|||
const [searchInput, setSearchInput] = useState("");
|
||||
const [stageWidth, setStageWidth] = useState(0);
|
||||
|
||||
const [selectedBlocks, setSelectedBlocks] = useState<string[]>(["stone"]);
|
||||
const [selectedBlocks, setSelectedBlocks] = useState<string[]>(Object.keys(blockData));
|
||||
const [blockTypeCheckboxesChecked, setBlockTypeCheckboxesChecked] = useState({
|
||||
creative: false,
|
||||
tile_entity: false,
|
||||
fallable: false,
|
||||
});
|
||||
|
||||
const blockData = getBlockData(version);
|
||||
|
||||
useEffect(() => {
|
||||
if (acceptedFiles[0]) {
|
||||
const img = new Image();
|
||||
|
|
@ -89,9 +90,15 @@ function OpenImage({ close }: DialogProps) {
|
|||
setBlockTypeCheckboxesChecked((prev) => ({ ...prev, [property]: checked }));
|
||||
};
|
||||
|
||||
const onBlockSelectionChange = (newValue: string[]) => {
|
||||
userModifiedBlocks.current = true;
|
||||
setSelectedBlocks(newValue);
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
if (image) {
|
||||
setLoading(true);
|
||||
setUsableBlocks(selectedBlocks);
|
||||
|
||||
// Wait for loading indicator to appear
|
||||
setTimeout(() => {
|
||||
|
|
@ -113,6 +120,12 @@ function OpenImage({ close }: DialogProps) {
|
|||
});
|
||||
}, [selectedBlocks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userModifiedBlocks.current) {
|
||||
setSelectedBlocks(Object.keys(blockData));
|
||||
}
|
||||
}, [version]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!divRef.current) return;
|
||||
setStageWidth(divRef.current.clientWidth);
|
||||
|
|
@ -237,10 +250,10 @@ function OpenImage({ close }: DialogProps) {
|
|||
</div>
|
||||
|
||||
<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
|
||||
</Button>
|
||||
<Button className="h-8" onClick={() => setSelectedBlocks([])}>
|
||||
<Button className="h-8" onClick={() => onBlockSelectionChange([])}>
|
||||
Uncheck all blocks
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -256,21 +269,24 @@ function OpenImage({ close }: DialogProps) {
|
|||
searchInput={searchInput}
|
||||
selectedBlocks={selectedBlocks}
|
||||
setSelectedBlocks={setSelectedBlocks}
|
||||
userModifiedBlocks={userModifiedBlocks}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
{/* todo: add version selector here */}
|
||||
<DialogFooter className="!justify-between">
|
||||
<VersionCombobox version={version} setVersion={setVersion} />
|
||||
|
||||
<Button variant="outline" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" onClick={onSubmit}>
|
||||
Open
|
||||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button variant="outline" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" onClick={onSubmit}>
|
||||
Open
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ interface Props {
|
|||
searchInput: string;
|
||||
selectedBlocks: 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 { missingTexture, textures } = useContext(TexturesContext);
|
||||
const { isDark } = useContext(ThemeContext);
|
||||
|
|
@ -27,6 +28,8 @@ function BlockSelector({ stageWidth, searchInput, selectedBlocks, setSelectedBlo
|
|||
const blocksPerColumn = Math.floor(stageWidth / (32 + 2));
|
||||
|
||||
const onClick = (block: string) => {
|
||||
userModifiedBlocks.current = true;
|
||||
|
||||
if (selectedBlocks.includes(block)) {
|
||||
setSelectedBlocks((prev) => prev.filter((blockName) => blockName !== block));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import { createContext, ReactNode, useState } from "react";
|
|||
interface Context {
|
||||
image: HTMLImageElement | undefined;
|
||||
imageDimensions: Dimension;
|
||||
usableBlocks: string[];
|
||||
setImage: React.Dispatch<React.SetStateAction<HTMLImageElement | undefined>>;
|
||||
setImageDimensions: React.Dispatch<React.SetStateAction<Dimension>>;
|
||||
setUsableBlocks: React.Dispatch<React.SetStateAction<string[]>>;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
|
|
@ -16,6 +18,11 @@ export const ImageContext = createContext<Context>({} as Context);
|
|||
export const ImageProvider = ({ children }: Props) => {
|
||||
const [image, setImage] = useState<HTMLImageElement>();
|
||||
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 => {
|
||||
return Object.entries(data).reduce(
|
||||
(closestBlock, [block, data]) => {
|
||||
import _blockData from "@/data/blocks/data.json";
|
||||
const blockData: BlockData = _blockData;
|
||||
|
||||
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(
|
||||
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;
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in a new issue