@proveanything/smartlinks-utils-ui 1.0.1 → 1.13.1
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-PQD2B6DA.js → chunk-HKL24TFC.js} +699 -120
- package/dist/chunk-HKL24TFC.js.map +1 -0
- package/dist/components/AssetPicker/index.css +86 -12
- package/dist/components/AssetPicker/index.css.map +1 -1
- package/dist/components/AssetPicker/index.d.ts +1 -1
- package/dist/components/AssetPicker/index.js +1 -1
- package/dist/components/ConditionsEditor/index.css +86 -12
- package/dist/components/ConditionsEditor/index.css.map +1 -1
- package/dist/components/FontPicker/index.css +86 -12
- package/dist/components/FontPicker/index.css.map +1 -1
- package/dist/components/IconPicker/index.css +86 -12
- package/dist/components/IconPicker/index.css.map +1 -1
- package/dist/components/LinkPicker/index.css +86 -12
- package/dist/components/LinkPicker/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.css +86 -12
- package/dist/components/RecordsAdmin/index.css.map +1 -1
- package/dist/index.css +86 -12
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{useAssets-BUct54e4.d.ts → useAssets-tRkW00zx.d.ts} +19 -2
- package/package.json +3 -3
- package/dist/chunk-PQD2B6DA.js.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { assertStylesLoaded } from './chunk-OLYC54YT.js';
|
|
2
2
|
import { cn } from './chunk-L7FQ52F5.js';
|
|
3
|
-
import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
3
|
+
import React7, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
4
4
|
import * as SL from '@proveanything/smartlinks';
|
|
5
|
-
import { Filter, Search, LayoutGrid, List, X, Loader2, AlertCircle, Tag, ImageOff, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image,
|
|
5
|
+
import { Filter, Search, LayoutGrid, List, X, Loader2, AlertCircle, Tag, ImageOff, Wand2, Maximize2, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image as Image$1, Plus, FileIcon, Film, Music, FileText, AppWindow, MoreVertical, Trash2 } from 'lucide-react';
|
|
6
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
// src/components/AssetPicker/types.ts
|
|
@@ -309,7 +309,7 @@ function useAppRegistry(collectionId) {
|
|
|
309
309
|
}
|
|
310
310
|
function getIcon(mimeType) {
|
|
311
311
|
if (!mimeType) return FileIcon;
|
|
312
|
-
if (mimeType.startsWith("image/")) return Image;
|
|
312
|
+
if (mimeType.startsWith("image/")) return Image$1;
|
|
313
313
|
if (mimeType.startsWith("video/")) return Film;
|
|
314
314
|
if (mimeType.startsWith("audio/")) return Music;
|
|
315
315
|
return FileText;
|
|
@@ -349,7 +349,129 @@ var AppBadge = ({ appId, appName, size = "sm" }) => {
|
|
|
349
349
|
}
|
|
350
350
|
);
|
|
351
351
|
};
|
|
352
|
-
var
|
|
352
|
+
var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolute" }) => {
|
|
353
|
+
const [open, setOpen] = useState(false);
|
|
354
|
+
const ref = useRef(null);
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
if (!open) return;
|
|
357
|
+
const handler = (e) => {
|
|
358
|
+
if (!ref.current?.contains(e.target)) setOpen(false);
|
|
359
|
+
};
|
|
360
|
+
document.addEventListener("mousedown", handler);
|
|
361
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
362
|
+
}, [open]);
|
|
363
|
+
if (!onRename && !onReplace && !onEditTags && !onDelete) return null;
|
|
364
|
+
return /* @__PURE__ */ jsxs(
|
|
365
|
+
"div",
|
|
366
|
+
{
|
|
367
|
+
ref,
|
|
368
|
+
className: cn(
|
|
369
|
+
position === "absolute" ? "absolute bottom-1.5 right-1.5" : "relative flex-shrink-0"
|
|
370
|
+
),
|
|
371
|
+
onClick: (e) => e.stopPropagation(),
|
|
372
|
+
onDoubleClick: (e) => e.stopPropagation(),
|
|
373
|
+
children: [
|
|
374
|
+
/* @__PURE__ */ jsx(
|
|
375
|
+
"button",
|
|
376
|
+
{
|
|
377
|
+
type: "button",
|
|
378
|
+
onClick: (e) => {
|
|
379
|
+
e.stopPropagation();
|
|
380
|
+
setOpen((o) => !o);
|
|
381
|
+
},
|
|
382
|
+
className: cn(
|
|
383
|
+
"w-6 h-6 rounded-full flex items-center justify-center transition-all",
|
|
384
|
+
"bg-background/90 border border-border text-foreground hover:bg-background shadow-sm",
|
|
385
|
+
position === "absolute" && "opacity-0 group-hover:opacity-100",
|
|
386
|
+
open && "opacity-100"
|
|
387
|
+
),
|
|
388
|
+
title: "Asset actions",
|
|
389
|
+
"aria-label": "Asset actions",
|
|
390
|
+
children: /* @__PURE__ */ jsx(MoreVertical, { className: "w-3 h-3" })
|
|
391
|
+
}
|
|
392
|
+
),
|
|
393
|
+
open && /* @__PURE__ */ jsxs(
|
|
394
|
+
"div",
|
|
395
|
+
{
|
|
396
|
+
className: "absolute bottom-full right-0 mb-1 z-20 min-w-[140px] rounded-md border border-border bg-popover text-popover-foreground shadow-md py-1",
|
|
397
|
+
role: "menu",
|
|
398
|
+
children: [
|
|
399
|
+
onRename && /* @__PURE__ */ jsxs(
|
|
400
|
+
"button",
|
|
401
|
+
{
|
|
402
|
+
type: "button",
|
|
403
|
+
role: "menuitem",
|
|
404
|
+
onClick: (e) => {
|
|
405
|
+
e.stopPropagation();
|
|
406
|
+
setOpen(false);
|
|
407
|
+
onRename();
|
|
408
|
+
},
|
|
409
|
+
className: "w-full flex items-center gap-2 px-2.5 py-1.5 text-xs hover:bg-accent",
|
|
410
|
+
children: [
|
|
411
|
+
/* @__PURE__ */ jsx(Pencil, { className: "w-3 h-3" }),
|
|
412
|
+
" Rename"
|
|
413
|
+
]
|
|
414
|
+
}
|
|
415
|
+
),
|
|
416
|
+
onReplace && /* @__PURE__ */ jsxs(
|
|
417
|
+
"button",
|
|
418
|
+
{
|
|
419
|
+
type: "button",
|
|
420
|
+
role: "menuitem",
|
|
421
|
+
onClick: (e) => {
|
|
422
|
+
e.stopPropagation();
|
|
423
|
+
setOpen(false);
|
|
424
|
+
onReplace();
|
|
425
|
+
},
|
|
426
|
+
className: "w-full flex items-center gap-2 px-2.5 py-1.5 text-xs hover:bg-accent",
|
|
427
|
+
children: [
|
|
428
|
+
/* @__PURE__ */ jsx(Upload, { className: "w-3 h-3" }),
|
|
429
|
+
" Replace file"
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
),
|
|
433
|
+
onEditTags && /* @__PURE__ */ jsxs(
|
|
434
|
+
"button",
|
|
435
|
+
{
|
|
436
|
+
type: "button",
|
|
437
|
+
role: "menuitem",
|
|
438
|
+
onClick: (e) => {
|
|
439
|
+
e.stopPropagation();
|
|
440
|
+
setOpen(false);
|
|
441
|
+
onEditTags();
|
|
442
|
+
},
|
|
443
|
+
className: "w-full flex items-center gap-2 px-2.5 py-1.5 text-xs hover:bg-accent",
|
|
444
|
+
children: [
|
|
445
|
+
/* @__PURE__ */ jsx(Tag, { className: "w-3 h-3" }),
|
|
446
|
+
" Edit tags"
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
),
|
|
450
|
+
onDelete && /* @__PURE__ */ jsxs(
|
|
451
|
+
"button",
|
|
452
|
+
{
|
|
453
|
+
type: "button",
|
|
454
|
+
role: "menuitem",
|
|
455
|
+
onClick: (e) => {
|
|
456
|
+
e.stopPropagation();
|
|
457
|
+
setOpen(false);
|
|
458
|
+
onDelete();
|
|
459
|
+
},
|
|
460
|
+
className: "w-full flex items-center gap-2 px-2.5 py-1.5 text-xs text-destructive hover:bg-destructive/10",
|
|
461
|
+
children: [
|
|
462
|
+
/* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3" }),
|
|
463
|
+
" Delete"
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
]
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
]
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
};
|
|
474
|
+
var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
353
475
|
const thumb = getThumbnail(asset2);
|
|
354
476
|
const Icon = getIcon(asset2.mimeType);
|
|
355
477
|
const ownerAppId = getAssetAppId(asset2);
|
|
@@ -424,24 +546,20 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
424
546
|
] })
|
|
425
547
|
] }),
|
|
426
548
|
selected && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 w-5 h-5 rounded-full bg-primary flex items-center justify-center", children: /* @__PURE__ */ jsx(Check, { className: "w-3 h-3 text-primary-foreground" }) }),
|
|
427
|
-
|
|
428
|
-
|
|
549
|
+
/* @__PURE__ */ jsx(
|
|
550
|
+
CardMenu,
|
|
429
551
|
{
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
onDelete();
|
|
435
|
-
},
|
|
436
|
-
title: "Delete asset",
|
|
437
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3" })
|
|
552
|
+
onRename,
|
|
553
|
+
onReplace,
|
|
554
|
+
onEditTags,
|
|
555
|
+
onDelete: allowDelete ? onDelete : void 0
|
|
438
556
|
}
|
|
439
557
|
)
|
|
440
558
|
]
|
|
441
559
|
}
|
|
442
560
|
);
|
|
443
561
|
};
|
|
444
|
-
var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
562
|
+
var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
445
563
|
const thumb = getThumbnail(asset2);
|
|
446
564
|
const Icon = getIcon(asset2.mimeType);
|
|
447
565
|
const ownerAppId = getAssetAppId(asset2);
|
|
@@ -504,17 +622,14 @@ var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
504
622
|
}) })
|
|
505
623
|
] }),
|
|
506
624
|
selected && /* @__PURE__ */ jsx("div", { className: "w-5 h-5 rounded-full bg-primary flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx(Check, { className: "w-3 h-3 text-primary-foreground" }) }),
|
|
507
|
-
|
|
508
|
-
|
|
625
|
+
/* @__PURE__ */ jsx(
|
|
626
|
+
CardMenu,
|
|
509
627
|
{
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
},
|
|
516
|
-
title: "Delete",
|
|
517
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3" })
|
|
628
|
+
onRename,
|
|
629
|
+
onReplace,
|
|
630
|
+
onEditTags,
|
|
631
|
+
onDelete: allowDelete ? onDelete : void 0,
|
|
632
|
+
position: "inline"
|
|
518
633
|
}
|
|
519
634
|
)
|
|
520
635
|
]
|
|
@@ -528,6 +643,9 @@ var AssetGrid = ({
|
|
|
528
643
|
onToggleSelect,
|
|
529
644
|
onDoubleClickSelect,
|
|
530
645
|
onDelete,
|
|
646
|
+
onRename,
|
|
647
|
+
onReplace,
|
|
648
|
+
onEditTags,
|
|
531
649
|
allowDelete,
|
|
532
650
|
currentAppId,
|
|
533
651
|
getAppName,
|
|
@@ -544,6 +662,9 @@ var AssetGrid = ({
|
|
|
544
662
|
onToggle: () => onToggleSelect(asset2),
|
|
545
663
|
onDoubleClick: onDoubleClickSelect ? () => onDoubleClickSelect(asset2) : void 0,
|
|
546
664
|
onDelete: allowDelete && onDelete ? () => onDelete(asset2.id) : void 0,
|
|
665
|
+
onRename: onRename ? () => onRename(asset2) : void 0,
|
|
666
|
+
onReplace: onReplace ? () => onReplace(asset2) : void 0,
|
|
667
|
+
onEditTags: onEditTags ? () => onEditTags(asset2) : void 0,
|
|
547
668
|
allowDelete,
|
|
548
669
|
currentAppId,
|
|
549
670
|
getAppName,
|
|
@@ -561,6 +682,9 @@ var AssetGrid = ({
|
|
|
561
682
|
onToggle: () => onToggleSelect(asset2),
|
|
562
683
|
onDoubleClick: onDoubleClickSelect ? () => onDoubleClickSelect(asset2) : void 0,
|
|
563
684
|
onDelete: allowDelete && onDelete ? () => onDelete(asset2.id) : void 0,
|
|
685
|
+
onRename: onRename ? () => onRename(asset2) : void 0,
|
|
686
|
+
onReplace: onReplace ? () => onReplace(asset2) : void 0,
|
|
687
|
+
onEditTags: onEditTags ? () => onEditTags(asset2) : void 0,
|
|
564
688
|
allowDelete,
|
|
565
689
|
currentAppId,
|
|
566
690
|
getAppName,
|
|
@@ -570,18 +694,144 @@ var AssetGrid = ({
|
|
|
570
694
|
asset2.id
|
|
571
695
|
)) });
|
|
572
696
|
};
|
|
697
|
+
|
|
698
|
+
// src/components/AssetPicker/imageProcessing.ts
|
|
699
|
+
var WEBP_CANDIDATES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/jpg", "image/bmp"]);
|
|
700
|
+
function isWebpCandidate(file) {
|
|
701
|
+
return WEBP_CANDIDATES.has(file.type.toLowerCase());
|
|
702
|
+
}
|
|
703
|
+
function isProcessableImage(file) {
|
|
704
|
+
if (!file.type.startsWith("image/")) return false;
|
|
705
|
+
if (file.type === "image/svg+xml" || file.type === "image/gif") return false;
|
|
706
|
+
return true;
|
|
707
|
+
}
|
|
708
|
+
async function loadImage(file) {
|
|
709
|
+
const url = URL.createObjectURL(file);
|
|
710
|
+
const img = new Image();
|
|
711
|
+
img.decoding = "async";
|
|
712
|
+
await new Promise((resolve, reject) => {
|
|
713
|
+
img.onload = () => resolve();
|
|
714
|
+
img.onerror = () => reject(new Error("Failed to decode image"));
|
|
715
|
+
img.src = url;
|
|
716
|
+
});
|
|
717
|
+
return { img, url };
|
|
718
|
+
}
|
|
719
|
+
async function getImageDimensions(file) {
|
|
720
|
+
if (!isProcessableImage(file)) return null;
|
|
721
|
+
try {
|
|
722
|
+
const { img, url } = await loadImage(file);
|
|
723
|
+
const out = { width: img.naturalWidth, height: img.naturalHeight };
|
|
724
|
+
URL.revokeObjectURL(url);
|
|
725
|
+
return out;
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
async function processImage(file, opts = {}) {
|
|
731
|
+
const toWebp = opts.toWebp ?? isWebpCandidate(file);
|
|
732
|
+
const maxDim = opts.maxDimension ?? 2048;
|
|
733
|
+
const quality = opts.quality ?? 0.85;
|
|
734
|
+
const fallback = {
|
|
735
|
+
file,
|
|
736
|
+
changed: false,
|
|
737
|
+
originalSize: file.size,
|
|
738
|
+
newSize: file.size,
|
|
739
|
+
width: 0,
|
|
740
|
+
height: 0
|
|
741
|
+
};
|
|
742
|
+
if (!isProcessableImage(file)) return fallback;
|
|
743
|
+
let img;
|
|
744
|
+
let url;
|
|
745
|
+
try {
|
|
746
|
+
({ img, url } = await loadImage(file));
|
|
747
|
+
} catch {
|
|
748
|
+
return fallback;
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
const w = img.naturalWidth;
|
|
752
|
+
const h = img.naturalHeight;
|
|
753
|
+
const longest = Math.max(w, h);
|
|
754
|
+
const needsResize = maxDim > 0 && longest > maxDim;
|
|
755
|
+
const needsReencode = toWebp && file.type !== "image/webp";
|
|
756
|
+
if (!needsResize && !needsReencode) {
|
|
757
|
+
return { ...fallback, width: w, height: h };
|
|
758
|
+
}
|
|
759
|
+
const scale = needsResize ? maxDim / longest : 1;
|
|
760
|
+
const targetW = Math.round(w * scale);
|
|
761
|
+
const targetH = Math.round(h * scale);
|
|
762
|
+
const canvas = document.createElement("canvas");
|
|
763
|
+
canvas.width = targetW;
|
|
764
|
+
canvas.height = targetH;
|
|
765
|
+
const ctx = canvas.getContext("2d");
|
|
766
|
+
if (!ctx) return { ...fallback, width: w, height: h };
|
|
767
|
+
ctx.drawImage(img, 0, 0, targetW, targetH);
|
|
768
|
+
const outType = needsReencode ? "image/webp" : file.type;
|
|
769
|
+
const blob = await new Promise(
|
|
770
|
+
(resolve) => canvas.toBlob((b) => resolve(b), outType, quality)
|
|
771
|
+
);
|
|
772
|
+
if (!blob) return { ...fallback, width: w, height: h };
|
|
773
|
+
const base = file.name.replace(/\.[^.]+$/, "") || "image";
|
|
774
|
+
const ext = needsReencode ? "webp" : file.name.match(/\.([^.]+)$/)?.[1] || "png";
|
|
775
|
+
const newName = `${base}.${ext}`;
|
|
776
|
+
const newFile = new File([blob], newName, { type: outType });
|
|
777
|
+
return {
|
|
778
|
+
file: newFile,
|
|
779
|
+
changed: true,
|
|
780
|
+
originalSize: file.size,
|
|
781
|
+
newSize: newFile.size,
|
|
782
|
+
width: targetW,
|
|
783
|
+
height: targetH
|
|
784
|
+
};
|
|
785
|
+
} finally {
|
|
786
|
+
URL.revokeObjectURL(url);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
573
789
|
var UploadZone = ({
|
|
574
790
|
onFiles,
|
|
575
791
|
accept,
|
|
576
792
|
multiple,
|
|
577
793
|
uploading,
|
|
578
794
|
uploadProgress = 0,
|
|
579
|
-
className
|
|
795
|
+
className,
|
|
796
|
+
imageOptimization
|
|
580
797
|
}) => {
|
|
798
|
+
const optConfig = useMemo(() => {
|
|
799
|
+
if (imageOptimization === false) return { forced: "off" };
|
|
800
|
+
if (imageOptimization === true) return { forced: "on", maxDimension: 2048, quality: 0.85 };
|
|
801
|
+
if (typeof imageOptimization === "object" && imageOptimization) {
|
|
802
|
+
return {
|
|
803
|
+
forced: imageOptimization.userToggleable === false ? imageOptimization.defaultEnabled === false ? "off" : "on" : null,
|
|
804
|
+
defaultEnabled: imageOptimization.defaultEnabled ?? true,
|
|
805
|
+
maxDimension: imageOptimization.maxDimension ?? 2048,
|
|
806
|
+
quality: imageOptimization.quality ?? 0.85
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
return { forced: null, defaultEnabled: true, maxDimension: 2048, quality: 0.85 };
|
|
810
|
+
}, [imageOptimization]);
|
|
581
811
|
const [dragOver, setDragOver] = useState(false);
|
|
582
812
|
const [pastedFile, setPastedFile] = useState(null);
|
|
583
813
|
const [editingName, setEditingName] = useState(false);
|
|
584
814
|
const [fileName, setFileName] = useState("");
|
|
815
|
+
const [lightboxOpen, setLightboxOpen] = useState(false);
|
|
816
|
+
const [autoOptimizeUser, setAutoOptimizeUser] = useState(() => {
|
|
817
|
+
if (optConfig.forced) return optConfig.forced === "on";
|
|
818
|
+
try {
|
|
819
|
+
const v = localStorage.getItem("smartlinks.assetPicker.autoOptimize");
|
|
820
|
+
if (v !== null) return v === "1";
|
|
821
|
+
} catch {
|
|
822
|
+
}
|
|
823
|
+
return optConfig.defaultEnabled ?? true;
|
|
824
|
+
});
|
|
825
|
+
const autoOptimize = optConfig.forced ? optConfig.forced === "on" : autoOptimizeUser;
|
|
826
|
+
const showOptimizeToggle = optConfig.forced === null;
|
|
827
|
+
const setAutoOptimizePersist = useCallback((v) => {
|
|
828
|
+
setAutoOptimizeUser(v);
|
|
829
|
+
try {
|
|
830
|
+
localStorage.setItem("smartlinks.assetPicker.autoOptimize", v ? "1" : "0");
|
|
831
|
+
} catch {
|
|
832
|
+
}
|
|
833
|
+
}, []);
|
|
834
|
+
const [optimizing, setOptimizing] = useState(false);
|
|
585
835
|
const inputRef = useRef(null);
|
|
586
836
|
const nameInputRef = useRef(null);
|
|
587
837
|
const zoneRef = useRef(null);
|
|
@@ -606,9 +856,12 @@ var UploadZone = ({
|
|
|
606
856
|
e.preventDefault();
|
|
607
857
|
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : "";
|
|
608
858
|
const defaultName = file.name === "image.png" ? `pasted-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[T:]/g, "-")}` : file.name.replace(/\.[^.]+$/, "");
|
|
609
|
-
setPastedFile({ file, previewUrl, name: defaultName });
|
|
859
|
+
setPastedFile({ file, previewUrl, name: defaultName, origSize: file.size });
|
|
610
860
|
setFileName(defaultName);
|
|
611
861
|
setEditingName(false);
|
|
862
|
+
getImageDimensions(file).then((dims) => {
|
|
863
|
+
if (dims) setPastedFile((prev) => prev && prev.file === file ? { ...prev, origDims: dims } : prev);
|
|
864
|
+
});
|
|
612
865
|
return;
|
|
613
866
|
}
|
|
614
867
|
}
|
|
@@ -616,15 +869,28 @@ var UploadZone = ({
|
|
|
616
869
|
document.addEventListener("paste", handlePaste);
|
|
617
870
|
return () => document.removeEventListener("paste", handlePaste);
|
|
618
871
|
}, [uploading, accept]);
|
|
619
|
-
const handleConfirmPaste = useCallback(() => {
|
|
872
|
+
const handleConfirmPaste = useCallback(async () => {
|
|
620
873
|
if (!pastedFile) return;
|
|
621
|
-
|
|
874
|
+
let working = pastedFile.file;
|
|
875
|
+
if (autoOptimize && isProcessableImage(working)) {
|
|
876
|
+
setOptimizing(true);
|
|
877
|
+
try {
|
|
878
|
+
const result = await processImage(working, {
|
|
879
|
+
maxDimension: optConfig.maxDimension,
|
|
880
|
+
quality: optConfig.quality
|
|
881
|
+
});
|
|
882
|
+
if (result.changed) working = result.file;
|
|
883
|
+
} finally {
|
|
884
|
+
setOptimizing(false);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const ext = working.name.includes(".") ? working.name.split(".").pop() : "png";
|
|
622
888
|
const finalName = `${fileName.trim() || "pasted-image"}.${ext}`;
|
|
623
|
-
const renamedFile = new File([
|
|
889
|
+
const renamedFile = new File([working], finalName, { type: working.type });
|
|
624
890
|
onFiles([renamedFile]);
|
|
625
891
|
if (pastedFile.previewUrl) URL.revokeObjectURL(pastedFile.previewUrl);
|
|
626
892
|
setPastedFile(null);
|
|
627
|
-
}, [pastedFile, fileName, onFiles]);
|
|
893
|
+
}, [pastedFile, fileName, onFiles, autoOptimize, optConfig]);
|
|
628
894
|
const handleCancelPaste = useCallback(() => {
|
|
629
895
|
if (pastedFile?.previewUrl) URL.revokeObjectURL(pastedFile.previewUrl);
|
|
630
896
|
setPastedFile(null);
|
|
@@ -647,9 +913,12 @@ var UploadZone = ({
|
|
|
647
913
|
const presentForRename = useCallback((file) => {
|
|
648
914
|
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : "";
|
|
649
915
|
const defaultName = file.name.replace(/\.[^.]+$/, "") || "file";
|
|
650
|
-
setPastedFile({ file, previewUrl, name: defaultName });
|
|
916
|
+
setPastedFile({ file, previewUrl, name: defaultName, origSize: file.size });
|
|
651
917
|
setFileName(defaultName);
|
|
652
918
|
setEditingName(false);
|
|
919
|
+
getImageDimensions(file).then((dims) => {
|
|
920
|
+
if (dims) setPastedFile((prev) => prev && prev.file === file ? { ...prev, origDims: dims } : prev);
|
|
921
|
+
});
|
|
653
922
|
}, []);
|
|
654
923
|
const handleDrop = useCallback((e) => {
|
|
655
924
|
e.preventDefault();
|
|
@@ -659,7 +928,7 @@ var UploadZone = ({
|
|
|
659
928
|
if (files.length === 1 && !multiple) {
|
|
660
929
|
presentForRename(files[0]);
|
|
661
930
|
} else if (files.length > 0) {
|
|
662
|
-
|
|
931
|
+
void handleBatchFiles(multiple ? files : [files[0]]);
|
|
663
932
|
}
|
|
664
933
|
}, [onFiles, multiple, presentForRename]);
|
|
665
934
|
const handleInputChange = useCallback((e) => {
|
|
@@ -667,87 +936,189 @@ var UploadZone = ({
|
|
|
667
936
|
if (files.length === 1 && !multiple) {
|
|
668
937
|
presentForRename(files[0]);
|
|
669
938
|
} else if (files.length > 0) {
|
|
670
|
-
|
|
939
|
+
void handleBatchFiles(multiple ? files : [files[0]]);
|
|
671
940
|
}
|
|
672
941
|
e.target.value = "";
|
|
673
942
|
}, [onFiles, multiple, presentForRename]);
|
|
943
|
+
const handleBatchFiles = useCallback(async (files) => {
|
|
944
|
+
if (!autoOptimize) {
|
|
945
|
+
onFiles(files);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
setOptimizing(true);
|
|
949
|
+
try {
|
|
950
|
+
const out = [];
|
|
951
|
+
for (const f of files) {
|
|
952
|
+
if (isProcessableImage(f)) {
|
|
953
|
+
const r = await processImage(f, {
|
|
954
|
+
maxDimension: optConfig.maxDimension,
|
|
955
|
+
quality: optConfig.quality
|
|
956
|
+
});
|
|
957
|
+
out.push(r.changed ? r.file : f);
|
|
958
|
+
} else {
|
|
959
|
+
out.push(f);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
onFiles(out);
|
|
963
|
+
} finally {
|
|
964
|
+
setOptimizing(false);
|
|
965
|
+
}
|
|
966
|
+
}, [autoOptimize, onFiles, optConfig]);
|
|
967
|
+
const fmtKB = (n) => n >= 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)} MB` : `${(n / 1024).toFixed(1)} KB`;
|
|
968
|
+
const optimizeToggle = showOptimizeToggle ? /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-[10px] text-muted-foreground cursor-pointer select-none", children: [
|
|
969
|
+
/* @__PURE__ */ jsx(
|
|
970
|
+
"input",
|
|
971
|
+
{
|
|
972
|
+
type: "checkbox",
|
|
973
|
+
checked: autoOptimize,
|
|
974
|
+
onChange: (e) => setAutoOptimizePersist(e.target.checked),
|
|
975
|
+
className: "cursor-pointer"
|
|
976
|
+
}
|
|
977
|
+
),
|
|
978
|
+
/* @__PURE__ */ jsx(Wand2, { className: "w-3 h-3" }),
|
|
979
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
980
|
+
"Optimize for web (WebP, max ",
|
|
981
|
+
optConfig.maxDimension ?? 2048,
|
|
982
|
+
"px)"
|
|
983
|
+
] })
|
|
984
|
+
] }) : null;
|
|
674
985
|
if (pastedFile) {
|
|
675
|
-
|
|
986
|
+
const willOptimize = autoOptimize && isProcessableImage(pastedFile.file);
|
|
987
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
676
988
|
"border-2 border-solid border-primary rounded-lg p-4 transition-colors",
|
|
677
989
|
className
|
|
678
|
-
), children:
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
{
|
|
682
|
-
src: pastedFile.previewUrl,
|
|
683
|
-
alt: "Pasted content",
|
|
684
|
-
className: "max-h-32 max-w-full rounded-md object-contain border border-border"
|
|
685
|
-
}
|
|
686
|
-
) : /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-md bg-muted flex items-center justify-center", children: /* @__PURE__ */ jsx(Clipboard, { className: "w-6 h-6 text-muted-foreground" }) }),
|
|
687
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 w-full max-w-xs", children: editingName ? /* @__PURE__ */ jsx(
|
|
688
|
-
"input",
|
|
689
|
-
{
|
|
690
|
-
ref: nameInputRef,
|
|
691
|
-
type: "text",
|
|
692
|
-
value: fileName,
|
|
693
|
-
onChange: (e) => setFileName(e.target.value),
|
|
694
|
-
onKeyDown: (e) => {
|
|
695
|
-
if (e.key === "Enter") {
|
|
696
|
-
setEditingName(false);
|
|
697
|
-
handleConfirmPaste();
|
|
698
|
-
}
|
|
699
|
-
if (e.key === "Escape") setEditingName(false);
|
|
700
|
-
},
|
|
701
|
-
onBlur: () => setEditingName(false),
|
|
702
|
-
className: "flex-1 px-2 py-1 text-sm rounded border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring text-center",
|
|
703
|
-
placeholder: "File name"
|
|
704
|
-
}
|
|
705
|
-
) : /* @__PURE__ */ jsxs(
|
|
706
|
-
"button",
|
|
707
|
-
{
|
|
708
|
-
type: "button",
|
|
709
|
-
onClick: () => setEditingName(true),
|
|
710
|
-
className: "flex items-center gap-1 mx-auto px-2 py-1 text-sm text-muted-foreground hover:text-foreground rounded hover:bg-accent transition-colors",
|
|
711
|
-
title: "Rename",
|
|
712
|
-
children: [
|
|
713
|
-
/* @__PURE__ */ jsx("span", { className: "truncate max-w-[200px]", children: fileName }),
|
|
714
|
-
/* @__PURE__ */ jsx(Pencil, { className: "w-3 h-3 flex-shrink-0 opacity-50" })
|
|
715
|
-
]
|
|
716
|
-
}
|
|
717
|
-
) }),
|
|
718
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
719
|
-
pastedFile.file.type,
|
|
720
|
-
" \xB7 ",
|
|
721
|
-
(pastedFile.file.size / 1024).toFixed(1),
|
|
722
|
-
" KB"
|
|
723
|
-
] }),
|
|
724
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
725
|
-
/* @__PURE__ */ jsxs(
|
|
990
|
+
), children: [
|
|
991
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
992
|
+
pastedFile.previewUrl ? /* @__PURE__ */ jsxs(
|
|
726
993
|
"button",
|
|
727
994
|
{
|
|
728
995
|
type: "button",
|
|
729
|
-
onClick:
|
|
730
|
-
className: "
|
|
996
|
+
onClick: () => setLightboxOpen(true),
|
|
997
|
+
className: "group relative w-24 h-24 rounded-md border border-border bg-muted overflow-hidden flex items-center justify-center",
|
|
998
|
+
title: "Click to preview full size",
|
|
731
999
|
children: [
|
|
732
|
-
/* @__PURE__ */ jsx(
|
|
733
|
-
|
|
1000
|
+
/* @__PURE__ */ jsx(
|
|
1001
|
+
"img",
|
|
1002
|
+
{
|
|
1003
|
+
src: pastedFile.previewUrl,
|
|
1004
|
+
alt: "Pasted content",
|
|
1005
|
+
className: "w-full h-full object-cover",
|
|
1006
|
+
loading: "lazy",
|
|
1007
|
+
decoding: "async"
|
|
1008
|
+
}
|
|
1009
|
+
),
|
|
1010
|
+
/* @__PURE__ */ jsx("span", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/40 transition-colors flex items-center justify-center opacity-0 group-hover:opacity-100", children: /* @__PURE__ */ jsx(Maximize2, { className: "w-4 h-4 text-white" }) })
|
|
734
1011
|
]
|
|
735
1012
|
}
|
|
736
|
-
),
|
|
737
|
-
/* @__PURE__ */
|
|
1013
|
+
) : /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-md bg-muted flex items-center justify-center", children: /* @__PURE__ */ jsx(Clipboard, { className: "w-6 h-6 text-muted-foreground" }) }),
|
|
1014
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 w-full max-w-xs", children: editingName ? /* @__PURE__ */ jsx(
|
|
1015
|
+
"input",
|
|
1016
|
+
{
|
|
1017
|
+
ref: nameInputRef,
|
|
1018
|
+
type: "text",
|
|
1019
|
+
value: fileName,
|
|
1020
|
+
onChange: (e) => setFileName(e.target.value),
|
|
1021
|
+
onKeyDown: (e) => {
|
|
1022
|
+
if (e.key === "Enter") {
|
|
1023
|
+
setEditingName(false);
|
|
1024
|
+
handleConfirmPaste();
|
|
1025
|
+
}
|
|
1026
|
+
if (e.key === "Escape") setEditingName(false);
|
|
1027
|
+
},
|
|
1028
|
+
onBlur: () => setEditingName(false),
|
|
1029
|
+
className: "flex-1 px-2 py-1 text-sm rounded border border-border bg-transparent focus:outline-none focus:ring-1 focus:ring-ring text-center",
|
|
1030
|
+
placeholder: "File name"
|
|
1031
|
+
}
|
|
1032
|
+
) : /* @__PURE__ */ jsxs(
|
|
738
1033
|
"button",
|
|
739
1034
|
{
|
|
740
1035
|
type: "button",
|
|
741
|
-
onClick:
|
|
742
|
-
className: "
|
|
1036
|
+
onClick: () => setEditingName(true),
|
|
1037
|
+
className: "flex items-center gap-1 mx-auto px-2 py-1 text-sm text-muted-foreground hover:text-foreground rounded hover:bg-accent transition-colors",
|
|
1038
|
+
title: "Rename",
|
|
743
1039
|
children: [
|
|
744
|
-
/* @__PURE__ */ jsx(
|
|
745
|
-
"
|
|
1040
|
+
/* @__PURE__ */ jsx("span", { className: "truncate max-w-[200px]", children: fileName }),
|
|
1041
|
+
/* @__PURE__ */ jsx(Pencil, { className: "w-3 h-3 flex-shrink-0 opacity-50" })
|
|
746
1042
|
]
|
|
747
1043
|
}
|
|
748
|
-
)
|
|
749
|
-
|
|
750
|
-
|
|
1044
|
+
) }),
|
|
1045
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground text-center", children: [
|
|
1046
|
+
pastedFile.file.type,
|
|
1047
|
+
" \xB7 ",
|
|
1048
|
+
fmtKB(pastedFile.origSize),
|
|
1049
|
+
pastedFile.origDims && ` \xB7 ${pastedFile.origDims.width}\xD7${pastedFile.origDims.height}`,
|
|
1050
|
+
willOptimize && pastedFile.origDims && (pastedFile.origDims.width > 2048 || pastedFile.origDims.height > 2048 || pastedFile.file.type !== "image/webp") && /* @__PURE__ */ jsxs("span", { className: "block text-primary mt-0.5", children: [
|
|
1051
|
+
/* @__PURE__ */ jsx(Wand2, { className: "w-3 h-3 inline -mt-0.5 mr-1" }),
|
|
1052
|
+
"Will be optimized to WebP",
|
|
1053
|
+
pastedFile.origDims.width > 2048 || pastedFile.origDims.height > 2048 ? ", max 2048px" : ""
|
|
1054
|
+
] })
|
|
1055
|
+
] }),
|
|
1056
|
+
isProcessableImage(pastedFile.file) && optimizeToggle,
|
|
1057
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1058
|
+
/* @__PURE__ */ jsxs(
|
|
1059
|
+
"button",
|
|
1060
|
+
{
|
|
1061
|
+
type: "button",
|
|
1062
|
+
onClick: handleCancelPaste,
|
|
1063
|
+
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",
|
|
1064
|
+
disabled: optimizing,
|
|
1065
|
+
children: [
|
|
1066
|
+
/* @__PURE__ */ jsx(X, { className: "w-3 h-3" }),
|
|
1067
|
+
" Cancel"
|
|
1068
|
+
]
|
|
1069
|
+
}
|
|
1070
|
+
),
|
|
1071
|
+
/* @__PURE__ */ jsxs(
|
|
1072
|
+
"button",
|
|
1073
|
+
{
|
|
1074
|
+
type: "button",
|
|
1075
|
+
onClick: handleConfirmPaste,
|
|
1076
|
+
className: "px-3 py-1.5 text-xs font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors flex items-center gap-1",
|
|
1077
|
+
disabled: optimizing,
|
|
1078
|
+
children: [
|
|
1079
|
+
optimizing ? /* @__PURE__ */ jsx(Loader2, { className: "w-3 h-3 animate-spin" }) : /* @__PURE__ */ jsx(Check, { className: "w-3 h-3" }),
|
|
1080
|
+
optimizing ? "Optimizing\u2026" : "Upload"
|
|
1081
|
+
]
|
|
1082
|
+
}
|
|
1083
|
+
)
|
|
1084
|
+
] })
|
|
1085
|
+
] }),
|
|
1086
|
+
lightboxOpen && pastedFile.previewUrl && /* @__PURE__ */ jsxs(
|
|
1087
|
+
"div",
|
|
1088
|
+
{
|
|
1089
|
+
className: "fixed inset-0 z-[100] bg-black/80 flex items-center justify-center p-6",
|
|
1090
|
+
onClick: () => setLightboxOpen(false),
|
|
1091
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1092
|
+
onTouchStart: (e) => e.stopPropagation(),
|
|
1093
|
+
role: "dialog",
|
|
1094
|
+
"aria-label": "Image preview",
|
|
1095
|
+
children: [
|
|
1096
|
+
/* @__PURE__ */ jsx(
|
|
1097
|
+
"button",
|
|
1098
|
+
{
|
|
1099
|
+
type: "button",
|
|
1100
|
+
onClick: (e) => {
|
|
1101
|
+
e.stopPropagation();
|
|
1102
|
+
setLightboxOpen(false);
|
|
1103
|
+
},
|
|
1104
|
+
className: "absolute top-4 right-4 p-2 rounded-md bg-white/10 hover:bg-white/20 text-white",
|
|
1105
|
+
"aria-label": "Close preview",
|
|
1106
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-5 h-5" })
|
|
1107
|
+
}
|
|
1108
|
+
),
|
|
1109
|
+
/* @__PURE__ */ jsx(
|
|
1110
|
+
"img",
|
|
1111
|
+
{
|
|
1112
|
+
src: pastedFile.previewUrl,
|
|
1113
|
+
alt: "Full preview",
|
|
1114
|
+
className: "max-w-full max-h-full object-contain",
|
|
1115
|
+
onClick: (e) => e.stopPropagation()
|
|
1116
|
+
}
|
|
1117
|
+
)
|
|
1118
|
+
]
|
|
1119
|
+
}
|
|
1120
|
+
)
|
|
1121
|
+
] });
|
|
751
1122
|
}
|
|
752
1123
|
return /* @__PURE__ */ jsxs(
|
|
753
1124
|
"div",
|
|
@@ -782,13 +1153,10 @@ var UploadZone = ({
|
|
|
782
1153
|
className: "hidden"
|
|
783
1154
|
}
|
|
784
1155
|
),
|
|
785
|
-
uploading ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-2", children: [
|
|
1156
|
+
uploading || optimizing ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-2 py-2", children: [
|
|
786
1157
|
/* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 text-primary animate-spin" }),
|
|
787
|
-
/* @__PURE__ */
|
|
788
|
-
|
|
789
|
-
uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ""
|
|
790
|
-
] }),
|
|
791
|
-
uploadProgress > 0 && /* @__PURE__ */ jsx("div", { className: "w-full max-w-xs h-1.5 bg-muted rounded-full overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
1158
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: optimizing ? "Optimizing\u2026" : `Uploading\u2026 ${uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ""}` }),
|
|
1159
|
+
!optimizing && uploadProgress > 0 && /* @__PURE__ */ jsx("div", { className: "w-full max-w-xs h-1.5 bg-muted rounded-full overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
792
1160
|
"div",
|
|
793
1161
|
{
|
|
794
1162
|
className: "h-full bg-primary rounded-full transition-all duration-300",
|
|
@@ -809,7 +1177,15 @@ var UploadZone = ({
|
|
|
809
1177
|
accept && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
810
1178
|
"Accepts: ",
|
|
811
1179
|
accept
|
|
812
|
-
] })
|
|
1180
|
+
] }),
|
|
1181
|
+
/* @__PURE__ */ jsx(
|
|
1182
|
+
"div",
|
|
1183
|
+
{
|
|
1184
|
+
className: "mt-2 pt-2 border-t border-border/50 w-full flex justify-center",
|
|
1185
|
+
onClick: (e) => e.stopPropagation(),
|
|
1186
|
+
children: optimizeToggle
|
|
1187
|
+
}
|
|
1188
|
+
)
|
|
813
1189
|
] })
|
|
814
1190
|
]
|
|
815
1191
|
}
|
|
@@ -1461,7 +1837,7 @@ var StockPhotoSearch = ({
|
|
|
1461
1837
|
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5" }),
|
|
1462
1838
|
" Saved"
|
|
1463
1839
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1464
|
-
/* @__PURE__ */ jsx(Image, { className: "w-3.5 h-3.5" }),
|
|
1840
|
+
/* @__PURE__ */ jsx(Image$1, { className: "w-3.5 h-3.5" }),
|
|
1465
1841
|
" Save"
|
|
1466
1842
|
] })
|
|
1467
1843
|
}
|
|
@@ -1496,6 +1872,144 @@ var StockPhotoSearch = ({
|
|
|
1496
1872
|
}) })
|
|
1497
1873
|
] });
|
|
1498
1874
|
};
|
|
1875
|
+
var TagEditor = ({ initial, suggestions, assetName, onCancel, onSave }) => {
|
|
1876
|
+
const [labels, setLabels] = useState(() => Array.from(new Set(initial.map((l) => l.trim()).filter(Boolean))));
|
|
1877
|
+
const [input, setInput] = useState("");
|
|
1878
|
+
const [saving, setSaving] = useState(false);
|
|
1879
|
+
const inputRef = useRef(null);
|
|
1880
|
+
useEffect(() => {
|
|
1881
|
+
inputRef.current?.focus();
|
|
1882
|
+
}, []);
|
|
1883
|
+
const filteredSuggestions = useMemo(() => {
|
|
1884
|
+
const q = input.trim().toLowerCase();
|
|
1885
|
+
const have = new Set(labels.map((l) => l.toLowerCase()));
|
|
1886
|
+
return suggestions.filter((s) => !have.has(s.toLowerCase())).filter((s) => !q || s.toLowerCase().includes(q)).slice(0, 8);
|
|
1887
|
+
}, [suggestions, input, labels]);
|
|
1888
|
+
const addLabel = (raw) => {
|
|
1889
|
+
const v = raw.trim();
|
|
1890
|
+
if (!v) return;
|
|
1891
|
+
setLabels((prev) => prev.some((p) => p.toLowerCase() === v.toLowerCase()) ? prev : [...prev, v]);
|
|
1892
|
+
setInput("");
|
|
1893
|
+
};
|
|
1894
|
+
const removeLabel = (label) => {
|
|
1895
|
+
setLabels((prev) => prev.filter((l) => l !== label));
|
|
1896
|
+
};
|
|
1897
|
+
const handleKey = (e) => {
|
|
1898
|
+
if (e.key === "Enter" || e.key === ",") {
|
|
1899
|
+
e.preventDefault();
|
|
1900
|
+
addLabel(input);
|
|
1901
|
+
} else if (e.key === "Backspace" && !input && labels.length > 0) {
|
|
1902
|
+
setLabels((prev) => prev.slice(0, -1));
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
const handleSave = async () => {
|
|
1906
|
+
setSaving(true);
|
|
1907
|
+
try {
|
|
1908
|
+
await onSave(labels);
|
|
1909
|
+
} finally {
|
|
1910
|
+
setSaving(false);
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
return /* @__PURE__ */ jsx(
|
|
1914
|
+
"div",
|
|
1915
|
+
{
|
|
1916
|
+
className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/60",
|
|
1917
|
+
onClick: onCancel,
|
|
1918
|
+
onMouseDown: (e) => e.stopPropagation(),
|
|
1919
|
+
onTouchStart: (e) => e.stopPropagation(),
|
|
1920
|
+
children: /* @__PURE__ */ jsxs(
|
|
1921
|
+
"div",
|
|
1922
|
+
{
|
|
1923
|
+
className: "w-[min(28rem,92vw)] rounded-lg bg-background border border-border shadow-xl p-4 flex flex-col gap-3",
|
|
1924
|
+
onClick: (e) => e.stopPropagation(),
|
|
1925
|
+
children: [
|
|
1926
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1927
|
+
/* @__PURE__ */ jsx(Tag, { className: "w-4 h-4 text-muted-foreground" }),
|
|
1928
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-sm font-semibold text-foreground truncate", children: [
|
|
1929
|
+
"Edit tags",
|
|
1930
|
+
assetName ? ` \u2014 ${assetName}` : ""
|
|
1931
|
+
] }),
|
|
1932
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, className: "ml-auto p-1 rounded hover:bg-muted", "aria-label": "Close", children: /* @__PURE__ */ jsx(X, { className: "w-4 h-4 text-muted-foreground" }) })
|
|
1933
|
+
] }),
|
|
1934
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1 p-2 rounded-md border border-border min-h-[2.5rem]", children: [
|
|
1935
|
+
labels.map((label) => /* @__PURE__ */ jsxs(
|
|
1936
|
+
"span",
|
|
1937
|
+
{
|
|
1938
|
+
className: "inline-flex items-center gap-1 rounded-full bg-primary/10 text-primary border border-primary/30 px-2 py-0.5 text-xs",
|
|
1939
|
+
children: [
|
|
1940
|
+
label,
|
|
1941
|
+
/* @__PURE__ */ jsx(
|
|
1942
|
+
"button",
|
|
1943
|
+
{
|
|
1944
|
+
type: "button",
|
|
1945
|
+
onClick: () => removeLabel(label),
|
|
1946
|
+
className: "opacity-70 hover:opacity-100",
|
|
1947
|
+
"aria-label": `Remove ${label}`,
|
|
1948
|
+
children: /* @__PURE__ */ jsx(X, { className: "w-3 h-3" })
|
|
1949
|
+
}
|
|
1950
|
+
)
|
|
1951
|
+
]
|
|
1952
|
+
},
|
|
1953
|
+
label
|
|
1954
|
+
)),
|
|
1955
|
+
/* @__PURE__ */ jsx(
|
|
1956
|
+
"input",
|
|
1957
|
+
{
|
|
1958
|
+
ref: inputRef,
|
|
1959
|
+
type: "text",
|
|
1960
|
+
value: input,
|
|
1961
|
+
onChange: (e) => setInput(e.target.value),
|
|
1962
|
+
onKeyDown: handleKey,
|
|
1963
|
+
placeholder: labels.length === 0 ? "Add a tag and press Enter\u2026" : "Add another\u2026",
|
|
1964
|
+
className: "flex-1 min-w-[8rem] bg-transparent text-sm focus:outline-none text-foreground"
|
|
1965
|
+
}
|
|
1966
|
+
)
|
|
1967
|
+
] }),
|
|
1968
|
+
filteredSuggestions.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1", children: [
|
|
1969
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground self-center mr-1", children: "Suggestions:" }),
|
|
1970
|
+
filteredSuggestions.map((s) => /* @__PURE__ */ jsxs(
|
|
1971
|
+
"button",
|
|
1972
|
+
{
|
|
1973
|
+
type: "button",
|
|
1974
|
+
onClick: () => addLabel(s),
|
|
1975
|
+
className: cn(
|
|
1976
|
+
"inline-flex items-center gap-0.5 rounded-full border border-border bg-muted text-muted-foreground hover:bg-muted/70 px-2 py-0.5 text-[11px]"
|
|
1977
|
+
),
|
|
1978
|
+
children: [
|
|
1979
|
+
/* @__PURE__ */ jsx(Plus, { className: "w-2.5 h-2.5" }),
|
|
1980
|
+
s
|
|
1981
|
+
]
|
|
1982
|
+
},
|
|
1983
|
+
s
|
|
1984
|
+
))
|
|
1985
|
+
] }),
|
|
1986
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 pt-1", children: [
|
|
1987
|
+
/* @__PURE__ */ jsx(
|
|
1988
|
+
"button",
|
|
1989
|
+
{
|
|
1990
|
+
type: "button",
|
|
1991
|
+
onClick: onCancel,
|
|
1992
|
+
className: "px-3 py-1.5 text-xs rounded-md border border-border text-foreground hover:bg-muted",
|
|
1993
|
+
children: "Cancel"
|
|
1994
|
+
}
|
|
1995
|
+
),
|
|
1996
|
+
/* @__PURE__ */ jsx(
|
|
1997
|
+
"button",
|
|
1998
|
+
{
|
|
1999
|
+
type: "button",
|
|
2000
|
+
onClick: handleSave,
|
|
2001
|
+
disabled: saving,
|
|
2002
|
+
className: "px-3 py-1.5 text-xs rounded-md bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
2003
|
+
children: saving ? "Saving\u2026" : "Save tags"
|
|
2004
|
+
}
|
|
2005
|
+
)
|
|
2006
|
+
] })
|
|
2007
|
+
]
|
|
2008
|
+
}
|
|
2009
|
+
)
|
|
2010
|
+
}
|
|
2011
|
+
);
|
|
2012
|
+
};
|
|
1499
2013
|
var GlobalUploadToggle = ({ checked, onChange, appName }) => /* @__PURE__ */ jsxs("label", { className: "flex items-start gap-2 text-xs text-muted-foreground cursor-pointer select-none p-2 rounded-md border border-border bg-muted/30", children: [
|
|
1500
2014
|
/* @__PURE__ */ jsx(
|
|
1501
2015
|
"input",
|
|
@@ -1511,8 +2025,44 @@ var GlobalUploadToggle = ({ checked, onChange, appName }) => /* @__PURE__ */ jsx
|
|
|
1511
2025
|
/* @__PURE__ */ jsx("span", { className: "block", children: checked ? `Asset will be available to every app in this collection.` : `Asset will be tagged to ${appName}. Tick to share with every app in the collection instead.` })
|
|
1512
2026
|
] })
|
|
1513
2027
|
] });
|
|
1514
|
-
var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId, currentAppId, currentAppName, getAppName }) => {
|
|
1515
|
-
const { assets, loading, error, refresh } = useAssets({ scope, accept, pageSize, listAppId });
|
|
2028
|
+
var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId, requireProductId, currentAppId, currentAppName, getAppName }) => {
|
|
2029
|
+
const { assets, loading, error, refresh, remove, updateAsset, replaceFile } = useAssets({ scope, accept, pageSize, listAppId });
|
|
2030
|
+
const replaceInputRef = React7.useRef(null);
|
|
2031
|
+
const replaceTargetRef = React7.useRef(null);
|
|
2032
|
+
const handleRename = useCallback(async (asset2) => {
|
|
2033
|
+
const current = asset2.cleanName || asset2.name || "";
|
|
2034
|
+
const next = window.prompt("Rename asset", current);
|
|
2035
|
+
if (next === null) return;
|
|
2036
|
+
const trimmed = next.trim();
|
|
2037
|
+
if (!trimmed || trimmed === current) return;
|
|
2038
|
+
await updateAsset(asset2.id, { name: trimmed });
|
|
2039
|
+
}, [updateAsset]);
|
|
2040
|
+
const handleReplace = useCallback((asset2) => {
|
|
2041
|
+
replaceTargetRef.current = asset2.id;
|
|
2042
|
+
replaceInputRef.current?.click();
|
|
2043
|
+
}, []);
|
|
2044
|
+
const handleReplaceFiles = useCallback(async (e) => {
|
|
2045
|
+
const file = e.target.files?.[0];
|
|
2046
|
+
const assetId = replaceTargetRef.current;
|
|
2047
|
+
e.target.value = "";
|
|
2048
|
+
replaceTargetRef.current = null;
|
|
2049
|
+
if (!file || !assetId) return;
|
|
2050
|
+
await replaceFile(assetId, file);
|
|
2051
|
+
}, [replaceFile]);
|
|
2052
|
+
const handleDeleteWithConfirm = useCallback(async (assetId) => {
|
|
2053
|
+
if (!window.confirm("Delete this asset? It can be restored from the asset manager within 30 days.")) return;
|
|
2054
|
+
const ok = await remove(assetId);
|
|
2055
|
+
if (ok) onDelete?.(assetId);
|
|
2056
|
+
}, [remove, onDelete]);
|
|
2057
|
+
const [tagEditorAsset, setTagEditorAsset] = useState(null);
|
|
2058
|
+
const handleEditTags = useCallback((asset2) => {
|
|
2059
|
+
setTagEditorAsset(asset2);
|
|
2060
|
+
}, []);
|
|
2061
|
+
const handleSaveTags = useCallback(async (next) => {
|
|
2062
|
+
if (!tagEditorAsset) return;
|
|
2063
|
+
await updateAsset(tagEditorAsset.id, { labels: next });
|
|
2064
|
+
setTagEditorAsset(null);
|
|
2065
|
+
}, [tagEditorAsset, updateAsset]);
|
|
1516
2066
|
const [activeLabels, setActiveLabels] = useState(/* @__PURE__ */ new Set());
|
|
1517
2067
|
const toggleLabel = useCallback((label) => {
|
|
1518
2068
|
setActiveLabels((prev) => {
|
|
@@ -1535,6 +2085,10 @@ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedI
|
|
|
1535
2085
|
const filteredAssets = useMemo(() => {
|
|
1536
2086
|
const q = search.trim().toLowerCase();
|
|
1537
2087
|
return assets.filter((a) => {
|
|
2088
|
+
if (requireProductId) {
|
|
2089
|
+
const pid = a.productId || a.metadata?.productId || (a.scope?.type === "product" ? a.scope?.productId : void 0);
|
|
2090
|
+
if (pid !== requireProductId) return false;
|
|
2091
|
+
}
|
|
1538
2092
|
if (q) {
|
|
1539
2093
|
const hit = (a.name || "").toLowerCase().includes(q) || (a.cleanName || "").toLowerCase().includes(q) || (a.mimeType || "").toLowerCase().includes(q) || (a.labels || []).some((l) => l.toLowerCase().includes(q));
|
|
1540
2094
|
if (!hit) return false;
|
|
@@ -1547,7 +2101,7 @@ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedI
|
|
|
1547
2101
|
}
|
|
1548
2102
|
return true;
|
|
1549
2103
|
});
|
|
1550
|
-
}, [assets, search, activeLabels]);
|
|
2104
|
+
}, [assets, search, activeLabels, requireProductId]);
|
|
1551
2105
|
if (loading && assets.length === 0) {
|
|
1552
2106
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 text-muted-foreground animate-spin" }) });
|
|
1553
2107
|
}
|
|
@@ -1607,7 +2161,10 @@ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedI
|
|
|
1607
2161
|
selectedIds,
|
|
1608
2162
|
onToggleSelect,
|
|
1609
2163
|
onDoubleClickSelect,
|
|
1610
|
-
onDelete: allowDelete ?
|
|
2164
|
+
onDelete: allowDelete ? handleDeleteWithConfirm : void 0,
|
|
2165
|
+
onRename: handleRename,
|
|
2166
|
+
onReplace: handleReplace,
|
|
2167
|
+
onEditTags: handleEditTags,
|
|
1611
2168
|
allowDelete,
|
|
1612
2169
|
currentAppId,
|
|
1613
2170
|
currentAppName,
|
|
@@ -1615,6 +2172,25 @@ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedI
|
|
|
1615
2172
|
activeLabels,
|
|
1616
2173
|
onToggleLabel: toggleLabel
|
|
1617
2174
|
}
|
|
2175
|
+
),
|
|
2176
|
+
/* @__PURE__ */ jsx(
|
|
2177
|
+
"input",
|
|
2178
|
+
{
|
|
2179
|
+
ref: replaceInputRef,
|
|
2180
|
+
type: "file",
|
|
2181
|
+
className: "hidden",
|
|
2182
|
+
onChange: handleReplaceFiles
|
|
2183
|
+
}
|
|
2184
|
+
),
|
|
2185
|
+
tagEditorAsset && /* @__PURE__ */ jsx(
|
|
2186
|
+
TagEditor,
|
|
2187
|
+
{
|
|
2188
|
+
initial: tagEditorAsset.labels || [],
|
|
2189
|
+
suggestions: allLabels.map((l) => l.label),
|
|
2190
|
+
assetName: tagEditorAsset.cleanName || tagEditorAsset.name,
|
|
2191
|
+
onCancel: () => setTagEditorAsset(null),
|
|
2192
|
+
onSave: handleSaveTags
|
|
2193
|
+
}
|
|
1618
2194
|
)
|
|
1619
2195
|
] });
|
|
1620
2196
|
};
|
|
@@ -1637,7 +2213,8 @@ var AssetPickerContent = ({
|
|
|
1637
2213
|
defaultView = "grid",
|
|
1638
2214
|
emptyText,
|
|
1639
2215
|
pageSize = 50,
|
|
1640
|
-
onConfirm
|
|
2216
|
+
onConfirm,
|
|
2217
|
+
imageOptimization
|
|
1641
2218
|
}) => {
|
|
1642
2219
|
const [appFilter, setAppFilter] = useState(defaultAppFilter);
|
|
1643
2220
|
const hasAppFilter = !!appId;
|
|
@@ -1647,13 +2224,6 @@ var AssetPickerContent = ({
|
|
|
1647
2224
|
const { apps: registryApps, getAppName } = useAppRegistry(collectionIdForRegistry);
|
|
1648
2225
|
const resolvedAppName = appName || (appId ? getAppName(appId) : void 0);
|
|
1649
2226
|
const listAppId = hasAppFilter ? appFilter === "app" ? appId : otherAppFilter || void 0 : void 0;
|
|
1650
|
-
const { assets, upload, uploadFromUrl, uploadFromRemoteUrl, uploading, uploadProgress } = useAssets({
|
|
1651
|
-
scope,
|
|
1652
|
-
accept: acceptProp,
|
|
1653
|
-
pageSize,
|
|
1654
|
-
appId: uploadGlobal ? void 0 : appId,
|
|
1655
|
-
listAppId
|
|
1656
|
-
});
|
|
1657
2227
|
const [tab, setTab] = useState("browse");
|
|
1658
2228
|
const [viewMode, setViewMode] = useState(defaultView);
|
|
1659
2229
|
const [search, setSearch] = useState("");
|
|
@@ -1663,7 +2233,7 @@ var AssetPickerContent = ({
|
|
|
1663
2233
|
return new Set(Array.isArray(value) ? value : [value]);
|
|
1664
2234
|
});
|
|
1665
2235
|
const hasProductScope = !!productScope;
|
|
1666
|
-
const [scopeTab, setScopeTab] = useState("collection");
|
|
2236
|
+
const [scopeTab, setScopeTab] = useState(hasProductScope ? "product" : "collection");
|
|
1667
2237
|
const effectiveAccept = useMemo(() => {
|
|
1668
2238
|
if (acceptProp) return acceptProp;
|
|
1669
2239
|
const entry = ASSET_MIME_FILTERS.find((f) => f.value === mimeFilter);
|
|
@@ -1680,6 +2250,13 @@ var AssetPickerContent = ({
|
|
|
1680
2250
|
}
|
|
1681
2251
|
return scope;
|
|
1682
2252
|
}, [scope, productScope, scopeTab, hasProductScope]);
|
|
2253
|
+
const { assets, upload, uploadFromUrl, uploadFromRemoteUrl, uploading, uploadProgress } = useAssets({
|
|
2254
|
+
scope: activeScope,
|
|
2255
|
+
accept: acceptProp,
|
|
2256
|
+
pageSize,
|
|
2257
|
+
appId: uploadGlobal ? void 0 : appId,
|
|
2258
|
+
listAppId
|
|
2259
|
+
});
|
|
1683
2260
|
const toSelection = useCallback((asset2) => ({
|
|
1684
2261
|
id: asset2.id,
|
|
1685
2262
|
url: asset2.url,
|
|
@@ -1928,6 +2505,7 @@ var AssetPickerContent = ({
|
|
|
1928
2505
|
allowDelete,
|
|
1929
2506
|
emptyText,
|
|
1930
2507
|
listAppId,
|
|
2508
|
+
requireProductId: hasProductScope && scopeTab === "product" ? productScope.productId : void 0,
|
|
1931
2509
|
currentAppId: appId,
|
|
1932
2510
|
currentAppName: resolvedAppName,
|
|
1933
2511
|
getAppName
|
|
@@ -1950,7 +2528,8 @@ var AssetPickerContent = ({
|
|
|
1950
2528
|
accept: acceptProp,
|
|
1951
2529
|
multiple,
|
|
1952
2530
|
uploading,
|
|
1953
|
-
uploadProgress
|
|
2531
|
+
uploadProgress,
|
|
2532
|
+
imageOptimization
|
|
1954
2533
|
}
|
|
1955
2534
|
)
|
|
1956
2535
|
] }),
|
|
@@ -2099,5 +2678,5 @@ var AssetPicker = (props) => {
|
|
|
2099
2678
|
assertStylesLoaded();
|
|
2100
2679
|
|
|
2101
2680
|
export { ASSET_MIME_FILTERS, AssetPicker, useAppRegistry, useAssets };
|
|
2102
|
-
//# sourceMappingURL=chunk-
|
|
2103
|
-
//# sourceMappingURL=chunk-
|
|
2681
|
+
//# sourceMappingURL=chunk-HKL24TFC.js.map
|
|
2682
|
+
//# sourceMappingURL=chunk-HKL24TFC.js.map
|