@proveanything/smartlinks-utils-ui 1.0.0 → 1.13.0
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-ZTUZPAHD.js → chunk-PSVYUVZC.js} +931 -150
- package/dist/chunk-PSVYUVZC.js.map +1 -0
- package/dist/components/AssetPicker/index.css +89 -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 +89 -12
- package/dist/components/ConditionsEditor/index.css.map +1 -1
- package/dist/components/FontPicker/index.css +89 -12
- package/dist/components/FontPicker/index.css.map +1 -1
- package/dist/components/IconPicker/index.css +89 -12
- package/dist/components/IconPicker/index.css.map +1 -1
- package/dist/components/LinkPicker/index.css +89 -12
- package/dist/components/LinkPicker/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.css +89 -12
- package/dist/components/RecordsAdmin/index.css.map +1 -1
- package/dist/index.css +89 -12
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{useAssets-DwUdZePm.d.ts → useAssets-tRkW00zx.d.ts} +62 -1
- package/package.json +3 -3
- package/dist/chunk-ZTUZPAHD.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, 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
|
|
@@ -130,8 +130,13 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
|
|
|
130
130
|
}
|
|
131
131
|
}, [scope, appId]);
|
|
132
132
|
const remove = useCallback(async (assetId) => {
|
|
133
|
+
const collectionId = scope.collectionId;
|
|
133
134
|
try {
|
|
134
|
-
|
|
135
|
+
if (collectionId && SL.asset.deleteAdmin) {
|
|
136
|
+
await SL.asset.deleteAdmin({ collectionId, assetId });
|
|
137
|
+
} else {
|
|
138
|
+
await SL.asset.remove({ assetId, scope });
|
|
139
|
+
}
|
|
135
140
|
if (mountedRef.current) {
|
|
136
141
|
setAssets((prev) => prev.filter((a) => a.id !== assetId));
|
|
137
142
|
}
|
|
@@ -141,6 +146,75 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
|
|
|
141
146
|
return false;
|
|
142
147
|
}
|
|
143
148
|
}, [scope]);
|
|
149
|
+
const restore = useCallback(async (assetId) => {
|
|
150
|
+
const collectionId = scope.collectionId;
|
|
151
|
+
if (!collectionId) {
|
|
152
|
+
if (mountedRef.current) setError("Restore requires a collection scope");
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const result = await SL.asset.restoreAdmin(collectionId, assetId);
|
|
157
|
+
if (mountedRef.current) {
|
|
158
|
+
setAssets((prev) => {
|
|
159
|
+
const exists = prev.some((a) => a.id === assetId);
|
|
160
|
+
return exists ? prev.map((a) => a.id === assetId ? result : a) : [result, ...prev];
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
} catch (err) {
|
|
165
|
+
if (mountedRef.current) setError(err?.message || "Restore failed");
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
}, [scope]);
|
|
169
|
+
const replaceFile = useCallback(async (assetId, file, onProgress) => {
|
|
170
|
+
const collectionId = scope.collectionId;
|
|
171
|
+
if (!collectionId) {
|
|
172
|
+
if (mountedRef.current) setError("Replace requires a collection scope");
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
setUploading(true);
|
|
176
|
+
setUploadProgress(0);
|
|
177
|
+
try {
|
|
178
|
+
const result = await SL.asset.replaceFile({
|
|
179
|
+
collectionId,
|
|
180
|
+
assetId,
|
|
181
|
+
file,
|
|
182
|
+
onProgress: (pct) => {
|
|
183
|
+
setUploadProgress(pct);
|
|
184
|
+
onProgress?.(pct);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
if (mountedRef.current) {
|
|
188
|
+
setAssets((prev) => prev.map((a) => a.id === assetId ? result : a));
|
|
189
|
+
}
|
|
190
|
+
return result;
|
|
191
|
+
} catch (err) {
|
|
192
|
+
if (mountedRef.current) setError(err?.message || "Replace failed");
|
|
193
|
+
return null;
|
|
194
|
+
} finally {
|
|
195
|
+
if (mountedRef.current) {
|
|
196
|
+
setUploading(false);
|
|
197
|
+
setUploadProgress(0);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}, [scope]);
|
|
201
|
+
const updateAsset = useCallback(async (assetId, patch) => {
|
|
202
|
+
const collectionId = scope.collectionId;
|
|
203
|
+
if (!collectionId) {
|
|
204
|
+
if (mountedRef.current) setError("Update requires a collection scope");
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
const result = await SL.asset.updateAdmin({ collectionId, assetId, ...patch });
|
|
209
|
+
if (mountedRef.current) {
|
|
210
|
+
setAssets((prev) => prev.map((a) => a.id === assetId ? result : a));
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (mountedRef.current) setError(err?.message || "Update failed");
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}, [scope]);
|
|
144
218
|
return {
|
|
145
219
|
assets,
|
|
146
220
|
loading,
|
|
@@ -150,6 +224,9 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
|
|
|
150
224
|
uploadFromUrl,
|
|
151
225
|
uploadFromRemoteUrl,
|
|
152
226
|
remove,
|
|
227
|
+
restore,
|
|
228
|
+
replaceFile,
|
|
229
|
+
updateAsset,
|
|
153
230
|
uploading,
|
|
154
231
|
uploadProgress
|
|
155
232
|
};
|
|
@@ -232,7 +309,7 @@ function useAppRegistry(collectionId) {
|
|
|
232
309
|
}
|
|
233
310
|
function getIcon(mimeType) {
|
|
234
311
|
if (!mimeType) return FileIcon;
|
|
235
|
-
if (mimeType.startsWith("image/")) return Image;
|
|
312
|
+
if (mimeType.startsWith("image/")) return Image$1;
|
|
236
313
|
if (mimeType.startsWith("video/")) return Film;
|
|
237
314
|
if (mimeType.startsWith("audio/")) return Music;
|
|
238
315
|
return FileText;
|
|
@@ -244,6 +321,7 @@ function formatSize(bytes) {
|
|
|
244
321
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
245
322
|
}
|
|
246
323
|
function getThumbnail(asset2) {
|
|
324
|
+
if (asset2.thumbnail) return asset2.thumbnail;
|
|
247
325
|
if (asset2.thumbnails?.x200) return asset2.thumbnails.x200;
|
|
248
326
|
if (asset2.thumbnails?.x100) return asset2.thumbnails.x100;
|
|
249
327
|
if (asset2.thumbnails?.x512) return asset2.thumbnails.x512;
|
|
@@ -251,7 +329,7 @@ function getThumbnail(asset2) {
|
|
|
251
329
|
return null;
|
|
252
330
|
}
|
|
253
331
|
function getAssetAppId(asset2) {
|
|
254
|
-
return asset2.appId || asset2.metadata?.appId || asset2.metadata?.app || void 0;
|
|
332
|
+
return asset2.app || asset2.appId || asset2.metadata?.appId || asset2.metadata?.app || void 0;
|
|
255
333
|
}
|
|
256
334
|
var AppBadge = ({ appId, appName, size = "sm" }) => {
|
|
257
335
|
const label = appName && appName !== appId ? appName : appId;
|
|
@@ -271,11 +349,134 @@ var AppBadge = ({ appId, appName, size = "sm" }) => {
|
|
|
271
349
|
}
|
|
272
350
|
);
|
|
273
351
|
};
|
|
274
|
-
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 }) => {
|
|
275
475
|
const thumb = getThumbnail(asset2);
|
|
276
476
|
const Icon = getIcon(asset2.mimeType);
|
|
277
477
|
const ownerAppId = getAssetAppId(asset2);
|
|
278
478
|
const showAppBadge = !!currentAppId && !!ownerAppId && ownerAppId !== currentAppId;
|
|
479
|
+
const labels = asset2.labels || [];
|
|
279
480
|
return /* @__PURE__ */ jsxs(
|
|
280
481
|
"div",
|
|
281
482
|
{
|
|
@@ -313,31 +514,57 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
313
514
|
formatSize(asset2.size),
|
|
314
515
|
asset2.mimeType && ` \u2022 ${asset2.mimeType.split("/")[1]?.toUpperCase() || asset2.mimeType}`
|
|
315
516
|
] }),
|
|
316
|
-
showAppBadge && /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(AppBadge, { appId: ownerAppId, appName: getAppName?.(ownerAppId), size: "xs" }) })
|
|
517
|
+
showAppBadge && /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(AppBadge, { appId: ownerAppId, appName: getAppName?.(ownerAppId), size: "xs" }) }),
|
|
518
|
+
labels.length > 0 && /* @__PURE__ */ jsxs("div", { className: "mt-1 flex flex-wrap gap-0.5", children: [
|
|
519
|
+
labels.slice(0, 3).map((lbl) => {
|
|
520
|
+
const active = !!activeLabels?.has(lbl);
|
|
521
|
+
return /* @__PURE__ */ jsxs(
|
|
522
|
+
"button",
|
|
523
|
+
{
|
|
524
|
+
type: "button",
|
|
525
|
+
onClick: (e) => {
|
|
526
|
+
e.stopPropagation();
|
|
527
|
+
onToggleLabel?.(lbl);
|
|
528
|
+
},
|
|
529
|
+
className: cn(
|
|
530
|
+
"inline-flex items-center gap-0.5 rounded-full border px-1 py-0 text-[9px] max-w-full truncate transition-colors",
|
|
531
|
+
active ? "bg-primary text-primary-foreground border-primary" : "bg-muted text-muted-foreground border-border hover:bg-muted/70"
|
|
532
|
+
),
|
|
533
|
+
title: `Filter by label: ${lbl}`,
|
|
534
|
+
children: [
|
|
535
|
+
/* @__PURE__ */ jsx(Tag, { className: "w-2 h-2 flex-shrink-0" }),
|
|
536
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: lbl })
|
|
537
|
+
]
|
|
538
|
+
},
|
|
539
|
+
lbl
|
|
540
|
+
);
|
|
541
|
+
}),
|
|
542
|
+
labels.length > 3 && /* @__PURE__ */ jsxs("span", { className: "text-[9px] text-muted-foreground", children: [
|
|
543
|
+
"+",
|
|
544
|
+
labels.length - 3
|
|
545
|
+
] })
|
|
546
|
+
] })
|
|
317
547
|
] }),
|
|
318
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" }) }),
|
|
319
|
-
|
|
320
|
-
|
|
549
|
+
/* @__PURE__ */ jsx(
|
|
550
|
+
CardMenu,
|
|
321
551
|
{
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
onDelete();
|
|
327
|
-
},
|
|
328
|
-
title: "Delete asset",
|
|
329
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3" })
|
|
552
|
+
onRename,
|
|
553
|
+
onReplace,
|
|
554
|
+
onEditTags,
|
|
555
|
+
onDelete: allowDelete ? onDelete : void 0
|
|
330
556
|
}
|
|
331
557
|
)
|
|
332
558
|
]
|
|
333
559
|
}
|
|
334
560
|
);
|
|
335
561
|
};
|
|
336
|
-
var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, allowDelete, currentAppId, getAppName }) => {
|
|
562
|
+
var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, onRename, onReplace, onEditTags, allowDelete, currentAppId, getAppName, activeLabels, onToggleLabel }) => {
|
|
337
563
|
const thumb = getThumbnail(asset2);
|
|
338
564
|
const Icon = getIcon(asset2.mimeType);
|
|
339
565
|
const ownerAppId = getAssetAppId(asset2);
|
|
340
566
|
const showAppBadge = !!currentAppId && !!ownerAppId && ownerAppId !== currentAppId;
|
|
567
|
+
const labels = asset2.labels || [];
|
|
341
568
|
return /* @__PURE__ */ jsxs(
|
|
342
569
|
"div",
|
|
343
570
|
{
|
|
@@ -369,20 +596,40 @@ var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
|
|
|
369
596
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
370
597
|
formatSize(asset2.size),
|
|
371
598
|
asset2.mimeType && ` \u2022 ${asset2.mimeType}`
|
|
372
|
-
] })
|
|
599
|
+
] }),
|
|
600
|
+
labels.length > 0 && /* @__PURE__ */ jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: labels.map((lbl) => {
|
|
601
|
+
const active = !!activeLabels?.has(lbl);
|
|
602
|
+
return /* @__PURE__ */ jsxs(
|
|
603
|
+
"button",
|
|
604
|
+
{
|
|
605
|
+
type: "button",
|
|
606
|
+
onClick: (e) => {
|
|
607
|
+
e.stopPropagation();
|
|
608
|
+
onToggleLabel?.(lbl);
|
|
609
|
+
},
|
|
610
|
+
className: cn(
|
|
611
|
+
"inline-flex items-center gap-0.5 rounded-full border px-1.5 py-0 text-[10px] transition-colors",
|
|
612
|
+
active ? "bg-primary text-primary-foreground border-primary" : "bg-muted text-muted-foreground border-border hover:bg-muted/70"
|
|
613
|
+
),
|
|
614
|
+
title: `Filter by label: ${lbl}`,
|
|
615
|
+
children: [
|
|
616
|
+
/* @__PURE__ */ jsx(Tag, { className: "w-2.5 h-2.5 flex-shrink-0" }),
|
|
617
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: lbl })
|
|
618
|
+
]
|
|
619
|
+
},
|
|
620
|
+
lbl
|
|
621
|
+
);
|
|
622
|
+
}) })
|
|
373
623
|
] }),
|
|
374
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" }) }),
|
|
375
|
-
|
|
376
|
-
|
|
625
|
+
/* @__PURE__ */ jsx(
|
|
626
|
+
CardMenu,
|
|
377
627
|
{
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
},
|
|
384
|
-
title: "Delete",
|
|
385
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "w-3 h-3" })
|
|
628
|
+
onRename,
|
|
629
|
+
onReplace,
|
|
630
|
+
onEditTags,
|
|
631
|
+
onDelete: allowDelete ? onDelete : void 0,
|
|
632
|
+
position: "inline"
|
|
386
633
|
}
|
|
387
634
|
)
|
|
388
635
|
]
|
|
@@ -396,9 +643,14 @@ var AssetGrid = ({
|
|
|
396
643
|
onToggleSelect,
|
|
397
644
|
onDoubleClickSelect,
|
|
398
645
|
onDelete,
|
|
646
|
+
onRename,
|
|
647
|
+
onReplace,
|
|
648
|
+
onEditTags,
|
|
399
649
|
allowDelete,
|
|
400
650
|
currentAppId,
|
|
401
|
-
getAppName
|
|
651
|
+
getAppName,
|
|
652
|
+
activeLabels,
|
|
653
|
+
onToggleLabel
|
|
402
654
|
}) => {
|
|
403
655
|
if (assets.length === 0) return null;
|
|
404
656
|
if (viewMode === "list") {
|
|
@@ -410,9 +662,14 @@ var AssetGrid = ({
|
|
|
410
662
|
onToggle: () => onToggleSelect(asset2),
|
|
411
663
|
onDoubleClick: onDoubleClickSelect ? () => onDoubleClickSelect(asset2) : void 0,
|
|
412
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,
|
|
413
668
|
allowDelete,
|
|
414
669
|
currentAppId,
|
|
415
|
-
getAppName
|
|
670
|
+
getAppName,
|
|
671
|
+
activeLabels,
|
|
672
|
+
onToggleLabel
|
|
416
673
|
},
|
|
417
674
|
asset2.id
|
|
418
675
|
)) });
|
|
@@ -425,25 +682,156 @@ var AssetGrid = ({
|
|
|
425
682
|
onToggle: () => onToggleSelect(asset2),
|
|
426
683
|
onDoubleClick: onDoubleClickSelect ? () => onDoubleClickSelect(asset2) : void 0,
|
|
427
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,
|
|
428
688
|
allowDelete,
|
|
429
689
|
currentAppId,
|
|
430
|
-
getAppName
|
|
690
|
+
getAppName,
|
|
691
|
+
activeLabels,
|
|
692
|
+
onToggleLabel
|
|
431
693
|
},
|
|
432
694
|
asset2.id
|
|
433
695
|
)) });
|
|
434
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
|
+
}
|
|
435
789
|
var UploadZone = ({
|
|
436
790
|
onFiles,
|
|
437
791
|
accept,
|
|
438
792
|
multiple,
|
|
439
793
|
uploading,
|
|
440
794
|
uploadProgress = 0,
|
|
441
|
-
className
|
|
795
|
+
className,
|
|
796
|
+
imageOptimization
|
|
442
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]);
|
|
443
811
|
const [dragOver, setDragOver] = useState(false);
|
|
444
812
|
const [pastedFile, setPastedFile] = useState(null);
|
|
445
813
|
const [editingName, setEditingName] = useState(false);
|
|
446
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);
|
|
447
835
|
const inputRef = useRef(null);
|
|
448
836
|
const nameInputRef = useRef(null);
|
|
449
837
|
const zoneRef = useRef(null);
|
|
@@ -468,9 +856,12 @@ var UploadZone = ({
|
|
|
468
856
|
e.preventDefault();
|
|
469
857
|
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : "";
|
|
470
858
|
const defaultName = file.name === "image.png" ? `pasted-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/[T:]/g, "-")}` : file.name.replace(/\.[^.]+$/, "");
|
|
471
|
-
setPastedFile({ file, previewUrl, name: defaultName });
|
|
859
|
+
setPastedFile({ file, previewUrl, name: defaultName, origSize: file.size });
|
|
472
860
|
setFileName(defaultName);
|
|
473
861
|
setEditingName(false);
|
|
862
|
+
getImageDimensions(file).then((dims) => {
|
|
863
|
+
if (dims) setPastedFile((prev) => prev && prev.file === file ? { ...prev, origDims: dims } : prev);
|
|
864
|
+
});
|
|
474
865
|
return;
|
|
475
866
|
}
|
|
476
867
|
}
|
|
@@ -478,15 +869,28 @@ var UploadZone = ({
|
|
|
478
869
|
document.addEventListener("paste", handlePaste);
|
|
479
870
|
return () => document.removeEventListener("paste", handlePaste);
|
|
480
871
|
}, [uploading, accept]);
|
|
481
|
-
const handleConfirmPaste = useCallback(() => {
|
|
872
|
+
const handleConfirmPaste = useCallback(async () => {
|
|
482
873
|
if (!pastedFile) return;
|
|
483
|
-
|
|
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";
|
|
484
888
|
const finalName = `${fileName.trim() || "pasted-image"}.${ext}`;
|
|
485
|
-
const renamedFile = new File([
|
|
889
|
+
const renamedFile = new File([working], finalName, { type: working.type });
|
|
486
890
|
onFiles([renamedFile]);
|
|
487
891
|
if (pastedFile.previewUrl) URL.revokeObjectURL(pastedFile.previewUrl);
|
|
488
892
|
setPastedFile(null);
|
|
489
|
-
}, [pastedFile, fileName, onFiles]);
|
|
893
|
+
}, [pastedFile, fileName, onFiles, autoOptimize, optConfig]);
|
|
490
894
|
const handleCancelPaste = useCallback(() => {
|
|
491
895
|
if (pastedFile?.previewUrl) URL.revokeObjectURL(pastedFile.previewUrl);
|
|
492
896
|
setPastedFile(null);
|
|
@@ -509,9 +913,12 @@ var UploadZone = ({
|
|
|
509
913
|
const presentForRename = useCallback((file) => {
|
|
510
914
|
const previewUrl = file.type.startsWith("image/") ? URL.createObjectURL(file) : "";
|
|
511
915
|
const defaultName = file.name.replace(/\.[^.]+$/, "") || "file";
|
|
512
|
-
setPastedFile({ file, previewUrl, name: defaultName });
|
|
916
|
+
setPastedFile({ file, previewUrl, name: defaultName, origSize: file.size });
|
|
513
917
|
setFileName(defaultName);
|
|
514
918
|
setEditingName(false);
|
|
919
|
+
getImageDimensions(file).then((dims) => {
|
|
920
|
+
if (dims) setPastedFile((prev) => prev && prev.file === file ? { ...prev, origDims: dims } : prev);
|
|
921
|
+
});
|
|
515
922
|
}, []);
|
|
516
923
|
const handleDrop = useCallback((e) => {
|
|
517
924
|
e.preventDefault();
|
|
@@ -521,7 +928,7 @@ var UploadZone = ({
|
|
|
521
928
|
if (files.length === 1 && !multiple) {
|
|
522
929
|
presentForRename(files[0]);
|
|
523
930
|
} else if (files.length > 0) {
|
|
524
|
-
|
|
931
|
+
void handleBatchFiles(multiple ? files : [files[0]]);
|
|
525
932
|
}
|
|
526
933
|
}, [onFiles, multiple, presentForRename]);
|
|
527
934
|
const handleInputChange = useCallback((e) => {
|
|
@@ -529,87 +936,189 @@ var UploadZone = ({
|
|
|
529
936
|
if (files.length === 1 && !multiple) {
|
|
530
937
|
presentForRename(files[0]);
|
|
531
938
|
} else if (files.length > 0) {
|
|
532
|
-
|
|
939
|
+
void handleBatchFiles(multiple ? files : [files[0]]);
|
|
533
940
|
}
|
|
534
941
|
e.target.value = "";
|
|
535
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;
|
|
536
985
|
if (pastedFile) {
|
|
537
|
-
|
|
986
|
+
const willOptimize = autoOptimize && isProcessableImage(pastedFile.file);
|
|
987
|
+
return /* @__PURE__ */ jsxs("div", { className: cn(
|
|
538
988
|
"border-2 border-solid border-primary rounded-lg p-4 transition-colors",
|
|
539
989
|
className
|
|
540
|
-
), children:
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
{
|
|
544
|
-
src: pastedFile.previewUrl,
|
|
545
|
-
alt: "Pasted content",
|
|
546
|
-
className: "max-h-32 max-w-full rounded-md object-contain border border-border"
|
|
547
|
-
}
|
|
548
|
-
) : /* @__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" }) }),
|
|
549
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5 w-full max-w-xs", children: editingName ? /* @__PURE__ */ jsx(
|
|
550
|
-
"input",
|
|
551
|
-
{
|
|
552
|
-
ref: nameInputRef,
|
|
553
|
-
type: "text",
|
|
554
|
-
value: fileName,
|
|
555
|
-
onChange: (e) => setFileName(e.target.value),
|
|
556
|
-
onKeyDown: (e) => {
|
|
557
|
-
if (e.key === "Enter") {
|
|
558
|
-
setEditingName(false);
|
|
559
|
-
handleConfirmPaste();
|
|
560
|
-
}
|
|
561
|
-
if (e.key === "Escape") setEditingName(false);
|
|
562
|
-
},
|
|
563
|
-
onBlur: () => setEditingName(false),
|
|
564
|
-
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",
|
|
565
|
-
placeholder: "File name"
|
|
566
|
-
}
|
|
567
|
-
) : /* @__PURE__ */ jsxs(
|
|
568
|
-
"button",
|
|
569
|
-
{
|
|
570
|
-
type: "button",
|
|
571
|
-
onClick: () => setEditingName(true),
|
|
572
|
-
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",
|
|
573
|
-
title: "Rename",
|
|
574
|
-
children: [
|
|
575
|
-
/* @__PURE__ */ jsx("span", { className: "truncate max-w-[200px]", children: fileName }),
|
|
576
|
-
/* @__PURE__ */ jsx(Pencil, { className: "w-3 h-3 flex-shrink-0 opacity-50" })
|
|
577
|
-
]
|
|
578
|
-
}
|
|
579
|
-
) }),
|
|
580
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
581
|
-
pastedFile.file.type,
|
|
582
|
-
" \xB7 ",
|
|
583
|
-
(pastedFile.file.size / 1024).toFixed(1),
|
|
584
|
-
" KB"
|
|
585
|
-
] }),
|
|
586
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
587
|
-
/* @__PURE__ */ jsxs(
|
|
990
|
+
), children: [
|
|
991
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3", children: [
|
|
992
|
+
pastedFile.previewUrl ? /* @__PURE__ */ jsxs(
|
|
588
993
|
"button",
|
|
589
994
|
{
|
|
590
995
|
type: "button",
|
|
591
|
-
onClick:
|
|
592
|
-
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",
|
|
593
999
|
children: [
|
|
594
|
-
/* @__PURE__ */ jsx(
|
|
595
|
-
|
|
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" }) })
|
|
596
1011
|
]
|
|
597
1012
|
}
|
|
598
|
-
),
|
|
599
|
-
/* @__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(
|
|
600
1033
|
"button",
|
|
601
1034
|
{
|
|
602
1035
|
type: "button",
|
|
603
|
-
onClick:
|
|
604
|
-
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",
|
|
605
1039
|
children: [
|
|
606
|
-
/* @__PURE__ */ jsx(
|
|
607
|
-
"
|
|
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" })
|
|
608
1042
|
]
|
|
609
1043
|
}
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
|
|
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
|
+
] });
|
|
613
1122
|
}
|
|
614
1123
|
return /* @__PURE__ */ jsxs(
|
|
615
1124
|
"div",
|
|
@@ -644,13 +1153,10 @@ var UploadZone = ({
|
|
|
644
1153
|
className: "hidden"
|
|
645
1154
|
}
|
|
646
1155
|
),
|
|
647
|
-
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: [
|
|
648
1157
|
/* @__PURE__ */ jsx(Loader2, { className: "w-6 h-6 text-primary animate-spin" }),
|
|
649
|
-
/* @__PURE__ */
|
|
650
|
-
|
|
651
|
-
uploadProgress > 0 ? `${Math.round(uploadProgress)}%` : ""
|
|
652
|
-
] }),
|
|
653
|
-
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(
|
|
654
1160
|
"div",
|
|
655
1161
|
{
|
|
656
1162
|
className: "h-full bg-primary rounded-full transition-all duration-300",
|
|
@@ -671,7 +1177,15 @@ var UploadZone = ({
|
|
|
671
1177
|
accept && /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
672
1178
|
"Accepts: ",
|
|
673
1179
|
accept
|
|
674
|
-
] })
|
|
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
|
+
)
|
|
675
1189
|
] })
|
|
676
1190
|
]
|
|
677
1191
|
}
|
|
@@ -1323,7 +1837,7 @@ var StockPhotoSearch = ({
|
|
|
1323
1837
|
/* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5" }),
|
|
1324
1838
|
" Saved"
|
|
1325
1839
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1326
|
-
/* @__PURE__ */ jsx(Image, { className: "w-3.5 h-3.5" }),
|
|
1840
|
+
/* @__PURE__ */ jsx(Image$1, { className: "w-3.5 h-3.5" }),
|
|
1327
1841
|
" Save"
|
|
1328
1842
|
] })
|
|
1329
1843
|
}
|
|
@@ -1358,6 +1872,144 @@ var StockPhotoSearch = ({
|
|
|
1358
1872
|
}) })
|
|
1359
1873
|
] });
|
|
1360
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
|
+
};
|
|
1361
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: [
|
|
1362
2014
|
/* @__PURE__ */ jsx(
|
|
1363
2015
|
"input",
|
|
@@ -1374,14 +2026,78 @@ var GlobalUploadToggle = ({ checked, onChange, appName }) => /* @__PURE__ */ jsx
|
|
|
1374
2026
|
] })
|
|
1375
2027
|
] });
|
|
1376
2028
|
var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId, currentAppId, currentAppName, getAppName }) => {
|
|
1377
|
-
const { assets, loading, error, refresh } = useAssets({ scope, accept, pageSize, listAppId });
|
|
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]);
|
|
2066
|
+
const [activeLabels, setActiveLabels] = useState(/* @__PURE__ */ new Set());
|
|
2067
|
+
const toggleLabel = useCallback((label) => {
|
|
2068
|
+
setActiveLabels((prev) => {
|
|
2069
|
+
const next = new Set(prev);
|
|
2070
|
+
if (next.has(label)) next.delete(label);
|
|
2071
|
+
else next.add(label);
|
|
2072
|
+
return next;
|
|
2073
|
+
});
|
|
2074
|
+
}, []);
|
|
2075
|
+
const clearLabels = useCallback(() => setActiveLabels(/* @__PURE__ */ new Set()), []);
|
|
2076
|
+
const allLabels = useMemo(() => {
|
|
2077
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2078
|
+
for (const a of assets) {
|
|
2079
|
+
for (const lbl of a.labels || []) {
|
|
2080
|
+
counts.set(lbl, (counts.get(lbl) || 0) + 1);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
return Array.from(counts.entries()).sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([label, count]) => ({ label, count }));
|
|
2084
|
+
}, [assets]);
|
|
1378
2085
|
const filteredAssets = useMemo(() => {
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
2086
|
+
const q = search.trim().toLowerCase();
|
|
2087
|
+
return assets.filter((a) => {
|
|
2088
|
+
if (q) {
|
|
2089
|
+
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));
|
|
2090
|
+
if (!hit) return false;
|
|
2091
|
+
}
|
|
2092
|
+
if (activeLabels.size > 0) {
|
|
2093
|
+
const labels = new Set(a.labels || []);
|
|
2094
|
+
for (const need of activeLabels) {
|
|
2095
|
+
if (!labels.has(need)) return false;
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
return true;
|
|
2099
|
+
});
|
|
2100
|
+
}, [assets, search, activeLabels]);
|
|
1385
2101
|
if (loading && assets.length === 0) {
|
|
1386
2102
|
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" }) });
|
|
1387
2103
|
}
|
|
@@ -1392,28 +2108,87 @@ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedI
|
|
|
1392
2108
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: refresh, className: "ml-auto underline text-xs", children: "Retry" })
|
|
1393
2109
|
] });
|
|
1394
2110
|
}
|
|
1395
|
-
|
|
1396
|
-
|
|
2111
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2112
|
+
allLabels.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 flex-wrap", children: [
|
|
2113
|
+
/* @__PURE__ */ jsx(Tag, { className: "w-3.5 h-3.5 text-muted-foreground flex-shrink-0" }),
|
|
2114
|
+
allLabels.slice(0, 12).map(({ label, count }) => {
|
|
2115
|
+
const active = activeLabels.has(label);
|
|
2116
|
+
return /* @__PURE__ */ jsxs(
|
|
2117
|
+
"button",
|
|
2118
|
+
{
|
|
2119
|
+
type: "button",
|
|
2120
|
+
onClick: () => toggleLabel(label),
|
|
2121
|
+
className: cn(
|
|
2122
|
+
"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] transition-colors",
|
|
2123
|
+
active ? "bg-primary text-primary-foreground border-primary" : "bg-muted text-muted-foreground border-border hover:bg-muted/70"
|
|
2124
|
+
),
|
|
2125
|
+
title: active ? `Remove "${label}" from filter` : `Filter by "${label}"`,
|
|
2126
|
+
children: [
|
|
2127
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: label }),
|
|
2128
|
+
/* @__PURE__ */ jsx("span", { className: "opacity-70", children: count })
|
|
2129
|
+
]
|
|
2130
|
+
},
|
|
2131
|
+
label
|
|
2132
|
+
);
|
|
2133
|
+
}),
|
|
2134
|
+
activeLabels.size > 0 && /* @__PURE__ */ jsxs(
|
|
2135
|
+
"button",
|
|
2136
|
+
{
|
|
2137
|
+
type: "button",
|
|
2138
|
+
onClick: clearLabels,
|
|
2139
|
+
className: "inline-flex items-center gap-0.5 text-[11px] text-muted-foreground hover:text-foreground",
|
|
2140
|
+
title: "Clear label filter",
|
|
2141
|
+
children: [
|
|
2142
|
+
/* @__PURE__ */ jsx(X, { className: "w-3 h-3" }),
|
|
2143
|
+
" clear"
|
|
2144
|
+
]
|
|
2145
|
+
}
|
|
2146
|
+
)
|
|
2147
|
+
] }),
|
|
2148
|
+
filteredAssets.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-muted-foreground", children: [
|
|
1397
2149
|
/* @__PURE__ */ jsx(ImageOff, { className: "w-8 h-8 mb-2" }),
|
|
1398
2150
|
/* @__PURE__ */ jsx("p", { className: "text-sm", children: emptyText || "No assets found" }),
|
|
1399
|
-
search && /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: "Try adjusting your
|
|
1400
|
-
] })
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
2151
|
+
(search || activeLabels.size > 0) && /* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: "Try adjusting your filters" })
|
|
2152
|
+
] }) : /* @__PURE__ */ jsx(
|
|
2153
|
+
AssetGrid,
|
|
2154
|
+
{
|
|
2155
|
+
assets: filteredAssets,
|
|
2156
|
+
viewMode,
|
|
2157
|
+
selectedIds,
|
|
2158
|
+
onToggleSelect,
|
|
2159
|
+
onDoubleClickSelect,
|
|
2160
|
+
onDelete: allowDelete ? handleDeleteWithConfirm : void 0,
|
|
2161
|
+
onRename: allowDelete ? handleRename : void 0,
|
|
2162
|
+
onReplace: allowDelete ? handleReplace : void 0,
|
|
2163
|
+
onEditTags: allowDelete ? handleEditTags : void 0,
|
|
2164
|
+
allowDelete,
|
|
2165
|
+
currentAppId,
|
|
2166
|
+
currentAppName,
|
|
2167
|
+
getAppName,
|
|
2168
|
+
activeLabels,
|
|
2169
|
+
onToggleLabel: toggleLabel
|
|
2170
|
+
}
|
|
2171
|
+
),
|
|
2172
|
+
/* @__PURE__ */ jsx(
|
|
2173
|
+
"input",
|
|
2174
|
+
{
|
|
2175
|
+
ref: replaceInputRef,
|
|
2176
|
+
type: "file",
|
|
2177
|
+
className: "hidden",
|
|
2178
|
+
onChange: handleReplaceFiles
|
|
2179
|
+
}
|
|
2180
|
+
),
|
|
2181
|
+
tagEditorAsset && /* @__PURE__ */ jsx(
|
|
2182
|
+
TagEditor,
|
|
2183
|
+
{
|
|
2184
|
+
initial: tagEditorAsset.labels || [],
|
|
2185
|
+
suggestions: allLabels.map((l) => l.label),
|
|
2186
|
+
assetName: tagEditorAsset.cleanName || tagEditorAsset.name,
|
|
2187
|
+
onCancel: () => setTagEditorAsset(null),
|
|
2188
|
+
onSave: handleSaveTags
|
|
2189
|
+
}
|
|
2190
|
+
)
|
|
2191
|
+
] });
|
|
1417
2192
|
};
|
|
1418
2193
|
var AssetPickerContent = ({
|
|
1419
2194
|
scope,
|
|
@@ -1434,7 +2209,8 @@ var AssetPickerContent = ({
|
|
|
1434
2209
|
defaultView = "grid",
|
|
1435
2210
|
emptyText,
|
|
1436
2211
|
pageSize = 50,
|
|
1437
|
-
onConfirm
|
|
2212
|
+
onConfirm,
|
|
2213
|
+
imageOptimization
|
|
1438
2214
|
}) => {
|
|
1439
2215
|
const [appFilter, setAppFilter] = useState(defaultAppFilter);
|
|
1440
2216
|
const hasAppFilter = !!appId;
|
|
@@ -1444,13 +2220,6 @@ var AssetPickerContent = ({
|
|
|
1444
2220
|
const { apps: registryApps, getAppName } = useAppRegistry(collectionIdForRegistry);
|
|
1445
2221
|
const resolvedAppName = appName || (appId ? getAppName(appId) : void 0);
|
|
1446
2222
|
const listAppId = hasAppFilter ? appFilter === "app" ? appId : otherAppFilter || void 0 : void 0;
|
|
1447
|
-
const { assets, upload, uploadFromUrl, uploadFromRemoteUrl, uploading, uploadProgress } = useAssets({
|
|
1448
|
-
scope,
|
|
1449
|
-
accept: acceptProp,
|
|
1450
|
-
pageSize,
|
|
1451
|
-
appId: uploadGlobal ? void 0 : appId,
|
|
1452
|
-
listAppId
|
|
1453
|
-
});
|
|
1454
2223
|
const [tab, setTab] = useState("browse");
|
|
1455
2224
|
const [viewMode, setViewMode] = useState(defaultView);
|
|
1456
2225
|
const [search, setSearch] = useState("");
|
|
@@ -1460,7 +2229,7 @@ var AssetPickerContent = ({
|
|
|
1460
2229
|
return new Set(Array.isArray(value) ? value : [value]);
|
|
1461
2230
|
});
|
|
1462
2231
|
const hasProductScope = !!productScope;
|
|
1463
|
-
const [scopeTab, setScopeTab] = useState("collection");
|
|
2232
|
+
const [scopeTab, setScopeTab] = useState(hasProductScope ? "product" : "collection");
|
|
1464
2233
|
const effectiveAccept = useMemo(() => {
|
|
1465
2234
|
if (acceptProp) return acceptProp;
|
|
1466
2235
|
const entry = ASSET_MIME_FILTERS.find((f) => f.value === mimeFilter);
|
|
@@ -1477,6 +2246,13 @@ var AssetPickerContent = ({
|
|
|
1477
2246
|
}
|
|
1478
2247
|
return scope;
|
|
1479
2248
|
}, [scope, productScope, scopeTab, hasProductScope]);
|
|
2249
|
+
const { assets, upload, uploadFromUrl, uploadFromRemoteUrl, uploading, uploadProgress } = useAssets({
|
|
2250
|
+
scope: activeScope,
|
|
2251
|
+
accept: acceptProp,
|
|
2252
|
+
pageSize,
|
|
2253
|
+
appId: uploadGlobal ? void 0 : appId,
|
|
2254
|
+
listAppId
|
|
2255
|
+
});
|
|
1480
2256
|
const toSelection = useCallback((asset2) => ({
|
|
1481
2257
|
id: asset2.id,
|
|
1482
2258
|
url: asset2.url,
|
|
@@ -1484,6 +2260,10 @@ var AssetPickerContent = ({
|
|
|
1484
2260
|
mimeType: asset2.mimeType,
|
|
1485
2261
|
size: asset2.size,
|
|
1486
2262
|
metadata: asset2.metadata,
|
|
2263
|
+
thumbnail: asset2.thumbnail ?? null,
|
|
2264
|
+
app: asset2.app ?? null,
|
|
2265
|
+
labels: asset2.labels,
|
|
2266
|
+
// Deprecated — kept for backward-compat with hosts still reading `thumbnails`.
|
|
1487
2267
|
thumbnails: asset2.thumbnails
|
|
1488
2268
|
}), []);
|
|
1489
2269
|
const handleToggleSelect = useCallback((asset2) => {
|
|
@@ -1743,7 +2523,8 @@ var AssetPickerContent = ({
|
|
|
1743
2523
|
accept: acceptProp,
|
|
1744
2524
|
multiple,
|
|
1745
2525
|
uploading,
|
|
1746
|
-
uploadProgress
|
|
2526
|
+
uploadProgress,
|
|
2527
|
+
imageOptimization
|
|
1747
2528
|
}
|
|
1748
2529
|
)
|
|
1749
2530
|
] }),
|
|
@@ -1892,5 +2673,5 @@ var AssetPicker = (props) => {
|
|
|
1892
2673
|
assertStylesLoaded();
|
|
1893
2674
|
|
|
1894
2675
|
export { ASSET_MIME_FILTERS, AssetPicker, useAppRegistry, useAssets };
|
|
1895
|
-
//# sourceMappingURL=chunk-
|
|
1896
|
-
//# sourceMappingURL=chunk-
|
|
2676
|
+
//# sourceMappingURL=chunk-PSVYUVZC.js.map
|
|
2677
|
+
//# sourceMappingURL=chunk-PSVYUVZC.js.map
|