nuxt-media-library 0.0.58 → 0.0.60
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/README.md +58 -0
- package/dist/module.d.mts +12 -0
- package/dist/module.d.ts +12 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -0
- package/dist/runtime/components/MLAssetManager/AssetOverview.vue +3 -2
- package/dist/runtime/components/MLMedia/Field.d.vue.ts +19 -9
- package/dist/runtime/components/MLMedia/Field.vue +11 -2
- package/dist/runtime/components/MLMedia/Field.vue.d.ts +19 -9
- package/dist/runtime/components/MLMedia/FileFacts.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/FileFacts.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +17 -7
- package/dist/runtime/components/MLMedia/Gallery.vue +9 -1
- package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +17 -7
- package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +12 -9
- package/dist/runtime/components/MLMedia/GallerySection.vue +7 -1
- package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +12 -9
- package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +3 -3
- package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +3 -3
- package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +3 -3
- package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +3 -3
- package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +2 -2
- package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +2 -2
- package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +2 -2
- package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +2 -2
- package/dist/runtime/components/MLMedia/Thumbnail.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/Thumbnail.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/UploadRow.vue +2 -2
- package/dist/runtime/components/MLMedia/UploadSteps.vue +5 -5
- package/dist/runtime/components/MLMedia/UsageList.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/UsageList.vue.d.ts +1 -1
- package/dist/runtime/components/MLProcessing/Pipeline.vue +2 -2
- package/dist/runtime/composables/useMLAssetLibrary.js +2 -1
- package/dist/runtime/composables/useMLAssetManager.js +2 -1
- package/dist/runtime/composables/useMLAssetPreview.js +9 -2
- package/dist/runtime/composables/useMLMediaAssets.js +25 -3
- package/dist/runtime/composables/useMLMediaBrowse.d.ts +6 -0
- package/dist/runtime/composables/useMLMediaBrowse.js +2 -1
- package/dist/runtime/composables/useMLMediaGallery.d.ts +2 -0
- package/dist/runtime/composables/useMLMediaGallery.js +2 -0
- package/dist/runtime/composables/useMLMediaMeta.d.ts +18 -0
- package/dist/runtime/composables/useMLMediaMeta.js +55 -0
- package/dist/runtime/composables/useMLProcessingStepper.js +2 -1
- package/dist/runtime/composables/useMLUploadQueue.js +7 -5
- package/dist/runtime/composables/useMLWebAppDeploys.d.ts +48 -0
- package/dist/runtime/composables/useMLWebAppDeploys.js +69 -0
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/plugins/config.js +7 -0
- package/dist/runtime/server/assets-proxy.js +9 -1
- package/dist/runtime/server/create-ml-proxy.js +5 -1
- package/dist/runtime/server/grants.js +3 -0
- package/dist/runtime/server/index.d.ts +3 -0
- package/dist/runtime/server/index.js +1 -0
- package/dist/runtime/types/asset.d.ts +35 -4
- package/dist/runtime/utils/errorMessage.d.ts +42 -0
- package/dist/runtime/utils/errorMessage.js +112 -0
- package/dist/runtime/utils/fileMeta.d.ts +48 -0
- package/dist/runtime/utils/fileMeta.js +84 -0
- package/dist/runtime/utils/processingSteps.d.ts +9 -1
- package/dist/runtime/utils/processingSteps.js +18 -11
- package/dist/runtime/utils/streamUrls.d.ts +9 -0
- package/dist/runtime/utils/streamUrls.js +11 -0
- package/dist/runtime/utils/webAppDeploys.d.ts +14 -0
- package/dist/runtime/utils/webAppDeploys.js +17 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -294,6 +294,64 @@ export default defineNuxtConfig({
|
|
|
294
294
|
- There is no default backend: each layer's widgets name theirs. The module
|
|
295
295
|
only stores the paths; each layer's own routes reach its service.
|
|
296
296
|
|
|
297
|
+
### File meta from the widgets
|
|
298
|
+
|
|
299
|
+
`MLMediaField` and `MLMediaGallery` report what they know about their files:
|
|
300
|
+
name, extension, MIME type, kind, size, duration, width, height and pages.
|
|
301
|
+
|
|
302
|
+
```vue
|
|
303
|
+
<MLMediaField v-model="lesson.videoId" v-model:meta="videoMeta" @meta="m => console.log(m?.durationSeconds)" />
|
|
304
|
+
|
|
305
|
+
<MLMediaGallery v-model="post.mediaIds" v-model:meta="mediaMeta" @meta="(assetId, m) => …" />
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
- `v-model:meta` is output only: `MediaFileMeta | null` on the field, a map
|
|
309
|
+
by asset ID on the gallery (every role). `@meta` fires per file that
|
|
310
|
+
changed, with null when it left.
|
|
311
|
+
- A file uploaded in this tab is read in the browser right away
|
|
312
|
+
(`source: 'local'`, duration and size from a hidden video, audio or image
|
|
313
|
+
element). The media library's data replaces it once loaded
|
|
314
|
+
(`source: 'server'`); values it has no rendition for yet stay local.
|
|
315
|
+
- A file needs an asset ID before it has meta: during the byte upload it
|
|
316
|
+
has none yet.
|
|
317
|
+
- `useMLMediaMeta(ids, getAsset)` gives the same map for your own lists.
|
|
318
|
+
|
|
319
|
+
### Error messages, in the editor's language
|
|
320
|
+
|
|
321
|
+
When a backend refuses a request it answers `{ detail, reason }`: `reason`
|
|
322
|
+
is a stable code (py_client `UploadRejected`: `too_large`, `mime_type`,
|
|
323
|
+
`context_mismatch`, …), `detail` English text. The widgets show, first that
|
|
324
|
+
applies:
|
|
325
|
+
|
|
326
|
+
1. the text for a known `reason`, in the editor's language;
|
|
327
|
+
2. `detail` (a 422's first `msg`);
|
|
328
|
+
3. the backend's inner `message`, then the status text;
|
|
329
|
+
4. the place's own fallback ("Upload failed", "Could not add the file", …).
|
|
330
|
+
|
|
331
|
+
The body is read whether the module's own proxy streamed it (`err.data`) or
|
|
332
|
+
an app proxy rethrew it with h3 `createError({ data })` (`err.data.data`).
|
|
333
|
+
The raw "[POST] url: 400" text is never shown.
|
|
334
|
+
|
|
335
|
+
The language is the app's `@nuxtjs/i18n` locale, else `mediaLibrary.locale`;
|
|
336
|
+
its language part picks the texts (`ar-SA` → `ar`). Built in: `en` and `ar`.
|
|
337
|
+
An app can change any text, or add a language:
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
mediaLibrary: {
|
|
341
|
+
locale: 'ar', // without @nuxtjs/i18n
|
|
342
|
+
errorTexts: {
|
|
343
|
+
ar: {
|
|
344
|
+
reasons: { too_large: 'حجم هذا الملف أكبر من المسموح.' },
|
|
345
|
+
fallbacks: { upload: 'فشل رفع الملف' },
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
A reason with no text in the editor's language uses the English one, then
|
|
352
|
+
`detail`. `mediaErrorMessage(err, fallback, { locale, texts })` and
|
|
353
|
+
`mlErrorMessage(err, fallback)` are exported for your own pages.
|
|
354
|
+
|
|
297
355
|
### Vue installation (non-Nuxt)
|
|
298
356
|
|
|
299
357
|
```bash
|
package/dist/module.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
|
|
3
3
|
import { MediaBackends } from '../dist/runtime/utils/backends.js';
|
|
4
|
+
import { MediaErrorTextsByLocale } from '../dist/runtime/utils/errorMessage.js';
|
|
4
5
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
5
6
|
export * from '../dist/runtime/types/uploader.js';
|
|
6
7
|
export * from '../dist/runtime/types/preview.js';
|
|
@@ -72,6 +73,17 @@ interface ModuleOptions {
|
|
|
72
73
|
* its routes.
|
|
73
74
|
*/
|
|
74
75
|
backends?: MediaBackends;
|
|
76
|
+
/**
|
|
77
|
+
* Language of the widgets' error texts when the app has no i18n locale
|
|
78
|
+
* (with @nuxtjs/i18n its active locale wins). Built in: `en`, `ar`.
|
|
79
|
+
*/
|
|
80
|
+
locale?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The app's error texts over the built-in ones, by language:
|
|
83
|
+
* `{ ar: { reasons: { too_large: '…' }, fallbacks: { upload: '…' } } }`.
|
|
84
|
+
* `reasons` are backend reason codes (py_client `UploadRejected`).
|
|
85
|
+
*/
|
|
86
|
+
errorTexts?: MediaErrorTextsByLocale;
|
|
75
87
|
}
|
|
76
88
|
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
77
89
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
|
|
3
3
|
import { MediaBackends } from '../dist/runtime/utils/backends.js';
|
|
4
|
+
import { MediaErrorTextsByLocale } from '../dist/runtime/utils/errorMessage.js';
|
|
4
5
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
5
6
|
export * from '../dist/runtime/types/uploader.js';
|
|
6
7
|
export * from '../dist/runtime/types/preview.js';
|
|
@@ -72,6 +73,17 @@ interface ModuleOptions {
|
|
|
72
73
|
* its routes.
|
|
73
74
|
*/
|
|
74
75
|
backends?: MediaBackends;
|
|
76
|
+
/**
|
|
77
|
+
* Language of the widgets' error texts when the app has no i18n locale
|
|
78
|
+
* (with @nuxtjs/i18n its active locale wins). Built in: `en`, `ar`.
|
|
79
|
+
*/
|
|
80
|
+
locale?: string;
|
|
81
|
+
/**
|
|
82
|
+
* The app's error texts over the built-in ones, by language:
|
|
83
|
+
* `{ ar: { reasons: { too_large: '…' }, fallbacks: { upload: '…' } } }`.
|
|
84
|
+
* `reasons` are backend reason codes (py_client `UploadRejected`).
|
|
85
|
+
*/
|
|
86
|
+
errorTexts?: MediaErrorTextsByLocale;
|
|
75
87
|
}
|
|
76
88
|
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
77
89
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -177,6 +177,9 @@ const module$1 = defineNuxtModule({
|
|
|
177
177
|
// The backend registry (NUXT_PUBLIC_MEDIA_LIBRARY_BACKENDS): names widgets pass as `backend`.
|
|
178
178
|
backends,
|
|
179
179
|
// Older form, kept for code that reads it: each backend's upload routes.
|
|
180
|
+
// NUXT_PUBLIC_MEDIA_LIBRARY_LOCALE; the app's i18n locale wins at runtime.
|
|
181
|
+
locale: existingPublic.locale || options.locale || "",
|
|
182
|
+
errorTexts: { ...options.errorTexts, ...existingPublic.errorTexts },
|
|
180
183
|
serviceUploadBackends: Object.fromEntries(
|
|
181
184
|
Object.entries(backends).flatMap(([name, b]) => b.upload ? [[name, b.upload]] : [])
|
|
182
185
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
3
|
import { useMLResourcePolicy, Visibility } from "../../index";
|
|
4
|
+
import { mlErrorMessage } from "../../utils/errorMessage";
|
|
4
5
|
const props = defineProps({
|
|
5
6
|
asset: { type: Object, required: true },
|
|
6
7
|
manager: { type: Object, required: false }
|
|
@@ -71,7 +72,7 @@ async function changeVisibility(next) {
|
|
|
71
72
|
try {
|
|
72
73
|
await props.manager.updateAsset(props.asset.id, { visibility: next });
|
|
73
74
|
} catch (err) {
|
|
74
|
-
visibilityError.value = err
|
|
75
|
+
visibilityError.value = mlErrorMessage(err, "updateVisibility");
|
|
75
76
|
} finally {
|
|
76
77
|
visibilityUpdating.value = false;
|
|
77
78
|
}
|
|
@@ -138,7 +139,7 @@ async function loadPolicy() {
|
|
|
138
139
|
try {
|
|
139
140
|
policy.value = await policyApi.get(id);
|
|
140
141
|
} catch (err) {
|
|
141
|
-
policyError.value = err
|
|
142
|
+
policyError.value = mlErrorMessage(err, "loadPolicy");
|
|
142
143
|
policy.value = null;
|
|
143
144
|
} finally {
|
|
144
145
|
policyLoading.value = false;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MediaFileMeta } from '../../composables/useMLMediaMeta.js';
|
|
1
2
|
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
2
3
|
type __VLS_Props = {
|
|
3
4
|
/** Asset kinds the field takes: ['image'], "image,video". Empty means any file. */
|
|
@@ -42,34 +43,43 @@ type __VLS_ModelProps = {
|
|
|
42
43
|
modelValue?: string | null;
|
|
43
44
|
/** Alt text for the file, stored on the host record. Edited in the media editor. */
|
|
44
45
|
'alt'?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* The file's meta (name, extension, MIME type, kind, size, duration,
|
|
48
|
+
* dimensions), or null when empty. Output only: the field writes it.
|
|
49
|
+
*/
|
|
50
|
+
'meta'?: MediaFileMeta | null;
|
|
45
51
|
};
|
|
46
52
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
47
53
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
48
54
|
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
55
|
+
meta: (meta: MediaFileMeta | null) => any;
|
|
49
56
|
"update:modelValue": (value: string | null) => any;
|
|
50
57
|
"update:alt": (value: string | undefined) => any;
|
|
58
|
+
"update:meta": (value: MediaFileMeta | null) => any;
|
|
51
59
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
52
60
|
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
61
|
+
onMeta?: ((meta: MediaFileMeta | null) => any) | undefined;
|
|
53
62
|
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
54
63
|
"onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
|
|
64
|
+
"onUpdate:meta"?: ((value: MediaFileMeta | null) => any) | undefined;
|
|
55
65
|
}>, {
|
|
56
|
-
visibility: "private" | "
|
|
57
|
-
label: string;
|
|
66
|
+
visibility: "private" | "public" | "restricted";
|
|
58
67
|
disabled: boolean;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
label: string;
|
|
69
|
+
backend: string | null;
|
|
70
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
71
|
+
profile: string | null;
|
|
63
72
|
processingConfig: {
|
|
64
73
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
65
74
|
useFilenameAsAssetId?: boolean;
|
|
66
75
|
enableTranscription?: boolean;
|
|
67
76
|
};
|
|
68
77
|
collectionName: string | null;
|
|
69
|
-
profile: string | null;
|
|
70
|
-
backend: string | null;
|
|
71
|
-
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
72
78
|
serviceBody: Record<string, unknown> | null;
|
|
79
|
+
accept: readonly string[] | string;
|
|
80
|
+
maxSize: number | null;
|
|
81
|
+
required: boolean;
|
|
82
|
+
removeLabel: string;
|
|
73
83
|
hint: string;
|
|
74
84
|
missingAlt: boolean;
|
|
75
85
|
picker: boolean;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { computed } from "vue";
|
|
2
|
+
import { computed, watch } from "vue";
|
|
3
3
|
import { useMLMediaField } from "../../composables/useMLMediaField";
|
|
4
4
|
import { useMLFilePicker } from "../../composables/useMLFilePicker";
|
|
5
5
|
import { useMLMediaEditor } from "../../composables/useMLMediaEditor";
|
|
6
|
+
import { useMLMediaMeta } from "../../composables/useMLMediaMeta";
|
|
6
7
|
import { assetDimensions, assetDurationSeconds, assetName, assetSizeBytes, formatBytes, formatDuration } from "../../utils/assetFormat";
|
|
7
8
|
const props = defineProps({
|
|
8
9
|
accept: { type: [Array, String], required: false, default: "" },
|
|
@@ -27,7 +28,8 @@ const props = defineProps({
|
|
|
27
28
|
});
|
|
28
29
|
const model = defineModel({ type: [String, null], ...{ default: null } });
|
|
29
30
|
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
30
|
-
const
|
|
31
|
+
const meta = defineModel("meta", { type: [Object, null], ...{ default: null } });
|
|
32
|
+
const emit = defineEmits(["uploaded", "meta"]);
|
|
31
33
|
const field = useMLMediaField(model, {
|
|
32
34
|
accept: () => props.accept,
|
|
33
35
|
maxSize: () => props.maxSize,
|
|
@@ -42,6 +44,13 @@ const field = useMLMediaField(model, {
|
|
|
42
44
|
onUploaded: (id, sessionId) => emit("uploaded", id, sessionId)
|
|
43
45
|
});
|
|
44
46
|
const { state, asset, upload, message } = field;
|
|
47
|
+
const metaById = useMLMediaMeta(() => [model.value], (id) => id === model.value ? asset.value : null);
|
|
48
|
+
const currentMeta = computed(() => model.value ? metaById.value[model.value] ?? null : null);
|
|
49
|
+
watch(() => JSON.stringify(currentMeta.value), (now, before) => {
|
|
50
|
+
if (now === before || before === void 0 && !currentMeta.value) return;
|
|
51
|
+
meta.value = currentMeta.value;
|
|
52
|
+
emit("meta", currentMeta.value);
|
|
53
|
+
}, { immediate: true });
|
|
45
54
|
const helpText = computed(() => {
|
|
46
55
|
if (props.hint) return props.hint;
|
|
47
56
|
const parts = [field.acceptLabel.value === "any file" ? "" : `Takes ${field.acceptLabel.value}`];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MediaFileMeta } from '../../composables/useMLMediaMeta.js';
|
|
1
2
|
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
2
3
|
type __VLS_Props = {
|
|
3
4
|
/** Asset kinds the field takes: ['image'], "image,video". Empty means any file. */
|
|
@@ -42,34 +43,43 @@ type __VLS_ModelProps = {
|
|
|
42
43
|
modelValue?: string | null;
|
|
43
44
|
/** Alt text for the file, stored on the host record. Edited in the media editor. */
|
|
44
45
|
'alt'?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* The file's meta (name, extension, MIME type, kind, size, duration,
|
|
48
|
+
* dimensions), or null when empty. Output only: the field writes it.
|
|
49
|
+
*/
|
|
50
|
+
'meta'?: MediaFileMeta | null;
|
|
45
51
|
};
|
|
46
52
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
47
53
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
48
54
|
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
55
|
+
meta: (meta: MediaFileMeta | null) => any;
|
|
49
56
|
"update:modelValue": (value: string | null) => any;
|
|
50
57
|
"update:alt": (value: string | undefined) => any;
|
|
58
|
+
"update:meta": (value: MediaFileMeta | null) => any;
|
|
51
59
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
52
60
|
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
61
|
+
onMeta?: ((meta: MediaFileMeta | null) => any) | undefined;
|
|
53
62
|
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
54
63
|
"onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
|
|
64
|
+
"onUpdate:meta"?: ((value: MediaFileMeta | null) => any) | undefined;
|
|
55
65
|
}>, {
|
|
56
|
-
visibility: "private" | "
|
|
57
|
-
label: string;
|
|
66
|
+
visibility: "private" | "public" | "restricted";
|
|
58
67
|
disabled: boolean;
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
label: string;
|
|
69
|
+
backend: string | null;
|
|
70
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
71
|
+
profile: string | null;
|
|
63
72
|
processingConfig: {
|
|
64
73
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
65
74
|
useFilenameAsAssetId?: boolean;
|
|
66
75
|
enableTranscription?: boolean;
|
|
67
76
|
};
|
|
68
77
|
collectionName: string | null;
|
|
69
|
-
profile: string | null;
|
|
70
|
-
backend: string | null;
|
|
71
|
-
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
72
78
|
serviceBody: Record<string, unknown> | null;
|
|
79
|
+
accept: readonly string[] | string;
|
|
80
|
+
maxSize: number | null;
|
|
81
|
+
required: boolean;
|
|
82
|
+
removeLabel: string;
|
|
73
83
|
hint: string;
|
|
74
84
|
missingAlt: boolean;
|
|
75
85
|
picker: boolean;
|
|
@@ -10,8 +10,8 @@ type __VLS_Props = {
|
|
|
10
10
|
title?: string;
|
|
11
11
|
};
|
|
12
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
13
|
-
asset: AssetDetail | DeliveryAsset | null;
|
|
14
13
|
title: string;
|
|
14
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
15
15
|
items: AssetFact[];
|
|
16
16
|
extra: AssetFact[];
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -10,8 +10,8 @@ type __VLS_Props = {
|
|
|
10
10
|
title?: string;
|
|
11
11
|
};
|
|
12
12
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
13
|
-
asset: AssetDetail | DeliveryAsset | null;
|
|
14
13
|
title: string;
|
|
14
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
15
15
|
items: AssetFact[];
|
|
16
16
|
extra: AssetFact[];
|
|
17
17
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -14,11 +14,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
14
14
|
onClose?: ((ids: string[] | null) => any) | undefined;
|
|
15
15
|
}>, {
|
|
16
16
|
title: string;
|
|
17
|
+
upload: MediaUploadOptions;
|
|
17
18
|
accept: readonly string[] | string;
|
|
18
19
|
max: number;
|
|
19
20
|
selected: readonly string[];
|
|
20
21
|
maxSize: number | null;
|
|
21
|
-
upload: MediaUploadOptions;
|
|
22
22
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
23
23
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
24
|
declare const _default: typeof __VLS_export;
|
|
@@ -14,11 +14,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
14
14
|
onClose?: ((ids: string[] | null) => any) | undefined;
|
|
15
15
|
}>, {
|
|
16
16
|
title: string;
|
|
17
|
+
upload: MediaUploadOptions;
|
|
17
18
|
accept: readonly string[] | string;
|
|
18
19
|
max: number;
|
|
19
20
|
selected: readonly string[];
|
|
20
21
|
maxSize: number | null;
|
|
21
|
-
upload: MediaUploadOptions;
|
|
22
22
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
23
23
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
24
|
declare const _default: typeof __VLS_export;
|
|
@@ -24,11 +24,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
24
24
|
onDone?: ((ids: string[]) => any) | undefined;
|
|
25
25
|
}>, {
|
|
26
26
|
title: string;
|
|
27
|
+
upload: MediaUploadOptions;
|
|
27
28
|
accept: readonly string[] | string;
|
|
28
29
|
max: number;
|
|
29
30
|
selected: readonly string[];
|
|
30
31
|
maxSize: number | null;
|
|
31
|
-
upload: MediaUploadOptions;
|
|
32
32
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
33
33
|
bodyClass: string;
|
|
34
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -24,11 +24,11 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
24
24
|
onDone?: ((ids: string[]) => any) | undefined;
|
|
25
25
|
}>, {
|
|
26
26
|
title: string;
|
|
27
|
+
upload: MediaUploadOptions;
|
|
27
28
|
accept: readonly string[] | string;
|
|
28
29
|
max: number;
|
|
29
30
|
selected: readonly string[];
|
|
30
31
|
maxSize: number | null;
|
|
31
|
-
upload: MediaUploadOptions;
|
|
32
32
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
33
33
|
bodyClass: string;
|
|
34
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
2
|
+
import { type MediaFileMeta } from '../../composables/useMLMediaMeta.js';
|
|
2
3
|
export interface MediaGalleryRole {
|
|
3
4
|
/** Shown on chips, badges and menus, e.g. "Cover". */
|
|
4
5
|
label: string;
|
|
@@ -52,35 +53,44 @@ type __VLS_Props = {
|
|
|
52
53
|
type __VLS_ModelProps = {
|
|
53
54
|
modelValue?: string[];
|
|
54
55
|
'alt'?: Record<string, string> | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Meta of every file in the gallery (all roles), by asset ID: name,
|
|
58
|
+
* extension, MIME type, kind, size, duration, dimensions. Output only.
|
|
59
|
+
*/
|
|
60
|
+
'meta'?: Record<string, MediaFileMeta>;
|
|
55
61
|
};
|
|
56
62
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
57
63
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
58
64
|
uploaded: (assetId: string, role: string, sessionId: string | null) => any;
|
|
65
|
+
meta: (assetId: string, meta: MediaFileMeta | null) => any;
|
|
59
66
|
"update:modelValue": (value: string[]) => any;
|
|
60
67
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
68
|
+
"update:meta": (value: Record<string, MediaFileMeta>) => any;
|
|
61
69
|
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
62
70
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
63
71
|
onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
|
|
72
|
+
onMeta?: ((assetId: string, meta: MediaFileMeta | null) => any) | undefined;
|
|
64
73
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
65
74
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
75
|
+
"onUpdate:meta"?: ((value: Record<string, MediaFileMeta>) => any) | undefined;
|
|
66
76
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
67
77
|
}>, {
|
|
68
|
-
visibility: "private" | "restricted" | "public";
|
|
69
78
|
title: string;
|
|
79
|
+
visibility: "private" | "public" | "restricted";
|
|
70
80
|
disabled: boolean;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
backend: string | null;
|
|
82
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
83
|
+
profile: string | null;
|
|
74
84
|
processingConfig: {
|
|
75
85
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
76
86
|
useFilenameAsAssetId?: boolean;
|
|
77
87
|
enableTranscription?: boolean;
|
|
78
88
|
};
|
|
79
89
|
collectionName: string | null;
|
|
80
|
-
profile: string | null;
|
|
81
|
-
backend: string | null;
|
|
82
|
-
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
83
90
|
serviceBody: Record<string, unknown> | null;
|
|
91
|
+
accept: readonly string[] | string;
|
|
92
|
+
max: number | null;
|
|
93
|
+
maxSize: number | null;
|
|
84
94
|
hint: string;
|
|
85
95
|
picker: boolean;
|
|
86
96
|
onPick: () => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
3
|
import { $fetch } from "ofetch";
|
|
4
|
+
import { watchMediaMeta } from "../../composables/useMLMediaMeta";
|
|
4
5
|
import { kindForFile } from "../../utils/fileAccept";
|
|
5
6
|
import { GALLERY_TILE_TYPE } from "../../utils/galleryDrag";
|
|
6
7
|
import GallerySection from "./GallerySection.vue";
|
|
@@ -29,7 +30,12 @@ const props = defineProps({
|
|
|
29
30
|
});
|
|
30
31
|
const model = defineModel({ type: Array, ...{ default: () => [] } });
|
|
31
32
|
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
32
|
-
const
|
|
33
|
+
const meta = defineModel("meta", { type: Object, ...{ default: () => ({}) } });
|
|
34
|
+
const emit = defineEmits(["uploaded", "update:profiles", "meta"]);
|
|
35
|
+
const sectionMeta = ref({});
|
|
36
|
+
watchMediaMeta(computed(() => sectionMeta.value), (all) => {
|
|
37
|
+
meta.value = all;
|
|
38
|
+
}, (id, m) => emit("meta", id, m));
|
|
33
39
|
const roles = computed(() => Object.entries(props.profiles ?? {}).map(([key, r]) => ({ key, ...r })));
|
|
34
40
|
const hasRoles = computed(() => roles.value.length > 0);
|
|
35
41
|
const single = (r) => r.max === 1;
|
|
@@ -296,6 +302,7 @@ function chipCount(key, max) {
|
|
|
296
302
|
:on-add="onAdd"
|
|
297
303
|
@update:model-value="onUnionUpdate"
|
|
298
304
|
@uploaded="onUploaded"
|
|
305
|
+
@meta="(m) => sectionMeta = m"
|
|
299
306
|
>
|
|
300
307
|
<template #toolbar>
|
|
301
308
|
<div class="flex flex-col gap-1.5">
|
|
@@ -363,5 +370,6 @@ function chipCount(key, max) {
|
|
|
363
370
|
:editor="editor"
|
|
364
371
|
:on-open="onOpen"
|
|
365
372
|
@uploaded="(id, sessionId) => emit('uploaded', id, '', sessionId)"
|
|
373
|
+
@meta="(m) => sectionMeta = m"
|
|
366
374
|
/>
|
|
367
375
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
2
|
+
import { type MediaFileMeta } from '../../composables/useMLMediaMeta.js';
|
|
2
3
|
export interface MediaGalleryRole {
|
|
3
4
|
/** Shown on chips, badges and menus, e.g. "Cover". */
|
|
4
5
|
label: string;
|
|
@@ -52,35 +53,44 @@ type __VLS_Props = {
|
|
|
52
53
|
type __VLS_ModelProps = {
|
|
53
54
|
modelValue?: string[];
|
|
54
55
|
'alt'?: Record<string, string> | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Meta of every file in the gallery (all roles), by asset ID: name,
|
|
58
|
+
* extension, MIME type, kind, size, duration, dimensions. Output only.
|
|
59
|
+
*/
|
|
60
|
+
'meta'?: Record<string, MediaFileMeta>;
|
|
55
61
|
};
|
|
56
62
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
57
63
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
58
64
|
uploaded: (assetId: string, role: string, sessionId: string | null) => any;
|
|
65
|
+
meta: (assetId: string, meta: MediaFileMeta | null) => any;
|
|
59
66
|
"update:modelValue": (value: string[]) => any;
|
|
60
67
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
68
|
+
"update:meta": (value: Record<string, MediaFileMeta>) => any;
|
|
61
69
|
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
62
70
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
63
71
|
onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
|
|
72
|
+
onMeta?: ((assetId: string, meta: MediaFileMeta | null) => any) | undefined;
|
|
64
73
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
65
74
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
75
|
+
"onUpdate:meta"?: ((value: Record<string, MediaFileMeta>) => any) | undefined;
|
|
66
76
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
67
77
|
}>, {
|
|
68
|
-
visibility: "private" | "restricted" | "public";
|
|
69
78
|
title: string;
|
|
79
|
+
visibility: "private" | "public" | "restricted";
|
|
70
80
|
disabled: boolean;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
81
|
+
backend: string | null;
|
|
82
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
83
|
+
profile: string | null;
|
|
74
84
|
processingConfig: {
|
|
75
85
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
76
86
|
useFilenameAsAssetId?: boolean;
|
|
77
87
|
enableTranscription?: boolean;
|
|
78
88
|
};
|
|
79
89
|
collectionName: string | null;
|
|
80
|
-
profile: string | null;
|
|
81
|
-
backend: string | null;
|
|
82
|
-
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
83
90
|
serviceBody: Record<string, unknown> | null;
|
|
91
|
+
accept: readonly string[] | string;
|
|
92
|
+
max: number | null;
|
|
93
|
+
maxSize: number | null;
|
|
84
94
|
hint: string;
|
|
85
95
|
picker: boolean;
|
|
86
96
|
onPick: () => void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DropdownMenuItem } from '@nuxt/ui';
|
|
2
2
|
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
3
|
+
import { type MediaFileMeta } from '../../composables/useMLMediaMeta.js';
|
|
3
4
|
type __VLS_Props = {
|
|
4
5
|
/** Asset kinds the list takes. Empty means any file. */
|
|
5
6
|
accept?: readonly string[] | string;
|
|
@@ -69,32 +70,34 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
69
70
|
uploadFiles: typeof uploadFiles;
|
|
70
71
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
71
72
|
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
73
|
+
meta: (meta: Record<string, MediaFileMeta>) => any;
|
|
72
74
|
"update:modelValue": (value: string[]) => any;
|
|
73
75
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
74
76
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
75
77
|
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
78
|
+
onMeta?: ((meta: Record<string, MediaFileMeta>) => any) | undefined;
|
|
76
79
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
77
80
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
78
81
|
}>, {
|
|
79
|
-
visibility: "private" | "restricted" | "public";
|
|
80
82
|
title: string;
|
|
83
|
+
visibility: "private" | "public" | "restricted";
|
|
81
84
|
disabled: boolean;
|
|
82
85
|
highlight: ReadonlySet<string> | null;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
backend: string | null;
|
|
87
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
88
|
+
profile: string | null;
|
|
89
|
+
profileFor: (file: File) => string | null | undefined;
|
|
87
90
|
processingConfig: {
|
|
88
91
|
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
89
92
|
useFilenameAsAssetId?: boolean;
|
|
90
93
|
enableTranscription?: boolean;
|
|
91
94
|
};
|
|
92
95
|
collectionName: string | null;
|
|
93
|
-
profile: string | null;
|
|
94
|
-
backend: string | null;
|
|
95
|
-
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
96
96
|
serviceBody: Record<string, unknown> | null;
|
|
97
|
-
|
|
97
|
+
accept: readonly string[] | string;
|
|
98
|
+
max: number | null;
|
|
99
|
+
maxSize: number | null;
|
|
100
|
+
removeLabel: string;
|
|
98
101
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
99
102
|
hint: string;
|
|
100
103
|
picker: boolean;
|
|
@@ -3,6 +3,7 @@ import { computed, nextTick, ref, watch } from "vue";
|
|
|
3
3
|
import { useMLMediaGallery } from "../../composables/useMLMediaGallery";
|
|
4
4
|
import { useMLFilePicker } from "../../composables/useMLFilePicker";
|
|
5
5
|
import { useMLMediaEditor } from "../../composables/useMLMediaEditor";
|
|
6
|
+
import { useMLMediaMeta } from "../../composables/useMLMediaMeta";
|
|
6
7
|
import { assetName } from "../../utils/assetFormat";
|
|
7
8
|
import { assetThumbnailUrl } from "../../utils/assetThumbnail";
|
|
8
9
|
import { acceptForKinds, partitionFiles } from "../../utils/fileAccept";
|
|
@@ -37,7 +38,7 @@ const props = defineProps({
|
|
|
37
38
|
});
|
|
38
39
|
const model = defineModel({ type: Array, ...{ default: () => [] } });
|
|
39
40
|
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
40
|
-
const emit = defineEmits(["uploaded"]);
|
|
41
|
+
const emit = defineEmits(["uploaded", "meta"]);
|
|
41
42
|
const gallery = useMLMediaGallery(model, {
|
|
42
43
|
accept: () => props.accept,
|
|
43
44
|
max: () => props.max,
|
|
@@ -54,6 +55,11 @@ const gallery = useMLMediaGallery(model, {
|
|
|
54
55
|
onAdd: (id, tag) => props.onAdd?.(id, tag)
|
|
55
56
|
});
|
|
56
57
|
const { items, message, full, replaces } = gallery;
|
|
58
|
+
const metaById = useMLMediaMeta(model, (id) => gallery.asset(id));
|
|
59
|
+
watch(() => JSON.stringify(metaById.value), (now, before) => {
|
|
60
|
+
if (before === void 0 && now === "{}") return;
|
|
61
|
+
emit("meta", metaById.value);
|
|
62
|
+
}, { immediate: true });
|
|
57
63
|
const removeLabel = computed(() => props.removeLabel ?? (props.recordName ? `Remove from ${props.recordName}` : "Remove"));
|
|
58
64
|
const showAddTile = computed(() => !props.disabled && !full.value && !(replaces.value && items.value.length > 0));
|
|
59
65
|
const countLabel = computed(() => {
|