quasar-ui-danx 0.4.92 → 0.4.93
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/danx.es.js +7133 -6404
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +90 -90
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +123 -136
- package/src/components/Utility/Files/CarouselHeader.vue +80 -0
- package/src/components/Utility/Files/FilePreview.vue +76 -21
- package/src/components/Utility/Files/FileRenderer.vue +111 -0
- package/src/components/Utility/Files/ThumbnailStrip.vue +64 -0
- package/src/components/Utility/Files/TranscodeNavigator.vue +175 -0
- package/src/components/Utility/Files/VirtualCarousel.vue +237 -0
- package/src/components/Utility/Files/index.ts +5 -0
- package/src/components/Utility/Widgets/LabelPillWidget.vue +10 -0
- package/src/composables/index.ts +4 -0
- package/src/composables/useFileNavigation.ts +129 -0
- package/src/composables/useKeyboardNavigation.ts +58 -0
- package/src/composables/useThumbnailScroll.ts +43 -0
- package/src/composables/useVirtualCarousel.ts +93 -0
- package/src/helpers/filePreviewHelpers.ts +107 -0
- package/src/helpers/index.ts +1 -0
- package/src/types/files.d.ts +38 -0
- package/src/types/widgets.d.ts +1 -1
- package/src/vue-plugin.ts +1 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { nextTick, Ref, watch } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface UseThumbnailScrollOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Container element that holds the thumbnails
|
|
6
|
+
*/
|
|
7
|
+
containerRef: Ref<HTMLElement | null>;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Current active index
|
|
11
|
+
*/
|
|
12
|
+
currentIndex: Ref<number>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional CSS selector for thumbnail elements (defaults to '.thumbnail')
|
|
16
|
+
*/
|
|
17
|
+
thumbnailSelector?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Composable for auto-scrolling thumbnails into view
|
|
22
|
+
* Watches the current index and scrolls the active thumbnail into the visible area
|
|
23
|
+
*/
|
|
24
|
+
export function useThumbnailScroll(options: UseThumbnailScrollOptions) {
|
|
25
|
+
const { containerRef, currentIndex, thumbnailSelector = ".thumbnail" } = options;
|
|
26
|
+
|
|
27
|
+
watch(currentIndex, (newIndex) => {
|
|
28
|
+
nextTick(() => {
|
|
29
|
+
const thumbnails = containerRef.value?.querySelectorAll(thumbnailSelector);
|
|
30
|
+
const activeThumbnail = thumbnails?.[newIndex] as HTMLElement;
|
|
31
|
+
|
|
32
|
+
if (activeThumbnail && containerRef.value) {
|
|
33
|
+
activeThumbnail.scrollIntoView({
|
|
34
|
+
behavior: "smooth",
|
|
35
|
+
block: "nearest",
|
|
36
|
+
inline: "center"
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {};
|
|
43
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { computed, Ref, shallowRef, watch } from "vue";
|
|
2
|
+
import { UploadedFile, VirtualCarouselSlide } from "../types";
|
|
3
|
+
|
|
4
|
+
const BUFFER_SIZE = 2; // Render current slide ± 2 slides
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Composable for managing virtual carousel rendering
|
|
8
|
+
* Only renders slides within buffer window for performance with large file sets
|
|
9
|
+
*/
|
|
10
|
+
export function useVirtualCarousel(
|
|
11
|
+
files: Ref<UploadedFile[]>,
|
|
12
|
+
currentIndex: Ref<number>
|
|
13
|
+
) {
|
|
14
|
+
// Shallow ref to avoid deep reactivity for performance
|
|
15
|
+
const visibleSlides = shallowRef<VirtualCarouselSlide[]>([]);
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Calculate which slides should be visible based on current index
|
|
19
|
+
*/
|
|
20
|
+
const visibleIndices = computed(() => {
|
|
21
|
+
const start = Math.max(0, currentIndex.value - BUFFER_SIZE);
|
|
22
|
+
const end = Math.min(files.value.length - 1, currentIndex.value + BUFFER_SIZE);
|
|
23
|
+
const indices: number[] = [];
|
|
24
|
+
|
|
25
|
+
for (let i = start; i <= end; i++) {
|
|
26
|
+
indices.push(i);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return indices;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Update visible slides based on current index
|
|
34
|
+
*/
|
|
35
|
+
function updateVisibleSlides() {
|
|
36
|
+
const indices = visibleIndices.value;
|
|
37
|
+
const newSlides: VirtualCarouselSlide[] = [];
|
|
38
|
+
|
|
39
|
+
for (const index of indices) {
|
|
40
|
+
if (index >= 0 && index < files.value.length) {
|
|
41
|
+
newSlides.push({
|
|
42
|
+
file: files.value[index],
|
|
43
|
+
index,
|
|
44
|
+
isActive: index === currentIndex.value,
|
|
45
|
+
isVisible: true
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
visibleSlides.value = newSlides;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Check if a slide at given index is visible
|
|
55
|
+
*/
|
|
56
|
+
function isSlideVisible(index: number): boolean {
|
|
57
|
+
return visibleIndices.value.includes(index);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get slide by index (returns null if not visible)
|
|
62
|
+
*/
|
|
63
|
+
function getSlide(index: number): VirtualCarouselSlide | null {
|
|
64
|
+
return visibleSlides.value.find(s => s.index === index) || null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get slides that should be preloaded (visible + buffer)
|
|
69
|
+
*/
|
|
70
|
+
const preloadIndices = computed(() => {
|
|
71
|
+
const start = Math.max(0, currentIndex.value - BUFFER_SIZE - 1);
|
|
72
|
+
const end = Math.min(files.value.length - 1, currentIndex.value + BUFFER_SIZE + 1);
|
|
73
|
+
const indices: number[] = [];
|
|
74
|
+
|
|
75
|
+
for (let i = start; i <= end; i++) {
|
|
76
|
+
indices.push(i);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return indices;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Watch for changes to update visible slides
|
|
83
|
+
watch([currentIndex, files], updateVisibleSlides, { immediate: true });
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
visibleSlides,
|
|
87
|
+
visibleIndices,
|
|
88
|
+
preloadIndices,
|
|
89
|
+
isSlideVisible,
|
|
90
|
+
getSlide,
|
|
91
|
+
updateVisibleSlides
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { UploadedFile } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Placeholder constants for file preview
|
|
5
|
+
*/
|
|
6
|
+
export const FILE_PLACEHOLDERS = {
|
|
7
|
+
IMAGE: "https://placehold.co/64x64?text=?",
|
|
8
|
+
VIDEO_SVG: `data:image/svg+xml;base64,${btoa(
|
|
9
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white"><path d="M8 5v14l11-7z"/></svg>'
|
|
10
|
+
)}`
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* MIME type regex patterns for file type detection
|
|
15
|
+
*/
|
|
16
|
+
export const MIME_TYPES = {
|
|
17
|
+
IMAGE: /^image\//,
|
|
18
|
+
VIDEO: /^video\//,
|
|
19
|
+
TEXT: /^text\//,
|
|
20
|
+
PDF: /^application\/pdf/
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check if file is an image
|
|
25
|
+
*/
|
|
26
|
+
export function isImage(file: UploadedFile): boolean {
|
|
27
|
+
const mimeType = getMimeType(file);
|
|
28
|
+
return MIME_TYPES.IMAGE.test(mimeType);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if file is a video
|
|
33
|
+
*/
|
|
34
|
+
export function isVideo(file: UploadedFile): boolean {
|
|
35
|
+
const mimeType = getMimeType(file);
|
|
36
|
+
return MIME_TYPES.VIDEO.test(mimeType);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if file is a text file
|
|
41
|
+
*/
|
|
42
|
+
export function isText(file: UploadedFile): boolean {
|
|
43
|
+
const mimeType = getMimeType(file);
|
|
44
|
+
return MIME_TYPES.TEXT.test(mimeType);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if file is a PDF
|
|
49
|
+
*/
|
|
50
|
+
export function isPdf(file: UploadedFile): boolean {
|
|
51
|
+
const mimeType = getMimeType(file);
|
|
52
|
+
return MIME_TYPES.PDF.test(mimeType);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get the MIME type from a file
|
|
57
|
+
*/
|
|
58
|
+
export function getMimeType(file: UploadedFile): string {
|
|
59
|
+
return file.mime || file.type || "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the file extension from a filename
|
|
64
|
+
*/
|
|
65
|
+
export function getFileExtension(filename: string): string {
|
|
66
|
+
return filename.split(".").pop()?.toLowerCase() || "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get the preview URL for a file
|
|
71
|
+
* Priority: optimized > blobUrl > url > thumb > empty string
|
|
72
|
+
*/
|
|
73
|
+
export function getPreviewUrl(file: UploadedFile): string {
|
|
74
|
+
if (file.optimized?.url) {
|
|
75
|
+
return file.optimized.url;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (isImage(file)) {
|
|
79
|
+
return file.blobUrl || file.url || "";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return file.thumb?.url || "";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Get the thumbnail URL for a file
|
|
87
|
+
* For videos without thumbs, returns a play icon SVG
|
|
88
|
+
*/
|
|
89
|
+
export function getThumbUrl(file: UploadedFile): string {
|
|
90
|
+
if (file.thumb?.url) {
|
|
91
|
+
return file.thumb.url;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (isVideo(file)) {
|
|
95
|
+
return FILE_PLACEHOLDERS.VIDEO_SVG;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return getPreviewUrl(file) || FILE_PLACEHOLDERS.IMAGE;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get the optimized URL for a file (used for preview)
|
|
103
|
+
* Priority: optimized > blobUrl > url
|
|
104
|
+
*/
|
|
105
|
+
export function getOptimizedUrl(file: UploadedFile): string {
|
|
106
|
+
return file.optimized?.url || file.blobUrl || file.url || "";
|
|
107
|
+
}
|
package/src/helpers/index.ts
CHANGED
package/src/types/files.d.ts
CHANGED
|
@@ -34,6 +34,23 @@ export interface UploadedFile extends TypedObject {
|
|
|
34
34
|
meta?: AnyObject;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export interface StoredFile extends TypedObject {
|
|
38
|
+
filename: string;
|
|
39
|
+
url: string;
|
|
40
|
+
mime: string;
|
|
41
|
+
size?: number;
|
|
42
|
+
location?: {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
};
|
|
46
|
+
meta?: AnyObject;
|
|
47
|
+
page_number?: number;
|
|
48
|
+
is_transcoding?: boolean;
|
|
49
|
+
thumb?: StoredFile;
|
|
50
|
+
optimized?: StoredFile;
|
|
51
|
+
transcodes?: StoredFile[];
|
|
52
|
+
}
|
|
53
|
+
|
|
37
54
|
export interface FileUploadCompleteCallbackParams {
|
|
38
55
|
file?: UploadedFile | null;
|
|
39
56
|
uploadedFile?: UploadedFile | null;
|
|
@@ -60,3 +77,24 @@ export type FileUploadProgressCallback = (params: FileUploadProgressCallbackPara
|
|
|
60
77
|
export type FileUploadErrorCallback = (params: FileUploadErrorCallbackParams) => void
|
|
61
78
|
export type OnFilesChangeCallback = (files: UploadedFile[]) => void;
|
|
62
79
|
export type VoidCallback = () => void;
|
|
80
|
+
|
|
81
|
+
// File Navigation Types
|
|
82
|
+
export interface FileNavigationState {
|
|
83
|
+
currentFile: UploadedFile | null;
|
|
84
|
+
relatedFiles: UploadedFile[];
|
|
85
|
+
parentStack: FileNavigationParent[];
|
|
86
|
+
currentIndex: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface FileNavigationParent {
|
|
90
|
+
file: UploadedFile;
|
|
91
|
+
relatedFiles: UploadedFile[];
|
|
92
|
+
index: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface VirtualCarouselSlide {
|
|
96
|
+
file: UploadedFile;
|
|
97
|
+
index: number;
|
|
98
|
+
isActive: boolean;
|
|
99
|
+
isVisible: boolean;
|
|
100
|
+
}
|
package/src/types/widgets.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export interface LabelPillWidgetProps {
|
|
2
2
|
label?: string | number;
|
|
3
3
|
size?: "xs" | "sm" | "md" | "lg";
|
|
4
|
-
color?: "sky" | "green" | "red" | "amber" | "yellow" | "blue" | "slate" | "slate-mid" | "gray" | "none";
|
|
4
|
+
color?: "sky" | "green" | "red" | "amber" | "yellow" | "blue" | "purple" | "slate" | "slate-mid" | "gray" | "emerald" | "orange" | "lime" | "teal" | "cyan" | "rose" | "indigo" | "violet" | "fuchsia" | "none";
|
|
5
5
|
}
|