feat: design open image dialog
This commit is contained in:
parent
370ec76c9b
commit
98e6354a6e
2 changed files with 104 additions and 3 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pixi/react": "7",
|
"@pixi/react": "7",
|
||||||
"@radix-ui/react-dialog": "^1.1.3",
|
"@radix-ui/react-dialog": "^1.1.3",
|
||||||
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-menubar": "^1.1.2",
|
"@radix-ui/react-menubar": "^1.1.2",
|
||||||
"@radix-ui/react-slider": "^1.2.2",
|
"@radix-ui/react-slider": "^1.2.2",
|
||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
"pixi.js": "7",
|
"pixi.js": "7",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-dropzone": "^14.3.5",
|
||||||
"react-router": "^7.0.2",
|
"react-router": "^7.0.2",
|
||||||
"tailwind-merge": "^2.5.5",
|
"tailwind-merge": "^2.5.5",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,114 @@
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
||||||
|
import { CircleAlertIcon, UploadIcon } from "lucide-react";
|
||||||
|
|
||||||
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 { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
||||||
function OpenImage() {
|
function OpenImage() {
|
||||||
|
const { acceptedFiles, getRootProps, getInputProps } = useDropzone({
|
||||||
|
accept: {
|
||||||
|
"image/*": [".png", ".jpg", ".jpeg", ".bmp", ".webp", ".tiff"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const [image, setImage] = useState<HTMLImageElement>();
|
||||||
|
const [imageDimensions, setImageDimensions] = useState<Dimension>({ width: 0, height: 0 });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (acceptedFiles[0]) {
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = () => {
|
||||||
|
setImage(img);
|
||||||
|
setImageDimensions({ width: img.width, height: img.height });
|
||||||
|
};
|
||||||
|
img.src = URL.createObjectURL(acceptedFiles[0]);
|
||||||
|
}
|
||||||
|
}, [acceptedFiles]);
|
||||||
|
|
||||||
|
const onDimensionChange = (e: React.ChangeEvent<HTMLInputElement>, isWidth: boolean) => {
|
||||||
|
const newDimension = Number(e.target.value);
|
||||||
|
const aspectRatio = imageDimensions.width / imageDimensions.height;
|
||||||
|
|
||||||
|
if (newDimension < 1 || newDimension > 10000) return;
|
||||||
|
setImageDimensions(() => {
|
||||||
|
if (isWidth) {
|
||||||
|
return { width: newDimension, height: Math.round(newDimension / aspectRatio) };
|
||||||
|
} else {
|
||||||
|
return { width: Math.round(newDimension / aspectRatio), height: newDimension };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Open image</DialogTitle>
|
<DialogTitle>Open Image</DialogTitle>
|
||||||
<DialogDescription>Open your image to load as blocks into the canvas</DialogDescription>
|
<DialogDescription>Open your image to load as blocks into the canvas</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<DialogFooter>
|
|
||||||
<Button type="submit">Submit</Button>
|
<div className="flex flex-col gap-2">
|
||||||
|
<div
|
||||||
|
{...getRootProps({
|
||||||
|
className: "flex flex-col justify-center items-center gap-2 p-4 rounded border border-2 border-dashed select-none",
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<input {...getInputProps({ multiple: false })} />
|
||||||
|
<UploadIcon />
|
||||||
|
<p>Drag and drop your image here</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-[auto,1fr] gap-2">
|
||||||
|
{image && acceptedFiles[0] && (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
src={image.src}
|
||||||
|
alt="your image"
|
||||||
|
className="w-48 h-48 object-contain border rounded-lg"
|
||||||
|
style={{ background: "repeating-conic-gradient(#fff 0 90deg, #bbb 0 180deg) 0 0/25% 25%" }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div>
|
||||||
|
<Label>File name</Label>
|
||||||
|
<p>{acceptedFiles[0].name}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="width">Width (blocks)</Label>
|
||||||
|
<Input type="number" id="width" placeholder="Width" value={imageDimensions.width} onChange={(e) => onDimensionChange(e, true)} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="height">Height (blocks)</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
id="height"
|
||||||
|
placeholder="Height"
|
||||||
|
value={imageDimensions.height}
|
||||||
|
onChange={(e) => onDimensionChange(e, false)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className="!justify-between items-center">
|
||||||
|
{imageDimensions.height > 320 && (
|
||||||
|
<div className="flex items-center gap-1 h-min">
|
||||||
|
<CircleAlertIcon className="text-red-400" size={22} />
|
||||||
|
<span className="text-red-400 text-sm">The height is above 320 blocks!</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="ml-auto">
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue