nuxt-media-library 0.0.62 → 0.0.64
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 +1 -1
- package/dist/module.json +1 -1
- package/dist/runtime/components/MLMedia/Field.d.vue.ts +6 -0
- package/dist/runtime/components/MLMedia/Field.vue +2 -0
- package/dist/runtime/components/MLMedia/Field.vue.d.ts +6 -0
- package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +2 -0
- package/dist/runtime/components/MLMedia/FilePicker.vue +2 -0
- package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +2 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +6 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue +5 -1
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +6 -0
- package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +17 -0
- package/dist/runtime/components/MLMedia/Gallery.vue +56 -7
- package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +17 -0
- package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +5 -0
- package/dist/runtime/components/MLMedia/GallerySection.vue +8 -3
- package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +5 -0
- package/dist/runtime/composables/useMLFilePicker.d.ts +6 -0
- package/dist/runtime/composables/useMLMediaBrowse.d.ts +25 -5
- package/dist/runtime/composables/useMLMediaBrowse.js +26 -0
- package/dist/runtime/composables/useMLMediaRenewal.d.ts +6 -1
- package/dist/runtime/composables/useMLMediaRenewal.js +15 -4
- package/dist/runtime/utils/galleryRoles.d.ts +7 -0
- package/dist/runtime/utils/galleryRoles.js +11 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -217,7 +217,7 @@ The module auto-imports the helpers that read it (a structural type,
|
|
|
217
217
|
| `mlMediaSrcset(media)` | `srcset` from `media.sources`, or `undefined` |
|
|
218
218
|
| `mlPlayableSource(media, { preferFile? })` | `{ src, type: 'hls' \| 'progressive' }`: the HLS ladder, else the file; `null` when locked or not ready |
|
|
219
219
|
| `mlIsPlayableVideo(media)` | a video the viewer can play now |
|
|
220
|
-
| `useMLMediaRenewal(media, renew, { marginSeconds? })` | calls `renew` 5 minutes (default) before tokened links expire; browser only |
|
|
220
|
+
| `useMLMediaRenewal(media, renew, { marginSeconds?, retrySeconds? })` | calls `renew` 5 minutes (default) before tokened links expire, and again every 30 s (default) while it fails; browser only |
|
|
221
221
|
|
|
222
222
|
```vue
|
|
223
223
|
<script setup lang="ts">
|
package/dist/module.json
CHANGED
|
@@ -32,6 +32,11 @@ type __VLS_Props = {
|
|
|
32
32
|
picker?: boolean;
|
|
33
33
|
/** Replaces the built-in picker, e.g. with the host's own dialog. */
|
|
34
34
|
onPick?: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* The picker lists only these asset IDs (resolved through the backend's
|
|
37
|
+
* `resolve` route) instead of the library. For backends without a library listing.
|
|
38
|
+
*/
|
|
39
|
+
pickFrom?: readonly string[] | null;
|
|
35
40
|
/** Show the eye button on the thumbnail, which opens the media editor. */
|
|
36
41
|
editor?: boolean;
|
|
37
42
|
/** Label for the editor's remove button. Empty hides it. */
|
|
@@ -78,6 +83,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
78
83
|
serviceBody: Record<string, unknown> | null;
|
|
79
84
|
accept: readonly string[] | string;
|
|
80
85
|
maxSize: number | null;
|
|
86
|
+
pickFrom: readonly string[] | null;
|
|
81
87
|
required: boolean;
|
|
82
88
|
removeLabel: string;
|
|
83
89
|
hint: string;
|
|
@@ -22,6 +22,7 @@ const props = defineProps({
|
|
|
22
22
|
serviceBody: { type: [Object, null], required: false, default: null },
|
|
23
23
|
picker: { type: Boolean, required: false, default: true },
|
|
24
24
|
onPick: { type: Function, required: false, default: void 0 },
|
|
25
|
+
pickFrom: { type: [Array, null], required: false, default: null },
|
|
25
26
|
editor: { type: Boolean, required: false, default: true },
|
|
26
27
|
removeLabel: { type: String, required: false, default: "Remove from this field" },
|
|
27
28
|
onOpen: { type: Function, required: false, default: void 0 }
|
|
@@ -101,6 +102,7 @@ async function pickExisting() {
|
|
|
101
102
|
selected: [model.value],
|
|
102
103
|
title: props.label ? `Select ${props.label.toLowerCase()}` : "Select file",
|
|
103
104
|
maxSize: props.maxSize,
|
|
105
|
+
pickFrom: props.pickFrom,
|
|
104
106
|
upload: uploadOptions.value,
|
|
105
107
|
onUploaded: (id, sessionId) => sessions.set(id, sessionId)
|
|
106
108
|
});
|
|
@@ -32,6 +32,11 @@ type __VLS_Props = {
|
|
|
32
32
|
picker?: boolean;
|
|
33
33
|
/** Replaces the built-in picker, e.g. with the host's own dialog. */
|
|
34
34
|
onPick?: () => void;
|
|
35
|
+
/**
|
|
36
|
+
* The picker lists only these asset IDs (resolved through the backend's
|
|
37
|
+
* `resolve` route) instead of the library. For backends without a library listing.
|
|
38
|
+
*/
|
|
39
|
+
pickFrom?: readonly string[] | null;
|
|
35
40
|
/** Show the eye button on the thumbnail, which opens the media editor. */
|
|
36
41
|
editor?: boolean;
|
|
37
42
|
/** Label for the editor's remove button. Empty hides it. */
|
|
@@ -78,6 +83,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
78
83
|
serviceBody: Record<string, unknown> | null;
|
|
79
84
|
accept: readonly string[] | string;
|
|
80
85
|
maxSize: number | null;
|
|
86
|
+
pickFrom: readonly string[] | null;
|
|
81
87
|
required: boolean;
|
|
82
88
|
removeLabel: string;
|
|
83
89
|
hint: string;
|
|
@@ -5,6 +5,7 @@ type __VLS_Props = {
|
|
|
5
5
|
selected?: readonly string[];
|
|
6
6
|
title?: string;
|
|
7
7
|
maxSize?: number | null;
|
|
8
|
+
pickFrom?: readonly string[] | null;
|
|
8
9
|
upload?: MediaUploadOptions;
|
|
9
10
|
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
10
11
|
};
|
|
@@ -19,6 +20,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
19
20
|
max: number;
|
|
20
21
|
selected: readonly string[];
|
|
21
22
|
maxSize: number | null;
|
|
23
|
+
pickFrom: readonly string[] | null;
|
|
22
24
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
23
25
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
26
|
declare const _default: typeof __VLS_export;
|
|
@@ -5,6 +5,7 @@ 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
|
+
pickFrom: { type: [Array, null], required: false, default: null },
|
|
8
9
|
upload: { type: Object, required: false, default: () => ({}) },
|
|
9
10
|
onUploaded: { type: Function, required: false, default: void 0 }
|
|
10
11
|
});
|
|
@@ -25,6 +26,7 @@ const emit = defineEmits(["close"]);
|
|
|
25
26
|
:max="max"
|
|
26
27
|
:selected="selected"
|
|
27
28
|
:max-size="maxSize"
|
|
29
|
+
:pick-from="pickFrom"
|
|
28
30
|
:upload="upload"
|
|
29
31
|
:on-uploaded="onUploaded"
|
|
30
32
|
@done="(ids) => emit('close', ids)"
|
|
@@ -5,6 +5,7 @@ type __VLS_Props = {
|
|
|
5
5
|
selected?: readonly string[];
|
|
6
6
|
title?: string;
|
|
7
7
|
maxSize?: number | null;
|
|
8
|
+
pickFrom?: readonly string[] | null;
|
|
8
9
|
upload?: MediaUploadOptions;
|
|
9
10
|
onUploaded?: (assetId: string, sessionId: string | null) => void;
|
|
10
11
|
};
|
|
@@ -19,6 +20,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
19
20
|
max: number;
|
|
20
21
|
selected: readonly string[];
|
|
21
22
|
maxSize: number | null;
|
|
23
|
+
pickFrom: readonly string[] | null;
|
|
22
24
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
23
25
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
26
|
declare const _default: typeof __VLS_export;
|
|
@@ -9,6 +9,11 @@ type __VLS_Props = {
|
|
|
9
9
|
title?: string;
|
|
10
10
|
/** Bytes, for uploads from the picker. */
|
|
11
11
|
maxSize?: number | null;
|
|
12
|
+
/**
|
|
13
|
+
* List only these asset IDs (resolved through `upload.backend`'s `resolve`
|
|
14
|
+
* route) instead of the library. Search and the kind filter run here.
|
|
15
|
+
*/
|
|
16
|
+
pickFrom?: readonly string[] | null;
|
|
12
17
|
/** Upload session settings for files dropped into the picker. */
|
|
13
18
|
upload?: MediaUploadOptions;
|
|
14
19
|
/** A file uploaded from the picker exists in the library (with its upload session). */
|
|
@@ -29,6 +34,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
29
34
|
max: number;
|
|
30
35
|
selected: readonly string[];
|
|
31
36
|
maxSize: number | null;
|
|
37
|
+
pickFrom: readonly string[] | null;
|
|
32
38
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
33
39
|
bodyClass: string;
|
|
34
40
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -14,6 +14,7 @@ const props = defineProps({
|
|
|
14
14
|
selected: { type: Array, required: false, default: () => [] },
|
|
15
15
|
title: { type: String, required: false, default: void 0 },
|
|
16
16
|
maxSize: { type: [Number, null], required: false, default: null },
|
|
17
|
+
pickFrom: { type: [Array, null], required: false, default: null },
|
|
17
18
|
upload: { type: Object, required: false, default: () => ({}) },
|
|
18
19
|
onUploaded: { type: Function, required: false, default: void 0 },
|
|
19
20
|
bodyClass: { type: String, required: false, default: "max-h-[min(60vh,560px)]" }
|
|
@@ -32,7 +33,10 @@ const kindOptions = computed(
|
|
|
32
33
|
() => (acceptKinds.value.length ? acceptKinds.value : ALL_KINDS).map((k) => ({ label: assetKindMeta(k).label, value: k }))
|
|
33
34
|
);
|
|
34
35
|
const filtering = computed(() => !!search.value.trim() || kindFilter.value.length > 0);
|
|
35
|
-
const browse = useMLMediaBrowse(
|
|
36
|
+
const browse = useMLMediaBrowse(
|
|
37
|
+
() => ({ kind: kindFilter.value[0] ?? null, query: search.value }),
|
|
38
|
+
props.pickFrom ? { ids: () => props.pickFrom ?? [], backend: () => props.upload?.backend } : {}
|
|
39
|
+
);
|
|
36
40
|
const initial = [...props.selected];
|
|
37
41
|
const chosen = ref([...initial]);
|
|
38
42
|
const limitNote = ref(null);
|
|
@@ -9,6 +9,11 @@ type __VLS_Props = {
|
|
|
9
9
|
title?: string;
|
|
10
10
|
/** Bytes, for uploads from the picker. */
|
|
11
11
|
maxSize?: number | null;
|
|
12
|
+
/**
|
|
13
|
+
* List only these asset IDs (resolved through `upload.backend`'s `resolve`
|
|
14
|
+
* route) instead of the library. Search and the kind filter run here.
|
|
15
|
+
*/
|
|
16
|
+
pickFrom?: readonly string[] | null;
|
|
12
17
|
/** Upload session settings for files dropped into the picker. */
|
|
13
18
|
upload?: MediaUploadOptions;
|
|
14
19
|
/** A file uploaded from the picker exists in the library (with its upload session). */
|
|
@@ -29,6 +34,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {
|
|
|
29
34
|
max: number;
|
|
30
35
|
selected: readonly string[];
|
|
31
36
|
maxSize: number | null;
|
|
37
|
+
pickFrom: readonly string[] | null;
|
|
32
38
|
onUploaded: (assetId: string, sessionId: string | null) => void;
|
|
33
39
|
bodyClass: string;
|
|
34
40
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -47,6 +47,12 @@ type __VLS_Props = {
|
|
|
47
47
|
serviceBody?: Record<string, unknown> | null;
|
|
48
48
|
picker?: boolean;
|
|
49
49
|
onPick?: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* The picker lists only these asset IDs (resolved through the backend's
|
|
52
|
+
* `resolve` route, searched and filtered here) instead of the library.
|
|
53
|
+
* For backends without a library listing.
|
|
54
|
+
*/
|
|
55
|
+
pickFrom?: readonly string[] | null;
|
|
50
56
|
editor?: boolean;
|
|
51
57
|
onOpen?: (assetId: string) => void;
|
|
52
58
|
};
|
|
@@ -58,6 +64,12 @@ type __VLS_ModelProps = {
|
|
|
58
64
|
* extension, MIME type, kind, size, duration, dimensions. Output only.
|
|
59
65
|
*/
|
|
60
66
|
'meta'?: Record<string, MediaFileMeta>;
|
|
67
|
+
/**
|
|
68
|
+
* With `profiles`: files in the grid that no role holds (replaced out of
|
|
69
|
+
* their last role, or unchecked). They are not in `update:profiles`, so the
|
|
70
|
+
* page's save drops them; warn before saving. Set [] to clear.
|
|
71
|
+
*/
|
|
72
|
+
'unplaced'?: string[];
|
|
61
73
|
};
|
|
62
74
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
63
75
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -66,14 +78,18 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
66
78
|
"update:modelValue": (value: string[]) => any;
|
|
67
79
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
68
80
|
"update:meta": (value: Record<string, MediaFileMeta>) => any;
|
|
81
|
+
picked: (assetIds: string[], role: string) => any;
|
|
69
82
|
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
83
|
+
"update:unplaced": (value: string[]) => any;
|
|
70
84
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
71
85
|
onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
|
|
72
86
|
onMeta?: ((assetId: string, meta: MediaFileMeta | null) => any) | undefined;
|
|
73
87
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
74
88
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
75
89
|
"onUpdate:meta"?: ((value: Record<string, MediaFileMeta>) => any) | undefined;
|
|
90
|
+
onPicked?: ((assetIds: string[], role: string) => any) | undefined;
|
|
76
91
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
92
|
+
"onUpdate:unplaced"?: ((value: string[]) => any) | undefined;
|
|
77
93
|
}>, {
|
|
78
94
|
title: string;
|
|
79
95
|
visibility: "private" | "restricted" | "public";
|
|
@@ -91,6 +107,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
91
107
|
accept: readonly string[] | string;
|
|
92
108
|
max: number | null;
|
|
93
109
|
maxSize: number | null;
|
|
110
|
+
pickFrom: readonly string[] | null;
|
|
94
111
|
hint: string;
|
|
95
112
|
picker: boolean;
|
|
96
113
|
onPick: () => void;
|
|
@@ -6,6 +6,7 @@ import { watchMediaMeta } from "../../composables/useMLMediaMeta";
|
|
|
6
6
|
import { kindForFile } from "../../utils/fileAccept";
|
|
7
7
|
import { backendsFromConfig, resolveAssetsUrl } from "../../utils/backends";
|
|
8
8
|
import { GALLERY_TILE_TYPE } from "../../utils/galleryDrag";
|
|
9
|
+
import { nextUnplaced } from "../../utils/galleryRoles";
|
|
9
10
|
import GallerySection from "./GallerySection.vue";
|
|
10
11
|
const props = defineProps({
|
|
11
12
|
profiles: { type: [Object, null], required: false, default: null },
|
|
@@ -27,13 +28,23 @@ const props = defineProps({
|
|
|
27
28
|
serviceBody: { type: [Object, null], required: false, default: null },
|
|
28
29
|
picker: { type: Boolean, required: false, default: true },
|
|
29
30
|
onPick: { type: Function, required: false, default: void 0 },
|
|
31
|
+
pickFrom: { type: [Array, null], required: false, default: null },
|
|
30
32
|
editor: { type: Boolean, required: false, default: true },
|
|
31
33
|
onOpen: { type: Function, required: false, default: void 0 }
|
|
32
34
|
});
|
|
33
35
|
const model = defineModel({ type: Array, ...{ default: () => [] } });
|
|
34
36
|
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
35
37
|
const meta = defineModel("meta", { type: Object, ...{ default: () => ({}) } });
|
|
36
|
-
const
|
|
38
|
+
const unplacedModel = defineModel("unplaced", { type: Array, ...{ default: () => [] } });
|
|
39
|
+
const unplaced = ref([...unplacedModel.value]);
|
|
40
|
+
const sameIds = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
|
|
41
|
+
watch(unplacedModel, (v) => {
|
|
42
|
+
if (!sameIds(v, unplaced.value)) unplaced.value = [...v];
|
|
43
|
+
});
|
|
44
|
+
watch(unplaced, (v) => {
|
|
45
|
+
if (!sameIds(v, unplacedModel.value)) unplacedModel.value = [...v];
|
|
46
|
+
});
|
|
47
|
+
const emit = defineEmits(["uploaded", "picked", "update:profiles", "meta"]);
|
|
37
48
|
const sectionMeta = ref({});
|
|
38
49
|
watchMediaMeta(computed(() => sectionMeta.value), (all) => {
|
|
39
50
|
meta.value = all;
|
|
@@ -52,16 +63,22 @@ watch(() => props.profiles, (p) => {
|
|
|
52
63
|
const next = {};
|
|
53
64
|
for (const [key, r] of Object.entries(p ?? {})) next[key] = [...r.selected ?? []];
|
|
54
65
|
selections.value = next;
|
|
66
|
+
const placed = new Set(Object.values(next).flat());
|
|
67
|
+
if (unplaced.value.some((id) => placed.has(id))) unplaced.value = unplaced.value.filter((id) => !placed.has(id));
|
|
55
68
|
}, { immediate: true, deep: true });
|
|
56
69
|
const sel = (key) => selections.value[key] ?? [];
|
|
57
70
|
const union = computed(() => {
|
|
58
71
|
const seen = /* @__PURE__ */ new Set();
|
|
59
72
|
const ordered = [...roles.value.filter(single), ...roles.value.filter((r) => !single(r))];
|
|
60
73
|
for (const r of ordered) for (const id of sel(r.key)) seen.add(id);
|
|
74
|
+
for (const id of unplaced.value) seen.add(id);
|
|
61
75
|
return [...seen];
|
|
62
76
|
});
|
|
63
|
-
function commit(changes) {
|
|
77
|
+
function commit(changes, removed = /* @__PURE__ */ new Set()) {
|
|
78
|
+
const before = selections.value;
|
|
64
79
|
selections.value = { ...selections.value, ...changes };
|
|
80
|
+
const left = nextUnplaced(before, selections.value, unplaced.value, removed);
|
|
81
|
+
if (!sameIds(left, unplaced.value)) unplaced.value = left;
|
|
65
82
|
const out = {};
|
|
66
83
|
for (const [k, r] of Object.entries(props.profiles ?? {})) out[k] = { ...r, selected: [...sel(k)] };
|
|
67
84
|
emit("update:profiles", out);
|
|
@@ -174,21 +191,44 @@ async function placeByKind(ids) {
|
|
|
174
191
|
}
|
|
175
192
|
}
|
|
176
193
|
if (Object.keys(changes).length) commit(changes);
|
|
194
|
+
for (const id of ids) awaitingKind.delete(id);
|
|
195
|
+
const picked = ids.filter((id) => pendingPicked.delete(id));
|
|
196
|
+
if (picked.length) emitPicked(picked);
|
|
177
197
|
}
|
|
178
198
|
const pendingEmit = /* @__PURE__ */ new Map();
|
|
199
|
+
const awaitingKind = /* @__PURE__ */ new Set();
|
|
200
|
+
const pendingPicked = /* @__PURE__ */ new Set();
|
|
201
|
+
function emitPicked(ids) {
|
|
202
|
+
const byRole = /* @__PURE__ */ new Map();
|
|
203
|
+
for (const id of ids) {
|
|
204
|
+
const role = roles.value.find((r) => sel(r.key).includes(id));
|
|
205
|
+
if (role) byRole.set(role.key, [...byRole.get(role.key) ?? [], id]);
|
|
206
|
+
}
|
|
207
|
+
for (const [key, list] of byRole) emit("picked", list, key);
|
|
208
|
+
}
|
|
209
|
+
function onPicked(ids) {
|
|
210
|
+
const now = [];
|
|
211
|
+
for (const id of ids) {
|
|
212
|
+
if (awaitingKind.has(id)) pendingPicked.add(id);
|
|
213
|
+
else now.push(id);
|
|
214
|
+
}
|
|
215
|
+
if (now.length) emitPicked(now);
|
|
216
|
+
}
|
|
179
217
|
function onUnionUpdate(next) {
|
|
180
218
|
notice.value = null;
|
|
181
219
|
const keep = new Set(next);
|
|
182
220
|
const before = new Set(union.value);
|
|
221
|
+
const removed = new Set(union.value.filter((id) => !keep.has(id)));
|
|
183
222
|
const changes = {};
|
|
184
223
|
for (const r of roles.value) changes[r.key] = sel(r.key).filter((id) => keep.has(id));
|
|
185
|
-
const
|
|
224
|
+
const needKind = [];
|
|
186
225
|
for (const id of next) {
|
|
187
226
|
if (before.has(id)) continue;
|
|
188
227
|
const tagged = pendingRole.get(id);
|
|
189
228
|
pendingRole.delete(id);
|
|
190
229
|
if (!tagged && kindMatters.value) {
|
|
191
|
-
|
|
230
|
+
needKind.push(id);
|
|
231
|
+
awaitingKind.add(id);
|
|
192
232
|
continue;
|
|
193
233
|
}
|
|
194
234
|
const key = tagged ?? defaultKey.value;
|
|
@@ -199,8 +239,10 @@ function onUnionUpdate(next) {
|
|
|
199
239
|
for (const r of roles.value) {
|
|
200
240
|
if (!single(r)) changes[r.key] = [...changes[r.key]].sort((a, b) => (pos.get(a) ?? 0) - (pos.get(b) ?? 0));
|
|
201
241
|
}
|
|
202
|
-
|
|
203
|
-
if (unplaced.
|
|
242
|
+
const order = unplaced.value.filter((id) => keep.has(id)).sort((a, b) => (pos.get(a) ?? 0) - (pos.get(b) ?? 0));
|
|
243
|
+
if (!sameIds(order, unplaced.value)) unplaced.value = order;
|
|
244
|
+
commit(changes, removed);
|
|
245
|
+
if (needKind.length) void placeByKind(needKind);
|
|
204
246
|
}
|
|
205
247
|
function onAdd(id, tag) {
|
|
206
248
|
const key = tag ?? defaultKey.value;
|
|
@@ -217,7 +259,10 @@ function onUploaded(id, sessionId) {
|
|
|
217
259
|
}
|
|
218
260
|
emit("uploaded", id, key ?? defaultKey.value, sessionId);
|
|
219
261
|
}
|
|
220
|
-
const badges = (id) =>
|
|
262
|
+
const badges = (id) => {
|
|
263
|
+
const labels = roles.value.filter((r) => sel(r.key).includes(id)).map((r) => r.label);
|
|
264
|
+
return labels.length || !unplaced.value.includes(id) ? labels : ["Not used"];
|
|
265
|
+
};
|
|
221
266
|
function tileMenu(id, kind) {
|
|
222
267
|
return [
|
|
223
268
|
[
|
|
@@ -297,6 +342,7 @@ function chipCount(key, max) {
|
|
|
297
342
|
:collection-name="collectionName"
|
|
298
343
|
:picker="picker"
|
|
299
344
|
:on-pick="onPick"
|
|
345
|
+
:pick-from="pickFrom"
|
|
300
346
|
:editor="editor"
|
|
301
347
|
:on-open="onOpen"
|
|
302
348
|
:badges="badges"
|
|
@@ -305,6 +351,7 @@ function chipCount(key, max) {
|
|
|
305
351
|
:on-add="onAdd"
|
|
306
352
|
@update:model-value="onUnionUpdate"
|
|
307
353
|
@uploaded="onUploaded"
|
|
354
|
+
@picked="onPicked"
|
|
308
355
|
@meta="(m) => sectionMeta = m"
|
|
309
356
|
>
|
|
310
357
|
<template #toolbar>
|
|
@@ -370,9 +417,11 @@ function chipCount(key, max) {
|
|
|
370
417
|
:service-body="serviceBody"
|
|
371
418
|
:picker="picker"
|
|
372
419
|
:on-pick="onPick"
|
|
420
|
+
:pick-from="pickFrom"
|
|
373
421
|
:editor="editor"
|
|
374
422
|
:on-open="onOpen"
|
|
375
423
|
@uploaded="(id, sessionId) => emit('uploaded', id, '', sessionId)"
|
|
424
|
+
@picked="(ids) => emit('picked', ids, '')"
|
|
376
425
|
@meta="(m) => sectionMeta = m"
|
|
377
426
|
/>
|
|
378
427
|
</template>
|
|
@@ -47,6 +47,12 @@ type __VLS_Props = {
|
|
|
47
47
|
serviceBody?: Record<string, unknown> | null;
|
|
48
48
|
picker?: boolean;
|
|
49
49
|
onPick?: () => void;
|
|
50
|
+
/**
|
|
51
|
+
* The picker lists only these asset IDs (resolved through the backend's
|
|
52
|
+
* `resolve` route, searched and filtered here) instead of the library.
|
|
53
|
+
* For backends without a library listing.
|
|
54
|
+
*/
|
|
55
|
+
pickFrom?: readonly string[] | null;
|
|
50
56
|
editor?: boolean;
|
|
51
57
|
onOpen?: (assetId: string) => void;
|
|
52
58
|
};
|
|
@@ -58,6 +64,12 @@ type __VLS_ModelProps = {
|
|
|
58
64
|
* extension, MIME type, kind, size, duration, dimensions. Output only.
|
|
59
65
|
*/
|
|
60
66
|
'meta'?: Record<string, MediaFileMeta>;
|
|
67
|
+
/**
|
|
68
|
+
* With `profiles`: files in the grid that no role holds (replaced out of
|
|
69
|
+
* their last role, or unchecked). They are not in `update:profiles`, so the
|
|
70
|
+
* page's save drops them; warn before saving. Set [] to clear.
|
|
71
|
+
*/
|
|
72
|
+
'unplaced'?: string[];
|
|
61
73
|
};
|
|
62
74
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
63
75
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
@@ -66,14 +78,18 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
66
78
|
"update:modelValue": (value: string[]) => any;
|
|
67
79
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
68
80
|
"update:meta": (value: Record<string, MediaFileMeta>) => any;
|
|
81
|
+
picked: (assetIds: string[], role: string) => any;
|
|
69
82
|
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
83
|
+
"update:unplaced": (value: string[]) => any;
|
|
70
84
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
71
85
|
onUploaded?: ((assetId: string, role: string, sessionId: string | null) => any) | undefined;
|
|
72
86
|
onMeta?: ((assetId: string, meta: MediaFileMeta | null) => any) | undefined;
|
|
73
87
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
74
88
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
75
89
|
"onUpdate:meta"?: ((value: Record<string, MediaFileMeta>) => any) | undefined;
|
|
90
|
+
onPicked?: ((assetIds: string[], role: string) => any) | undefined;
|
|
76
91
|
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
92
|
+
"onUpdate:unplaced"?: ((value: string[]) => any) | undefined;
|
|
77
93
|
}>, {
|
|
78
94
|
title: string;
|
|
79
95
|
visibility: "private" | "restricted" | "public";
|
|
@@ -91,6 +107,7 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
|
|
|
91
107
|
accept: readonly string[] | string;
|
|
92
108
|
max: number | null;
|
|
93
109
|
maxSize: number | null;
|
|
110
|
+
pickFrom: readonly string[] | null;
|
|
94
111
|
hint: string;
|
|
95
112
|
picker: boolean;
|
|
96
113
|
onPick: () => void;
|
|
@@ -22,6 +22,8 @@ type __VLS_Props = {
|
|
|
22
22
|
/** Show "Select existing", which opens the library picker. */
|
|
23
23
|
picker?: boolean;
|
|
24
24
|
onPick?: () => void;
|
|
25
|
+
/** The picker lists only these asset IDs instead of the library. */
|
|
26
|
+
pickFrom?: readonly string[] | null;
|
|
25
27
|
/** Show the eye on each tile, which opens the media editor. */
|
|
26
28
|
editor?: boolean;
|
|
27
29
|
onOpen?: (assetId: string) => void;
|
|
@@ -73,11 +75,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
73
75
|
meta: (meta: Record<string, MediaFileMeta>) => any;
|
|
74
76
|
"update:modelValue": (value: string[]) => any;
|
|
75
77
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
78
|
+
picked: (assetIds: string[]) => any;
|
|
76
79
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
77
80
|
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
78
81
|
onMeta?: ((meta: Record<string, MediaFileMeta>) => any) | undefined;
|
|
79
82
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
80
83
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
84
|
+
onPicked?: ((assetIds: string[]) => any) | undefined;
|
|
81
85
|
}>, {
|
|
82
86
|
title: string;
|
|
83
87
|
visibility: "private" | "restricted" | "public";
|
|
@@ -97,6 +101,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
97
101
|
accept: readonly string[] | string;
|
|
98
102
|
max: number | null;
|
|
99
103
|
maxSize: number | null;
|
|
104
|
+
pickFrom: readonly string[] | null;
|
|
100
105
|
removeLabel: string;
|
|
101
106
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
102
107
|
hint: string;
|
|
@@ -22,6 +22,7 @@ const props = defineProps({
|
|
|
22
22
|
collectionName: { type: [String, null], required: false, default: null },
|
|
23
23
|
picker: { type: Boolean, required: false, default: true },
|
|
24
24
|
onPick: { type: Function, required: false, default: void 0 },
|
|
25
|
+
pickFrom: { type: [Array, null], required: false, default: null },
|
|
25
26
|
editor: { type: Boolean, required: false, default: true },
|
|
26
27
|
onOpen: { type: Function, required: false, default: void 0 },
|
|
27
28
|
profile: { type: [String, null], required: false, default: null },
|
|
@@ -38,7 +39,7 @@ const props = defineProps({
|
|
|
38
39
|
});
|
|
39
40
|
const model = defineModel({ type: Array, ...{ default: () => [] } });
|
|
40
41
|
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
41
|
-
const emit = defineEmits(["uploaded", "meta"]);
|
|
42
|
+
const emit = defineEmits(["uploaded", "picked", "meta"]);
|
|
42
43
|
const gallery = useMLMediaGallery(model, {
|
|
43
44
|
accept: () => props.accept,
|
|
44
45
|
max: () => props.max,
|
|
@@ -132,6 +133,7 @@ async function pickExisting() {
|
|
|
132
133
|
selected: model.value,
|
|
133
134
|
title: `Select ${props.title.toLowerCase()}`,
|
|
134
135
|
maxSize: props.maxSize,
|
|
136
|
+
pickFrom: props.pickFrom,
|
|
135
137
|
upload: {
|
|
136
138
|
visibility: props.visibility,
|
|
137
139
|
processingConfig: props.processingConfig,
|
|
@@ -146,9 +148,12 @@ async function pickExisting() {
|
|
|
146
148
|
});
|
|
147
149
|
if (!ids) return;
|
|
148
150
|
gallery.setFromPicker(ids);
|
|
149
|
-
|
|
150
|
-
|
|
151
|
+
const added = ids.filter((id) => !before.has(id));
|
|
152
|
+
for (const id of added) {
|
|
153
|
+
if (sessions.has(id)) emit("uploaded", id, sessions.get(id) ?? null);
|
|
151
154
|
}
|
|
155
|
+
const picked = added.filter((id) => !sessions.has(id));
|
|
156
|
+
if (picked.length) emit("picked", picked);
|
|
152
157
|
}
|
|
153
158
|
function openFor(item) {
|
|
154
159
|
const id = item.id;
|
|
@@ -22,6 +22,8 @@ type __VLS_Props = {
|
|
|
22
22
|
/** Show "Select existing", which opens the library picker. */
|
|
23
23
|
picker?: boolean;
|
|
24
24
|
onPick?: () => void;
|
|
25
|
+
/** The picker lists only these asset IDs instead of the library. */
|
|
26
|
+
pickFrom?: readonly string[] | null;
|
|
25
27
|
/** Show the eye on each tile, which opens the media editor. */
|
|
26
28
|
editor?: boolean;
|
|
27
29
|
onOpen?: (assetId: string) => void;
|
|
@@ -73,11 +75,13 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
73
75
|
meta: (meta: Record<string, MediaFileMeta>) => any;
|
|
74
76
|
"update:modelValue": (value: string[]) => any;
|
|
75
77
|
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
78
|
+
picked: (assetIds: string[]) => any;
|
|
76
79
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
77
80
|
onUploaded?: ((assetId: string, sessionId: string | null) => any) | undefined;
|
|
78
81
|
onMeta?: ((meta: Record<string, MediaFileMeta>) => any) | undefined;
|
|
79
82
|
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
80
83
|
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
84
|
+
onPicked?: ((assetIds: string[]) => any) | undefined;
|
|
81
85
|
}>, {
|
|
82
86
|
title: string;
|
|
83
87
|
visibility: "private" | "restricted" | "public";
|
|
@@ -97,6 +101,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
97
101
|
accept: readonly string[] | string;
|
|
98
102
|
max: number | null;
|
|
99
103
|
maxSize: number | null;
|
|
104
|
+
pickFrom: readonly string[] | null;
|
|
100
105
|
removeLabel: string;
|
|
101
106
|
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
102
107
|
hint: string;
|
|
@@ -7,6 +7,12 @@ export interface FilePickerOpenOptions {
|
|
|
7
7
|
/** IDs already attached. Listed first and checked; the result keeps their order. */
|
|
8
8
|
selected?: readonly (string | null | undefined)[];
|
|
9
9
|
title?: string;
|
|
10
|
+
/**
|
|
11
|
+
* List only these asset IDs (resolved through `upload.backend`'s `resolve`
|
|
12
|
+
* route, searched and filtered in the browser) instead of the library.
|
|
13
|
+
* For backends without a library listing. null or omitted lists the library.
|
|
14
|
+
*/
|
|
15
|
+
pickFrom?: readonly string[] | null;
|
|
10
16
|
/** Bytes, for uploads from inside the picker. */
|
|
11
17
|
maxSize?: number | null;
|
|
12
18
|
upload?: MediaUploadOptions;
|
|
@@ -6,14 +6,34 @@ export interface MediaBrowseFilters {
|
|
|
6
6
|
status?: string | null;
|
|
7
7
|
query?: string | null;
|
|
8
8
|
}
|
|
9
|
+
export interface MediaBrowseOptions {
|
|
10
|
+
pageSize?: number;
|
|
11
|
+
debounceMs?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Browse only these asset IDs instead of the library: they are resolved
|
|
14
|
+
* through `backend`'s `resolve` route and filtered here, with no call to
|
|
15
|
+
* the library's list. For backends that have no listing.
|
|
16
|
+
*/
|
|
17
|
+
ids?: MaybeRefOrGetter<readonly string[]>;
|
|
18
|
+
/** Backend whose `resolve` route answers for `ids` (see useMLMediaAssets). */
|
|
19
|
+
backend?: MaybeRefOrGetter<string | null | undefined>;
|
|
20
|
+
}
|
|
21
|
+
/** The assets that match `filters`, in the given order (kind and status exact, query in the name). */
|
|
22
|
+
export declare function filterBrowseAssets(assets: readonly AssetDetail[], filters: MediaBrowseFilters): AssetDetail[];
|
|
9
23
|
/**
|
|
10
24
|
* The library as a growing list: page 1 on filter change, more on loadMore().
|
|
11
|
-
* Search text is debounced. Browser only, like useMLMediaAssets.
|
|
25
|
+
* Search text is debounced. Browser only, like useMLMediaAssets. With
|
|
26
|
+
* `ids`, a fixed set of files instead (see MediaBrowseOptions).
|
|
12
27
|
*/
|
|
13
|
-
export declare function useMLMediaBrowse(filters: MaybeRefOrGetter<MediaBrowseFilters>, opts?: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
export declare function useMLMediaBrowse(filters: MaybeRefOrGetter<MediaBrowseFilters>, opts?: MediaBrowseOptions): {
|
|
29
|
+
items: import("vue").ComputedRef<AssetDetail[]>;
|
|
30
|
+
total: import("vue").ComputedRef<number>;
|
|
31
|
+
error: import("vue").Ref<string | null, string | null>;
|
|
32
|
+
loading: import("vue").ComputedRef<boolean>;
|
|
33
|
+
hasMore: import("vue").ComputedRef<boolean>;
|
|
34
|
+
loadMore: () => void;
|
|
35
|
+
reload: () => Promise<void>;
|
|
36
|
+
} | {
|
|
17
37
|
items: import("vue").Ref<{
|
|
18
38
|
id: string;
|
|
19
39
|
kind: string;
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import { computed, onBeforeUnmount, onMounted, ref, toValue, watch } from "vue";
|
|
2
2
|
import { $fetch } from "ofetch";
|
|
3
|
+
import { assetName } from "../utils/assetFormat.js";
|
|
3
4
|
import { mlErrorMessage } from "../utils/errorMessage.js";
|
|
5
|
+
import { useMLMediaAssets } from "./useMLMediaAssets.js";
|
|
6
|
+
export function filterBrowseAssets(assets, filters) {
|
|
7
|
+
const kind = filters.kind?.toLowerCase();
|
|
8
|
+
const status = filters.status?.toLowerCase();
|
|
9
|
+
const query = filters.query?.trim().toLowerCase();
|
|
10
|
+
return assets.filter(
|
|
11
|
+
(a) => (!kind || (a.kind ?? "").toLowerCase() === kind) && (!status || (a.status ?? "").toLowerCase() === status) && (!query || assetName(a).toLowerCase().includes(query))
|
|
12
|
+
);
|
|
13
|
+
}
|
|
4
14
|
export function useMLMediaBrowse(filters, opts = {}) {
|
|
15
|
+
if (opts.ids !== void 0) return browseIds(filters, opts.ids, opts.backend);
|
|
5
16
|
const pageSize = opts.pageSize ?? 30;
|
|
6
17
|
const debounceMs = opts.debounceMs ?? 300;
|
|
7
18
|
const items = ref([]);
|
|
@@ -71,3 +82,18 @@ export function useMLMediaBrowse(filters, opts = {}) {
|
|
|
71
82
|
reload
|
|
72
83
|
};
|
|
73
84
|
}
|
|
85
|
+
function browseIds(filters, ids, backend) {
|
|
86
|
+
const pool = useMLMediaAssets(ids, { backend });
|
|
87
|
+
const all = computed(() => toValue(ids).map((id) => pool.get(id)).filter((a) => !!a));
|
|
88
|
+
const items = computed(() => filterBrowseAssets(all.value, toValue(filters)));
|
|
89
|
+
return {
|
|
90
|
+
items,
|
|
91
|
+
total: computed(() => items.value.length),
|
|
92
|
+
error: pool.error,
|
|
93
|
+
loading: pool.loading,
|
|
94
|
+
hasMore: computed(() => false),
|
|
95
|
+
loadMore: () => {
|
|
96
|
+
},
|
|
97
|
+
reload: () => pool.refresh()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -3,13 +3,18 @@ import type { MLEmbeddedMedia } from '../types/embeddedMedia.js';
|
|
|
3
3
|
export interface UseMLMediaRenewalOptions {
|
|
4
4
|
/** Seconds before expiry to renew. Default 300. */
|
|
5
5
|
marginSeconds?: number;
|
|
6
|
+
/**
|
|
7
|
+
* Seconds to wait before trying again when `renew` rejects (a transient
|
|
8
|
+
* network error must not let the links expire mid-playback). Default 30.
|
|
9
|
+
*/
|
|
10
|
+
retrySeconds?: number;
|
|
6
11
|
}
|
|
7
12
|
/**
|
|
8
13
|
* The renewal loop without the client guard: watches `media` and calls
|
|
9
14
|
* `renew` shortly before its links expire. Returns a stop function. Prefer
|
|
10
15
|
* `useMLMediaRenewal`, which runs this in the browser only.
|
|
11
16
|
*/
|
|
12
|
-
export declare function watchMLMediaRenewal(media: MaybeRefOrGetter<MLEmbeddedMedia | null | undefined>, renew: () => Promise<unknown>, { marginSeconds }?: UseMLMediaRenewalOptions): () => void;
|
|
17
|
+
export declare function watchMLMediaRenewal(media: MaybeRefOrGetter<MLEmbeddedMedia | null | undefined>, renew: () => Promise<unknown>, { marginSeconds, retrySeconds }?: UseMLMediaRenewalOptions): () => void;
|
|
13
18
|
/**
|
|
14
19
|
* Keeps embedded media links alive: tokened links expire (`expiresIn`), so
|
|
15
20
|
* shortly before they do this calls `renew`, which re-fetches the record and
|
|
@@ -1,20 +1,31 @@
|
|
|
1
1
|
import { onScopeDispose, toValue, watch } from "vue";
|
|
2
2
|
import { refreshDelay } from "../utils/streamUrls.js";
|
|
3
3
|
const DEFAULT_MARGIN_SECONDS = 300;
|
|
4
|
-
|
|
4
|
+
const DEFAULT_RETRY_SECONDS = 30;
|
|
5
|
+
export function watchMLMediaRenewal(media, renew, { marginSeconds = DEFAULT_MARGIN_SECONDS, retrySeconds = DEFAULT_RETRY_SECONDS } = {}) {
|
|
5
6
|
let timer = null;
|
|
7
|
+
let generation = 0;
|
|
6
8
|
const clear = () => {
|
|
9
|
+
generation++;
|
|
7
10
|
if (timer) clearTimeout(timer);
|
|
8
11
|
timer = null;
|
|
9
12
|
};
|
|
13
|
+
const arm = (delay) => {
|
|
14
|
+
const current = generation;
|
|
15
|
+
timer = setTimeout(() => {
|
|
16
|
+
timer = null;
|
|
17
|
+
renew().catch((err) => {
|
|
18
|
+
console.warn("[nuxt-media-library] media renewal failed, retrying:", err);
|
|
19
|
+
if (current === generation) arm(retrySeconds * 1e3);
|
|
20
|
+
});
|
|
21
|
+
}, delay);
|
|
22
|
+
};
|
|
10
23
|
const stopWatch = watch(() => toValue(media), (current) => {
|
|
11
24
|
clear();
|
|
12
25
|
const now = Date.now();
|
|
13
26
|
const delay = refreshDelay(current?.expiresIn, now, now, marginSeconds);
|
|
14
27
|
if (delay == null) return;
|
|
15
|
-
|
|
16
|
-
renew().catch((err) => console.warn("[nuxt-media-library] media renewal failed:", err));
|
|
17
|
-
}, delay);
|
|
28
|
+
arm(delay);
|
|
18
29
|
}, { immediate: true });
|
|
19
30
|
return () => {
|
|
20
31
|
stopWatch();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unplaced files after a role change. A file that lost its last role
|
|
3
|
+
* (replaced in a one-file role, or unchecked) stays on the record, unplaced,
|
|
4
|
+
* until it gets a role again or is removed. Order: those already unplaced,
|
|
5
|
+
* then newly unplaced ones in the order they were held.
|
|
6
|
+
*/
|
|
7
|
+
export declare function nextUnplaced(before: Record<string, readonly string[]>, after: Record<string, readonly string[]>, unplaced: readonly string[], removed?: ReadonlySet<string>): string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function nextUnplaced(before, after, unplaced, removed = /* @__PURE__ */ new Set()) {
|
|
2
|
+
const placed = new Set(Object.values(after).flat());
|
|
3
|
+
const out = [];
|
|
4
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const id of [...unplaced, ...Object.values(before).flat()]) {
|
|
6
|
+
if (seen.has(id) || placed.has(id) || removed.has(id)) continue;
|
|
7
|
+
seen.add(id);
|
|
8
|
+
out.push(id);
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-media-library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.64",
|
|
4
4
|
"description": "Nuxt module for media library management — chunked uploads, asset previews, and resource policies",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"@nuxt/test-utils": "^3.21.0",
|
|
74
74
|
"@nuxt/ui": "4.3.0",
|
|
75
75
|
"@types/node": "latest",
|
|
76
|
+
"@vitejs/plugin-vue": "^6.0.6",
|
|
76
77
|
"dashjs": "^5.1.1",
|
|
77
78
|
"eslint": "^9.39.2",
|
|
78
79
|
"hls.js": "^1.6.16",
|