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,25 @@
|
|
|
1
|
+
export type ProgressVeilPhase = 'uploading' | 'processing' | 'failed';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
phase: ProgressVeilPhase;
|
|
4
|
+
/** 0–100. Null or omitted shows an indeterminate bar (e.g. a step with no progress events). */
|
|
5
|
+
progress?: number | null;
|
|
6
|
+
/** Replaces the phase label, e.g. "Transcoding". */
|
|
7
|
+
label?: string;
|
|
8
|
+
/** Second line, usually the file name. */
|
|
9
|
+
detail?: string;
|
|
10
|
+
/** Bar only, no text. For thumbnails smaller than ~80px. */
|
|
11
|
+
compact?: boolean;
|
|
12
|
+
/** Passing a listener shows the matching button. */
|
|
13
|
+
onCancel?: () => void;
|
|
14
|
+
onRetry?: () => void;
|
|
15
|
+
};
|
|
16
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
|
+
progress: number | null;
|
|
18
|
+
label: string;
|
|
19
|
+
compact: boolean;
|
|
20
|
+
onCancel: () => void;
|
|
21
|
+
onRetry: () => void;
|
|
22
|
+
detail: string;
|
|
23
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
24
|
+
declare const _default: typeof __VLS_export;
|
|
25
|
+
export default _default;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BadgeProps } from '@nuxt/ui';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
status: string | null | undefined;
|
|
4
|
+
/** 0–100. Shown after the label while the asset is processing. */
|
|
5
|
+
progress?: number | null;
|
|
6
|
+
icon?: boolean;
|
|
7
|
+
size?: BadgeProps['size'];
|
|
8
|
+
variant?: BadgeProps['variant'];
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
+
size: "md" | "xs" | "sm" | "lg" | "xl";
|
|
12
|
+
progress: number | null;
|
|
13
|
+
variant: "solid" | "outline" | "soft" | "subtle";
|
|
14
|
+
icon: boolean;
|
|
15
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
declare const _default: typeof __VLS_export;
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { assetStatusMeta } from "../../utils/assetDisplay";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
status: { type: null, required: true },
|
|
6
|
+
progress: { type: [Number, null], required: false, default: null },
|
|
7
|
+
icon: { type: Boolean, required: false, default: false },
|
|
8
|
+
size: { type: null, required: false, default: "sm" },
|
|
9
|
+
variant: { type: null, required: false, default: "subtle" }
|
|
10
|
+
});
|
|
11
|
+
const meta = computed(() => assetStatusMeta(props.status));
|
|
12
|
+
const isProcessing = computed(() => (props.status ?? "").toLowerCase() === "processing");
|
|
13
|
+
const label = computed(() => {
|
|
14
|
+
if (isProcessing.value && props.progress != null) {
|
|
15
|
+
return `${meta.value.label} ${Math.round(Math.min(100, Math.max(0, props.progress)))}%`;
|
|
16
|
+
}
|
|
17
|
+
return meta.value.label;
|
|
18
|
+
});
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<UBadge
|
|
23
|
+
:color="meta.color"
|
|
24
|
+
:variant="variant"
|
|
25
|
+
:size="size"
|
|
26
|
+
:label="label"
|
|
27
|
+
:ui="{ leadingIcon: isProcessing ? 'animate-spin motion-reduce:animate-none' : void 0 }"
|
|
28
|
+
:icon="icon ? meta.icon : void 0"
|
|
29
|
+
class="tabular-nums"
|
|
30
|
+
/>
|
|
31
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BadgeProps } from '@nuxt/ui';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
status: string | null | undefined;
|
|
4
|
+
/** 0–100. Shown after the label while the asset is processing. */
|
|
5
|
+
progress?: number | null;
|
|
6
|
+
icon?: boolean;
|
|
7
|
+
size?: BadgeProps['size'];
|
|
8
|
+
variant?: BadgeProps['variant'];
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
11
|
+
size: "md" | "xs" | "sm" | "lg" | "xl";
|
|
12
|
+
progress: number | null;
|
|
13
|
+
variant: "solid" | "outline" | "soft" | "subtle";
|
|
14
|
+
icon: boolean;
|
|
15
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
declare const _default: typeof __VLS_export;
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'fill';
|
|
3
|
+
type Aspect = 'square' | 'landscape' | 'portrait' | 'video';
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
/** Resolves src and kind for you. Explicit src / kind win over it. */
|
|
6
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
7
|
+
src?: string | null;
|
|
8
|
+
kind?: string | null;
|
|
9
|
+
alt?: string;
|
|
10
|
+
/** Fixed sizes for lists and fields; `fill` takes the parent's width (tiles, cards). */
|
|
11
|
+
size?: Size;
|
|
12
|
+
aspect?: Aspect;
|
|
13
|
+
/** `contain` letterboxes, for documents and logos that must not be cropped. */
|
|
14
|
+
fit?: 'cover' | 'contain';
|
|
15
|
+
};
|
|
16
|
+
declare var __VLS_11: {};
|
|
17
|
+
type __VLS_Slots = {} & {
|
|
18
|
+
default?: (props: typeof __VLS_11) => any;
|
|
19
|
+
};
|
|
20
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
21
|
+
size: Size;
|
|
22
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
23
|
+
src: string | null;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
alt: string;
|
|
26
|
+
aspect: Aspect;
|
|
27
|
+
fit: "cover" | "contain";
|
|
28
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
30
|
+
declare const _default: typeof __VLS_export;
|
|
31
|
+
export default _default;
|
|
32
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
33
|
+
new (): {
|
|
34
|
+
$slots: S;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, ref, watch } from "vue";
|
|
3
|
+
import { assetKindMeta } from "../../utils/assetDisplay";
|
|
4
|
+
import { assetThumbnailUrl } from "../../utils/assetThumbnail";
|
|
5
|
+
const props = defineProps({
|
|
6
|
+
asset: { type: [Object, null], required: false, default: null },
|
|
7
|
+
src: { type: [String, null], required: false, default: void 0 },
|
|
8
|
+
kind: { type: [String, null], required: false, default: void 0 },
|
|
9
|
+
alt: { type: String, required: false, default: "" },
|
|
10
|
+
size: { type: String, required: false, default: "md" },
|
|
11
|
+
aspect: { type: String, required: false, default: "square" },
|
|
12
|
+
fit: { type: String, required: false, default: "cover" }
|
|
13
|
+
});
|
|
14
|
+
const resolvedSrc = computed(() => props.src !== void 0 ? props.src : assetThumbnailUrl(props.asset));
|
|
15
|
+
const resolvedKind = computed(() => props.kind !== void 0 ? props.kind : props.asset?.kind);
|
|
16
|
+
const meta = computed(() => assetKindMeta(resolvedKind.value));
|
|
17
|
+
const state = ref("idle");
|
|
18
|
+
watch(resolvedSrc, (src) => {
|
|
19
|
+
state.value = src ? "loading" : "idle";
|
|
20
|
+
}, { immediate: true });
|
|
21
|
+
const sizeClass = {
|
|
22
|
+
xs: "w-8",
|
|
23
|
+
sm: "w-10",
|
|
24
|
+
md: "w-20",
|
|
25
|
+
lg: "w-32",
|
|
26
|
+
xl: "w-48",
|
|
27
|
+
fill: "w-full"
|
|
28
|
+
};
|
|
29
|
+
const iconClass = {
|
|
30
|
+
xs: "size-4",
|
|
31
|
+
sm: "size-5",
|
|
32
|
+
md: "size-7",
|
|
33
|
+
lg: "size-10",
|
|
34
|
+
xl: "size-12",
|
|
35
|
+
fill: "size-[28%] max-w-16 max-h-16"
|
|
36
|
+
};
|
|
37
|
+
const aspectClass = {
|
|
38
|
+
square: "aspect-square",
|
|
39
|
+
landscape: "aspect-[4/3]",
|
|
40
|
+
portrait: "aspect-[3/4]",
|
|
41
|
+
video: "aspect-video"
|
|
42
|
+
};
|
|
43
|
+
const radiusClass = computed(() => props.size === "xs" || props.size === "sm" ? "rounded-md" : "rounded-lg");
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div
|
|
48
|
+
class="relative max-w-full shrink-0 overflow-hidden bg-elevated ring ring-default"
|
|
49
|
+
:class="[sizeClass[size], aspectClass[aspect], radiusClass]"
|
|
50
|
+
role="img"
|
|
51
|
+
:aria-label="alt || meta.label"
|
|
52
|
+
:data-state="state"
|
|
53
|
+
>
|
|
54
|
+
<img
|
|
55
|
+
v-if="resolvedSrc && state !== 'error'"
|
|
56
|
+
:src="resolvedSrc"
|
|
57
|
+
alt=""
|
|
58
|
+
loading="lazy"
|
|
59
|
+
decoding="async"
|
|
60
|
+
draggable="false"
|
|
61
|
+
class="size-full transition-opacity duration-200 motion-reduce:transition-none"
|
|
62
|
+
:class="[fit === 'cover' ? 'object-cover' : 'object-contain', state === 'loaded' ? 'opacity-100' : 'opacity-0']"
|
|
63
|
+
@load="state = 'loaded'"
|
|
64
|
+
@error="state = 'error'"
|
|
65
|
+
>
|
|
66
|
+
|
|
67
|
+
<USkeleton
|
|
68
|
+
v-if="state === 'loading'"
|
|
69
|
+
class="absolute inset-0 rounded-none"
|
|
70
|
+
/>
|
|
71
|
+
|
|
72
|
+
<div
|
|
73
|
+
v-if="state === 'idle' || state === 'error'"
|
|
74
|
+
class="absolute inset-0 grid place-items-center text-dimmed"
|
|
75
|
+
>
|
|
76
|
+
<UIcon
|
|
77
|
+
:name="meta.icon"
|
|
78
|
+
:class="iconClass[size]"
|
|
79
|
+
/>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<slot />
|
|
83
|
+
</div>
|
|
84
|
+
</template>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'fill';
|
|
3
|
+
type Aspect = 'square' | 'landscape' | 'portrait' | 'video';
|
|
4
|
+
type __VLS_Props = {
|
|
5
|
+
/** Resolves src and kind for you. Explicit src / kind win over it. */
|
|
6
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
7
|
+
src?: string | null;
|
|
8
|
+
kind?: string | null;
|
|
9
|
+
alt?: string;
|
|
10
|
+
/** Fixed sizes for lists and fields; `fill` takes the parent's width (tiles, cards). */
|
|
11
|
+
size?: Size;
|
|
12
|
+
aspect?: Aspect;
|
|
13
|
+
/** `contain` letterboxes, for documents and logos that must not be cropped. */
|
|
14
|
+
fit?: 'cover' | 'contain';
|
|
15
|
+
};
|
|
16
|
+
declare var __VLS_11: {};
|
|
17
|
+
type __VLS_Slots = {} & {
|
|
18
|
+
default?: (props: typeof __VLS_11) => any;
|
|
19
|
+
};
|
|
20
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
21
|
+
size: Size;
|
|
22
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
23
|
+
src: string | null;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
alt: string;
|
|
26
|
+
aspect: Aspect;
|
|
27
|
+
fit: "cover" | "contain";
|
|
28
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
29
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
30
|
+
declare const _default: typeof __VLS_export;
|
|
31
|
+
export default _default;
|
|
32
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
33
|
+
new (): {
|
|
34
|
+
$slots: S;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface MediaUsage {
|
|
2
|
+
/** Record type as the host names it: "Product", "Course", "Page". */
|
|
3
|
+
type: string;
|
|
4
|
+
/** Which record and field: "Oak lounge chair · media 1". */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Host app, when files are shared across apps. */
|
|
7
|
+
app?: string;
|
|
8
|
+
/** Link to the record in the host app. */
|
|
9
|
+
to?: string;
|
|
10
|
+
}
|
|
11
|
+
type __VLS_Props = {
|
|
12
|
+
usages: MediaUsage[];
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
/** Shown above the list. Defaults to "Used in N places". */
|
|
15
|
+
title?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
|
+
select: (usage: MediaUsage) => any;
|
|
19
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
20
|
+
onSelect?: ((usage: MediaUsage) => any) | undefined;
|
|
21
|
+
}>, {
|
|
22
|
+
loading: boolean;
|
|
23
|
+
title: string;
|
|
24
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
usages: { type: Array, required: true },
|
|
5
|
+
loading: { type: Boolean, required: false, default: false },
|
|
6
|
+
title: { type: String, required: false, default: void 0 }
|
|
7
|
+
});
|
|
8
|
+
const emit = defineEmits(["select"]);
|
|
9
|
+
const heading = computed(() => props.title ?? (props.usages.length === 0 ? "Not used" : `Used in ${props.usages.length} ${props.usages.length === 1 ? "place" : "places"}`));
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<template>
|
|
13
|
+
<section class="flex flex-col gap-1.5">
|
|
14
|
+
<h4 class="text-[11px] font-semibold uppercase tracking-wider text-dimmed">
|
|
15
|
+
{{ loading ? "Used in" : heading }}
|
|
16
|
+
</h4>
|
|
17
|
+
|
|
18
|
+
<div
|
|
19
|
+
v-if="loading"
|
|
20
|
+
class="flex flex-col gap-1.5"
|
|
21
|
+
>
|
|
22
|
+
<USkeleton
|
|
23
|
+
v-for="i in 2"
|
|
24
|
+
:key="i"
|
|
25
|
+
class="h-5 w-full"
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<p
|
|
30
|
+
v-else-if="!usages.length"
|
|
31
|
+
class="text-xs text-muted"
|
|
32
|
+
>
|
|
33
|
+
No records point to this file. It can be deleted safely.
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<ul
|
|
37
|
+
v-else
|
|
38
|
+
class="flex flex-col gap-0.5"
|
|
39
|
+
>
|
|
40
|
+
<li
|
|
41
|
+
v-for="(u, i) in usages"
|
|
42
|
+
:key="`${u.type}:${u.label}:${i}`"
|
|
43
|
+
>
|
|
44
|
+
<component
|
|
45
|
+
:is="u.to ? 'a' : 'button'"
|
|
46
|
+
:href="u.to"
|
|
47
|
+
:type="u.to ? void 0 : 'button'"
|
|
48
|
+
class="-mx-1.5 flex w-[calc(100%+0.75rem)] min-w-0 items-center gap-2 rounded-md px-1.5 py-1 text-left text-xs hover:bg-elevated"
|
|
49
|
+
@click="emit('select', u)"
|
|
50
|
+
>
|
|
51
|
+
<UBadge
|
|
52
|
+
color="neutral"
|
|
53
|
+
variant="subtle"
|
|
54
|
+
size="sm"
|
|
55
|
+
:label="u.type"
|
|
56
|
+
/>
|
|
57
|
+
<span class="min-w-0 flex-1 truncate">{{ u.label }}</span>
|
|
58
|
+
<span
|
|
59
|
+
v-if="u.app"
|
|
60
|
+
class="shrink-0 text-muted"
|
|
61
|
+
>{{ u.app }}</span>
|
|
62
|
+
</component>
|
|
63
|
+
</li>
|
|
64
|
+
</ul>
|
|
65
|
+
</section>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface MediaUsage {
|
|
2
|
+
/** Record type as the host names it: "Product", "Course", "Page". */
|
|
3
|
+
type: string;
|
|
4
|
+
/** Which record and field: "Oak lounge chair · media 1". */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Host app, when files are shared across apps. */
|
|
7
|
+
app?: string;
|
|
8
|
+
/** Link to the record in the host app. */
|
|
9
|
+
to?: string;
|
|
10
|
+
}
|
|
11
|
+
type __VLS_Props = {
|
|
12
|
+
usages: MediaUsage[];
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
/** Shown above the list. Defaults to "Used in N places". */
|
|
15
|
+
title?: string;
|
|
16
|
+
};
|
|
17
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
18
|
+
select: (usage: MediaUsage) => any;
|
|
19
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
20
|
+
onSelect?: ((usage: MediaUsage) => any) | undefined;
|
|
21
|
+
}>, {
|
|
22
|
+
loading: boolean;
|
|
23
|
+
title: string;
|
|
24
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
25
|
+
declare const _default: typeof __VLS_export;
|
|
26
|
+
export default _default;
|
|
@@ -16,8 +16,8 @@ type __VLS_Slots = {} & {
|
|
|
16
16
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
17
|
description: string;
|
|
18
18
|
icon: string;
|
|
19
|
-
titleClass: string;
|
|
20
19
|
iconClass: string;
|
|
20
|
+
titleClass: string;
|
|
21
21
|
spinning: boolean;
|
|
22
22
|
showBody: boolean;
|
|
23
23
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -16,8 +16,8 @@ type __VLS_Slots = {} & {
|
|
|
16
16
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
|
|
17
17
|
description: string;
|
|
18
18
|
icon: string;
|
|
19
|
-
titleClass: string;
|
|
20
19
|
iconClass: string;
|
|
20
|
+
titleClass: string;
|
|
21
21
|
spinning: boolean;
|
|
22
22
|
showBody: boolean;
|
|
23
23
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
@@ -12,14 +12,14 @@ type __VLS_Props = {
|
|
|
12
12
|
uploadFailedText?: string;
|
|
13
13
|
};
|
|
14
14
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
15
|
-
remove: () => any;
|
|
16
15
|
error: (message: string) => any;
|
|
16
|
+
remove: () => any;
|
|
17
17
|
progress: (percent: number) => any;
|
|
18
18
|
uploaded: (assetIds: string[]) => any;
|
|
19
19
|
canceled: () => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
|
-
onRemove?: (() => any) | undefined;
|
|
22
21
|
onError?: ((message: string) => any) | undefined;
|
|
22
|
+
onRemove?: (() => any) | undefined;
|
|
23
23
|
onProgress?: ((percent: number) => any) | undefined;
|
|
24
24
|
onUploaded?: ((assetIds: string[]) => any) | undefined;
|
|
25
25
|
onCanceled?: (() => any) | undefined;
|
|
@@ -12,14 +12,14 @@ type __VLS_Props = {
|
|
|
12
12
|
uploadFailedText?: string;
|
|
13
13
|
};
|
|
14
14
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
15
|
-
remove: () => any;
|
|
16
15
|
error: (message: string) => any;
|
|
16
|
+
remove: () => any;
|
|
17
17
|
progress: (percent: number) => any;
|
|
18
18
|
uploaded: (assetIds: string[]) => any;
|
|
19
19
|
canceled: () => any;
|
|
20
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
21
|
-
onRemove?: (() => any) | undefined;
|
|
22
21
|
onError?: ((message: string) => any) | undefined;
|
|
22
|
+
onRemove?: (() => any) | undefined;
|
|
23
23
|
onProgress?: ((percent: number) => any) | undefined;
|
|
24
24
|
onUploaded?: ((assetIds: string[]) => any) | undefined;
|
|
25
25
|
onCanceled?: (() => any) | undefined;
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ref } from "vue";
|
|
2
2
|
import { $fetch } from "ofetch";
|
|
3
|
+
import { mediaGrantHeaders } from "./useMLMediaGrants.js";
|
|
4
|
+
import { onMediaGrantsAdded } from "../utils/grants/listeners.js";
|
|
3
5
|
const assets = ref({});
|
|
4
6
|
const pending = ref(/* @__PURE__ */ new Set());
|
|
5
7
|
const broken = ref(/* @__PURE__ */ new Set());
|
|
6
8
|
const forbidden = ref(/* @__PURE__ */ new Set());
|
|
7
9
|
const loading = ref(false);
|
|
10
|
+
const lockedByGrant = /* @__PURE__ */ new Set();
|
|
8
11
|
function computeSources(asset) {
|
|
9
12
|
const sources = [];
|
|
10
13
|
for (const rendition of asset.renditions ?? []) {
|
|
@@ -62,9 +65,13 @@ async function fetchAssets(ids) {
|
|
|
62
65
|
try {
|
|
63
66
|
const result = await $fetch("/api/media-library/delivery/assets/resolve", {
|
|
64
67
|
method: "POST",
|
|
65
|
-
body: { assetIds: targetIds }
|
|
68
|
+
body: { assetIds: targetIds },
|
|
69
|
+
headers: mediaGrantHeaders()
|
|
66
70
|
});
|
|
67
71
|
for (const item of result.items ?? []) {
|
|
72
|
+
if (item.status === "forbidden") {
|
|
73
|
+
lockedByGrant.add(item.assetId);
|
|
74
|
+
}
|
|
68
75
|
if (item.status !== "ok" || !item.asset) {
|
|
69
76
|
broken.value.add(item.assetId);
|
|
70
77
|
continue;
|
|
@@ -135,6 +142,15 @@ async function flushPending() {
|
|
|
135
142
|
pending.value.clear();
|
|
136
143
|
await fetchAssets(ids);
|
|
137
144
|
}
|
|
145
|
+
onMediaGrantsAdded(() => {
|
|
146
|
+
if (!lockedByGrant.size) return;
|
|
147
|
+
for (const id of lockedByGrant) {
|
|
148
|
+
broken.value.delete(id);
|
|
149
|
+
pending.value.add(id);
|
|
150
|
+
}
|
|
151
|
+
lockedByGrant.clear();
|
|
152
|
+
if (import.meta.client) scheduleFetch();
|
|
153
|
+
});
|
|
138
154
|
export const useMLAssetPreview = () => {
|
|
139
155
|
return {
|
|
140
156
|
assets,
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MediaUploadOptions } from './useMLMediaUpload.js';
|
|
2
|
+
export interface FilePickerOpenOptions {
|
|
3
|
+
/** Kinds the caller takes. Others are listed but dimmed. */
|
|
4
|
+
accept?: readonly string[] | string;
|
|
5
|
+
/** Most files that can be chosen. 1 makes it a single choice. */
|
|
6
|
+
max?: number;
|
|
7
|
+
/** IDs already attached. Listed first and checked; the result keeps their order. */
|
|
8
|
+
selected?: readonly (string | null | undefined)[];
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Bytes, for uploads from inside the picker. */
|
|
11
|
+
maxSize?: number | null;
|
|
12
|
+
upload?: MediaUploadOptions;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Opens the library dialog. `open()` resolves with the chosen asset IDs in
|
|
16
|
+
* order, or null when the user cancels. Needs <UApp> (it provides overlays).
|
|
17
|
+
*/
|
|
18
|
+
export declare function useMLFilePicker(): {
|
|
19
|
+
open: (options?: FilePickerOpenOptions) => Promise<string[] | null>;
|
|
20
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineAsyncComponent } from "vue";
|
|
2
|
+
import { useOverlay } from "#imports";
|
|
3
|
+
const LazyFilePicker = defineAsyncComponent(() => import("../components/MLMedia/FilePicker.vue"));
|
|
4
|
+
export function useMLFilePicker() {
|
|
5
|
+
const overlay = useOverlay();
|
|
6
|
+
async function open(options = {}) {
|
|
7
|
+
const { selected, ...rest } = options;
|
|
8
|
+
const picker = overlay.create(LazyFilePicker, { destroyOnClose: true });
|
|
9
|
+
const instance = picker.open({
|
|
10
|
+
...rest,
|
|
11
|
+
selected: (selected ?? []).filter((id) => !!id)
|
|
12
|
+
});
|
|
13
|
+
const result = await instance.result;
|
|
14
|
+
return Array.isArray(result) ? result : null;
|
|
15
|
+
}
|
|
16
|
+
return { open };
|
|
17
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import type { AssetDetail } from '../types/asset.js';
|
|
3
|
+
export type MediaAssetLookup = 'ok' | 'not_found' | 'forbidden' | 'error';
|
|
4
|
+
/**
|
|
5
|
+
* Details for a list of asset IDs, kept in step with the list. Only IDs not
|
|
6
|
+
* already loaded are requested. Runs in the browser only: the management
|
|
7
|
+
* proxy needs the viewer's session, and rendering the loading state on both
|
|
8
|
+
* sides keeps hydration stable.
|
|
9
|
+
*/
|
|
10
|
+
export declare function useMLMediaAssets(ids: MaybeRefOrGetter<readonly (string | null | undefined)[]>): {
|
|
11
|
+
assets: import("vue").Ref<Record<string, AssetDetail>, Record<string, AssetDetail>>;
|
|
12
|
+
lookups: import("vue").Ref<Record<string, MediaAssetLookup>, Record<string, MediaAssetLookup>>;
|
|
13
|
+
error: import("vue").Ref<string | null, string | null>;
|
|
14
|
+
/** True until every wanted ID has an answer (also during SSR and hydration). */
|
|
15
|
+
loading: import("vue").ComputedRef<boolean>;
|
|
16
|
+
get: (id: string | null | undefined) => AssetDetail | null;
|
|
17
|
+
lookup: (id: string | null | undefined) => MediaAssetLookup | "loading" | null;
|
|
18
|
+
refresh: (only?: string[]) => Promise<void>;
|
|
19
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { computed, onMounted, ref, toValue, watch } from "vue";
|
|
2
|
+
import { $fetch } from "ofetch";
|
|
3
|
+
export function useMLMediaAssets(ids) {
|
|
4
|
+
const assets = ref({});
|
|
5
|
+
const lookups = ref({});
|
|
6
|
+
const pending = ref(/* @__PURE__ */ new Set());
|
|
7
|
+
const error = ref(null);
|
|
8
|
+
const mounted = ref(false);
|
|
9
|
+
const wanted = computed(() => [...new Set(toValue(ids).filter((id) => !!id))]);
|
|
10
|
+
async function load(list) {
|
|
11
|
+
if (!list.length) return;
|
|
12
|
+
pending.value = /* @__PURE__ */ new Set([...pending.value, ...list]);
|
|
13
|
+
error.value = null;
|
|
14
|
+
try {
|
|
15
|
+
const res = await $fetch("/api/media-library/manage/assets/resolve", {
|
|
16
|
+
method: "POST",
|
|
17
|
+
body: { assetIds: list }
|
|
18
|
+
});
|
|
19
|
+
const nextAssets = { ...assets.value };
|
|
20
|
+
const nextLookups = { ...lookups.value };
|
|
21
|
+
for (const item of res.items ?? []) {
|
|
22
|
+
nextLookups[item.assetId] = item.status;
|
|
23
|
+
if (item.status === "ok" && item.asset) nextAssets[item.assetId] = item.asset;
|
|
24
|
+
}
|
|
25
|
+
assets.value = nextAssets;
|
|
26
|
+
lookups.value = nextLookups;
|
|
27
|
+
} catch (err) {
|
|
28
|
+
error.value = err?.data?.message ?? err?.message ?? "Could not load files";
|
|
29
|
+
lookups.value = { ...lookups.value, ...Object.fromEntries(list.map((id) => [id, "error"])) };
|
|
30
|
+
} finally {
|
|
31
|
+
const next = new Set(pending.value);
|
|
32
|
+
list.forEach((id) => next.delete(id));
|
|
33
|
+
pending.value = next;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function refresh(only) {
|
|
37
|
+
return load(only ?? wanted.value);
|
|
38
|
+
}
|
|
39
|
+
onMounted(() => {
|
|
40
|
+
mounted.value = true;
|
|
41
|
+
});
|
|
42
|
+
watch([wanted, mounted], ([list, isMounted]) => {
|
|
43
|
+
if (!isMounted) return;
|
|
44
|
+
void load(list.filter((id) => !(id in lookups.value) && !pending.value.has(id)));
|
|
45
|
+
}, { immediate: true });
|
|
46
|
+
return {
|
|
47
|
+
assets,
|
|
48
|
+
lookups,
|
|
49
|
+
error,
|
|
50
|
+
/** True until every wanted ID has an answer (also during SSR and hydration). */
|
|
51
|
+
loading: computed(() => !mounted.value || wanted.value.some((id) => !(id in lookups.value))),
|
|
52
|
+
get: (id) => id ? assets.value[id] ?? null : null,
|
|
53
|
+
lookup: (id) => !id ? null : lookups.value[id] ?? "loading",
|
|
54
|
+
refresh
|
|
55
|
+
};
|
|
56
|
+
}
|