nuxt-media-library 0.0.48 → 0.0.49

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 (87) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +60 -0
  3. package/dist/runtime/components/MLMedia/Dropzone.vue +137 -0
  4. package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +60 -0
  5. package/dist/runtime/components/MLMedia/Editor.d.vue.ts +23 -0
  6. package/dist/runtime/components/MLMedia/Editor.vue +34 -0
  7. package/dist/runtime/components/MLMedia/Editor.vue.d.ts +23 -0
  8. package/dist/runtime/components/MLMedia/EditorPanel.d.vue.ts +38 -0
  9. package/dist/runtime/components/MLMedia/EditorPanel.vue +220 -0
  10. package/dist/runtime/components/MLMedia/EditorPanel.vue.d.ts +38 -0
  11. package/dist/runtime/components/MLMedia/Field.d.vue.ts +65 -0
  12. package/dist/runtime/components/MLMedia/Field.vue +282 -0
  13. package/dist/runtime/components/MLMedia/Field.vue.d.ts +65 -0
  14. package/dist/runtime/components/MLMedia/FileFacts.d.vue.ts +19 -0
  15. package/dist/runtime/components/MLMedia/FileFacts.vue +38 -0
  16. package/dist/runtime/components/MLMedia/FileFacts.vue.d.ts +19 -0
  17. package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +23 -0
  18. package/dist/runtime/components/MLMedia/FilePicker.vue +33 -0
  19. package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +23 -0
  20. package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +33 -0
  21. package/dist/runtime/components/MLMedia/FilePickerPanel.vue +300 -0
  22. package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +33 -0
  23. package/dist/runtime/components/MLMedia/FilterPill.d.vue.ts +30 -0
  24. package/dist/runtime/components/MLMedia/FilterPill.vue +84 -0
  25. package/dist/runtime/components/MLMedia/FilterPill.vue.d.ts +30 -0
  26. package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +78 -0
  27. package/dist/runtime/components/MLMedia/Gallery.vue +256 -0
  28. package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +78 -0
  29. package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +102 -0
  30. package/dist/runtime/components/MLMedia/GallerySection.vue +422 -0
  31. package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +102 -0
  32. package/dist/runtime/components/MLMedia/KindBadge.d.vue.ts +17 -0
  33. package/dist/runtime/components/MLMedia/KindBadge.vue +22 -0
  34. package/dist/runtime/components/MLMedia/KindBadge.vue.d.ts +17 -0
  35. package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +63 -0
  36. package/dist/runtime/components/MLMedia/MediaTile.vue +117 -0
  37. package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +63 -0
  38. package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +42 -0
  39. package/dist/runtime/components/MLMedia/PickerCard.vue +79 -0
  40. package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +42 -0
  41. package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +31 -0
  42. package/dist/runtime/components/MLMedia/PickerRow.vue +65 -0
  43. package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +31 -0
  44. package/dist/runtime/components/MLMedia/PreviewEye.d.vue.ts +16 -0
  45. package/dist/runtime/components/MLMedia/PreviewEye.vue +21 -0
  46. package/dist/runtime/components/MLMedia/PreviewEye.vue.d.ts +16 -0
  47. package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +25 -0
  48. package/dist/runtime/components/MLMedia/ProgressVeil.vue +98 -0
  49. package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +25 -0
  50. package/dist/runtime/components/MLMedia/StatusBadge.d.vue.ts +17 -0
  51. package/dist/runtime/components/MLMedia/StatusBadge.vue +31 -0
  52. package/dist/runtime/components/MLMedia/StatusBadge.vue.d.ts +17 -0
  53. package/dist/runtime/components/MLMedia/Thumbnail.d.vue.ts +36 -0
  54. package/dist/runtime/components/MLMedia/Thumbnail.vue +84 -0
  55. package/dist/runtime/components/MLMedia/Thumbnail.vue.d.ts +36 -0
  56. package/dist/runtime/components/MLMedia/UsageList.d.vue.ts +26 -0
  57. package/dist/runtime/components/MLMedia/UsageList.vue +66 -0
  58. package/dist/runtime/components/MLMedia/UsageList.vue.d.ts +26 -0
  59. package/dist/runtime/components/MLProcessing/StepLayout.d.vue.ts +1 -1
  60. package/dist/runtime/components/MLProcessing/StepLayout.vue.d.ts +1 -1
  61. package/dist/runtime/composables/useMLFilePicker.d.ts +20 -0
  62. package/dist/runtime/composables/useMLFilePicker.js +17 -0
  63. package/dist/runtime/composables/useMLMediaAssets.d.ts +19 -0
  64. package/dist/runtime/composables/useMLMediaAssets.js +56 -0
  65. package/dist/runtime/composables/useMLMediaBrowse.d.ts +147 -0
  66. package/dist/runtime/composables/useMLMediaBrowse.js +72 -0
  67. package/dist/runtime/composables/useMLMediaEditor.d.ts +22 -0
  68. package/dist/runtime/composables/useMLMediaEditor.js +20 -0
  69. package/dist/runtime/composables/useMLMediaField.d.ts +29 -0
  70. package/dist/runtime/composables/useMLMediaField.js +77 -0
  71. package/dist/runtime/composables/useMLMediaGallery.d.ts +55 -0
  72. package/dist/runtime/composables/useMLMediaGallery.js +134 -0
  73. package/dist/runtime/composables/useMLMediaUpload.d.ts +134 -0
  74. package/dist/runtime/composables/useMLMediaUpload.js +153 -0
  75. package/dist/runtime/index.d.ts +11 -0
  76. package/dist/runtime/index.js +11 -0
  77. package/dist/runtime/utils/assetDisplay.d.ts +8 -0
  78. package/dist/runtime/utils/assetDisplay.js +29 -0
  79. package/dist/runtime/utils/assetFormat.d.ts +30 -0
  80. package/dist/runtime/utils/assetFormat.js +69 -0
  81. package/dist/runtime/utils/assetThumbnail.d.ts +11 -0
  82. package/dist/runtime/utils/assetThumbnail.js +14 -0
  83. package/dist/runtime/utils/fileAccept.d.ts +26 -0
  84. package/dist/runtime/utils/fileAccept.js +104 -0
  85. package/dist/runtime/utils/galleryDrag.d.ts +2 -0
  86. package/dist/runtime/utils/galleryDrag.js +1 -0
  87. package/package.json +1 -1
@@ -0,0 +1,153 @@
1
+ import { onBeforeUnmount, reactive, ref, watch } from "vue";
2
+ import { $fetch } from "ofetch";
3
+ import { ChunkUploader } from "../utils/uploader/uploader.js";
4
+ import { kindForFile } from "../utils/fileAccept.js";
5
+ import { useMLChunkUploader } from "./useMLChunkUploader.js";
6
+ import { useMLProcessingStatus } from "./useMLProcessingStatus.js";
7
+ let seq = 0;
8
+ export function useMLMediaUpload(defaults = {}) {
9
+ const base = useMLChunkUploader(defaults.chunkUploader).config;
10
+ const cfg = base.proxyUploads ? { ...base, baseURL: "" } : base;
11
+ const { track } = useMLProcessingStatus();
12
+ const uploads = ref([]);
13
+ const cleanups = /* @__PURE__ */ new Map();
14
+ function start(file, overrides = {}) {
15
+ const opts = { ...defaults, ...overrides };
16
+ const kind = opts.kind ?? kindForFile(file);
17
+ const isVisual = file.type.startsWith("image/") || file.type.startsWith("video/");
18
+ const localPreview = isVisual && typeof URL !== "undefined" ? URL.createObjectURL(file) : null;
19
+ let uploader = null;
20
+ let stream = null;
21
+ let stopWatch = null;
22
+ let resolveCreated;
23
+ const upload = reactive({
24
+ key: `upload-${++seq}`,
25
+ file,
26
+ kind,
27
+ localPreview,
28
+ phase: "uploading",
29
+ progress: 0,
30
+ assetId: null,
31
+ error: null,
32
+ cancel,
33
+ retry,
34
+ created: new Promise((r) => {
35
+ resolveCreated = r;
36
+ })
37
+ });
38
+ function finish(phase, err = null) {
39
+ upload.phase = phase;
40
+ upload.error = err;
41
+ if (phase === "ready") upload.progress = 100;
42
+ stopWatch?.();
43
+ stopWatch = null;
44
+ stream?.close();
45
+ stream = null;
46
+ }
47
+ async function checkStatus() {
48
+ if (!upload.assetId) return null;
49
+ try {
50
+ const res = await $fetch(
51
+ "/api/media-library/manage/assets/resolve",
52
+ { method: "POST", body: { assetIds: [upload.assetId] } }
53
+ );
54
+ return res.items?.[0]?.asset?.status?.toLowerCase() ?? null;
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+ async function followProcessing(assetId) {
60
+ upload.phase = "processing";
61
+ upload.progress = null;
62
+ stream = track(assetId);
63
+ stopWatch = watch(
64
+ () => [stream?.progress.value, stream?.isComplete.value, stream?.isFailed.value, stream?.error.value],
65
+ async ([progress, complete, failed, dropped]) => {
66
+ if (upload.phase !== "processing") return;
67
+ if (complete) return finish("ready");
68
+ if (failed) return finish("failed", "Processing failed");
69
+ if (dropped) {
70
+ const status2 = await checkStatus();
71
+ if (status2 === "ready") finish("ready");
72
+ else if (status2 === "failed") finish("failed", "Processing failed");
73
+ else upload.progress = null;
74
+ return;
75
+ }
76
+ upload.progress = progress ? progress : null;
77
+ }
78
+ );
79
+ const status = await checkStatus();
80
+ if (upload.phase !== "processing") return;
81
+ if (status === "ready") finish("ready");
82
+ else if (status === "failed") finish("failed", "Processing failed");
83
+ }
84
+ async function run() {
85
+ uploader = new ChunkUploader(cfg);
86
+ upload.phase = "uploading";
87
+ upload.progress = 0;
88
+ upload.error = null;
89
+ const create = {
90
+ kind,
91
+ visibility: opts.visibility,
92
+ processing_config: opts.processingConfig,
93
+ collection_name: opts.collectionName ?? null,
94
+ ...opts.profile ? { profile: opts.profile } : {}
95
+ };
96
+ try {
97
+ const res = await uploader.uploadFile(file, {
98
+ create,
99
+ onProgress: (p) => {
100
+ upload.progress = p.percent;
101
+ }
102
+ });
103
+ const ids = Array.isArray(res?.finalize?.asset_ids) ? res.finalize.asset_ids : res?.finalize?.asset_id ? [res.finalize.asset_id] : [];
104
+ const assetId = ids[0] ?? null;
105
+ if (!assetId) {
106
+ finish("failed", "The upload finished but no file was created");
107
+ resolveCreated(null);
108
+ return;
109
+ }
110
+ upload.assetId = assetId;
111
+ resolveCreated(assetId);
112
+ await followProcessing(assetId);
113
+ } catch (err) {
114
+ if (upload.phase === "canceled") return;
115
+ finish("failed", err?.data?.message ?? err?.message ?? "Upload failed");
116
+ if (!upload.assetId) resolveCreated(null);
117
+ }
118
+ }
119
+ function cancel() {
120
+ if (upload.phase !== "uploading" && upload.phase !== "processing") return;
121
+ uploader?.abort();
122
+ finish("canceled");
123
+ if (!upload.assetId) resolveCreated(null);
124
+ }
125
+ function retry() {
126
+ if (upload.phase !== "failed") return;
127
+ if (upload.assetId) {
128
+ void followProcessing(upload.assetId);
129
+ return;
130
+ }
131
+ void run();
132
+ }
133
+ cleanups.set(upload.key, () => {
134
+ uploader?.abort();
135
+ stopWatch?.();
136
+ stream?.close();
137
+ if (localPreview) URL.revokeObjectURL(localPreview);
138
+ });
139
+ uploads.value = [...uploads.value, upload];
140
+ void run();
141
+ return upload;
142
+ }
143
+ function remove(upload) {
144
+ cleanups.get(upload.key)?.();
145
+ cleanups.delete(upload.key);
146
+ uploads.value = uploads.value.filter((u) => u.key !== upload.key);
147
+ }
148
+ onBeforeUnmount(() => {
149
+ cleanups.forEach((fn) => fn());
150
+ cleanups.clear();
151
+ });
152
+ return { uploads, start, remove };
153
+ }
@@ -6,8 +6,19 @@ export * from './composables/useMLAssetManager.js';
6
6
  export * from './composables/useMLAssetLibrary.js';
7
7
  export * from './composables/useMLAssetManagerPreview.js';
8
8
  export * from './composables/useElapsedTimer.js';
9
+ export * from './composables/useMLMediaAssets.js';
10
+ export * from './composables/useMLMediaUpload.js';
11
+ export * from './composables/useMLMediaField.js';
12
+ export * from './composables/useMLMediaBrowse.js';
13
+ export * from './composables/useMLFilePicker.js';
14
+ export * from './composables/useMLMediaEditor.js';
15
+ export * from './composables/useMLMediaGallery.js';
9
16
  export * from './composables/resourcePolicy/useMLResourcePolicy.js';
10
17
  export * from './types/resourcePolicy.js';
11
18
  export * from './types/uploader.js';
12
19
  export * from './types/asset.js';
13
20
  export * from './utils/uploader/uploader.js';
21
+ export * from './utils/assetDisplay.js';
22
+ export * from './utils/assetThumbnail.js';
23
+ export * from './utils/assetFormat.js';
24
+ export * from './utils/fileAccept.js';
@@ -6,8 +6,19 @@ export * from "./composables/useMLAssetManager.js";
6
6
  export * from "./composables/useMLAssetLibrary.js";
7
7
  export * from "./composables/useMLAssetManagerPreview.js";
8
8
  export * from "./composables/useElapsedTimer.js";
9
+ export * from "./composables/useMLMediaAssets.js";
10
+ export * from "./composables/useMLMediaUpload.js";
11
+ export * from "./composables/useMLMediaField.js";
12
+ export * from "./composables/useMLMediaBrowse.js";
13
+ export * from "./composables/useMLFilePicker.js";
14
+ export * from "./composables/useMLMediaEditor.js";
15
+ export * from "./composables/useMLMediaGallery.js";
9
16
  export * from "./composables/resourcePolicy/useMLResourcePolicy.js";
10
17
  export * from "./types/resourcePolicy.js";
11
18
  export * from "./types/uploader.js";
12
19
  export * from "./types/asset.js";
13
20
  export * from "./utils/uploader/uploader.js";
21
+ export * from "./utils/assetDisplay.js";
22
+ export * from "./utils/assetThumbnail.js";
23
+ export * from "./utils/assetFormat.js";
24
+ export * from "./utils/fileAccept.js";
@@ -0,0 +1,8 @@
1
+ export type AssetBadgeColor = 'primary' | 'secondary' | 'success' | 'info' | 'warning' | 'error' | 'neutral';
2
+ export interface AssetDisplayMeta {
3
+ label: string;
4
+ color: AssetBadgeColor;
5
+ icon: string;
6
+ }
7
+ export declare function assetKindMeta(kind: string | null | undefined): AssetDisplayMeta;
8
+ export declare function assetStatusMeta(status: string | null | undefined): AssetDisplayMeta;
@@ -0,0 +1,29 @@
1
+ const KINDS = {
2
+ image: { label: "Image", color: "info", icon: "i-lucide-image" },
3
+ image_batch: { label: "Image set", color: "info", icon: "i-lucide-images" },
4
+ video: { label: "Video", color: "secondary", icon: "i-lucide-film" },
5
+ audio: { label: "Audio", color: "warning", icon: "i-lucide-music" },
6
+ document: { label: "Document", color: "success", icon: "i-lucide-file-text" },
7
+ archive: { label: "Archive", color: "neutral", icon: "i-lucide-archive" },
8
+ font: { label: "Font", color: "neutral", icon: "i-lucide-type" },
9
+ model_3d: { label: "3D", color: "neutral", icon: "i-lucide-box" },
10
+ web_app: { label: "Web app", color: "neutral", icon: "i-lucide-globe" },
11
+ other: { label: "File", color: "neutral", icon: "i-lucide-file" }
12
+ };
13
+ const STATUSES = {
14
+ pending: { label: "Pending", color: "neutral", icon: "i-lucide-clock" },
15
+ uploaded: { label: "Uploaded", color: "info", icon: "i-lucide-cloud-check" },
16
+ processing: { label: "Processing", color: "warning", icon: "i-lucide-loader-circle" },
17
+ ready: { label: "Ready", color: "success", icon: "i-lucide-circle-check" },
18
+ failed: { label: "Failed", color: "error", icon: "i-lucide-circle-alert" },
19
+ archived: { label: "Archived", color: "neutral", icon: "i-lucide-archive" }
20
+ };
21
+ const titleCase = (s) => s.replace(/_/g, " ").replace(/^\w/, (c) => c.toUpperCase());
22
+ export function assetKindMeta(kind) {
23
+ const key = (kind ?? "").toLowerCase();
24
+ return KINDS[key] ?? { ...KINDS.other, label: key ? titleCase(key) : KINDS.other.label };
25
+ }
26
+ export function assetStatusMeta(status) {
27
+ const key = (status ?? "").toLowerCase();
28
+ return STATUSES[key] ?? { label: key ? titleCase(key) : "Unknown", color: "neutral", icon: "i-lucide-circle-help" };
29
+ }
@@ -0,0 +1,30 @@
1
+ import type { AssetDetail, DeliveryAsset } from '../types/asset.js';
2
+ type AnyAsset = AssetDetail | DeliveryAsset;
3
+ /** 1536 → "1.5 KB". Binary units, one decimal above bytes. */
4
+ export declare function formatBytes(bytes: number | null | undefined): string;
5
+ /** 32 → "0:32", 2467 → "41:07", 3725 → "1:02:05". */
6
+ export declare function formatDuration(seconds: number | null | undefined): string;
7
+ /** "chair.Front.PNG" → "PNG". Empty when there is no extension. */
8
+ export declare function fileExtension(name: string | null | undefined): string;
9
+ /**
10
+ * Short date for lists: "Sep 25", or "Sep 25, 2024" outside the current year.
11
+ * Locale and timezone are fixed by default so server and browser render the
12
+ * same text (otherwise SSR hydration mismatches).
13
+ */
14
+ export declare function formatShortDate(iso: string | null | undefined, now?: Date, locale?: string, timeZone?: string): string;
15
+ export declare function assetName(asset: AnyAsset | null | undefined): string;
16
+ export declare function assetSizeBytes(asset: AnyAsset | null | undefined): number | null;
17
+ /** Longest duration any rendition reports (video, audio). */
18
+ export declare function assetDurationSeconds(asset: AnyAsset | null | undefined): number | null;
19
+ /** Largest width × height any rendition variant reports. */
20
+ export declare function assetDimensions(asset: AnyAsset | null | undefined): {
21
+ width: number;
22
+ height: number;
23
+ } | null;
24
+ export interface AssetFact {
25
+ label: string;
26
+ value: string;
27
+ }
28
+ /** The facts the editor side panel lists. Missing values are left out, not shown as "—". */
29
+ export declare function assetFacts(asset: AnyAsset | null | undefined): AssetFact[];
30
+ export {};
@@ -0,0 +1,69 @@
1
+ export function formatBytes(bytes) {
2
+ if (!bytes || bytes < 0) return "0 B";
3
+ const units = ["B", "KB", "MB", "GB", "TB"];
4
+ const i = Math.min(units.length - 1, Math.floor(Math.log(bytes) / Math.log(1024)));
5
+ return `${(bytes / 1024 ** i).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
6
+ }
7
+ export function formatDuration(seconds) {
8
+ if (seconds == null || !Number.isFinite(seconds) || seconds < 0) return "";
9
+ const s = Math.round(seconds);
10
+ const h = Math.floor(s / 3600);
11
+ const m = Math.floor(s % 3600 / 60);
12
+ const sec = String(s % 60).padStart(2, "0");
13
+ return h ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
14
+ }
15
+ export function fileExtension(name) {
16
+ const match = /\.([a-z0-9]{1,8})$/i.exec(name ?? "");
17
+ return match ? match[1].toUpperCase() : "";
18
+ }
19
+ export function formatShortDate(iso, now = /* @__PURE__ */ new Date(), locale = "en-US", timeZone = "UTC") {
20
+ if (!iso) return "";
21
+ const d = new Date(iso);
22
+ if (Number.isNaN(d.getTime())) return "";
23
+ const opts = { month: "short", day: "numeric", timeZone };
24
+ if (d.getUTCFullYear() !== now.getUTCFullYear()) opts.year = "numeric";
25
+ return d.toLocaleDateString(locale, opts);
26
+ }
27
+ export function assetName(asset) {
28
+ if (!asset) return "";
29
+ const filename = "original_filename" in asset ? asset.original_filename || asset.originalFilename : void 0;
30
+ return filename || asset.title || asset.id.slice(0, 8);
31
+ }
32
+ export function assetSizeBytes(asset) {
33
+ if (!asset || !("size_bytes" in asset)) return null;
34
+ return asset.size_bytes ?? asset.sizeBytes ?? asset.bytes ?? null;
35
+ }
36
+ export function assetDurationSeconds(asset) {
37
+ const all = (asset?.renditions ?? []).map((r) => "durationSeconds" in r ? r.durationSeconds : null).filter((n) => n != null);
38
+ return all.length ? Math.max(...all) : null;
39
+ }
40
+ export function assetDimensions(asset) {
41
+ let best = null;
42
+ for (const r of asset?.renditions ?? []) {
43
+ for (const v of r.variants ?? []) {
44
+ if (v.width && v.height && (!best || v.width * v.height > best.width * best.height)) {
45
+ best = { width: v.width, height: v.height };
46
+ }
47
+ }
48
+ }
49
+ return best;
50
+ }
51
+ export function assetFacts(asset) {
52
+ if (!asset) return [];
53
+ const facts = [];
54
+ const size = assetSizeBytes(asset);
55
+ if (size) facts.push({ label: "Size", value: formatBytes(size) });
56
+ const dims = assetDimensions(asset);
57
+ if (dims) facts.push({ label: "Dimensions", value: `${dims.width} \xD7 ${dims.height}` });
58
+ const duration = assetDurationSeconds(asset);
59
+ if (duration) facts.push({ label: "Duration", value: formatDuration(duration) });
60
+ const pages = (asset.renditions ?? []).map((r) => "pages" in r ? r.pages : null).find((p) => p);
61
+ if (pages) facts.push({ label: "Pages", value: String(pages) });
62
+ const ext = fileExtension(assetName(asset));
63
+ const mime = "mime_type" in asset ? asset.mime_type || asset.mimeType : asset.mimeType;
64
+ if (ext || mime) facts.push({ label: "Type", value: ext || mime });
65
+ const created = "created_at" in asset ? asset.created_at || asset.createdAt : void 0;
66
+ const date = formatShortDate(created);
67
+ if (date) facts.push({ label: "Added", value: date });
68
+ return facts;
69
+ }
@@ -0,0 +1,11 @@
1
+ import type { AssetDetail, DeliveryAsset } from '../types/asset.js';
2
+ type ThumbnailSource = Pick<AssetDetail, 'kind' | 'renditions'> & Partial<Pick<AssetDetail, 'previewUrl' | 'mime_type' | 'mimeType'>> | Pick<DeliveryAsset, 'kind' | 'renditions' | 'mimeType'> & {
3
+ previewUrl?: string | null;
4
+ };
5
+ /**
6
+ * Best small image for an asset: the thumbnail rendition (its first variant
7
+ * with a URL, else the rendition's own URL), then the original for images.
8
+ * Null means "show the kind icon".
9
+ */
10
+ export declare function assetThumbnailUrl(asset: ThumbnailSource | null | undefined): string | null;
11
+ export {};
@@ -0,0 +1,14 @@
1
+ const READY = /* @__PURE__ */ new Set(["ready", "completed", "succeeded"]);
2
+ export function assetThumbnailUrl(asset) {
3
+ if (!asset) return null;
4
+ for (const r of asset.renditions ?? []) {
5
+ if (r.renditionType !== "thumbnail" || !READY.has((r.status ?? "").toLowerCase())) continue;
6
+ const variant = (r.variants ?? []).find((v) => v.previewUrl);
7
+ const url = variant?.previewUrl ?? r.previewUrl;
8
+ if (url) return url;
9
+ }
10
+ const kind = (asset.kind ?? "").toLowerCase();
11
+ const mime = ("mime_type" in asset ? asset.mime_type : void 0) ?? asset.mimeType ?? "";
12
+ if ((kind === "image" || mime.startsWith("image/")) && asset.previewUrl) return asset.previewUrl;
13
+ return null;
14
+ }
@@ -0,0 +1,26 @@
1
+ /** ["image", "video"] → "image/*,video/*". Unknown kinds or "other" mean anything. */
2
+ export declare function acceptForKinds(kinds: readonly string[] | string | null | undefined): string;
3
+ export declare function matchesAccept(file: Pick<File, 'name' | 'type'>, accept: string | null | undefined): boolean;
4
+ export type FileRejectReason = 'type' | 'size' | 'count';
5
+ export interface RejectedFile {
6
+ file: File;
7
+ reason: FileRejectReason;
8
+ }
9
+ export interface FileFilterOptions {
10
+ accept?: string | null;
11
+ /** Bytes. */
12
+ maxSize?: number | null;
13
+ /** How many more files fit. Files past it are rejected with reason "count". */
14
+ maxFiles?: number | null;
15
+ }
16
+ export declare function partitionFiles(files: readonly File[], opts?: FileFilterOptions): {
17
+ accepted: File[];
18
+ rejected: RejectedFile[];
19
+ };
20
+ /**
21
+ * The asset kind an upload session should declare for a file. Policy-less
22
+ * sessions must send `kind`; the service no longer infers it.
23
+ */
24
+ export declare function kindForFile(file: Pick<File, 'name' | 'type'>): string;
25
+ /** "image,video" → "images and videos", for messages such as "This field takes …". */
26
+ export declare function describeKinds(kinds: readonly string[] | string | null | undefined): string;
@@ -0,0 +1,104 @@
1
+ const KIND_ACCEPT = {
2
+ image: ["image/*"],
3
+ image_batch: ["image/*"],
4
+ video: ["video/*"],
5
+ audio: ["audio/*"],
6
+ document: ["application/pdf", ".doc", ".docx", ".ppt", ".pptx", ".xls", ".xlsx", ".odt", ".txt", ".csv", ".md", ".rtf"],
7
+ archive: [".zip", ".tar", ".gz", ".tgz", ".7z", ".rar"],
8
+ font: ["font/*", ".ttf", ".otf", ".woff", ".woff2"],
9
+ model_3d: ["model/*", ".glb", ".gltf", ".obj", ".fbx", ".stl", ".usdz"],
10
+ web_app: [".zip"]
11
+ };
12
+ export function acceptForKinds(kinds) {
13
+ const list = typeof kinds === "string" ? kinds.split(",") : kinds ?? [];
14
+ const parts = /* @__PURE__ */ new Set();
15
+ for (const raw of list) {
16
+ const k = raw.trim().toLowerCase();
17
+ if (!k) continue;
18
+ const accept = KIND_ACCEPT[k];
19
+ if (!accept) return "*";
20
+ accept.forEach((a) => parts.add(a));
21
+ }
22
+ return parts.size ? [...parts].join(",") : "*";
23
+ }
24
+ export function matchesAccept(file, accept) {
25
+ if (!accept || accept.trim() === "*") return true;
26
+ const name = file.name.toLowerCase();
27
+ const type = (file.type || "").toLowerCase();
28
+ return accept.split(",").map((a) => a.trim().toLowerCase()).filter(Boolean).some((rule) => {
29
+ if (rule === "*" || rule === "*/*") return true;
30
+ if (rule.startsWith(".")) return name.endsWith(rule);
31
+ if (rule.endsWith("/*")) return type.startsWith(rule.slice(0, -1));
32
+ return type === rule;
33
+ });
34
+ }
35
+ export function partitionFiles(files, opts = {}) {
36
+ const accepted = [];
37
+ const rejected = [];
38
+ for (const file of files) {
39
+ if (!matchesAccept(file, opts.accept)) rejected.push({ file, reason: "type" });
40
+ else if (opts.maxSize && file.size > opts.maxSize) rejected.push({ file, reason: "size" });
41
+ else if (opts.maxFiles != null && accepted.length >= opts.maxFiles) rejected.push({ file, reason: "count" });
42
+ else accepted.push(file);
43
+ }
44
+ return { accepted, rejected };
45
+ }
46
+ const EXT_KIND = {
47
+ pdf: "document",
48
+ doc: "document",
49
+ docx: "document",
50
+ ppt: "document",
51
+ pptx: "document",
52
+ xls: "document",
53
+ xlsx: "document",
54
+ odt: "document",
55
+ txt: "document",
56
+ csv: "document",
57
+ md: "document",
58
+ rtf: "document",
59
+ zip: "archive",
60
+ tar: "archive",
61
+ gz: "archive",
62
+ tgz: "archive",
63
+ "7z": "archive",
64
+ rar: "archive",
65
+ ttf: "font",
66
+ otf: "font",
67
+ woff: "font",
68
+ woff2: "font",
69
+ glb: "model_3d",
70
+ gltf: "model_3d",
71
+ obj: "model_3d",
72
+ fbx: "model_3d",
73
+ stl: "model_3d",
74
+ usdz: "model_3d"
75
+ };
76
+ export function kindForFile(file) {
77
+ const type = (file.type || "").toLowerCase();
78
+ for (const k of ["image", "video", "audio"]) {
79
+ if (type.startsWith(`${k}/`)) return k;
80
+ }
81
+ if (type === "application/pdf" || type.startsWith("text/")) return "document";
82
+ if (type.startsWith("font/")) return "font";
83
+ if (type.startsWith("model/")) return "model_3d";
84
+ const ext = /\.([a-z0-9]{1,8})$/i.exec(file.name)?.[1]?.toLowerCase() ?? "";
85
+ return EXT_KIND[ext] ?? "other";
86
+ }
87
+ export function describeKinds(kinds) {
88
+ const NAMES = {
89
+ image: "images",
90
+ image_batch: "image sets",
91
+ video: "videos",
92
+ audio: "audio",
93
+ document: "documents",
94
+ archive: "archives",
95
+ font: "fonts",
96
+ model_3d: "3D models",
97
+ web_app: "web apps",
98
+ other: "any file"
99
+ };
100
+ const list = (typeof kinds === "string" ? kinds.split(",") : kinds ?? []).map((k) => k.trim().toLowerCase()).filter(Boolean);
101
+ if (!list.length) return "any file";
102
+ const names = list.map((k) => NAMES[k] ?? k.replace(/_/g, " "));
103
+ return names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names.at(-1)}`;
104
+ }
@@ -0,0 +1,2 @@
1
+ /** dataTransfer type for a gallery tile being dragged (reorder, or onto a role chip). */
2
+ export declare const GALLERY_TILE_TYPE = "application/x-ml-media-tile";
@@ -0,0 +1 @@
1
+ export const GALLERY_TILE_TYPE = "application/x-ml-media-tile";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-media-library",
3
- "version": "0.0.48",
3
+ "version": "0.0.49",
4
4
  "description": "Nuxt module for media library management — chunked uploads, asset previews, and resource policies",
5
5
  "repository": {
6
6
  "type": "git",