nuxt-media-library 0.0.56 → 0.0.58
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 +19 -8
- package/dist/module.d.mts +13 -3
- package/dist/module.d.ts +13 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +11 -19
- package/dist/runtime/components/MLMedia/Field.d.vue.ts +6 -3
- package/dist/runtime/components/MLMedia/Field.vue +4 -0
- package/dist/runtime/components/MLMedia/Field.vue.d.ts +6 -3
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue +1 -0
- package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +11 -4
- package/dist/runtime/components/MLMedia/Gallery.vue +109 -4
- package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +11 -4
- package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +10 -1
- package/dist/runtime/components/MLMedia/GallerySection.vue +13 -2
- package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +10 -1
- package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +1 -1
- package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +1 -1
- package/dist/runtime/components/MLMedia/UploadProgress.d.vue.ts +17 -0
- package/dist/runtime/components/MLMedia/UploadProgress.vue +33 -0
- package/dist/runtime/components/MLMedia/UploadProgress.vue.d.ts +17 -0
- package/dist/runtime/components/MLMedia/UploadRow.d.vue.ts +12 -0
- package/dist/runtime/components/MLMedia/UploadRow.vue +252 -0
- package/dist/runtime/components/MLMedia/UploadRow.vue.d.ts +12 -0
- package/dist/runtime/components/MLMedia/UploadStep.d.vue.ts +17 -0
- package/dist/runtime/components/MLMedia/UploadStep.vue +168 -0
- package/dist/runtime/components/MLMedia/UploadStep.vue.d.ts +17 -0
- package/dist/runtime/components/MLMedia/UploadSteps.d.vue.ts +14 -0
- package/dist/runtime/components/MLMedia/UploadSteps.vue +129 -0
- package/dist/runtime/components/MLMedia/UploadSteps.vue.d.ts +14 -0
- package/dist/runtime/components/MLMedia/UploadTray.d.vue.ts +12 -0
- package/dist/runtime/components/MLMedia/UploadTray.vue +222 -0
- package/dist/runtime/components/MLMedia/UploadTray.vue.d.ts +12 -0
- package/dist/runtime/composables/useMLMediaField.js +5 -3
- package/dist/runtime/composables/useMLMediaGallery.js +4 -1
- package/dist/runtime/composables/useMLMediaUpload.d.ts +119 -7
- package/dist/runtime/composables/useMLMediaUpload.js +27 -169
- package/dist/runtime/composables/useMLUploadQueue.d.ts +33 -0
- package/dist/runtime/composables/useMLUploadQueue.js +403 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/utils/backends.d.ts +63 -0
- package/dist/runtime/utils/backends.js +72 -0
- package/dist/runtime/utils/processingSteps.d.ts +5 -0
- package/dist/runtime/utils/processingSteps.js +36 -0
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -237,9 +237,11 @@ merges the layers, so every widget on the site can use any backend by name.
|
|
|
237
237
|
export default defineNuxtConfig({
|
|
238
238
|
modules: ['nuxt-media-library'],
|
|
239
239
|
mediaLibrary: {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
backends: {
|
|
241
|
+
videos: {
|
|
242
|
+
upload: { start: '/api/videos/uploads' }, // refresh defaults to `${start}/:sessionId/grant`
|
|
243
|
+
claim: '/api/videos/uploads/:sessionId/attach', // py_client on_attach
|
|
244
|
+
},
|
|
243
245
|
},
|
|
244
246
|
},
|
|
245
247
|
})
|
|
@@ -257,17 +259,26 @@ export default defineNuxtConfig({
|
|
|
257
259
|
```vue
|
|
258
260
|
<MLMediaGallery
|
|
259
261
|
v-model="lesson.mediaIds"
|
|
260
|
-
|
|
262
|
+
backend="videos"
|
|
261
263
|
profile="lesson-video"
|
|
262
264
|
:service-body="{ context: { lessonId } }"
|
|
263
265
|
@uploaded="(assetId, role, sessionId) => attach(assetId, sessionId)"
|
|
264
266
|
/>
|
|
265
267
|
```
|
|
266
268
|
|
|
267
|
-
- `
|
|
268
|
-
`useMLMediaUpload`): a
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
- `backend` on `MLMediaGallery`, `MLMediaField` and the file picker (and
|
|
270
|
+
`useMLMediaUpload`): a name in `mediaLibrary.backends`. `service` is its
|
|
271
|
+
older name and still takes `{ start }` for a one-off; `backend` wins.
|
|
272
|
+
Without either, widgets upload through Nuxt's own route as before; an
|
|
273
|
+
unknown name fails that upload with a message naming the missing backend.
|
|
274
|
+
- Each backend has a `kind`: `media-library` (the default) or `media-kit`.
|
|
275
|
+
This version uploads only to `media-library` backends; a `media-kit` one
|
|
276
|
+
fails the upload with a clear message.
|
|
277
|
+
- With `claim`, the upload queue attaches files whose page was left before
|
|
278
|
+
the widget attached them (`POST { assetId, profile }`; 409 counts as
|
|
279
|
+
attached). The tray shows a refusal with "Add again".
|
|
280
|
+
- `mediaLibrary.serviceUploads.backends` still works: each entry becomes a
|
|
281
|
+
`media-library` backend with that `upload` and no `claim`.
|
|
271
282
|
- The start request carries the file (`mimeType`, `sizeBytes`, `filename`),
|
|
272
283
|
`profile` (each role's, in a gallery with roles) and `service-body`. The
|
|
273
284
|
service's profile decides kind, visibility and processing, so
|
package/dist/module.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
|
|
3
|
+
import { MediaBackends } from '../dist/runtime/utils/backends.js';
|
|
3
4
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
4
5
|
export * from '../dist/runtime/types/uploader.js';
|
|
5
6
|
export * from '../dist/runtime/types/preview.js';
|
|
@@ -43,9 +44,8 @@ interface ModuleOptions {
|
|
|
43
44
|
* public API root with `/v1`, or a proxy route); with `proxy` it defaults
|
|
44
45
|
* to the pass-through route.
|
|
45
46
|
*
|
|
46
|
-
* `backends
|
|
47
|
-
*
|
|
48
|
-
* merges them across layers. Paths only: each layer serves its routes.
|
|
47
|
+
* `backends`: older form of `mediaLibrary.backends` (start/refresh only).
|
|
48
|
+
* Each entry becomes a `media-library` backend with that `upload`.
|
|
49
49
|
*/
|
|
50
50
|
serviceUploads?: {
|
|
51
51
|
proxy?: boolean;
|
|
@@ -62,6 +62,16 @@ interface ModuleOptions {
|
|
|
62
62
|
* pass-through are still registered.
|
|
63
63
|
*/
|
|
64
64
|
manageProxy?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The backend registry: named places widgets upload to and read from
|
|
67
|
+
* (`<MLMediaGallery backend="videos" />`). Each entry has a `kind`
|
|
68
|
+
* (`media-library`, the default, or `media-kit`) and its routes: `upload`
|
|
69
|
+
* (`{ start, refresh? }`, py_client `create_upload_router`), `claim`
|
|
70
|
+
* (attach a finished upload, `/…/:sessionId/attach`). Each service's Nuxt
|
|
71
|
+
* layer registers its own; Nuxt merges them. Paths only: each layer serves
|
|
72
|
+
* its routes.
|
|
73
|
+
*/
|
|
74
|
+
backends?: MediaBackends;
|
|
65
75
|
}
|
|
66
76
|
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
67
77
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
package/dist/module.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
2
|
import { ServiceUploadBackend } from '../dist/runtime/utils/uploader/serviceBackends.js';
|
|
3
|
+
import { MediaBackends } from '../dist/runtime/utils/backends.js';
|
|
3
4
|
export * from '../dist/runtime/types/resourcePolicy.js';
|
|
4
5
|
export * from '../dist/runtime/types/uploader.js';
|
|
5
6
|
export * from '../dist/runtime/types/preview.js';
|
|
@@ -43,9 +44,8 @@ interface ModuleOptions {
|
|
|
43
44
|
* public API root with `/v1`, or a proxy route); with `proxy` it defaults
|
|
44
45
|
* to the pass-through route.
|
|
45
46
|
*
|
|
46
|
-
* `backends
|
|
47
|
-
*
|
|
48
|
-
* merges them across layers. Paths only: each layer serves its routes.
|
|
47
|
+
* `backends`: older form of `mediaLibrary.backends` (start/refresh only).
|
|
48
|
+
* Each entry becomes a `media-library` backend with that `upload`.
|
|
49
49
|
*/
|
|
50
50
|
serviceUploads?: {
|
|
51
51
|
proxy?: boolean;
|
|
@@ -62,6 +62,16 @@ interface ModuleOptions {
|
|
|
62
62
|
* pass-through are still registered.
|
|
63
63
|
*/
|
|
64
64
|
manageProxy?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* The backend registry: named places widgets upload to and read from
|
|
67
|
+
* (`<MLMediaGallery backend="videos" />`). Each entry has a `kind`
|
|
68
|
+
* (`media-library`, the default, or `media-kit`) and its routes: `upload`
|
|
69
|
+
* (`{ start, refresh? }`, py_client `create_upload_router`), `claim`
|
|
70
|
+
* (attach a finished upload, `/…/:sessionId/attach`). Each service's Nuxt
|
|
71
|
+
* layer registers its own; Nuxt merges them. Paths only: each layer serves
|
|
72
|
+
* its routes.
|
|
73
|
+
*/
|
|
74
|
+
backends?: MediaBackends;
|
|
65
75
|
}
|
|
66
76
|
declare const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
67
77
|
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { defineNuxtModule, createResolver, addImportsDir, addComponentsDir, addServerHandler, useLogger, addPlugin } from '@nuxt/kit';
|
|
4
|
+
import { normalizeBackends } from '../dist/runtime/utils/backends.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';
|
|
@@ -22,23 +23,6 @@ function browserBaseURL(existingPublic, publicBaseURL) {
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const SERVICE_UPLOADS_ROUTE = "/api/media-library/service-uploads";
|
|
25
|
-
function serviceBackends(backends, logger) {
|
|
26
|
-
const out = {};
|
|
27
|
-
for (const [name, backend] of Object.entries(backends ?? {})) {
|
|
28
|
-
if (!backend || typeof backend.start !== "string" || !backend.start) {
|
|
29
|
-
logger.warn(`mediaLibrary.serviceUploads.backends.${name} has no start path; ignored.`);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
if (backend.refresh && !backend.refresh.includes(":sessionId")) {
|
|
33
|
-
logger.warn(`mediaLibrary.serviceUploads.backends.${name}.refresh needs a :sessionId placeholder; using the default.`);
|
|
34
|
-
}
|
|
35
|
-
out[name] = {
|
|
36
|
-
start: backend.start,
|
|
37
|
-
...backend.refresh?.includes(":sessionId") ? { refresh: backend.refresh } : {}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
26
|
function appCatchAlls(layers) {
|
|
43
27
|
const found = [];
|
|
44
28
|
for (const { config } of layers) {
|
|
@@ -171,6 +155,10 @@ const module$1 = defineNuxtModule({
|
|
|
171
155
|
if (existingPublic.proxyUploads === false && !existingPublic.baseURL && !options.publicBaseURL) {
|
|
172
156
|
logger.warn("proxyUploads is false but no public media library address is set: set mediaLibrary.publicBaseURL. baseURL is server-side only.");
|
|
173
157
|
}
|
|
158
|
+
const backends = {
|
|
159
|
+
...normalizeBackends(options.backends, options.serviceUploads?.backends, (m) => logger.warn(m)),
|
|
160
|
+
...normalizeBackends(existingPublic.backends, existingPublic.serviceUploadBackends, (m) => logger.warn(m))
|
|
161
|
+
};
|
|
174
162
|
nuxt.options.runtimeConfig.public.mediaLibrary = {
|
|
175
163
|
...existingPublic,
|
|
176
164
|
baseURL: browserBaseURL(existingPublic.baseURL, options.publicBaseURL),
|
|
@@ -186,8 +174,12 @@ const module$1 = defineNuxtModule({
|
|
|
186
174
|
headers: publicHeaders.headers,
|
|
187
175
|
// Where useMLServiceUpload sends parts (NUXT_PUBLIC_MEDIA_LIBRARY_SERVICE_UPLOAD_BASE_URL).
|
|
188
176
|
serviceUploadBaseURL: existingPublic.serviceUploadBaseURL || options.serviceUploads?.baseURL || (options.serviceUploads?.proxy ? SERVICE_UPLOADS_ROUTE : ""),
|
|
189
|
-
//
|
|
190
|
-
|
|
177
|
+
// The backend registry (NUXT_PUBLIC_MEDIA_LIBRARY_BACKENDS): names widgets pass as `backend`.
|
|
178
|
+
backends,
|
|
179
|
+
// Older form, kept for code that reads it: each backend's upload routes.
|
|
180
|
+
serviceUploadBackends: Object.fromEntries(
|
|
181
|
+
Object.entries(backends).flatMap(([name, b]) => b.upload ? [[name, b.upload]] : [])
|
|
182
|
+
)
|
|
191
183
|
};
|
|
192
184
|
addPlugin({
|
|
193
185
|
src: resolver.resolve("./runtime/plugins/config")
|
|
@@ -18,10 +18,12 @@ type __VLS_Props = {
|
|
|
18
18
|
/** Upload profile name for new files. */
|
|
19
19
|
profile?: string | null;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* The backend registered under `mediaLibrary.backends` that uploads go to
|
|
22
|
+
* (e.g. "videos"). Its `claim` route attaches files whose page was left
|
|
23
|
+
* before they finished. Wins over `service`.
|
|
24
24
|
*/
|
|
25
|
+
backend?: string | null;
|
|
26
|
+
/** Older name for `backend`; also takes an inline `{ start }`. See useMLMediaUpload. */
|
|
25
27
|
service?: MediaUploadOptions['service'];
|
|
26
28
|
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
27
29
|
serviceBody?: Record<string, unknown> | null;
|
|
@@ -65,6 +67,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
65
67
|
};
|
|
66
68
|
collectionName: string | null;
|
|
67
69
|
profile: string | null;
|
|
70
|
+
backend: string | null;
|
|
68
71
|
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
69
72
|
serviceBody: Record<string, unknown> | null;
|
|
70
73
|
hint: string;
|
|
@@ -16,6 +16,7 @@ const props = defineProps({
|
|
|
16
16
|
processingConfig: { type: Object, required: false, default: void 0 },
|
|
17
17
|
collectionName: { type: [String, null], required: false, default: null },
|
|
18
18
|
profile: { type: [String, null], required: false, default: null },
|
|
19
|
+
backend: { type: [String, null], required: false, default: null },
|
|
19
20
|
service: { type: null, required: false, default: void 0 },
|
|
20
21
|
serviceBody: { type: [Object, null], required: false, default: null },
|
|
21
22
|
picker: { type: Boolean, required: false, default: true },
|
|
@@ -34,8 +35,10 @@ const field = useMLMediaField(model, {
|
|
|
34
35
|
processingConfig: props.processingConfig,
|
|
35
36
|
collectionName: props.collectionName,
|
|
36
37
|
profile: props.profile,
|
|
38
|
+
backend: props.backend,
|
|
37
39
|
service: props.service,
|
|
38
40
|
serviceBody: props.serviceBody,
|
|
41
|
+
label: props.label || void 0,
|
|
39
42
|
onUploaded: (id, sessionId) => emit("uploaded", id, sessionId)
|
|
40
43
|
});
|
|
41
44
|
const { state, asset, upload, message } = field;
|
|
@@ -76,6 +79,7 @@ const uploadOptions = computed(() => ({
|
|
|
76
79
|
processingConfig: props.processingConfig,
|
|
77
80
|
collectionName: props.collectionName,
|
|
78
81
|
profile: props.profile,
|
|
82
|
+
backend: props.backend,
|
|
79
83
|
service: props.service,
|
|
80
84
|
serviceBody: props.serviceBody
|
|
81
85
|
}));
|
|
@@ -18,10 +18,12 @@ type __VLS_Props = {
|
|
|
18
18
|
/** Upload profile name for new files. */
|
|
19
19
|
profile?: string | null;
|
|
20
20
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* The backend registered under `mediaLibrary.backends` that uploads go to
|
|
22
|
+
* (e.g. "videos"). Its `claim` route attaches files whose page was left
|
|
23
|
+
* before they finished. Wins over `service`.
|
|
24
24
|
*/
|
|
25
|
+
backend?: string | null;
|
|
26
|
+
/** Older name for `backend`; also takes an inline `{ start }`. See useMLMediaUpload. */
|
|
25
27
|
service?: MediaUploadOptions['service'];
|
|
26
28
|
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
27
29
|
serviceBody?: Record<string, unknown> | null;
|
|
@@ -65,6 +67,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
65
67
|
};
|
|
66
68
|
collectionName: string | null;
|
|
67
69
|
profile: string | null;
|
|
70
|
+
backend: string | null;
|
|
68
71
|
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
69
72
|
serviceBody: Record<string, unknown> | null;
|
|
70
73
|
hint: string;
|
|
@@ -15,7 +15,11 @@ export type MediaGalleryProfiles = Record<string, MediaGalleryRole>;
|
|
|
15
15
|
type __VLS_Props = {
|
|
16
16
|
/** Roles of this usage. Omit for a single list driven by v-model. */
|
|
17
17
|
profiles?: MediaGalleryProfiles | null;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Role tried first for new uploads and picked files, when it takes their
|
|
20
|
+
* kind. Defaults to the first role that holds more than one file. Files of
|
|
21
|
+
* other kinds go to the first role that takes them.
|
|
22
|
+
*/
|
|
19
23
|
defaultRole?: string;
|
|
20
24
|
accept?: readonly string[] | string;
|
|
21
25
|
max?: number | null;
|
|
@@ -31,10 +35,12 @@ type __VLS_Props = {
|
|
|
31
35
|
/** Upload profile for the single list (with `profiles`, each role has its own). */
|
|
32
36
|
profile?: string | null;
|
|
33
37
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
38
|
+
* The backend registered under `mediaLibrary.backends` that uploads go to
|
|
39
|
+
* (e.g. "videos"). Its `claim` route attaches files whose page was left
|
|
40
|
+
* before they finished. Each role's profile is sent with the start request. Wins over `service`.
|
|
37
41
|
*/
|
|
42
|
+
backend?: string | null;
|
|
43
|
+
/** Older name for `backend`; also takes an inline `{ start }`. */
|
|
38
44
|
service?: MediaUploadOptions['service'];
|
|
39
45
|
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
40
46
|
serviceBody?: Record<string, unknown> | null;
|
|
@@ -72,6 +78,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
72
78
|
};
|
|
73
79
|
collectionName: string | null;
|
|
74
80
|
profile: string | null;
|
|
81
|
+
backend: string | null;
|
|
75
82
|
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
76
83
|
serviceBody: Record<string, unknown> | null;
|
|
77
84
|
hint: string;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { $fetch } from "ofetch";
|
|
4
|
+
import { kindForFile } from "../../utils/fileAccept";
|
|
3
5
|
import { GALLERY_TILE_TYPE } from "../../utils/galleryDrag";
|
|
4
6
|
import GallerySection from "./GallerySection.vue";
|
|
5
7
|
const props = defineProps({
|
|
@@ -17,6 +19,7 @@ const props = defineProps({
|
|
|
17
19
|
processingConfig: { type: Object, required: false, default: void 0 },
|
|
18
20
|
collectionName: { type: [String, null], required: false, default: null },
|
|
19
21
|
profile: { type: [String, null], required: false, default: null },
|
|
22
|
+
backend: { type: [String, null], required: false, default: null },
|
|
20
23
|
service: { type: null, required: false, default: void 0 },
|
|
21
24
|
serviceBody: { type: [Object, null], required: false, default: null },
|
|
22
25
|
picker: { type: Boolean, required: false, default: true },
|
|
@@ -78,16 +81,108 @@ function setRole(id, key, on) {
|
|
|
78
81
|
}
|
|
79
82
|
const pendingRole = /* @__PURE__ */ new Map();
|
|
80
83
|
const uploadedRole = /* @__PURE__ */ new Map();
|
|
84
|
+
const roleAccept = (r) => r.accept ?? props.accept;
|
|
85
|
+
const anyAccept = computed(() => {
|
|
86
|
+
const kinds = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const r of roles.value) {
|
|
88
|
+
const a = roleAccept(r);
|
|
89
|
+
const list = (typeof a === "string" ? a.split(",") : [...a ?? []]).map((k) => k.trim().toLowerCase()).filter(Boolean);
|
|
90
|
+
if (!list.length) return "";
|
|
91
|
+
list.forEach((k) => kinds.add(k));
|
|
92
|
+
}
|
|
93
|
+
return [...kinds];
|
|
94
|
+
});
|
|
95
|
+
const anyMaxSize = computed(() => {
|
|
96
|
+
let max = 0;
|
|
97
|
+
for (const r of roles.value) {
|
|
98
|
+
const m = r.maxSize ?? props.maxSize;
|
|
99
|
+
if (m == null) return null;
|
|
100
|
+
max = Math.max(max, m);
|
|
101
|
+
}
|
|
102
|
+
return max || null;
|
|
103
|
+
});
|
|
104
|
+
const kindMatters = computed(() => {
|
|
105
|
+
const seen = new Set(roles.value.map((r) => JSON.stringify(roleAccept(r) ?? "")));
|
|
106
|
+
return seen.size > 1;
|
|
107
|
+
});
|
|
108
|
+
function roleForKind(kind, taken = /* @__PURE__ */ new Set()) {
|
|
109
|
+
const ordered = [...roles.value.filter((r) => r.key === defaultKey.value), ...roles.value.filter((r) => r.key !== defaultKey.value)];
|
|
110
|
+
const takes = ordered.filter((r) => kindAllowed(kind, roleAccept(r)));
|
|
111
|
+
const room = takes.find((r) => single(r) ? !taken.has(r.key) && !sel(r.key).length : r.max == null || sel(r.key).length < r.max);
|
|
112
|
+
return (room ?? takes[0])?.key ?? null;
|
|
113
|
+
}
|
|
114
|
+
const profileFor = (file) => {
|
|
115
|
+
const key = roleForKind(kindForFile(file));
|
|
116
|
+
return key ? props.profiles?.[key]?.profile ?? null : null;
|
|
117
|
+
};
|
|
118
|
+
function routeFiles(files) {
|
|
119
|
+
notice.value = null;
|
|
120
|
+
const taken = /* @__PURE__ */ new Set();
|
|
121
|
+
const groups = /* @__PURE__ */ new Map();
|
|
122
|
+
const left = [];
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
const key = roleForKind(kindForFile(file), taken);
|
|
125
|
+
if (!key) {
|
|
126
|
+
left.push(file);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
taken.add(key);
|
|
130
|
+
groups.set(key, [...groups.get(key) ?? [], file]);
|
|
131
|
+
}
|
|
132
|
+
for (const [key, list] of groups) {
|
|
133
|
+
const role = props.profiles[key];
|
|
134
|
+
grid.value?.uploadFiles(list, {
|
|
135
|
+
profile: role.profile ?? null,
|
|
136
|
+
tag: key,
|
|
137
|
+
accept: roleAccept(role),
|
|
138
|
+
maxSize: role.maxSize ?? props.maxSize,
|
|
139
|
+
maxFiles: single(role) ? 1 : role.max != null ? Math.max(0, role.max - sel(key).length) : void 0
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (left.length) notice.value = `${left[0].name}${left.length > 1 ? ` and ${left.length - 1} more` : ""} can't be used in any role here.`;
|
|
143
|
+
}
|
|
144
|
+
async function placeByKind(ids) {
|
|
145
|
+
let kinds = /* @__PURE__ */ new Map();
|
|
146
|
+
try {
|
|
147
|
+
const res = await $fetch(
|
|
148
|
+
"/api/media-library/manage/assets/resolve",
|
|
149
|
+
{ method: "POST", body: { assetIds: ids } }
|
|
150
|
+
);
|
|
151
|
+
kinds = new Map(res.items.map((it, i) => [it.asset?.id ?? it.assetId ?? ids[i], it.asset?.kind?.toLowerCase() ?? null]));
|
|
152
|
+
} catch {
|
|
153
|
+
}
|
|
154
|
+
const taken = /* @__PURE__ */ new Set();
|
|
155
|
+
const changes = {};
|
|
156
|
+
for (const id of ids) {
|
|
157
|
+
const key = roleForKind(kinds.get(id) ?? null, taken) ?? defaultKey.value;
|
|
158
|
+
taken.add(key);
|
|
159
|
+
const placed = withFile(key, id, changes[key] ?? sel(key));
|
|
160
|
+
if (placed) changes[key] = placed;
|
|
161
|
+
const session = pendingEmit.get(id);
|
|
162
|
+
if (session !== void 0) {
|
|
163
|
+
pendingEmit.delete(id);
|
|
164
|
+
emit("uploaded", id, key, session);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (Object.keys(changes).length) commit(changes);
|
|
168
|
+
}
|
|
169
|
+
const pendingEmit = /* @__PURE__ */ new Map();
|
|
81
170
|
function onUnionUpdate(next) {
|
|
82
171
|
notice.value = null;
|
|
83
172
|
const keep = new Set(next);
|
|
84
173
|
const before = new Set(union.value);
|
|
85
174
|
const changes = {};
|
|
86
175
|
for (const r of roles.value) changes[r.key] = sel(r.key).filter((id) => keep.has(id));
|
|
176
|
+
const unplaced = [];
|
|
87
177
|
for (const id of next) {
|
|
88
178
|
if (before.has(id)) continue;
|
|
89
|
-
const
|
|
179
|
+
const tagged = pendingRole.get(id);
|
|
90
180
|
pendingRole.delete(id);
|
|
181
|
+
if (!tagged && kindMatters.value) {
|
|
182
|
+
unplaced.push(id);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const key = tagged ?? defaultKey.value;
|
|
91
186
|
const placed = withFile(key, id, changes[key]);
|
|
92
187
|
if (placed) changes[key] = placed;
|
|
93
188
|
}
|
|
@@ -96,6 +191,7 @@ function onUnionUpdate(next) {
|
|
|
96
191
|
if (!single(r)) changes[r.key] = [...changes[r.key]].sort((a, b) => (pos.get(a) ?? 0) - (pos.get(b) ?? 0));
|
|
97
192
|
}
|
|
98
193
|
commit(changes);
|
|
194
|
+
if (unplaced.length) void placeByKind(unplaced);
|
|
99
195
|
}
|
|
100
196
|
function onAdd(id, tag) {
|
|
101
197
|
const key = tag ?? defaultKey.value;
|
|
@@ -104,8 +200,13 @@ function onAdd(id, tag) {
|
|
|
104
200
|
else pendingRole.set(id, key);
|
|
105
201
|
}
|
|
106
202
|
function onUploaded(id, sessionId) {
|
|
107
|
-
|
|
203
|
+
const key = uploadedRole.get(id);
|
|
108
204
|
uploadedRole.delete(id);
|
|
205
|
+
if (!key && kindMatters.value && !union.value.includes(id)) {
|
|
206
|
+
pendingEmit.set(id, sessionId);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
emit("uploaded", id, key ?? defaultKey.value, sessionId);
|
|
109
210
|
}
|
|
110
211
|
const badges = (id) => roles.value.filter((r) => sel(r.key).includes(id)).map((r) => r.label);
|
|
111
212
|
function tileMenu(id, kind) {
|
|
@@ -170,9 +271,12 @@ function chipCount(key, max) {
|
|
|
170
271
|
:model-value="union"
|
|
171
272
|
:title="title"
|
|
172
273
|
:hint="hint"
|
|
173
|
-
:accept="
|
|
174
|
-
:max-size="
|
|
274
|
+
:accept="anyAccept"
|
|
275
|
+
:max-size="anyMaxSize"
|
|
175
276
|
:profile="defaultRole?.profile ?? null"
|
|
277
|
+
:profile-for="profileFor"
|
|
278
|
+
:on-route-files="routeFiles"
|
|
279
|
+
:backend="backend"
|
|
176
280
|
:service="service"
|
|
177
281
|
:service-body="serviceBody"
|
|
178
282
|
:disabled="disabled"
|
|
@@ -251,6 +355,7 @@ function chipCount(key, max) {
|
|
|
251
355
|
:processing-config="processingConfig"
|
|
252
356
|
:collection-name="collectionName"
|
|
253
357
|
:profile="profile"
|
|
358
|
+
:backend="backend"
|
|
254
359
|
:service="service"
|
|
255
360
|
:service-body="serviceBody"
|
|
256
361
|
:picker="picker"
|
|
@@ -15,7 +15,11 @@ export type MediaGalleryProfiles = Record<string, MediaGalleryRole>;
|
|
|
15
15
|
type __VLS_Props = {
|
|
16
16
|
/** Roles of this usage. Omit for a single list driven by v-model. */
|
|
17
17
|
profiles?: MediaGalleryProfiles | null;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* Role tried first for new uploads and picked files, when it takes their
|
|
20
|
+
* kind. Defaults to the first role that holds more than one file. Files of
|
|
21
|
+
* other kinds go to the first role that takes them.
|
|
22
|
+
*/
|
|
19
23
|
defaultRole?: string;
|
|
20
24
|
accept?: readonly string[] | string;
|
|
21
25
|
max?: number | null;
|
|
@@ -31,10 +35,12 @@ type __VLS_Props = {
|
|
|
31
35
|
/** Upload profile for the single list (with `profiles`, each role has its own). */
|
|
32
36
|
profile?: string | null;
|
|
33
37
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
38
|
+
* The backend registered under `mediaLibrary.backends` that uploads go to
|
|
39
|
+
* (e.g. "videos"). Its `claim` route attaches files whose page was left
|
|
40
|
+
* before they finished. Each role's profile is sent with the start request. Wins over `service`.
|
|
37
41
|
*/
|
|
42
|
+
backend?: string | null;
|
|
43
|
+
/** Older name for `backend`; also takes an inline `{ start }`. */
|
|
38
44
|
service?: MediaUploadOptions['service'];
|
|
39
45
|
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
40
46
|
serviceBody?: Record<string, unknown> | null;
|
|
@@ -72,6 +78,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
72
78
|
};
|
|
73
79
|
collectionName: string | null;
|
|
74
80
|
profile: string | null;
|
|
81
|
+
backend: string | null;
|
|
75
82
|
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
76
83
|
serviceBody: Record<string, unknown> | null;
|
|
77
84
|
hint: string;
|
|
@@ -26,7 +26,9 @@ 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
|
-
/**
|
|
29
|
+
/** Registered backend uploads go to (`mediaLibrary.backends`). Wins over `service`. */
|
|
30
|
+
backend?: string | null;
|
|
31
|
+
/** Older name for `backend`; also takes an inline `{ start }`. See useMLMediaUpload. */
|
|
30
32
|
service?: MediaUploadOptions['service'];
|
|
31
33
|
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
32
34
|
serviceBody?: Record<string, unknown> | null;
|
|
@@ -40,6 +42,10 @@ type __VLS_Props = {
|
|
|
40
42
|
highlight?: ReadonlySet<string> | null;
|
|
41
43
|
/** See useMLMediaGallery's onAdd. */
|
|
42
44
|
onAdd?: (assetId: string, tag: string | undefined) => void;
|
|
45
|
+
/** Take new files instead of uploading them here (a gallery with roles routes them by kind). */
|
|
46
|
+
onRouteFiles?: (files: File[]) => void;
|
|
47
|
+
/** Upload profile per file, for uploads inside the picker. */
|
|
48
|
+
profileFor?: (file: File) => string | null | undefined;
|
|
43
49
|
};
|
|
44
50
|
/** Upload files with another profile than the list's own, e.g. from a role chip. */
|
|
45
51
|
declare function uploadFiles(files: File[], opts?: {
|
|
@@ -85,8 +91,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
85
91
|
};
|
|
86
92
|
collectionName: string | null;
|
|
87
93
|
profile: string | null;
|
|
94
|
+
backend: string | null;
|
|
88
95
|
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
89
96
|
serviceBody: Record<string, unknown> | null;
|
|
97
|
+
profileFor: (file: File) => string | null | undefined;
|
|
90
98
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
91
99
|
hint: string;
|
|
92
100
|
picker: boolean;
|
|
@@ -97,6 +105,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
97
105
|
recordName: string;
|
|
98
106
|
badges: (assetId: string) => string[];
|
|
99
107
|
tileMenu: (assetId: string, kind: string | null) => DropdownMenuItem[][];
|
|
108
|
+
onRouteFiles: (files: File[]) => void;
|
|
100
109
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
101
110
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
102
111
|
declare const _default: typeof __VLS_export;
|
|
@@ -24,13 +24,16 @@ 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
|
+
backend: { type: [String, null], required: false, default: null },
|
|
27
28
|
service: { type: null, required: false, default: void 0 },
|
|
28
29
|
serviceBody: { type: [Object, null], required: false, default: null },
|
|
29
30
|
removeLabel: { type: String, required: false, default: void 0 },
|
|
30
31
|
badges: { type: Function, required: false, default: void 0 },
|
|
31
32
|
tileMenu: { type: Function, required: false, default: void 0 },
|
|
32
33
|
highlight: { type: [Set, null], required: false, default: null },
|
|
33
|
-
onAdd: { type: Function, required: false, default: void 0 }
|
|
34
|
+
onAdd: { type: Function, required: false, default: void 0 },
|
|
35
|
+
onRouteFiles: { type: Function, required: false, default: void 0 },
|
|
36
|
+
profileFor: { type: Function, required: false, default: void 0 }
|
|
34
37
|
});
|
|
35
38
|
const model = defineModel({ type: Array, ...{ default: () => [] } });
|
|
36
39
|
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
@@ -43,8 +46,10 @@ const gallery = useMLMediaGallery(model, {
|
|
|
43
46
|
processingConfig: props.processingConfig,
|
|
44
47
|
collectionName: props.collectionName,
|
|
45
48
|
profile: props.profile,
|
|
49
|
+
backend: props.backend,
|
|
46
50
|
service: props.service,
|
|
47
51
|
serviceBody: props.serviceBody,
|
|
52
|
+
label: props.recordName ? `${props.title} of ${props.recordName}` : props.title,
|
|
48
53
|
onUploaded: (id, sessionId) => emit("uploaded", id, sessionId),
|
|
49
54
|
onAdd: (id, tag) => props.onAdd?.(id, tag)
|
|
50
55
|
});
|
|
@@ -126,6 +131,8 @@ async function pickExisting() {
|
|
|
126
131
|
processingConfig: props.processingConfig,
|
|
127
132
|
collectionName: props.collectionName,
|
|
128
133
|
profile: props.profile,
|
|
134
|
+
profileFor: props.profileFor,
|
|
135
|
+
backend: props.backend,
|
|
129
136
|
service: props.service,
|
|
130
137
|
serviceBody: props.serviceBody
|
|
131
138
|
},
|
|
@@ -199,6 +206,10 @@ function uploadFiles(files, opts = {}) {
|
|
|
199
206
|
if (rejected.length) gallery.onRejected(rejected);
|
|
200
207
|
if (accepted.length) gallery.onFiles(accepted, { profile: opts.profile, tag: opts.tag });
|
|
201
208
|
}
|
|
209
|
+
function onDropFiles(files) {
|
|
210
|
+
if (props.onRouteFiles) props.onRouteFiles(files);
|
|
211
|
+
else gallery.onFiles(files);
|
|
212
|
+
}
|
|
202
213
|
defineExpose({ uploadFiles });
|
|
203
214
|
function moveAndAnnounce(from, to) {
|
|
204
215
|
const id = model.value[from];
|
|
@@ -279,7 +290,7 @@ async function onTileKey(e, index) {
|
|
|
279
290
|
:disabled="disabled || full"
|
|
280
291
|
title="Drop to add"
|
|
281
292
|
class="rounded-lg"
|
|
282
|
-
@files="
|
|
293
|
+
@files="onDropFiles"
|
|
283
294
|
@rejected="gallery.onRejected"
|
|
284
295
|
>
|
|
285
296
|
<template #default="{ open }">
|
|
@@ -26,7 +26,9 @@ 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
|
-
/**
|
|
29
|
+
/** Registered backend uploads go to (`mediaLibrary.backends`). Wins over `service`. */
|
|
30
|
+
backend?: string | null;
|
|
31
|
+
/** Older name for `backend`; also takes an inline `{ start }`. See useMLMediaUpload. */
|
|
30
32
|
service?: MediaUploadOptions['service'];
|
|
31
33
|
/** Extra fields for the service's start request, e.g. `{ context: { lessonId } }`. */
|
|
32
34
|
serviceBody?: Record<string, unknown> | null;
|
|
@@ -40,6 +42,10 @@ type __VLS_Props = {
|
|
|
40
42
|
highlight?: ReadonlySet<string> | null;
|
|
41
43
|
/** See useMLMediaGallery's onAdd. */
|
|
42
44
|
onAdd?: (assetId: string, tag: string | undefined) => void;
|
|
45
|
+
/** Take new files instead of uploading them here (a gallery with roles routes them by kind). */
|
|
46
|
+
onRouteFiles?: (files: File[]) => void;
|
|
47
|
+
/** Upload profile per file, for uploads inside the picker. */
|
|
48
|
+
profileFor?: (file: File) => string | null | undefined;
|
|
43
49
|
};
|
|
44
50
|
/** Upload files with another profile than the list's own, e.g. from a role chip. */
|
|
45
51
|
declare function uploadFiles(files: File[], opts?: {
|
|
@@ -85,8 +91,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
85
91
|
};
|
|
86
92
|
collectionName: string | null;
|
|
87
93
|
profile: string | null;
|
|
94
|
+
backend: string | null;
|
|
88
95
|
service: string | import("../../index.js").MediaServiceUpload | null;
|
|
89
96
|
serviceBody: Record<string, unknown> | null;
|
|
97
|
+
profileFor: (file: File) => string | null | undefined;
|
|
90
98
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
91
99
|
hint: string;
|
|
92
100
|
picker: boolean;
|
|
@@ -97,6 +105,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
97
105
|
recordName: string;
|
|
98
106
|
badges: (assetId: string) => string[];
|
|
99
107
|
tileMenu: (assetId: string, kind: string | null) => DropdownMenuItem[][];
|
|
108
|
+
onRouteFiles: (files: File[]) => void;
|
|
100
109
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
101
110
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
102
111
|
declare const _default: typeof __VLS_export;
|