nuxt-media-library 0.0.44

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 (164) hide show
  1. package/README.md +243 -0
  2. package/dist/module.d.mts +31 -0
  3. package/dist/module.d.ts +31 -0
  4. package/dist/module.json +9 -0
  5. package/dist/module.mjs +118 -0
  6. package/dist/runtime/components/MLAssetManager/AssetActivity.d.vue.ts +6 -0
  7. package/dist/runtime/components/MLAssetManager/AssetActivity.vue +13 -0
  8. package/dist/runtime/components/MLAssetManager/AssetActivity.vue.d.ts +6 -0
  9. package/dist/runtime/components/MLAssetManager/AssetCard.d.vue.ts +14 -0
  10. package/dist/runtime/components/MLAssetManager/AssetCard.vue +100 -0
  11. package/dist/runtime/components/MLAssetManager/AssetCard.vue.d.ts +14 -0
  12. package/dist/runtime/components/MLAssetManager/AssetDetail.d.vue.ts +14 -0
  13. package/dist/runtime/components/MLAssetManager/AssetDetail.vue +156 -0
  14. package/dist/runtime/components/MLAssetManager/AssetDetail.vue.d.ts +14 -0
  15. package/dist/runtime/components/MLAssetManager/AssetList.d.vue.ts +15 -0
  16. package/dist/runtime/components/MLAssetManager/AssetList.vue +41 -0
  17. package/dist/runtime/components/MLAssetManager/AssetList.vue.d.ts +15 -0
  18. package/dist/runtime/components/MLAssetManager/AssetOverview.d.vue.ts +8 -0
  19. package/dist/runtime/components/MLAssetManager/AssetOverview.vue +321 -0
  20. package/dist/runtime/components/MLAssetManager/AssetOverview.vue.d.ts +8 -0
  21. package/dist/runtime/components/MLAssetManager/AssetPreview.d.vue.ts +10 -0
  22. package/dist/runtime/components/MLAssetManager/AssetPreview.vue +18 -0
  23. package/dist/runtime/components/MLAssetManager/AssetPreview.vue.d.ts +10 -0
  24. package/dist/runtime/components/MLAssetManager/AssetPreviewAudio.d.vue.ts +9 -0
  25. package/dist/runtime/components/MLAssetManager/AssetPreviewAudio.vue +21 -0
  26. package/dist/runtime/components/MLAssetManager/AssetPreviewAudio.vue.d.ts +9 -0
  27. package/dist/runtime/components/MLAssetManager/AssetPreviewDocument.d.vue.ts +9 -0
  28. package/dist/runtime/components/MLAssetManager/AssetPreviewDocument.vue +39 -0
  29. package/dist/runtime/components/MLAssetManager/AssetPreviewDocument.vue.d.ts +9 -0
  30. package/dist/runtime/components/MLAssetManager/AssetPreviewFallback.d.vue.ts +7 -0
  31. package/dist/runtime/components/MLAssetManager/AssetPreviewFallback.vue +41 -0
  32. package/dist/runtime/components/MLAssetManager/AssetPreviewFallback.vue.d.ts +7 -0
  33. package/dist/runtime/components/MLAssetManager/AssetPreviewImage.d.vue.ts +9 -0
  34. package/dist/runtime/components/MLAssetManager/AssetPreviewImage.vue +30 -0
  35. package/dist/runtime/components/MLAssetManager/AssetPreviewImage.vue.d.ts +9 -0
  36. package/dist/runtime/components/MLAssetManager/AssetPreviewSidebar.d.vue.ts +15 -0
  37. package/dist/runtime/components/MLAssetManager/AssetPreviewSidebar.vue +69 -0
  38. package/dist/runtime/components/MLAssetManager/AssetPreviewSidebar.vue.d.ts +15 -0
  39. package/dist/runtime/components/MLAssetManager/AssetPreviewVideo.d.vue.ts +9 -0
  40. package/dist/runtime/components/MLAssetManager/AssetPreviewVideo.vue +148 -0
  41. package/dist/runtime/components/MLAssetManager/AssetPreviewVideo.vue.d.ts +9 -0
  42. package/dist/runtime/components/MLAssetManager/AssetRenditions.d.vue.ts +8 -0
  43. package/dist/runtime/components/MLAssetManager/AssetRenditions.vue +148 -0
  44. package/dist/runtime/components/MLAssetManager/AssetRenditions.vue.d.ts +8 -0
  45. package/dist/runtime/components/MLAssetManager/Processing.d.vue.ts +8 -0
  46. package/dist/runtime/components/MLAssetManager/Processing.vue +84 -0
  47. package/dist/runtime/components/MLAssetManager/Processing.vue.d.ts +8 -0
  48. package/dist/runtime/components/MLAssetManager/SlotCard.d.vue.ts +17 -0
  49. package/dist/runtime/components/MLAssetManager/SlotCard.vue +94 -0
  50. package/dist/runtime/components/MLAssetManager/SlotCard.vue.d.ts +17 -0
  51. package/dist/runtime/components/MLAssetManager/Widget.d.vue.ts +14 -0
  52. package/dist/runtime/components/MLAssetManager/Widget.vue +328 -0
  53. package/dist/runtime/components/MLAssetManager/Widget.vue.d.ts +14 -0
  54. package/dist/runtime/components/MLPreviewProvider.d.vue.ts +31 -0
  55. package/dist/runtime/components/MLPreviewProvider.vue +51 -0
  56. package/dist/runtime/components/MLPreviewProvider.vue.d.ts +31 -0
  57. package/dist/runtime/components/MLProcessing/Forms/ConfigureStepModal.d.vue.ts +13 -0
  58. package/dist/runtime/components/MLProcessing/Forms/ConfigureStepModal.vue +47 -0
  59. package/dist/runtime/components/MLProcessing/Forms/ConfigureStepModal.vue.d.ts +13 -0
  60. package/dist/runtime/components/MLProcessing/Forms/FramePicker.d.vue.ts +11 -0
  61. package/dist/runtime/components/MLProcessing/Forms/FramePicker.vue +162 -0
  62. package/dist/runtime/components/MLProcessing/Forms/FramePicker.vue.d.ts +11 -0
  63. package/dist/runtime/components/MLProcessing/Forms/StepOptionsForm.d.vue.ts +14 -0
  64. package/dist/runtime/components/MLProcessing/Forms/StepOptionsForm.vue +113 -0
  65. package/dist/runtime/components/MLProcessing/Forms/StepOptionsForm.vue.d.ts +14 -0
  66. package/dist/runtime/components/MLProcessing/Pipeline.d.vue.ts +15 -0
  67. package/dist/runtime/components/MLProcessing/Pipeline.vue +178 -0
  68. package/dist/runtime/components/MLProcessing/Pipeline.vue.d.ts +15 -0
  69. package/dist/runtime/components/MLProcessing/PipelineStepItem.d.vue.ts +20 -0
  70. package/dist/runtime/components/MLProcessing/PipelineStepItem.vue +309 -0
  71. package/dist/runtime/components/MLProcessing/PipelineStepItem.vue.d.ts +20 -0
  72. package/dist/runtime/components/MLProcessing/ProgressBar.d.vue.ts +9 -0
  73. package/dist/runtime/components/MLProcessing/ProgressBar.vue +27 -0
  74. package/dist/runtime/components/MLProcessing/ProgressBar.vue.d.ts +9 -0
  75. package/dist/runtime/components/MLProcessing/StepLayout.d.vue.ts +31 -0
  76. package/dist/runtime/components/MLProcessing/StepLayout.vue +36 -0
  77. package/dist/runtime/components/MLProcessing/StepLayout.vue.d.ts +31 -0
  78. package/dist/runtime/components/MLProcessing/Stepper.d.vue.ts +16 -0
  79. package/dist/runtime/components/MLProcessing/Stepper.vue +87 -0
  80. package/dist/runtime/components/MLProcessing/Stepper.vue.d.ts +16 -0
  81. package/dist/runtime/components/MLProcessing/StepperItem.d.vue.ts +16 -0
  82. package/dist/runtime/components/MLProcessing/StepperItem.vue +150 -0
  83. package/dist/runtime/components/MLProcessing/StepperItem.vue.d.ts +16 -0
  84. package/dist/runtime/components/MLProcessing/TopBar.d.vue.ts +21 -0
  85. package/dist/runtime/components/MLProcessing/TopBar.vue +46 -0
  86. package/dist/runtime/components/MLProcessing/TopBar.vue.d.ts +21 -0
  87. package/dist/runtime/components/MLUploader/ButtonUploader.d.vue.ts +55 -0
  88. package/dist/runtime/components/MLUploader/ButtonUploader.vue +211 -0
  89. package/dist/runtime/components/MLUploader/ButtonUploader.vue.d.ts +55 -0
  90. package/dist/runtime/components/MLUploader/Item.d.vue.ts +34 -0
  91. package/dist/runtime/components/MLUploader/Item.vue +242 -0
  92. package/dist/runtime/components/MLUploader/Item.vue.d.ts +34 -0
  93. package/dist/runtime/components/MLUploader/PolicyPopover.d.vue.ts +7 -0
  94. package/dist/runtime/components/MLUploader/PolicyPopover.vue +160 -0
  95. package/dist/runtime/components/MLUploader/PolicyPopover.vue.d.ts +7 -0
  96. package/dist/runtime/components/MLUploader/Widget.d.vue.ts +44 -0
  97. package/dist/runtime/components/MLUploader/Widget.vue +251 -0
  98. package/dist/runtime/components/MLUploader/Widget.vue.d.ts +44 -0
  99. package/dist/runtime/composables/resourcePolicy/mimeUtils.d.ts +4 -0
  100. package/dist/runtime/composables/resourcePolicy/mimeUtils.js +71 -0
  101. package/dist/runtime/composables/resourcePolicy/useMLResourcePolicy.d.ts +17 -0
  102. package/dist/runtime/composables/resourcePolicy/useMLResourcePolicy.js +150 -0
  103. package/dist/runtime/composables/useElapsedTimer.d.ts +13 -0
  104. package/dist/runtime/composables/useElapsedTimer.js +46 -0
  105. package/dist/runtime/composables/useMLAssetLibrary.d.ts +31 -0
  106. package/dist/runtime/composables/useMLAssetLibrary.js +53 -0
  107. package/dist/runtime/composables/useMLAssetManager.d.ts +30 -0
  108. package/dist/runtime/composables/useMLAssetManager.js +229 -0
  109. package/dist/runtime/composables/useMLAssetManagerPreview.d.ts +28 -0
  110. package/dist/runtime/composables/useMLAssetManagerPreview.js +232 -0
  111. package/dist/runtime/composables/useMLAssetPreview.d.ts +23 -0
  112. package/dist/runtime/composables/useMLAssetPreview.js +153 -0
  113. package/dist/runtime/composables/useMLChunkUploader.d.ts +9 -0
  114. package/dist/runtime/composables/useMLChunkUploader.js +22 -0
  115. package/dist/runtime/composables/useMLProcessingPipeline.d.ts +38 -0
  116. package/dist/runtime/composables/useMLProcessingPipeline.js +258 -0
  117. package/dist/runtime/composables/useMLProcessingStatus.d.ts +34 -0
  118. package/dist/runtime/composables/useMLProcessingStatus.js +100 -0
  119. package/dist/runtime/composables/useMLProcessingStepper.d.ts +32 -0
  120. package/dist/runtime/composables/useMLProcessingStepper.js +280 -0
  121. package/dist/runtime/composables/useMLProcessingStrategies.d.ts +7 -0
  122. package/dist/runtime/composables/useMLProcessingStrategies.js +47 -0
  123. package/dist/runtime/composables/useMLRepeater.d.ts +5 -0
  124. package/dist/runtime/composables/useMLRepeater.js +11 -0
  125. package/dist/runtime/css/index.css +1 -0
  126. package/dist/runtime/index.d.ts +13 -0
  127. package/dist/runtime/index.js +13 -0
  128. package/dist/runtime/plugins/asset-direcetive-client.d.ts +2 -0
  129. package/dist/runtime/plugins/asset-direcetive-client.js +64 -0
  130. package/dist/runtime/plugins/config.d.ts +13 -0
  131. package/dist/runtime/plugins/config.js +23 -0
  132. package/dist/runtime/schemas/resource-policy.schema.d.ts +607 -0
  133. package/dist/runtime/schemas/resource-policy.schema.js +117 -0
  134. package/dist/runtime/server/assets-proxy.d.ts +2 -0
  135. package/dist/runtime/server/assets-proxy.js +81 -0
  136. package/dist/runtime/server/create-ml-proxy.d.ts +47 -0
  137. package/dist/runtime/server/create-ml-proxy.js +232 -0
  138. package/dist/runtime/server/delivery-proxy.d.ts +2 -0
  139. package/dist/runtime/server/delivery-proxy.js +74 -0
  140. package/dist/runtime/server/index.d.ts +2 -0
  141. package/dist/runtime/server/index.js +1 -0
  142. package/dist/runtime/server/processing-proxy.d.ts +2 -0
  143. package/dist/runtime/server/processing-proxy.js +55 -0
  144. package/dist/runtime/server/resource-policy-proxy.d.ts +2 -0
  145. package/dist/runtime/server/resource-policy-proxy.js +80 -0
  146. package/dist/runtime/server/tsconfig.json +0 -0
  147. package/dist/runtime/server/uploads-proxy.d.ts +2 -0
  148. package/dist/runtime/server/uploads-proxy.js +99 -0
  149. package/dist/runtime/types/asset.d.ts +174 -0
  150. package/dist/runtime/types/asset.js +7 -0
  151. package/dist/runtime/types/preview.d.ts +20 -0
  152. package/dist/runtime/types/preview.js +0 -0
  153. package/dist/runtime/types/resourcePolicy.d.ts +77 -0
  154. package/dist/runtime/types/resourcePolicy.js +25 -0
  155. package/dist/runtime/types/uploader.d.ts +71 -0
  156. package/dist/runtime/types/uploader.js +0 -0
  157. package/dist/runtime/utils/uploader/checksum.d.ts +21 -0
  158. package/dist/runtime/utils/uploader/checksum.js +23 -0
  159. package/dist/runtime/utils/uploader/chunking.d.ts +8 -0
  160. package/dist/runtime/utils/uploader/chunking.js +21 -0
  161. package/dist/runtime/utils/uploader/uploader.d.ts +20 -0
  162. package/dist/runtime/utils/uploader/uploader.js +206 -0
  163. package/dist/types.d.mts +11 -0
  164. package/package.json +81 -0
@@ -0,0 +1,251 @@
1
+ <script setup>
2
+ import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
3
+ import { useMLResourcePolicy } from "../../index";
4
+ import { buildAcceptAttribute, isFileMimeAllowed } from "../../composables/resourcePolicy/mimeUtils";
5
+ const props = defineProps({
6
+ resourceType: { type: String, required: true },
7
+ collectionName: { type: [String, null], required: false },
8
+ chunkUploader: { type: [Object, String], required: false },
9
+ titleText: { type: String, required: false, default: "Upload your files" },
10
+ hintText: { type: String, required: false, default: "Drag and drop your files here" },
11
+ clickText: { type: String, required: false, default: "(or click to select files)" },
12
+ ariaLabelText: { type: String, required: false, default: "Upload files by clicking or dragging them here" },
13
+ emptyStateIconText: { type: String, required: false, default: "\u2B06" },
14
+ policyErrorText: { type: String, required: false, default: "Failed to load resource policy." },
15
+ policyLoadingText: { type: String, required: false, default: "Resource policy not loaded yet." },
16
+ uploadErrorText: { type: String, required: false, default: "Upload failed. Please try again." },
17
+ singleFileErrorText: { type: String, required: false, default: "Only one file is allowed for this policy." },
18
+ fileTypeErrorText: { type: String, required: false, default: "File type is not allowed." },
19
+ fileTooLargeText: { type: String, required: false, default: "File is larger than the maximum allowed size." },
20
+ fileTooSmallText: { type: String, required: false, default: "File is smaller than the minimum allowed size." }
21
+ });
22
+ const model = defineModel({ type: Array, ...{ default: () => [] } });
23
+ const uploading = ref(false);
24
+ const errorMessage = ref(null);
25
+ const policy = ref(null);
26
+ const policyError = ref(null);
27
+ const fileInput = ref(null);
28
+ const isDragging = ref(false);
29
+ const items = ref([]);
30
+ async function loadPolicy(resourceType, collectionName, forceRefresh = false) {
31
+ if (!forceRefresh) {
32
+ policy.value = null;
33
+ items.value = [];
34
+ uploading.value = false;
35
+ }
36
+ policyError.value = null;
37
+ errorMessage.value = null;
38
+ if (!resourceType) return;
39
+ try {
40
+ const resourcePolicy = useMLResourcePolicy();
41
+ const fetcher = forceRefresh ? resourcePolicy.refreshByTypeCollection : resourcePolicy.findByTypeCollection;
42
+ policy.value = await fetcher(resourceType, collectionName);
43
+ } catch (err) {
44
+ console.error("Failed to load resource policy", err);
45
+ policyError.value = props.policyErrorText;
46
+ }
47
+ }
48
+ const onVisibilityChange = () => {
49
+ if (typeof document === "undefined") return;
50
+ if (document.visibilityState === "visible" && props.resourceType) {
51
+ loadPolicy(props.resourceType, props.collectionName, true);
52
+ }
53
+ };
54
+ onMounted(() => {
55
+ loadPolicy(props.resourceType, props.collectionName, true);
56
+ if (typeof document !== "undefined") {
57
+ document.addEventListener("visibilitychange", onVisibilityChange);
58
+ }
59
+ });
60
+ onBeforeUnmount(() => {
61
+ if (typeof document !== "undefined") {
62
+ document.removeEventListener("visibilitychange", onVisibilityChange);
63
+ }
64
+ });
65
+ watch(
66
+ () => [props.resourceType, props.collectionName],
67
+ ([newType, newCollection]) => {
68
+ if (newType) loadPolicy(newType, newCollection, true);
69
+ }
70
+ );
71
+ const acceptTypes = computed(() => {
72
+ const allowed = policy.value?.file_constraints?.allowed_mime_types;
73
+ return allowed?.length ? buildAcceptAttribute(allowed) : void 0;
74
+ });
75
+ const allowMultiple = computed(() => {
76
+ const allow = policy.value?.file_constraints?.allow_multiple;
77
+ return Boolean(allow);
78
+ });
79
+ const validateFile = (file) => {
80
+ const constraints = policy.value?.file_constraints;
81
+ if (!constraints) return null;
82
+ if (constraints.allowed_mime_types?.length) {
83
+ if (!isFileMimeAllowed(file, constraints.allowed_mime_types)) {
84
+ return props.fileTypeErrorText;
85
+ }
86
+ }
87
+ if (constraints.max_size_bytes != null && file.size > constraints.max_size_bytes) {
88
+ return props.fileTooLargeText;
89
+ }
90
+ if (constraints.min_size_bytes != null && file.size < constraints.min_size_bytes) {
91
+ return props.fileTooSmallText;
92
+ }
93
+ return null;
94
+ };
95
+ const triggerFileSelect = () => fileInput.value?.click();
96
+ const handleFiles = async (fileList) => {
97
+ const list = Array.from(fileList);
98
+ if (!allowMultiple.value && list.length > 1) {
99
+ errorMessage.value = props.singleFileErrorText;
100
+ return;
101
+ }
102
+ const selected = allowMultiple.value ? list : list.slice(0, 1);
103
+ if (!selected.length) return;
104
+ if (policyError.value) {
105
+ errorMessage.value = policyError.value;
106
+ return;
107
+ }
108
+ if (!policy.value) {
109
+ errorMessage.value = props.policyLoadingText;
110
+ return;
111
+ }
112
+ for (const file of selected) {
113
+ const validationError = validateFile(file);
114
+ if (validationError) {
115
+ errorMessage.value = validationError;
116
+ return;
117
+ }
118
+ }
119
+ items.value = selected.map((file) => ({
120
+ id: `${Date.now()}-${file.name}-${Math.random().toString(16).slice(2)}`,
121
+ file,
122
+ status: "uploading"
123
+ }));
124
+ errorMessage.value = null;
125
+ uploading.value = true;
126
+ };
127
+ const onPick = (e) => {
128
+ const input = e.target;
129
+ if (!input?.files?.length) return;
130
+ handleFiles(input.files);
131
+ input.value = "";
132
+ };
133
+ const onDrop = (event) => {
134
+ event.preventDefault();
135
+ isDragging.value = false;
136
+ if (event.dataTransfer?.files?.length) {
137
+ handleFiles(event.dataTransfer.files);
138
+ }
139
+ };
140
+ const checkAllDone = () => {
141
+ if (items.value.every((i) => i.status !== "pending" && i.status !== "uploading")) {
142
+ uploading.value = false;
143
+ }
144
+ };
145
+ const onItemError = (item, msg) => {
146
+ item.status = "error";
147
+ item.message = msg;
148
+ errorMessage.value = msg;
149
+ uploading.value = false;
150
+ };
151
+ const onItemCanceled = (item) => {
152
+ item.status = "error";
153
+ uploading.value = false;
154
+ };
155
+ const onItemRemove = (item) => {
156
+ const idx = items.value.findIndex((i) => i.id === item.id);
157
+ if (idx >= 0) items.value.splice(idx, 1);
158
+ if (items.value.length === 0) uploading.value = false;
159
+ updateModelValue();
160
+ };
161
+ const updateModelValue = () => {
162
+ const assetIds = [];
163
+ const seen = /* @__PURE__ */ new Set();
164
+ for (const item of items.value) {
165
+ if (!item.assetIds?.length) continue;
166
+ for (const id of item.assetIds) {
167
+ if (seen.has(id)) continue;
168
+ seen.add(id);
169
+ assetIds.push(id);
170
+ }
171
+ }
172
+ model.value = assetIds;
173
+ };
174
+ const onItemUploadedWithAssets = (item, assetIds) => {
175
+ item.status = "uploaded";
176
+ item.assetIds = assetIds;
177
+ checkAllDone();
178
+ updateModelValue();
179
+ };
180
+ const reset = () => {
181
+ items.value = [];
182
+ uploading.value = false;
183
+ errorMessage.value = null;
184
+ model.value = [];
185
+ };
186
+ const isBusy = () => uploading.value || items.value.some(
187
+ (i) => i.status === "pending" || i.status === "uploading"
188
+ );
189
+ defineExpose({ reset, isBusy });
190
+ </script>
191
+
192
+ <template>
193
+ <div class="relative ml-uploader-root">
194
+ <MLUploaderPolicyPopover :policy="policy"/>
195
+ <div
196
+ class=" flex flex-col items-center justify-center w-full p-6 mx-auto bg-default border-2 border-dashed rounded-lg shadow-md transition-all duration-300"
197
+ :class="isDragging ? 'border-primary bg-[color-mix(in srgb, var(--ui-primary) 12%, var(--ui-bg))]' : 'border-[var(--ui-border)]'"
198
+ role="button"
199
+ tabindex="0"
200
+ :aria-label="props.ariaLabelText"
201
+ @dragover.prevent="isDragging = true"
202
+ @dragleave="isDragging = false"
203
+ @drop.prevent="onDrop"
204
+ @click="triggerFileSelect"
205
+ >
206
+
207
+ <h2 class="text-lg font-medium text-default mb-4">{{ props.titleText }}</h2>
208
+
209
+ <div
210
+ v-if="items.length === 0 && !isDragging"
211
+ class="flex flex-col items-center justify-center w-full h-40 cursor-pointer hover:bg-muted rounded-lg"
212
+ >
213
+ <div class="w-12 h-12 mb-2 text-default">{{ props.emptyStateIconText }}</div>
214
+ <p class="text-sm text-default">{{ props.hintText }}</p>
215
+ <p class="text-xs text-default">{{ props.clickText }}</p>
216
+ </div>
217
+
218
+ <div v-else
219
+ class="w-full flex flex-wrap justify-center items-center gap-2 hover:bg-muted rounded-lg p-2">
220
+ <MLUploaderItem
221
+ v-for="item in items"
222
+ :key="item.id"
223
+ :file="item.file"
224
+ :policy="policy"
225
+ :resource-type="props.resourceType"
226
+ :collection-name="props.collectionName"
227
+ :chunk-uploader="props.chunkUploader"
228
+ :upload-failed-text="props.uploadErrorText"
229
+ @uploaded="(assetIds) => onItemUploadedWithAssets(item, assetIds)"
230
+ @error="(msg) => onItemError(item, msg)"
231
+ @canceled="() => onItemCanceled(item)"
232
+ @remove="() => onItemRemove(item)"
233
+ />
234
+ </div>
235
+
236
+ <input
237
+ ref="fileInput"
238
+ type="file"
239
+ class="hidden"
240
+ :multiple="allowMultiple"
241
+ :accept="acceptTypes"
242
+ @change="onPick"
243
+ />
244
+ </div>
245
+ </div>
246
+
247
+ <div class="space-y-2 mt-3">
248
+ <div v-if="policyError" class="text-sm text-(--ui-danger)">{{ policyError }}</div>
249
+ <div v-if="errorMessage" class="text-sm text-(--ui-danger)">{{ errorMessage }}</div>
250
+ </div>
251
+ </template>
@@ -0,0 +1,44 @@
1
+ type __VLS_Props = {
2
+ resourceType: string;
3
+ collectionName?: string | null;
4
+ chunkUploader?: Record<string, any> | string;
5
+ titleText?: string;
6
+ hintText?: string;
7
+ clickText?: string;
8
+ ariaLabelText?: string;
9
+ emptyStateIconText?: string;
10
+ policyErrorText?: string;
11
+ policyLoadingText?: string;
12
+ uploadErrorText?: string;
13
+ singleFileErrorText?: string;
14
+ fileTypeErrorText?: string;
15
+ fileTooLargeText?: string;
16
+ fileTooSmallText?: string;
17
+ };
18
+ type __VLS_ModelProps = {
19
+ modelValue?: string[];
20
+ };
21
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
22
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
23
+ reset: () => void;
24
+ isBusy: () => boolean;
25
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
26
+ "update:modelValue": (value: string[]) => any;
27
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
28
+ "onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
29
+ }>, {
30
+ fileTypeErrorText: string;
31
+ fileTooLargeText: string;
32
+ fileTooSmallText: string;
33
+ titleText: string;
34
+ hintText: string;
35
+ clickText: string;
36
+ ariaLabelText: string;
37
+ emptyStateIconText: string;
38
+ policyErrorText: string;
39
+ policyLoadingText: string;
40
+ uploadErrorText: string;
41
+ singleFileErrorText: string;
42
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
43
+ declare const _default: typeof __VLS_export;
44
+ export default _default;
@@ -0,0 +1,4 @@
1
+ export declare const getFileExtension: (file: File) => string;
2
+ export declare const inferMimeFromExtension: (ext: string) => string[];
3
+ export declare const isFileMimeAllowed: (file: File, allowedMimeTypes: string[]) => boolean;
4
+ export declare const buildAcceptAttribute: (allowedMimeTypes: string[]) => string | undefined;
@@ -0,0 +1,71 @@
1
+ const MIME_TO_EXTENSIONS = {
2
+ "application/zip": ["zip"],
3
+ "application/x-zip-compressed": ["zip"],
4
+ "application/x-7z-compressed": ["7z"],
5
+ "application/x-rar-compressed": ["rar"],
6
+ "application/vnd.rar": ["rar"],
7
+ "application/x-tar": ["tar"],
8
+ "application/gzip": ["gz", "gzip"],
9
+ "application/pdf": ["pdf"],
10
+ "application/json": ["json"],
11
+ "application/msword": ["doc"],
12
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document": ["docx"],
13
+ "application/vnd.ms-excel": ["xls"],
14
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": ["xlsx"],
15
+ "application/vnd.ms-powerpoint": ["ppt"],
16
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation": ["pptx"],
17
+ "text/plain": ["txt"],
18
+ "text/csv": ["csv"],
19
+ "text/html": ["html", "htm"],
20
+ "text/markdown": ["md", "markdown"],
21
+ "image/jpeg": ["jpg", "jpeg"],
22
+ "image/png": ["png"],
23
+ "image/gif": ["gif"],
24
+ "image/webp": ["webp"],
25
+ "image/svg+xml": ["svg"],
26
+ "image/heic": ["heic"],
27
+ "image/heif": ["heif"],
28
+ "video/mp4": ["mp4", "m4v"],
29
+ "video/webm": ["webm"],
30
+ "video/quicktime": ["mov"],
31
+ "video/x-matroska": ["mkv"],
32
+ "audio/mpeg": ["mp3"],
33
+ "audio/wav": ["wav"],
34
+ "audio/x-wav": ["wav"],
35
+ "audio/ogg": ["ogg"],
36
+ "audio/mp4": ["m4a"],
37
+ "audio/aac": ["aac"]
38
+ };
39
+ export const getFileExtension = (file) => {
40
+ const name = file.name || "";
41
+ const dot = name.lastIndexOf(".");
42
+ if (dot < 0 || dot === name.length - 1) return "";
43
+ return name.slice(dot + 1).toLowerCase();
44
+ };
45
+ export const inferMimeFromExtension = (ext) => {
46
+ if (!ext) return [];
47
+ const matches = [];
48
+ for (const [mime, extensions] of Object.entries(MIME_TO_EXTENSIONS)) {
49
+ if (extensions.includes(ext)) matches.push(mime);
50
+ }
51
+ return matches;
52
+ };
53
+ export const isFileMimeAllowed = (file, allowedMimeTypes) => {
54
+ if (!allowedMimeTypes.length) return true;
55
+ const allowed = new Set(allowedMimeTypes);
56
+ if (file.type && allowed.has(file.type)) return true;
57
+ if (file.type) return false;
58
+ const inferred = inferMimeFromExtension(getFileExtension(file));
59
+ return inferred.some((m) => allowed.has(m));
60
+ };
61
+ export const buildAcceptAttribute = (allowedMimeTypes) => {
62
+ if (!allowedMimeTypes.length) return void 0;
63
+ const tokens = /* @__PURE__ */ new Set();
64
+ for (const mime of allowedMimeTypes) {
65
+ tokens.add(mime);
66
+ for (const ext of MIME_TO_EXTENSIONS[mime] || []) {
67
+ tokens.add(`.${ext}`);
68
+ }
69
+ }
70
+ return Array.from(tokens).join(",");
71
+ };
@@ -0,0 +1,17 @@
1
+ import type { CreateResourcePolicyRequest, ResourcePolicyListQuery, ResourcePolicyResponse, UpdateResourcePolicyRequest } from '../../types/resourcePolicy.js';
2
+ type HeadersInput = Record<string, string> | (() => Record<string, string>);
3
+ interface UseResourcePolicyOptions {
4
+ headers?: HeadersInput;
5
+ }
6
+ export declare const useMLResourcePolicy: (options?: UseResourcePolicyOptions) => {
7
+ createOrUpdate: (data: CreateResourcePolicyRequest) => Promise<ResourcePolicyResponse>;
8
+ list: (query?: ResourcePolicyListQuery) => Promise<ResourcePolicyResponse[]>;
9
+ get: (policyId: string) => Promise<ResourcePolicyResponse>;
10
+ findByTypeCollection: (resourceType: string, collectionName?: string | null) => Promise<ResourcePolicyResponse>;
11
+ refreshByTypeCollection: (resourceType: string, collectionName?: string | null) => Promise<ResourcePolicyResponse>;
12
+ update: (policyId: string, data: UpdateResourcePolicyRequest) => Promise<ResourcePolicyResponse>;
13
+ remove: (policyId: string, hard?: boolean) => Promise<undefined>;
14
+ invalidate: (policy: Pick<ResourcePolicyResponse, "id" | "resource_type" | "collection_name">) => void;
15
+ invalidateAll: () => void;
16
+ };
17
+ export {};
@@ -0,0 +1,150 @@
1
+ import { useRuntimeConfig } from "#app";
2
+ import { $fetch } from "ofetch";
3
+ const POLICY_CACHE_TTL_MS = 6e4;
4
+ const policyCache = /* @__PURE__ */ new Map();
5
+ const policyByTypeCache = /* @__PURE__ */ new Map();
6
+ const typeCollectionKey = (resourceType, collectionName) => `${resourceType}::${collectionName ?? ""}`;
7
+ const readCache = (map, key) => {
8
+ const entry = map.get(key);
9
+ if (!entry) return void 0;
10
+ if (entry.expiresAt <= Date.now()) {
11
+ map.delete(key);
12
+ return void 0;
13
+ }
14
+ return entry.value;
15
+ };
16
+ const writeCache = (map, key, value) => {
17
+ map.set(key, { value, expiresAt: Date.now() + POLICY_CACHE_TTL_MS });
18
+ };
19
+ const invalidatePolicy = (policy) => {
20
+ policyCache.delete(policy.id);
21
+ policyByTypeCache.delete(typeCollectionKey(policy.resource_type, policy.collection_name));
22
+ };
23
+ const invalidateAllPolicies = () => {
24
+ policyCache.clear();
25
+ policyByTypeCache.clear();
26
+ };
27
+ const camelToSnake = (key) => key.replace(/[A-Z]/g, (c) => `_${c.toLowerCase()}`);
28
+ const normalizeKeysToSnake = (value) => {
29
+ if (Array.isArray(value)) return value.map(normalizeKeysToSnake);
30
+ if (value && typeof value === "object" && value.constructor === Object) {
31
+ const out = {};
32
+ for (const [k, v] of Object.entries(value)) {
33
+ out[camelToSnake(k)] = normalizeKeysToSnake(v);
34
+ }
35
+ return out;
36
+ }
37
+ return value;
38
+ };
39
+ const normalizePolicyResponse = (policy) => {
40
+ if (!policy || typeof policy !== "object") return policy;
41
+ return {
42
+ ...policy,
43
+ file_constraints: normalizeKeysToSnake(policy.file_constraints),
44
+ permission_requirements: normalizeKeysToSnake(policy.permission_requirements),
45
+ processing_config: normalizeKeysToSnake(policy.processing_config)
46
+ };
47
+ };
48
+ const resolveHeaders = (headers) => {
49
+ if (!headers) return {};
50
+ return typeof headers === "function" ? headers() : headers;
51
+ };
52
+ export const useMLResourcePolicy = (options = {}) => {
53
+ const config = useRuntimeConfig();
54
+ const proxyPrefix = "/api/media-library/manage/resource-policies";
55
+ const publicHeaders = config.public?.mediaLibrary?.headers;
56
+ const headers = {
57
+ ...resolveHeaders(publicHeaders),
58
+ ...resolveHeaders(options.headers)
59
+ };
60
+ const request = (path, init = {}) => {
61
+ const initHeaders = init.headers instanceof Headers ? Object.fromEntries(init.headers.entries()) : init.headers || {};
62
+ return $fetch(path, {
63
+ headers: {
64
+ ...headers,
65
+ ...initHeaders
66
+ },
67
+ ...init
68
+ });
69
+ };
70
+ const fetchByTypeCollection = async (resourceType, collectionName) => {
71
+ const policies = await request(proxyPrefix, {
72
+ method: "GET",
73
+ query: { resource_type: resourceType, is_active: true }
74
+ });
75
+ const target = collectionName ?? null;
76
+ const match = policies.find((p) => (p.collection_name ?? null) === target);
77
+ if (!match) {
78
+ throw new Error(
79
+ `No active resource policy found for resource_type="${resourceType}" collection_name="${target ?? ""}"`
80
+ );
81
+ }
82
+ return normalizePolicyResponse(match);
83
+ };
84
+ return {
85
+ createOrUpdate: async (data) => {
86
+ const policy = normalizePolicyResponse(await request(proxyPrefix, {
87
+ method: "POST",
88
+ body: data
89
+ }));
90
+ invalidatePolicy(policy);
91
+ return policy;
92
+ },
93
+ list: async (query) => {
94
+ const policies = await request(proxyPrefix, {
95
+ method: "GET",
96
+ query
97
+ });
98
+ return policies.map(normalizePolicyResponse);
99
+ },
100
+ get: async (policyId) => {
101
+ const cached = readCache(policyCache, policyId);
102
+ if (cached) return cached;
103
+ const policy = normalizePolicyResponse(await request(`${proxyPrefix}/${policyId}`, {
104
+ method: "GET"
105
+ }));
106
+ writeCache(policyCache, policyId, policy);
107
+ return policy;
108
+ },
109
+ findByTypeCollection: async (resourceType, collectionName) => {
110
+ const key = typeCollectionKey(resourceType, collectionName);
111
+ const cached = readCache(policyByTypeCache, key);
112
+ if (cached) return cached;
113
+ const match = await fetchByTypeCollection(resourceType, collectionName);
114
+ writeCache(policyByTypeCache, key, match);
115
+ writeCache(policyCache, match.id, match);
116
+ return match;
117
+ },
118
+ refreshByTypeCollection: async (resourceType, collectionName) => {
119
+ const key = typeCollectionKey(resourceType, collectionName);
120
+ policyByTypeCache.delete(key);
121
+ const match = await fetchByTypeCollection(resourceType, collectionName);
122
+ writeCache(policyByTypeCache, key, match);
123
+ writeCache(policyCache, match.id, match);
124
+ return match;
125
+ },
126
+ update: async (policyId, data) => {
127
+ const policy = normalizePolicyResponse(await request(`${proxyPrefix}/${policyId}`, {
128
+ method: "PUT",
129
+ body: data
130
+ }));
131
+ invalidatePolicy(policy);
132
+ return policy;
133
+ },
134
+ remove: async (policyId, hard = false) => {
135
+ const cached = policyCache.get(policyId)?.value;
136
+ const result = await request(`${proxyPrefix}/${policyId}`, {
137
+ method: "DELETE",
138
+ query: { hard }
139
+ });
140
+ if (cached) {
141
+ invalidatePolicy(cached);
142
+ } else {
143
+ policyCache.delete(policyId);
144
+ }
145
+ return result;
146
+ },
147
+ invalidate: invalidatePolicy,
148
+ invalidateAll: invalidateAllPolicies
149
+ };
150
+ };
@@ -0,0 +1,13 @@
1
+ import { type Ref } from 'vue';
2
+ declare function formatDuration(ms: number): string;
3
+ /**
4
+ * Live-ticking elapsed time for running processing steps.
5
+ *
6
+ * While `isRunning` is true, `elapsed` updates every second.
7
+ * When `isRunning` becomes false, the timer stops and shows the final value.
8
+ */
9
+ export declare function useElapsedTimer(startedAt: Ref<string | null>, isRunning: Ref<boolean>): {
10
+ elapsed: Ref<string, string>;
11
+ formatDuration: typeof formatDuration;
12
+ };
13
+ export {};
@@ -0,0 +1,46 @@
1
+ import { ref, watch, onUnmounted } from "vue";
2
+ function formatDuration(ms) {
3
+ if (ms < 0) ms = 0;
4
+ const secs = Math.floor(ms / 1e3);
5
+ if (secs < 60) return `${secs}s`;
6
+ const mins = Math.floor(secs / 60);
7
+ return `${mins}m ${secs % 60}s`;
8
+ }
9
+ function parseUtc(iso) {
10
+ if (!iso.endsWith("Z") && !iso.includes("+") && !/\d{2}:\d{2}$/.test(iso.slice(-6))) {
11
+ return (/* @__PURE__ */ new Date(iso + "Z")).getTime();
12
+ }
13
+ return new Date(iso).getTime();
14
+ }
15
+ export function useElapsedTimer(startedAt, isRunning) {
16
+ const elapsed = ref("");
17
+ let timer = null;
18
+ function tick() {
19
+ if (!startedAt.value) {
20
+ elapsed.value = "";
21
+ return;
22
+ }
23
+ const start = parseUtc(startedAt.value);
24
+ elapsed.value = formatDuration(Date.now() - start);
25
+ }
26
+ function startTimer() {
27
+ stopTimer();
28
+ tick();
29
+ timer = setInterval(tick, 1e3);
30
+ }
31
+ function stopTimer() {
32
+ if (timer) {
33
+ clearInterval(timer);
34
+ timer = null;
35
+ }
36
+ }
37
+ watch(isRunning, (running) => {
38
+ if (running) startTimer();
39
+ else {
40
+ stopTimer();
41
+ tick();
42
+ }
43
+ }, { immediate: true });
44
+ onUnmounted(stopTimer);
45
+ return { elapsed, formatDuration };
46
+ }
@@ -0,0 +1,31 @@
1
+ import { type Ref } from 'vue';
2
+ import type { AssetDetail } from '../types/asset.js';
3
+ export type AssetKind = 'image' | 'video' | 'audio' | 'document' | 'web_app' | 'other' | 'archive' | 'font' | 'model_3d' | 'image_batch';
4
+ export type AssetStatus = 'pending' | 'uploaded' | 'processing' | 'awaiting_input' | 'ready' | 'failed' | 'archived' | 'deleted';
5
+ export interface AssetLibraryFilters {
6
+ kind?: AssetKind | null;
7
+ status?: AssetStatus | null;
8
+ query?: string | null;
9
+ resourcePolicyId?: string | null;
10
+ /** When provided, resolves these specific asset IDs instead of listing. Pagination is ignored. */
11
+ ids?: string[] | null;
12
+ }
13
+ export interface UseMLAssetLibraryOptions {
14
+ filters?: AssetLibraryFilters;
15
+ pageSize?: number;
16
+ /** Set to false to skip the initial fetch. Default: true. */
17
+ immediate?: boolean;
18
+ }
19
+ export interface UseMLAssetLibraryReturn {
20
+ items: Ref<AssetDetail[]>;
21
+ total: Ref<number>;
22
+ page: Ref<number>;
23
+ pageSize: Ref<number>;
24
+ loading: Ref<boolean>;
25
+ error: Ref<string | null>;
26
+ filters: Ref<AssetLibraryFilters>;
27
+ fetch: () => Promise<void>;
28
+ setPage: (page: number) => void;
29
+ setFilters: (filters: Partial<AssetLibraryFilters>) => void;
30
+ }
31
+ export declare function useMLAssetLibrary(options?: UseMLAssetLibraryOptions): UseMLAssetLibraryReturn;
@@ -0,0 +1,53 @@
1
+ import { ref, watch } from "vue";
2
+ import { $fetch } from "ofetch";
3
+ export function useMLAssetLibrary(options = {}) {
4
+ const items = ref([]);
5
+ const total = ref(0);
6
+ const page = ref(1);
7
+ const pageSize = ref(options.pageSize ?? 20);
8
+ const loading = ref(false);
9
+ const error = ref(null);
10
+ const filters = ref(options.filters ?? {});
11
+ async function fetch() {
12
+ loading.value = true;
13
+ error.value = null;
14
+ try {
15
+ if (filters.value.ids?.length) {
16
+ const result = await $fetch("/api/media-library/manage/assets/resolve", {
17
+ method: "POST",
18
+ body: { assetIds: filters.value.ids }
19
+ });
20
+ items.value = (result.items ?? []).flatMap((i) => i.status === "ok" && i.asset ? [i.asset] : []);
21
+ total.value = items.value.length;
22
+ } else {
23
+ const query = {
24
+ page: String(page.value),
25
+ pageSize: String(pageSize.value)
26
+ };
27
+ if (filters.value.kind) query.kind = filters.value.kind;
28
+ if (filters.value.status) query.status = filters.value.status;
29
+ if (filters.value.query) query.query = filters.value.query;
30
+ if (filters.value.resourcePolicyId) query.resourcePolicyId = filters.value.resourcePolicyId;
31
+ const result = await $fetch(
32
+ "/api/media-library/manage/assets",
33
+ { query }
34
+ );
35
+ items.value = result.items ?? [];
36
+ total.value = result.total ?? 0;
37
+ }
38
+ } catch (err) {
39
+ error.value = err?.message ?? "Failed to load assets";
40
+ } finally {
41
+ loading.value = false;
42
+ }
43
+ }
44
+ function setPage(p) {
45
+ page.value = p;
46
+ }
47
+ function setFilters(f) {
48
+ filters.value = { ...filters.value, ...f };
49
+ page.value = 1;
50
+ }
51
+ watch([filters, page], () => fetch(), { deep: true, immediate: options.immediate !== false });
52
+ return { items, total, page, pageSize, loading, error, filters, fetch, setPage, setFilters };
53
+ }