nuxt-media-library 0.0.48 → 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/dist/module.json +1 -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/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/useMLMediaUpload.d.ts +134 -0
- package/dist/runtime/composables/useMLMediaUpload.js +153 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.js +11 -0
- 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/package.json +1 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { assetDurationSeconds, assetName, formatDuration } from "../../utils/assetFormat";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
asset: { type: [Object, null], required: false, default: null },
|
|
6
|
+
src: { type: [String, null], required: false, default: void 0 },
|
|
7
|
+
kind: { type: [String, null], required: false, default: void 0 },
|
|
8
|
+
name: { type: String, required: false, default: void 0 },
|
|
9
|
+
duration: { type: [Number, null], required: false, default: void 0 },
|
|
10
|
+
missingAlt: { type: Boolean, required: false, default: false },
|
|
11
|
+
selectable: { type: Boolean, required: false, default: false },
|
|
12
|
+
selected: { type: Boolean, required: false, default: false },
|
|
13
|
+
selecting: { type: Boolean, required: false, default: false },
|
|
14
|
+
phase: { type: [String, null], required: false, default: null },
|
|
15
|
+
progress: { type: [Number, null], required: false, default: null },
|
|
16
|
+
phaseDetail: { type: String, required: false, default: void 0 },
|
|
17
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
18
|
+
aspect: { type: String, required: false, default: "square" },
|
|
19
|
+
onOpen: { type: Function, required: false, default: void 0 }
|
|
20
|
+
});
|
|
21
|
+
const emit = defineEmits(["update:selected", "cancel", "retry"]);
|
|
22
|
+
defineSlots();
|
|
23
|
+
const resolvedKind = computed(() => (props.kind !== void 0 ? props.kind : props.asset?.kind) ?? "");
|
|
24
|
+
const resolvedName = computed(() => props.name ?? assetName(props.asset));
|
|
25
|
+
const isImage = computed(() => resolvedKind.value.toLowerCase() === "image");
|
|
26
|
+
const durationLabel = computed(() => formatDuration(props.duration !== void 0 ? props.duration : assetDurationSeconds(props.asset)));
|
|
27
|
+
function onKey(e) {
|
|
28
|
+
if (props.disabled) return;
|
|
29
|
+
if (e.key === "Enter" && props.onOpen) {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
props.onOpen();
|
|
32
|
+
} else if (e.key === " " && props.selectable) {
|
|
33
|
+
e.preventDefault();
|
|
34
|
+
emit("update:selected", !props.selected);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<template>
|
|
40
|
+
<div
|
|
41
|
+
class="group relative rounded-lg outline-offset-2 focus-visible:outline-2 focus-visible:outline-primary"
|
|
42
|
+
:class="[
|
|
43
|
+
selected ? 'ring-2 ring-primary' : '',
|
|
44
|
+
disabled ? 'pointer-events-none opacity-50' : selectable ? 'cursor-pointer' : ''
|
|
45
|
+
]"
|
|
46
|
+
role="button"
|
|
47
|
+
:tabindex="disabled ? -1 : 0"
|
|
48
|
+
:aria-label="resolvedName"
|
|
49
|
+
:aria-pressed="selectable ? selected : void 0"
|
|
50
|
+
:aria-disabled="disabled || void 0"
|
|
51
|
+
@click="!disabled && selectable && emit('update:selected', !selected)"
|
|
52
|
+
@keydown="onKey"
|
|
53
|
+
>
|
|
54
|
+
<MLMediaThumbnail
|
|
55
|
+
:asset="asset"
|
|
56
|
+
:src="src"
|
|
57
|
+
:kind="resolvedKind"
|
|
58
|
+
:alt="resolvedName"
|
|
59
|
+
size="fill"
|
|
60
|
+
:aspect="aspect"
|
|
61
|
+
>
|
|
62
|
+
<MLMediaKindBadge
|
|
63
|
+
v-if="!isImage && resolvedKind"
|
|
64
|
+
:kind="resolvedKind"
|
|
65
|
+
size="sm"
|
|
66
|
+
variant="solid"
|
|
67
|
+
class="absolute bottom-1.5 left-1.5"
|
|
68
|
+
/>
|
|
69
|
+
<span
|
|
70
|
+
v-if="durationLabel"
|
|
71
|
+
class="absolute bottom-1.5 right-1.5 rounded bg-black/70 px-1.5 font-mono text-[11px] text-white tabular-nums"
|
|
72
|
+
>{{ durationLabel }}</span>
|
|
73
|
+
<UBadge
|
|
74
|
+
v-if="missingAlt && isImage"
|
|
75
|
+
color="warning"
|
|
76
|
+
variant="solid"
|
|
77
|
+
size="sm"
|
|
78
|
+
label="No alt"
|
|
79
|
+
class="absolute right-1.5 top-1.5"
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<slot />
|
|
83
|
+
|
|
84
|
+
<MLMediaProgressVeil
|
|
85
|
+
v-if="phase"
|
|
86
|
+
:phase="phase"
|
|
87
|
+
:progress="progress"
|
|
88
|
+
:detail="phaseDetail"
|
|
89
|
+
:on-cancel="phase !== 'failed' ? () => emit('cancel') : void 0"
|
|
90
|
+
:on-retry="phase === 'failed' ? () => emit('retry') : void 0"
|
|
91
|
+
/>
|
|
92
|
+
</MLMediaThumbnail>
|
|
93
|
+
|
|
94
|
+
<MLMediaPreviewEye
|
|
95
|
+
v-if="onOpen && !disabled && phase !== 'uploading'"
|
|
96
|
+
:name="resolvedName"
|
|
97
|
+
:visible="selecting"
|
|
98
|
+
class="absolute bottom-1.5 right-1.5 z-20"
|
|
99
|
+
:class="durationLabel ? 'bottom-7' : ''"
|
|
100
|
+
@click="onOpen"
|
|
101
|
+
/>
|
|
102
|
+
|
|
103
|
+
<div
|
|
104
|
+
v-if="selectable"
|
|
105
|
+
class="absolute left-1.5 top-1.5 z-20 flex rounded bg-default p-0.5 shadow-sm transition-opacity motion-reduce:transition-none"
|
|
106
|
+
:class="selected || selecting ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100'"
|
|
107
|
+
@click.stop
|
|
108
|
+
>
|
|
109
|
+
<UCheckbox
|
|
110
|
+
:model-value="selected"
|
|
111
|
+
:aria-label="`Select ${resolvedName}`"
|
|
112
|
+
tabindex="-1"
|
|
113
|
+
@update:model-value="(v) => emit('update:selected', v === true)"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
</template>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
import type { ProgressVeilPhase } from './ProgressVeil.vue.js';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
5
|
+
src?: string | null;
|
|
6
|
+
kind?: string | null;
|
|
7
|
+
name?: string;
|
|
8
|
+
/** Seconds. Read from the asset's renditions when omitted. */
|
|
9
|
+
duration?: number | null;
|
|
10
|
+
/** Shows the "No alt" warning. Only meaningful for images; the host knows where alt text lives. */
|
|
11
|
+
missingAlt?: boolean;
|
|
12
|
+
/** Shows the selection checkbox on hover and focus; always visible once anything is selected. */
|
|
13
|
+
selectable?: boolean;
|
|
14
|
+
selected?: boolean;
|
|
15
|
+
/** Put the checkbox on screen without hovering, e.g. while the gallery is in selection mode. */
|
|
16
|
+
selecting?: boolean;
|
|
17
|
+
/** Overlay for uploads and processing. Omit when the file is ready. */
|
|
18
|
+
phase?: ProgressVeilPhase | null;
|
|
19
|
+
progress?: number | null;
|
|
20
|
+
phaseDetail?: string;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
/** Fixed shape for grid cells. */
|
|
23
|
+
aspect?: 'square' | 'landscape' | 'portrait' | 'video';
|
|
24
|
+
/** Passing a listener shows the eye button, which opens the editor. Enter does the same. */
|
|
25
|
+
onOpen?: () => void;
|
|
26
|
+
};
|
|
27
|
+
type __VLS_Slots = {
|
|
28
|
+
/** Extra overlays, e.g. a drag handle or a "Featured" label. */
|
|
29
|
+
default?: () => unknown;
|
|
30
|
+
};
|
|
31
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
32
|
+
cancel: () => any;
|
|
33
|
+
retry: () => any;
|
|
34
|
+
"update:selected": (value: boolean) => any;
|
|
35
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
36
|
+
onCancel?: (() => any) | undefined;
|
|
37
|
+
onRetry?: (() => any) | undefined;
|
|
38
|
+
"onUpdate:selected"?: ((value: boolean) => any) | undefined;
|
|
39
|
+
}>, {
|
|
40
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
41
|
+
src: string | null;
|
|
42
|
+
progress: number | null;
|
|
43
|
+
name: string;
|
|
44
|
+
kind: string | null;
|
|
45
|
+
phase: ProgressVeilPhase | null;
|
|
46
|
+
disabled: boolean;
|
|
47
|
+
selected: boolean;
|
|
48
|
+
missingAlt: boolean;
|
|
49
|
+
onOpen: () => void;
|
|
50
|
+
duration: number | null;
|
|
51
|
+
selectable: boolean;
|
|
52
|
+
selecting: boolean;
|
|
53
|
+
phaseDetail: string;
|
|
54
|
+
aspect: "square" | "landscape" | "portrait" | "video";
|
|
55
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
56
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
57
|
+
declare const _default: typeof __VLS_export;
|
|
58
|
+
export default _default;
|
|
59
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
60
|
+
new (): {
|
|
61
|
+
$slots: S;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
4
|
+
src?: string | null;
|
|
5
|
+
kind?: string | null;
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Second line. Defaults to the file extension. */
|
|
8
|
+
meta?: string;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
/** Dimmed and not selectable, e.g. a kind the calling field doesn't accept. */
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
disabledReason?: string;
|
|
13
|
+
/** Passing a listener shows the preview button on hover. */
|
|
14
|
+
onPreview?: () => void;
|
|
15
|
+
};
|
|
16
|
+
type __VLS_Slots = {
|
|
17
|
+
/** Overlays on the thumbnail, e.g. an upload veil. */
|
|
18
|
+
default?: () => unknown;
|
|
19
|
+
};
|
|
20
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
21
|
+
toggle: () => any;
|
|
22
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
23
|
+
onToggle?: (() => any) | undefined;
|
|
24
|
+
}>, {
|
|
25
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
26
|
+
src: string | null;
|
|
27
|
+
name: string;
|
|
28
|
+
kind: string | null;
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
meta: string;
|
|
31
|
+
selected: boolean;
|
|
32
|
+
disabledReason: string;
|
|
33
|
+
onPreview: () => void;
|
|
34
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
38
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
39
|
+
new (): {
|
|
40
|
+
$slots: S;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { assetName, fileExtension } from "../../utils/assetFormat";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
asset: { type: [Object, null], required: false, default: null },
|
|
6
|
+
src: { type: [String, null], required: false, default: void 0 },
|
|
7
|
+
kind: { type: [String, null], required: false, default: void 0 },
|
|
8
|
+
name: { type: String, required: false, default: void 0 },
|
|
9
|
+
meta: { type: String, required: false, default: void 0 },
|
|
10
|
+
selected: { type: Boolean, required: false, default: false },
|
|
11
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
12
|
+
disabledReason: { type: String, required: false, default: void 0 },
|
|
13
|
+
onPreview: { type: Function, required: false, default: void 0 }
|
|
14
|
+
});
|
|
15
|
+
const emit = defineEmits(["toggle"]);
|
|
16
|
+
defineSlots();
|
|
17
|
+
const resolvedKind = computed(() => props.kind !== void 0 ? props.kind : props.asset?.kind);
|
|
18
|
+
const resolvedName = computed(() => props.name ?? assetName(props.asset));
|
|
19
|
+
const subtitle = computed(() => props.meta ?? fileExtension(resolvedName.value));
|
|
20
|
+
const isTall = computed(() => (resolvedKind.value ?? "").toLowerCase() === "document");
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div
|
|
25
|
+
class="group relative rounded-lg"
|
|
26
|
+
:class="disabled ? 'opacity-40' : selected ? 'bg-elevated' : 'hover:bg-elevated'"
|
|
27
|
+
:title="disabled ? disabledReason : void 0"
|
|
28
|
+
>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
class="flex w-full flex-col items-center gap-2 rounded-lg px-2.5 pb-2.5 pt-3 text-center outline-offset-2 focus-visible:outline-2 focus-visible:outline-primary"
|
|
32
|
+
:class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
|
|
33
|
+
:aria-pressed="selected"
|
|
34
|
+
:disabled="disabled"
|
|
35
|
+
@click="emit('toggle')"
|
|
36
|
+
>
|
|
37
|
+
<span class="grid aspect-square w-full max-w-28 place-items-center rounded-md bg-default p-1 ring ring-default">
|
|
38
|
+
<MLMediaThumbnail
|
|
39
|
+
:asset="asset"
|
|
40
|
+
:src="src"
|
|
41
|
+
:kind="resolvedKind"
|
|
42
|
+
size="fill"
|
|
43
|
+
:aspect="isTall ? 'portrait' : 'landscape'"
|
|
44
|
+
:fit="isTall ? 'contain' : 'cover'"
|
|
45
|
+
:class="isTall ? 'w-3/4!' : ''"
|
|
46
|
+
class="rounded-sm! ring-0!"
|
|
47
|
+
>
|
|
48
|
+
<slot />
|
|
49
|
+
</MLMediaThumbnail>
|
|
50
|
+
</span>
|
|
51
|
+
<span class="flex w-full min-w-0 flex-col text-xs">
|
|
52
|
+
<span class="truncate">{{ resolvedName }}</span>
|
|
53
|
+
<span
|
|
54
|
+
v-if="subtitle"
|
|
55
|
+
class="text-muted"
|
|
56
|
+
>{{ subtitle }}</span>
|
|
57
|
+
</span>
|
|
58
|
+
</button>
|
|
59
|
+
|
|
60
|
+
<span
|
|
61
|
+
class="pointer-events-none absolute left-2 top-2 grid size-4 place-items-center rounded border-[1.5px]"
|
|
62
|
+
:class="selected ? 'border-inverted bg-inverted text-inverted' : 'border-accented bg-default'"
|
|
63
|
+
aria-hidden="true"
|
|
64
|
+
>
|
|
65
|
+
<UIcon
|
|
66
|
+
v-if="selected"
|
|
67
|
+
name="i-lucide-check"
|
|
68
|
+
class="size-3"
|
|
69
|
+
/>
|
|
70
|
+
</span>
|
|
71
|
+
|
|
72
|
+
<MLMediaPreviewEye
|
|
73
|
+
v-if="onPreview && !disabled"
|
|
74
|
+
:name="resolvedName"
|
|
75
|
+
class="absolute bottom-12 right-2 z-10"
|
|
76
|
+
@click="onPreview"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
4
|
+
src?: string | null;
|
|
5
|
+
kind?: string | null;
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Second line. Defaults to the file extension. */
|
|
8
|
+
meta?: string;
|
|
9
|
+
selected?: boolean;
|
|
10
|
+
/** Dimmed and not selectable, e.g. a kind the calling field doesn't accept. */
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
disabledReason?: string;
|
|
13
|
+
/** Passing a listener shows the preview button on hover. */
|
|
14
|
+
onPreview?: () => void;
|
|
15
|
+
};
|
|
16
|
+
type __VLS_Slots = {
|
|
17
|
+
/** Overlays on the thumbnail, e.g. an upload veil. */
|
|
18
|
+
default?: () => unknown;
|
|
19
|
+
};
|
|
20
|
+
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
21
|
+
toggle: () => any;
|
|
22
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
23
|
+
onToggle?: (() => any) | undefined;
|
|
24
|
+
}>, {
|
|
25
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
26
|
+
src: string | null;
|
|
27
|
+
name: string;
|
|
28
|
+
kind: string | null;
|
|
29
|
+
disabled: boolean;
|
|
30
|
+
meta: string;
|
|
31
|
+
selected: boolean;
|
|
32
|
+
disabledReason: string;
|
|
33
|
+
onPreview: () => void;
|
|
34
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
35
|
+
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
36
|
+
declare const _default: typeof __VLS_export;
|
|
37
|
+
export default _default;
|
|
38
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
39
|
+
new (): {
|
|
40
|
+
$slots: S;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
4
|
+
src?: string | null;
|
|
5
|
+
kind?: string | null;
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Defaults to the asset's size. */
|
|
8
|
+
size?: string;
|
|
9
|
+
/** Defaults to the asset's created date. */
|
|
10
|
+
date?: string;
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
disabledReason?: string;
|
|
14
|
+
};
|
|
15
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
|
+
toggle: () => any;
|
|
17
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
18
|
+
onToggle?: (() => any) | undefined;
|
|
19
|
+
}>, {
|
|
20
|
+
size: string;
|
|
21
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
22
|
+
src: string | null;
|
|
23
|
+
name: string;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
disabled: boolean;
|
|
26
|
+
selected: boolean;
|
|
27
|
+
date: string;
|
|
28
|
+
disabledReason: string;
|
|
29
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
|
+
declare const _default: typeof __VLS_export;
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
import { assetName, assetSizeBytes, formatBytes, formatShortDate } from "../../utils/assetFormat";
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
asset: { type: [Object, null], required: false, default: null },
|
|
6
|
+
src: { type: [String, null], required: false, default: void 0 },
|
|
7
|
+
kind: { type: [String, null], required: false, default: void 0 },
|
|
8
|
+
name: { type: String, required: false, default: void 0 },
|
|
9
|
+
size: { type: String, required: false, default: void 0 },
|
|
10
|
+
date: { type: String, required: false, default: void 0 },
|
|
11
|
+
selected: { type: Boolean, required: false, default: false },
|
|
12
|
+
disabled: { type: Boolean, required: false, default: false },
|
|
13
|
+
disabledReason: { type: String, required: false, default: void 0 }
|
|
14
|
+
});
|
|
15
|
+
const emit = defineEmits(["toggle"]);
|
|
16
|
+
const resolvedKind = computed(() => props.kind !== void 0 ? props.kind : props.asset?.kind);
|
|
17
|
+
const resolvedName = computed(() => props.name ?? assetName(props.asset));
|
|
18
|
+
const sizeLabel = computed(() => {
|
|
19
|
+
if (props.size !== void 0) return props.size;
|
|
20
|
+
const bytes = assetSizeBytes(props.asset);
|
|
21
|
+
return bytes ? formatBytes(bytes) : "";
|
|
22
|
+
});
|
|
23
|
+
const dateLabel = computed(() => {
|
|
24
|
+
if (props.date !== void 0) return props.date;
|
|
25
|
+
const a = props.asset;
|
|
26
|
+
return a && "created_at" in a ? formatShortDate(a.created_at || a.createdAt) : "";
|
|
27
|
+
});
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
class="grid w-full grid-cols-[1rem_2.5rem_minmax(0,1fr)_auto] items-center gap-3 px-3 py-1.5 text-left text-sm outline-offset-[-2px] focus-visible:outline-2 focus-visible:outline-primary sm:grid-cols-[1rem_2.5rem_minmax(0,1fr)_auto_5rem_4.5rem]"
|
|
34
|
+
:class="[
|
|
35
|
+
selected ? 'bg-elevated' : 'hover:bg-elevated',
|
|
36
|
+
disabled ? 'cursor-not-allowed opacity-40' : 'cursor-pointer'
|
|
37
|
+
]"
|
|
38
|
+
:aria-pressed="selected"
|
|
39
|
+
:disabled="disabled"
|
|
40
|
+
:title="disabled ? disabledReason : void 0"
|
|
41
|
+
@click="emit('toggle')"
|
|
42
|
+
>
|
|
43
|
+
<span
|
|
44
|
+
class="grid size-4 place-items-center rounded border-[1.5px]"
|
|
45
|
+
:class="selected ? 'border-inverted bg-inverted text-inverted' : 'border-accented bg-default'"
|
|
46
|
+
aria-hidden="true"
|
|
47
|
+
>
|
|
48
|
+
<UIcon
|
|
49
|
+
v-if="selected"
|
|
50
|
+
name="i-lucide-check"
|
|
51
|
+
class="size-3"
|
|
52
|
+
/>
|
|
53
|
+
</span>
|
|
54
|
+
<MLMediaThumbnail
|
|
55
|
+
:asset="asset"
|
|
56
|
+
:src="src"
|
|
57
|
+
:kind="resolvedKind"
|
|
58
|
+
size="sm"
|
|
59
|
+
/>
|
|
60
|
+
<span class="truncate">{{ resolvedName }}</span>
|
|
61
|
+
<MLMediaKindBadge :kind="resolvedKind" />
|
|
62
|
+
<span class="hidden text-right text-muted tabular-nums sm:block">{{ sizeLabel }}</span>
|
|
63
|
+
<span class="hidden text-right text-muted tabular-nums sm:block">{{ dateLabel }}</span>
|
|
64
|
+
</button>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AssetDetail, DeliveryAsset } from '../../types/asset.js';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
asset?: AssetDetail | DeliveryAsset | null;
|
|
4
|
+
src?: string | null;
|
|
5
|
+
kind?: string | null;
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Defaults to the asset's size. */
|
|
8
|
+
size?: string;
|
|
9
|
+
/** Defaults to the asset's created date. */
|
|
10
|
+
date?: string;
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
disabledReason?: string;
|
|
14
|
+
};
|
|
15
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
|
+
toggle: () => any;
|
|
17
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
18
|
+
onToggle?: (() => any) | undefined;
|
|
19
|
+
}>, {
|
|
20
|
+
size: string;
|
|
21
|
+
asset: AssetDetail | DeliveryAsset | null;
|
|
22
|
+
src: string | null;
|
|
23
|
+
name: string;
|
|
24
|
+
kind: string | null;
|
|
25
|
+
disabled: boolean;
|
|
26
|
+
selected: boolean;
|
|
27
|
+
date: string;
|
|
28
|
+
disabledReason: string;
|
|
29
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
30
|
+
declare const _default: typeof __VLS_export;
|
|
31
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** File name, for the accessible label. */
|
|
3
|
+
name?: string;
|
|
4
|
+
/** Keep it visible without hovering (touch screens, selection mode). */
|
|
5
|
+
visible?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
click: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onClick?: (() => any) | undefined;
|
|
11
|
+
}>, {
|
|
12
|
+
name: string;
|
|
13
|
+
visible: boolean;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
defineProps({
|
|
3
|
+
name: { type: String, required: false, default: "" },
|
|
4
|
+
visible: { type: Boolean, required: false, default: false }
|
|
5
|
+
});
|
|
6
|
+
const emit = defineEmits(["click"]);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<UButton
|
|
11
|
+
icon="i-lucide-eye"
|
|
12
|
+
color="info"
|
|
13
|
+
variant="solid"
|
|
14
|
+
size="xs"
|
|
15
|
+
square
|
|
16
|
+
class="rounded-md shadow-sm transition-opacity motion-reduce:transition-none"
|
|
17
|
+
:class="visible ? 'opacity-100' : 'opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 focus-visible:opacity-100 [@media(hover:none)]:opacity-100'"
|
|
18
|
+
:aria-label="name ? `Preview ${name}` : 'Preview'"
|
|
19
|
+
@click.stop="emit('click')"
|
|
20
|
+
/>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
/** File name, for the accessible label. */
|
|
3
|
+
name?: string;
|
|
4
|
+
/** Keep it visible without hovering (touch screens, selection mode). */
|
|
5
|
+
visible?: boolean;
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
8
|
+
click: () => any;
|
|
9
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
10
|
+
onClick?: (() => any) | undefined;
|
|
11
|
+
}>, {
|
|
12
|
+
name: string;
|
|
13
|
+
visible: boolean;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -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,98 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
const props = defineProps({
|
|
4
|
+
phase: { type: String, required: true },
|
|
5
|
+
progress: { type: [Number, null], required: false, default: null },
|
|
6
|
+
label: { type: String, required: false, default: void 0 },
|
|
7
|
+
detail: { type: String, required: false, default: void 0 },
|
|
8
|
+
compact: { type: Boolean, required: false, default: false },
|
|
9
|
+
onCancel: { type: Function, required: false, default: void 0 },
|
|
10
|
+
onRetry: { type: Function, required: false, default: void 0 }
|
|
11
|
+
});
|
|
12
|
+
const PHASES = {
|
|
13
|
+
uploading: { label: "Uploading", color: "primary", icon: "i-lucide-upload" },
|
|
14
|
+
processing: { label: "Processing", color: "warning", icon: "i-lucide-loader-circle" },
|
|
15
|
+
failed: { label: "Failed", color: "error", icon: "i-lucide-circle-alert" }
|
|
16
|
+
};
|
|
17
|
+
const meta = computed(() => PHASES[props.phase]);
|
|
18
|
+
const value = computed(() => props.progress == null ? null : Math.round(Math.min(100, Math.max(0, props.progress))));
|
|
19
|
+
const heading = computed(() => {
|
|
20
|
+
const text = props.label ?? meta.value.label;
|
|
21
|
+
return value.value != null && props.phase !== "failed" ? `${text} ${value.value}%` : text;
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<div
|
|
27
|
+
class="absolute inset-0 z-10 grid place-items-center bg-default/70 backdrop-blur-[2px]"
|
|
28
|
+
:class="compact ? 'p-1' : 'p-2'"
|
|
29
|
+
role="status"
|
|
30
|
+
aria-live="polite"
|
|
31
|
+
:aria-label="detail ? `${heading}: ${detail}` : heading"
|
|
32
|
+
>
|
|
33
|
+
<div
|
|
34
|
+
v-if="compact"
|
|
35
|
+
class="w-full self-end"
|
|
36
|
+
>
|
|
37
|
+
<UProgress
|
|
38
|
+
v-if="phase !== 'failed'"
|
|
39
|
+
:model-value="value"
|
|
40
|
+
:color="meta.color"
|
|
41
|
+
size="2xs"
|
|
42
|
+
/>
|
|
43
|
+
<UIcon
|
|
44
|
+
v-else
|
|
45
|
+
:name="meta.icon"
|
|
46
|
+
class="mx-auto block size-4 text-error"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div
|
|
51
|
+
v-else
|
|
52
|
+
class="flex w-full max-w-40 flex-col items-center gap-1.5 text-center"
|
|
53
|
+
>
|
|
54
|
+
<UIcon
|
|
55
|
+
:name="meta.icon"
|
|
56
|
+
class="size-4"
|
|
57
|
+
:class="{
|
|
58
|
+
'animate-spin motion-reduce:animate-none': phase === 'processing',
|
|
59
|
+
'text-error': phase === 'failed'
|
|
60
|
+
}"
|
|
61
|
+
/>
|
|
62
|
+
<span class="text-xs font-semibold tabular-nums">{{ heading }}</span>
|
|
63
|
+
<span
|
|
64
|
+
v-if="detail"
|
|
65
|
+
class="w-full truncate text-xs text-muted"
|
|
66
|
+
>{{ detail }}</span>
|
|
67
|
+
<UProgress
|
|
68
|
+
v-if="phase !== 'failed'"
|
|
69
|
+
:model-value="value"
|
|
70
|
+
:color="meta.color"
|
|
71
|
+
size="xs"
|
|
72
|
+
class="w-4/5"
|
|
73
|
+
/>
|
|
74
|
+
<div
|
|
75
|
+
v-if="onCancel || onRetry"
|
|
76
|
+
class="flex gap-1"
|
|
77
|
+
>
|
|
78
|
+
<UButton
|
|
79
|
+
v-if="onRetry && phase === 'failed'"
|
|
80
|
+
size="xs"
|
|
81
|
+
variant="soft"
|
|
82
|
+
color="neutral"
|
|
83
|
+
icon="i-lucide-rotate-ccw"
|
|
84
|
+
label="Retry"
|
|
85
|
+
@click.stop="onRetry"
|
|
86
|
+
/>
|
|
87
|
+
<UButton
|
|
88
|
+
v-if="onCancel && phase !== 'failed'"
|
|
89
|
+
size="xs"
|
|
90
|
+
variant="ghost"
|
|
91
|
+
color="neutral"
|
|
92
|
+
label="Cancel"
|
|
93
|
+
@click.stop="onCancel"
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</template>
|