nuxt-media-library 0.0.65 → 0.0.66

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 (25) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/MLMedia/Field.d.vue.ts +1 -1
  3. package/dist/runtime/components/MLMedia/Field.vue.d.ts +1 -1
  4. package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +1 -1
  5. package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +1 -1
  6. package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +1 -1
  7. package/dist/runtime/components/MLMedia/GallerySection.vue +21 -6
  8. package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +1 -1
  9. package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +6 -0
  10. package/dist/runtime/components/MLMedia/MediaTile.vue +5 -2
  11. package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +6 -0
  12. package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +1 -1
  13. package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +1 -1
  14. package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +2 -1
  15. package/dist/runtime/components/MLMedia/ProgressVeil.vue +10 -7
  16. package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +2 -1
  17. package/dist/runtime/components/MLProcessing/StepLayout.d.vue.ts +1 -1
  18. package/dist/runtime/components/MLProcessing/StepLayout.vue.d.ts +1 -1
  19. package/dist/runtime/components/MLProcessing/StepperItem.d.vue.ts +2 -2
  20. package/dist/runtime/components/MLProcessing/StepperItem.vue.d.ts +2 -2
  21. package/dist/runtime/composables/useMLAssetStall.d.ts +23 -0
  22. package/dist/runtime/composables/useMLAssetStall.js +59 -0
  23. package/dist/runtime/composables/useMLMediaGallery.d.ts +2 -0
  24. package/dist/runtime/composables/useMLMediaGallery.js +2 -0
  25. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "media-library",
3
3
  "configKey": "mediaLibrary",
4
- "version": "0.0.65",
4
+ "version": "0.0.66",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -86,10 +86,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
86
86
  pickFrom: readonly string[] | null;
87
87
  required: boolean;
88
88
  removeLabel: string;
89
- onPick: () => void;
90
89
  hint: string;
91
90
  missingAlt: boolean;
92
91
  picker: boolean;
92
+ onPick: () => void;
93
93
  editor: boolean;
94
94
  onOpen: (assetId: string) => void;
95
95
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -86,10 +86,10 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
86
86
  pickFrom: readonly string[] | null;
87
87
  required: boolean;
88
88
  removeLabel: string;
89
- onPick: () => void;
90
89
  hint: string;
91
90
  missingAlt: boolean;
92
91
  picker: boolean;
92
+ onPick: () => void;
93
93
  editor: boolean;
94
94
  onOpen: (assetId: string) => void;
95
95
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -108,9 +108,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
108
108
  max: number | null;
109
109
  maxSize: number | null;
110
110
  pickFrom: readonly string[] | null;
111
- onPick: () => void;
112
111
  hint: string;
113
112
  picker: boolean;
113
+ onPick: () => void;
114
114
  editor: boolean;
115
115
  onOpen: (assetId: string) => void;
116
116
  featured: boolean;
@@ -108,9 +108,9 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
108
108
  max: number | null;
109
109
  maxSize: number | null;
110
110
  pickFrom: readonly string[] | null;
111
- onPick: () => void;
112
111
  hint: string;
113
112
  picker: boolean;
113
+ onPick: () => void;
114
114
  editor: boolean;
115
115
  onOpen: (assetId: string) => void;
116
116
  featured: boolean;
@@ -104,9 +104,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
104
104
  pickFrom: readonly string[] | null;
105
105
  removeLabel: string;
106
106
  onAdd: (assetId: string, tag: string | undefined) => void;
107
- onPick: () => void;
108
107
  hint: string;
109
108
  picker: boolean;
109
+ onPick: () => void;
110
110
  editor: boolean;
111
111
  onOpen: (assetId: string) => void;
112
112
  featured: boolean;
@@ -4,6 +4,7 @@ import { useMLMediaGallery } from "../../composables/useMLMediaGallery";
4
4
  import { useMLFilePicker } from "../../composables/useMLFilePicker";
5
5
  import { useMLMediaEditor } from "../../composables/useMLMediaEditor";
6
6
  import { useMLMediaMeta } from "../../composables/useMLMediaMeta";
7
+ import { useMLAssetStall } from "../../composables/useMLAssetStall";
7
8
  import { assetName } from "../../utils/assetFormat";
8
9
  import { assetThumbnailUrl } from "../../utils/assetThumbnail";
9
10
  import { acceptForKinds, partitionFiles } from "../../utils/fileAccept";
@@ -90,12 +91,24 @@ function removeSelected() {
90
91
  gallery.remove([...selected.value]);
91
92
  selected.value = /* @__PURE__ */ new Set();
92
93
  }
93
- function tilePhase(item) {
94
+ const uploadRunning = (item) => {
94
95
  const p = item.upload?.phase;
95
- if (p === "uploading" || p === "processing" || p === "failed") return p;
96
+ return p === "uploading" || p === "processing" || p === "failed";
97
+ };
98
+ const stalls = useMLAssetStall(
99
+ () => items.value.filter((i) => i.id && !uploadRunning(i)).map((i) => i.id),
100
+ (id) => gallery.asset(id),
101
+ (ids) => gallery.refresh(ids)
102
+ );
103
+ const STUCK_HINT = "Remove it, or upload the file again.";
104
+ function tileState(item) {
105
+ if (uploadRunning(item)) return { phase: item.upload.phase, detail: !item.id ? item.upload.file.name : void 0 };
96
106
  const s = item.asset?.status?.toLowerCase();
97
- if (s === "processing" || s === "uploaded" || s === "pending") return "processing";
98
- if (s === "failed") return "failed";
107
+ if (s === "failed") return { phase: "failed", label: "Processing failed", detail: STUCK_HINT };
108
+ const stall = stalls.stall(item.id);
109
+ if (stall === "not_started") return { phase: "stalled", label: "Processing didn't start", detail: STUCK_HINT };
110
+ if (stall === "stalled") return { phase: "stalled", label: "Processing stalled", detail: STUCK_HINT };
111
+ if (s === "processing" || s === "uploaded" || s === "pending") return { phase: "processing" };
99
112
  return null;
100
113
  }
101
114
  function tileSrc(item) {
@@ -375,9 +388,11 @@ async function onTileKey(e, index) {
375
388
  :selectable="!!item.id && !disabled"
376
389
  :selected="!!item.id && selected.has(item.id)"
377
390
  :selecting="selected.size > 0"
378
- :phase="tilePhase(item)"
391
+ :phase="tileState(item)?.phase ?? null"
392
+ :phase-label="tileState(item)?.label"
393
+ :controls="uploadRunning(item)"
379
394
  :progress="item.upload?.progress ?? null"
380
- :phase-detail="item.upload && !item.id ? item.upload.file.name : void 0"
395
+ :phase-detail="tileState(item)?.detail"
381
396
  :on-open="openFor(item)"
382
397
  @update:selected="(v) => item.id && setSelected(item.id, v)"
383
398
  @cancel="onCancel(item)"
@@ -104,9 +104,9 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
104
104
  pickFrom: readonly string[] | null;
105
105
  removeLabel: string;
106
106
  onAdd: (assetId: string, tag: string | undefined) => void;
107
- onPick: () => void;
108
107
  hint: string;
109
108
  picker: boolean;
109
+ onPick: () => void;
110
110
  editor: boolean;
111
111
  onOpen: (assetId: string) => void;
112
112
  featured: boolean;
@@ -17,7 +17,11 @@ type __VLS_Props = {
17
17
  /** Overlay for uploads and processing. Omit when the file is ready. */
18
18
  phase?: ProgressVeilPhase | null;
19
19
  progress?: number | null;
20
+ /** Replaces the phase's label, e.g. "Processing didn't start". */
21
+ phaseLabel?: string;
20
22
  phaseDetail?: string;
23
+ /** Show the veil's Cancel / Retry. Off for files with no upload running here. */
24
+ controls?: boolean;
21
25
  disabled?: boolean;
22
26
  /** Fixed shape for grid cells. */
23
27
  aspect?: 'square' | 'landscape' | 'portrait' | 'video';
@@ -46,10 +50,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
46
50
  disabled: boolean;
47
51
  phase: ProgressVeilPhase | null;
48
52
  selected: boolean;
53
+ controls: boolean;
49
54
  selectable: boolean;
50
55
  missingAlt: boolean;
51
56
  onOpen: () => void;
52
57
  selecting: boolean;
58
+ phaseLabel: string;
53
59
  phaseDetail: string;
54
60
  aspect: "square" | "landscape" | "portrait" | "video";
55
61
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -13,7 +13,9 @@ const props = defineProps({
13
13
  selecting: { type: Boolean, required: false, default: false },
14
14
  phase: { type: [String, null], required: false, default: null },
15
15
  progress: { type: [Number, null], required: false, default: null },
16
+ phaseLabel: { type: String, required: false, default: void 0 },
16
17
  phaseDetail: { type: String, required: false, default: void 0 },
18
+ controls: { type: Boolean, required: false, default: true },
17
19
  disabled: { type: Boolean, required: false, default: false },
18
20
  aspect: { type: String, required: false, default: "square" },
19
21
  onOpen: { type: Function, required: false, default: void 0 }
@@ -85,9 +87,10 @@ function onKey(e) {
85
87
  v-if="phase"
86
88
  :phase="phase"
87
89
  :progress="progress"
90
+ :label="phaseLabel"
88
91
  :detail="phaseDetail"
89
- :on-cancel="phase !== 'failed' ? () => emit('cancel') : void 0"
90
- :on-retry="phase === 'failed' ? () => emit('retry') : void 0"
92
+ :on-cancel="controls && phase !== 'failed' ? () => emit('cancel') : void 0"
93
+ :on-retry="controls && phase === 'failed' ? () => emit('retry') : void 0"
91
94
  />
92
95
  </MLMediaThumbnail>
93
96
 
@@ -17,7 +17,11 @@ type __VLS_Props = {
17
17
  /** Overlay for uploads and processing. Omit when the file is ready. */
18
18
  phase?: ProgressVeilPhase | null;
19
19
  progress?: number | null;
20
+ /** Replaces the phase's label, e.g. "Processing didn't start". */
21
+ phaseLabel?: string;
20
22
  phaseDetail?: string;
23
+ /** Show the veil's Cancel / Retry. Off for files with no upload running here. */
24
+ controls?: boolean;
21
25
  disabled?: boolean;
22
26
  /** Fixed shape for grid cells. */
23
27
  aspect?: 'square' | 'landscape' | 'portrait' | 'video';
@@ -46,10 +50,12 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
46
50
  disabled: boolean;
47
51
  phase: ProgressVeilPhase | null;
48
52
  selected: boolean;
53
+ controls: boolean;
49
54
  selectable: boolean;
50
55
  missingAlt: boolean;
51
56
  onOpen: () => void;
52
57
  selecting: boolean;
58
+ phaseLabel: string;
53
59
  phaseDetail: string;
54
60
  aspect: "square" | "landscape" | "portrait" | "video";
55
61
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
@@ -32,8 +32,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
32
32
  name: string;
33
33
  disabled: boolean;
34
34
  selected: boolean;
35
- date: string;
36
35
  selectable: boolean;
36
+ date: string;
37
37
  disabledReason: string;
38
38
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
39
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -32,8 +32,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
32
32
  name: string;
33
33
  disabled: boolean;
34
34
  selected: boolean;
35
- date: string;
36
35
  selectable: boolean;
36
+ date: string;
37
37
  disabledReason: string;
38
38
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
39
39
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -1,4 +1,5 @@
1
- export type ProgressVeilPhase = 'uploading' | 'processing' | 'failed';
1
+ /** 'stalled': processing didn't start or stopped moving (no spinner, no bar). */
2
+ export type ProgressVeilPhase = 'uploading' | 'processing' | 'failed' | 'stalled';
2
3
  type __VLS_Props = {
3
4
  phase: ProgressVeilPhase;
4
5
  /** 0–100. Null or omitted shows an indeterminate bar (e.g. a step with no progress events). */
@@ -12,13 +12,14 @@ const props = defineProps({
12
12
  const PHASES = {
13
13
  uploading: { label: "Uploading", color: "primary", icon: "i-lucide-upload" },
14
14
  processing: { label: "Processing", color: "warning", icon: "i-lucide-loader-circle" },
15
- failed: { label: "Failed", color: "error", icon: "i-lucide-circle-alert" }
15
+ failed: { label: "Failed", color: "error", icon: "i-lucide-circle-alert" },
16
+ stalled: { label: "Stuck", color: "warning", icon: "i-lucide-clock-alert" }
16
17
  };
17
18
  const meta = computed(() => PHASES[props.phase]);
18
19
  const value = computed(() => props.progress == null ? null : Math.round(Math.min(100, Math.max(0, props.progress))));
19
20
  const heading = computed(() => {
20
21
  const text = props.label ?? meta.value.label;
21
- return value.value != null && props.phase !== "failed" ? `${text} ${value.value}%` : text;
22
+ return value.value != null && props.phase !== "failed" && props.phase !== "stalled" ? `${text} ${value.value}%` : text;
22
23
  });
23
24
  </script>
24
25
 
@@ -35,7 +36,7 @@ const heading = computed(() => {
35
36
  class="w-full self-end"
36
37
  >
37
38
  <UProgress
38
- v-if="phase !== 'failed'"
39
+ v-if="phase !== 'failed' && phase !== 'stalled'"
39
40
  :model-value="value"
40
41
  :color="meta.color"
41
42
  size="2xs"
@@ -43,7 +44,8 @@ const heading = computed(() => {
43
44
  <UIcon
44
45
  v-else
45
46
  :name="meta.icon"
46
- class="mx-auto block size-4 text-error"
47
+ class="mx-auto block size-4"
48
+ :class="phase === 'stalled' ? 'text-warning' : 'text-error'"
47
49
  />
48
50
  </div>
49
51
 
@@ -56,7 +58,8 @@ const heading = computed(() => {
56
58
  class="size-4"
57
59
  :class="{
58
60
  'animate-spin motion-reduce:animate-none': phase === 'processing',
59
- 'text-error': phase === 'failed'
61
+ 'text-error': phase === 'failed',
62
+ 'text-warning': phase === 'stalled'
60
63
  }"
61
64
  />
62
65
  <span class="text-xs font-semibold tabular-nums">{{ heading }}</span>
@@ -65,7 +68,7 @@ const heading = computed(() => {
65
68
  class="w-full truncate text-xs text-muted"
66
69
  >{{ detail }}</span>
67
70
  <UProgress
68
- v-if="phase !== 'failed'"
71
+ v-if="phase !== 'failed' && phase !== 'stalled'"
69
72
  :model-value="value"
70
73
  :color="meta.color"
71
74
  size="xs"
@@ -85,7 +88,7 @@ const heading = computed(() => {
85
88
  @click.stop="onRetry"
86
89
  />
87
90
  <UButton
88
- v-if="onCancel && phase !== 'failed'"
91
+ v-if="onCancel && phase !== 'failed' && phase !== 'stalled'"
89
92
  size="xs"
90
93
  variant="ghost"
91
94
  color="neutral"
@@ -1,4 +1,5 @@
1
- export type ProgressVeilPhase = 'uploading' | 'processing' | 'failed';
1
+ /** 'stalled': processing didn't start or stopped moving (no spinner, no bar). */
2
+ export type ProgressVeilPhase = 'uploading' | 'processing' | 'failed' | 'stalled';
2
3
  type __VLS_Props = {
3
4
  phase: ProgressVeilPhase;
4
5
  /** 0–100. Null or omitted shows an indeterminate bar (e.g. a step with no progress events). */
@@ -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>;
@@ -5,12 +5,12 @@ type __VLS_Props = {
5
5
  };
6
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
7
  retry: () => any;
8
- "update:options": (value: Record<string, any>) => any;
9
8
  run: () => any;
9
+ "update:options": (value: Record<string, any>) => any;
10
10
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
11
  onRetry?: (() => any) | undefined;
12
- "onUpdate:options"?: ((value: Record<string, any>) => any) | undefined;
13
12
  onRun?: (() => any) | undefined;
13
+ "onUpdate:options"?: ((value: Record<string, any>) => any) | undefined;
14
14
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
15
  declare const _default: typeof __VLS_export;
16
16
  export default _default;
@@ -5,12 +5,12 @@ type __VLS_Props = {
5
5
  };
6
6
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
7
  retry: () => any;
8
- "update:options": (value: Record<string, any>) => any;
9
8
  run: () => any;
9
+ "update:options": (value: Record<string, any>) => any;
10
10
  }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
11
11
  onRetry?: (() => any) | undefined;
12
- "onUpdate:options"?: ((value: Record<string, any>) => any) | undefined;
13
12
  onRun?: (() => any) | undefined;
13
+ "onUpdate:options"?: ((value: Record<string, any>) => any) | undefined;
14
14
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
15
15
  declare const _default: typeof __VLS_export;
16
16
  export default _default;
@@ -0,0 +1,23 @@
1
+ import { type MaybeRefOrGetter } from 'vue';
2
+ import type { AssetDetail, DeliveryAsset } from '../types/asset.js';
3
+ /** 'not_started': still uploaded / pending after a re-check. 'stalled': processing with no change for a long time. */
4
+ export type AssetStall = 'not_started' | 'stalled';
5
+ export interface UseMLAssetStallOptions {
6
+ /** Re-check interval, ms. */
7
+ pollMs?: number;
8
+ /** Processing with no change for this long is stalled, ms. */
9
+ stallMs?: number;
10
+ }
11
+ /**
12
+ * Watches files that came from the record still waiting for or in
13
+ * processing (not ones uploading here: their upload follows processing).
14
+ * The resolve answer has no timestamps, so this re-checks them every
15
+ * `pollMs` and judges by what it sees: still uploaded / pending after one
16
+ * re-check means processing didn't start; processing with nothing changed
17
+ * for `stallMs` means it stalled. Stuck files keep being re-checked, so
18
+ * they recover when the library catches up.
19
+ */
20
+ export declare function useMLAssetStall(ids: MaybeRefOrGetter<readonly string[]>, asset: (id: string) => AssetDetail | DeliveryAsset | null, refresh: (ids: string[]) => Promise<void>, options?: UseMLAssetStallOptions): {
21
+ /** Why this file looks stuck, or null. */
22
+ stall: (id: string | null | undefined) => AssetStall | null;
23
+ };
@@ -0,0 +1,59 @@
1
+ import { getCurrentScope, onScopeDispose, ref, toValue, watch } from "vue";
2
+ const WAITING = /* @__PURE__ */ new Set(["pending", "uploaded"]);
3
+ const BUSY = /* @__PURE__ */ new Set([...WAITING, "processing"]);
4
+ function signature(asset) {
5
+ return [asset.status, ...(asset.renditions ?? []).map((r) => `${r.id}:${r.status}`)].join("|").toLowerCase();
6
+ }
7
+ export function useMLAssetStall(ids, asset, refresh, options = {}) {
8
+ const pollMs = options.pollMs ?? 2e4;
9
+ const stallMs = options.stallMs ?? 15 * 6e4;
10
+ const seen = /* @__PURE__ */ new Map();
11
+ const stalls = ref({});
12
+ let timer;
13
+ const watched = () => toValue(ids).filter((id) => {
14
+ const a = asset(id);
15
+ return !!a && BUSY.has(a.status?.toLowerCase());
16
+ });
17
+ function evaluate(rechecked) {
18
+ const now = Date.now();
19
+ const next = {};
20
+ const live = new Set(watched());
21
+ for (const id of [...seen.keys()]) if (!live.has(id)) seen.delete(id);
22
+ for (const id of live) {
23
+ const a = asset(id);
24
+ const sig = signature(a);
25
+ const prev = seen.get(id);
26
+ const entry = prev && prev.sig === sig ? { ...prev, checks: prev.checks + (rechecked ? 1 : 0) } : { sig, since: now, checks: 0 };
27
+ seen.set(id, entry);
28
+ if (WAITING.has(a.status.toLowerCase())) {
29
+ if (entry.checks >= 1) next[id] = "not_started";
30
+ } else if (now - entry.since >= stallMs) {
31
+ next[id] = "stalled";
32
+ }
33
+ }
34
+ if (JSON.stringify(next) !== JSON.stringify(stalls.value)) stalls.value = next;
35
+ }
36
+ async function tick() {
37
+ const list = watched();
38
+ if (!list.length) return stop();
39
+ try {
40
+ await refresh(list);
41
+ } catch {
42
+ }
43
+ evaluate(true);
44
+ }
45
+ function stop() {
46
+ clearInterval(timer);
47
+ timer = void 0;
48
+ }
49
+ watch(() => watched().map((id) => `${id}:${signature(asset(id))}`).join(","), () => {
50
+ evaluate(false);
51
+ if (watched().length && !timer && import.meta.client !== false) timer = setInterval(() => void tick(), pollMs);
52
+ if (!watched().length) stop();
53
+ }, { immediate: true });
54
+ if (getCurrentScope()) onScopeDispose(stop);
55
+ return {
56
+ /** Why this file looks stuck, or null. */
57
+ stall: (id) => id ? stalls.value[id] ?? null : null
58
+ };
59
+ }
@@ -43,6 +43,8 @@ export declare function useMLMediaGallery(model: Ref<string[]>, options?: UseMLM
43
43
  items: import("vue").ComputedRef<GalleryItem[]>;
44
44
  /** The loaded asset for an ID in the list, if any. */
45
45
  asset: (id: string) => AssetDetail | null;
46
+ /** Reload some files' details from the library, e.g. to re-check their processing. */
47
+ refresh: (only: string[]) => Promise<void>;
46
48
  message: Ref<string | null, string | null>;
47
49
  remaining: import("vue").ComputedRef<number>;
48
50
  full: import("vue").ComputedRef<boolean>;
@@ -118,6 +118,8 @@ export function useMLMediaGallery(model, options = {}) {
118
118
  items,
119
119
  /** The loaded asset for an ID in the list, if any. */
120
120
  asset: (id) => library.get(id),
121
+ /** Reload some files' details from the library, e.g. to re-check their processing. */
122
+ refresh: (only) => library.refresh(only),
121
123
  message,
122
124
  remaining,
123
125
  full,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-media-library",
3
- "version": "0.0.65",
3
+ "version": "0.0.66",
4
4
  "description": "Nuxt module for media library management — chunked uploads, asset previews, and resource policies",
5
5
  "repository": {
6
6
  "type": "git",