@proveanything/smartlinks-utils-ui 1.13.5 → 1.13.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-N2FPPTHH.js → chunk-WLN4WW7K.js} +801 -97
- package/dist/chunk-WLN4WW7K.js.map +1 -0
- package/dist/components/AssetPicker/index.css +61 -3
- package/dist/components/AssetPicker/index.css.map +1 -1
- package/dist/components/AssetPicker/index.js +1 -1
- package/dist/components/ConditionsEditor/index.css +61 -3
- package/dist/components/ConditionsEditor/index.css.map +1 -1
- package/dist/components/FontPicker/index.css +61 -3
- package/dist/components/FontPicker/index.css.map +1 -1
- package/dist/components/IconPicker/index.css +61 -3
- package/dist/components/IconPicker/index.css.map +1 -1
- package/dist/components/LinkPicker/index.css +61 -3
- package/dist/components/LinkPicker/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.css +61 -3
- package/dist/components/RecordsAdmin/index.css.map +1 -1
- package/dist/index.css +61 -3
- package/dist/index.css.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-N2FPPTHH.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { assertStylesLoaded } from './chunk-OLYC54YT.js';
|
|
2
2
|
import { cn } from './chunk-L7FQ52F5.js';
|
|
3
|
-
import
|
|
3
|
+
import React8, { useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect } from 'react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
5
|
import * as SL from '@proveanything/smartlinks';
|
|
6
|
-
import { Filter, Search, LayoutGrid, List, Loader2, AlertCircle, Tag, X, ImageOff, Wand2, Maximize2, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image as Image$1, Plus, AlertTriangle, FileIcon, Film, Music, FileText, AppWindow, MoreVertical, Trash2 } from 'lucide-react';
|
|
6
|
+
import { Filter, Search, LayoutGrid, List, Loader2, AlertCircle, Tag, X, ImageOff, Wand2, Maximize2, Clipboard, Pencil, Crop, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image as Image$1, RotateCcw, RotateCw, FlipHorizontal, FlipVertical, RefreshCw, Plus, AlertTriangle, FileIcon, Film, Music, FileText, AppWindow, MoreVertical, Trash2 } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
|
|
9
9
|
// src/components/AssetPicker/types.ts
|
|
@@ -350,7 +350,7 @@ var AppBadge = ({ appId, appName, size = "sm" }) => {
|
|
|
350
350
|
}
|
|
351
351
|
);
|
|
352
352
|
};
|
|
353
|
-
var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolute" }) => {
|
|
353
|
+
var CardMenu = ({ onRename, onReplace, onEditTags, onEditImage, onDelete, position = "absolute" }) => {
|
|
354
354
|
const [open, setOpen] = useState(false);
|
|
355
355
|
const ref = useRef(null);
|
|
356
356
|
const btnRef = useRef(null);
|
|
@@ -402,7 +402,7 @@ var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolut
|
|
|
402
402
|
window.removeEventListener("scroll", update, true);
|
|
403
403
|
};
|
|
404
404
|
}, [open]);
|
|
405
|
-
if (!onRename && !onReplace && !onEditTags && !onDelete) return null;
|
|
405
|
+
if (!onRename && !onReplace && !onEditTags && !onEditImage && !onDelete) return null;
|
|
406
406
|
return /* @__PURE__ */ jsxs(
|
|
407
407
|
"div",
|
|
408
408
|
{
|
|
@@ -480,6 +480,23 @@ var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolut
|
|
|
480
480
|
]
|
|
481
481
|
}
|
|
482
482
|
),
|
|
483
|
+
onEditImage && /* @__PURE__ */ jsxs(
|
|
484
|
+
"button",
|
|
485
|
+
{
|
|
486
|
+
type: "button",
|
|
487
|
+
role: "menuitem",
|
|
488
|
+
onClick: (e) => {
|
|
489
|
+
e.stopPropagation();
|
|
490
|
+
setOpen(false);
|
|
491
|
+
onEditImage();
|
|
492
|
+
},
|
|
493
|
+
className: "w-full flex items-center gap-2 px-2.5 py-1.5 text-xs hover:bg-accent",
|
|
494
|
+
children: [
|
|
495
|
+
/* @__PURE__ */ jsx(Crop, { className: "w-3 h-3" }),
|
|
496
|
+
" Edit image"
|
|
497
|
+
]
|
|
498
|
+
}
|
|
499
|
+
),
|
|
483
500
|
onEditTags && /* @__PURE__ */ jsxs(
|
|
484
501
|
"button",
|
|
485
502
|
{
|
|
@@ -523,7 +540,7 @@ var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolut
|
|
|
523
540
|
}
|
|
524
541
|
);
|
|
525
542
|
};
|
|
526
|
-
var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
543
|
+
var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, onEditImage, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
527
544
|
const thumb = getThumbnail(asset2);
|
|
528
545
|
const Icon = getIcon(asset2.mimeType);
|
|
529
546
|
const ownerAppId = getAssetAppId(asset2);
|
|
@@ -604,6 +621,7 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
604
621
|
onRename,
|
|
605
622
|
onReplace,
|
|
606
623
|
onEditTags,
|
|
624
|
+
onEditImage,
|
|
607
625
|
onDelete: allowDelete ? onDelete : void 0
|
|
608
626
|
}
|
|
609
627
|
)
|
|
@@ -611,7 +629,7 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
611
629
|
}
|
|
612
630
|
);
|
|
613
631
|
};
|
|
614
|
-
var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
632
|
+
var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, onEditImage, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
615
633
|
const thumb = getThumbnail(asset2);
|
|
616
634
|
const Icon = getIcon(asset2.mimeType);
|
|
617
635
|
const ownerAppId = getAssetAppId(asset2);
|
|
@@ -680,6 +698,7 @@ var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
680
698
|
onRename,
|
|
681
699
|
onReplace,
|
|
682
700
|
onEditTags,
|
|
701
|
+
onEditImage,
|
|
683
702
|
onDelete: allowDelete ? onDelete : void 0,
|
|
684
703
|
position: "inline"
|
|
685
704
|
}
|
|
@@ -698,6 +717,7 @@ var AssetGrid = ({
|
|
|
698
717
|
onRename,
|
|
699
718
|
onReplace,
|
|
700
719
|
onEditTags,
|
|
720
|
+
onEditImage,
|
|
701
721
|
allowDelete,
|
|
702
722
|
currentAppId,
|
|
703
723
|
getAppName,
|
|
@@ -717,6 +737,7 @@ var AssetGrid = ({
|
|
|
717
737
|
onRename: onRename ? () => onRename(asset2) : void 0,
|
|
718
738
|
onReplace: onReplace ? () => onReplace(asset2) : void 0,
|
|
719
739
|
onEditTags: onEditTags ? () => onEditTags(asset2) : void 0,
|
|
740
|
+
onEditImage: onEditImage ? () => onEditImage(asset2) : void 0,
|
|
720
741
|
allowDelete,
|
|
721
742
|
currentAppId,
|
|
722
743
|
getAppName,
|
|
@@ -737,6 +758,7 @@ var AssetGrid = ({
|
|
|
737
758
|
onRename: onRename ? () => onRename(asset2) : void 0,
|
|
738
759
|
onReplace: onReplace ? () => onReplace(asset2) : void 0,
|
|
739
760
|
onEditTags: onEditTags ? () => onEditTags(asset2) : void 0,
|
|
761
|
+
onEditImage: onEditImage ? () => onEditImage(asset2) : void 0,
|
|
740
762
|
allowDelete,
|
|
741
763
|
currentAppId,
|
|
742
764
|
getAppName,
|
|
@@ -838,6 +860,431 @@ async function processImage(file, opts = {}) {
|
|
|
838
860
|
URL.revokeObjectURL(url);
|
|
839
861
|
}
|
|
840
862
|
}
|
|
863
|
+
var ASPECTS = [
|
|
864
|
+
{ key: "free", label: "Free", ratio: null },
|
|
865
|
+
{ key: "1:1", label: "1:1", ratio: 1 },
|
|
866
|
+
{ key: "4:3", label: "4:3", ratio: 4 / 3 },
|
|
867
|
+
{ key: "3:4", label: "3:4", ratio: 3 / 4 },
|
|
868
|
+
{ key: "16:9", label: "16:9", ratio: 16 / 9 },
|
|
869
|
+
{ key: "9:16", label: "9:16", ratio: 9 / 16 }
|
|
870
|
+
];
|
|
871
|
+
function loadImage2(file) {
|
|
872
|
+
const url = URL.createObjectURL(file);
|
|
873
|
+
const img = new Image();
|
|
874
|
+
return new Promise((resolve, reject) => {
|
|
875
|
+
img.onload = () => resolve({ img, url });
|
|
876
|
+
img.onerror = () => {
|
|
877
|
+
URL.revokeObjectURL(url);
|
|
878
|
+
reject(new Error("Failed to decode"));
|
|
879
|
+
};
|
|
880
|
+
img.src = url;
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
var ImageEditor = ({
|
|
884
|
+
file,
|
|
885
|
+
initialName,
|
|
886
|
+
maxDimension = 2048,
|
|
887
|
+
quality = 0.85,
|
|
888
|
+
toWebp,
|
|
889
|
+
onCancel,
|
|
890
|
+
onConfirm
|
|
891
|
+
}) => {
|
|
892
|
+
const [img, setImg] = useState(null);
|
|
893
|
+
const [imgUrl, setImgUrl] = useState(null);
|
|
894
|
+
const [rotation, setRotation] = useState(0);
|
|
895
|
+
const [flipH, setFlipH] = useState(false);
|
|
896
|
+
const [flipV, setFlipV] = useState(false);
|
|
897
|
+
const [aspect, setAspect] = useState("free");
|
|
898
|
+
const [crop, setCrop] = useState(null);
|
|
899
|
+
const [resizeMax, setResizeMax] = useState(maxDimension || 0);
|
|
900
|
+
const [name, setName] = useState(initialName || file.name.replace(/\.[^.]+$/, ""));
|
|
901
|
+
const [busy, setBusy] = useState(false);
|
|
902
|
+
const stageRef = useRef(null);
|
|
903
|
+
const dragStateRef = useRef(null);
|
|
904
|
+
useEffect(() => {
|
|
905
|
+
let cancelled = false;
|
|
906
|
+
loadImage2(file).then(({ img: img2, url }) => {
|
|
907
|
+
if (cancelled) {
|
|
908
|
+
URL.revokeObjectURL(url);
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
setImg(img2);
|
|
912
|
+
setImgUrl(url);
|
|
913
|
+
}).catch(() => {
|
|
914
|
+
});
|
|
915
|
+
return () => {
|
|
916
|
+
cancelled = true;
|
|
917
|
+
};
|
|
918
|
+
}, [file]);
|
|
919
|
+
useEffect(() => () => {
|
|
920
|
+
if (imgUrl) URL.revokeObjectURL(imgUrl);
|
|
921
|
+
}, [imgUrl]);
|
|
922
|
+
const rotated = rotation === 90 || rotation === 270;
|
|
923
|
+
const effW = img ? rotated ? img.naturalHeight : img.naturalWidth : 0;
|
|
924
|
+
const effH = img ? rotated ? img.naturalWidth : img.naturalHeight : 0;
|
|
925
|
+
useEffect(() => {
|
|
926
|
+
if (!img) return;
|
|
927
|
+
setCrop(null);
|
|
928
|
+
}, [img, rotation]);
|
|
929
|
+
const ratio = ASPECTS.find((a) => a.key === aspect)?.ratio || null;
|
|
930
|
+
const beginCrop = useCallback(() => {
|
|
931
|
+
if (!effW || !effH) return;
|
|
932
|
+
if (ratio) {
|
|
933
|
+
let w = effW, h = effW / ratio;
|
|
934
|
+
if (h > effH) {
|
|
935
|
+
h = effH;
|
|
936
|
+
w = effH * ratio;
|
|
937
|
+
}
|
|
938
|
+
setCrop({ x: (effW - w) / 2, y: (effH - h) / 2, w, h });
|
|
939
|
+
} else {
|
|
940
|
+
const w = effW * 0.8;
|
|
941
|
+
const h = effH * 0.8;
|
|
942
|
+
setCrop({ x: (effW - w) / 2, y: (effH - h) / 2, w, h });
|
|
943
|
+
}
|
|
944
|
+
}, [effW, effH, ratio]);
|
|
945
|
+
useEffect(() => {
|
|
946
|
+
if (!crop || !ratio) return;
|
|
947
|
+
let { x, y, w, h } = crop;
|
|
948
|
+
h = w / ratio;
|
|
949
|
+
if (y + h > effH) h = effH - y;
|
|
950
|
+
w = h * ratio;
|
|
951
|
+
if (x + w > effW) {
|
|
952
|
+
w = effW - x;
|
|
953
|
+
h = w / ratio;
|
|
954
|
+
}
|
|
955
|
+
setCrop({ x, y, w, h });
|
|
956
|
+
}, [aspect]);
|
|
957
|
+
const onPointerDown = useCallback((mode) => (e) => {
|
|
958
|
+
if (!crop || !stageRef.current) return;
|
|
959
|
+
e.preventDefault();
|
|
960
|
+
e.stopPropagation();
|
|
961
|
+
e.target.setPointerCapture(e.pointerId);
|
|
962
|
+
const rect = stageRef.current.getBoundingClientRect();
|
|
963
|
+
dragStateRef.current = {
|
|
964
|
+
mode,
|
|
965
|
+
startX: e.clientX,
|
|
966
|
+
startY: e.clientY,
|
|
967
|
+
startCrop: { ...crop },
|
|
968
|
+
stageW: rect.width,
|
|
969
|
+
stageH: rect.height
|
|
970
|
+
};
|
|
971
|
+
}, [crop]);
|
|
972
|
+
const onPointerMove = useCallback((e) => {
|
|
973
|
+
const ds = dragStateRef.current;
|
|
974
|
+
if (!ds || !crop) return;
|
|
975
|
+
const scaleX = effW / ds.stageW;
|
|
976
|
+
const scaleY = effH / ds.stageH;
|
|
977
|
+
const dx = (e.clientX - ds.startX) * scaleX;
|
|
978
|
+
const dy = (e.clientY - ds.startY) * scaleY;
|
|
979
|
+
let { x, y, w, h } = ds.startCrop;
|
|
980
|
+
const clampX = (v) => Math.max(0, Math.min(effW, v));
|
|
981
|
+
const clampY = (v) => Math.max(0, Math.min(effH, v));
|
|
982
|
+
if (ds.mode === "move") {
|
|
983
|
+
x = clampX(x + dx);
|
|
984
|
+
y = clampY(y + dy);
|
|
985
|
+
if (x + w > effW) x = effW - w;
|
|
986
|
+
if (y + h > effH) y = effH - h;
|
|
987
|
+
} else {
|
|
988
|
+
let x2 = x + w, y2 = y + h;
|
|
989
|
+
if (ds.mode?.includes("w")) x = clampX(x + dx);
|
|
990
|
+
if (ds.mode?.includes("e")) x2 = clampX(x2 + dx);
|
|
991
|
+
if (ds.mode?.includes("n")) y = clampY(y + dy);
|
|
992
|
+
if (ds.mode?.includes("s")) y2 = clampY(y2 + dy);
|
|
993
|
+
w = Math.max(8, x2 - x);
|
|
994
|
+
h = Math.max(8, y2 - y);
|
|
995
|
+
if (ratio) {
|
|
996
|
+
const sign = ds.mode === "nw" || ds.mode === "ne" || ds.mode === "n" ? -1 : 1;
|
|
997
|
+
const newH = w / ratio;
|
|
998
|
+
if (sign < 0) {
|
|
999
|
+
y = y + h - newH;
|
|
1000
|
+
if (y < 0) {
|
|
1001
|
+
y = 0;
|
|
1002
|
+
h = ds.startCrop.y + ds.startCrop.h;
|
|
1003
|
+
w = h * ratio;
|
|
1004
|
+
} else h = newH;
|
|
1005
|
+
} else {
|
|
1006
|
+
h = newH;
|
|
1007
|
+
if (y + h > effH) {
|
|
1008
|
+
h = effH - y;
|
|
1009
|
+
w = h * ratio;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
setCrop({ x, y, w, h });
|
|
1015
|
+
}, [crop, effW, effH, ratio]);
|
|
1016
|
+
const onPointerUp = useCallback((e) => {
|
|
1017
|
+
dragStateRef.current = null;
|
|
1018
|
+
try {
|
|
1019
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1020
|
+
} catch {
|
|
1021
|
+
}
|
|
1022
|
+
}, []);
|
|
1023
|
+
const handleApply = useCallback(async () => {
|
|
1024
|
+
if (!img) return;
|
|
1025
|
+
setBusy(true);
|
|
1026
|
+
try {
|
|
1027
|
+
const baseW = effW;
|
|
1028
|
+
const baseH = effH;
|
|
1029
|
+
const work = document.createElement("canvas");
|
|
1030
|
+
work.width = baseW;
|
|
1031
|
+
work.height = baseH;
|
|
1032
|
+
const wctx = work.getContext("2d");
|
|
1033
|
+
if (!wctx) throw new Error("canvas unsupported");
|
|
1034
|
+
wctx.save();
|
|
1035
|
+
wctx.translate(baseW / 2, baseH / 2);
|
|
1036
|
+
wctx.rotate(rotation * Math.PI / 180);
|
|
1037
|
+
wctx.scale(flipH ? -1 : 1, flipV ? -1 : 1);
|
|
1038
|
+
wctx.drawImage(img, -img.naturalWidth / 2, -img.naturalHeight / 2);
|
|
1039
|
+
wctx.restore();
|
|
1040
|
+
const c = crop || { x: 0, y: 0, w: baseW, h: baseH };
|
|
1041
|
+
let outW = Math.round(c.w);
|
|
1042
|
+
let outH = Math.round(c.h);
|
|
1043
|
+
if (resizeMax > 0) {
|
|
1044
|
+
const longest = Math.max(outW, outH);
|
|
1045
|
+
if (longest > resizeMax) {
|
|
1046
|
+
const s = resizeMax / longest;
|
|
1047
|
+
outW = Math.round(outW * s);
|
|
1048
|
+
outH = Math.round(outH * s);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
const out = document.createElement("canvas");
|
|
1052
|
+
out.width = outW;
|
|
1053
|
+
out.height = outH;
|
|
1054
|
+
const octx = out.getContext("2d");
|
|
1055
|
+
if (!octx) throw new Error("canvas unsupported");
|
|
1056
|
+
octx.drawImage(work, c.x, c.y, c.w, c.h, 0, 0, outW, outH);
|
|
1057
|
+
const useWebp = toWebp ?? file.type !== "image/webp";
|
|
1058
|
+
const outType = useWebp ? "image/webp" : file.type || "image/png";
|
|
1059
|
+
const blob = await new Promise((r) => out.toBlob((b) => r(b), outType, quality));
|
|
1060
|
+
if (!blob) throw new Error("encode failed");
|
|
1061
|
+
const ext = useWebp ? "webp" : file.name.match(/\.([^.]+)$/)?.[1] || "png";
|
|
1062
|
+
const trimmed = (name.trim() || "image").replace(/\.[^.]+$/, "");
|
|
1063
|
+
const newFile = new File([blob], `${trimmed}.${ext}`, { type: outType });
|
|
1064
|
+
onConfirm(newFile, trimmed);
|
|
1065
|
+
} finally {
|
|
1066
|
+
setBusy(false);
|
|
1067
|
+
}
|
|
1068
|
+
}, [img, crop, effW, effH, rotation, flipH, flipV, resizeMax, file, toWebp, quality, name, onConfirm]);
|
|
1069
|
+
const reset = useCallback(() => {
|
|
1070
|
+
setRotation(0);
|
|
1071
|
+
setFlipH(false);
|
|
1072
|
+
setFlipV(false);
|
|
1073
|
+
setCrop(null);
|
|
1074
|
+
setAspect("free");
|
|
1075
|
+
setResizeMax(maxDimension || 0);
|
|
1076
|
+
}, [maxDimension]);
|
|
1077
|
+
const cropStyle = useMemo(() => {
|
|
1078
|
+
if (!crop || !effW || !effH) return null;
|
|
1079
|
+
return {
|
|
1080
|
+
left: `${crop.x / effW * 100}%`,
|
|
1081
|
+
top: `${crop.y / effH * 100}%`,
|
|
1082
|
+
width: `${crop.w / effW * 100}%`,
|
|
1083
|
+
height: `${crop.h / effH * 100}%`
|
|
1084
|
+
};
|
|
1085
|
+
}, [crop, effW, effH]);
|
|
1086
|
+
if (typeof document === "undefined") return null;
|
|
1087
|
+
return createPortal(
|
|
1088
|
+
/* @__PURE__ */ jsx(
|
|
1089
|
+
"div",
|
|
1090
|
+
{
|
|
1091
|
+
className: "fixed inset-0 z-[2147483646] flex items-center justify-center p-4 bg-black/60",
|
|
1092
|
+
role: "dialog",
|
|
1093
|
+
"aria-modal": "true",
|
|
1094
|
+
"aria-label": "Edit image",
|
|
1095
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1096
|
+
onTouchStart: (e) => e.stopPropagation(),
|
|
1097
|
+
onClick: (e) => e.stopPropagation(),
|
|
1098
|
+
children: /* @__PURE__ */ jsxs("div", { className: "relative w-full max-w-3xl max-h-[90vh] flex flex-col rounded-lg border border-border bg-popover text-popover-foreground shadow-xl overflow-hidden", children: [
|
|
1099
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border", children: [
|
|
1100
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold flex items-center gap-1.5", children: [
|
|
1101
|
+
/* @__PURE__ */ jsx(Crop, { className: "w-4 h-4" }),
|
|
1102
|
+
" Edit image"
|
|
1103
|
+
] }),
|
|
1104
|
+
/* @__PURE__ */ jsx(
|
|
1105
|
+
"button",
|
|
1106
|
+
{
|
|
1107
|
+
type: "button",
|
|
1108
|
+
onClick: onCancel,
|
|
1109
|
+
className: "p-1 rounded hover:bg-accent",
|
|
1110
|
+
"aria-label": "Close",
|
|
1111
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4" })
|
|
1112
|
+
}
|
|
1113
|
+
)
|
|
1114
|
+
] }),
|
|
1115
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0 flex items-center justify-center bg-muted/30 p-3 overflow-auto", children: !img ? /* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsxs(
|
|
1116
|
+
"div",
|
|
1117
|
+
{
|
|
1118
|
+
ref: stageRef,
|
|
1119
|
+
className: "relative select-none touch-none",
|
|
1120
|
+
style: {
|
|
1121
|
+
aspectRatio: `${effW} / ${effH}`,
|
|
1122
|
+
maxWidth: "100%",
|
|
1123
|
+
maxHeight: "60vh",
|
|
1124
|
+
width: "min(100%, 60vh * (var(--ar)))",
|
|
1125
|
+
// CSS var fallback — explicit width/height managed by aspect-ratio + maxes
|
|
1126
|
+
["--ar"]: `${effW / effH}`
|
|
1127
|
+
},
|
|
1128
|
+
children: [
|
|
1129
|
+
/* @__PURE__ */ jsx(
|
|
1130
|
+
"img",
|
|
1131
|
+
{
|
|
1132
|
+
src: imgUrl,
|
|
1133
|
+
alt: "",
|
|
1134
|
+
draggable: false,
|
|
1135
|
+
className: "block w-full h-full object-contain pointer-events-none",
|
|
1136
|
+
style: {
|
|
1137
|
+
transform: `rotate(${rotation}deg) scale(${flipH ? -1 : 1}, ${flipV ? -1 : 1})`,
|
|
1138
|
+
transformOrigin: "center"
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
),
|
|
1142
|
+
cropStyle && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1143
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute inset-0 pointer-events-none", children: [
|
|
1144
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bg-black/50", style: { left: 0, top: 0, right: 0, height: cropStyle.top } }),
|
|
1145
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bg-black/50", style: { left: 0, bottom: 0, right: 0, top: `calc(${cropStyle.top} + ${cropStyle.height})` } }),
|
|
1146
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bg-black/50", style: { left: 0, top: cropStyle.top, width: cropStyle.left, height: cropStyle.height } }),
|
|
1147
|
+
/* @__PURE__ */ jsx("div", { className: "absolute bg-black/50", style: { right: 0, top: cropStyle.top, left: `calc(${cropStyle.left} + ${cropStyle.width})`, height: cropStyle.height } })
|
|
1148
|
+
] }),
|
|
1149
|
+
/* @__PURE__ */ jsx(
|
|
1150
|
+
"div",
|
|
1151
|
+
{
|
|
1152
|
+
className: "absolute border-2 border-primary cursor-move",
|
|
1153
|
+
style: cropStyle,
|
|
1154
|
+
onPointerDown: onPointerDown("move"),
|
|
1155
|
+
onPointerMove,
|
|
1156
|
+
onPointerUp,
|
|
1157
|
+
children: ["nw", "ne", "sw", "se"].map((h) => /* @__PURE__ */ jsx(
|
|
1158
|
+
"div",
|
|
1159
|
+
{
|
|
1160
|
+
className: cn(
|
|
1161
|
+
"absolute w-3 h-3 bg-primary border border-background rounded-sm",
|
|
1162
|
+
h === "nw" && "-top-1.5 -left-1.5 cursor-nwse-resize",
|
|
1163
|
+
h === "ne" && "-top-1.5 -right-1.5 cursor-nesw-resize",
|
|
1164
|
+
h === "sw" && "-bottom-1.5 -left-1.5 cursor-nesw-resize",
|
|
1165
|
+
h === "se" && "-bottom-1.5 -right-1.5 cursor-nwse-resize"
|
|
1166
|
+
),
|
|
1167
|
+
onPointerDown: onPointerDown(h),
|
|
1168
|
+
onPointerMove,
|
|
1169
|
+
onPointerUp
|
|
1170
|
+
},
|
|
1171
|
+
h
|
|
1172
|
+
))
|
|
1173
|
+
}
|
|
1174
|
+
)
|
|
1175
|
+
] })
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
) }),
|
|
1179
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 px-4 py-2 border-t border-border", children: [
|
|
1180
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
1181
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1182
|
+
/* @__PURE__ */ jsx("button", { type: "button", title: "Rotate left", onClick: () => setRotation((r) => (r + 270) % 360), className: "p-1.5 rounded border border-border hover:bg-accent", children: /* @__PURE__ */ jsx(RotateCcw, { className: "w-3.5 h-3.5" }) }),
|
|
1183
|
+
/* @__PURE__ */ jsx("button", { type: "button", title: "Rotate right", onClick: () => setRotation((r) => (r + 90) % 360), className: "p-1.5 rounded border border-border hover:bg-accent", children: /* @__PURE__ */ jsx(RotateCw, { className: "w-3.5 h-3.5" }) }),
|
|
1184
|
+
/* @__PURE__ */ jsx("button", { type: "button", title: "Flip horizontal", onClick: () => setFlipH((v) => !v), className: cn("p-1.5 rounded border border-border hover:bg-accent", flipH && "bg-accent"), children: /* @__PURE__ */ jsx(FlipHorizontal, { className: "w-3.5 h-3.5" }) }),
|
|
1185
|
+
/* @__PURE__ */ jsx("button", { type: "button", title: "Flip vertical", onClick: () => setFlipV((v) => !v), className: cn("p-1.5 rounded border border-border hover:bg-accent", flipV && "bg-accent"), children: /* @__PURE__ */ jsx(FlipVertical, { className: "w-3.5 h-3.5" }) })
|
|
1186
|
+
] }),
|
|
1187
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-border" }),
|
|
1188
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 flex-wrap", children: [
|
|
1189
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: "Crop:" }),
|
|
1190
|
+
ASPECTS.map((a) => /* @__PURE__ */ jsx(
|
|
1191
|
+
"button",
|
|
1192
|
+
{
|
|
1193
|
+
type: "button",
|
|
1194
|
+
onClick: () => {
|
|
1195
|
+
setAspect(a.key);
|
|
1196
|
+
if (!crop) beginCrop();
|
|
1197
|
+
},
|
|
1198
|
+
className: cn(
|
|
1199
|
+
"px-2 py-0.5 text-[11px] rounded border transition-colors",
|
|
1200
|
+
aspect === a.key && crop ? "border-primary bg-primary text-primary-foreground" : "border-border text-muted-foreground hover:bg-accent"
|
|
1201
|
+
),
|
|
1202
|
+
children: a.label
|
|
1203
|
+
},
|
|
1204
|
+
a.key
|
|
1205
|
+
)),
|
|
1206
|
+
!crop && /* @__PURE__ */ jsx("button", { type: "button", onClick: beginCrop, className: "px-2 py-0.5 text-[11px] rounded border border-border text-muted-foreground hover:bg-accent", children: "Start crop" }),
|
|
1207
|
+
crop && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setCrop(null), className: "px-2 py-0.5 text-[11px] rounded border border-border text-muted-foreground hover:bg-accent", children: "Clear crop" })
|
|
1208
|
+
] }),
|
|
1209
|
+
/* @__PURE__ */ jsx("span", { className: "mx-1 h-5 w-px bg-border" }),
|
|
1210
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
1211
|
+
"Max edge:",
|
|
1212
|
+
/* @__PURE__ */ jsx(
|
|
1213
|
+
"input",
|
|
1214
|
+
{
|
|
1215
|
+
type: "number",
|
|
1216
|
+
min: 0,
|
|
1217
|
+
step: 64,
|
|
1218
|
+
value: resizeMax,
|
|
1219
|
+
onChange: (e) => setResizeMax(Math.max(0, parseInt(e.target.value || "0", 10))),
|
|
1220
|
+
className: "w-20 px-1.5 py-0.5 text-xs rounded border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring"
|
|
1221
|
+
}
|
|
1222
|
+
),
|
|
1223
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
1224
|
+
"px ",
|
|
1225
|
+
resizeMax === 0 && "(off)"
|
|
1226
|
+
] })
|
|
1227
|
+
] }),
|
|
1228
|
+
/* @__PURE__ */ jsxs("button", { type: "button", onClick: reset, className: "ml-auto inline-flex items-center gap-1 text-[11px] text-muted-foreground hover:text-foreground", children: [
|
|
1229
|
+
/* @__PURE__ */ jsx(RefreshCw, { className: "w-3 h-3" }),
|
|
1230
|
+
" Reset"
|
|
1231
|
+
] })
|
|
1232
|
+
] }),
|
|
1233
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1234
|
+
/* @__PURE__ */ jsx(
|
|
1235
|
+
"input",
|
|
1236
|
+
{
|
|
1237
|
+
type: "text",
|
|
1238
|
+
value: name,
|
|
1239
|
+
onChange: (e) => setName(e.target.value),
|
|
1240
|
+
placeholder: "File name",
|
|
1241
|
+
className: "flex-1 px-2 py-1 text-xs rounded border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring"
|
|
1242
|
+
}
|
|
1243
|
+
),
|
|
1244
|
+
/* @__PURE__ */ jsx(
|
|
1245
|
+
"button",
|
|
1246
|
+
{
|
|
1247
|
+
type: "button",
|
|
1248
|
+
onClick: onCancel,
|
|
1249
|
+
disabled: busy,
|
|
1250
|
+
className: "px-3 py-1 text-xs rounded border border-border text-muted-foreground hover:bg-accent",
|
|
1251
|
+
children: "Cancel"
|
|
1252
|
+
}
|
|
1253
|
+
),
|
|
1254
|
+
/* @__PURE__ */ jsxs(
|
|
1255
|
+
"button",
|
|
1256
|
+
{
|
|
1257
|
+
type: "button",
|
|
1258
|
+
onClick: handleApply,
|
|
1259
|
+
disabled: busy || !img,
|
|
1260
|
+
className: "px-3 py-1 text-xs rounded bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50 inline-flex items-center gap-1",
|
|
1261
|
+
children: [
|
|
1262
|
+
busy ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "w-3 h-3" }),
|
|
1263
|
+
"Apply"
|
|
1264
|
+
]
|
|
1265
|
+
}
|
|
1266
|
+
)
|
|
1267
|
+
] })
|
|
1268
|
+
] })
|
|
1269
|
+
] })
|
|
1270
|
+
}
|
|
1271
|
+
),
|
|
1272
|
+
document.body
|
|
1273
|
+
);
|
|
1274
|
+
};
|
|
1275
|
+
async function urlToFile(url, name) {
|
|
1276
|
+
const res = await fetch(url, { mode: "cors" });
|
|
1277
|
+
const blob = await res.blob();
|
|
1278
|
+
const ext = (name.match(/\.([^.]+)$/)?.[1] || (blob.type.split("/")[1] || "png")).toLowerCase();
|
|
1279
|
+
const finalName = /\.[^.]+$/.test(name) ? name : `${name}.${ext}`;
|
|
1280
|
+
return new File([blob], finalName, { type: blob.type || "image/png" });
|
|
1281
|
+
}
|
|
1282
|
+
function isEditableImage(mimeType) {
|
|
1283
|
+
if (!mimeType) return false;
|
|
1284
|
+
if (!mimeType.startsWith("image/")) return false;
|
|
1285
|
+
if (mimeType === "image/svg+xml" || mimeType === "image/gif") return false;
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
841
1288
|
var UploadZone = ({
|
|
842
1289
|
onFiles,
|
|
843
1290
|
accept,
|
|
@@ -884,6 +1331,7 @@ var UploadZone = ({
|
|
|
884
1331
|
}
|
|
885
1332
|
}, []);
|
|
886
1333
|
const [optimizing, setOptimizing] = useState(false);
|
|
1334
|
+
const [editing, setEditing] = useState(false);
|
|
887
1335
|
const inputRef = useRef(null);
|
|
888
1336
|
const nameInputRef = useRef(null);
|
|
889
1337
|
const zoneRef = useRef(null);
|
|
@@ -1107,6 +1555,19 @@ var UploadZone = ({
|
|
|
1107
1555
|
] }),
|
|
1108
1556
|
isProcessableImage(pastedFile.file) && optimizeToggle,
|
|
1109
1557
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1558
|
+
isEditableImage(pastedFile.file.type) && /* @__PURE__ */ jsxs(
|
|
1559
|
+
"button",
|
|
1560
|
+
{
|
|
1561
|
+
type: "button",
|
|
1562
|
+
onClick: () => setEditing(true),
|
|
1563
|
+
className: "px-3 py-1.5 text-xs font-medium rounded-md border border-border text-muted-foreground hover:bg-accent transition-colors flex items-center gap-1",
|
|
1564
|
+
disabled: optimizing,
|
|
1565
|
+
children: [
|
|
1566
|
+
/* @__PURE__ */ jsx(Crop, { className: "w-3 h-3" }),
|
|
1567
|
+
" Edit"
|
|
1568
|
+
]
|
|
1569
|
+
}
|
|
1570
|
+
),
|
|
1110
1571
|
/* @__PURE__ */ jsxs(
|
|
1111
1572
|
"button",
|
|
1112
1573
|
{
|
|
@@ -1135,6 +1596,26 @@ var UploadZone = ({
|
|
|
1135
1596
|
)
|
|
1136
1597
|
] })
|
|
1137
1598
|
] }),
|
|
1599
|
+
editing && /* @__PURE__ */ jsx(
|
|
1600
|
+
ImageEditor,
|
|
1601
|
+
{
|
|
1602
|
+
file: pastedFile.file,
|
|
1603
|
+
initialName: fileName,
|
|
1604
|
+
maxDimension: optConfig.maxDimension ?? 2048,
|
|
1605
|
+
quality: optConfig.quality ?? 0.85,
|
|
1606
|
+
onCancel: () => setEditing(false),
|
|
1607
|
+
onConfirm: (newFile, newName) => {
|
|
1608
|
+
if (pastedFile.previewUrl) URL.revokeObjectURL(pastedFile.previewUrl);
|
|
1609
|
+
const previewUrl = URL.createObjectURL(newFile);
|
|
1610
|
+
setPastedFile({ file: newFile, previewUrl, name: newName, origSize: newFile.size });
|
|
1611
|
+
setFileName(newName);
|
|
1612
|
+
getImageDimensions(newFile).then((dims) => {
|
|
1613
|
+
if (dims) setPastedFile((prev) => prev && prev.file === newFile ? { ...prev, origDims: dims } : prev);
|
|
1614
|
+
});
|
|
1615
|
+
setEditing(false);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1138
1619
|
lightboxOpen && pastedFile.previewUrl && /* @__PURE__ */ jsxs(
|
|
1139
1620
|
"div",
|
|
1140
1621
|
{
|
|
@@ -1364,6 +1845,7 @@ function extractImageUrl(response) {
|
|
|
1364
1845
|
var AIImageGenerate = ({
|
|
1365
1846
|
collectionId,
|
|
1366
1847
|
onSave,
|
|
1848
|
+
onSaveFile,
|
|
1367
1849
|
saving,
|
|
1368
1850
|
className
|
|
1369
1851
|
}) => {
|
|
@@ -1379,6 +1861,13 @@ var AIImageGenerate = ({
|
|
|
1379
1861
|
const [previewUrl, setPreviewUrl] = useState(null);
|
|
1380
1862
|
const [saved, setSaved] = useState(false);
|
|
1381
1863
|
const [customName, setCustomName] = useState("");
|
|
1864
|
+
const [editing, setEditing] = useState(false);
|
|
1865
|
+
const [editedFile, setEditedFile] = useState(null);
|
|
1866
|
+
const [editedPreview, setEditedPreview] = useState(null);
|
|
1867
|
+
const [preparingEdit, setPreparingEdit] = useState(false);
|
|
1868
|
+
useEffect(() => () => {
|
|
1869
|
+
if (editedPreview) URL.revokeObjectURL(editedPreview);
|
|
1870
|
+
}, [editedPreview]);
|
|
1382
1871
|
const SpeechRecognitionCtor = typeof window !== "undefined" ? window.SpeechRecognition || window.webkitSpeechRecognition : null;
|
|
1383
1872
|
const voiceSupported = !!SpeechRecognitionCtor;
|
|
1384
1873
|
const recognitionRef = useRef(null);
|
|
@@ -1483,16 +1972,40 @@ var AIImageGenerate = ({
|
|
|
1483
1972
|
if (!previewUrl) return;
|
|
1484
1973
|
const custom = customName.trim();
|
|
1485
1974
|
const fileName = custom ? /\.[a-z0-9]{2,5}$/i.test(custom) ? custom : `${custom}.png` : `ai-${prompt.trim().slice(0, 60).replace(/\s+/g, "-") || "ai-image"}.png`;
|
|
1486
|
-
|
|
1975
|
+
let result;
|
|
1976
|
+
if (editedFile && onSaveFile) {
|
|
1977
|
+
result = await onSaveFile(editedFile, fileName);
|
|
1978
|
+
} else {
|
|
1979
|
+
result = await onSave(previewUrl, fileName);
|
|
1980
|
+
}
|
|
1487
1981
|
if (result) {
|
|
1488
1982
|
setSaved(true);
|
|
1489
1983
|
setTimeout(() => {
|
|
1490
1984
|
setPreviewUrl(null);
|
|
1491
1985
|
setSaved(false);
|
|
1492
1986
|
setCustomName("");
|
|
1987
|
+
if (editedPreview) URL.revokeObjectURL(editedPreview);
|
|
1988
|
+
setEditedFile(null);
|
|
1989
|
+
setEditedPreview(null);
|
|
1493
1990
|
}, 1500);
|
|
1494
1991
|
}
|
|
1495
|
-
}, [previewUrl, prompt, customName, onSave]);
|
|
1992
|
+
}, [previewUrl, prompt, customName, onSave, onSaveFile, editedFile, editedPreview]);
|
|
1993
|
+
const startEdit = useCallback(async () => {
|
|
1994
|
+
if (!previewUrl) return;
|
|
1995
|
+
setPreparingEdit(true);
|
|
1996
|
+
try {
|
|
1997
|
+
const baseName = customName.trim() || `ai-${prompt.trim().slice(0, 40).replace(/\s+/g, "-") || "image"}`;
|
|
1998
|
+
const file = editedFile || await urlToFile(previewUrl, `${baseName}.png`);
|
|
1999
|
+
if (!editedFile) {
|
|
2000
|
+
setEditedFile(file);
|
|
2001
|
+
}
|
|
2002
|
+
setEditing(true);
|
|
2003
|
+
} catch {
|
|
2004
|
+
setError("Could not load image for editing.");
|
|
2005
|
+
} finally {
|
|
2006
|
+
setPreparingEdit(false);
|
|
2007
|
+
}
|
|
2008
|
+
}, [previewUrl, customName, prompt, editedFile]);
|
|
1496
2009
|
if (!collectionId) {
|
|
1497
2010
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 rounded-md bg-muted text-muted-foreground text-sm", children: [
|
|
1498
2011
|
/* @__PURE__ */ jsx(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
|
|
@@ -1669,14 +2182,17 @@ var AIImageGenerate = ({
|
|
|
1669
2182
|
error
|
|
1670
2183
|
] }),
|
|
1671
2184
|
previewUrl && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border border-border rounded-md p-3 bg-muted/30", children: [
|
|
1672
|
-
/* @__PURE__ */
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
2185
|
+
/* @__PURE__ */ jsxs("div", { className: "relative rounded overflow-hidden bg-background flex items-center justify-center", style: { minHeight: "12rem" }, children: [
|
|
2186
|
+
/* @__PURE__ */ jsx(
|
|
2187
|
+
"img",
|
|
2188
|
+
{
|
|
2189
|
+
src: editedPreview || previewUrl,
|
|
2190
|
+
alt: prompt,
|
|
2191
|
+
className: "max-w-full max-h-80 object-contain"
|
|
2192
|
+
}
|
|
2193
|
+
),
|
|
2194
|
+
editedPreview && /* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1 px-1.5 py-0.5 text-[10px] rounded bg-primary text-primary-foreground", children: "Edited" })
|
|
2195
|
+
] }),
|
|
1680
2196
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
1681
2197
|
/* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-foreground", children: "Name (optional)" }),
|
|
1682
2198
|
/* @__PURE__ */ jsx(
|
|
@@ -1692,6 +2208,19 @@ var AIImageGenerate = ({
|
|
|
1692
2208
|
)
|
|
1693
2209
|
] }),
|
|
1694
2210
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
2211
|
+
/* @__PURE__ */ jsxs(
|
|
2212
|
+
"button",
|
|
2213
|
+
{
|
|
2214
|
+
type: "button",
|
|
2215
|
+
onClick: startEdit,
|
|
2216
|
+
disabled: saving || saved || preparingEdit,
|
|
2217
|
+
className: "px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors inline-flex items-center gap-1",
|
|
2218
|
+
children: [
|
|
2219
|
+
preparingEdit ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Crop, { className: "w-3 h-3" }),
|
|
2220
|
+
"Edit"
|
|
2221
|
+
]
|
|
2222
|
+
}
|
|
2223
|
+
),
|
|
1695
2224
|
/* @__PURE__ */ jsx(
|
|
1696
2225
|
"button",
|
|
1697
2226
|
{
|
|
@@ -1699,6 +2228,9 @@ var AIImageGenerate = ({
|
|
|
1699
2228
|
onClick: () => {
|
|
1700
2229
|
setPreviewUrl(null);
|
|
1701
2230
|
setSaved(false);
|
|
2231
|
+
if (editedPreview) URL.revokeObjectURL(editedPreview);
|
|
2232
|
+
setEditedFile(null);
|
|
2233
|
+
setEditedPreview(null);
|
|
1702
2234
|
},
|
|
1703
2235
|
disabled: saving,
|
|
1704
2236
|
className: "px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
|
|
@@ -1722,7 +2254,22 @@ var AIImageGenerate = ({
|
|
|
1722
2254
|
]
|
|
1723
2255
|
}
|
|
1724
2256
|
)
|
|
1725
|
-
] })
|
|
2257
|
+
] }),
|
|
2258
|
+
editing && editedFile && /* @__PURE__ */ jsx(
|
|
2259
|
+
ImageEditor,
|
|
2260
|
+
{
|
|
2261
|
+
file: editedFile,
|
|
2262
|
+
initialName: customName.trim() || `ai-${prompt.trim().slice(0, 40).replace(/\s+/g, "-") || "image"}`,
|
|
2263
|
+
onCancel: () => setEditing(false),
|
|
2264
|
+
onConfirm: (file, name) => {
|
|
2265
|
+
if (editedPreview) URL.revokeObjectURL(editedPreview);
|
|
2266
|
+
setEditedFile(file);
|
|
2267
|
+
setEditedPreview(URL.createObjectURL(file));
|
|
2268
|
+
setCustomName(name);
|
|
2269
|
+
setEditing(false);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
)
|
|
1726
2273
|
] })
|
|
1727
2274
|
] });
|
|
1728
2275
|
};
|
|
@@ -1741,6 +2288,7 @@ function getFullUrl(p) {
|
|
|
1741
2288
|
var StockPhotoSearch = ({
|
|
1742
2289
|
collectionId,
|
|
1743
2290
|
onSave,
|
|
2291
|
+
onSaveFile,
|
|
1744
2292
|
saving,
|
|
1745
2293
|
className
|
|
1746
2294
|
}) => {
|
|
@@ -1749,15 +2297,19 @@ var StockPhotoSearch = ({
|
|
|
1749
2297
|
const [searching, setSearching] = useState(false);
|
|
1750
2298
|
const [error, setError] = useState(null);
|
|
1751
2299
|
const [results, setResults] = useState([]);
|
|
1752
|
-
const [
|
|
1753
|
-
const [
|
|
1754
|
-
const [
|
|
2300
|
+
const [staged, setStaged] = useState(null);
|
|
2301
|
+
const [preparingEdit, setPreparingEdit] = useState(false);
|
|
2302
|
+
const [editing, setEditing] = useState(false);
|
|
2303
|
+
const [savedFlash, setSavedFlash] = useState(false);
|
|
2304
|
+
useEffect(() => () => {
|
|
2305
|
+
if (staged?.previewUrl) URL.revokeObjectURL(staged.previewUrl);
|
|
2306
|
+
}, [staged]);
|
|
1755
2307
|
const handleSearch = useCallback(async () => {
|
|
1756
2308
|
if (!query.trim() || !collectionId) return;
|
|
1757
2309
|
setSearching(true);
|
|
1758
2310
|
setError(null);
|
|
1759
2311
|
setResults([]);
|
|
1760
|
-
|
|
2312
|
+
setSavedFlash(false);
|
|
1761
2313
|
try {
|
|
1762
2314
|
const params = {
|
|
1763
2315
|
query: query.trim(),
|
|
@@ -1776,18 +2328,42 @@ var StockPhotoSearch = ({
|
|
|
1776
2328
|
setSearching(false);
|
|
1777
2329
|
}
|
|
1778
2330
|
}, [query, orientation, collectionId]);
|
|
1779
|
-
const
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
2331
|
+
const handlePick = useCallback((photo) => {
|
|
2332
|
+
const baseName = `stock-${(photo.alt || query.trim() || "photo").slice(0, 60).replace(/\s+/g, "-")}`;
|
|
2333
|
+
setStaged({ photo, name: baseName, file: null, previewUrl: null });
|
|
2334
|
+
}, [query]);
|
|
2335
|
+
const handleConfirmSave = useCallback(async () => {
|
|
2336
|
+
if (!staged) return;
|
|
2337
|
+
const fullUrl = getFullUrl(staged.photo);
|
|
2338
|
+
const trimmed = staged.name.trim() || "stock-photo";
|
|
2339
|
+
const fileName = /\.[a-z0-9]{2,5}$/i.test(trimmed) ? trimmed : `${trimmed}.${staged.file ? "webp" : "jpg"}`;
|
|
2340
|
+
let result;
|
|
2341
|
+
if (staged.file && onSaveFile) {
|
|
2342
|
+
result = await onSaveFile(staged.file, fileName);
|
|
2343
|
+
} else {
|
|
2344
|
+
result = await onSave(fullUrl, fileName);
|
|
2345
|
+
}
|
|
1786
2346
|
if (result) {
|
|
1787
|
-
|
|
1788
|
-
|
|
2347
|
+
setSavedFlash(true);
|
|
2348
|
+
if (staged.previewUrl) URL.revokeObjectURL(staged.previewUrl);
|
|
2349
|
+
setStaged(null);
|
|
2350
|
+
setTimeout(() => setSavedFlash(false), 1500);
|
|
2351
|
+
}
|
|
2352
|
+
}, [staged, onSave, onSaveFile]);
|
|
2353
|
+
const handleStartEdit = useCallback(async () => {
|
|
2354
|
+
if (!staged) return;
|
|
2355
|
+
setPreparingEdit(true);
|
|
2356
|
+
try {
|
|
2357
|
+
const fullUrl = getFullUrl(staged.photo);
|
|
2358
|
+
const file = staged.file || await urlToFile(fullUrl, `${staged.name || "stock-photo"}.jpg`);
|
|
2359
|
+
setStaged((prev) => prev ? { ...prev, file } : prev);
|
|
2360
|
+
setEditing(true);
|
|
2361
|
+
} catch {
|
|
2362
|
+
setError("Could not load image for editing.");
|
|
2363
|
+
} finally {
|
|
2364
|
+
setPreparingEdit(false);
|
|
1789
2365
|
}
|
|
1790
|
-
}, [
|
|
2366
|
+
}, [staged]);
|
|
1791
2367
|
if (!collectionId) {
|
|
1792
2368
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 rounded-md bg-muted text-muted-foreground text-sm", children: [
|
|
1793
2369
|
/* @__PURE__ */ jsx(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
|
|
@@ -1850,74 +2426,151 @@ var StockPhotoSearch = ({
|
|
|
1850
2426
|
/* @__PURE__ */ jsx(AlertCircle, { className: "w-4 h-4 flex-shrink-0" }),
|
|
1851
2427
|
error
|
|
1852
2428
|
] }),
|
|
2429
|
+
savedFlash && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-2 rounded-md bg-primary/10 text-primary text-xs", children: [
|
|
2430
|
+
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5" }),
|
|
2431
|
+
" Saved to library."
|
|
2432
|
+
] }),
|
|
2433
|
+
staged && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 border border-border rounded-md p-3 bg-muted/30", children: [
|
|
2434
|
+
/* @__PURE__ */ jsxs("div", { className: "relative rounded overflow-hidden bg-background flex items-center justify-center", style: { minHeight: "12rem" }, children: [
|
|
2435
|
+
/* @__PURE__ */ jsx(
|
|
2436
|
+
"img",
|
|
2437
|
+
{
|
|
2438
|
+
src: staged.previewUrl || getThumb(staged.photo),
|
|
2439
|
+
alt: staged.photo.alt || "",
|
|
2440
|
+
className: "max-w-full max-h-80 object-contain"
|
|
2441
|
+
}
|
|
2442
|
+
),
|
|
2443
|
+
staged.previewUrl && /* @__PURE__ */ jsx("span", { className: "absolute top-1 left-1 px-1.5 py-0.5 text-[10px] rounded bg-primary text-primary-foreground", children: "Edited" })
|
|
2444
|
+
] }),
|
|
2445
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
2446
|
+
/* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-foreground", children: "Name" }),
|
|
2447
|
+
/* @__PURE__ */ jsx(
|
|
2448
|
+
"input",
|
|
2449
|
+
{
|
|
2450
|
+
type: "text",
|
|
2451
|
+
value: staged.name,
|
|
2452
|
+
onChange: (e) => setStaged((prev) => prev ? { ...prev, name: e.target.value } : prev),
|
|
2453
|
+
disabled: !!saving,
|
|
2454
|
+
className: "w-full px-2 py-1.5 text-xs rounded-md border border-border bg-transparent placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
|
2455
|
+
}
|
|
2456
|
+
)
|
|
2457
|
+
] }),
|
|
2458
|
+
staged.photo.photographer && !staged.previewUrl && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
2459
|
+
"Photo by ",
|
|
2460
|
+
staged.photo.photographerUrl ? /* @__PURE__ */ jsx("a", { href: staged.photo.photographerUrl, target: "_blank", rel: "noreferrer noopener", className: "underline", children: staged.photo.photographer }) : staged.photo.photographer
|
|
2461
|
+
] }),
|
|
2462
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
2463
|
+
/* @__PURE__ */ jsxs(
|
|
2464
|
+
"button",
|
|
2465
|
+
{
|
|
2466
|
+
type: "button",
|
|
2467
|
+
onClick: handleStartEdit,
|
|
2468
|
+
disabled: !!saving || preparingEdit,
|
|
2469
|
+
className: "px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors inline-flex items-center gap-1",
|
|
2470
|
+
children: [
|
|
2471
|
+
preparingEdit ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Crop, { className: "w-3 h-3" }),
|
|
2472
|
+
"Edit"
|
|
2473
|
+
]
|
|
2474
|
+
}
|
|
2475
|
+
),
|
|
2476
|
+
/* @__PURE__ */ jsxs(
|
|
2477
|
+
"button",
|
|
2478
|
+
{
|
|
2479
|
+
type: "button",
|
|
2480
|
+
onClick: () => {
|
|
2481
|
+
if (staged.previewUrl) URL.revokeObjectURL(staged.previewUrl);
|
|
2482
|
+
setStaged(null);
|
|
2483
|
+
},
|
|
2484
|
+
disabled: !!saving,
|
|
2485
|
+
className: "px-3 py-1.5 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors inline-flex items-center gap-1",
|
|
2486
|
+
children: [
|
|
2487
|
+
/* @__PURE__ */ jsx(X, { className: "w-3 h-3" }),
|
|
2488
|
+
" Discard"
|
|
2489
|
+
]
|
|
2490
|
+
}
|
|
2491
|
+
),
|
|
2492
|
+
/* @__PURE__ */ jsxs(
|
|
2493
|
+
"button",
|
|
2494
|
+
{
|
|
2495
|
+
type: "button",
|
|
2496
|
+
onClick: handleConfirmSave,
|
|
2497
|
+
disabled: !!saving,
|
|
2498
|
+
className: "px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-70 inline-flex items-center gap-1.5",
|
|
2499
|
+
children: [
|
|
2500
|
+
saving ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "w-3 h-3" }),
|
|
2501
|
+
saving ? "Saving\u2026" : "Save to library"
|
|
2502
|
+
]
|
|
2503
|
+
}
|
|
2504
|
+
)
|
|
2505
|
+
] }),
|
|
2506
|
+
editing && staged.file && /* @__PURE__ */ jsx(
|
|
2507
|
+
ImageEditor,
|
|
2508
|
+
{
|
|
2509
|
+
file: staged.file,
|
|
2510
|
+
initialName: staged.name,
|
|
2511
|
+
onCancel: () => setEditing(false),
|
|
2512
|
+
onConfirm: (file, name) => {
|
|
2513
|
+
setStaged((prev) => {
|
|
2514
|
+
if (!prev) return prev;
|
|
2515
|
+
if (prev.previewUrl) URL.revokeObjectURL(prev.previewUrl);
|
|
2516
|
+
return { ...prev, file, previewUrl: URL.createObjectURL(file), name };
|
|
2517
|
+
});
|
|
2518
|
+
setEditing(false);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
)
|
|
2522
|
+
] }),
|
|
1853
2523
|
results.length > 0 && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2", children: results.map((photo, i) => {
|
|
1854
2524
|
const thumb = getThumb(photo);
|
|
1855
2525
|
const full = getFullUrl(photo);
|
|
1856
|
-
const
|
|
1857
|
-
|
|
1858
|
-
return /* @__PURE__ */ jsxs(
|
|
2526
|
+
const isStaged = staged?.photo && getFullUrl(staged.photo) === full;
|
|
2527
|
+
return /* @__PURE__ */ jsx(
|
|
1859
2528
|
"div",
|
|
1860
2529
|
{
|
|
1861
2530
|
className: "flex flex-col gap-1",
|
|
1862
|
-
children: [
|
|
1863
|
-
/* @__PURE__ */ jsxs("div", { className: "group relative aspect-square rounded-md overflow-hidden border border-border bg-muted/30", children: [
|
|
1864
|
-
/* @__PURE__ */ jsx(
|
|
1865
|
-
"img",
|
|
1866
|
-
{
|
|
1867
|
-
src: thumb,
|
|
1868
|
-
alt: photo.alt || "",
|
|
1869
|
-
loading: "lazy",
|
|
1870
|
-
className: "w-full h-full object-cover"
|
|
1871
|
-
}
|
|
1872
|
-
),
|
|
1873
|
-
/* @__PURE__ */ jsx(
|
|
1874
|
-
"button",
|
|
1875
|
-
{
|
|
1876
|
-
type: "button",
|
|
1877
|
-
onClick: () => handleSave(photo),
|
|
1878
|
-
disabled: !!pendingUrl || saving,
|
|
1879
|
-
className: cn(
|
|
1880
|
-
"absolute inset-0 flex items-center justify-center text-xs font-medium",
|
|
1881
|
-
"bg-background/80 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
1882
|
-
"text-foreground gap-1.5",
|
|
1883
|
-
(isPending || isSaved) && "opacity-100"
|
|
1884
|
-
),
|
|
1885
|
-
children: isPending ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1886
|
-
/* @__PURE__ */ jsx(Loader2, { className: "w-3.5 h-3.5 animate-spin" }),
|
|
1887
|
-
" Saving\u2026"
|
|
1888
|
-
] }) : isSaved ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1889
|
-
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5" }),
|
|
1890
|
-
" Saved"
|
|
1891
|
-
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1892
|
-
/* @__PURE__ */ jsx(Image$1, { className: "w-3.5 h-3.5" }),
|
|
1893
|
-
" Save"
|
|
1894
|
-
] })
|
|
1895
|
-
}
|
|
1896
|
-
),
|
|
1897
|
-
photo.photographer && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 inset-x-0 px-1.5 py-0.5 text-[10px] text-background bg-foreground/60 truncate", children: photo.photographerUrl ? /* @__PURE__ */ jsx(
|
|
1898
|
-
"a",
|
|
1899
|
-
{
|
|
1900
|
-
href: photo.photographerUrl,
|
|
1901
|
-
target: "_blank",
|
|
1902
|
-
rel: "noreferrer noopener",
|
|
1903
|
-
onClick: (e) => e.stopPropagation(),
|
|
1904
|
-
className: "hover:underline",
|
|
1905
|
-
children: photo.photographer
|
|
1906
|
-
}
|
|
1907
|
-
) : photo.photographer })
|
|
1908
|
-
] }),
|
|
2531
|
+
children: /* @__PURE__ */ jsxs("div", { className: "group relative aspect-square rounded-md overflow-hidden border border-border bg-muted/30", children: [
|
|
1909
2532
|
/* @__PURE__ */ jsx(
|
|
1910
|
-
"
|
|
2533
|
+
"img",
|
|
1911
2534
|
{
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
disabled: isPending || saving,
|
|
1917
|
-
className: "w-full px-1.5 py-1 text-[11px] rounded-md border border-border bg-transparent placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring"
|
|
2535
|
+
src: thumb,
|
|
2536
|
+
alt: photo.alt || "",
|
|
2537
|
+
loading: "lazy",
|
|
2538
|
+
className: "w-full h-full object-cover"
|
|
1918
2539
|
}
|
|
1919
|
-
)
|
|
1920
|
-
|
|
2540
|
+
),
|
|
2541
|
+
/* @__PURE__ */ jsx(
|
|
2542
|
+
"button",
|
|
2543
|
+
{
|
|
2544
|
+
type: "button",
|
|
2545
|
+
onClick: () => handlePick(photo),
|
|
2546
|
+
disabled: !!staged || saving,
|
|
2547
|
+
className: cn(
|
|
2548
|
+
"absolute inset-0 flex items-center justify-center text-xs font-medium",
|
|
2549
|
+
"bg-background/80 opacity-0 group-hover:opacity-100 transition-opacity",
|
|
2550
|
+
"text-foreground gap-1.5",
|
|
2551
|
+
isStaged && "opacity-100"
|
|
2552
|
+
),
|
|
2553
|
+
children: isStaged ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2554
|
+
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5" }),
|
|
2555
|
+
" Selected"
|
|
2556
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2557
|
+
/* @__PURE__ */ jsx(Image$1, { className: "w-3.5 h-3.5" }),
|
|
2558
|
+
" Pick"
|
|
2559
|
+
] })
|
|
2560
|
+
}
|
|
2561
|
+
),
|
|
2562
|
+
photo.photographer && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 inset-x-0 px-1.5 py-0.5 text-[10px] text-background bg-foreground/60 truncate", children: photo.photographerUrl ? /* @__PURE__ */ jsx(
|
|
2563
|
+
"a",
|
|
2564
|
+
{
|
|
2565
|
+
href: photo.photographerUrl,
|
|
2566
|
+
target: "_blank",
|
|
2567
|
+
rel: "noreferrer noopener",
|
|
2568
|
+
onClick: (e) => e.stopPropagation(),
|
|
2569
|
+
className: "hover:underline",
|
|
2570
|
+
children: photo.photographer
|
|
2571
|
+
}
|
|
2572
|
+
) : photo.photographer })
|
|
2573
|
+
] })
|
|
1921
2574
|
},
|
|
1922
2575
|
`${full}-${i}`
|
|
1923
2576
|
);
|
|
@@ -2063,7 +2716,7 @@ var TagEditor = ({ initial, suggestions, assetName, onCancel, onSave }) => {
|
|
|
2063
2716
|
);
|
|
2064
2717
|
};
|
|
2065
2718
|
var InlineConfirm = ({ open, title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive, onConfirm, onCancel }) => {
|
|
2066
|
-
|
|
2719
|
+
React8.useEffect(() => {
|
|
2067
2720
|
if (!open) return;
|
|
2068
2721
|
const onKey = (e) => {
|
|
2069
2722
|
if (e.key === "Escape") {
|
|
@@ -2168,8 +2821,8 @@ var AttachToContextToggle = ({ checked, onChange, contextLabel }) => /* @__PURE_
|
|
|
2168
2821
|
] })
|
|
2169
2822
|
] });
|
|
2170
2823
|
var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId: _listAppId, currentAppId, currentAppName, getAppName, assets, loading, error, refresh, remove, updateAsset, replaceFile }) => {
|
|
2171
|
-
const replaceInputRef =
|
|
2172
|
-
const replaceTargetRef =
|
|
2824
|
+
const replaceInputRef = React8.useRef(null);
|
|
2825
|
+
const replaceTargetRef = React8.useRef(null);
|
|
2173
2826
|
const handleRename = useCallback(async (asset2) => {
|
|
2174
2827
|
const current = asset2.cleanName || asset2.name || "";
|
|
2175
2828
|
const next = window.prompt("Rename asset", current);
|
|
@@ -2182,6 +2835,21 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
|
|
|
2182
2835
|
replaceTargetRef.current = asset2.id;
|
|
2183
2836
|
replaceInputRef.current?.click();
|
|
2184
2837
|
}, []);
|
|
2838
|
+
const [editAsset, setEditAsset] = useState(null);
|
|
2839
|
+
const [editFile, setEditFile] = useState(null);
|
|
2840
|
+
const [editLoading, setEditLoading] = useState(false);
|
|
2841
|
+
const handleEditImage = useCallback(async (asset2) => {
|
|
2842
|
+
if (!isEditableImage(asset2.mimeType || void 0)) return;
|
|
2843
|
+
setEditLoading(true);
|
|
2844
|
+
try {
|
|
2845
|
+
const file = await urlToFile(asset2.url, asset2.cleanName || asset2.name || "image");
|
|
2846
|
+
setEditFile(file);
|
|
2847
|
+
setEditAsset(asset2);
|
|
2848
|
+
} catch {
|
|
2849
|
+
} finally {
|
|
2850
|
+
setEditLoading(false);
|
|
2851
|
+
}
|
|
2852
|
+
}, []);
|
|
2185
2853
|
const handleReplaceFiles = useCallback(async (e) => {
|
|
2186
2854
|
const file = e.target.files?.[0];
|
|
2187
2855
|
const assetId = replaceTargetRef.current;
|
|
@@ -2308,6 +2976,7 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
|
|
|
2308
2976
|
onRename: handleRename,
|
|
2309
2977
|
onReplace: handleReplace,
|
|
2310
2978
|
onEditTags: handleEditTags,
|
|
2979
|
+
onEditImage: handleEditImage,
|
|
2311
2980
|
allowDelete,
|
|
2312
2981
|
currentAppId,
|
|
2313
2982
|
currentAppName,
|
|
@@ -2325,6 +2994,24 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
|
|
|
2325
2994
|
onChange: handleReplaceFiles
|
|
2326
2995
|
}
|
|
2327
2996
|
),
|
|
2997
|
+
editAsset && editFile && /* @__PURE__ */ jsx(
|
|
2998
|
+
ImageEditor,
|
|
2999
|
+
{
|
|
3000
|
+
file: editFile,
|
|
3001
|
+
initialName: editAsset.cleanName || editAsset.name || "image",
|
|
3002
|
+
onCancel: () => {
|
|
3003
|
+
setEditAsset(null);
|
|
3004
|
+
setEditFile(null);
|
|
3005
|
+
},
|
|
3006
|
+
onConfirm: async (file) => {
|
|
3007
|
+
const target = editAsset;
|
|
3008
|
+
setEditAsset(null);
|
|
3009
|
+
setEditFile(null);
|
|
3010
|
+
if (target) await replaceFile(target.id, file);
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
),
|
|
3014
|
+
editLoading && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[2147483645] flex items-center justify-center bg-black/30 pointer-events-none", children: /* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 animate-spin text-primary" }) }),
|
|
2328
3015
|
tagEditorAsset && /* @__PURE__ */ jsx(
|
|
2329
3016
|
TagEditor,
|
|
2330
3017
|
{
|
|
@@ -2503,6 +3190,21 @@ var AssetPickerContent = ({
|
|
|
2503
3190
|
setTab("browse");
|
|
2504
3191
|
await reconcileAfterUpload(targetScope);
|
|
2505
3192
|
}, [upload, multiple, onSelect, toSelection, uploadScope, activeScope, reconcileAfterUpload]);
|
|
3193
|
+
const handleSaveEditedFile = useCallback(async (file, name) => {
|
|
3194
|
+
const finalFile = name && name.trim() ? new File([file], /\.[^.]+$/.test(name) ? name : `${name}.${file.name.split(".").pop() || "webp"}`, { type: file.type }) : file;
|
|
3195
|
+
const targetScope = uploadScope;
|
|
3196
|
+
const sameAsActive = targetScope === activeScope;
|
|
3197
|
+
const result = await upload(finalFile, void 0, sameAsActive ? void 0 : targetScope);
|
|
3198
|
+
if (result) {
|
|
3199
|
+
setTab("browse");
|
|
3200
|
+
if (!multiple) {
|
|
3201
|
+
setSelectedIds(/* @__PURE__ */ new Set([result.id]));
|
|
3202
|
+
onSelect?.(toSelection(result));
|
|
3203
|
+
}
|
|
3204
|
+
await reconcileAfterUpload(targetScope);
|
|
3205
|
+
}
|
|
3206
|
+
return result;
|
|
3207
|
+
}, [upload, uploadScope, activeScope, multiple, onSelect, toSelection, reconcileAfterUpload]);
|
|
2506
3208
|
const handleUrlImport = useCallback(async (url, name) => {
|
|
2507
3209
|
const targetScope = uploadScope;
|
|
2508
3210
|
const sameAsActive = targetScope === activeScope;
|
|
@@ -2587,7 +3289,7 @@ var AssetPickerContent = ({
|
|
|
2587
3289
|
)
|
|
2588
3290
|
] }),
|
|
2589
3291
|
tab === "browse" && /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
2590
|
-
/* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground" }),
|
|
3292
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-2 top-1/2 -translate-y-1/2 w-3.5 h-3.5 text-muted-foreground pointer-events-none" }),
|
|
2591
3293
|
/* @__PURE__ */ jsx(
|
|
2592
3294
|
"input",
|
|
2593
3295
|
{
|
|
@@ -2595,7 +3297,7 @@ var AssetPickerContent = ({
|
|
|
2595
3297
|
value: search,
|
|
2596
3298
|
onChange: (e) => setSearch(e.target.value),
|
|
2597
3299
|
placeholder: "Search\u2026",
|
|
2598
|
-
className: "pl-
|
|
3300
|
+
className: "pl-8 pr-2 py-1 text-xs rounded-md border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring w-36"
|
|
2599
3301
|
}
|
|
2600
3302
|
)
|
|
2601
3303
|
] }),
|
|
@@ -2804,6 +3506,7 @@ var AssetPickerContent = ({
|
|
|
2804
3506
|
{
|
|
2805
3507
|
collectionId,
|
|
2806
3508
|
onSave: handleRemoteIngest,
|
|
3509
|
+
onSaveFile: handleSaveEditedFile,
|
|
2807
3510
|
saving: uploading
|
|
2808
3511
|
}
|
|
2809
3512
|
)
|
|
@@ -2830,6 +3533,7 @@ var AssetPickerContent = ({
|
|
|
2830
3533
|
{
|
|
2831
3534
|
collectionId,
|
|
2832
3535
|
onSave: handleRemoteIngest,
|
|
3536
|
+
onSaveFile: handleSaveEditedFile,
|
|
2833
3537
|
saving: uploading
|
|
2834
3538
|
}
|
|
2835
3539
|
)
|
|
@@ -2928,5 +3632,5 @@ var AssetPicker = (props) => {
|
|
|
2928
3632
|
assertStylesLoaded();
|
|
2929
3633
|
|
|
2930
3634
|
export { ASSET_MIME_FILTERS, AssetPicker, useAppRegistry, useAssets };
|
|
2931
|
-
//# sourceMappingURL=chunk-
|
|
2932
|
-
//# sourceMappingURL=chunk-
|
|
3635
|
+
//# sourceMappingURL=chunk-WLN4WW7K.js.map
|
|
3636
|
+
//# sourceMappingURL=chunk-WLN4WW7K.js.map
|