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.
Files changed (111) hide show
  1. package/README.md +71 -0
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +4 -1
  4. package/dist/runtime/components/MLAssetManager/Processing.vue +3 -1
  5. package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +60 -0
  6. package/dist/runtime/components/MLMedia/Dropzone.vue +137 -0
  7. package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +60 -0
  8. package/dist/runtime/components/MLMedia/Editor.d.vue.ts +23 -0
  9. package/dist/runtime/components/MLMedia/Editor.vue +34 -0
  10. package/dist/runtime/components/MLMedia/Editor.vue.d.ts +23 -0
  11. package/dist/runtime/components/MLMedia/EditorPanel.d.vue.ts +38 -0
  12. package/dist/runtime/components/MLMedia/EditorPanel.vue +220 -0
  13. package/dist/runtime/components/MLMedia/EditorPanel.vue.d.ts +38 -0
  14. package/dist/runtime/components/MLMedia/Field.d.vue.ts +65 -0
  15. package/dist/runtime/components/MLMedia/Field.vue +282 -0
  16. package/dist/runtime/components/MLMedia/Field.vue.d.ts +65 -0
  17. package/dist/runtime/components/MLMedia/FileFacts.d.vue.ts +19 -0
  18. package/dist/runtime/components/MLMedia/FileFacts.vue +38 -0
  19. package/dist/runtime/components/MLMedia/FileFacts.vue.d.ts +19 -0
  20. package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +23 -0
  21. package/dist/runtime/components/MLMedia/FilePicker.vue +33 -0
  22. package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +23 -0
  23. package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +33 -0
  24. package/dist/runtime/components/MLMedia/FilePickerPanel.vue +300 -0
  25. package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +33 -0
  26. package/dist/runtime/components/MLMedia/FilterPill.d.vue.ts +30 -0
  27. package/dist/runtime/components/MLMedia/FilterPill.vue +84 -0
  28. package/dist/runtime/components/MLMedia/FilterPill.vue.d.ts +30 -0
  29. package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +78 -0
  30. package/dist/runtime/components/MLMedia/Gallery.vue +256 -0
  31. package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +78 -0
  32. package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +102 -0
  33. package/dist/runtime/components/MLMedia/GallerySection.vue +422 -0
  34. package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +102 -0
  35. package/dist/runtime/components/MLMedia/KindBadge.d.vue.ts +17 -0
  36. package/dist/runtime/components/MLMedia/KindBadge.vue +22 -0
  37. package/dist/runtime/components/MLMedia/KindBadge.vue.d.ts +17 -0
  38. package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +63 -0
  39. package/dist/runtime/components/MLMedia/MediaTile.vue +117 -0
  40. package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +63 -0
  41. package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +42 -0
  42. package/dist/runtime/components/MLMedia/PickerCard.vue +79 -0
  43. package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +42 -0
  44. package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +31 -0
  45. package/dist/runtime/components/MLMedia/PickerRow.vue +65 -0
  46. package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +31 -0
  47. package/dist/runtime/components/MLMedia/PreviewEye.d.vue.ts +16 -0
  48. package/dist/runtime/components/MLMedia/PreviewEye.vue +21 -0
  49. package/dist/runtime/components/MLMedia/PreviewEye.vue.d.ts +16 -0
  50. package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +25 -0
  51. package/dist/runtime/components/MLMedia/ProgressVeil.vue +98 -0
  52. package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +25 -0
  53. package/dist/runtime/components/MLMedia/StatusBadge.d.vue.ts +17 -0
  54. package/dist/runtime/components/MLMedia/StatusBadge.vue +31 -0
  55. package/dist/runtime/components/MLMedia/StatusBadge.vue.d.ts +17 -0
  56. package/dist/runtime/components/MLMedia/Thumbnail.d.vue.ts +36 -0
  57. package/dist/runtime/components/MLMedia/Thumbnail.vue +84 -0
  58. package/dist/runtime/components/MLMedia/Thumbnail.vue.d.ts +36 -0
  59. package/dist/runtime/components/MLMedia/UsageList.d.vue.ts +26 -0
  60. package/dist/runtime/components/MLMedia/UsageList.vue +66 -0
  61. package/dist/runtime/components/MLMedia/UsageList.vue.d.ts +26 -0
  62. package/dist/runtime/components/MLProcessing/StepLayout.d.vue.ts +1 -1
  63. package/dist/runtime/components/MLProcessing/StepLayout.vue.d.ts +1 -1
  64. package/dist/runtime/components/MLUploader/Item.d.vue.ts +2 -2
  65. package/dist/runtime/components/MLUploader/Item.vue.d.ts +2 -2
  66. package/dist/runtime/composables/useMLAssetPreview.js +17 -1
  67. package/dist/runtime/composables/useMLFilePicker.d.ts +20 -0
  68. package/dist/runtime/composables/useMLFilePicker.js +17 -0
  69. package/dist/runtime/composables/useMLMediaAssets.d.ts +19 -0
  70. package/dist/runtime/composables/useMLMediaAssets.js +56 -0
  71. package/dist/runtime/composables/useMLMediaBrowse.d.ts +147 -0
  72. package/dist/runtime/composables/useMLMediaBrowse.js +72 -0
  73. package/dist/runtime/composables/useMLMediaEditor.d.ts +22 -0
  74. package/dist/runtime/composables/useMLMediaEditor.js +20 -0
  75. package/dist/runtime/composables/useMLMediaField.d.ts +29 -0
  76. package/dist/runtime/composables/useMLMediaField.js +77 -0
  77. package/dist/runtime/composables/useMLMediaGallery.d.ts +55 -0
  78. package/dist/runtime/composables/useMLMediaGallery.js +134 -0
  79. package/dist/runtime/composables/useMLMediaGrants.d.ts +23 -0
  80. package/dist/runtime/composables/useMLMediaGrants.js +22 -0
  81. package/dist/runtime/composables/useMLMediaUpload.d.ts +134 -0
  82. package/dist/runtime/composables/useMLMediaUpload.js +153 -0
  83. package/dist/runtime/composables/useMLServiceUpload.d.ts +36 -0
  84. package/dist/runtime/composables/useMLServiceUpload.js +32 -0
  85. package/dist/runtime/index.d.ts +11 -0
  86. package/dist/runtime/index.js +11 -0
  87. package/dist/runtime/server/create-ml-proxy.d.ts +16 -0
  88. package/dist/runtime/server/create-ml-proxy.js +138 -40
  89. package/dist/runtime/server/grants.d.ts +124 -0
  90. package/dist/runtime/server/grants.js +161 -0
  91. package/dist/runtime/server/index.d.ts +2 -0
  92. package/dist/runtime/server/index.js +7 -0
  93. package/dist/runtime/server/uploads-proxy.js +5 -2
  94. package/dist/runtime/types/asset.d.ts +3 -1
  95. package/dist/runtime/utils/assetDisplay.d.ts +8 -0
  96. package/dist/runtime/utils/assetDisplay.js +29 -0
  97. package/dist/runtime/utils/assetFormat.d.ts +30 -0
  98. package/dist/runtime/utils/assetFormat.js +69 -0
  99. package/dist/runtime/utils/assetThumbnail.d.ts +11 -0
  100. package/dist/runtime/utils/assetThumbnail.js +14 -0
  101. package/dist/runtime/utils/fileAccept.d.ts +26 -0
  102. package/dist/runtime/utils/fileAccept.js +104 -0
  103. package/dist/runtime/utils/galleryDrag.d.ts +2 -0
  104. package/dist/runtime/utils/galleryDrag.js +1 -0
  105. package/dist/runtime/utils/grants/listeners.d.ts +2 -0
  106. package/dist/runtime/utils/grants/listeners.js +8 -0
  107. package/dist/runtime/utils/grants/store.d.ts +26 -0
  108. package/dist/runtime/utils/grants/store.js +37 -0
  109. package/dist/runtime/utils/uploader/serviceUpload.d.ts +84 -0
  110. package/dist/runtime/utils/uploader/serviceUpload.js +109 -0
  111. package/package.json +5 -2
package/README.md CHANGED
@@ -82,6 +82,77 @@ If you use the uploader component in Nuxt, provide config via the Nuxt plugin
82
82
  </template>
83
83
  ```
84
84
 
85
+ ### Authorizing `/manage` with media grants
86
+
87
+ The module's server proxies forward `/api/media-library/manage/*` to the
88
+ media library. Without an `authorize` hook they forward the browser's
89
+ credentials (or your static headers) as before. With one, every `/manage`
90
+ request is checked by your rules and sent upstream with a short-lived
91
+ **media grant** scoped to exactly that request (server:
92
+ `docs/security/media-grants.md`). Your app never re-implements the
93
+ endpoints, and no long-lived credential reaches the media library from the
94
+ browser.
95
+
96
+ ```ts
97
+ // server/api/media-library/[...].ts
98
+ import { createMLCatchAllProxy, specFromNeed } from '@fritill-team/nuxt-media-library/server'
99
+
100
+ const COVER = { kind: 'image', visibility: 'public', allowedMimeTypes: ['image/*'], maxSizeBytes: 5_000_000 }
101
+
102
+ export default createMLCatchAllProxy({
103
+ async authorize(event, need) {
104
+ const user = await requireUser(event) // your session
105
+ const subject = String(user.id)
106
+ if (need.scope === 'upload' && !need.sessionId) { // creating an upload
107
+ return user.canUpload ? { scopes: ['upload'], profile: COVER, subject } : false
108
+ }
109
+ if (need.taskId) return false // or look up the task's asset
110
+ if (need.scope === 'manage:*') return user.isAdmin // tenant-wide list, stream
111
+ return (need.assetIds ?? []).every(id => user.canEdit(id))
112
+ ? { ...specFromNeed(need), subject }
113
+ : false
114
+ },
115
+ })
116
+ ```
117
+
118
+ - `need`: `{ scope, method, path, assetIds?, taskId?, sessionId?, upload? }`
119
+ (`upload` is the create body: MIME type, size, file name).
120
+ - Return `false` (403), `true` (the need as is), or a spec
121
+ `{ scopes, assetIds?, sessionIds?, profile?, subject?, ttlSeconds? }`.
122
+ Creating an upload needs a `profile`: kind, visibility and processing come
123
+ from it, not from the browser. `manage:*` is only accepted for requests
124
+ that need it.
125
+ - Grants are issued with the private runtime config
126
+ `mediaLibrary.serviceToken` (`NUXT_MEDIA_LIBRARY_SERVICE_TOKEN`), or pass
127
+ `issueGrant(spec)` to get them from your backend (e.g. the Python
128
+ client's `create_grant_router`). They are cached per spec until shortly
129
+ before they expire; an upload's parts, finalize and abort reuse the grant
130
+ that created the session.
131
+ - `/delivery` and the processing metadata routes are not affected.
132
+
133
+ ### Showing assets of several services: read grants
134
+
135
+ Every link comes from the media library; each service decides only for its
136
+ own assets by returning a read grant (`mlg_…`, e.g. the Python client's
137
+ `client.grants.read(ids)`) with the data that references them. Hand the
138
+ grants to the module and every preview resolve sends them in one request:
139
+
140
+ ```ts
141
+ const { addMediaGrants } = useMLMediaGrants()
142
+
143
+ const lesson = await $fetch('/api/lessons/42') // course service
144
+ const profile = await $fetch('/api/profile') // profile service
145
+ addMediaGrants(lesson.mediaGrant, profile.mediaGrant) // "mlg_…" or { grant, expiresAt }
146
+ ```
147
+
148
+ - Live grants (up to 10, latest expiry first) go in `X-Media-Grants` on
149
+ `POST /api/media-library/delivery/assets/resolve`; the proxy forwards
150
+ them. Expired ones are dropped.
151
+ - Assets no grant lists come back `forbidden` from the server and are shown
152
+ as broken (`isBroken(id)`), as before; they are resolved again whenever
153
+ grants are added.
154
+ - Grants live in the browser only, never in server-side state.
155
+
85
156
  ### Vue installation (non-Nuxt)
86
157
 
87
158
  ```bash
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "media-library",
3
3
  "configKey": "mediaLibrary",
4
- "version": "0.0.44",
4
+ "version": "0.0.49",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -93,7 +93,10 @@ const module$1 = defineNuxtModule({
93
93
  ...existingPrivate,
94
94
  baseURL: existingPrivate.baseURL ?? envBaseURL ?? options.baseURL ?? "",
95
95
  routes: existingPrivate.routes ?? options.routes ?? routes,
96
- headers: resolvedHeaders
96
+ headers: resolvedHeaders,
97
+ // Only read when a proxy has `authorize` and no `issueGrant` (media
98
+ // grants). Declared so NUXT_MEDIA_LIBRARY_SERVICE_TOKEN applies.
99
+ serviceToken: existingPrivate.serviceToken ?? ""
97
100
  };
98
101
  nuxt.options.runtimeConfig.public.mediaLibrary = {
99
102
  ...existingPublic,
@@ -1,6 +1,7 @@
1
1
  <script setup>
2
2
  import { computed, ref, onMounted, watch } from "vue";
3
3
  import { useMLResourcePolicy } from "../../composables/resourcePolicy/useMLResourcePolicy";
4
+ import { mediaGrantHeaders } from "../../composables/useMLMediaGrants";
4
5
  const props = defineProps({
5
6
  assetId: { type: String, required: true },
6
7
  manager: { type: Object, required: true }
@@ -34,7 +35,8 @@ async function resolveVideoUrl() {
34
35
  }
35
36
  try {
36
37
  const result = await $fetch(
37
- `/api/media-library/delivery/assets/${encodeURIComponent(props.assetId)}`
38
+ `/api/media-library/delivery/assets/${encodeURIComponent(props.assetId)}`,
39
+ { headers: mediaGrantHeaders() }
38
40
  );
39
41
  videoUrl.value = result.downloadUrl ?? null;
40
42
  } catch {
@@ -0,0 +1,60 @@
1
+ import { type RejectedFile } from '../../utils/fileAccept.js';
2
+ type __VLS_Props = {
3
+ /** Browser accept syntax ("image/*,.glb"). Build one from kinds with acceptForKinds(). */
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ /** Bytes. Larger files are rejected with reason "size". */
7
+ maxSize?: number | null;
8
+ /** How many more files fit. Extra files are rejected with reason "count". */
9
+ maxFiles?: number | null;
10
+ disabled?: boolean;
11
+ /**
12
+ * `area`: a dashed box with an upload button (fields, picker).
13
+ * `overlay`: wraps the default slot and only shows a highlight while files are dragged over it (gallery).
14
+ */
15
+ variant?: 'area' | 'overlay';
16
+ title?: string;
17
+ hint?: string;
18
+ /** The button that opens the computer's file dialog. Defaults to "Upload file(s)". */
19
+ buttonLabel?: string;
20
+ /** One short row instead of a box, for places where the drop zone is secondary (the picker). */
21
+ compact?: boolean;
22
+ };
23
+ type __VLS_Slots = {
24
+ default?: (props: {
25
+ isDragging: boolean;
26
+ open: () => void;
27
+ }) => unknown;
28
+ actions?: (props: {
29
+ open: () => void;
30
+ }) => unknown;
31
+ };
32
+ declare function open(): void;
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
34
+ open: typeof open;
35
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ files: (files: File[]) => any;
37
+ rejected: (files: RejectedFile[]) => any;
38
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
39
+ onFiles?: ((files: File[]) => any) | undefined;
40
+ onRejected?: ((files: RejectedFile[]) => any) | undefined;
41
+ }>, {
42
+ title: string;
43
+ accept: string;
44
+ maxSize: number | null;
45
+ variant: "area" | "overlay";
46
+ compact: boolean;
47
+ disabled: boolean;
48
+ multiple: boolean;
49
+ maxFiles: number | null;
50
+ hint: string;
51
+ buttonLabel: string;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
53
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
54
+ declare const _default: typeof __VLS_export;
55
+ export default _default;
56
+ type __VLS_WithSlots<T, S> = T & {
57
+ new (): {
58
+ $slots: S;
59
+ };
60
+ };
@@ -0,0 +1,137 @@
1
+ <script setup>
2
+ import { computed, ref } from "vue";
3
+ import { useFileUpload } from "#imports";
4
+ import { formatBytes } from "../../utils/assetFormat";
5
+ import { partitionFiles } from "../../utils/fileAccept";
6
+ const props = defineProps({
7
+ accept: { type: String, required: false, default: "*" },
8
+ multiple: { type: Boolean, required: false, default: true },
9
+ maxSize: { type: [Number, null], required: false, default: null },
10
+ maxFiles: { type: [Number, null], required: false, default: null },
11
+ disabled: { type: Boolean, required: false, default: false },
12
+ variant: { type: String, required: false, default: "area" },
13
+ title: { type: String, required: false, default: void 0 },
14
+ hint: { type: String, required: false, default: void 0 },
15
+ buttonLabel: { type: String, required: false, default: void 0 },
16
+ compact: { type: Boolean, required: false, default: false }
17
+ });
18
+ const emit = defineEmits(["files", "rejected"]);
19
+ defineSlots();
20
+ const { isDragging, dropzoneRef } = useFileUpload({
21
+ accept: "*",
22
+ multiple: true,
23
+ dropzone: true,
24
+ onUpdate: (files) => handle(files)
25
+ });
26
+ const input = ref();
27
+ function handle(files) {
28
+ if (props.disabled || !files.length) return;
29
+ const max = props.multiple ? props.maxFiles : Math.min(1, props.maxFiles ?? 1);
30
+ const { accepted, rejected } = partitionFiles(files, { accept: props.accept, maxSize: props.maxSize, maxFiles: max });
31
+ if (accepted.length) emit("files", accepted);
32
+ if (rejected.length) emit("rejected", rejected);
33
+ }
34
+ function open() {
35
+ if (!props.disabled) input.value?.click();
36
+ }
37
+ function onInput(e) {
38
+ const el = e.target;
39
+ handle(Array.from(el.files ?? []));
40
+ el.value = "";
41
+ }
42
+ const carriesFiles = ref(false);
43
+ function onDragEnter(e) {
44
+ carriesFiles.value = !!e.dataTransfer && Array.from(e.dataTransfer.types).includes("Files");
45
+ }
46
+ const active = computed(() => isDragging.value && carriesFiles.value && !props.disabled);
47
+ const sizeHint = computed(() => props.maxSize ? `Up to ${formatBytes(props.maxSize)}` : "");
48
+ defineExpose({ open });
49
+ </script>
50
+
51
+ <template>
52
+ <div
53
+ ref="dropzoneRef"
54
+ class="relative"
55
+ @dragenter.capture="onDragEnter"
56
+ :data-dragging="active || void 0"
57
+ :aria-disabled="disabled || void 0"
58
+ >
59
+ <input
60
+ ref="input"
61
+ type="file"
62
+ class="sr-only"
63
+ tabindex="-1"
64
+ aria-hidden="true"
65
+ :accept="accept === '*' ? void 0 : accept"
66
+ :multiple="multiple"
67
+ :disabled="disabled"
68
+ @change="onInput"
69
+ >
70
+
71
+ <template v-if="variant === 'overlay'">
72
+ <slot
73
+ :is-dragging="active"
74
+ :open="open"
75
+ />
76
+ <div
77
+ v-if="active"
78
+ class="pointer-events-none absolute inset-0 z-20 grid place-items-center rounded-[inherit] border-2 border-dashed border-primary bg-primary/10 backdrop-blur-[1px]"
79
+ >
80
+ <span class="flex items-center gap-2 rounded-md bg-default px-3 py-1.5 text-sm font-semibold text-primary shadow-sm">
81
+ <UIcon
82
+ name="i-lucide-upload"
83
+ class="size-4"
84
+ />
85
+ {{ title ?? "Drop to upload" }}
86
+ </span>
87
+ </div>
88
+ </template>
89
+
90
+ <div
91
+ v-else
92
+ class="flex items-center rounded-lg border-[1.5px] border-dashed text-center transition-colors motion-reduce:transition-none"
93
+ :class="[
94
+ compact ? 'flex-row flex-wrap justify-center gap-x-3 gap-y-1.5 px-3 py-2.5' : 'flex-col gap-2.5 px-4 py-5',
95
+ active ? 'border-primary bg-primary/10' : 'border-accented bg-default',
96
+ disabled ? 'opacity-60' : ''
97
+ ]"
98
+ >
99
+ <slot
100
+ :is-dragging="active"
101
+ :open="open"
102
+ >
103
+ <UIcon
104
+ v-if="!compact"
105
+ name="i-lucide-upload"
106
+ class="size-5"
107
+ :class="active ? 'text-primary' : 'text-dimmed'"
108
+ />
109
+ <p class="text-sm">
110
+ <span :class="compact ? 'text-muted' : 'font-semibold'">{{ title ?? (multiple ? "Drop files here" : "Drop a file here") }}</span>
111
+ <span class="text-muted"> or</span>
112
+ </p>
113
+ <div class="flex flex-wrap justify-center gap-1.5">
114
+ <UButton
115
+ size="sm"
116
+ color="neutral"
117
+ variant="outline"
118
+ icon="i-lucide-upload"
119
+ :label="buttonLabel ?? (multiple ? 'Upload files' : 'Upload file')"
120
+ :disabled="disabled"
121
+ @click="open"
122
+ />
123
+ <slot
124
+ name="actions"
125
+ :open="open"
126
+ />
127
+ </div>
128
+ <p
129
+ v-if="hint || sizeHint"
130
+ class="text-xs text-dimmed"
131
+ >
132
+ {{ [hint, sizeHint].filter(Boolean).join(" \xB7 ") }}
133
+ </p>
134
+ </slot>
135
+ </div>
136
+ </div>
137
+ </template>
@@ -0,0 +1,60 @@
1
+ import { type RejectedFile } from '../../utils/fileAccept.js';
2
+ type __VLS_Props = {
3
+ /** Browser accept syntax ("image/*,.glb"). Build one from kinds with acceptForKinds(). */
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ /** Bytes. Larger files are rejected with reason "size". */
7
+ maxSize?: number | null;
8
+ /** How many more files fit. Extra files are rejected with reason "count". */
9
+ maxFiles?: number | null;
10
+ disabled?: boolean;
11
+ /**
12
+ * `area`: a dashed box with an upload button (fields, picker).
13
+ * `overlay`: wraps the default slot and only shows a highlight while files are dragged over it (gallery).
14
+ */
15
+ variant?: 'area' | 'overlay';
16
+ title?: string;
17
+ hint?: string;
18
+ /** The button that opens the computer's file dialog. Defaults to "Upload file(s)". */
19
+ buttonLabel?: string;
20
+ /** One short row instead of a box, for places where the drop zone is secondary (the picker). */
21
+ compact?: boolean;
22
+ };
23
+ type __VLS_Slots = {
24
+ default?: (props: {
25
+ isDragging: boolean;
26
+ open: () => void;
27
+ }) => unknown;
28
+ actions?: (props: {
29
+ open: () => void;
30
+ }) => unknown;
31
+ };
32
+ declare function open(): void;
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
34
+ open: typeof open;
35
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ files: (files: File[]) => any;
37
+ rejected: (files: RejectedFile[]) => any;
38
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
39
+ onFiles?: ((files: File[]) => any) | undefined;
40
+ onRejected?: ((files: RejectedFile[]) => any) | undefined;
41
+ }>, {
42
+ title: string;
43
+ accept: string;
44
+ maxSize: number | null;
45
+ variant: "area" | "overlay";
46
+ compact: boolean;
47
+ disabled: boolean;
48
+ multiple: boolean;
49
+ maxFiles: number | null;
50
+ hint: string;
51
+ buttonLabel: string;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
53
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
54
+ declare const _default: typeof __VLS_export;
55
+ export default _default;
56
+ type __VLS_WithSlots<T, S> = T & {
57
+ new (): {
58
+ $slots: S;
59
+ };
60
+ };
@@ -0,0 +1,23 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ export type MediaEditorResult = {
3
+ action: 'save';
4
+ alt: Record<string, string>;
5
+ } | {
6
+ action: 'remove';
7
+ assetId: string;
8
+ };
9
+ type __VLS_Props = {
10
+ assetId: string;
11
+ siblings?: readonly string[];
12
+ alt?: Readonly<Record<string, string>>;
13
+ readonly?: boolean;
14
+ removeLabel?: string;
15
+ usages?: Readonly<Record<string, MediaUsage[]>>;
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
18
+ close: (result: MediaEditorResult | null) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ onClose?: ((result: MediaEditorResult | null) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
@@ -0,0 +1,34 @@
1
+ <script setup>
2
+ defineProps({
3
+ assetId: { type: String, required: true },
4
+ siblings: { type: Array, required: false },
5
+ alt: { type: Object, required: false },
6
+ readonly: { type: Boolean, required: false },
7
+ removeLabel: { type: String, required: false },
8
+ usages: { type: Object, required: false }
9
+ });
10
+ const emit = defineEmits(["close"]);
11
+ </script>
12
+
13
+ <template>
14
+ <UModal
15
+ :title="readonly ? 'Preview' : 'Edit media'"
16
+ :close="{ onClick: () => emit('close', null) }"
17
+ :ui="{ content: 'sm:max-w-5xl', body: 'p-0 sm:p-0' }"
18
+ @update:open="(open) => !open && emit('close', null)"
19
+ >
20
+ <template #body>
21
+ <MLMediaEditorPanel
22
+ :asset-id="assetId"
23
+ :siblings="siblings"
24
+ :alt="alt"
25
+ :readonly="readonly"
26
+ :remove-label="removeLabel"
27
+ :usages="usages"
28
+ @save="({ alt: a }) => emit('close', { action: 'save', alt: a })"
29
+ @remove="(id) => emit('close', { action: 'remove', assetId: id })"
30
+ @cancel="emit('close', null)"
31
+ />
32
+ </template>
33
+ </UModal>
34
+ </template>
@@ -0,0 +1,23 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ export type MediaEditorResult = {
3
+ action: 'save';
4
+ alt: Record<string, string>;
5
+ } | {
6
+ action: 'remove';
7
+ assetId: string;
8
+ };
9
+ type __VLS_Props = {
10
+ assetId: string;
11
+ siblings?: readonly string[];
12
+ alt?: Readonly<Record<string, string>>;
13
+ readonly?: boolean;
14
+ removeLabel?: string;
15
+ usages?: Readonly<Record<string, MediaUsage[]>>;
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
18
+ close: (result: MediaEditorResult | null) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ onClose?: ((result: MediaEditorResult | null) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ type __VLS_Props = {
3
+ /** The file to show first. */
4
+ assetId: string;
5
+ /** The record's other files, in order. Enables previous / next. */
6
+ siblings?: readonly string[];
7
+ /** Current alt text per asset ID. Alt text lives on the host record, not the file. */
8
+ alt?: Readonly<Record<string, string>>;
9
+ /** Preview only: no alt editing, no Save. Used from the picker. */
10
+ readonly?: boolean;
11
+ /** Label for the remove button, e.g. "Remove from product". Empty hides it. */
12
+ removeLabel?: string;
13
+ /** Where each file is used, per asset ID. Omit to hide the section. */
14
+ usages?: Readonly<Record<string, MediaUsage[]>>;
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
17
+ currentId: import("vue").ComputedRef<string>;
18
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ remove: (assetId: string) => any;
20
+ cancel: () => any;
21
+ save: (result: {
22
+ alt: Record<string, string>;
23
+ }) => any;
24
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
25
+ onRemove?: ((assetId: string) => any) | undefined;
26
+ onCancel?: (() => any) | undefined;
27
+ onSave?: ((result: {
28
+ alt: Record<string, string>;
29
+ }) => any) | undefined;
30
+ }>, {
31
+ usages: Readonly<Record<string, MediaUsage[]>>;
32
+ siblings: readonly string[];
33
+ alt: Readonly<Record<string, string>>;
34
+ readonly: boolean;
35
+ removeLabel: string;
36
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
37
+ declare const _default: typeof __VLS_export;
38
+ export default _default;