@proveanything/smartlinks-utils-ui 1.13.3 → 1.13.5

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.
@@ -3,7 +3,7 @@ import { cn } from './chunk-L7FQ52F5.js';
3
3
  import React7, { useState, useRef, useEffect, useCallback, useMemo, useLayoutEffect } from 'react';
4
4
  import { createPortal } from 'react-dom';
5
5
  import * as SL from '@proveanything/smartlinks';
6
- import { Filter, Search, LayoutGrid, List, Loader2, AlertCircle, Tag, X, ImageOff, Wand2, Maximize2, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image as Image$1, Plus, FileIcon, Film, Music, FileText, AppWindow, MoreVertical, Trash2 } from 'lucide-react';
6
+ import { Filter, Search, LayoutGrid, List, Loader2, AlertCircle, Tag, X, ImageOff, Wand2, Maximize2, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image as Image$1, Plus, AlertTriangle, FileIcon, Film, Music, FileText, AppWindow, MoreVertical, Trash2 } from 'lucide-react';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
 
9
9
  // src/components/AssetPicker/types.ts
@@ -15,66 +15,6 @@ var ASSET_MIME_FILTERS = [
15
15
  { value: "document", label: "Documents", prefix: "application/" },
16
16
  { value: "pdf", label: "PDFs", prefix: "application/pdf" }
17
17
  ];
18
- function safeDecode(value) {
19
- if (!value) return "";
20
- try {
21
- return decodeURIComponent(value);
22
- } catch {
23
- return value;
24
- }
25
- }
26
- function getAssetIdentityKeys(asset2) {
27
- const keys = [];
28
- if (asset2.id) keys.push(`id:${asset2.id}`);
29
- const normalizedUrl = safeDecode(asset2.url).trim().toLowerCase();
30
- if (normalizedUrl) keys.push(`url:${normalizedUrl}`);
31
- const normalizedThumb = safeDecode(asset2.thumbnail).trim().toLowerCase();
32
- if (normalizedThumb) keys.push(`thumb:${normalizedThumb}`);
33
- const normalizedHash = asset2.hash?.trim().toLowerCase();
34
- if (normalizedHash) keys.push(`hash:${normalizedHash}`);
35
- return keys;
36
- }
37
- function scoreAsset(asset2) {
38
- const productId = asset2.productId ?? asset2.metadata?.productId;
39
- const proofId = asset2.proofId ?? asset2.metadata?.proofId;
40
- const plainName = asset2.name && !asset2.name.startsWith("sites/") ? 1 : 0;
41
- const plainCleanName = asset2.cleanName && !asset2.cleanName.startsWith("sites/") ? 1 : 0;
42
- return (proofId ? 32 : 0) + (productId ? 16 : 0) + (asset2.thumbnail ? 8 : 0) + (asset2.mimeType ? 4 : 0) + (asset2.size ? 2 : 0) + plainName + plainCleanName;
43
- }
44
- function mergeAssets(a, b) {
45
- const keepB = scoreAsset(b) > scoreAsset(a);
46
- const primary = keepB ? b : a;
47
- const secondary = keepB ? a : b;
48
- return {
49
- ...secondary,
50
- ...primary,
51
- metadata: {
52
- ...secondary.metadata || {},
53
- ...primary.metadata || {}
54
- },
55
- labels: Array.from(/* @__PURE__ */ new Set([...secondary.labels || [], ...primary.labels || []])),
56
- thumbnail: primary.thumbnail ?? secondary.thumbnail ?? null,
57
- app: primary.app ?? secondary.app ?? null,
58
- thumbnails: primary.thumbnails ?? secondary.thumbnails
59
- };
60
- }
61
- function dedupeAssetList(items) {
62
- const deduped = [];
63
- const keyToIndex = /* @__PURE__ */ new Map();
64
- for (const asset2 of items) {
65
- const keys = getAssetIdentityKeys(asset2);
66
- const existingIndex = keys.map((key) => keyToIndex.get(key)).find((index) => index !== void 0);
67
- if (existingIndex === void 0) {
68
- const nextIndex = deduped.push(asset2) - 1;
69
- keys.forEach((key) => keyToIndex.set(key, nextIndex));
70
- continue;
71
- }
72
- const merged = mergeAssets(deduped[existingIndex], asset2);
73
- deduped[existingIndex] = merged;
74
- getAssetIdentityKeys(merged).forEach((key) => keyToIndex.set(key, existingIndex));
75
- }
76
- return deduped;
77
- }
78
18
  function useAssets({ scope, accept, pageSize, appId, listAppId }) {
79
19
  const [assets, setAssets] = useState([]);
80
20
  const [loading, setLoading] = useState(true);
@@ -102,19 +42,7 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
102
42
  ...listAppId ? { appId: listAppId } : {}
103
43
  });
104
44
  if (mountedRef.current) {
105
- const raw = result;
106
- const deduped = dedupeAssetList(raw);
107
- const idCounts = /* @__PURE__ */ new Map();
108
- for (const a of raw) idCounts.set(a.id, (idCounts.get(a.id) || 0) + 1);
109
- const dupIds = Array.from(idCounts.entries()).filter(([, n]) => n > 1);
110
- console.debug("[AssetPicker] list", {
111
- scope,
112
- listAppId,
113
- rawCount: raw.length,
114
- dedupedCount: deduped.length,
115
- duplicateIds: dupIds
116
- });
117
- setAssets(deduped);
45
+ setAssets(result);
118
46
  }
119
47
  } catch (err) {
120
48
  if (mountedRef.current) {
@@ -144,7 +72,7 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
144
72
  }
145
73
  });
146
74
  if (mountedRef.current && !scopeOverride) {
147
- setAssets((prev) => dedupeAssetList([result, ...prev]));
75
+ setAssets((prev) => [result, ...prev]);
148
76
  }
149
77
  return result;
150
78
  } catch (err) {
@@ -189,7 +117,7 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
189
117
  admin: true
190
118
  });
191
119
  if (mountedRef.current && !opts?.scopeOverride) {
192
- setAssets((prev) => dedupeAssetList([result, ...prev]));
120
+ setAssets((prev) => [result, ...prev]);
193
121
  }
194
122
  return result;
195
123
  } catch (err) {
@@ -228,10 +156,10 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
228
156
  try {
229
157
  const result = await SL.asset.restoreAdmin(collectionId, assetId);
230
158
  if (mountedRef.current) {
231
- setAssets((prev) => dedupeAssetList((() => {
159
+ setAssets((prev) => {
232
160
  const exists = prev.some((a) => a.id === assetId);
233
161
  return exists ? prev.map((a) => a.id === assetId ? result : a) : [result, ...prev];
234
- })()));
162
+ });
235
163
  }
236
164
  return result;
237
165
  } catch (err) {
@@ -258,7 +186,7 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
258
186
  }
259
187
  });
260
188
  if (mountedRef.current) {
261
- setAssets((prev) => dedupeAssetList(prev.map((a) => a.id === assetId ? result : a)));
189
+ setAssets((prev) => prev.map((a) => a.id === assetId ? result : a));
262
190
  }
263
191
  return result;
264
192
  } catch (err) {
@@ -280,7 +208,7 @@ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
280
208
  try {
281
209
  const result = await SL.asset.updateAdmin({ collectionId, assetId, ...patch });
282
210
  if (mountedRef.current) {
283
- setAssets((prev) => dedupeAssetList(prev.map((a) => a.id === assetId ? result : a)));
211
+ setAssets((prev) => prev.map((a) => a.id === assetId ? result : a));
284
212
  }
285
213
  return result;
286
214
  } catch (err) {
@@ -480,7 +408,7 @@ var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolut
480
408
  {
481
409
  ref,
482
410
  className: cn(
483
- position === "absolute" ? "absolute top-1.5 right-1.5 z-10" : "relative flex-shrink-0"
411
+ position === "absolute" ? "absolute top-1.5 left-1.5 z-10" : "relative flex-shrink-0"
484
412
  ),
485
413
  onClick: (e) => e.stopPropagation(),
486
414
  onDoubleClick: (e) => e.stopPropagation(),
@@ -496,7 +424,11 @@ var CardMenu = ({ onRename, onReplace, onEditTags, onDelete, position = "absolut
496
424
  },
497
425
  className: cn(
498
426
  "w-6 h-6 rounded-full flex items-center justify-center transition-all",
499
- "bg-background/90 border border-border text-foreground hover:bg-background shadow-sm"
427
+ // Frosted pill so the button stays legible over both light and dark
428
+ // thumbnails. Backdrop blur + slight white tint + ring gives a clear
429
+ // outline regardless of the underlying image.
430
+ "bg-background/80 backdrop-blur-sm border border-border text-foreground",
431
+ "hover:bg-background shadow-sm ring-1 ring-black/10 dark:ring-white/20"
500
432
  ),
501
433
  title: "Asset actions",
502
434
  "aria-label": "Asset actions",
@@ -665,7 +597,7 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
665
597
  ] })
666
598
  ] })
667
599
  ] }),
668
- selected && /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-2 w-5 h-5 rounded-full bg-primary flex items-center justify-center z-10", children: /* @__PURE__ */ jsx(Check, { className: "w-3 h-3 text-primary-foreground" }) }),
600
+ selected && /* @__PURE__ */ jsx("div", { className: "absolute top-2 right-2 w-5 h-5 rounded-full bg-primary flex items-center justify-center z-10 shadow-sm ring-1 ring-black/10 dark:ring-white/20", children: /* @__PURE__ */ jsx(Check, { className: "w-3 h-3 text-primary-foreground" }) }),
669
601
  /* @__PURE__ */ jsx(
670
602
  CardMenu,
671
603
  {
@@ -2130,6 +2062,78 @@ var TagEditor = ({ initial, suggestions, assetName, onCancel, onSave }) => {
2130
2062
  }
2131
2063
  );
2132
2064
  };
2065
+ var InlineConfirm = ({ open, title, body, confirmLabel = "Confirm", cancelLabel = "Cancel", destructive, onConfirm, onCancel }) => {
2066
+ React7.useEffect(() => {
2067
+ if (!open) return;
2068
+ const onKey = (e) => {
2069
+ if (e.key === "Escape") {
2070
+ e.stopPropagation();
2071
+ onCancel();
2072
+ }
2073
+ };
2074
+ window.addEventListener("keydown", onKey, true);
2075
+ return () => window.removeEventListener("keydown", onKey, true);
2076
+ }, [open, onCancel]);
2077
+ if (!open || typeof document === "undefined") return null;
2078
+ return createPortal(
2079
+ /* @__PURE__ */ jsxs(
2080
+ "div",
2081
+ {
2082
+ className: "fixed inset-0 z-[2147483646] flex items-center justify-center p-4",
2083
+ onMouseDown: (e) => e.stopPropagation(),
2084
+ onClick: (e) => e.stopPropagation(),
2085
+ children: [
2086
+ /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/50", onClick: onCancel }),
2087
+ /* @__PURE__ */ jsxs(
2088
+ "div",
2089
+ {
2090
+ role: "alertdialog",
2091
+ "aria-modal": "true",
2092
+ className: "relative w-full max-w-sm rounded-lg border border-border bg-popover text-popover-foreground shadow-xl p-4",
2093
+ children: [
2094
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
2095
+ /* @__PURE__ */ jsx("span", { className: cn(
2096
+ "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
2097
+ destructive ? "bg-destructive/10 text-destructive" : "bg-muted text-muted-foreground"
2098
+ ), children: /* @__PURE__ */ jsx(AlertTriangle, { className: "w-4 h-4" }) }),
2099
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2100
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: title }),
2101
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: body })
2102
+ ] })
2103
+ ] }),
2104
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
2105
+ /* @__PURE__ */ jsx(
2106
+ "button",
2107
+ {
2108
+ type: "button",
2109
+ onClick: onCancel,
2110
+ className: "px-3 py-1.5 text-xs rounded-md border border-border bg-background hover:bg-accent",
2111
+ children: cancelLabel
2112
+ }
2113
+ ),
2114
+ /* @__PURE__ */ jsx(
2115
+ "button",
2116
+ {
2117
+ type: "button",
2118
+ autoFocus: true,
2119
+ onClick: onConfirm,
2120
+ className: cn(
2121
+ "px-3 py-1.5 text-xs rounded-md",
2122
+ destructive ? "bg-destructive text-destructive-foreground hover:bg-destructive/90" : "bg-primary text-primary-foreground hover:bg-primary/90"
2123
+ ),
2124
+ children: confirmLabel
2125
+ }
2126
+ )
2127
+ ] })
2128
+ ]
2129
+ }
2130
+ )
2131
+ ]
2132
+ }
2133
+ ),
2134
+ document.body
2135
+ );
2136
+ };
2133
2137
  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: [
2134
2138
  /* @__PURE__ */ jsx(
2135
2139
  "input",
@@ -2163,7 +2167,7 @@ var AttachToContextToggle = ({ checked, onChange, contextLabel }) => /* @__PURE_
2163
2167
  /* @__PURE__ */ jsx("span", { className: "block", children: checked ? `Asset will be tagged to ${contextLabel}.` : `Asset will be added to the collection (available everywhere). Tick to attach it to ${contextLabel} instead.` })
2164
2168
  ] })
2165
2169
  ] });
2166
- var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId: _listAppId, requireProductId, currentAppId, currentAppName, getAppName, assets, loading, error, refresh, remove, updateAsset, replaceFile }) => {
2170
+ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId: _listAppId, currentAppId, currentAppName, getAppName, assets, loading, error, refresh, remove, updateAsset, replaceFile }) => {
2167
2171
  const replaceInputRef = React7.useRef(null);
2168
2172
  const replaceTargetRef = React7.useRef(null);
2169
2173
  const handleRename = useCallback(async (asset2) => {
@@ -2186,11 +2190,17 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
2186
2190
  if (!file || !assetId) return;
2187
2191
  await replaceFile(assetId, file);
2188
2192
  }, [replaceFile]);
2189
- const handleDeleteWithConfirm = useCallback(async (assetId) => {
2190
- if (!window.confirm("Delete this asset? It can be restored from the asset manager within 30 days.")) return;
2191
- const ok = await remove(assetId);
2192
- if (ok) onDelete?.(assetId);
2193
- }, [remove, onDelete]);
2193
+ const [pendingDeleteId, setPendingDeleteId] = useState(null);
2194
+ const handleDeleteWithConfirm = useCallback((assetId) => {
2195
+ setPendingDeleteId(assetId);
2196
+ }, []);
2197
+ const confirmDelete = useCallback(async () => {
2198
+ const id = pendingDeleteId;
2199
+ setPendingDeleteId(null);
2200
+ if (!id) return;
2201
+ const ok = await remove(id);
2202
+ if (ok) onDelete?.(id);
2203
+ }, [pendingDeleteId, remove, onDelete]);
2194
2204
  const [tagEditorAsset, setTagEditorAsset] = useState(null);
2195
2205
  const handleEditTags = useCallback((asset2) => {
2196
2206
  setTagEditorAsset(asset2);
@@ -2222,10 +2232,6 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
2222
2232
  const filteredAssets = useMemo(() => {
2223
2233
  const q = search.trim().toLowerCase();
2224
2234
  return assets.filter((a) => {
2225
- if (requireProductId) {
2226
- const pid = a.productId || a.metadata?.productId || (a.scope?.type === "product" ? a.scope?.productId : void 0);
2227
- if (pid !== requireProductId) return false;
2228
- }
2229
2235
  if (q) {
2230
2236
  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));
2231
2237
  if (!hit) return false;
@@ -2238,7 +2244,7 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
2238
2244
  }
2239
2245
  return true;
2240
2246
  });
2241
- }, [assets, search, activeLabels, requireProductId]);
2247
+ }, [assets, search, activeLabels]);
2242
2248
  if (loading && assets.length === 0) {
2243
2249
  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" }) });
2244
2250
  }
@@ -2328,6 +2334,19 @@ var ScopedAssetBrowser = ({ scope: _scope, accept: _accept, pageSize: _pageSize,
2328
2334
  onCancel: () => setTagEditorAsset(null),
2329
2335
  onSave: handleSaveTags
2330
2336
  }
2337
+ ),
2338
+ /* @__PURE__ */ jsx(
2339
+ InlineConfirm,
2340
+ {
2341
+ open: pendingDeleteId !== null,
2342
+ title: "Delete asset?",
2343
+ body: "This asset can be restored from the asset manager within 30 days.",
2344
+ confirmLabel: "Delete",
2345
+ cancelLabel: "Cancel",
2346
+ destructive: true,
2347
+ onConfirm: confirmDelete,
2348
+ onCancel: () => setPendingDeleteId(null)
2349
+ }
2331
2350
  )
2332
2351
  ] });
2333
2352
  };
@@ -2346,7 +2365,7 @@ var AssetPickerContent = ({
2346
2365
  showTypeFilter,
2347
2366
  value,
2348
2367
  onSelect,
2349
- allowDelete = false,
2368
+ allowDelete = true,
2350
2369
  defaultView = "grid",
2351
2370
  emptyText,
2352
2371
  pageSize = 50,
@@ -2696,7 +2715,6 @@ var AssetPickerContent = ({
2696
2715
  allowDelete,
2697
2716
  emptyText,
2698
2717
  listAppId,
2699
- requireProductId: hasProductScope && scopeTab === "product" ? productScope.productId : void 0,
2700
2718
  currentAppId: appId,
2701
2719
  currentAppName: resolvedAppName,
2702
2720
  getAppName,
@@ -2910,5 +2928,5 @@ var AssetPicker = (props) => {
2910
2928
  assertStylesLoaded();
2911
2929
 
2912
2930
  export { ASSET_MIME_FILTERS, AssetPicker, useAppRegistry, useAssets };
2913
- //# sourceMappingURL=chunk-VP4LZEEZ.js.map
2914
- //# sourceMappingURL=chunk-VP4LZEEZ.js.map
2931
+ //# sourceMappingURL=chunk-N2FPPTHH.js.map
2932
+ //# sourceMappingURL=chunk-N2FPPTHH.js.map