{pool.map((drop, index) => {
- const lootType = LOOT_TYPES.find(t => t.value === drop.type);
- const Icon = lootType?.icon || Package;
+
return (
@@ -149,10 +148,7 @@ export function LootTableBuilder({ pool, onChange }: LootTableBuilderProps) {
onValueChange={(value) => changeDropType(index, value as LootType)}
>
-
-
-
-
+
{LOOT_TYPES.map((type) => (
diff --git a/web/src/components/ui/dialog.tsx b/web/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..0edcceb
--- /dev/null
+++ b/web/src/components/ui/dialog.tsx
@@ -0,0 +1,122 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { X } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+const Dialog = DialogPrimitive.Root
+
+const DialogTrigger = DialogPrimitive.Trigger
+
+const DialogPortal = DialogPrimitive.Portal
+
+const DialogClose = DialogPrimitive.Close
+
+const DialogOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
+
+const DialogContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+ {children}
+
+
+ Close
+
+
+
+))
+DialogContent.displayName = DialogPrimitive.Content.displayName
+
+const DialogHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogHeader.displayName = "DialogHeader"
+
+const DialogFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+DialogFooter.displayName = "DialogFooter"
+
+const DialogTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogTitle.displayName = DialogPrimitive.Title.displayName
+
+const DialogDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DialogDescription.displayName = DialogPrimitive.Description.displayName
+
+export {
+ Dialog,
+ DialogPortal,
+ DialogOverlay,
+ DialogClose,
+ DialogTrigger,
+ DialogContent,
+ DialogHeader,
+ DialogFooter,
+ DialogTitle,
+ DialogDescription,
+}
diff --git a/web/src/lib/canvasUtils.ts b/web/src/lib/canvasUtils.ts
new file mode 100644
index 0000000..135f99b
--- /dev/null
+++ b/web/src/lib/canvasUtils.ts
@@ -0,0 +1,89 @@
+export const createImage = (url: string): Promise =>
+ new Promise((resolve, reject) => {
+ const image = new Image()
+ image.addEventListener('load', () => resolve(image))
+ image.addEventListener('error', (error) => reject(error))
+ image.setAttribute('crossOrigin', 'anonymous') // needed to avoid cross-origin issues on CodeSandbox
+ image.src = url
+ })
+
+export function getRadianAngle(degreeValue: number) {
+ return (degreeValue * Math.PI) / 180
+}
+
+/**
+ * Returns the new bounding area of a rotated rectangle.
+ */
+export function rotateSize(width: number, height: number, rotation: number) {
+ const rotRad = getRadianAngle(rotation)
+
+ return {
+ width:
+ Math.abs(Math.cos(rotRad) * width) + Math.abs(Math.sin(rotRad) * height),
+ height:
+ Math.abs(Math.sin(rotRad) * width) + Math.abs(Math.cos(rotRad) * height),
+ }
+}
+
+/**
+ * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
+ */
+export async function getCroppedImg(
+ imageSrc: string,
+ pixelCrop: { x: number; y: number; width: number; height: number },
+ rotation = 0,
+ flip = { horizontal: false, vertical: false }
+): Promise {
+ const image = await createImage(imageSrc)
+ const canvas = document.createElement('canvas')
+ const ctx = canvas.getContext('2d')
+
+ if (!ctx) {
+ return null
+ }
+
+ const rotRad = getRadianAngle(rotation)
+
+ // calculate bounding box of the rotated image
+ const { width: bBoxWidth, height: bBoxHeight } = rotateSize(
+ image.width,
+ image.height,
+ rotation
+ )
+
+ // set canvas size to match the bounding box
+ canvas.width = bBoxWidth
+ canvas.height = bBoxHeight
+
+ // translate canvas context to a central location to allow rotating and flipping around the center
+ ctx.translate(bBoxWidth / 2, bBoxHeight / 2)
+ ctx.rotate(rotRad)
+ ctx.scale(flip.horizontal ? -1 : 1, flip.vertical ? -1 : 1)
+ ctx.translate(-image.width / 2, -image.height / 2)
+
+ // draw rotated image
+ ctx.drawImage(image, 0, 0)
+
+ // croppedAreaPixels values are bounding box relative
+ // extract the cropped image using these values
+ const data = ctx.getImageData(
+ pixelCrop.x,
+ pixelCrop.y,
+ pixelCrop.width,
+ pixelCrop.height
+ )
+
+ // set canvas width to final desired crop size - this will clear existing context
+ canvas.width = pixelCrop.width
+ canvas.height = pixelCrop.height
+
+ // paste generated rotate image at the top left corner
+ ctx.putImageData(data, 0, 0)
+
+ // As a Blob
+ return new Promise((resolve) => {
+ canvas.toBlob((file) => {
+ resolve(file)
+ }, 'image/jpeg')
+ })
+}