import { useContext, useEffect, useRef, useState } from "react"; 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"; import { Checkbox } from "@/components/ui/checkbox"; import { CheckedState } from "@radix-ui/react-checkbox"; import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Separator } from "@/components/ui/separator"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Toggle } from "@/components/ui/toggle"; import { useBlockData } from "@/hooks/useBlockData"; import BlockSelector from "./open-image/BlockSelector"; import VersionCombobox from "@/components/VersionCombobox"; import { findBlockFromRgb } from "@/utils/findBlockFromRgb"; function OpenImage({ close }: DialogProps) { const { blocks, version, setBlocks, setVersion, centerCanvas } = useContext(CanvasContext); const { addHistory } = useContext(HistoryContext); const { setLoading } = useContext(LoadingContext); const { acceptedFiles, getRootProps, getInputProps } = useDropzone({ accept: { "image/*": [".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".tiff", ".svg"], }, }); const blockData = useBlockData(version); const divRef = useRef(null); const userModifiedBlocks = useRef(false); const [image, setImage] = useState(); const [imageDimensions, setImageDimensions] = useState({ width: 0, height: 0 }); const [aspectRatio, setAspectRatio] = useState(1); const [linkAspectRatio, setLinkAspectRatio] = useState(true); const [searchInput, setSearchInput] = useState(""); const [stageWidth, setStageWidth] = useState(0); const [selectedBlocks, setSelectedBlocks] = useState(Object.keys(blockData)); const [blockTypeCheckboxesChecked, setBlockTypeCheckboxesChecked] = useState>({ creative: false, tile_entity: false, falling: false, }); // Used for centering the canvas const isFinished = useRef(false); useEffect(() => { if (acceptedFiles[0]) { const img = new Image(); img.onload = () => { setImage(img); setImageDimensions({ width: img.width, height: img.height }); setAspectRatio(img.width / img.height); }; img.src = URL.createObjectURL(acceptedFiles[0]); } }, [acceptedFiles]); const onDimensionChange = (e: React.ChangeEvent, isWidth: boolean) => { const newDimension = Number(e.target.value); if (newDimension < 1 || newDimension > 10000) return; setImageDimensions((prev) => { if (isWidth) return linkAspectRatio ? { width: newDimension, height: Math.round(newDimension / aspectRatio) } : { ...prev, width: newDimension }; return linkAspectRatio ? { width: Math.round(newDimension * aspectRatio), height: newDimension } : { ...prev, height: newDimension }; }); }; const onBlockTypeCheckedChange = (checked: CheckedState, property: keyof BlockData[string]) => { const blocksWithProperty = Object.entries(blockData) .filter(([, data]) => data[property] === true) .map(([blockName]) => blockName); if (checked) { setSelectedBlocks((prev) => [...prev, ...blocksWithProperty]); } else { setSelectedBlocks((prev) => prev.filter((block) => !blocksWithProperty.includes(block))); } setBlockTypeCheckboxesChecked((prev) => ({ ...prev, [property]: checked })); }; const onBlockSelectionChange = (newValue: string[]) => { userModifiedBlocks.current = true; setSelectedBlocks(newValue); }; const onSubmit = async () => { if (image) { const oldBlocks = [...blocks]; isFinished.current = false; setLoading(true); // Wait for loading indicator to appear await new Promise((resolve) => setTimeout(resolve, 1)); // Load image through JS canvas const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); if (ctx) { canvas.width = imageDimensions.width; canvas.height = imageDimensions.height; ctx.drawImage(image, 0, 0, imageDimensions.width, imageDimensions.height); const imageData = ctx.getImageData(0, 0, imageDimensions.width, imageDimensions.height); const newBlocks: Block[] = []; // Go through each pixel in the image and find block by checking closest RGB to the average color of the texture for (let i = 0; i < imageData.data.length; i += 4) { const block = findBlockFromRgb(selectedBlocks, 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); const y = Math.floor(i / 4 / imageData.width); newBlocks.push({ name: block, x, y, }); } setBlocks(newBlocks); addHistory( "Open Image", () => setBlocks(newBlocks), () => setBlocks(oldBlocks) ); } setLoading(false); isFinished.current = true; } }; useEffect(() => { if (!isFinished.current) return; centerCanvas(); close(); return () => { isFinished.current = false; }; }, [isFinished, centerCanvas, close]); useEffect(() => { setBlockTypeCheckboxesChecked((prev) => { const newState = { ...prev }; Object.keys(prev).forEach((property) => { const blocksWithProperty = Object.entries(blockData) .filter(([, data]) => data[property as keyof BlockData[string]] === true) .map(([blockName]) => blockName); const selectedCount = blocksWithProperty.filter((block) => selectedBlocks.includes(block)).length; if (selectedCount === 0) { newState[property] = false; } else if (selectedCount === blocksWithProperty.length) { newState[property] = true; } else { newState[property] = "indeterminate"; } }); return newState; }); }, [selectedBlocks]); useEffect(() => { if (!userModifiedBlocks.current) { setSelectedBlocks(Object.keys(blockData)); } }, [version]); useEffect(() => { if (!divRef.current) return; setStageWidth(divRef.current.clientWidth); }, [divRef.current?.clientWidth]); return ( Open Image Open your image to load as blocks into the canvas
Image Blocks

Drag and drop your image here
or click to open

{image && acceptedFiles[0] && ( <> your image

{acceptedFiles[0].name}

onDimensionChange(e, true)} />
setLinkAspectRatio(!linkAspectRatio)} className="h-8 !min-w-8 p-0 mt-auto mb-1" >
onDimensionChange(e, false)} />
{imageDimensions.height > (version >= 1180 ? 384 : 256) && (
The height is above {version >= 1180 ? 384 : 256} blocks!
)}
)}
onBlockTypeCheckedChange(value, "creative")} />
onBlockTypeCheckedChange(value, "tile_entity")} />
onBlockTypeCheckedChange(value, "falling")} />
setSearchInput(e.target.value)} />
); } export default OpenImage;