UNPKG

geoiq-frontend-ui-kit

Version:

This project is a UI kit for GeoIQ's frontend. It's built with React, TypeScript, and Vite.

751 lines (750 loc) 21.9 kB
import n, { useRef as b, useCallback as h, useEffect as ee } from "react"; import { Stage as Pe, Layer as Le, Group as M, Image as te, Line as We, Rect as w, Text as qe, Transformer as He } from "react-konva"; import Ue from "./index.es87.js"; import A from "./index.es88.js"; import _e from "./index.es89.js"; import $e from "./index.es90.js"; import { useDownloadHandlers as Oe } from "./index.es91.js"; import { useCropHandlers as Ne } from "./index.es92.js"; import { loadImage as Xe, isPointInImage as Ye, getInitialPositionInImage as Ge } from "./index.es93.js"; import { handleImageTransform as je, handleTextTransform as Je } from "./index.es94.js"; import { getRotatedPointerPosition as Ke } from "./index.es95.js"; import { handleUndo as Qe, handleRedo as Ve } from "./index.es96.js"; import { useEditorState as Ze } from "./index.es97.js"; import { useBackgroundState as et } from "./index.es98.js"; import { useCanvasState as tt } from "./index.es99.js"; import { useSelectionState as ot } from "./index.es100.js"; import { useCanvasDimensions as nt } from "./index.es101.js"; import { useDrawingHandlers as rt } from "./index.es102.js"; import { useMouseHandlers as it } from "./index.es103.js"; import { useToast as st } from "./index.es73.js"; const at = "Double Tap to Edit", Mt = ({ stickers: oe = [], // Array of sticker URLs backgroundImage: F, // Background image URL onAssetUpload: ne, onSave: re }) => { const [ie, P] = n.useState(!1), { toast: L } = st(), W = b([]), R = b([]), x = b(null), se = b(null), ae = b(null), le = b(null), { editorState: a, setEditorState: f, toggleDrawingMode: ce, toggleTextEditing: S, toggleSheetOpen: q } = Ze(), { backgroundState: s, setBackgroundState: E, rotateBackground: H } = et({ bgImageSize: { width: 0, height: 0 }, // Current displayed size bgImagePosition: { x: 0, y: 0 }, // Current position bgRotation: 0, // Rotation angle in degrees originalBgSize: { width: 0, height: 0 } // Original size before any transforms }), { canvasElements: l, setCanvasElements: d, history: y, saveStateToHistory: c, undo: U, redo: _, setHistory: ge } = tt( { images: [], // Array of sticker images squares: [], // Array of sticker positions/sizes textBoxes: [], // Array of text boxes lines: [], // Array of drawn lines bgImage: null // Background image }, s, E ), { selectionState: i, clearSelection: k, selectImage: I, selectTextBox: B } = ot(), m = nt(), D = h((e) => Xe(e), []), de = h( (e, t) => Ye(e, t, s, m), [s, m] ), $ = h( (e, t) => Ge(e, t, s), [s] ), he = h( (e) => Ke(e, s, m), [s, m] ), me = h( (e, t) => { je( e, t, d, c ); }, [c] ), ue = h( (e) => { const t = R.current[e]; t && Je( e, t, d, f, c ); }, [c] ), { handleMouseDown: O, handleMouseMove: N, handleMouseUp: X, selectedColor: pe, setSelectedColor: fe } = rt({ editorState: a, setEditorState: f, canvasElements: l, setCanvasElements: d, backgroundState: s, getRotatedPointerPosition: he, isPointInImage: de, selectImage: I, saveStateToHistory: c }), { handleStageClick: C, handleImageClick: Y, handleTextClick: G, handleLineClick: j, handleTextDblClick: J, handleTextTransformStart: we } = it({ editorState: a, setEditorState: f, setCanvasElements: d, clearSelection: k, selectImage: I, selectTextBox: B, toggleTextEditing: S, saveStateToHistory: c, transformerRef: x, textBoxRefs: R }), { handleDownload: xe } = Oe({ transformerRef: x, setEditorState: f, onAssetUpload: ne }), { cropState: r, toggleCropMode: Ee, handleCropMouseDown: K, handleCropMouseMove: Q, handleCropMouseUp: V, handleCancelCrop: ye, handleApplyCrop: Ie } = Ne( l, s, m, d, E, f, k, S, ge, c ), Z = h(() => { x.current?.getLayer()?.batchDraw(), c(); }, [c]), Re = h(() => { f((g) => ({ ...g, isDrawingMode: !1 })); const e = s.bgImageSize.width * 0.25, t = { x: s.bgImagePosition.x + (s.bgImageSize.width - e) / 2, y: s.bgImagePosition.y + 20 // 20px from top }, o = { x: t.x, y: t.y, text: at, fontSize: Math.max(12, s.bgImageSize.width * 0.03), width: e, height: 30, // Initial height fill: "black", draggable: !0, rotation: 0, id: Math.random().toString(36).substring(2, 9), // Random ID align: "center", verticalAlign: "middle", backgroundColor: "#FFB82F" }; d((g) => ({ ...g, textBoxes: [...g.textBoxes, o] })), c(); }, [s, c]), be = h( (e, t) => { d((o) => ({ ...o, textBoxes: o.textBoxes.map( (g, u) => u === e ? { ...g, text: t } : g ) })), S(!1), c(); }, [c, S] ), z = h(() => { i.selectedImageIndex !== null ? (d((e) => ({ ...e, images: e.images.filter( (t, o) => o !== i.selectedImageIndex ), squares: e.squares.filter( (t, o) => o !== i.selectedImageIndex ) })), I(null), c()) : i.selectedTextBoxIndex !== null ? (d((e) => ({ ...e, textBoxes: e.textBoxes.filter( (t, o) => o !== i.selectedTextBoxIndex ) })), B(null), c()) : i.selectedLineId !== null && (d((e) => ({ ...e, lines: e.lines.filter( (t) => t.id !== i.selectedLineId ) })), I(null), c()); }, [ i.selectedImageIndex, i.selectedTextBoxIndex, i.selectedLineId, c, I, B ]), Se = h(() => { f((e) => ({ ...e, isEraseMode: !e.isEraseMode, // Toggle erase mode isDrawingMode: !1 // Disable drawing mode when erasing })); }, [f]), ke = h(() => { f((e) => ({ ...e, isDrawingMode: !1, isEditingText: !1 })), d({ images: [], squares: [], textBoxes: [], lines: [], bgImage: l.bgImage }), I(null); }, [l.bgImage, I]), Ce = h(() => { H(); }, [H]), Te = h( async (e) => { try { const t = await D(e), o = s.bgImageSize.width * 0.25, g = o, u = $(o, g); d((p) => ({ ...p, images: [...p.images, t], squares: [ ...p.squares, { x: u.x, y: u.y, width: o, height: g, rotation: 0 } ] })), c(); } catch (t) { console.error("Error loading sticker:", t); } }, [ D, $, s.bgImageSize.width, c ] ), Me = h(() => { Qe(y, d, E, U), f((e) => ({ ...e, isEditingText: !1 })), k(); }, [y, U, E, k]), Fe = h(() => { Ve(y, d, E, _); }, [y, _, E]); ee(() => { F && (async () => { try { const o = await D(F || ""); o.crossOrigin = "Anonymous"; const g = o.width / o.height, u = Math.min(m.width, 800), p = u / g, T = (m.width - u) / 2, v = (m.height - p) / 2; d((Ae) => ({ ...Ae, bgImage: o })), E({ bgImageSize: { width: u, height: p }, bgImagePosition: { x: T, y: v }, bgRotation: 0, originalBgSize: { width: u, height: p } }), console.log("Initial history.past.length:", y.past.length); } catch (t) { console.error("Error loading background image:", t); } })(); }, [F]), ee(() => { if (!x.current) return; const e = i.selectedImageIndex !== null ? W.current[i.selectedImageIndex] : i.selectedTextBoxIndex !== null ? R.current[i.selectedTextBoxIndex] : null; e ? (x.current.nodes([e]), x.current.getLayer()?.batchDraw()) : x.current.nodes([]); }, [i.selectedImageIndex, i.selectedTextBoxIndex]); const Be = "right-2 top-2", De = y.past.length > 0, ze = y.future.length > 0; if (!a.isClient) return /* @__PURE__ */ n.createElement("div", { className: "flex items-center justify-center w-screen h-screen" }, /* @__PURE__ */ n.createElement("div", { className: "text-center" }, /* @__PURE__ */ n.createElement("p", { className: "text-gray-600" }, "Loading editor..."))); async function ve() { try { P(!0); const e = await xe(); e && (L({ title: "Success", description: "Image updated successfully!", icon: "success" }), re({ private_url: e.private_url, public_url: e.public_url })); } catch (e) { console.error("Save failed:", e), L({ title: "Error", description: "Failed to update image. Please try again.", icon: "danger" }); } finally { P(!1); } } return /* @__PURE__ */ n.createElement( "div", { ref: se, className: "relative w-screen h-screen overflow-hidden touch-none bg-light-4", style: { overflowY: "hidden" } }, /* @__PURE__ */ n.createElement( $e, { editorState: a, canUndo: De, canRedo: ze, cropState: r, onSave: ve, onAddTextBox: Re, onOpenStickerSheet: () => q(!0), onToggleDrawingMode: ce, onToggleCropMode: Ee, onApplyCrop: Ie, onCancelCrop: ye, onClearAllLines: Se, onClearAll: ke, onUndo: Me, onRedo: Fe, onRotateBackground: Ce, buttonPositionClass: Be, isEraserEnabled: l.lines.length > 0, isTrashEnabled: l.textBoxes.length > 0 || l.lines.length > 0 || l.images.length > 0, isDownloading: ie } ), /* @__PURE__ */ n.createElement( _e, { isOpen: a.isSheetOpen, onOpenChange: (e) => q(e), onSelectSticker: Te, stickers: oe } ), /* @__PURE__ */ n.createElement( Pe, { width: m.width, height: m.height, onMouseDown: a.isCropping ? K : O, onMouseMove: a.isCropping ? Q : N, onMouseUp: a.isCropping ? V : X, onTouchStart: a.isCropping ? K : O, onTouchMove: a.isCropping ? Q : N, onTouchEnd: a.isCropping ? V : X, onClick: C, onTap: C }, /* @__PURE__ */ n.createElement(Le, null, /* @__PURE__ */ n.createElement( M, { ref: ae, x: m.width / 2, y: m.height / 2, rotation: s.bgRotation, offsetX: m.width / 2, offsetY: m.height / 2 }, l.bgImage && /* @__PURE__ */ n.createElement( te, { image: l.bgImage, width: s.bgImageSize.width, height: s.bgImageSize.height, x: s.bgImagePosition.x, y: s.bgImagePosition.y, onClick: C, onTap: C } ), l.images.map((e, t) => /* @__PURE__ */ n.createElement(M, { key: `image-${t}` }, /* @__PURE__ */ n.createElement( te, { ref: (o) => W.current[t] = o, x: l.squares[t]?.x || 0, y: l.squares[t]?.y || 0, image: e, width: l.squares[t]?.width || 200, height: l.squares[t]?.height || 200, draggable: !a.isDrawingMode, rotation: l.squares[t]?.rotation || 0, onDragEnd: (o) => { d((g) => ({ ...g, squares: g.squares.map( (u, p) => p === t ? { ...u, x: o.target.x(), y: o.target.y() } : u ) })), Z(); }, onClick: (o) => Y(t, o), onTap: (o) => Y(t, o), onTransformEnd: (o) => { const g = o.target; me(t, g); } } ), i.selectedImageIndex === t && /* @__PURE__ */ n.createElement( A, { x: l.squares[t]?.x || 0, y: l.squares[t]?.y || 0, onClick: z } ))), l.lines.map((e) => /* @__PURE__ */ n.createElement(M, { key: `line-${e.id}` }, /* @__PURE__ */ n.createElement( We, { points: e.points, stroke: e.stroke, strokeWidth: e.strokeWidth, tension: 0.5, lineCap: "round", lineJoin: "round", globalCompositeOperation: "source-over", onClick: (t) => j(e.id, t), onTap: (t) => j(e.id, t), shadowColor: i.selectedLineId === e.id ? "yellow" : void 0, shadowBlur: i.selectedLineId === e.id ? 10 : void 0, shadowOpacity: i.selectedLineId === e.id ? 0.8 : void 0 } ), i.selectedLineId === e.id && /* @__PURE__ */ n.createElement( A, { x: e.points[0], y: e.points[1], onClick: z } ))), l.textBoxes.map((e, t) => /* @__PURE__ */ n.createElement(M, { key: `text-${t}` }, /* @__PURE__ */ n.createElement( w, { x: e.x, y: e.y, width: e.width, height: R.current[t]?.height() || e.fontSize * 2.5, fill: e.backgroundColor, rotation: e.rotation, cornerRadius: 4, stroke: "black", strokeWidth: 2 } ), /* @__PURE__ */ n.createElement( qe, { ref: (o) => R.current[t] = o, x: e.x, y: e.y, text: e.text, fontSize: e.fontSize, width: e.width, height: e.height, fill: e.fill, align: e.align, verticalAlign: e.verticalAlign, draggable: !a.isEditingText && !a.isDrawingMode, rotation: e.rotation, onClick: (o) => G(t, o), onTap: (o) => G(t, o), onDblClick: () => J(t), onDblTap: () => J(t), onTransformStart: we, onTransformEnd: () => ue(t), onDragEnd: (o) => { if (a.isEditingText) return; const g = o.target.x(), u = o.target.y(); d((p) => ({ ...p, textBoxes: p.textBoxes.map( (T, v) => v === t ? { ...T, x: g, y: u } : T ) })), Z(); }, visible: !a.isEditingText || i.selectedTextBoxIndex !== t, perfectDrawEnabled: !1, listening: !a.isEditingText, scaleX: 1, scaleY: 1 } ), i.selectedTextBoxIndex === t && !a.isEditingText && /* @__PURE__ */ n.createElement( A, { x: e.x, y: e.y, onClick: z } ), a.isEditingText && i.selectedTextBoxIndex === t && /* @__PURE__ */ n.createElement( Ue, { textNode: R.current[t], onChange: (o) => be(t, o), onClose: () => f((o) => ({ ...o, isEditingText: !1 })), bgImagePosition: s.bgImagePosition, bgImageSize: s.bgImageSize, dimensions: m } ))), a.isCropping && /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement( w, { ref: le, x: r.cropRect.x, y: r.cropRect.y, width: r.cropRect.width, height: r.cropRect.height, stroke: "#FFFAEB", strokeWidth: 2, dash: [10, 10], fill: "rgba(0, 0, 0, 0.5)", listening: !0, cornerRadius: 4, shadowColor: r.cropRect.width > 0 && r.cropRect.height > 0 ? r.isDragging || r.isResizing ? "#3b82f6" : "yellow" : "red", shadowBlur: 10, shadowOpacity: 0.6, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "move"); }, onMouseLeave: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "default"); } } ), r.cropRect.width > 0 && r.cropRect.height > 0 && /* @__PURE__ */ n.createElement(n.Fragment, null, /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x - 5, y: r.cropRect.y - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "nwse-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x + r.cropRect.width - 5, y: r.cropRect.y - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "nesw-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x - 5, y: r.cropRect.y + r.cropRect.height - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "nesw-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x + r.cropRect.width - 5, y: r.cropRect.y + r.cropRect.height - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "nwse-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x + r.cropRect.width / 2 - 5, y: r.cropRect.y - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "ns-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x + r.cropRect.width - 5, y: r.cropRect.y + r.cropRect.height / 2 - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "ew-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x + r.cropRect.width / 2 - 5, y: r.cropRect.y + r.cropRect.height - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "ns-resize"); } } ), /* @__PURE__ */ n.createElement( w, { x: r.cropRect.x - 5, y: r.cropRect.y + r.cropRect.height / 2 - 5, width: 10, height: 10, fill: "#FFFAEB", stroke: "#000", strokeWidth: 1, cornerRadius: 2, onMouseEnter: (e) => { const t = e.target.getStage(); t && (t.container().style.cursor = "ew-resize"); } } ))) ), /* @__PURE__ */ n.createElement( He, { ref: x, enabledAnchors: i.selectedTextBoxIndex !== null ? [ "top-left", "top-right", "bottom-left", "bottom-right", "middle-left", "middle-right", "top-center", "bottom-center" ] : void 0, boundBoxFunc: (e, t) => i.selectedTextBoxIndex !== null ? { ...t, width: Math.max(30, t.width), height: Math.max(30, t.height) } : t, keepRatio: !1 } )) ), a.isDrawingMode && /* @__PURE__ */ n.createElement( "div", { style: { position: "absolute", bottom: 20, left: "50%", transform: "translateX(-50%)", display: "flex", gap: "10px" } }, ["#FFB82F", "red", "blue", "green", "black", "white"].map( (e) => /* @__PURE__ */ n.createElement( "div", { key: e, onClick: () => fe(e), style: { width: 30, height: 30, borderRadius: "50%", backgroundColor: e, border: pe === e ? "2px solid black" : "none", cursor: "pointer" } } ) ) ) ); }; export { Mt as ImageEditor }; //# sourceMappingURL=index.es63.js.map