quasar-ui-danx 0.4.95 → 0.4.99

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 (33) hide show
  1. package/dist/danx.es.js +24452 -22880
  2. package/dist/danx.es.js.map +1 -1
  3. package/dist/danx.umd.js +133 -122
  4. package/dist/danx.umd.js.map +1 -1
  5. package/dist/style.css +1 -1
  6. package/package.json +1 -1
  7. package/src/components/Utility/Buttons/ActionButton.vue +11 -3
  8. package/src/components/Utility/Code/CodeViewer.vue +219 -0
  9. package/src/components/Utility/Code/CodeViewerCollapsed.vue +34 -0
  10. package/src/components/Utility/Code/CodeViewerFooter.vue +53 -0
  11. package/src/components/Utility/Code/LanguageBadge.vue +122 -0
  12. package/src/components/Utility/Code/MarkdownContent.vue +251 -0
  13. package/src/components/Utility/Code/index.ts +5 -0
  14. package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +134 -38
  15. package/src/components/Utility/Files/CarouselHeader.vue +24 -0
  16. package/src/components/Utility/Files/FileMetadataDialog.vue +69 -0
  17. package/src/components/Utility/Files/FilePreview.vue +118 -166
  18. package/src/components/Utility/Files/index.ts +1 -0
  19. package/src/components/Utility/index.ts +1 -0
  20. package/src/composables/index.ts +5 -0
  21. package/src/composables/useCodeFormat.ts +199 -0
  22. package/src/composables/useCodeViewerCollapse.ts +125 -0
  23. package/src/composables/useCodeViewerEditor.ts +420 -0
  24. package/src/composables/useFilePreview.ts +119 -0
  25. package/src/composables/useTranscodeLoader.ts +68 -0
  26. package/src/helpers/formats/highlightSyntax.ts +327 -0
  27. package/src/helpers/formats/index.ts +3 -1
  28. package/src/helpers/formats/renderMarkdown.ts +338 -0
  29. package/src/styles/danx.scss +3 -0
  30. package/src/styles/themes/danx/code.scss +158 -0
  31. package/src/styles/themes/danx/index.scss +2 -0
  32. package/src/styles/themes/danx/markdown.scss +145 -0
  33. package/src/styles/themes/danx/scrollbar.scss +125 -0
@@ -0,0 +1,420 @@
1
+ import { computed, nextTick, onUnmounted, Ref, ref } from "vue";
2
+ import { CodeFormat, UseCodeFormatReturn, ValidationError } from "./useCodeFormat";
3
+ import { highlightSyntax } from "../helpers/formats/highlightSyntax";
4
+
5
+ export interface UseCodeViewerEditorOptions {
6
+ codeRef: Ref<HTMLPreElement | null>;
7
+ codeFormat: UseCodeFormatReturn;
8
+ currentFormat: Ref<CodeFormat>;
9
+ canEdit: Ref<boolean>;
10
+ editable: Ref<boolean>;
11
+ onEmitModelValue: (value: object | string | null) => void;
12
+ onEmitEditable: (editable: boolean) => void;
13
+ }
14
+
15
+ export interface UseCodeViewerEditorReturn {
16
+ // State
17
+ internalEditable: Ref<boolean>;
18
+ editingContent: Ref<string>;
19
+ cachedHighlightedContent: Ref<string>;
20
+ isUserEditing: Ref<boolean>;
21
+ validationError: Ref<ValidationError | null>;
22
+
23
+ // Computed
24
+ isEditing: Ref<boolean>;
25
+ hasValidationError: Ref<boolean>;
26
+ highlightedContent: Ref<string>;
27
+ displayContent: Ref<string>;
28
+ charCount: Ref<number>;
29
+ isValid: Ref<boolean>;
30
+
31
+ // Methods
32
+ toggleEdit: () => void;
33
+ onContentEditableInput: (event: Event) => void;
34
+ onContentEditableBlur: () => void;
35
+ onKeyDown: (event: KeyboardEvent) => void;
36
+ syncEditableFromProp: (value: boolean) => void;
37
+ syncEditingContentFromValue: () => void;
38
+ updateEditingContentOnFormatChange: () => void;
39
+ }
40
+
41
+ /**
42
+ * Get cursor offset in plain text within a contenteditable element
43
+ */
44
+ function getCursorOffset(codeRef: HTMLPreElement | null): number {
45
+ const selection = window.getSelection();
46
+ if (!selection || !selection.rangeCount || !codeRef) return 0;
47
+
48
+ const range = selection.getRangeAt(0);
49
+ const preCaretRange = document.createRange();
50
+ preCaretRange.selectNodeContents(codeRef);
51
+ preCaretRange.setEnd(range.startContainer, range.startOffset);
52
+
53
+ // Count characters by walking text nodes
54
+ let offset = 0;
55
+ const walker = document.createTreeWalker(preCaretRange.commonAncestorContainer, NodeFilter.SHOW_TEXT);
56
+ let node = walker.nextNode();
57
+ while (node) {
58
+ if (preCaretRange.intersectsNode(node)) {
59
+ const nodeRange = document.createRange();
60
+ nodeRange.selectNodeContents(node);
61
+ if (preCaretRange.compareBoundaryPoints(Range.END_TO_END, nodeRange) >= 0) {
62
+ offset += node.textContent?.length || 0;
63
+ } else {
64
+ // Partial node - cursor is in this node
65
+ offset += range.startOffset;
66
+ break;
67
+ }
68
+ }
69
+ node = walker.nextNode();
70
+ }
71
+ return offset;
72
+ }
73
+
74
+ /**
75
+ * Set cursor to offset in plain text within a contenteditable element
76
+ */
77
+ function setCursorOffset(codeRef: HTMLPreElement | null, targetOffset: number): void {
78
+ if (!codeRef) return;
79
+
80
+ const selection = window.getSelection();
81
+ if (!selection) return;
82
+
83
+ let currentOffset = 0;
84
+ const walker = document.createTreeWalker(codeRef, NodeFilter.SHOW_TEXT);
85
+ let node = walker.nextNode();
86
+
87
+ while (node) {
88
+ const nodeLength = node.textContent?.length || 0;
89
+ if (currentOffset + nodeLength >= targetOffset) {
90
+ const range = document.createRange();
91
+ range.setStart(node, targetOffset - currentOffset);
92
+ range.collapse(true);
93
+ selection.removeAllRanges();
94
+ selection.addRange(range);
95
+ return;
96
+ }
97
+ currentOffset += nodeLength;
98
+ node = walker.nextNode();
99
+ }
100
+
101
+ // If offset not found, place at end
102
+ const range = document.createRange();
103
+ range.selectNodeContents(codeRef);
104
+ range.collapse(false);
105
+ selection.removeAllRanges();
106
+ selection.addRange(range);
107
+ }
108
+
109
+ /**
110
+ * Get current line info from cursor position
111
+ */
112
+ function getCurrentLineInfo(editingContent: string, codeRef: HTMLPreElement | null): { indent: string; lineContent: string } | null {
113
+ const text = editingContent;
114
+ if (!text) return { indent: "", lineContent: "" };
115
+
116
+ // Get cursor position in plain text
117
+ const cursorOffset = getCursorOffset(codeRef);
118
+
119
+ // Find the start of the current line (after the previous newline)
120
+ const textBeforeCursor = text.substring(0, cursorOffset);
121
+ const lastNewlineIndex = textBeforeCursor.lastIndexOf("\n");
122
+ const lineStart = lastNewlineIndex + 1;
123
+
124
+ // Get the content from line start to cursor
125
+ const lineContent = textBeforeCursor.substring(lineStart);
126
+
127
+ // Extract indentation (spaces/tabs at start of line)
128
+ const indentMatch = lineContent.match(/^[\t ]*/);
129
+ const indent = indentMatch ? indentMatch[0] : "";
130
+
131
+ return { indent, lineContent };
132
+ }
133
+
134
+ /**
135
+ * Calculate smart indentation based on context
136
+ */
137
+ function getSmartIndent(lineInfo: { indent: string; lineContent: string }, format: CodeFormat): string {
138
+ const { indent, lineContent } = lineInfo;
139
+ const trimmedLine = lineContent.trim();
140
+ const indentUnit = " "; // 2 spaces
141
+
142
+ if (format === "yaml") {
143
+ // After a key with colon (e.g., "key:" or "key: |" or "key: >")
144
+ if (trimmedLine.endsWith(":") || trimmedLine.match(/:\s*[|>][-+]?\s*$/)) {
145
+ return indent + indentUnit;
146
+ }
147
+ // After array item start (e.g., "- item" or "- key: value")
148
+ if (trimmedLine.startsWith("- ")) {
149
+ return indent + indentUnit;
150
+ }
151
+ // Just "- " alone means we want to continue with same array indentation
152
+ if (trimmedLine === "-") {
153
+ return indent;
154
+ }
155
+ } else if (format === "json") {
156
+ // After opening brace/bracket
157
+ if (trimmedLine.endsWith("{") || trimmedLine.endsWith("[")) {
158
+ return indent + indentUnit;
159
+ }
160
+ // After comma, maintain indentation
161
+ if (trimmedLine.endsWith(",")) {
162
+ return indent;
163
+ }
164
+ }
165
+
166
+ // Default: maintain current indentation
167
+ return indent;
168
+ }
169
+
170
+ /**
171
+ * Composable for CodeViewer editor functionality
172
+ */
173
+ export function useCodeViewerEditor(options: UseCodeViewerEditorOptions): UseCodeViewerEditorReturn {
174
+ const { codeRef, codeFormat, currentFormat, canEdit, editable, onEmitModelValue, onEmitEditable } = options;
175
+
176
+ // Debounce timeout handles
177
+ let validationTimeout: ReturnType<typeof setTimeout> | null = null;
178
+ let highlightTimeout: ReturnType<typeof setTimeout> | null = null;
179
+
180
+ // Local state
181
+ const internalEditable = ref(editable.value);
182
+ const editingContent = ref("");
183
+ const cachedHighlightedContent = ref("");
184
+ const isUserEditing = ref(false);
185
+ const validationError = ref<ValidationError | null>(null);
186
+
187
+ // Computed: has validation error
188
+ const hasValidationError = computed(() => validationError.value !== null);
189
+
190
+ // Computed: is currently in edit mode
191
+ const isEditing = computed(() => canEdit.value && internalEditable.value);
192
+
193
+ // Computed: display content
194
+ const displayContent = computed(() => {
195
+ if (isUserEditing.value) {
196
+ return editingContent.value;
197
+ }
198
+ return codeFormat.formattedContent.value;
199
+ });
200
+
201
+ // Computed: highlighted content with syntax highlighting
202
+ const highlightedContent = computed(() => {
203
+ if (isUserEditing.value) {
204
+ return cachedHighlightedContent.value;
205
+ }
206
+ const highlighted = highlightSyntax(displayContent.value, { format: currentFormat.value });
207
+ cachedHighlightedContent.value = highlighted;
208
+ return highlighted;
209
+ });
210
+
211
+ // Computed: is current content valid
212
+ const isValid = computed(() => {
213
+ if (hasValidationError.value) return false;
214
+ return codeFormat.isValid.value;
215
+ });
216
+
217
+ // Computed: character count
218
+ const charCount = computed(() => {
219
+ return displayContent.value?.length || 0;
220
+ });
221
+
222
+ // Sync internal editable state with prop
223
+ function syncEditableFromProp(value: boolean): void {
224
+ internalEditable.value = value;
225
+ }
226
+
227
+ // Sync editing content when external value changes
228
+ function syncEditingContentFromValue(): void {
229
+ if (!isUserEditing.value) {
230
+ editingContent.value = codeFormat.formattedContent.value;
231
+ }
232
+ }
233
+
234
+ // Update editing content when format changes
235
+ function updateEditingContentOnFormatChange(): void {
236
+ if (isEditing.value) {
237
+ editingContent.value = codeFormat.formattedContent.value;
238
+ }
239
+ }
240
+
241
+ // Debounced validation
242
+ function debouncedValidate(): void {
243
+ if (validationTimeout) {
244
+ clearTimeout(validationTimeout);
245
+ }
246
+ validationTimeout = setTimeout(() => {
247
+ validationError.value = codeFormat.validateWithError(editingContent.value, currentFormat.value);
248
+ }, 300);
249
+ }
250
+
251
+ // Debounced highlighting
252
+ function debouncedHighlight(): void {
253
+ if (highlightTimeout) {
254
+ clearTimeout(highlightTimeout);
255
+ }
256
+ highlightTimeout = setTimeout(() => {
257
+ if (!codeRef.value || !isEditing.value) return;
258
+
259
+ // Save cursor position
260
+ const cursorOffset = getCursorOffset(codeRef.value);
261
+
262
+ // Re-apply highlighting
263
+ codeRef.value.innerHTML = highlightSyntax(editingContent.value, { format: currentFormat.value });
264
+
265
+ // Restore cursor position
266
+ setCursorOffset(codeRef.value, cursorOffset);
267
+ }, 300);
268
+ }
269
+
270
+ // Toggle edit mode
271
+ function toggleEdit(): void {
272
+ internalEditable.value = !internalEditable.value;
273
+ onEmitEditable(internalEditable.value);
274
+
275
+ if (internalEditable.value) {
276
+ // Entering edit mode - initialize editing content and clear any previous error
277
+ editingContent.value = codeFormat.formattedContent.value;
278
+ validationError.value = null;
279
+ // Set content imperatively with syntax highlighting and focus
280
+ nextTick(() => {
281
+ if (codeRef.value) {
282
+ codeRef.value.innerHTML = highlightSyntax(editingContent.value, { format: currentFormat.value });
283
+ codeRef.value.focus();
284
+ // Move cursor to end
285
+ const selection = window.getSelection();
286
+ const range = document.createRange();
287
+ range.selectNodeContents(codeRef.value);
288
+ range.collapse(false);
289
+ selection?.removeAllRanges();
290
+ selection?.addRange(range);
291
+ }
292
+ });
293
+ } else {
294
+ // Exiting edit mode - clear validation error
295
+ validationError.value = null;
296
+ }
297
+ }
298
+
299
+ // Handle contenteditable input
300
+ function onContentEditableInput(event: Event): void {
301
+ if (!isEditing.value) return;
302
+
303
+ isUserEditing.value = true;
304
+ const target = event.target as HTMLElement;
305
+ editingContent.value = target.innerText || "";
306
+
307
+ debouncedValidate();
308
+ debouncedHighlight();
309
+ }
310
+
311
+ // Handle blur - emit changes and re-apply highlighting
312
+ function onContentEditableBlur(): void {
313
+ if (!isEditing.value || !isUserEditing.value) return;
314
+
315
+ isUserEditing.value = false;
316
+
317
+ // Clear pending timeouts and process immediately
318
+ if (validationTimeout) {
319
+ clearTimeout(validationTimeout);
320
+ validationTimeout = null;
321
+ }
322
+ if (highlightTimeout) {
323
+ clearTimeout(highlightTimeout);
324
+ highlightTimeout = null;
325
+ }
326
+ validationError.value = codeFormat.validateWithError(editingContent.value, currentFormat.value);
327
+
328
+ // Parse and emit the value
329
+ const parsed = codeFormat.parse(editingContent.value);
330
+ if (parsed) {
331
+ onEmitModelValue(parsed);
332
+ } else {
333
+ onEmitModelValue(editingContent.value);
334
+ }
335
+
336
+ // Re-apply syntax highlighting after editing
337
+ if (codeRef.value) {
338
+ codeRef.value.innerHTML = highlightSyntax(editingContent.value, { format: currentFormat.value });
339
+ }
340
+ }
341
+
342
+ // Handle keyboard shortcuts in edit mode
343
+ function onKeyDown(event: KeyboardEvent): void {
344
+ if (!isEditing.value) return;
345
+
346
+ // Enter key - smart indentation
347
+ if (event.key === "Enter") {
348
+ const lineInfo = getCurrentLineInfo(editingContent.value, codeRef.value);
349
+ if (lineInfo) {
350
+ event.preventDefault();
351
+ const smartIndent = getSmartIndent(lineInfo, currentFormat.value);
352
+
353
+ const selection = window.getSelection();
354
+ if (selection && selection.rangeCount > 0) {
355
+ const range = selection.getRangeAt(0);
356
+ range.deleteContents();
357
+ const textNode = document.createTextNode("\n" + smartIndent);
358
+ range.insertNode(textNode);
359
+ range.setStartAfter(textNode);
360
+ range.setEndAfter(textNode);
361
+ selection.removeAllRanges();
362
+ selection.addRange(range);
363
+
364
+ codeRef.value?.dispatchEvent(new Event("input", { bubbles: true }));
365
+ }
366
+ }
367
+ }
368
+
369
+ // Tab key - insert spaces instead of moving focus
370
+ if (event.key === "Tab") {
371
+ event.preventDefault();
372
+ document.execCommand("insertText", false, " ");
373
+ }
374
+
375
+ // Escape - exit edit mode
376
+ if (event.key === "Escape") {
377
+ event.preventDefault();
378
+ onContentEditableBlur();
379
+ toggleEdit();
380
+ }
381
+
382
+ // Ctrl/Cmd + S - save without exiting
383
+ if ((event.ctrlKey || event.metaKey) && event.key === "s") {
384
+ event.preventDefault();
385
+ onContentEditableBlur();
386
+ }
387
+ }
388
+
389
+ // Cleanup timeouts on unmount
390
+ onUnmounted(() => {
391
+ if (validationTimeout) clearTimeout(validationTimeout);
392
+ if (highlightTimeout) clearTimeout(highlightTimeout);
393
+ });
394
+
395
+ return {
396
+ // State
397
+ internalEditable,
398
+ editingContent,
399
+ cachedHighlightedContent,
400
+ isUserEditing,
401
+ validationError,
402
+
403
+ // Computed
404
+ isEditing,
405
+ hasValidationError,
406
+ highlightedContent,
407
+ displayContent,
408
+ charCount,
409
+ isValid,
410
+
411
+ // Methods
412
+ toggleEdit,
413
+ onContentEditableInput,
414
+ onContentEditableBlur,
415
+ onKeyDown,
416
+ syncEditableFromProp,
417
+ syncEditingContentFromValue,
418
+ updateEditingContentOnFormatChange
419
+ };
420
+ }
@@ -0,0 +1,119 @@
1
+ import { computed, ComputedRef, Ref } from "vue";
2
+ import * as fileHelpers from "../helpers/filePreviewHelpers";
3
+ import { getMimeType, getOptimizedUrl, isExternalLinkFile } from "../helpers/filePreviewHelpers";
4
+ import { UploadedFile } from "../types";
5
+
6
+ export interface FileTranscode {
7
+ status: "Complete" | "Pending" | "In Progress" | "Timeout";
8
+ progress: number;
9
+ estimate_ms: number;
10
+ started_at: string;
11
+ completed_at: string;
12
+ message?: string;
13
+ }
14
+
15
+ export interface UseFilePreviewOptions {
16
+ file: Ref<UploadedFile | null | undefined>;
17
+ src?: Ref<string>;
18
+ }
19
+
20
+ export interface UseFilePreviewReturn {
21
+ computedImage: ComputedRef<UploadedFile | null>;
22
+ filename: ComputedRef<string>;
23
+ mimeType: ComputedRef<string>;
24
+ isImage: ComputedRef<boolean>;
25
+ isVideo: ComputedRef<boolean>;
26
+ isPdf: ComputedRef<boolean>;
27
+ isExternalLink: ComputedRef<boolean>;
28
+ previewUrl: ComputedRef<string>;
29
+ thumbUrl: ComputedRef<string>;
30
+ isPreviewable: ComputedRef<boolean>;
31
+ hasMetadata: ComputedRef<boolean>;
32
+ metadataKeyCount: ComputedRef<number>;
33
+ filteredMetadata: ComputedRef<Record<string, unknown>>;
34
+ hasTranscodes: ComputedRef<boolean>;
35
+ transcodingStatus: ComputedRef<(FileTranscode & { message: string }) | null>;
36
+ }
37
+
38
+ /**
39
+ * Composable for file preview computed properties
40
+ */
41
+ export function useFilePreview(options: UseFilePreviewOptions): UseFilePreviewReturn {
42
+ const { file, src } = options;
43
+
44
+ const computedImage: ComputedRef<UploadedFile | null> = computed(() => {
45
+ if (file.value) {
46
+ return file.value;
47
+ } else if (src?.value) {
48
+ return {
49
+ id: src.value,
50
+ url: src.value,
51
+ type: "image/" + src.value.split(".").pop()?.toLowerCase(),
52
+ name: "",
53
+ size: 0,
54
+ __type: "BrowserFile"
55
+ };
56
+ }
57
+ return null;
58
+ });
59
+
60
+ const filename = computed(() => computedImage.value?.name || computedImage.value?.filename || "");
61
+ const mimeType = computed(() => computedImage.value ? getMimeType(computedImage.value) : "");
62
+ const isImage = computed(() => computedImage.value ? fileHelpers.isImage(computedImage.value) : false);
63
+ const isVideo = computed(() => computedImage.value ? fileHelpers.isVideo(computedImage.value) : false);
64
+ const isPdf = computed(() => computedImage.value ? fileHelpers.isPdf(computedImage.value) : false);
65
+ const isExternalLink = computed(() => computedImage.value ? isExternalLinkFile(computedImage.value) : false);
66
+ const previewUrl = computed(() => computedImage.value ? getOptimizedUrl(computedImage.value) : "");
67
+ const thumbUrl = computed(() => computedImage.value?.thumb?.url || "");
68
+ const isPreviewable = computed(() => !!thumbUrl.value || isVideo.value || isImage.value);
69
+
70
+ const hasMetadata = computed(() => {
71
+ if (!file.value?.meta) return false;
72
+ const metaKeys = Object.keys(file.value.meta).filter(k => k !== "transcodes");
73
+ return metaKeys.length > 0;
74
+ });
75
+
76
+ const metadataKeyCount = computed(() => {
77
+ if (!file.value?.meta) return 0;
78
+ return Object.keys(file.value.meta).filter(k => k !== "transcodes").length;
79
+ });
80
+
81
+ const filteredMetadata = computed(() => {
82
+ if (!file.value?.meta) return {};
83
+ const { transcodes, ...rest } = file.value.meta;
84
+ return rest;
85
+ });
86
+
87
+ const hasTranscodes = computed(() => (file.value?.transcodes?.length || 0) > 0);
88
+
89
+ const transcodingStatus = computed(() => {
90
+ const metaTranscodes: Record<string, FileTranscode> = file.value?.meta?.transcodes || {};
91
+
92
+ for (const transcodeName of Object.keys(metaTranscodes)) {
93
+ const transcode = metaTranscodes[transcodeName];
94
+ if (!["Complete", "Timeout"].includes(transcode?.status)) {
95
+ return { ...transcode, message: `${transcodeName} ${transcode.status}` };
96
+ }
97
+ }
98
+
99
+ return null;
100
+ });
101
+
102
+ return {
103
+ computedImage,
104
+ filename,
105
+ mimeType,
106
+ isImage,
107
+ isVideo,
108
+ isPdf,
109
+ isExternalLink,
110
+ previewUrl,
111
+ thumbUrl,
112
+ isPreviewable,
113
+ hasMetadata,
114
+ metadataKeyCount,
115
+ filteredMetadata,
116
+ hasTranscodes,
117
+ transcodingStatus
118
+ };
119
+ }
@@ -0,0 +1,68 @@
1
+ import { onMounted, Ref, ref, watch } from "vue";
2
+ import { danxOptions } from "../config";
3
+ import { UploadedFile } from "../types";
4
+
5
+ export interface UseTranscodeLoaderOptions {
6
+ file: Ref<UploadedFile | null | undefined>;
7
+ }
8
+
9
+ export interface UseTranscodeLoaderReturn {
10
+ isLoading: Ref<boolean>;
11
+ loadTranscodes: () => Promise<void>;
12
+ }
13
+
14
+ /**
15
+ * Composable for loading transcodes for a file
16
+ * Automatically loads transcodes on mount and when the file changes
17
+ */
18
+ export function useTranscodeLoader(options: UseTranscodeLoaderOptions): UseTranscodeLoaderReturn {
19
+ const { file } = options;
20
+ const isLoading = ref(false);
21
+
22
+ function shouldLoadTranscodes(): boolean {
23
+ if (!file.value?.id) return false;
24
+ if (isLoading.value) return false;
25
+ if (!danxOptions.value.fileUpload?.refreshFile) return false;
26
+
27
+ // Only load if transcodes is explicitly null, undefined, or an empty array
28
+ const transcodes = file.value.transcodes;
29
+ return transcodes === null || transcodes === undefined || (Array.isArray(transcodes) && transcodes.length === 0);
30
+ }
31
+
32
+ async function loadTranscodes() {
33
+ if (!shouldLoadTranscodes()) return;
34
+
35
+ isLoading.value = true;
36
+
37
+ try {
38
+ const refreshFile = danxOptions.value.fileUpload?.refreshFile;
39
+ if (refreshFile && file.value?.id) {
40
+ const refreshedFile = await refreshFile(file.value.id);
41
+
42
+ // Update the file object with the loaded transcodes
43
+ if (refreshedFile.transcodes && file.value) {
44
+ file.value.transcodes = refreshedFile.transcodes;
45
+ }
46
+ }
47
+ } catch (error) {
48
+ console.error("Failed to load transcodes:", error);
49
+ } finally {
50
+ isLoading.value = false;
51
+ }
52
+ }
53
+
54
+ // Load transcodes when component mounts
55
+ onMounted(() => {
56
+ loadTranscodes();
57
+ });
58
+
59
+ // Watch for file changes and reload transcodes if needed
60
+ watch(() => file.value?.id, () => {
61
+ loadTranscodes();
62
+ });
63
+
64
+ return {
65
+ isLoading,
66
+ loadTranscodes
67
+ };
68
+ }