@proveanything/smartlinks-utils-ui 0.2.13 → 0.3.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.
@@ -1,7 +1,7 @@
1
1
  import { cn } from './chunk-L7FQ52F5.js';
2
2
  import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
3
3
  import * as SL from '@proveanything/smartlinks';
4
- import { Filter, Search, LayoutGrid, List, X, Loader2, AlertCircle, ImageOff, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image, Trash2, FileIcon, Film, Music, FileText } from 'lucide-react';
4
+ import { Filter, Search, LayoutGrid, List, X, Loader2, AlertCircle, ImageOff, Clipboard, Pencil, Check, Upload, Link, MicOff, Mic, ChevronDown, ChevronRight, Sparkles, Image, Trash2, FileIcon, Film, Music, FileText, AppWindow } from 'lucide-react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
 
7
7
  // src/components/AssetPicker/types.ts
@@ -13,7 +13,7 @@ var ASSET_MIME_FILTERS = [
13
13
  { value: "document", label: "Documents", prefix: "application/" },
14
14
  { value: "pdf", label: "PDFs", prefix: "application/pdf" }
15
15
  ];
16
- function useAssets({ scope, accept, pageSize }) {
16
+ function useAssets({ scope, accept, pageSize, appId, listAppId }) {
17
17
  const [assets, setAssets] = useState([]);
18
18
  const [loading, setLoading] = useState(true);
19
19
  const [error, setError] = useState(null);
@@ -33,7 +33,11 @@ function useAssets({ scope, accept, pageSize }) {
33
33
  const result = await SL.asset.list({
34
34
  scope,
35
35
  mimeTypePrefix: accept,
36
- limit: pageSize
36
+ limit: pageSize,
37
+ // Only filter by appId when explicitly requested. When listAppId is undefined,
38
+ // we list every asset in the scope — even if uploads are still being stamped
39
+ // with `appId`. This is what powers the "All in collection" pill.
40
+ ...listAppId ? { appId: listAppId } : {}
37
41
  });
38
42
  if (mountedRef.current) {
39
43
  setAssets(result);
@@ -46,7 +50,7 @@ function useAssets({ scope, accept, pageSize }) {
46
50
  } finally {
47
51
  if (mountedRef.current) setLoading(false);
48
52
  }
49
- }, [scope.type, scope.collectionId, scope.productId, scope.proofId, accept, pageSize]);
53
+ }, [scope.type, scope.collectionId, scope.productId, scope.proofId, accept, pageSize, listAppId]);
50
54
  useEffect(() => {
51
55
  fetchAssets();
52
56
  }, [fetchAssets]);
@@ -59,6 +63,7 @@ function useAssets({ scope, accept, pageSize }) {
59
63
  scope,
60
64
  name: file.name,
61
65
  admin: true,
66
+ ...appId ? { appId } : {},
62
67
  onProgress: (pct) => {
63
68
  setUploadProgress(pct);
64
69
  onProgress?.(pct);
@@ -77,7 +82,7 @@ function useAssets({ scope, accept, pageSize }) {
77
82
  setUploadProgress(0);
78
83
  }
79
84
  }
80
- }, [scope]);
85
+ }, [scope, appId]);
81
86
  const uploadFromUrl = useCallback(async (url, name) => {
82
87
  setUploading(true);
83
88
  setUploadProgress(0);
@@ -105,7 +110,9 @@ function useAssets({ scope, accept, pageSize }) {
105
110
  const result = await SL.asset.uploadFromUrl({
106
111
  url,
107
112
  scope,
108
- metadata: { name: opts?.name, ...opts?.metadata || {} }
113
+ metadata: { name: opts?.name, ...opts?.metadata || {} },
114
+ ...appId ? { appId } : {},
115
+ admin: true
109
116
  });
110
117
  if (mountedRef.current) {
111
118
  setAssets((prev) => [result, ...prev]);
@@ -120,7 +127,7 @@ function useAssets({ scope, accept, pageSize }) {
120
127
  setUploadProgress(0);
121
128
  }
122
129
  }
123
- }, [scope]);
130
+ }, [scope, appId]);
124
131
  const remove = useCallback(async (assetId) => {
125
132
  try {
126
133
  await SL.asset.remove({ assetId, scope });
@@ -146,6 +153,82 @@ function useAssets({ scope, accept, pageSize }) {
146
153
  uploadProgress
147
154
  };
148
155
  }
156
+ var cache = /* @__PURE__ */ new Map();
157
+ async function loadApps(collectionId) {
158
+ const existing = cache.get(collectionId);
159
+ if (existing?.data) return existing.data;
160
+ if (existing?.promise) return existing.promise;
161
+ const promise = (async () => {
162
+ try {
163
+ const result = await SL.collection.getAppsConfig(collectionId);
164
+ const apps = Array.isArray(result?.apps) ? result.apps : Array.isArray(result) ? result : [];
165
+ const mapped = apps.map((a) => ({
166
+ id: a.id,
167
+ name: a.name || a.id,
168
+ faIcon: a.faIcon,
169
+ hidden: a.hidden,
170
+ category: a.category
171
+ }));
172
+ cache.set(collectionId, { data: mapped });
173
+ return mapped;
174
+ } catch (err) {
175
+ cache.set(collectionId, { error: err?.message || "Failed to load apps", data: [] });
176
+ return [];
177
+ }
178
+ })();
179
+ cache.set(collectionId, { promise });
180
+ return promise;
181
+ }
182
+ function useAppRegistry(collectionId) {
183
+ const [apps, setApps] = useState(
184
+ () => collectionId ? cache.get(collectionId)?.data ?? [] : []
185
+ );
186
+ const [loading, setLoading] = useState(() => {
187
+ if (!collectionId) return false;
188
+ return !cache.get(collectionId)?.data;
189
+ });
190
+ useEffect(() => {
191
+ let cancelled = false;
192
+ if (!collectionId) {
193
+ setApps([]);
194
+ setLoading(false);
195
+ return () => {
196
+ cancelled = true;
197
+ };
198
+ }
199
+ const cached = cache.get(collectionId)?.data;
200
+ if (cached) {
201
+ setApps(cached);
202
+ setLoading(false);
203
+ return () => {
204
+ cancelled = true;
205
+ };
206
+ }
207
+ setLoading(true);
208
+ loadApps(collectionId).then((data) => {
209
+ if (cancelled) return;
210
+ setApps(data);
211
+ setLoading(false);
212
+ });
213
+ return () => {
214
+ cancelled = true;
215
+ };
216
+ }, [collectionId]);
217
+ const lookup = useMemo(() => {
218
+ const m = /* @__PURE__ */ new Map();
219
+ for (const a of apps) m.set(a.id, a);
220
+ return m;
221
+ }, [apps]);
222
+ const getApp = useCallback(
223
+ (appId) => appId ? lookup.get(appId) : void 0,
224
+ [lookup]
225
+ );
226
+ const getAppName = useCallback(
227
+ (appId) => appId ? lookup.get(appId)?.name ?? appId : "",
228
+ [lookup]
229
+ );
230
+ return { apps, loading, getAppName, getApp };
231
+ }
149
232
  function getIcon(mimeType) {
150
233
  if (!mimeType) return FileIcon;
151
234
  if (mimeType.startsWith("image/")) return Image;
@@ -166,9 +249,32 @@ function getThumbnail(asset2) {
166
249
  if (asset2.mimeType?.startsWith("image/")) return asset2.url;
167
250
  return null;
168
251
  }
169
- var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, allowDelete }) => {
252
+ function getAssetAppId(asset2) {
253
+ return asset2.appId || asset2.metadata?.appId || asset2.metadata?.app || void 0;
254
+ }
255
+ var AppBadge = ({ appId, appName, size = "sm" }) => {
256
+ const label = appName && appName !== appId ? appName : appId;
257
+ const tooltip = appName && appName !== appId ? `Uploaded by app: ${appName} (${appId})` : `Uploaded by app: ${appId}`;
258
+ return /* @__PURE__ */ jsxs(
259
+ "span",
260
+ {
261
+ className: cn(
262
+ "inline-flex items-center gap-0.5 rounded-full bg-muted text-muted-foreground border border-border max-w-full truncate",
263
+ size === "xs" ? "px-1 py-0 text-[9px]" : "px-1.5 py-0.5 text-[10px]"
264
+ ),
265
+ title: tooltip,
266
+ children: [
267
+ /* @__PURE__ */ jsx(AppWindow, { className: cn(size === "xs" ? "w-2 h-2" : "w-2.5 h-2.5", "flex-shrink-0") }),
268
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
269
+ ]
270
+ }
271
+ );
272
+ };
273
+ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, allowDelete, currentAppId, getAppName }) => {
170
274
  const thumb = getThumbnail(asset2);
171
275
  const Icon = getIcon(asset2.mimeType);
276
+ const ownerAppId = getAssetAppId(asset2);
277
+ const showAppBadge = !!currentAppId && !!ownerAppId && ownerAppId !== currentAppId;
172
278
  return /* @__PURE__ */ jsxs(
173
279
  "div",
174
280
  {
@@ -205,7 +311,8 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
205
311
  /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground mt-0.5", children: [
206
312
  formatSize(asset2.size),
207
313
  asset2.mimeType && ` \u2022 ${asset2.mimeType.split("/")[1]?.toUpperCase() || asset2.mimeType}`
208
- ] })
314
+ ] }),
315
+ showAppBadge && /* @__PURE__ */ jsx("div", { className: "mt-1", children: /* @__PURE__ */ jsx(AppBadge, { appId: ownerAppId, appName: getAppName?.(ownerAppId), size: "xs" }) })
209
316
  ] }),
210
317
  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" }) }),
211
318
  allowDelete && onDelete && /* @__PURE__ */ jsx(
@@ -225,9 +332,11 @@ var AssetGridItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
225
332
  }
226
333
  );
227
334
  };
228
- var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, allowDelete }) => {
335
+ var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelete, allowDelete, currentAppId, getAppName }) => {
229
336
  const thumb = getThumbnail(asset2);
230
337
  const Icon = getIcon(asset2.mimeType);
338
+ const ownerAppId = getAssetAppId(asset2);
339
+ const showAppBadge = !!currentAppId && !!ownerAppId && ownerAppId !== currentAppId;
231
340
  return /* @__PURE__ */ jsxs(
232
341
  "div",
233
342
  {
@@ -252,7 +361,10 @@ var AssetListItem = ({ asset: asset2, selected, onToggle, onDoubleClick, onDelet
252
361
  children: [
253
362
  /* @__PURE__ */ jsx("div", { className: "w-10 h-10 rounded bg-muted flex items-center justify-center overflow-hidden flex-shrink-0", children: thumb ? /* @__PURE__ */ jsx("img", { src: thumb, alt: asset2.name, className: "w-full h-full object-cover", loading: "lazy" }) : /* @__PURE__ */ jsx(Icon, { className: "w-4 h-4 text-muted-foreground" }) }),
254
363
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
255
- /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate text-foreground", children: asset2.cleanName || asset2.name || asset2.id }),
364
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 min-w-0", children: [
365
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate text-foreground", children: asset2.cleanName || asset2.name || asset2.id }),
366
+ showAppBadge && /* @__PURE__ */ jsx(AppBadge, { appId: ownerAppId, appName: getAppName?.(ownerAppId) })
367
+ ] }),
256
368
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
257
369
  formatSize(asset2.size),
258
370
  asset2.mimeType && ` \u2022 ${asset2.mimeType}`
@@ -283,7 +395,9 @@ var AssetGrid = ({
283
395
  onToggleSelect,
284
396
  onDoubleClickSelect,
285
397
  onDelete,
286
- allowDelete
398
+ allowDelete,
399
+ currentAppId,
400
+ getAppName
287
401
  }) => {
288
402
  if (assets.length === 0) return null;
289
403
  if (viewMode === "list") {
@@ -295,7 +409,9 @@ var AssetGrid = ({
295
409
  onToggle: () => onToggleSelect(asset2),
296
410
  onDoubleClick: onDoubleClickSelect ? () => onDoubleClickSelect(asset2) : void 0,
297
411
  onDelete: allowDelete && onDelete ? () => onDelete(asset2.id) : void 0,
298
- allowDelete
412
+ allowDelete,
413
+ currentAppId,
414
+ getAppName
299
415
  },
300
416
  asset2.id
301
417
  )) });
@@ -308,7 +424,9 @@ var AssetGrid = ({
308
424
  onToggle: () => onToggleSelect(asset2),
309
425
  onDoubleClick: onDoubleClickSelect ? () => onDoubleClickSelect(asset2) : void 0,
310
426
  onDelete: allowDelete && onDelete ? () => onDelete(asset2.id) : void 0,
311
- allowDelete
427
+ allowDelete,
428
+ currentAppId,
429
+ getAppName
312
430
  },
313
431
  asset2.id
314
432
  )) });
@@ -1179,8 +1297,8 @@ var StockPhotoSearch = ({
1179
1297
  }) })
1180
1298
  ] });
1181
1299
  };
1182
- var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText }) => {
1183
- const { assets, loading, error, refresh } = useAssets({ scope, accept, pageSize });
1300
+ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedIds, onToggleSelect, onDoubleClickSelect, onDelete, allowDelete, emptyText, listAppId, currentAppId, currentAppName, getAppName }) => {
1301
+ const { assets, loading, error, refresh } = useAssets({ scope, accept, pageSize, listAppId });
1184
1302
  const filteredAssets = useMemo(() => {
1185
1303
  if (!search.trim()) return assets;
1186
1304
  const q = search.toLowerCase();
@@ -1214,13 +1332,19 @@ var ScopedAssetBrowser = ({ scope, accept, pageSize, viewMode, search, selectedI
1214
1332
  onToggleSelect,
1215
1333
  onDoubleClickSelect,
1216
1334
  onDelete: allowDelete ? onDelete : void 0,
1217
- allowDelete
1335
+ allowDelete,
1336
+ currentAppId,
1337
+ currentAppName,
1338
+ getAppName
1218
1339
  }
1219
1340
  );
1220
1341
  };
1221
1342
  var AssetPickerContent = ({
1222
1343
  scope,
1223
1344
  productScope,
1345
+ appId,
1346
+ appName,
1347
+ defaultAppFilter = "app",
1224
1348
  allowUpload = true,
1225
1349
  allowUrlImport = true,
1226
1350
  allowAIGenerate,
@@ -1236,10 +1360,19 @@ var AssetPickerContent = ({
1236
1360
  pageSize = 50,
1237
1361
  onConfirm
1238
1362
  }) => {
1363
+ const [appFilter, setAppFilter] = useState(defaultAppFilter);
1364
+ const hasAppFilter = !!appId;
1365
+ const [otherAppFilter, setOtherAppFilter] = useState("");
1366
+ const collectionIdForRegistry = scope.collectionId;
1367
+ const { apps: registryApps, getAppName } = useAppRegistry(collectionIdForRegistry);
1368
+ const resolvedAppName = appName || (appId ? getAppName(appId) : void 0);
1369
+ const listAppId = hasAppFilter ? appFilter === "app" ? appId : otherAppFilter || void 0 : void 0;
1239
1370
  const { assets, upload, uploadFromUrl, uploadFromRemoteUrl, uploading, uploadProgress } = useAssets({
1240
1371
  scope,
1241
1372
  accept: acceptProp,
1242
- pageSize
1373
+ pageSize,
1374
+ appId,
1375
+ listAppId
1243
1376
  });
1244
1377
  const [tab, setTab] = useState("browse");
1245
1378
  const [viewMode, setViewMode] = useState(defaultView);
@@ -1444,6 +1577,58 @@ var AssetPickerContent = ({
1444
1577
  }
1445
1578
  )
1446
1579
  ] }),
1580
+ tab === "browse" && hasAppFilter && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
1581
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 bg-muted rounded-md p-0.5", children: [
1582
+ /* @__PURE__ */ jsx(
1583
+ "button",
1584
+ {
1585
+ type: "button",
1586
+ onClick: () => {
1587
+ setAppFilter("app");
1588
+ setOtherAppFilter("");
1589
+ },
1590
+ className: cn(
1591
+ "px-2.5 py-1 text-xs font-medium rounded transition-colors",
1592
+ appFilter === "app" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
1593
+ ),
1594
+ title: `Show only assets uploaded by ${resolvedAppName || appId}`,
1595
+ children: resolvedAppName || appId
1596
+ }
1597
+ ),
1598
+ /* @__PURE__ */ jsx(
1599
+ "button",
1600
+ {
1601
+ type: "button",
1602
+ onClick: () => setAppFilter("all"),
1603
+ className: cn(
1604
+ "px-2.5 py-1 text-xs font-medium rounded transition-colors",
1605
+ appFilter === "all" ? "bg-background text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"
1606
+ ),
1607
+ title: "Show every asset in the collection, regardless of which app uploaded it",
1608
+ children: "All in collection"
1609
+ }
1610
+ )
1611
+ ] }),
1612
+ appFilter === "all" && registryApps.length > 1 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1613
+ /* @__PURE__ */ jsx(Filter, { className: "w-3.5 h-3.5 text-muted-foreground" }),
1614
+ /* @__PURE__ */ jsxs(
1615
+ "select",
1616
+ {
1617
+ value: otherAppFilter,
1618
+ onChange: (e) => setOtherAppFilter(e.target.value),
1619
+ className: "text-xs py-1 pl-1 pr-5 rounded-md border border-border bg-transparent text-foreground focus:outline-none focus:ring-1 focus:ring-ring appearance-none cursor-pointer",
1620
+ title: "Narrow to assets uploaded by a specific app",
1621
+ children: [
1622
+ /* @__PURE__ */ jsx("option", { value: "", children: "Any app" }),
1623
+ registryApps.filter((a) => !a.hidden).map((a) => /* @__PURE__ */ jsxs("option", { value: a.id, children: [
1624
+ a.name,
1625
+ a.id !== appId ? "" : " (this app)"
1626
+ ] }, a.id))
1627
+ ]
1628
+ }
1629
+ )
1630
+ ] })
1631
+ ] }),
1447
1632
  tab === "browse" && /* @__PURE__ */ jsx(
1448
1633
  ScopedAssetBrowser,
1449
1634
  {
@@ -1457,9 +1642,13 @@ var AssetPickerContent = ({
1457
1642
  onDoubleClickSelect: handleDoubleClickSelect,
1458
1643
  onDelete: handleDelete,
1459
1644
  allowDelete,
1460
- emptyText
1645
+ emptyText,
1646
+ listAppId,
1647
+ currentAppId: appId,
1648
+ currentAppName: resolvedAppName,
1649
+ getAppName
1461
1650
  },
1462
- `${activeScope.type}-${activeScope.productId || ""}-${effectiveAccept || "all"}`
1651
+ `${activeScope.type}-${activeScope.productId || ""}-${effectiveAccept || "all"}-${listAppId || "no-app-filter"}`
1463
1652
  ),
1464
1653
  tab === "upload" && /* @__PURE__ */ jsx(
1465
1654
  UploadZone,
@@ -1582,6 +1771,6 @@ var AssetPicker = (props) => {
1582
1771
  ] });
1583
1772
  };
1584
1773
 
1585
- export { ASSET_MIME_FILTERS, AssetPicker, useAssets };
1586
- //# sourceMappingURL=chunk-XA5J6CZL.js.map
1587
- //# sourceMappingURL=chunk-XA5J6CZL.js.map
1774
+ export { ASSET_MIME_FILTERS, AssetPicker, useAppRegistry, useAssets };
1775
+ //# sourceMappingURL=chunk-YVZKCCYQ.js.map
1776
+ //# sourceMappingURL=chunk-YVZKCCYQ.js.map