nuxt-media-library 0.0.51 → 0.0.53
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 +57 -0
- package/dist/module.d.mts +6 -0
- package/dist/module.d.ts +6 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +20 -1
- package/dist/runtime/components/MLAssetManager/Processing.vue +3 -34
- package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/Field.d.vue.ts +17 -4
- package/dist/runtime/components/MLMedia/Field.vue +19 -2
- package/dist/runtime/components/MLMedia/Field.vue.d.ts +17 -4
- package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +2 -0
- package/dist/runtime/components/MLMedia/FilePicker.vue +3 -1
- package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +2 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +3 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue +6 -1
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +3 -0
- package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +15 -5
- package/dist/runtime/components/MLMedia/Gallery.vue +9 -3
- package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +15 -5
- package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +11 -5
- package/dist/runtime/components/MLMedia/GallerySection.vue +21 -3
- package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +11 -5
- 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 +2 -2
- package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +2 -2
- 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 +1 -1
- package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +1 -1
- package/dist/runtime/composables/useMLFilePicker.d.ts +2 -0
- package/dist/runtime/composables/useMLMediaField.d.ts +6 -2
- package/dist/runtime/composables/useMLMediaField.js +1 -1
- package/dist/runtime/composables/useMLMediaGallery.d.ts +5 -1
- package/dist/runtime/composables/useMLMediaGallery.js +2 -2
- package/dist/runtime/composables/useMLMediaUpload.d.ts +21 -0
- package/dist/runtime/composables/useMLMediaUpload.js +41 -12
- package/dist/runtime/utils/uploader/serviceBackends.d.ts +26 -0
- package/dist/runtime/utils/uploader/serviceBackends.js +18 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -208,6 +208,63 @@ await $fetch(`/api/videos/lessons/${lessonId}/video`, { method: 'PUT', body: { s
|
|
|
208
208
|
grant mode replaces the browser's grant with its own, and the service's
|
|
209
209
|
`claim_upload` rejects the session.
|
|
210
210
|
|
|
211
|
+
#### One layer per service, widgets by backend name
|
|
212
|
+
|
|
213
|
+
Each service can ship a Nuxt layer that registers its upload backend and
|
|
214
|
+
serves its own routes (start, refresh, attach, data) to its service. Nuxt
|
|
215
|
+
merges the layers, so every widget on the site can use any backend by name.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// itq-videos-layer/nuxt.config.ts
|
|
219
|
+
export default defineNuxtConfig({
|
|
220
|
+
modules: ['nuxt-media-library'],
|
|
221
|
+
mediaLibrary: {
|
|
222
|
+
serviceUploads: {
|
|
223
|
+
backends: { videos: { start: '/api/videos/uploads' } },
|
|
224
|
+
// refresh defaults to `${start}/:sessionId/grant`
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
})
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// the site
|
|
232
|
+
export default defineNuxtConfig({
|
|
233
|
+
extends: ['itq-videos-layer', 'itq-courses-layer'],
|
|
234
|
+
modules: ['nuxt-media-library'],
|
|
235
|
+
mediaLibrary: { serviceUploads: { proxy: true } }, // where parts go, for every backend
|
|
236
|
+
})
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```vue
|
|
240
|
+
<MLMediaGallery
|
|
241
|
+
v-model="lesson.mediaIds"
|
|
242
|
+
service="videos"
|
|
243
|
+
profile="lesson-video"
|
|
244
|
+
:service-body="{ context: { lessonId } }"
|
|
245
|
+
@uploaded="(assetId, role, sessionId) => attach(assetId, sessionId)"
|
|
246
|
+
/>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
- `service` on `MLMediaGallery`, `MLMediaField` and the file picker (and
|
|
250
|
+
`useMLMediaUpload`): a backend name, or `{ start }` for a one-off. Without
|
|
251
|
+
it, widgets upload through Nuxt's own route as before; an unknown name
|
|
252
|
+
fails that upload with a message naming the missing backend.
|
|
253
|
+
- The start request carries the file (`mimeType`, `sizeBytes`, `filename`),
|
|
254
|
+
`profile` (each role's, in a gallery with roles) and `service-body`. The
|
|
255
|
+
service's profile decides kind, visibility and processing, so
|
|
256
|
+
`visibility`, `processing-config` and `collection-name` are not used with
|
|
257
|
+
`service`.
|
|
258
|
+
- `uploaded` also gives the upload's `sessionId` (gallery:
|
|
259
|
+
`(assetId, role, sessionId)`, field: `(assetId, sessionId)`), including
|
|
260
|
+
files uploaded from the picker and chosen. Send both to the service's
|
|
261
|
+
attach endpoint: its `claim_upload` needs them.
|
|
262
|
+
- Processing progress uses `/api/media-library/manage/assets/resolve` and
|
|
263
|
+
the processing stream, so the app's proxy must allow `read` on a file the
|
|
264
|
+
user just uploaded.
|
|
265
|
+
- There is no default backend: each layer's widgets name theirs. The module
|
|
266
|
+
only stores the paths; each layer's own routes reach its service.
|
|
267
|
+
|
|
211
268
|
### Vue installation (non-Nuxt)
|
|
212
269
|
|
|
213
270
|
```bash
|
package/dist/module.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
|
|
2
3
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
3
4
|
export * from '../dist/runtime/types/uploader.js';
|
|
4
5
|
export * from '../dist/runtime/types/preview.js';
|
|
@@ -41,10 +42,15 @@ interface ModuleOptions {
|
|
|
41
42
|
* `baseURL` is where the browser sends those uploads (the media library's
|
|
42
43
|
* public API root with `/v1`, or a proxy route); with `proxy` it defaults
|
|
43
44
|
* to the pass-through route.
|
|
45
|
+
*
|
|
46
|
+
* `backends` names the services that start uploads, so widgets can use
|
|
47
|
+
* `service="videos"`. Each service's Nuxt layer registers its own; Nuxt
|
|
48
|
+
* merges them across layers. Paths only: each layer serves its routes.
|
|
44
49
|
*/
|
|
45
50
|
serviceUploads?: {
|
|
46
51
|
proxy?: boolean;
|
|
47
52
|
baseURL?: string;
|
|
53
|
+
backends?: Record<string, ServiceUploadBackend>;
|
|
48
54
|
};
|
|
49
55
|
}
|
|
50
56
|
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
package/dist/module.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
|
|
2
3
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
3
4
|
export * from '../dist/runtime/types/uploader.js';
|
|
4
5
|
export * from '../dist/runtime/types/preview.js';
|
|
@@ -41,10 +42,15 @@ interface ModuleOptions {
|
|
|
41
42
|
* `baseURL` is where the browser sends those uploads (the media library's
|
|
42
43
|
* public API root with `/v1`, or a proxy route); with `proxy` it defaults
|
|
43
44
|
* to the pass-through route.
|
|
45
|
+
*
|
|
46
|
+
* `backends` names the services that start uploads, so widgets can use
|
|
47
|
+
* `service="videos"`. Each service's Nuxt layer registers its own; Nuxt
|
|
48
|
+
* merges them across layers. Paths only: each layer serves its routes.
|
|
44
49
|
*/
|
|
45
50
|
serviceUploads?: {
|
|
46
51
|
proxy?: boolean;
|
|
47
52
|
baseURL?: string;
|
|
53
|
+
backends?: Record<string, ServiceUploadBackend>;
|
|
48
54
|
};
|
|
49
55
|
}
|
|
50
56
|
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -20,6 +20,23 @@ function browserBaseURL(existingPublic, publicBaseURL) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
23
|
+
function serviceBackends(backends, logger) {
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [name, backend] of Object.entries(backends ?? {})) {
|
|
26
|
+
if (!backend || typeof backend.start !== "string" || !backend.start) {
|
|
27
|
+
logger.warn(`mediaLibrary.serviceUploads.backends.${name} has no start path; ignored.`);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (backend.refresh && !backend.refresh.includes(":sessionId")) {
|
|
31
|
+
logger.warn(`mediaLibrary.serviceUploads.backends.${name}.refresh needs a :sessionId placeholder; using the default.`);
|
|
32
|
+
}
|
|
33
|
+
out[name] = {
|
|
34
|
+
start: backend.start,
|
|
35
|
+
...backend.refresh?.includes(":sessionId") ? { refresh: backend.refresh } : {}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
23
40
|
const module$1 = defineNuxtModule({
|
|
24
41
|
meta: {
|
|
25
42
|
name: "media-library",
|
|
@@ -145,7 +162,9 @@ const module$1 = defineNuxtModule({
|
|
|
145
162
|
},
|
|
146
163
|
headers: publicHeaders.headers,
|
|
147
164
|
// Where useMLServiceUpload sends parts (NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BASE_URL).
|
|
148
|
-
serviceUploadBaseURL: existingPublic.serviceUploadBaseURL || options.serviceUploads?.baseURL || (options.serviceUploads?.proxy ? SERVICE_UPLOADS_ROUTE : "")
|
|
165
|
+
serviceUploadBaseURL: existingPublic.serviceUploadBaseURL || options.serviceUploads?.baseURL || (options.serviceUploads?.proxy ? SERVICE_UPLOADS_ROUTE : ""),
|
|
166
|
+
// Names widgets can pass as `service` (NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BACKENDS).
|
|
167
|
+
serviceUploadBackends: { ...serviceBackends(options.serviceUploads?.backends, logger), ...existingPublic.serviceUploadBackends }
|
|
149
168
|
};
|
|
150
169
|
addPlugin({
|
|
151
170
|
src: resolver.resolve("./runtime/plugins/config")
|
|
@@ -1,33 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, onMounted, watch } from "vue";
|
|
3
|
-
import { useMLResourcePolicy } from "../../composables/resourcePolicy/useMLResourcePolicy";
|
|
4
3
|
import { mediaGrantHeaders } from "../../composables/useMLMediaGrants";
|
|
5
4
|
const props = defineProps({
|
|
6
5
|
assetId: { type: String, required: true },
|
|
7
6
|
manager: { type: Object, required: true }
|
|
8
7
|
});
|
|
9
|
-
const resourcePolicy = useMLResourcePolicy();
|
|
10
|
-
const policy = ref(null);
|
|
11
|
-
const policyLoading = ref(false);
|
|
12
8
|
const videoUrl = ref(null);
|
|
13
9
|
const asset = computed(() => props.manager.selectedAsset.value);
|
|
14
|
-
const policyId = computed(
|
|
15
|
-
() => asset.value?.resourcePolicyId ?? asset.value?.resource_policy_id ?? null
|
|
16
|
-
);
|
|
17
|
-
async function loadPolicy() {
|
|
18
|
-
if (!policyId.value) {
|
|
19
|
-
policy.value = null;
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
policyLoading.value = true;
|
|
23
|
-
try {
|
|
24
|
-
policy.value = await resourcePolicy.get(policyId.value);
|
|
25
|
-
} catch {
|
|
26
|
-
policy.value = null;
|
|
27
|
-
} finally {
|
|
28
|
-
policyLoading.value = false;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
10
|
async function resolveVideoUrl() {
|
|
32
11
|
if (!asset.value || asset.value.kind !== "video") {
|
|
33
12
|
videoUrl.value = null;
|
|
@@ -43,17 +22,9 @@ async function resolveVideoUrl() {
|
|
|
43
22
|
videoUrl.value = null;
|
|
44
23
|
}
|
|
45
24
|
}
|
|
46
|
-
onMounted(
|
|
47
|
-
loadPolicy();
|
|
48
|
-
resolveVideoUrl();
|
|
49
|
-
});
|
|
50
|
-
watch(policyId, loadPolicy);
|
|
25
|
+
onMounted(resolveVideoUrl);
|
|
51
26
|
watch(() => props.assetId, resolveVideoUrl);
|
|
52
|
-
const showStepper = computed(() =>
|
|
53
|
-
if (policyLoading.value) return false;
|
|
54
|
-
if (!policy.value) return !policyId.value;
|
|
55
|
-
return policy.value.processing_config?.auto_process === false;
|
|
56
|
-
});
|
|
27
|
+
const showStepper = computed(() => asset.value?.status === "uploaded");
|
|
57
28
|
function onComplete() {
|
|
58
29
|
props.manager.refresh(props.assetId);
|
|
59
30
|
}
|
|
@@ -64,10 +35,8 @@ function onFailed() {
|
|
|
64
35
|
|
|
65
36
|
<template>
|
|
66
37
|
<div>
|
|
67
|
-
<USkeleton v-if="policyLoading" class="h-40 w-full" />
|
|
68
|
-
|
|
69
38
|
<MLProcessingStepper
|
|
70
|
-
v-
|
|
39
|
+
v-if="showStepper"
|
|
71
40
|
:asset-id="assetId"
|
|
72
41
|
:asset-kind="asset?.kind"
|
|
73
42
|
:download-url="videoUrl"
|
|
@@ -39,11 +39,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
39
39
|
onFiles?: ((files: File[]) => any) | undefined;
|
|
40
40
|
onRejected?: ((files: RejectedFile[]) => any) | undefined;
|
|
41
41
|
}>, {
|
|
42
|
+
title: string;
|
|
42
43
|
variant: "area" | "overlay";
|
|
43
44
|
compact: boolean;
|
|
44
45
|
disabled: boolean;
|
|
45
46
|
multiple: boolean;
|
|
46
|
-
title: string;
|
|
47
47
|
accept: string;
|
|
48
48
|
maxSize: number | null;
|
|
49
49
|
maxFiles: number | null;
|
|
@@ -39,11 +39,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
|
|
|
39
39
|
onFiles?: ((files: File[]) => any) | undefined;
|
|
40
40
|
onRejected?: ((files: RejectedFile[]) => any) | undefined;
|
|
41
41
|
}>, {
|
|
42
|
+
title: string;
|
|
42
43
|
variant: "area" | "overlay";
|
|
43
44
|
compact: boolean;
|
|
44
45
|
disabled: boolean;
|
|
45
46
|
multiple: boolean;
|
|
46
|
-
title: string;
|
|
47
47
|
accept: string;
|
|
48
48
|
maxSize: number | null;
|
|
49
49
|
maxFiles: number | null;
|
|
@@ -15,6 +15,16 @@ type __VLS_Props = {
|
|
|
15
15
|
visibility?: MediaUploadOptions['visibility'];
|
|
16
16
|
processingConfig?: MediaUploadOptions['processingConfig'];
|
|
17
17
|
collectionName?: string | null;
|
|
18
|
+
/** Upload profile name for new files. */
|
|
19
|
+
profile?: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Start uploads at a backend service instead of Nuxt's own route: a name
|
|
22
|
+
* registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
|
|
23
|
+
* or `{ start }`. See useMLMediaUpload.
|
|
24
|
+
*/
|
|
25
|
+
service?: MediaUploadOptions['service'];
|
|
26
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
27
|
+
serviceBody?: Record<string, unknown> | null;
|
|
18
28
|
/** Show "Select existing", which opens the library picker. */
|
|
19
29
|
picker?: boolean;
|
|
20
30
|
/** Replaces the built-in picker, e.g. with the host's own dialog. */
|
|
@@ -33,17 +43,17 @@ type __VLS_ModelProps = {
|
|
|
33
43
|
};
|
|
34
44
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
35
45
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
36
|
-
uploaded: (assetId: string) => any;
|
|
46
|
+
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
37
47
|
"update:modelValue": (value: string | null) => any;
|
|
38
48
|
"update:alt": (value: string | undefined) => any;
|
|
39
49
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
40
|
-
onUploaded?: ((assetId: string) => any) | undefined;
|
|
50
|
+
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
41
51
|
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
42
52
|
"onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
|
|
43
53
|
}>, {
|
|
44
|
-
|
|
45
|
-
visibility: "private" | "public" | "restricted";
|
|
54
|
+
visibility: "private" | "restricted" | "public";
|
|
46
55
|
label: string;
|
|
56
|
+
disabled: boolean;
|
|
47
57
|
accept: readonly string[] | string;
|
|
48
58
|
maxSize: number | null;
|
|
49
59
|
required: boolean;
|
|
@@ -54,6 +64,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
54
64
|
enableTranscription?: boolean;
|
|
55
65
|
};
|
|
56
66
|
collectionName: string | null;
|
|
67
|
+
profile: string | null;
|
|
68
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
69
|
+
serviceBody: Record<string, unknown> | null;
|
|
57
70
|
hint: string;
|
|
58
71
|
missingAlt: boolean;
|
|
59
72
|
picker: boolean;
|
|
@@ -15,6 +15,9 @@ const props = defineProps({
|
|
|
15
15
|
visibility: { type: String, required: false, default: void 0 },
|
|
16
16
|
processingConfig: { type: Object, required: false, default: void 0 },
|
|
17
17
|
collectionName: { type: [String, null], required: false, default: null },
|
|
18
|
+
profile: { type: [String, null], required: false, default: null },
|
|
19
|
+
service: { type: null, required: false, default: void 0 },
|
|
20
|
+
serviceBody: { type: [Object, null], required: false, default: null },
|
|
18
21
|
picker: { type: Boolean, required: false, default: true },
|
|
19
22
|
onPick: { type: Function, required: false, default: void 0 },
|
|
20
23
|
editor: { type: Boolean, required: false, default: true },
|
|
@@ -30,7 +33,10 @@ const field = useMLMediaField(model, {
|
|
|
30
33
|
visibility: props.visibility,
|
|
31
34
|
processingConfig: props.processingConfig,
|
|
32
35
|
collectionName: props.collectionName,
|
|
33
|
-
|
|
36
|
+
profile: props.profile,
|
|
37
|
+
service: props.service,
|
|
38
|
+
serviceBody: props.serviceBody,
|
|
39
|
+
onUploaded: (id, sessionId) => emit("uploaded", id, sessionId)
|
|
34
40
|
});
|
|
35
41
|
const { state, asset, upload, message } = field;
|
|
36
42
|
const helpText = computed(() => {
|
|
@@ -65,19 +71,30 @@ const veilPhase = computed(() => {
|
|
|
65
71
|
});
|
|
66
72
|
const filePicker = useMLFilePicker();
|
|
67
73
|
const canPick = computed(() => !!props.onPick || props.picker);
|
|
74
|
+
const uploadOptions = computed(() => ({
|
|
75
|
+
visibility: props.visibility,
|
|
76
|
+
processingConfig: props.processingConfig,
|
|
77
|
+
collectionName: props.collectionName,
|
|
78
|
+
profile: props.profile,
|
|
79
|
+
service: props.service,
|
|
80
|
+
serviceBody: props.serviceBody
|
|
81
|
+
}));
|
|
68
82
|
async function pickExisting() {
|
|
69
83
|
if (props.onPick) return props.onPick();
|
|
84
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
70
85
|
const ids = await filePicker.open({
|
|
71
86
|
accept: props.accept,
|
|
72
87
|
max: 1,
|
|
73
88
|
selected: [model.value],
|
|
74
89
|
title: props.label ? `Select ${props.label.toLowerCase()}` : "Select file",
|
|
75
90
|
maxSize: props.maxSize,
|
|
76
|
-
upload:
|
|
91
|
+
upload: uploadOptions.value,
|
|
92
|
+
onUploaded: (id, sessionId) => sessions.set(id, sessionId)
|
|
77
93
|
});
|
|
78
94
|
if (ids) {
|
|
79
95
|
field.clearMessage();
|
|
80
96
|
model.value = ids[0] ?? null;
|
|
97
|
+
if (model.value && sessions.has(model.value)) emit("uploaded", model.value, sessions.get(model.value) ?? null);
|
|
81
98
|
}
|
|
82
99
|
}
|
|
83
100
|
const mediaEditor = useMLMediaEditor();
|
|
@@ -15,6 +15,16 @@ type __VLS_Props = {
|
|
|
15
15
|
visibility?: MediaUploadOptions['visibility'];
|
|
16
16
|
processingConfig?: MediaUploadOptions['processingConfig'];
|
|
17
17
|
collectionName?: string | null;
|
|
18
|
+
/** Upload profile name for new files. */
|
|
19
|
+
profile?: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Start uploads at a backend service instead of Nuxt's own route: a name
|
|
22
|
+
* registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
|
|
23
|
+
* or `{ start }`. See useMLMediaUpload.
|
|
24
|
+
*/
|
|
25
|
+
service?: MediaUploadOptions['service'];
|
|
26
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
27
|
+
serviceBody?: Record<string, unknown> | null;
|
|
18
28
|
/** Show "Select existing", which opens the library picker. */
|
|
19
29
|
picker?: boolean;
|
|
20
30
|
/** Replaces the built-in picker, e.g. with the host's own dialog. */
|
|
@@ -33,17 +43,17 @@ type __VLS_ModelProps = {
|
|
|
33
43
|
};
|
|
34
44
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
35
45
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
36
|
-
uploaded: (assetId: string) => any;
|
|
46
|
+
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
37
47
|
"update:modelValue": (value: string | null) => any;
|
|
38
48
|
"update:alt": (value: string | undefined) => any;
|
|
39
49
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
40
|
-
onUploaded?: ((assetId: string) => any) | undefined;
|
|
50
|
+
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
41
51
|
"onUpdate:modelValue"?: ((value: string | null) => any) | undefined;
|
|
42
52
|
"onUpdate:alt"?: ((value: string | undefined) => any) | undefined;
|
|
43
53
|
}>, {
|
|
44
|
-
|
|
45
|
-
visibility: "private" | "public" | "restricted";
|
|
54
|
+
visibility: "private" | "restricted" | "public";
|
|
46
55
|
label: string;
|
|
56
|
+
disabled: boolean;
|
|
47
57
|
accept: readonly string[] | string;
|
|
48
58
|
maxSize: number | null;
|
|
49
59
|
required: boolean;
|
|
@@ -54,6 +64,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
54
64
|
enableTranscription?: boolean;
|
|
55
65
|
};
|
|
56
66
|
collectionName: string | null;
|
|
67
|
+
profile: string | null;
|
|
68
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
69
|
+
serviceBody: Record<string, unknown> | null;
|
|
57
70
|
hint: string;
|
|
58
71
|
missingAlt: boolean;
|
|
59
72
|
picker: boolean;
|
|
@@ -6,6 +6,7 @@ type __VLS_Props = {
|
|
|
6
6
|
title?: string;
|
|
7
7
|
maxSize?: number | null;
|
|
8
8
|
upload?: MediaUploadOptions;
|
|
9
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
9
10
|
};
|
|
10
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
12
|
close: (ids: string[] | null) => any;
|
|
@@ -18,6 +19,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
18
19
|
selected: readonly string[];
|
|
19
20
|
maxSize: number | null;
|
|
20
21
|
upload: MediaUploadOptions;
|
|
22
|
+
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
21
23
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
24
|
declare const _default: typeof __VLS_export;
|
|
23
25
|
export default _default;
|
|
@@ -5,7 +5,8 @@ defineProps({
|
|
|
5
5
|
selected: { type: Array, required: false, default: () => [] },
|
|
6
6
|
title: { type: String, required: false, default: "Select file" },
|
|
7
7
|
maxSize: { type: [Number, null], required: false, default: null },
|
|
8
|
-
upload: { type: Object, required: false, default: () => ({}) }
|
|
8
|
+
upload: { type: Object, required: false, default: () => ({}) },
|
|
9
|
+
onUploaded: { type: Function, required: false, default: void 0 }
|
|
9
10
|
});
|
|
10
11
|
const emit = defineEmits(["close"]);
|
|
11
12
|
</script>
|
|
@@ -25,6 +26,7 @@ const emit = defineEmits(["close"]);
|
|
|
25
26
|
:selected="selected"
|
|
26
27
|
:max-size="maxSize"
|
|
27
28
|
:upload="upload"
|
|
29
|
+
:on-uploaded="onUploaded"
|
|
28
30
|
@done="(ids) => emit('close', ids)"
|
|
29
31
|
@cancel="emit('close', null)"
|
|
30
32
|
/>
|
|
@@ -6,6 +6,7 @@ type __VLS_Props = {
|
|
|
6
6
|
title?: string;
|
|
7
7
|
maxSize?: number | null;
|
|
8
8
|
upload?: MediaUploadOptions;
|
|
9
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
9
10
|
};
|
|
10
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
11
12
|
close: (ids: string[] | null) => any;
|
|
@@ -18,6 +19,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
18
19
|
selected: readonly string[];
|
|
19
20
|
maxSize: number | null;
|
|
20
21
|
upload: MediaUploadOptions;
|
|
22
|
+
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
21
23
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
22
24
|
declare const _default: typeof __VLS_export;
|
|
23
25
|
export default _default;
|
|
@@ -11,6 +11,8 @@ type __VLS_Props = {
|
|
|
11
11
|
maxSize?: number | null;
|
|
12
12
|
/** Upload session settings for files dropped into the picker. */
|
|
13
13
|
upload?: MediaUploadOptions;
|
|
14
|
+
/** A file uploaded from the picker exists in the library (with its upload session). */
|
|
15
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
14
16
|
/** Grid rows for a fixed-height body; the list scrolls inside it. */
|
|
15
17
|
bodyClass?: string;
|
|
16
18
|
};
|
|
@@ -27,6 +29,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
27
29
|
selected: readonly string[];
|
|
28
30
|
maxSize: number | null;
|
|
29
31
|
upload: MediaUploadOptions;
|
|
32
|
+
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
30
33
|
bodyClass: string;
|
|
31
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
32
35
|
declare const _default: typeof __VLS_export;
|
|
@@ -15,6 +15,7 @@ const props = defineProps({
|
|
|
15
15
|
title: { type: String, required: false, default: void 0 },
|
|
16
16
|
maxSize: { type: [Number, null], required: false, default: null },
|
|
17
17
|
upload: { type: Object, required: false, default: () => ({}) },
|
|
18
|
+
onUploaded: { type: Function, required: false, default: void 0 },
|
|
18
19
|
bodyClass: { type: String, required: false, default: "max-h-[min(60vh,560px)]" }
|
|
19
20
|
});
|
|
20
21
|
const emit = defineEmits(["done", "cancel"]);
|
|
@@ -69,7 +70,11 @@ function onFiles(files) {
|
|
|
69
70
|
rejectNote.value = null;
|
|
70
71
|
for (const file of files) {
|
|
71
72
|
const u = uploader.start(file);
|
|
72
|
-
void u.created.then((id) =>
|
|
73
|
+
void u.created.then((id) => {
|
|
74
|
+
if (!id) return;
|
|
75
|
+
props.onUploaded?.(id, u.sessionId);
|
|
76
|
+
if (!isChosen(id)) choose(id);
|
|
77
|
+
});
|
|
73
78
|
}
|
|
74
79
|
}
|
|
75
80
|
function onRejected(rejected) {
|
|
@@ -11,6 +11,8 @@ type __VLS_Props = {
|
|
|
11
11
|
maxSize?: number | null;
|
|
12
12
|
/** Upload session settings for files dropped into the picker. */
|
|
13
13
|
upload?: MediaUploadOptions;
|
|
14
|
+
/** A file uploaded from the picker exists in the library (with its upload session). */
|
|
15
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
14
16
|
/** Grid rows for a fixed-height body; the list scrolls inside it. */
|
|
15
17
|
bodyClass?: string;
|
|
16
18
|
};
|
|
@@ -27,6 +29,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
27
29
|
selected: readonly string[];
|
|
28
30
|
maxSize: number | null;
|
|
29
31
|
upload: MediaUploadOptions;
|
|
32
|
+
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
30
33
|
bodyClass: string;
|
|
31
34
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
32
35
|
declare const _default: typeof __VLS_export;
|
|
@@ -30,6 +30,14 @@ type __VLS_Props = {
|
|
|
30
30
|
collectionName?: string | null;
|
|
31
31
|
/** Upload profile for the single list (with `profiles`, each role has its own). */
|
|
32
32
|
profile?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Start uploads at a backend service instead of Nuxt's own route: a name
|
|
35
|
+
* registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
|
|
36
|
+
* or `{ start }`. Each role's profile is sent with the start request.
|
|
37
|
+
*/
|
|
38
|
+
service?: MediaUploadOptions['service'];
|
|
39
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
40
|
+
serviceBody?: Record<string, unknown> | null;
|
|
33
41
|
picker?: boolean;
|
|
34
42
|
onPick?: () => void;
|
|
35
43
|
editor?: boolean;
|
|
@@ -41,20 +49,19 @@ type __VLS_ModelProps = {
|
|
|
41
49
|
};
|
|
42
50
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
43
51
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
44
|
-
uploaded: (assetId: string, role: string) => any;
|
|
52
|
+
uploaded: (assetId: string, role: string, sessionId: string | null) => any;
|
|
45
53
|
"update:modelValue": (value: string[]) => any;
|
|
46
54
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
47
55
|
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
48
56
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
49
|
-
onUploaded?: ((assetId: string, role: string) => any) | undefined;
|
|
57
|
+
onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
|
|
50
58
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
51
59
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
52
60
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
53
61
|
}>, {
|
|
54
|
-
|
|
62
|
+
visibility: "private" | "restricted" | "public";
|
|
55
63
|
title: string;
|
|
56
|
-
|
|
57
|
-
profile: string | null;
|
|
64
|
+
disabled: boolean;
|
|
58
65
|
accept: readonly string[] | string;
|
|
59
66
|
max: number | null;
|
|
60
67
|
maxSize: number | null;
|
|
@@ -64,6 +71,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
64
71
|
enableTranscription?: boolean;
|
|
65
72
|
};
|
|
66
73
|
collectionName: string | null;
|
|
74
|
+
profile: string | null;
|
|
75
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
76
|
+
serviceBody: Record<string, unknown> | null;
|
|
67
77
|
hint: string;
|
|
68
78
|
picker: boolean;
|
|
69
79
|
onPick: () => void;
|
|
@@ -17,6 +17,8 @@ const props = defineProps({
|
|
|
17
17
|
processingConfig: { type: Object, required: false, default: void 0 },
|
|
18
18
|
collectionName: { type: [String, null], required: false, default: null },
|
|
19
19
|
profile: { type: [String, null], required: false, default: null },
|
|
20
|
+
service: { type: null, required: false, default: void 0 },
|
|
21
|
+
serviceBody: { type: [Object, null], required: false, default: null },
|
|
20
22
|
picker: { type: Boolean, required: false, default: true },
|
|
21
23
|
onPick: { type: Function, required: false, default: void 0 },
|
|
22
24
|
editor: { type: Boolean, required: false, default: true },
|
|
@@ -101,8 +103,8 @@ function onAdd(id, tag) {
|
|
|
101
103
|
if (union.value.includes(id)) setRole(id, key, true);
|
|
102
104
|
else pendingRole.set(id, key);
|
|
103
105
|
}
|
|
104
|
-
function onUploaded(id) {
|
|
105
|
-
emit("uploaded", id, uploadedRole.get(id) ?? defaultKey.value);
|
|
106
|
+
function onUploaded(id, sessionId) {
|
|
107
|
+
emit("uploaded", id, uploadedRole.get(id) ?? defaultKey.value, sessionId);
|
|
106
108
|
uploadedRole.delete(id);
|
|
107
109
|
}
|
|
108
110
|
const badges = (id) => roles.value.filter((r) => sel(r.key).includes(id)).map((r) => r.label);
|
|
@@ -171,6 +173,8 @@ function chipCount(key, max) {
|
|
|
171
173
|
:accept="defaultRole?.accept ?? accept"
|
|
172
174
|
:max-size="defaultRole?.maxSize ?? maxSize"
|
|
173
175
|
:profile="defaultRole?.profile ?? null"
|
|
176
|
+
:service="service"
|
|
177
|
+
:service-body="serviceBody"
|
|
174
178
|
:disabled="disabled"
|
|
175
179
|
:featured="featured"
|
|
176
180
|
:record-name="recordName"
|
|
@@ -247,10 +251,12 @@ function chipCount(key, max) {
|
|
|
247
251
|
:processing-config="processingConfig"
|
|
248
252
|
:collection-name="collectionName"
|
|
249
253
|
:profile="profile"
|
|
254
|
+
:service="service"
|
|
255
|
+
:service-body="serviceBody"
|
|
250
256
|
:picker="picker"
|
|
251
257
|
:on-pick="onPick"
|
|
252
258
|
:editor="editor"
|
|
253
259
|
:on-open="onOpen"
|
|
254
|
-
@uploaded="(id) => emit('uploaded', id, '')"
|
|
260
|
+
@uploaded="(id, sessionId) => emit('uploaded', id, '', sessionId)"
|
|
255
261
|
/>
|
|
256
262
|
</template>
|
|
@@ -30,6 +30,14 @@ type __VLS_Props = {
|
|
|
30
30
|
collectionName?: string | null;
|
|
31
31
|
/** Upload profile for the single list (with `profiles`, each role has its own). */
|
|
32
32
|
profile?: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Start uploads at a backend service instead of Nuxt's own route: a name
|
|
35
|
+
* registered under `mediaLibrary.serviceUploads.backends` (e.g. "videos"),
|
|
36
|
+
* or `{ start }`. Each role's profile is sent with the start request.
|
|
37
|
+
*/
|
|
38
|
+
service?: MediaUploadOptions['service'];
|
|
39
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
40
|
+
serviceBody?: Record<string, unknown> | null;
|
|
33
41
|
picker?: boolean;
|
|
34
42
|
onPick?: () => void;
|
|
35
43
|
editor?: boolean;
|
|
@@ -41,20 +49,19 @@ type __VLS_ModelProps = {
|
|
|
41
49
|
};
|
|
42
50
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
43
51
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
44
|
-
uploaded: (assetId: string, role: string) => any;
|
|
52
|
+
uploaded: (assetId: string, role: string, sessionId: string | null) => any;
|
|
45
53
|
"update:modelValue": (value: string[]) => any;
|
|
46
54
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
47
55
|
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
48
56
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
49
|
-
onUploaded?: ((assetId: string, role: string) => any) | undefined;
|
|
57
|
+
onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
|
|
50
58
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
51
59
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
52
60
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
53
61
|
}>, {
|
|
54
|
-
|
|
62
|
+
visibility: "private" | "restricted" | "public";
|
|
55
63
|
title: string;
|
|
56
|
-
|
|
57
|
-
profile: string | null;
|
|
64
|
+
disabled: boolean;
|
|
58
65
|
accept: readonly string[] | string;
|
|
59
66
|
max: number | null;
|
|
60
67
|
maxSize: number | null;
|
|
@@ -64,6 +71,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
64
71
|
enableTranscription?: boolean;
|
|
65
72
|
};
|
|
66
73
|
collectionName: string | null;
|
|
74
|
+
profile: string | null;
|
|
75
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
76
|
+
serviceBody: Record<string, unknown> | null;
|
|
67
77
|
hint: string;
|
|
68
78
|
picker: boolean;
|
|
69
79
|
onPick: () => void;
|
|
@@ -26,6 +26,10 @@ type __VLS_Props = {
|
|
|
26
26
|
onOpen?: (assetId: string) => void;
|
|
27
27
|
/** Upload profile name for new files in this list. */
|
|
28
28
|
profile?: string | null;
|
|
29
|
+
/** Start uploads at a backend service (name or `{ start }`). See useMLMediaUpload. */
|
|
30
|
+
service?: MediaUploadOptions['service'];
|
|
31
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
32
|
+
serviceBody?: Record<string, unknown> | null;
|
|
29
33
|
/** Overrides the "Remove from …" label. */
|
|
30
34
|
removeLabel?: string;
|
|
31
35
|
/** Labels shown on a tile, e.g. its roles. */
|
|
@@ -58,19 +62,18 @@ type __VLS_Slots = {} & {
|
|
|
58
62
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
59
63
|
uploadFiles: typeof uploadFiles;
|
|
60
64
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
61
|
-
uploaded: (assetId: string) => any;
|
|
65
|
+
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
62
66
|
"update:modelValue": (value: string[]) => any;
|
|
63
67
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
64
68
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
65
|
-
onUploaded?: ((assetId: string) => any) | undefined;
|
|
69
|
+
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
66
70
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
67
71
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
68
72
|
}>, {
|
|
73
|
+
visibility: "private" | "restricted" | "public";
|
|
74
|
+
title: string;
|
|
69
75
|
disabled: boolean;
|
|
70
76
|
highlight: ReadonlySet<string> | null;
|
|
71
|
-
title: string;
|
|
72
|
-
visibility: "private" | "public" | "restricted";
|
|
73
|
-
profile: string | null;
|
|
74
77
|
accept: readonly string[] | string;
|
|
75
78
|
max: number | null;
|
|
76
79
|
maxSize: number | null;
|
|
@@ -81,6 +84,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
81
84
|
enableTranscription?: boolean;
|
|
82
85
|
};
|
|
83
86
|
collectionName: string | null;
|
|
87
|
+
profile: string | null;
|
|
88
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
89
|
+
serviceBody: Record<string, unknown> | null;
|
|
84
90
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
85
91
|
hint: string;
|
|
86
92
|
picker: boolean;
|
|
@@ -24,6 +24,8 @@ const props = defineProps({
|
|
|
24
24
|
editor: { type: Boolean, required: false, default: true },
|
|
25
25
|
onOpen: { type: Function, required: false, default: void 0 },
|
|
26
26
|
profile: { type: [String, null], required: false, default: null },
|
|
27
|
+
service: { type: null, required: false, default: void 0 },
|
|
28
|
+
serviceBody: { type: [Object, null], required: false, default: null },
|
|
27
29
|
removeLabel: { type: String, required: false, default: void 0 },
|
|
28
30
|
badges: { type: Function, required: false, default: void 0 },
|
|
29
31
|
tileMenu: { type: Function, required: false, default: void 0 },
|
|
@@ -41,7 +43,9 @@ const gallery = useMLMediaGallery(model, {
|
|
|
41
43
|
processingConfig: props.processingConfig,
|
|
42
44
|
collectionName: props.collectionName,
|
|
43
45
|
profile: props.profile,
|
|
44
|
-
|
|
46
|
+
service: props.service,
|
|
47
|
+
serviceBody: props.serviceBody,
|
|
48
|
+
onUploaded: (id, sessionId) => emit("uploaded", id, sessionId),
|
|
45
49
|
onAdd: (id, tag) => props.onAdd?.(id, tag)
|
|
46
50
|
});
|
|
47
51
|
const { items, message, full, replaces } = gallery;
|
|
@@ -109,15 +113,29 @@ const mediaEditor = useMLMediaEditor();
|
|
|
109
113
|
const canPick = computed(() => !props.disabled && (!!props.onPick || props.picker));
|
|
110
114
|
async function pickExisting() {
|
|
111
115
|
if (props.onPick) return props.onPick();
|
|
116
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
117
|
+
const before = new Set(model.value);
|
|
112
118
|
const ids = await filePicker.open({
|
|
113
119
|
accept: props.accept,
|
|
114
120
|
max: gallery.limit.value,
|
|
115
121
|
selected: model.value,
|
|
116
122
|
title: `Select ${props.title.toLowerCase()}`,
|
|
117
123
|
maxSize: props.maxSize,
|
|
118
|
-
upload: {
|
|
124
|
+
upload: {
|
|
125
|
+
visibility: props.visibility,
|
|
126
|
+
processingConfig: props.processingConfig,
|
|
127
|
+
collectionName: props.collectionName,
|
|
128
|
+
profile: props.profile,
|
|
129
|
+
service: props.service,
|
|
130
|
+
serviceBody: props.serviceBody
|
|
131
|
+
},
|
|
132
|
+
onUploaded: (id, sessionId) => sessions.set(id, sessionId)
|
|
119
133
|
});
|
|
120
|
-
if (ids)
|
|
134
|
+
if (!ids) return;
|
|
135
|
+
gallery.setFromPicker(ids);
|
|
136
|
+
for (const id of ids) {
|
|
137
|
+
if (!before.has(id) && sessions.has(id)) emit("uploaded", id, sessions.get(id) ?? null);
|
|
138
|
+
}
|
|
121
139
|
}
|
|
122
140
|
function openFor(item) {
|
|
123
141
|
const id = item.id;
|
|
@@ -26,6 +26,10 @@ type __VLS_Props = {
|
|
|
26
26
|
onOpen?: (assetId: string) => void;
|
|
27
27
|
/** Upload profile name for new files in this list. */
|
|
28
28
|
profile?: string | null;
|
|
29
|
+
/** Start uploads at a backend service (name or `{ start }`). See useMLMediaUpload. */
|
|
30
|
+
service?: MediaUploadOptions['service'];
|
|
31
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
32
|
+
serviceBody?: Record<string, unknown> | null;
|
|
29
33
|
/** Overrides the "Remove from …" label. */
|
|
30
34
|
removeLabel?: string;
|
|
31
35
|
/** Labels shown on a tile, e.g. its roles. */
|
|
@@ -58,19 +62,18 @@ type __VLS_Slots = {} & {
|
|
|
58
62
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
59
63
|
uploadFiles: typeof uploadFiles;
|
|
60
64
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
61
|
-
uploaded: (assetId: string) => any;
|
|
65
|
+
uploaded: (assetId: string, sessionId: string | null) => any;
|
|
62
66
|
"update:modelValue": (value: string[]) => any;
|
|
63
67
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
64
68
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
65
|
-
onUploaded?: ((assetId: string) => any) | undefined;
|
|
69
|
+
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
66
70
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
67
71
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
68
72
|
}>, {
|
|
73
|
+
visibility: "private" | "restricted" | "public";
|
|
74
|
+
title: string;
|
|
69
75
|
disabled: boolean;
|
|
70
76
|
highlight: ReadonlySet<string> | null;
|
|
71
|
-
title: string;
|
|
72
|
-
visibility: "private" | "public" | "restricted";
|
|
73
|
-
profile: string | null;
|
|
74
77
|
accept: readonly string[] | string;
|
|
75
78
|
max: number | null;
|
|
76
79
|
maxSize: number | null;
|
|
@@ -81,6 +84,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
81
84
|
enableTranscription?: boolean;
|
|
82
85
|
};
|
|
83
86
|
collectionName: string | null;
|
|
87
|
+
profile: string | null;
|
|
88
|
+
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
89
|
+
serviceBody: Record<string, unknown> | null;
|
|
84
90
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
85
91
|
hint: string;
|
|
86
92
|
picker: boolean;
|
|
@@ -40,11 +40,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
40
40
|
asset: AssetDetail | DeliveryAsset | null;
|
|
41
41
|
src: string | null;
|
|
42
42
|
progress: number | null;
|
|
43
|
-
disabled: boolean;
|
|
44
|
-
kind: string | null;
|
|
45
43
|
name: string;
|
|
46
|
-
|
|
44
|
+
kind: string | null;
|
|
45
|
+
disabled: boolean;
|
|
47
46
|
selected: boolean;
|
|
47
|
+
phase: ProgressVeilPhase | null;
|
|
48
48
|
missingAlt: boolean;
|
|
49
49
|
onOpen: () => void;
|
|
50
50
|
duration: number | null;
|
|
@@ -40,11 +40,11 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
40
40
|
asset: AssetDetail | DeliveryAsset | null;
|
|
41
41
|
src: string | null;
|
|
42
42
|
progress: number | null;
|
|
43
|
-
disabled: boolean;
|
|
44
|
-
kind: string | null;
|
|
45
43
|
name: string;
|
|
46
|
-
|
|
44
|
+
kind: string | null;
|
|
45
|
+
disabled: boolean;
|
|
47
46
|
selected: boolean;
|
|
47
|
+
phase: ProgressVeilPhase | null;
|
|
48
48
|
missingAlt: boolean;
|
|
49
49
|
onOpen: () => void;
|
|
50
50
|
duration: number | null;
|
|
@@ -24,9 +24,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
24
24
|
}>, {
|
|
25
25
|
asset: AssetDetail | DeliveryAsset | null;
|
|
26
26
|
src: string | null;
|
|
27
|
-
disabled: boolean;
|
|
28
|
-
kind: string | null;
|
|
29
27
|
name: string;
|
|
28
|
+
kind: string | null;
|
|
29
|
+
disabled: boolean;
|
|
30
30
|
selected: boolean;
|
|
31
31
|
meta: string;
|
|
32
32
|
disabledReason: string;
|
|
@@ -24,9 +24,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
|
|
|
24
24
|
}>, {
|
|
25
25
|
asset: AssetDetail | DeliveryAsset | null;
|
|
26
26
|
src: string | null;
|
|
27
|
-
disabled: boolean;
|
|
28
|
-
kind: string | null;
|
|
29
27
|
name: string;
|
|
28
|
+
kind: string | null;
|
|
29
|
+
disabled: boolean;
|
|
30
30
|
selected: boolean;
|
|
31
31
|
meta: string;
|
|
32
32
|
disabledReason: string;
|
|
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
20
20
|
size: string;
|
|
21
21
|
asset: AssetDetail | DeliveryAsset | null;
|
|
22
22
|
src: string | null;
|
|
23
|
-
disabled: boolean;
|
|
24
|
-
kind: string | null;
|
|
25
23
|
name: string;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
disabled: boolean;
|
|
26
26
|
selected: boolean;
|
|
27
27
|
date: string;
|
|
28
28
|
disabledReason: string;
|
|
@@ -20,9 +20,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
20
20
|
size: string;
|
|
21
21
|
asset: AssetDetail | DeliveryAsset | null;
|
|
22
22
|
src: string | null;
|
|
23
|
-
disabled: boolean;
|
|
24
|
-
kind: string | null;
|
|
25
23
|
name: string;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
disabled: boolean;
|
|
26
26
|
selected: boolean;
|
|
27
27
|
date: string;
|
|
28
28
|
disabledReason: string;
|
|
@@ -15,8 +15,8 @@ type __VLS_Props = {
|
|
|
15
15
|
};
|
|
16
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
17
|
progress: number | null;
|
|
18
|
-
compact: boolean;
|
|
19
18
|
label: string;
|
|
19
|
+
compact: boolean;
|
|
20
20
|
onCancel: () => void;
|
|
21
21
|
onRetry: () => void;
|
|
22
22
|
detail: string;
|
|
@@ -15,8 +15,8 @@ type __VLS_Props = {
|
|
|
15
15
|
};
|
|
16
16
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
17
|
progress: number | null;
|
|
18
|
-
compact: boolean;
|
|
19
18
|
label: string;
|
|
19
|
+
compact: boolean;
|
|
20
20
|
onCancel: () => void;
|
|
21
21
|
onRetry: () => void;
|
|
22
22
|
detail: string;
|
|
@@ -10,6 +10,8 @@ export interface FilePickerOpenOptions {
|
|
|
10
10
|
/** Bytes, for uploads from inside the picker. */
|
|
11
11
|
maxSize?: number | null;
|
|
12
12
|
upload?: MediaUploadOptions;
|
|
13
|
+
/** A file uploaded from inside the picker exists in the library (its upload session too, for `service`). */
|
|
14
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
13
15
|
}
|
|
14
16
|
/**
|
|
15
17
|
* Opens the library dialog. `open()` resolves with the chosen asset IDs in
|
|
@@ -7,8 +7,12 @@ export interface UseMLMediaFieldOptions extends MediaUploadOptions {
|
|
|
7
7
|
accept?: MaybeRefOrGetter<readonly string[] | string | null | undefined>;
|
|
8
8
|
/** Bytes. */
|
|
9
9
|
maxSize?: MaybeRefOrGetter<number | null | undefined>;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Called once the uploaded file exists in the library (before processing
|
|
12
|
+
* finishes). `sessionId` is its upload session: with `service`, send both
|
|
13
|
+
* to the service's attach endpoint.
|
|
14
|
+
*/
|
|
15
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
12
16
|
}
|
|
13
17
|
/**
|
|
14
18
|
* Logic behind <MLMediaField>: one asset ID, uploads that replace it, and the
|
|
@@ -34,7 +34,7 @@ export function useMLMediaField(model, options = {}) {
|
|
|
34
34
|
void u.created.then((id) => {
|
|
35
35
|
if (!id || upload.value !== u) return;
|
|
36
36
|
model.value = id;
|
|
37
|
-
onUploaded?.(id);
|
|
37
|
+
onUploaded?.(id, u.sessionId);
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
watch(() => upload.value?.phase, async (phase) => {
|
|
@@ -18,7 +18,11 @@ export interface UseMLMediaGalleryOptions extends MediaUploadOptions {
|
|
|
18
18
|
max?: MaybeRefOrGetter<number | null | undefined>;
|
|
19
19
|
/** Bytes. */
|
|
20
20
|
maxSize?: MaybeRefOrGetter<number | null | undefined>;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* A new upload joined the list. `sessionId` is its upload session: with
|
|
23
|
+
* `service`, send both to the service's attach endpoint.
|
|
24
|
+
*/
|
|
25
|
+
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
22
26
|
/**
|
|
23
27
|
* Called with a new upload's asset ID right before it joins the list, with
|
|
24
28
|
* the tag passed to onFiles. Lets a gallery with roles place it.
|
|
@@ -43,7 +43,7 @@ export function useMLMediaGallery(model, options = {}) {
|
|
|
43
43
|
if (replaces.value) {
|
|
44
44
|
onAdd?.(id, opts.tag);
|
|
45
45
|
model.value = [id];
|
|
46
|
-
onUploaded?.(id);
|
|
46
|
+
onUploaded?.(id, u.sessionId);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
49
|
if (ids.value.includes(id)) {
|
|
@@ -56,7 +56,7 @@ export function useMLMediaGallery(model, options = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
onAdd?.(id, opts.tag);
|
|
58
58
|
model.value = [...ids.value, id];
|
|
59
|
-
onUploaded?.(id);
|
|
59
|
+
onUploaded?.(id, u.sessionId);
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ChunkUploaderConfig } from '../types/uploader.js';
|
|
2
|
+
import { type ServiceUploadChoice } from '../utils/uploader/serviceBackends.js';
|
|
3
|
+
export type { MediaServiceUpload, ServiceUploadBackend, ServiceUploadChoice } from '../utils/uploader/serviceBackends.js';
|
|
2
4
|
export type MediaUploadPhase = 'uploading' | 'processing' | 'ready' | 'failed' | 'canceled';
|
|
3
5
|
/** What a policy-less upload session takes. See docs/resource-policy/retirement.md. */
|
|
4
6
|
export interface MediaUploadOptions {
|
|
@@ -20,6 +22,18 @@ export interface MediaUploadOptions {
|
|
|
20
22
|
profile?: string | null;
|
|
21
23
|
/** Overrides the uploader config the module provides. */
|
|
22
24
|
chunkUploader?: ChunkUploaderConfig;
|
|
25
|
+
/**
|
|
26
|
+
* Start uploads at a backend service (py_client `create_upload_router`)
|
|
27
|
+
* instead of Nuxt's own upload route: the name of a backend registered
|
|
28
|
+
* under `mediaLibrary.serviceUploads.backends` (e.g. `'videos'`), or
|
|
29
|
+
* `{ start, … }`. The service creates the session and its profile decides
|
|
30
|
+
* kind, visibility and processing, so those options are not used here;
|
|
31
|
+
* `profile` is sent as the start request's `profile`. The file's parts go
|
|
32
|
+
* to `mediaLibrary.serviceUploads` (pass-through route or public URL).
|
|
33
|
+
*/
|
|
34
|
+
service?: ServiceUploadChoice;
|
|
35
|
+
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
36
|
+
serviceBody?: Record<string, unknown> | null;
|
|
23
37
|
}
|
|
24
38
|
export interface MediaUpload {
|
|
25
39
|
readonly key: string;
|
|
@@ -31,6 +45,11 @@ export interface MediaUpload {
|
|
|
31
45
|
/** 0–100, or null while a step reports no progress. */
|
|
32
46
|
progress: number | null;
|
|
33
47
|
assetId: string | null;
|
|
48
|
+
/**
|
|
49
|
+
* The upload session, once known. With `service`, send it with `assetId`
|
|
50
|
+
* to the service's attach endpoint (its `claim_upload` needs both).
|
|
51
|
+
*/
|
|
52
|
+
sessionId: string | null;
|
|
34
53
|
error: string | null;
|
|
35
54
|
cancel: () => void;
|
|
36
55
|
retry: () => void;
|
|
@@ -76,6 +95,7 @@ export declare function useMLMediaUpload(defaults?: MediaUploadOptions): {
|
|
|
76
95
|
phase: MediaUploadPhase;
|
|
77
96
|
progress: number | null;
|
|
78
97
|
assetId: string | null;
|
|
98
|
+
sessionId: string | null;
|
|
79
99
|
error: string | null;
|
|
80
100
|
cancel: () => void;
|
|
81
101
|
retry: () => void;
|
|
@@ -119,6 +139,7 @@ export declare function useMLMediaUpload(defaults?: MediaUploadOptions): {
|
|
|
119
139
|
phase: MediaUploadPhase;
|
|
120
140
|
progress: number | null;
|
|
121
141
|
assetId: string | null;
|
|
142
|
+
sessionId: string | null;
|
|
122
143
|
error: string | null;
|
|
123
144
|
cancel: () => void;
|
|
124
145
|
retry: () => void;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { onBeforeUnmount, reactive, ref, watch } from "vue";
|
|
2
2
|
import { $fetch } from "ofetch";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
4
|
import { ChunkUploader } from "../utils/uploader/uploader.js";
|
|
5
|
+
import { createServiceUpload } from "../utils/uploader/serviceUpload.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveServiceUpload
|
|
8
|
+
} from "../utils/uploader/serviceBackends.js";
|
|
4
9
|
import { kindForFile } from "../utils/fileAccept.js";
|
|
5
10
|
import { useMLChunkUploader } from "./useMLChunkUploader.js";
|
|
6
11
|
import { useMLProcessingStatus } from "./useMLProcessingStatus.js";
|
|
@@ -8,6 +13,9 @@ let seq = 0;
|
|
|
8
13
|
export function useMLMediaUpload(defaults = {}) {
|
|
9
14
|
const base = useMLChunkUploader(defaults.chunkUploader).config;
|
|
10
15
|
const cfg = base.proxyUploads ? { ...base, baseURL: "" } : base;
|
|
16
|
+
const publicConfig = useRuntimeConfig().public?.mediaLibrary;
|
|
17
|
+
const serviceBaseURL = publicConfig?.serviceUploadBaseURL;
|
|
18
|
+
const serviceBackends = publicConfig?.serviceUploadBackends;
|
|
11
19
|
const { track } = useMLProcessingStatus();
|
|
12
20
|
const uploads = ref([]);
|
|
13
21
|
const cleanups = /* @__PURE__ */ new Map();
|
|
@@ -17,6 +25,7 @@ export function useMLMediaUpload(defaults = {}) {
|
|
|
17
25
|
const isVisual = file.type.startsWith("image/") || file.type.startsWith("video/");
|
|
18
26
|
const localPreview = isVisual && typeof URL !== "undefined" ? URL.createObjectURL(file) : null;
|
|
19
27
|
let uploader = null;
|
|
28
|
+
let serviceRun = null;
|
|
20
29
|
let stream = null;
|
|
21
30
|
let stopWatch = null;
|
|
22
31
|
let resolveCreated;
|
|
@@ -28,6 +37,7 @@ export function useMLMediaUpload(defaults = {}) {
|
|
|
28
37
|
phase: "uploading",
|
|
29
38
|
progress: 0,
|
|
30
39
|
assetId: null,
|
|
40
|
+
sessionId: null,
|
|
31
41
|
error: null,
|
|
32
42
|
cancel,
|
|
33
43
|
retry,
|
|
@@ -81,11 +91,11 @@ export function useMLMediaUpload(defaults = {}) {
|
|
|
81
91
|
if (status === "ready") finish("ready");
|
|
82
92
|
else if (status === "failed") finish("failed", "Processing failed");
|
|
83
93
|
}
|
|
84
|
-
|
|
94
|
+
const onProgress = (p) => {
|
|
95
|
+
upload.progress = p.percent;
|
|
96
|
+
};
|
|
97
|
+
async function uploadViaNuxt() {
|
|
85
98
|
uploader = new ChunkUploader(cfg);
|
|
86
|
-
upload.phase = "uploading";
|
|
87
|
-
upload.progress = 0;
|
|
88
|
-
upload.error = null;
|
|
89
99
|
const create = {
|
|
90
100
|
kind,
|
|
91
101
|
visibility: opts.visibility,
|
|
@@ -93,15 +103,32 @@ export function useMLMediaUpload(defaults = {}) {
|
|
|
93
103
|
collection_name: opts.collectionName ?? null,
|
|
94
104
|
...opts.profile ? { profile: opts.profile } : {}
|
|
95
105
|
};
|
|
106
|
+
const res = await uploader.uploadFile(file, { create, onProgress });
|
|
107
|
+
const ids = Array.isArray(res?.finalize?.asset_ids) ? res.finalize.asset_ids : res?.finalize?.asset_id ? [res.finalize.asset_id] : [];
|
|
108
|
+
return { assetId: ids[0] ?? null, sessionId: res?.sessionId ?? null };
|
|
109
|
+
}
|
|
110
|
+
async function uploadViaService(service) {
|
|
111
|
+
serviceRun = createServiceUpload({
|
|
112
|
+
fetch: (url, o) => $fetch(url, o),
|
|
113
|
+
createUploader: (c) => new ChunkUploader(c),
|
|
114
|
+
baseURL: serviceBaseURL || void 0
|
|
115
|
+
});
|
|
116
|
+
const res = await serviceRun.upload(file, {
|
|
117
|
+
...service,
|
|
118
|
+
body: { ...opts.profile ? { profile: opts.profile } : {}, ...service.body },
|
|
119
|
+
onProgress
|
|
120
|
+
});
|
|
121
|
+
return { assetId: res.assetId, sessionId: res.sessionId };
|
|
122
|
+
}
|
|
123
|
+
async function run() {
|
|
124
|
+
upload.phase = "uploading";
|
|
125
|
+
upload.progress = 0;
|
|
126
|
+
upload.error = null;
|
|
127
|
+
upload.sessionId = null;
|
|
96
128
|
try {
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
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;
|
|
129
|
+
const service = resolveServiceUpload(opts.service, serviceBackends, opts.serviceBody);
|
|
130
|
+
const { assetId, sessionId } = service ? await uploadViaService(service) : await uploadViaNuxt();
|
|
131
|
+
upload.sessionId = sessionId;
|
|
105
132
|
if (!assetId) {
|
|
106
133
|
finish("failed", "The upload finished but no file was created");
|
|
107
134
|
resolveCreated(null);
|
|
@@ -119,6 +146,7 @@ export function useMLMediaUpload(defaults = {}) {
|
|
|
119
146
|
function cancel() {
|
|
120
147
|
if (upload.phase !== "uploading" && upload.phase !== "processing") return;
|
|
121
148
|
uploader?.abort();
|
|
149
|
+
serviceRun?.abort();
|
|
122
150
|
finish("canceled");
|
|
123
151
|
if (!upload.assetId) resolveCreated(null);
|
|
124
152
|
}
|
|
@@ -132,6 +160,7 @@ export function useMLMediaUpload(defaults = {}) {
|
|
|
132
160
|
}
|
|
133
161
|
cleanups.set(upload.key, () => {
|
|
134
162
|
uploader?.abort();
|
|
163
|
+
serviceRun?.abort();
|
|
135
164
|
stopWatch?.();
|
|
136
165
|
stream?.close();
|
|
137
166
|
if (localPreview) URL.revokeObjectURL(localPreview);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ServiceUploadOptions } from './serviceUpload.js';
|
|
2
|
+
/**
|
|
3
|
+
* A backend service that starts uploads (py_client `create_upload_router`),
|
|
4
|
+
* as registered under `mediaLibrary.serviceUploads.backends`. Each service's
|
|
5
|
+
* Nuxt layer registers its own; Nuxt merges them across layers.
|
|
6
|
+
*/
|
|
7
|
+
export interface ServiceUploadBackend {
|
|
8
|
+
/** The start endpoint the browser calls, e.g. `/api/videos/uploads`. */
|
|
9
|
+
start: string;
|
|
10
|
+
/**
|
|
11
|
+
* The grant refresh endpoint, with a `:sessionId` placeholder, e.g.
|
|
12
|
+
* `/api/videos/uploads/:sessionId/grant` (that is also the default).
|
|
13
|
+
*/
|
|
14
|
+
refresh?: string;
|
|
15
|
+
}
|
|
16
|
+
/** How a widget names where its uploads start. */
|
|
17
|
+
export type MediaServiceUpload = Pick<ServiceUploadOptions, 'start' | 'body' | 'refresh' | 'headers' | 'baseURL'>;
|
|
18
|
+
export type ServiceUploadChoice = string | MediaServiceUpload | null | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* The service upload for a widget's `service` option, or null to upload
|
|
21
|
+
* through Nuxt's own route. A name is looked up in the registered backends;
|
|
22
|
+
* `body` (e.g. `{ context }`) is merged into the start request.
|
|
23
|
+
*
|
|
24
|
+
* @throws Error for a name no layer registered.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveServiceUpload(service: ServiceUploadChoice, backends: Record<string, ServiceUploadBackend | undefined> | null | undefined, body?: Record<string, unknown> | null): MediaServiceUpload | null;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function resolveServiceUpload(service, backends, body) {
|
|
2
|
+
if (!service) return null;
|
|
3
|
+
let target;
|
|
4
|
+
if (typeof service === "string") {
|
|
5
|
+
const backend = backends?.[service];
|
|
6
|
+
if (!backend?.start) {
|
|
7
|
+
throw new Error(`No upload backend '${service}': add it under mediaLibrary.serviceUploads.backends.`);
|
|
8
|
+
}
|
|
9
|
+
const refresh = backend.refresh;
|
|
10
|
+
target = {
|
|
11
|
+
start: backend.start,
|
|
12
|
+
...refresh ? { refresh: (sessionId) => refresh.replace(/:sessionId\b|\{sessionId\}/g, encodeURIComponent(sessionId)) } : {}
|
|
13
|
+
};
|
|
14
|
+
} else {
|
|
15
|
+
target = service;
|
|
16
|
+
}
|
|
17
|
+
return body ? { ...target, body: { ...target.body, ...body } } : target;
|
|
18
|
+
}
|