@happyvertical/smrt-assets 0.30.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.
Files changed (119) hide show
  1. package/AGENTS.md +78 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +136 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/asset-association.d.ts +16 -0
  8. package/dist/asset-association.d.ts.map +1 -0
  9. package/dist/asset-associations.d.ts +27 -0
  10. package/dist/asset-associations.d.ts.map +1 -0
  11. package/dist/asset-capabilities.d.ts +137 -0
  12. package/dist/asset-capabilities.d.ts.map +1 -0
  13. package/dist/asset-conventions.d.ts +76 -0
  14. package/dist/asset-conventions.d.ts.map +1 -0
  15. package/dist/asset-metafield.d.ts +27 -0
  16. package/dist/asset-metafield.d.ts.map +1 -0
  17. package/dist/asset-metafields.d.ts +27 -0
  18. package/dist/asset-metafields.d.ts.map +1 -0
  19. package/dist/asset-runtime.d.ts +218 -0
  20. package/dist/asset-runtime.d.ts.map +1 -0
  21. package/dist/asset-serving.d.ts +146 -0
  22. package/dist/asset-serving.d.ts.map +1 -0
  23. package/dist/asset-status.d.ts +15 -0
  24. package/dist/asset-status.d.ts.map +1 -0
  25. package/dist/asset-statuses.d.ts +25 -0
  26. package/dist/asset-statuses.d.ts.map +1 -0
  27. package/dist/asset-store.d.ts +200 -0
  28. package/dist/asset-store.d.ts.map +1 -0
  29. package/dist/asset-type.d.ts +15 -0
  30. package/dist/asset-type.d.ts.map +1 -0
  31. package/dist/asset-types.d.ts +28 -0
  32. package/dist/asset-types.d.ts.map +1 -0
  33. package/dist/asset.d.ts +158 -0
  34. package/dist/asset.d.ts.map +1 -0
  35. package/dist/assets.d.ts +125 -0
  36. package/dist/assets.d.ts.map +1 -0
  37. package/dist/folder.d.ts +16 -0
  38. package/dist/folder.d.ts.map +1 -0
  39. package/dist/folders.d.ts +45 -0
  40. package/dist/folders.d.ts.map +1 -0
  41. package/dist/index.d.ts +21 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +2285 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/manifest.json +4079 -0
  46. package/dist/media-bundle-persistence.d.ts +99 -0
  47. package/dist/media-bundle-persistence.d.ts.map +1 -0
  48. package/dist/owned-asset-helpers.d.ts +20 -0
  49. package/dist/owned-asset-helpers.d.ts.map +1 -0
  50. package/dist/playground.d.ts +2 -0
  51. package/dist/playground.d.ts.map +1 -0
  52. package/dist/playground.js +127 -0
  53. package/dist/playground.js.map +1 -0
  54. package/dist/smrt-knowledge.json +1922 -0
  55. package/dist/svelte/ActionBar.svelte +203 -0
  56. package/dist/svelte/ActionBar.svelte.d.ts +5 -0
  57. package/dist/svelte/ActionBar.svelte.d.ts.map +1 -0
  58. package/dist/svelte/AssetDetail.svelte +521 -0
  59. package/dist/svelte/AssetDetail.svelte.d.ts +35 -0
  60. package/dist/svelte/AssetDetail.svelte.d.ts.map +1 -0
  61. package/dist/svelte/AssetGrid.svelte +351 -0
  62. package/dist/svelte/AssetGrid.svelte.d.ts +5 -0
  63. package/dist/svelte/AssetGrid.svelte.d.ts.map +1 -0
  64. package/dist/svelte/AssetList.svelte +436 -0
  65. package/dist/svelte/AssetList.svelte.d.ts +5 -0
  66. package/dist/svelte/AssetList.svelte.d.ts.map +1 -0
  67. package/dist/svelte/AssetManager.svelte +381 -0
  68. package/dist/svelte/AssetManager.svelte.d.ts +5 -0
  69. package/dist/svelte/AssetManager.svelte.d.ts.map +1 -0
  70. package/dist/svelte/AssetToolbar.svelte +388 -0
  71. package/dist/svelte/AssetToolbar.svelte.d.ts +5 -0
  72. package/dist/svelte/AssetToolbar.svelte.d.ts.map +1 -0
  73. package/dist/svelte/CreateAssetModal.svelte +373 -0
  74. package/dist/svelte/CreateAssetModal.svelte.d.ts +19 -0
  75. package/dist/svelte/CreateAssetModal.svelte.d.ts.map +1 -0
  76. package/dist/svelte/__tests__/ActionBar.test.js +72 -0
  77. package/dist/svelte/__tests__/AssetDetail.test.js +57 -0
  78. package/dist/svelte/__tests__/AssetGrid.test.js +69 -0
  79. package/dist/svelte/__tests__/AssetList.test.js +72 -0
  80. package/dist/svelte/__tests__/AssetManager.test.js +21 -0
  81. package/dist/svelte/__tests__/AssetManagerRoute.test.js +16 -0
  82. package/dist/svelte/__tests__/AssetToolbar.test.js +39 -0
  83. package/dist/svelte/__tests__/CreateAssetModal.test.js +42 -0
  84. package/dist/svelte/i18n.d.ts +76 -0
  85. package/dist/svelte/i18n.d.ts.map +1 -0
  86. package/dist/svelte/i18n.js +87 -0
  87. package/dist/svelte/index.d.ts +19 -0
  88. package/dist/svelte/index.d.ts.map +1 -0
  89. package/dist/svelte/index.js +30 -0
  90. package/dist/svelte/playground/AssetDetailPreview.svelte +131 -0
  91. package/dist/svelte/playground/AssetDetailPreview.svelte.d.ts +8 -0
  92. package/dist/svelte/playground/AssetDetailPreview.svelte.d.ts.map +1 -0
  93. package/dist/svelte/playground/CreateAssetModalPreview.svelte +151 -0
  94. package/dist/svelte/playground/CreateAssetModalPreview.svelte.d.ts +4 -0
  95. package/dist/svelte/playground/CreateAssetModalPreview.svelte.d.ts.map +1 -0
  96. package/dist/svelte/playground.d.ts +60 -0
  97. package/dist/svelte/playground.d.ts.map +1 -0
  98. package/dist/svelte/playground.js +93 -0
  99. package/dist/svelte/routes/AssetManagerRoute.svelte +209 -0
  100. package/dist/svelte/routes/AssetManagerRoute.svelte.d.ts +4 -0
  101. package/dist/svelte/routes/AssetManagerRoute.svelte.d.ts.map +1 -0
  102. package/dist/svelte/routes/index.d.ts +2 -0
  103. package/dist/svelte/routes/index.d.ts.map +1 -0
  104. package/dist/svelte/routes/index.js +1 -0
  105. package/dist/svelte/routes/shared.d.ts +25 -0
  106. package/dist/svelte/routes/shared.d.ts.map +1 -0
  107. package/dist/svelte/routes/shared.js +31 -0
  108. package/dist/svelte/types.d.ts +179 -0
  109. package/dist/svelte/types.d.ts.map +1 -0
  110. package/dist/svelte/types.js +6 -0
  111. package/dist/types.d.ts +80 -0
  112. package/dist/types.d.ts.map +1 -0
  113. package/dist/types.js +2 -0
  114. package/dist/types.js.map +1 -0
  115. package/dist/ui.d.ts +10 -0
  116. package/dist/ui.d.ts.map +1 -0
  117. package/dist/ui.js +85 -0
  118. package/dist/ui.js.map +1 -0
  119. package/package.json +102 -0
@@ -0,0 +1,436 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AssetList - Table/list view for assets
4
+ *
5
+ * Displays assets in a sortable table with selection, thumbnail previews,
6
+ * type badges, and metadata columns.
7
+ */
8
+
9
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
10
+ import { M } from './i18n.js';
11
+ import type {
12
+ AssetListProps,
13
+ AssetSort,
14
+ AssetSortField,
15
+ PersistedAsset,
16
+ } from './types';
17
+
18
+ const { t } = useI18n();
19
+
20
+ interface ListAsset extends PersistedAsset {
21
+ alt?: string;
22
+ }
23
+
24
+ let {
25
+ assets,
26
+ selectedIds,
27
+ sort,
28
+ onSelectionChange,
29
+ onAssetClick,
30
+ onSortChange,
31
+ loading = false,
32
+ }: AssetListProps = $props();
33
+
34
+ function toggleSelection(asset: ListAsset, event: Event) {
35
+ event.stopPropagation();
36
+ const next = new Set(selectedIds);
37
+ if (next.has(asset.id)) {
38
+ next.delete(asset.id);
39
+ } else {
40
+ next.add(asset.id);
41
+ }
42
+ onSelectionChange(next);
43
+ }
44
+
45
+ function toggleSelectAll() {
46
+ if (allSelected) {
47
+ onSelectionChange(new Set());
48
+ } else {
49
+ onSelectionChange(new Set(assets.map((a) => a.id)));
50
+ }
51
+ }
52
+
53
+ function handleSort(field: AssetSortField) {
54
+ if (sort.field === field) {
55
+ onSortChange({
56
+ field,
57
+ direction: sort.direction === 'asc' ? 'desc' : 'asc',
58
+ });
59
+ } else {
60
+ onSortChange({ field, direction: 'asc' });
61
+ }
62
+ }
63
+
64
+ function getSortIndicator(field: AssetSortField): string {
65
+ if (sort.field !== field) return '↕';
66
+ return sort.direction === 'asc' ? '↑' : '↓';
67
+ }
68
+
69
+ function isImage(asset: ListAsset): boolean {
70
+ return asset.mimeType?.startsWith('image/') ?? false;
71
+ }
72
+
73
+ function formatDate(date: Date | string): string {
74
+ const d = date instanceof Date ? date : new Date(date);
75
+ return d.toLocaleDateString(undefined, {
76
+ month: 'short',
77
+ day: 'numeric',
78
+ year: 'numeric',
79
+ });
80
+ }
81
+
82
+ function getTypeBadge(asset: ListAsset): string {
83
+ if (isImage(asset)) return 'IMG';
84
+ if (asset.mimeType?.startsWith('video/')) return 'VID';
85
+ if (asset.mimeType?.startsWith('audio/')) return 'AUD';
86
+ if (asset.mimeType?.includes('pdf')) return 'PDF';
87
+ return 'DOC';
88
+ }
89
+
90
+ const allSelected = $derived(
91
+ assets.length > 0 && assets.every((a) => selectedIds.has(a.id)),
92
+ );
93
+ const someSelected = $derived(
94
+ assets.some((a) => selectedIds.has(a.id)) && !allSelected,
95
+ );
96
+
97
+ // Svelte action to set indeterminate
98
+ function setIndeterminate(node: HTMLInputElement, value: boolean) {
99
+ node.indeterminate = value;
100
+ return {
101
+ update(newValue: boolean) {
102
+ node.indeterminate = newValue;
103
+ },
104
+ };
105
+ }
106
+ </script>
107
+
108
+ <div class="asset-list" class:asset-list--loading={loading}>
109
+ <table class="list-table">
110
+ <thead class="list-table__head">
111
+ <tr>
112
+ <th class="col-checkbox">
113
+ <input
114
+ type="checkbox"
115
+ checked={allSelected}
116
+ use:setIndeterminate={someSelected}
117
+ onchange={toggleSelectAll}
118
+ aria-label={t(M['assets.asset_list.select_all'])}
119
+ />
120
+ </th>
121
+ <th class="col-thumb">Preview</th>
122
+ <th class="col-name">
123
+ <button type="button" class="sort-btn" onclick={() => handleSort('name')}>
124
+ Name <span class="sort-indicator">{getSortIndicator('name')}</span>
125
+ </button>
126
+ </th>
127
+ <th class="col-type">Type</th>
128
+ <th class="col-date">
129
+ <button type="button" class="sort-btn" onclick={() => handleSort('createdAt')}>
130
+ Created <span class="sort-indicator">{getSortIndicator('createdAt')}</span>
131
+ </button>
132
+ </th>
133
+ <th class="col-status">Status</th>
134
+ </tr>
135
+ </thead>
136
+ <tbody class="list-table__body">
137
+ {#if loading}
138
+ <tr>
139
+ <td colspan="6" class="cell-empty">
140
+ <div class="asset-list__loading">
141
+ <span class="spinner"></span>
142
+ <span>{t(M['assets.asset_list.loading'])}</span>
143
+ </div>
144
+ </td>
145
+ </tr>
146
+ {:else if assets.length === 0}
147
+ <tr>
148
+ <td colspan="6" class="cell-empty">
149
+ <div class="empty-state">
150
+ <div class="empty-state__icon">
151
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
152
+ <line x1="8" y1="6" x2="21" y2="6"></line>
153
+ <line x1="8" y1="12" x2="21" y2="12"></line>
154
+ <line x1="8" y1="18" x2="21" y2="18"></line>
155
+ <line x1="3" y1="6" x2="3.01" y2="6"></line>
156
+ <line x1="3" y1="12" x2="3.01" y2="12"></line>
157
+ <line x1="3" y1="18" x2="3.01" y2="18"></line>
158
+ </svg>
159
+ </div>
160
+ <p class="empty-state__title">{t(M['assets.asset_list.no_assets_found'])}</p>
161
+ <p class="empty-state__desc">{t(M['assets.asset_list.empty_hint'])}</p>
162
+ </div>
163
+ </td>
164
+ </tr>
165
+ {:else}
166
+ {#each assets as asset (asset.id)}
167
+ {@const selected = selectedIds.has(asset.id)}
168
+ <tr
169
+ class="list-table__row"
170
+ class:list-table__row--selected={selected}
171
+ >
172
+ <td class="col-checkbox">
173
+ <input
174
+ type="checkbox"
175
+ checked={selected}
176
+ onchange={(e) => toggleSelection(asset, e)}
177
+ onclick={(e) => e.stopPropagation()}
178
+ aria-label={t(M['assets.asset_list.select_named'], { name: asset.name })}
179
+ />
180
+ </td>
181
+ <td class="col-thumb">
182
+ {#if isImage(asset) && asset.sourceUri}
183
+ <img src={asset.sourceUri} alt={(asset as any).alt || asset.name} class="row-thumb" loading="lazy" />
184
+ {:else}
185
+ <span class="row-thumb-placeholder">{getTypeBadge(asset)}</span>
186
+ {/if}
187
+ </td>
188
+ <td class="col-name">
189
+ <!-- The name is the row's open action (a button, not a
190
+ role="button" on the <tr> nesting the checkbox → avoids axe
191
+ nested-interactive). -->
192
+ <button
193
+ type="button"
194
+ class="row-name"
195
+ onclick={() => onAssetClick(asset)}
196
+ >
197
+ {asset.name || 'Untitled'}
198
+ </button>
199
+ {#if asset.description}
200
+ <span class="row-desc">{asset.description}</span>
201
+ {/if}
202
+ </td>
203
+ <td class="col-type">
204
+ <span class="type-badge">{getTypeBadge(asset)}</span>
205
+ </td>
206
+ <td class="col-date">
207
+ {formatDate(asset.createdAt)}
208
+ </td>
209
+ <td class="col-status">
210
+ <span class="status-dot" class:status-dot--active={asset.statusSlug === 'active' || asset.statusSlug === 'published'}></span>
211
+ {asset.statusSlug || 'draft'}
212
+ </td>
213
+ </tr>
214
+ {/each}
215
+ {/if}
216
+ </tbody>
217
+ </table>
218
+ </div>
219
+
220
+ <style>
221
+ .asset-list {
222
+ overflow-x: auto;
223
+ }
224
+
225
+ .asset-list--loading {
226
+ opacity: 0.6;
227
+ pointer-events: none;
228
+ }
229
+
230
+ .list-table {
231
+ width: 100%;
232
+ border-collapse: collapse;
233
+ font-size: var(--smrt-typography-body-medium-size, 0.875rem);
234
+ color: var(--smrt-color-on-surface, #111827);
235
+ }
236
+
237
+ .list-table__head {
238
+ background: var(--smrt-color-surface-container, #f3f4f6);
239
+ }
240
+
241
+ .list-table__head tr {
242
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
243
+ }
244
+
245
+ .list-table__head th {
246
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
247
+ text-align: left;
248
+ font-weight: var(--smrt-typography-weight-semibold, 600);
249
+ white-space: nowrap;
250
+ color: var(--smrt-color-on-surface, #111827);
251
+ }
252
+
253
+ .list-table__row {
254
+ border-bottom: 1px solid var(--smrt-color-outline-variant, #e5e7eb);
255
+ transition: background 150ms ease;
256
+ }
257
+
258
+ .list-table__row:hover {
259
+ background: var(--smrt-color-surface-container-low, #f9fafb);
260
+ }
261
+
262
+ .list-table__row--selected {
263
+ background: var(--smrt-color-primary-container, #dbeafe) !important;
264
+ }
265
+
266
+ .list-table__row td {
267
+ padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-3, 0.75rem);
268
+ vertical-align: middle;
269
+ }
270
+
271
+ .col-checkbox {
272
+ width: 40px;
273
+ text-align: center;
274
+ }
275
+
276
+ .col-checkbox input {
277
+ width: 16px;
278
+ height: 16px;
279
+ cursor: pointer;
280
+ accent-color: var(--smrt-color-primary, #005ac1);
281
+ }
282
+
283
+ .col-thumb {
284
+ width: 48px;
285
+ }
286
+
287
+ .row-thumb {
288
+ width: 40px;
289
+ height: 40px;
290
+ object-fit: cover;
291
+ border-radius: var(--smrt-radius-small, 0.25rem);
292
+ }
293
+
294
+ .row-thumb-placeholder {
295
+ display: flex;
296
+ align-items: center;
297
+ justify-content: center;
298
+ width: 40px;
299
+ height: 40px;
300
+ background: var(--smrt-color-surface-container, #f3f4f6);
301
+ border-radius: var(--smrt-radius-small, 0.25rem);
302
+ font-size: var(--smrt-typography-label-small-size, 0.6rem);
303
+ font-weight: var(--smrt-typography-weight-bold, 700);
304
+ color: var(--smrt-color-on-surface-variant, #6b7280);
305
+ }
306
+
307
+ .col-name {
308
+ min-width: 150px;
309
+ }
310
+
311
+ .row-name {
312
+ display: block;
313
+ font-weight: var(--smrt-typography-weight-medium, 500);
314
+ white-space: nowrap;
315
+ overflow: hidden;
316
+ text-overflow: ellipsis;
317
+ max-width: 250px;
318
+ /* Reset the open-action button so it renders as the row's name text. */
319
+ margin: 0;
320
+ padding: 0;
321
+ border: none;
322
+ background: none;
323
+ font-family: inherit;
324
+ font-size: inherit;
325
+ color: inherit;
326
+ text-align: left;
327
+ cursor: pointer;
328
+ }
329
+
330
+ .row-name:hover {
331
+ text-decoration: underline;
332
+ }
333
+
334
+ .row-name:focus-visible {
335
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
336
+ outline-offset: 2px;
337
+ border-radius: var(--smrt-radius-small, 0.25rem);
338
+ }
339
+
340
+ .row-desc {
341
+ display: block;
342
+ font-size: var(--smrt-typography-body-small-size, 0.75rem);
343
+ color: var(--smrt-color-on-surface-variant, #6b7280);
344
+ white-space: nowrap;
345
+ overflow: hidden;
346
+ text-overflow: ellipsis;
347
+ max-width: 250px;
348
+ }
349
+
350
+ .type-badge {
351
+ display: inline-block;
352
+ padding: var(--smrt-spacing-1, 4px) var(--smrt-spacing-2, 8px);
353
+ font-size: var(--smrt-typography-label-small-size, 0.65rem);
354
+ font-weight: var(--smrt-typography-weight-bold, 700);
355
+ text-transform: uppercase;
356
+ background: var(--smrt-color-surface-container, #f3f4f6);
357
+ color: var(--smrt-color-on-surface-variant, #6b7280);
358
+ border-radius: var(--smrt-radius-small, 0.25rem);
359
+ letter-spacing: var(--smrt-typography-label-small-tracking, 0.05em);
360
+ }
361
+
362
+ .col-date {
363
+ white-space: nowrap;
364
+ color: var(--smrt-color-on-surface-variant, #6b7280);
365
+ }
366
+
367
+ .col-status {
368
+ white-space: nowrap;
369
+ text-transform: capitalize;
370
+ }
371
+
372
+ .status-dot {
373
+ display: inline-block;
374
+ width: 8px;
375
+ height: 8px;
376
+ border-radius: var(--smrt-radius-full, 9999px);
377
+ background: var(--smrt-color-outline, #9ca3af);
378
+ margin-right: var(--smrt-spacing-1, 4px);
379
+ vertical-align: middle;
380
+ }
381
+
382
+ .status-dot--active {
383
+ background: var(--smrt-color-success, #22c55e);
384
+ }
385
+
386
+ /* Sort */
387
+ .sort-btn {
388
+ display: inline-flex;
389
+ align-items: center;
390
+ gap: var(--smrt-spacing-1, 4px);
391
+ padding: 0;
392
+ border: none;
393
+ background: transparent;
394
+ font: inherit;
395
+ font-weight: var(--smrt-typography-weight-semibold, 600);
396
+ color: inherit;
397
+ cursor: pointer;
398
+ }
399
+
400
+ .sort-btn:hover {
401
+ color: var(--smrt-color-primary, #005ac1);
402
+ }
403
+
404
+ .sort-indicator {
405
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
406
+ opacity: 0.5;
407
+ }
408
+
409
+ /* Loading / Empty */
410
+ .asset-list__loading {
411
+ display: flex;
412
+ align-items: center;
413
+ justify-content: center;
414
+ gap: var(--smrt-spacing-2, 0.5rem);
415
+ padding: var(--smrt-spacing-8, 2rem);
416
+ color: var(--smrt-color-on-surface-variant, #6b7280);
417
+ }
418
+
419
+ .spinner {
420
+ width: 20px;
421
+ height: 20px;
422
+ border: 2px solid var(--smrt-color-outline-variant, #e5e7eb);
423
+ border-top-color: var(--smrt-color-primary, #005ac1);
424
+ border-radius: var(--smrt-radius-full, 9999px);
425
+ animation: spin 0.8s linear infinite;
426
+ }
427
+
428
+ @keyframes spin {
429
+ to { transform: rotate(360deg); }
430
+ }
431
+
432
+ .list-table__row:focus-visible {
433
+ outline: 2px solid var(--smrt-color-primary, #005ac1);
434
+ outline-offset: -2px;
435
+ }
436
+ </style>
@@ -0,0 +1,5 @@
1
+ import type { AssetListProps } from './types';
2
+ declare const AssetList: import("svelte").Component<AssetListProps, {}, "">;
3
+ type AssetList = ReturnType<typeof AssetList>;
4
+ export default AssetList;
5
+ //# sourceMappingURL=AssetList.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetList.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/AssetList.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,cAAc,EAIf,MAAM,SAAS,CAAC;AAkMjB,QAAA,MAAM,SAAS,oDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}