nuxt-media-library 0.0.44 → 0.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +4 -1
- package/dist/runtime/components/MLAssetManager/Processing.vue +3 -1
- package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +60 -0
- package/dist/runtime/components/MLMedia/Dropzone.vue +137 -0
- package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +60 -0
- package/dist/runtime/components/MLMedia/Editor.d.vue.ts +23 -0
- package/dist/runtime/components/MLMedia/Editor.vue +34 -0
- package/dist/runtime/components/MLMedia/Editor.vue.d.ts +23 -0
- package/dist/runtime/components/MLMedia/EditorPanel.d.vue.ts +38 -0
- package/dist/runtime/components/MLMedia/EditorPanel.vue +220 -0
- package/dist/runtime/components/MLMedia/EditorPanel.vue.d.ts +38 -0
- package/dist/runtime/components/MLMedia/Field.d.vue.ts +65 -0
- package/dist/runtime/components/MLMedia/Field.vue +282 -0
- package/dist/runtime/components/MLMedia/Field.vue.d.ts +65 -0
- package/dist/runtime/components/MLMedia/FileFacts.d.vue.ts +19 -0
- package/dist/runtime/components/MLMedia/FileFacts.vue +38 -0
- package/dist/runtime/components/MLMedia/FileFacts.vue.d.ts +19 -0
- package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +23 -0
- package/dist/runtime/components/MLMedia/FilePicker.vue +33 -0
- package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +23 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +33 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue +300 -0
- package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +33 -0
- package/dist/runtime/components/MLMedia/FilterPill.d.vue.ts +30 -0
- package/dist/runtime/components/MLMedia/FilterPill.vue +84 -0
- package/dist/runtime/components/MLMedia/FilterPill.vue.d.ts +30 -0
- package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +78 -0
- package/dist/runtime/components/MLMedia/Gallery.vue +256 -0
- package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +78 -0
- package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +102 -0
- package/dist/runtime/components/MLMedia/GallerySection.vue +422 -0
- package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +102 -0
- package/dist/runtime/components/MLMedia/KindBadge.d.vue.ts +17 -0
- package/dist/runtime/components/MLMedia/KindBadge.vue +22 -0
- package/dist/runtime/components/MLMedia/KindBadge.vue.d.ts +17 -0
- package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +63 -0
- package/dist/runtime/components/MLMedia/MediaTile.vue +117 -0
- package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +63 -0
- package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +42 -0
- package/dist/runtime/components/MLMedia/PickerCard.vue +79 -0
- package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +42 -0
- package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +31 -0
- package/dist/runtime/components/MLMedia/PickerRow.vue +65 -0
- package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +31 -0
- package/dist/runtime/components/MLMedia/PreviewEye.d.vue.ts +16 -0
- package/dist/runtime/components/MLMedia/PreviewEye.vue +21 -0
- package/dist/runtime/components/MLMedia/PreviewEye.vue.d.ts +16 -0
- package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +25 -0
- package/dist/runtime/components/MLMedia/ProgressVeil.vue +98 -0
- package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +25 -0
- package/dist/runtime/components/MLMedia/StatusBadge.d.vue.ts +17 -0
- package/dist/runtime/components/MLMedia/StatusBadge.vue +31 -0
- package/dist/runtime/components/MLMedia/StatusBadge.vue.d.ts +17 -0
- package/dist/runtime/components/MLMedia/Thumbnail.d.vue.ts +36 -0
- package/dist/runtime/components/MLMedia/Thumbnail.vue +84 -0
- package/dist/runtime/components/MLMedia/Thumbnail.vue.d.ts +36 -0
- package/dist/runtime/components/MLMedia/UsageList.d.vue.ts +26 -0
- package/dist/runtime/components/MLMedia/UsageList.vue +66 -0
- package/dist/runtime/components/MLMedia/UsageList.vue.d.ts +26 -0
- package/dist/runtime/components/MLProcessing/StepLayout.d.vue.ts +1 -1
- package/dist/runtime/components/MLProcessing/StepLayout.vue.d.ts +1 -1
- package/dist/runtime/components/MLUploader/Item.d.vue.ts +2 -2
- package/dist/runtime/components/MLUploader/Item.vue.d.ts +2 -2
- package/dist/runtime/composables/useMLAssetPreview.js +17 -1
- package/dist/runtime/composables/useMLFilePicker.d.ts +20 -0
- package/dist/runtime/composables/useMLFilePicker.js +17 -0
- package/dist/runtime/composables/useMLMediaAssets.d.ts +19 -0
- package/dist/runtime/composables/useMLMediaAssets.js +56 -0
- package/dist/runtime/composables/useMLMediaBrowse.d.ts +147 -0
- package/dist/runtime/composables/useMLMediaBrowse.js +72 -0
- package/dist/runtime/composables/useMLMediaEditor.d.ts +22 -0
- package/dist/runtime/composables/useMLMediaEditor.js +20 -0
- package/dist/runtime/composables/useMLMediaField.d.ts +29 -0
- package/dist/runtime/composables/useMLMediaField.js +77 -0
- package/dist/runtime/composables/useMLMediaGallery.d.ts +55 -0
- package/dist/runtime/composables/useMLMediaGallery.js +134 -0
- package/dist/runtime/composables/useMLMediaGrants.d.ts +23 -0
- package/dist/runtime/composables/useMLMediaGrants.js +22 -0
- package/dist/runtime/composables/useMLMediaUpload.d.ts +134 -0
- package/dist/runtime/composables/useMLMediaUpload.js +153 -0
- package/dist/runtime/composables/useMLServiceUpload.d.ts +36 -0
- package/dist/runtime/composables/useMLServiceUpload.js +32 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.js +11 -0
- package/dist/runtime/server/create-ml-proxy.d.ts +16 -0
- package/dist/runtime/server/create-ml-proxy.js +138 -40
- package/dist/runtime/server/grants.d.ts +124 -0
- package/dist/runtime/server/grants.js +161 -0
- package/dist/runtime/server/index.d.ts +2 -0
- package/dist/runtime/server/index.js +7 -0
- package/dist/runtime/server/uploads-proxy.js +5 -2
- package/dist/runtime/types/asset.d.ts +3 -1
- package/dist/runtime/utils/assetDisplay.d.ts +8 -0
- package/dist/runtime/utils/assetDisplay.js +29 -0
- package/dist/runtime/utils/assetFormat.d.ts +30 -0
- package/dist/runtime/utils/assetFormat.js +69 -0
- package/dist/runtime/utils/assetThumbnail.d.ts +11 -0
- package/dist/runtime/utils/assetThumbnail.js +14 -0
- package/dist/runtime/utils/fileAccept.d.ts +26 -0
- package/dist/runtime/utils/fileAccept.js +104 -0
- package/dist/runtime/utils/galleryDrag.d.ts +2 -0
- package/dist/runtime/utils/galleryDrag.js +1 -0
- package/dist/runtime/utils/grants/listeners.d.ts +2 -0
- package/dist/runtime/utils/grants/listeners.js +8 -0
- package/dist/runtime/utils/grants/store.d.ts +26 -0
- package/dist/runtime/utils/grants/store.js +37 -0
- package/dist/runtime/utils/uploader/serviceUpload.d.ts +84 -0
- package/dist/runtime/utils/uploader/serviceUpload.js +109 -0
- package/package.json +5 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
2
|
+
export interface MediaGalleryRole {
|
|
3
|
+
/** Shown on chips, badges and menus, e.g. "Cover". */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Upload profile name for files uploaded into this role, e.g. "product-cover". */
|
|
6
|
+
profile?: string | null;
|
|
7
|
+
/** Asset IDs already in this role, in order. */
|
|
8
|
+
selected?: string[];
|
|
9
|
+
/** Most files in this role. 1 makes it a one-file slot: a new file replaces the old. */
|
|
10
|
+
max?: number | null;
|
|
11
|
+
accept?: readonly string[] | string;
|
|
12
|
+
maxSize?: number | null;
|
|
13
|
+
}
|
|
14
|
+
export type MediaGalleryProfiles = Record<string, MediaGalleryRole>;
|
|
15
|
+
type __VLS_Props = {
|
|
16
|
+
/** Roles of this usage. Omit for a single list driven by v-model. */
|
|
17
|
+
profiles?: MediaGalleryProfiles | null;
|
|
18
|
+
/** Role new uploads and picked files go to. Defaults to the first role that holds more than one file. */
|
|
19
|
+
defaultRole?: string;
|
|
20
|
+
accept?: readonly string[] | string;
|
|
21
|
+
max?: number | null;
|
|
22
|
+
maxSize?: number | null;
|
|
23
|
+
title?: string;
|
|
24
|
+
hint?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
featured?: boolean;
|
|
27
|
+
recordName?: string;
|
|
28
|
+
visibility?: MediaUploadOptions['visibility'];
|
|
29
|
+
processingConfig?: MediaUploadOptions['processingConfig'];
|
|
30
|
+
collectionName?: string | null;
|
|
31
|
+
/** Upload profile for the single list (with `profiles`, each role has its own). */
|
|
32
|
+
profile?: string | null;
|
|
33
|
+
picker?: boolean;
|
|
34
|
+
onPick?: () => void;
|
|
35
|
+
editor?: boolean;
|
|
36
|
+
onOpen?: (assetId: string) => void;
|
|
37
|
+
};
|
|
38
|
+
type __VLS_ModelProps = {
|
|
39
|
+
modelValue?: string[];
|
|
40
|
+
'alt'?: Record<string, string> | undefined;
|
|
41
|
+
};
|
|
42
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
43
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
44
|
+
uploaded: (assetId: string, role: string) => any;
|
|
45
|
+
"update:modelValue": (value: string[]) => any;
|
|
46
|
+
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
47
|
+
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
48
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
49
|
+
onUploaded?: ((assetId: string, role: string) => any) | undefined;
|
|
50
|
+
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
51
|
+
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
52
|
+
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
53
|
+
}>, {
|
|
54
|
+
visibility: "private" | "restricted" | "public";
|
|
55
|
+
title: string;
|
|
56
|
+
profile: string | null;
|
|
57
|
+
accept: readonly string[] | string;
|
|
58
|
+
maxSize: number | null;
|
|
59
|
+
processingConfig: {
|
|
60
|
+
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
61
|
+
useFilenameAsAssetId?: boolean;
|
|
62
|
+
enableTranscription?: boolean;
|
|
63
|
+
};
|
|
64
|
+
collectionName: string | null;
|
|
65
|
+
disabled: boolean;
|
|
66
|
+
max: number | null;
|
|
67
|
+
hint: string;
|
|
68
|
+
picker: boolean;
|
|
69
|
+
onPick: () => void;
|
|
70
|
+
editor: boolean;
|
|
71
|
+
onOpen: (assetId: string) => void;
|
|
72
|
+
featured: boolean;
|
|
73
|
+
recordName: string;
|
|
74
|
+
profiles: MediaGalleryProfiles | null;
|
|
75
|
+
defaultRole: string;
|
|
76
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
77
|
+
declare const _default: typeof __VLS_export;
|
|
78
|
+
export default _default;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { GALLERY_TILE_TYPE } from "../../utils/galleryDrag";
|
|
4
|
+
import GallerySection from "./GallerySection.vue";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
profiles: { type: [Object, null], required: false, default: null },
|
|
7
|
+
defaultRole: { type: String, required: false, default: void 0 },
|
|
8
|
+
accept: { type: [Array, String], required: false, default: "" },
|
|
9
|
+
max: { type: [Number, null], required: false, default: null },
|
|
10
|
+
maxSize: { type: [Number, null], required: false, default: null },
|
|
11
|
+
title: { type: String, required: false, default: "Media" },
|
|
12
|
+
hint: { type: String, required: false, default: void 0 },
|
|
13
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
14
|
+
featured: { type: Boolean, required: false, default: true },
|
|
15
|
+
recordName: { type: String, required: false, default: "" },
|
|
16
|
+
visibility: { type: String, required: false, default: void 0 },
|
|
17
|
+
processingConfig: { type: Object, required: false, default: void 0 },
|
|
18
|
+
collectionName: { type: [String, null], required: false, default: null },
|
|
19
|
+
profile: { type: [String, null], required: false, default: null },
|
|
20
|
+
picker: { type: Boolean, required: false, default: true },
|
|
21
|
+
onPick: { type: Function, required: false, default: void 0 },
|
|
22
|
+
editor: { type: Boolean, required: false, default: true },
|
|
23
|
+
onOpen: { type: Function, required: false, default: void 0 }
|
|
24
|
+
});
|
|
25
|
+
const model = defineModel({ type: Array, ...{ default: () => [] } });
|
|
26
|
+
const alt = defineModel("alt", { type: null, ...{ default: void 0 } });
|
|
27
|
+
const emit = defineEmits(["uploaded", "update:profiles"]);
|
|
28
|
+
const roles = computed(() => Object.entries(props.profiles ?? {}).map(([key, r]) => ({ key, ...r })));
|
|
29
|
+
const hasRoles = computed(() => roles.value.length > 0);
|
|
30
|
+
const single = (r) => r.max === 1;
|
|
31
|
+
const defaultKey = computed(() => {
|
|
32
|
+
if (props.defaultRole && props.profiles?.[props.defaultRole]) return props.defaultRole;
|
|
33
|
+
return (roles.value.find((r) => !single(r)) ?? roles.value[0])?.key ?? "";
|
|
34
|
+
});
|
|
35
|
+
const defaultRole = computed(() => props.profiles?.[defaultKey.value]);
|
|
36
|
+
const removeLabel = computed(() => props.recordName ? `Remove from ${props.recordName}` : "Remove");
|
|
37
|
+
const selections = ref({});
|
|
38
|
+
watch(() => props.profiles, (p) => {
|
|
39
|
+
const next = {};
|
|
40
|
+
for (const [key, r] of Object.entries(p ?? {})) next[key] = [...r.selected ?? []];
|
|
41
|
+
selections.value = next;
|
|
42
|
+
}, { immediate: true, deep: true });
|
|
43
|
+
const sel = (key) => selections.value[key] ?? [];
|
|
44
|
+
const union = computed(() => {
|
|
45
|
+
const seen = /* @__PURE__ */ new Set();
|
|
46
|
+
const ordered = [...roles.value.filter(single), ...roles.value.filter((r) => !single(r))];
|
|
47
|
+
for (const r of ordered) for (const id of sel(r.key)) seen.add(id);
|
|
48
|
+
return [...seen];
|
|
49
|
+
});
|
|
50
|
+
function commit(changes) {
|
|
51
|
+
selections.value = { ...selections.value, ...changes };
|
|
52
|
+
const out = {};
|
|
53
|
+
for (const [k, r] of Object.entries(props.profiles ?? {})) out[k] = { ...r, selected: [...sel(k)] };
|
|
54
|
+
emit("update:profiles", out);
|
|
55
|
+
}
|
|
56
|
+
const notice = ref(null);
|
|
57
|
+
function kindAllowed(kind, accept) {
|
|
58
|
+
const list = (typeof accept === "string" ? accept.split(",") : accept ?? []).map((k) => k.trim().toLowerCase()).filter(Boolean);
|
|
59
|
+
return !list.length || !kind || list.includes(kind.toLowerCase());
|
|
60
|
+
}
|
|
61
|
+
function withFile(key, id, list = sel(key)) {
|
|
62
|
+
const role = props.profiles?.[key];
|
|
63
|
+
if (!role) return null;
|
|
64
|
+
if (list.includes(id)) return list;
|
|
65
|
+
if (single(role)) return [id];
|
|
66
|
+
if (role.max != null && list.length >= role.max) {
|
|
67
|
+
notice.value = `${role.label} holds up to ${role.max} files. Remove one first.`;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
return [...list, id];
|
|
71
|
+
}
|
|
72
|
+
function setRole(id, key, on) {
|
|
73
|
+
notice.value = null;
|
|
74
|
+
const next = on ? withFile(key, id) : sel(key).filter((x) => x !== id);
|
|
75
|
+
if (next) commit({ [key]: next });
|
|
76
|
+
}
|
|
77
|
+
const pendingRole = /* @__PURE__ */ new Map();
|
|
78
|
+
const uploadedRole = /* @__PURE__ */ new Map();
|
|
79
|
+
function onUnionUpdate(next) {
|
|
80
|
+
notice.value = null;
|
|
81
|
+
const keep = new Set(next);
|
|
82
|
+
const before = new Set(union.value);
|
|
83
|
+
const changes = {};
|
|
84
|
+
for (const r of roles.value) changes[r.key] = sel(r.key).filter((id) => keep.has(id));
|
|
85
|
+
for (const id of next) {
|
|
86
|
+
if (before.has(id)) continue;
|
|
87
|
+
const key = pendingRole.get(id) ?? defaultKey.value;
|
|
88
|
+
pendingRole.delete(id);
|
|
89
|
+
const placed = withFile(key, id, changes[key]);
|
|
90
|
+
if (placed) changes[key] = placed;
|
|
91
|
+
}
|
|
92
|
+
const pos = new Map(next.map((id, i) => [id, i]));
|
|
93
|
+
for (const r of roles.value) {
|
|
94
|
+
if (!single(r)) changes[r.key] = [...changes[r.key]].sort((a, b) => (pos.get(a) ?? 0) - (pos.get(b) ?? 0));
|
|
95
|
+
}
|
|
96
|
+
commit(changes);
|
|
97
|
+
}
|
|
98
|
+
function onAdd(id, tag) {
|
|
99
|
+
const key = tag ?? defaultKey.value;
|
|
100
|
+
uploadedRole.set(id, key);
|
|
101
|
+
if (union.value.includes(id)) setRole(id, key, true);
|
|
102
|
+
else pendingRole.set(id, key);
|
|
103
|
+
}
|
|
104
|
+
function onUploaded(id) {
|
|
105
|
+
emit("uploaded", id, uploadedRole.get(id) ?? defaultKey.value);
|
|
106
|
+
uploadedRole.delete(id);
|
|
107
|
+
}
|
|
108
|
+
const badges = (id) => roles.value.filter((r) => sel(r.key).includes(id)).map((r) => r.label);
|
|
109
|
+
function tileMenu(id, kind) {
|
|
110
|
+
return [
|
|
111
|
+
[
|
|
112
|
+
{ type: "label", label: "Use as" },
|
|
113
|
+
...roles.value.map((r) => {
|
|
114
|
+
const has = sel(r.key).includes(id);
|
|
115
|
+
return {
|
|
116
|
+
label: single(r) && !has && sel(r.key).length ? `${r.label} (replaces current)` : r.label,
|
|
117
|
+
type: "checkbox",
|
|
118
|
+
checked: has,
|
|
119
|
+
disabled: !has && !kindAllowed(kind, r.accept ?? props.accept),
|
|
120
|
+
onUpdateChecked: (on) => setRole(id, r.key, on),
|
|
121
|
+
onSelect: (e) => e.preventDefault()
|
|
122
|
+
};
|
|
123
|
+
})
|
|
124
|
+
],
|
|
125
|
+
[{ label: removeLabel.value, icon: "i-lucide-x", color: "error", onSelect: () => onUnionUpdate(union.value.filter((x) => x !== id)) }]
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
const activeRole = ref(null);
|
|
129
|
+
const highlight = computed(() => activeRole.value ? new Set(sel(activeRole.value)) : null);
|
|
130
|
+
const grid = ref(null);
|
|
131
|
+
const chipOver = ref(null);
|
|
132
|
+
function chipDragOver(e, key) {
|
|
133
|
+
const types = e.dataTransfer?.types ?? [];
|
|
134
|
+
if (props.disabled || !(types.includes(GALLERY_TILE_TYPE) || types.includes("Files"))) return;
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
e.dataTransfer.dropEffect = "copy";
|
|
137
|
+
chipOver.value = key;
|
|
138
|
+
}
|
|
139
|
+
function chipDrop(e, key) {
|
|
140
|
+
chipOver.value = null;
|
|
141
|
+
const role = props.profiles?.[key];
|
|
142
|
+
if (!e.dataTransfer || !role || props.disabled) return;
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
const id = e.dataTransfer.getData(GALLERY_TILE_TYPE);
|
|
145
|
+
if (id) return setRole(id, key, true);
|
|
146
|
+
const files = [...e.dataTransfer.files];
|
|
147
|
+
if (!files.length) return;
|
|
148
|
+
notice.value = null;
|
|
149
|
+
grid.value?.uploadFiles(files, {
|
|
150
|
+
profile: role.profile ?? null,
|
|
151
|
+
tag: key,
|
|
152
|
+
accept: role.accept ?? props.accept,
|
|
153
|
+
maxSize: role.maxSize ?? props.maxSize,
|
|
154
|
+
maxFiles: single(role) ? 1 : role.max != null ? Math.max(0, role.max - sel(key).length) : void 0
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function chipCount(key, max) {
|
|
158
|
+
const n = sel(key).length;
|
|
159
|
+
return max != null ? `${n}/${max}` : String(n);
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
<template>
|
|
164
|
+
<GallerySection
|
|
165
|
+
v-if="hasRoles"
|
|
166
|
+
ref="grid"
|
|
167
|
+
v-model:alt="alt"
|
|
168
|
+
:model-value="union"
|
|
169
|
+
:title="title"
|
|
170
|
+
:hint="hint"
|
|
171
|
+
:accept="defaultRole?.accept ?? accept"
|
|
172
|
+
:max-size="defaultRole?.maxSize ?? maxSize"
|
|
173
|
+
:profile="defaultRole?.profile ?? null"
|
|
174
|
+
:disabled="disabled"
|
|
175
|
+
:featured="featured"
|
|
176
|
+
:record-name="recordName"
|
|
177
|
+
:remove-label="removeLabel"
|
|
178
|
+
:visibility="visibility"
|
|
179
|
+
:processing-config="processingConfig"
|
|
180
|
+
:collection-name="collectionName"
|
|
181
|
+
:picker="picker"
|
|
182
|
+
:on-pick="onPick"
|
|
183
|
+
:editor="editor"
|
|
184
|
+
:on-open="onOpen"
|
|
185
|
+
:badges="badges"
|
|
186
|
+
:tile-menu="tileMenu"
|
|
187
|
+
:highlight="highlight"
|
|
188
|
+
:on-add="onAdd"
|
|
189
|
+
@update:model-value="onUnionUpdate"
|
|
190
|
+
@uploaded="onUploaded"
|
|
191
|
+
>
|
|
192
|
+
<template #toolbar>
|
|
193
|
+
<div class="flex flex-col gap-1.5">
|
|
194
|
+
<div
|
|
195
|
+
class="flex flex-wrap gap-2"
|
|
196
|
+
role="group"
|
|
197
|
+
aria-label="Roles"
|
|
198
|
+
>
|
|
199
|
+
<button
|
|
200
|
+
v-for="r in roles"
|
|
201
|
+
:key="r.key"
|
|
202
|
+
type="button"
|
|
203
|
+
class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs transition-colors motion-reduce:transition-none"
|
|
204
|
+
:class="chipOver === r.key ? 'border-primary bg-primary/15 text-primary' : activeRole === r.key ? 'border-primary bg-primary/10 text-primary' : single(r) && !sel(r.key).length ? 'border-dashed border-warning text-warning' : 'border-accented bg-default text-default hover:bg-elevated'"
|
|
205
|
+
:aria-pressed="activeRole === r.key"
|
|
206
|
+
@click="activeRole = activeRole === r.key ? null : r.key"
|
|
207
|
+
@dragover="chipDragOver($event, r.key)"
|
|
208
|
+
@dragleave="chipOver === r.key && (chipOver = null)"
|
|
209
|
+
@drop="chipDrop($event, r.key)"
|
|
210
|
+
>
|
|
211
|
+
<UIcon
|
|
212
|
+
:name="single(r) && !sel(r.key).length ? 'i-lucide-triangle-alert' : single(r) ? 'i-lucide-star' : 'i-lucide-layout-grid'"
|
|
213
|
+
class="size-3.5"
|
|
214
|
+
/>
|
|
215
|
+
{{ single(r) && !sel(r.key).length ? `No ${r.label.toLowerCase()}` : r.label }}
|
|
216
|
+
<span class="tabular-nums opacity-70">{{ chipCount(r.key, r.max) }}</span>
|
|
217
|
+
</button>
|
|
218
|
+
</div>
|
|
219
|
+
<p class="text-xs text-dimmed">
|
|
220
|
+
Click a role to highlight its files. Drop a tile on a role to assign it, or a file from your computer to upload it as that role.
|
|
221
|
+
</p>
|
|
222
|
+
<UAlert
|
|
223
|
+
v-if="notice"
|
|
224
|
+
color="warning"
|
|
225
|
+
variant="subtle"
|
|
226
|
+
icon="i-lucide-triangle-alert"
|
|
227
|
+
:description="notice"
|
|
228
|
+
close
|
|
229
|
+
@update:open="notice = null"
|
|
230
|
+
/>
|
|
231
|
+
</div>
|
|
232
|
+
</template>
|
|
233
|
+
</GallerySection>
|
|
234
|
+
<GallerySection
|
|
235
|
+
v-else
|
|
236
|
+
v-model="model"
|
|
237
|
+
v-model:alt="alt"
|
|
238
|
+
:accept="accept"
|
|
239
|
+
:max="max"
|
|
240
|
+
:max-size="maxSize"
|
|
241
|
+
:title="title"
|
|
242
|
+
:hint="hint"
|
|
243
|
+
:disabled="disabled"
|
|
244
|
+
:featured="featured"
|
|
245
|
+
:record-name="recordName"
|
|
246
|
+
:visibility="visibility"
|
|
247
|
+
:processing-config="processingConfig"
|
|
248
|
+
:collection-name="collectionName"
|
|
249
|
+
:profile="profile"
|
|
250
|
+
:picker="picker"
|
|
251
|
+
:on-pick="onPick"
|
|
252
|
+
:editor="editor"
|
|
253
|
+
:on-open="onOpen"
|
|
254
|
+
@uploaded="(id) => emit('uploaded', id, '')"
|
|
255
|
+
/>
|
|
256
|
+
</template>
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
2
|
+
export interface MediaGalleryRole {
|
|
3
|
+
/** Shown on chips, badges and menus, e.g. "Cover". */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Upload profile name for files uploaded into this role, e.g. "product-cover". */
|
|
6
|
+
profile?: string | null;
|
|
7
|
+
/** Asset IDs already in this role, in order. */
|
|
8
|
+
selected?: string[];
|
|
9
|
+
/** Most files in this role. 1 makes it a one-file slot: a new file replaces the old. */
|
|
10
|
+
max?: number | null;
|
|
11
|
+
accept?: readonly string[] | string;
|
|
12
|
+
maxSize?: number | null;
|
|
13
|
+
}
|
|
14
|
+
export type MediaGalleryProfiles = Record<string, MediaGalleryRole>;
|
|
15
|
+
type __VLS_Props = {
|
|
16
|
+
/** Roles of this usage. Omit for a single list driven by v-model. */
|
|
17
|
+
profiles?: MediaGalleryProfiles | null;
|
|
18
|
+
/** Role new uploads and picked files go to. Defaults to the first role that holds more than one file. */
|
|
19
|
+
defaultRole?: string;
|
|
20
|
+
accept?: readonly string[] | string;
|
|
21
|
+
max?: number | null;
|
|
22
|
+
maxSize?: number | null;
|
|
23
|
+
title?: string;
|
|
24
|
+
hint?: string;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
featured?: boolean;
|
|
27
|
+
recordName?: string;
|
|
28
|
+
visibility?: MediaUploadOptions['visibility'];
|
|
29
|
+
processingConfig?: MediaUploadOptions['processingConfig'];
|
|
30
|
+
collectionName?: string | null;
|
|
31
|
+
/** Upload profile for the single list (with `profiles`, each role has its own). */
|
|
32
|
+
profile?: string | null;
|
|
33
|
+
picker?: boolean;
|
|
34
|
+
onPick?: () => void;
|
|
35
|
+
editor?: boolean;
|
|
36
|
+
onOpen?: (assetId: string) => void;
|
|
37
|
+
};
|
|
38
|
+
type __VLS_ModelProps = {
|
|
39
|
+
modelValue?: string[];
|
|
40
|
+
'alt'?: Record<string, string> | undefined;
|
|
41
|
+
};
|
|
42
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
43
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
44
|
+
uploaded: (assetId: string, role: string) => any;
|
|
45
|
+
"update:modelValue": (value: string[]) => any;
|
|
46
|
+
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
47
|
+
"update:profiles": (profiles: MediaGalleryProfiles) => any;
|
|
48
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
49
|
+
onUploaded?: ((assetId: string, role: string) => any) | undefined;
|
|
50
|
+
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
51
|
+
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
52
|
+
"onUpdate:profiles"?: ((profiles: MediaGalleryProfiles) => any) | undefined;
|
|
53
|
+
}>, {
|
|
54
|
+
visibility: "private" | "restricted" | "public";
|
|
55
|
+
title: string;
|
|
56
|
+
profile: string | null;
|
|
57
|
+
accept: readonly string[] | string;
|
|
58
|
+
maxSize: number | null;
|
|
59
|
+
processingConfig: {
|
|
60
|
+
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
61
|
+
useFilenameAsAssetId?: boolean;
|
|
62
|
+
enableTranscription?: boolean;
|
|
63
|
+
};
|
|
64
|
+
collectionName: string | null;
|
|
65
|
+
disabled: boolean;
|
|
66
|
+
max: number | null;
|
|
67
|
+
hint: string;
|
|
68
|
+
picker: boolean;
|
|
69
|
+
onPick: () => void;
|
|
70
|
+
editor: boolean;
|
|
71
|
+
onOpen: (assetId: string) => void;
|
|
72
|
+
featured: boolean;
|
|
73
|
+
recordName: string;
|
|
74
|
+
profiles: MediaGalleryProfiles | null;
|
|
75
|
+
defaultRole: string;
|
|
76
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
77
|
+
declare const _default: typeof __VLS_export;
|
|
78
|
+
export default _default;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { DropdownMenuItem } from '@nuxt/ui';
|
|
2
|
+
import type { MediaUploadOptions } from '../../composables/useMLMediaUpload.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
/** Asset kinds the list takes. Empty means any file. */
|
|
5
|
+
accept?: readonly string[] | string;
|
|
6
|
+
/** Most files on the record. */
|
|
7
|
+
max?: number | null;
|
|
8
|
+
/** Bytes, per file. */
|
|
9
|
+
maxSize?: number | null;
|
|
10
|
+
title?: string;
|
|
11
|
+
/** Under the grid. Defaults to how ordering works and what the list takes. */
|
|
12
|
+
hint?: string;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Show the first file larger, as the featured one. */
|
|
15
|
+
featured?: boolean;
|
|
16
|
+
/** Used in "Remove from …" labels, e.g. "product". */
|
|
17
|
+
recordName?: string;
|
|
18
|
+
visibility?: MediaUploadOptions['visibility'];
|
|
19
|
+
processingConfig?: MediaUploadOptions['processingConfig'];
|
|
20
|
+
collectionName?: string | null;
|
|
21
|
+
/** Show "Select existing", which opens the library picker. */
|
|
22
|
+
picker?: boolean;
|
|
23
|
+
onPick?: () => void;
|
|
24
|
+
/** Show the eye on each tile, which opens the media editor. */
|
|
25
|
+
editor?: boolean;
|
|
26
|
+
onOpen?: (assetId: string) => void;
|
|
27
|
+
/** Upload profile name for new files in this list. */
|
|
28
|
+
profile?: string | null;
|
|
29
|
+
/** Overrides the "Remove from …" label. */
|
|
30
|
+
removeLabel?: string;
|
|
31
|
+
/** Labels shown on a tile, e.g. its roles. */
|
|
32
|
+
badges?: (assetId: string) => string[];
|
|
33
|
+
/** Extra menu on each tile (⋯). */
|
|
34
|
+
tileMenu?: (assetId: string, kind: string | null) => DropdownMenuItem[][];
|
|
35
|
+
/** When set, these files are highlighted and the rest dimmed. */
|
|
36
|
+
highlight?: ReadonlySet<string> | null;
|
|
37
|
+
/** See useMLMediaGallery's onAdd. */
|
|
38
|
+
onAdd?: (assetId: string, tag: string | undefined) => void;
|
|
39
|
+
};
|
|
40
|
+
/** Upload files with another profile than the list's own, e.g. from a role chip. */
|
|
41
|
+
declare function uploadFiles(files: File[], opts?: {
|
|
42
|
+
profile?: string | null;
|
|
43
|
+
tag?: string;
|
|
44
|
+
accept?: readonly string[] | string;
|
|
45
|
+
maxSize?: number | null;
|
|
46
|
+
maxFiles?: number;
|
|
47
|
+
}): void;
|
|
48
|
+
type __VLS_ModelProps = {
|
|
49
|
+
modelValue?: string[];
|
|
50
|
+
/** Alt text per asset ID, stored on the host record. */
|
|
51
|
+
'alt'?: Record<string, string> | undefined;
|
|
52
|
+
};
|
|
53
|
+
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
54
|
+
declare var __VLS_29: {};
|
|
55
|
+
type __VLS_Slots = {} & {
|
|
56
|
+
toolbar?: (props: typeof __VLS_29) => any;
|
|
57
|
+
};
|
|
58
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
59
|
+
uploadFiles: typeof uploadFiles;
|
|
60
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
61
|
+
uploaded: (assetId: string) => any;
|
|
62
|
+
"update:modelValue": (value: string[]) => any;
|
|
63
|
+
"update:alt": (value: Record<string, string> | undefined) => any;
|
|
64
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
65
|
+
onUploaded?: ((assetId: string) => any) | undefined;
|
|
66
|
+
"onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
|
|
67
|
+
"onUpdate:alt"?: ((value: Record<string, string> | undefined) => any) | undefined;
|
|
68
|
+
}>, {
|
|
69
|
+
visibility: "private" | "restricted" | "public";
|
|
70
|
+
title: string;
|
|
71
|
+
profile: string | null;
|
|
72
|
+
accept: readonly string[] | string;
|
|
73
|
+
maxSize: number | null;
|
|
74
|
+
processingConfig: {
|
|
75
|
+
stepOptions?: Record<string, Record<string, unknown>> | null;
|
|
76
|
+
useFilenameAsAssetId?: boolean;
|
|
77
|
+
enableTranscription?: boolean;
|
|
78
|
+
};
|
|
79
|
+
collectionName: string | null;
|
|
80
|
+
disabled: boolean;
|
|
81
|
+
highlight: ReadonlySet<string> | null;
|
|
82
|
+
removeLabel: string;
|
|
83
|
+
max: number | null;
|
|
84
|
+
onAdd: (assetId: string, tag: string | undefined) => void;
|
|
85
|
+
hint: string;
|
|
86
|
+
picker: boolean;
|
|
87
|
+
onPick: () => void;
|
|
88
|
+
editor: boolean;
|
|
89
|
+
onOpen: (assetId: string) => void;
|
|
90
|
+
featured: boolean;
|
|
91
|
+
recordName: string;
|
|
92
|
+
badges: (assetId: string) => string[];
|
|
93
|
+
tileMenu: (assetId: string, kind: string | null) => DropdownMenuItem[][];
|
|
94
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
95
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
96
|
+
declare const _default: typeof __VLS_export;
|
|
97
|
+
export default _default;
|
|
98
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
99
|
+
new (): {
|
|
100
|
+
$slots: S;
|
|
101
|
+
};
|
|
102
|
+
};
|