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
JavaScript
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