quasar-ui-danx 0.4.10 → 0.4.13
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/danx.es.js +12389 -7677
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +137 -7
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/index.d.ts +7 -0
- package/index.ts +1 -0
- package/package.json +10 -4
- package/src/components/ActionTable/ActionMenu.vue +26 -31
- package/src/components/ActionTable/ActionTable.vue +4 -1
- package/src/components/ActionTable/Columns/ActionTableColumn.vue +14 -6
- package/src/components/ActionTable/Columns/ActionTableHeaderColumn.vue +63 -42
- package/src/components/ActionTable/Form/ActionForm.vue +55 -0
- package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +11 -5
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +1 -0
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +1 -1
- package/src/components/ActionTable/Form/Fields/NumberField.vue +0 -1
- package/src/components/ActionTable/Form/RenderedForm.vue +57 -50
- package/src/components/ActionTable/Form/index.ts +1 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +3 -3
- package/src/components/ActionTable/TableSummaryRow.vue +48 -37
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +2 -2
- package/src/components/ActionTable/listControls.ts +3 -2
- package/src/components/PanelsDrawer/PanelsDrawer.vue +15 -5
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +3 -1
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +17 -4
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +30 -5
- package/src/components/Utility/Files/FilePreview.vue +72 -12
- package/src/components/Utility/Popovers/PopoverMenu.vue +34 -29
- package/src/components/Utility/Tools/RenderVnode.vue +5 -1
- package/src/config/index.ts +2 -1
- package/src/helpers/FileUpload.ts +59 -8
- package/src/helpers/actions.ts +27 -27
- package/src/helpers/date.ts +2 -2
- package/src/helpers/download.ts +8 -2
- package/src/helpers/formats.ts +52 -5
- package/src/helpers/multiFileUpload.ts +6 -4
- package/src/helpers/objectStore.ts +14 -17
- package/src/helpers/request.ts +12 -0
- package/src/helpers/singleFileUpload.ts +63 -55
- package/src/helpers/utils.ts +12 -3
- package/src/index.ts +1 -0
- package/src/styles/danx.scss +5 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/themes/danx/action-table.scss +24 -13
- package/src/types/actions.d.ts +16 -7
- package/src/types/controls.d.ts +4 -4
- package/src/types/files.d.ts +10 -5
- package/src/types/forms.d.ts +19 -1
- package/src/types/index.d.ts +0 -1
- package/src/types/requests.d.ts +2 -0
- package/src/types/tables.d.ts +28 -22
- package/src/{vue-plugin.js → vue-plugin.ts} +5 -4
- package/tsconfig.json +1 -0
- package/types/index.d.ts +2 -0
@@ -68,10 +68,10 @@
|
|
68
68
|
</template>
|
69
69
|
<script setup lang="ts">
|
70
70
|
import { computed } from "vue";
|
71
|
-
import { ActionController,
|
71
|
+
import { ActionController, ActionPanel, FilterGroup, ResourceAction, TableColumn } from "../../../types";
|
72
72
|
import { PanelsDrawer } from "../../PanelsDrawer";
|
73
73
|
import { PreviousNextControls } from "../../Utility";
|
74
|
-
import ActionTable from "../ActionTable";
|
74
|
+
import ActionTable from "../ActionTable.vue";
|
75
75
|
import { CollapsableFiltersSidebar } from "../Filters";
|
76
76
|
import { ActionToolbar } from "../Toolbars";
|
77
77
|
|
@@ -83,7 +83,7 @@ const props = defineProps<{
|
|
83
83
|
columns: TableColumn[];
|
84
84
|
filters?: FilterGroup[];
|
85
85
|
panels?: ActionPanel[];
|
86
|
-
actions?:
|
86
|
+
actions?: ResourceAction[];
|
87
87
|
exporter?: () => Promise<void>;
|
88
88
|
panelTitleField?: string;
|
89
89
|
tableClass?: string;
|
@@ -4,8 +4,8 @@
|
|
4
4
|
:class="{'has-selection': selectedCount, 'is-loading': loading}"
|
5
5
|
>
|
6
6
|
<QTd
|
7
|
-
:colspan="
|
8
|
-
class="dx-table-summary-td transition-all"
|
7
|
+
:colspan="colspan"
|
8
|
+
class="dx-table-summary-td dx-table-summary-count transition-all"
|
9
9
|
:class="{'has-selection': selectedCount}"
|
10
10
|
>
|
11
11
|
<div class="flex flex-nowrap items-center">
|
@@ -36,60 +36,71 @@
|
|
36
36
|
<QTd
|
37
37
|
v-for="column in summaryColumns"
|
38
38
|
:key="column.name"
|
39
|
-
:align="column.align || '
|
39
|
+
:align="column.align || 'right'"
|
40
|
+
:class="column.summaryClass"
|
41
|
+
class="dx-table-summary-fd"
|
40
42
|
>
|
41
|
-
<
|
43
|
+
<div
|
44
|
+
v-if="summary"
|
45
|
+
:class="{'dx-summary-column-link': column.onClick}"
|
46
|
+
>
|
42
47
|
{{ formatValue(column) }}
|
43
|
-
</
|
48
|
+
</div>
|
44
49
|
</QTd>
|
45
50
|
</QTr>
|
46
51
|
</template>
|
47
|
-
<script setup>
|
52
|
+
<script setup lang="ts">
|
48
53
|
import { XCircleIcon as ClearIcon } from "@heroicons/vue/solid";
|
49
54
|
import { QSpinner, QTd, QTr } from "quasar";
|
50
55
|
import { computed } from "vue";
|
51
56
|
import { fNumber } from "../../helpers";
|
57
|
+
import { TableColumn } from "../../types";
|
58
|
+
|
59
|
+
interface TableSummaryRowProps {
|
60
|
+
loading: boolean;
|
61
|
+
label?: string;
|
62
|
+
selectedLabel?: string;
|
63
|
+
selectedCount?: number;
|
64
|
+
itemCount?: number;
|
65
|
+
summary?: Record<string, any> | null;
|
66
|
+
columns: TableColumn[];
|
67
|
+
stickyColspan?: number;
|
68
|
+
}
|
52
69
|
|
53
70
|
defineEmits(["clear"]);
|
54
|
-
const props = defineProps({
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
},
|
76
|
-
columns: {
|
77
|
-
type: Array,
|
78
|
-
required: true
|
79
|
-
},
|
80
|
-
stickyColspan: {
|
81
|
-
type: Number,
|
82
|
-
default: 2
|
71
|
+
const props = withDefaults(defineProps<TableSummaryRowProps>(), {
|
72
|
+
label: "Rows",
|
73
|
+
selectedLabel: "Selected",
|
74
|
+
selectedCount: 0,
|
75
|
+
itemCount: 0,
|
76
|
+
summary: null,
|
77
|
+
stickyColspan: null
|
78
|
+
});
|
79
|
+
|
80
|
+
// Allow the colspan for the first summary column w/ count + label to extend out to the first column with summary data
|
81
|
+
// (ie: take up as much room as possible without affecting the summary columns)
|
82
|
+
const colspan = computed(() => {
|
83
|
+
if (props.stickyColspan) return props.stickyColspan;
|
84
|
+
|
85
|
+
if (props.summary) {
|
86
|
+
for (let i = 0; i < props.columns.length; i++) {
|
87
|
+
const fieldName = props.columns[i].field || props.columns[i].name;
|
88
|
+
if (props.summary[fieldName]) {
|
89
|
+
return i + 1;
|
90
|
+
}
|
91
|
+
}
|
83
92
|
}
|
93
|
+
|
94
|
+
return props.columns.length + 1;
|
84
95
|
});
|
85
96
|
|
86
97
|
const summaryColumns = computed(() => {
|
87
98
|
// The sticky columns are where we display the selection count and should not be included in the summary columns
|
88
|
-
return props.columns.slice(
|
99
|
+
return props.columns.slice(colspan.value - 1);
|
89
100
|
});
|
90
101
|
|
91
102
|
function formatValue(column) {
|
92
|
-
const value = props.summary[column.name];
|
103
|
+
const value = props.summary && props.summary[column.name];
|
93
104
|
if (value === undefined) return "";
|
94
105
|
|
95
106
|
if (column.format) {
|
@@ -30,14 +30,14 @@
|
|
30
30
|
</div>
|
31
31
|
</template>
|
32
32
|
<script setup lang="ts">
|
33
|
-
import {
|
33
|
+
import { ActionTargetItem, AnyObject, ResourceAction } from "../../../types";
|
34
34
|
import { ExportButton, RefreshButton } from "../../Utility";
|
35
35
|
import ActionMenu from "../ActionMenu";
|
36
36
|
|
37
37
|
defineEmits(["refresh"]);
|
38
38
|
defineProps<{
|
39
39
|
title?: string,
|
40
|
-
actions?:
|
40
|
+
actions?: ResourceAction[],
|
41
41
|
actionTarget?: ActionTargetItem[],
|
42
42
|
refreshButton?: boolean,
|
43
43
|
loading?: boolean,
|
@@ -317,7 +317,8 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
317
317
|
// (ie: tasks, verifications, creatives, etc.)
|
318
318
|
if (options.routes.details) {
|
319
319
|
watch(() => activeItem.value, async (newItem, oldItem) => {
|
320
|
-
|
320
|
+
// Note we want a loose comparison in case it's a string vs int for the ID
|
321
|
+
if (newItem?.id && oldItem?.id != newItem.id) {
|
321
322
|
await getActiveItemDetails();
|
322
323
|
}
|
323
324
|
});
|
@@ -328,7 +329,7 @@ export function useListControls(name: string, options: ListControlsOptions): Act
|
|
328
329
|
*/
|
329
330
|
function activatePanel(item: ActionTargetItem | null, panel: string = "") {
|
330
331
|
// If we're already on the correct item and panel, don't do anything
|
331
|
-
if (item?.id
|
332
|
+
if (item?.id == activeItem.value?.id && panel === activePanel.value) return;
|
332
333
|
|
333
334
|
setActiveItem(item);
|
334
335
|
activePanel.value = panel;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
<template>
|
2
2
|
<ContentDrawer
|
3
3
|
position="right"
|
4
|
-
|
4
|
+
show
|
5
5
|
overlay
|
6
6
|
content-class="h-full"
|
7
7
|
class="dx-panels-drawer"
|
@@ -13,7 +13,13 @@
|
|
13
13
|
<div class="dx-panels-drawer-header flex items-center px-6 py-4">
|
14
14
|
<div class="flex-grow">
|
15
15
|
<slot name="header">
|
16
|
-
<h2
|
16
|
+
<h2 v-if="title">
|
17
|
+
{{ title }}
|
18
|
+
</h2>
|
19
|
+
<div v-if="!activeItem">
|
20
|
+
Loading
|
21
|
+
<QSpinnerHourglass />
|
22
|
+
</div>
|
17
23
|
</slot>
|
18
24
|
</div>
|
19
25
|
<div
|
@@ -32,10 +38,14 @@
|
|
32
38
|
</div>
|
33
39
|
</div>
|
34
40
|
<div class="dx-panels-drawer-body flex-grow overflow-hidden h-full">
|
35
|
-
<div
|
41
|
+
<div
|
42
|
+
v-if="activeItem.__timestamp > 0"
|
43
|
+
class="flex items-stretch flex-nowrap h-full"
|
44
|
+
>
|
36
45
|
<PanelsDrawerTabs
|
37
46
|
:key="'pd-tabs:' + activeItem.id"
|
38
47
|
v-model="activePanel"
|
48
|
+
:active-item="activeItem"
|
39
49
|
:class="tabsClass"
|
40
50
|
:panels="panels"
|
41
51
|
@update:model-value="$emit('update:model-value', $event)"
|
@@ -44,6 +54,7 @@
|
|
44
54
|
:key="'pd-panels:' + activeItem.id"
|
45
55
|
:panels="panels"
|
46
56
|
:active-panel="activePanel"
|
57
|
+
:active-item="activeItem"
|
47
58
|
:class="activePanelOptions?.class || panelsClass"
|
48
59
|
/>
|
49
60
|
<div
|
@@ -68,7 +79,7 @@ import PanelsDrawerTabs from "./PanelsDrawerTabs";
|
|
68
79
|
export interface Props {
|
69
80
|
title?: string,
|
70
81
|
modelValue?: string | number,
|
71
|
-
activeItem
|
82
|
+
activeItem: ActionTargetItem;
|
72
83
|
tabsClass?: string | object,
|
73
84
|
panelsClass?: string | object,
|
74
85
|
panels: ActionPanel[]
|
@@ -78,7 +89,6 @@ defineEmits(["update:model-value", "close"]);
|
|
78
89
|
const props = withDefaults(defineProps<Props>(), {
|
79
90
|
title: "",
|
80
91
|
modelValue: null,
|
81
|
-
activeItem: null,
|
82
92
|
tabsClass: "w-[13.5rem]",
|
83
93
|
panelsClass: "w-[35.5rem]"
|
84
94
|
});
|
@@ -11,17 +11,19 @@
|
|
11
11
|
<RenderVnode
|
12
12
|
v-if="panel.vnode"
|
13
13
|
:vnode="panel.vnode"
|
14
|
+
:props="activeItem"
|
14
15
|
/>
|
15
16
|
</QTabPanel>
|
16
17
|
</QTabPanels>
|
17
18
|
</template>
|
18
19
|
|
19
20
|
<script setup lang="ts">
|
20
|
-
import { ActionPanel } from "../../types";
|
21
|
+
import { ActionPanel, ActionTargetItem } from "../../types";
|
21
22
|
import { RenderVnode } from "../Utility";
|
22
23
|
|
23
24
|
defineProps<{
|
24
25
|
activePanel?: string | number,
|
26
|
+
activeItem: ActionTargetItem,
|
25
27
|
panels: ActionPanel[]
|
26
28
|
}>();
|
27
29
|
</script>
|
@@ -9,11 +9,11 @@
|
|
9
9
|
@update:model-value="$emit('update:model-value', $event)"
|
10
10
|
>
|
11
11
|
<template v-for="panel in panels">
|
12
|
-
<template v-if="panel
|
12
|
+
<template v-if="isEnabled(panel)">
|
13
13
|
<RenderVnode
|
14
14
|
v-if="panel.tabVnode"
|
15
15
|
:key="panel.name"
|
16
|
-
:vnode="panel.tabVnode(modelValue)"
|
16
|
+
:vnode="panel.tabVnode(activeItem, modelValue)"
|
17
17
|
:is-active="modelValue === panel.name"
|
18
18
|
:name="panel.name"
|
19
19
|
:label="panel.label"
|
@@ -30,19 +30,32 @@
|
|
30
30
|
</template>
|
31
31
|
<script setup lang="ts">
|
32
32
|
import { QTab } from "quasar";
|
33
|
-
import { ActionPanel } from "../../types";
|
33
|
+
import { ActionPanel, ActionTargetItem } from "../../types";
|
34
34
|
import { RenderVnode } from "../Utility";
|
35
35
|
|
36
36
|
defineEmits(["update:model-value"]);
|
37
37
|
|
38
38
|
interface Props {
|
39
39
|
modelValue?: string | number;
|
40
|
+
activeItem: ActionTargetItem;
|
40
41
|
panels: ActionPanel[];
|
41
42
|
}
|
42
43
|
|
43
|
-
withDefaults(defineProps<Props>(), {
|
44
|
+
const props = withDefaults(defineProps<Props>(), {
|
44
45
|
modelValue: "general"
|
45
46
|
});
|
47
|
+
|
48
|
+
function isEnabled(panel) {
|
49
|
+
if (panel.enabled === undefined) return true;
|
50
|
+
|
51
|
+
if (!panel.enabled) return false;
|
52
|
+
|
53
|
+
if (typeof panel.enabled === "function") {
|
54
|
+
return panel.enabled(props.activeItem);
|
55
|
+
}
|
56
|
+
|
57
|
+
return true;
|
58
|
+
}
|
46
59
|
</script>
|
47
60
|
|
48
61
|
<style lang="scss" module="cls">
|
@@ -37,17 +37,37 @@
|
|
37
37
|
</video>
|
38
38
|
</template>
|
39
39
|
<img
|
40
|
-
v-else
|
40
|
+
v-else-if="getPreviewUrl(file)"
|
41
41
|
:alt="file.filename"
|
42
42
|
:src="getPreviewUrl(file)"
|
43
43
|
>
|
44
|
+
<div v-else>
|
45
|
+
<h3 class="text-center mb-4">
|
46
|
+
No Preview Available
|
47
|
+
</h3>
|
48
|
+
<a
|
49
|
+
:href="file.url"
|
50
|
+
target="_blank"
|
51
|
+
class="text-base"
|
52
|
+
>
|
53
|
+
{{ file.url }}
|
54
|
+
</a>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
|
58
|
+
<div class="text-base text-center py-5 bg-slate-800 opacity-70 text-slate-300 absolute-top hover:opacity-20 transition-all">
|
59
|
+
{{ file.filename || file.name }}
|
44
60
|
</div>
|
45
61
|
</QCarouselSlide>
|
46
62
|
</QCarousel>
|
47
|
-
<
|
48
|
-
class="absolute top-
|
63
|
+
<a
|
64
|
+
class="absolute top-0 right-0 text-white flex items-center justify-center w-16 h-16 hover:bg-slate-600 transition-all"
|
49
65
|
@click="$emit('close')"
|
50
|
-
|
66
|
+
>
|
67
|
+
<CloseIcon
|
68
|
+
class="w-8 h-8"
|
69
|
+
/>
|
70
|
+
</a>
|
51
71
|
</div>
|
52
72
|
</QDialog>
|
53
73
|
</template>
|
@@ -73,8 +93,13 @@ function isVideo(file) {
|
|
73
93
|
return file.mime?.startsWith("video");
|
74
94
|
}
|
75
95
|
|
96
|
+
function isImage(file) {
|
97
|
+
return file.mime?.startsWith("image");
|
98
|
+
}
|
99
|
+
|
76
100
|
function getPreviewUrl(file) {
|
77
|
-
|
101
|
+
// Use the optimized URL first if available. If not, use the URL directly if its an image, otherwise use the thumb URL
|
102
|
+
return file.optimized?.url || (isImage(file) ? (file.blobUrl || file.url) : file.thumb?.url);
|
78
103
|
}
|
79
104
|
|
80
105
|
function getThumbUrl(file) {
|
@@ -45,6 +45,12 @@
|
|
45
45
|
v-else
|
46
46
|
class="w-24"
|
47
47
|
/>
|
48
|
+
<div
|
49
|
+
v-if="filename"
|
50
|
+
class="text-[.7rem] bg-slate-900 text-slate-300 opacity-80 h-[2.25rem] py-.5 px-1 absolute-bottom"
|
51
|
+
>
|
52
|
+
{{ filename }}
|
53
|
+
</div>
|
48
54
|
</div>
|
49
55
|
</div>
|
50
56
|
<div
|
@@ -54,15 +60,26 @@
|
|
54
60
|
<slot name="action-button" />
|
55
61
|
</div>
|
56
62
|
<div
|
57
|
-
v-if="
|
58
|
-
class="absolute-bottom w-full"
|
63
|
+
v-if="isUploading || transcodingStatus"
|
64
|
+
class="absolute-bottom w-full bg-slate-800"
|
59
65
|
>
|
60
66
|
<QLinearProgress
|
61
|
-
:value="file.progress"
|
62
|
-
size="
|
63
|
-
color="green-
|
67
|
+
:value="isUploading ? file.progress : (transcodingStatus.progress / 100)"
|
68
|
+
size="36px"
|
69
|
+
:color="isUploading ? 'green-800' : 'blue-800'"
|
70
|
+
:animation-speed="transcodingStatus?.estimate_ms || 3000"
|
64
71
|
stripe
|
65
|
-
|
72
|
+
>
|
73
|
+
<div class="absolute-full flex items-center flex-nowrap text-[.7rem] text-slate-200 justify-start px-1">
|
74
|
+
<QSpinnerPie
|
75
|
+
class="mr-2 text-slate-50 ml-1"
|
76
|
+
size="20"
|
77
|
+
/>
|
78
|
+
<div>
|
79
|
+
{{ isUploading ? "Uploading..." : transcodingStatus.message }}
|
80
|
+
</div>
|
81
|
+
</div>
|
82
|
+
</QLinearProgress>
|
66
83
|
</div>
|
67
84
|
</template>
|
68
85
|
<template v-else>
|
@@ -105,9 +122,9 @@
|
|
105
122
|
</div>
|
106
123
|
|
107
124
|
<FullScreenCarouselDialog
|
108
|
-
v-if="showPreview && !disabled"
|
109
|
-
:files="
|
110
|
-
:default-slide="
|
125
|
+
v-if="showPreview && !disabled && previewableFiles"
|
126
|
+
:files="previewableFiles"
|
127
|
+
:default-slide="previewableFiles[0]?.id || ''"
|
111
128
|
@close="showPreview = false"
|
112
129
|
/>
|
113
130
|
</div>
|
@@ -115,12 +132,21 @@
|
|
115
132
|
|
116
133
|
<script setup lang="ts">
|
117
134
|
import { DocumentTextIcon as TextFileIcon, DownloadIcon, PlayIcon } from "@heroicons/vue/outline";
|
118
|
-
import { computed, ComputedRef, ref } from "vue";
|
119
|
-
import { download } from "../../../helpers";
|
135
|
+
import { computed, ComputedRef, onMounted, ref } from "vue";
|
136
|
+
import { download, FileUpload } from "../../../helpers";
|
120
137
|
import { ImageIcon, PdfIcon, TrashIcon as RemoveIcon } from "../../../svg";
|
121
138
|
import { UploadedFile } from "../../../types";
|
122
139
|
import { FullScreenCarouselDialog } from "../Dialogs";
|
123
140
|
|
141
|
+
export interface FileTranscode {
|
142
|
+
status: "Complete" | "Pending" | "In Progress";
|
143
|
+
progress: number;
|
144
|
+
estimate_ms: number;
|
145
|
+
started_at: string;
|
146
|
+
completed_at: string;
|
147
|
+
message?: string;
|
148
|
+
}
|
149
|
+
|
124
150
|
export interface FilePreviewProps {
|
125
151
|
src?: string;
|
126
152
|
file?: UploadedFile;
|
@@ -149,6 +175,7 @@ const props = withDefaults(defineProps<FilePreviewProps>(), {
|
|
149
175
|
square: false
|
150
176
|
});
|
151
177
|
|
178
|
+
|
152
179
|
const showPreview = ref(false);
|
153
180
|
const computedImage: ComputedRef<UploadedFile | null> = computed(() => {
|
154
181
|
if (props.file) {
|
@@ -159,11 +186,19 @@ const computedImage: ComputedRef<UploadedFile | null> = computed(() => {
|
|
159
186
|
url: props.src,
|
160
187
|
type: "image/" + props.src.split(".").pop()?.toLowerCase(),
|
161
188
|
name: "",
|
162
|
-
size: 0
|
189
|
+
size: 0,
|
190
|
+
__type: "BrowserFile"
|
163
191
|
};
|
164
192
|
}
|
165
193
|
return null;
|
166
194
|
});
|
195
|
+
|
196
|
+
const isUploading = computed(() => !props.file || props.file?.progress !== undefined);
|
197
|
+
const previewableFiles: ComputedRef<[UploadedFile | null]> = computed(() => {
|
198
|
+
return props.relatedFiles?.length > 0 ? props.relatedFiles : [computedImage.value];
|
199
|
+
});
|
200
|
+
|
201
|
+
const filename = computed(() => computedImage.value?.name || computedImage.value?.filename || "");
|
167
202
|
const mimeType = computed(
|
168
203
|
() => computedImage.value?.type || computedImage.value?.mime || ""
|
169
204
|
);
|
@@ -179,6 +214,31 @@ const thumbUrl = computed(() => {
|
|
179
214
|
const isPreviewable = computed(() => {
|
180
215
|
return !!thumbUrl.value || isVideo.value || isImage.value;
|
181
216
|
});
|
217
|
+
|
218
|
+
/**
|
219
|
+
* Resolve the active transcoding operation if there is one, otherwise return null
|
220
|
+
*/
|
221
|
+
const transcodingStatus = computed(() => {
|
222
|
+
let status = null;
|
223
|
+
const metaTranscodes: FileTranscode[] = props.file?.meta?.transcodes || [];
|
224
|
+
|
225
|
+
for (let transcodeName of Object.keys(metaTranscodes)) {
|
226
|
+
const transcode = metaTranscodes[transcodeName];
|
227
|
+
if (!transcode?.completed_at) {
|
228
|
+
return { ...transcode, message: `${transcodeName} ${transcode.status}` };
|
229
|
+
}
|
230
|
+
}
|
231
|
+
|
232
|
+
return status;
|
233
|
+
});
|
234
|
+
|
235
|
+
// Check for an active transcode and make sure the file is being polled for updates
|
236
|
+
onMounted(() => {
|
237
|
+
if (transcodingStatus.value) {
|
238
|
+
(new FileUpload([])).waitForTranscode(props.file);
|
239
|
+
}
|
240
|
+
});
|
241
|
+
|
182
242
|
const isConfirmingRemove = ref(false);
|
183
243
|
function onRemove() {
|
184
244
|
if (!isConfirmingRemove.value) {
|
@@ -24,62 +24,67 @@
|
|
24
24
|
auto-close
|
25
25
|
>
|
26
26
|
<QList>
|
27
|
-
<template
|
27
|
+
<template
|
28
|
+
v-for="item in items"
|
29
|
+
:key="item.name"
|
30
|
+
>
|
28
31
|
<a
|
29
32
|
v-if="item.url"
|
30
|
-
:key="item.url"
|
31
33
|
class="q-item"
|
32
34
|
target="_blank"
|
33
35
|
:href="item.url"
|
34
36
|
:class="item.class"
|
35
37
|
>
|
36
|
-
|
38
|
+
<Component
|
39
|
+
:is="item.icon"
|
40
|
+
v-if="item.icon"
|
41
|
+
:class="item.iconClass"
|
42
|
+
class="mr-3 w-4"
|
43
|
+
/> {{ item.label }}
|
37
44
|
</a>
|
38
45
|
<QItem
|
39
46
|
v-else
|
40
|
-
:key="item.name || item.action"
|
41
47
|
clickable
|
42
48
|
:class="item.class"
|
43
49
|
@click="onAction(item)"
|
44
50
|
>
|
45
|
-
|
51
|
+
<Component
|
52
|
+
:is="item.icon"
|
53
|
+
v-if="item.icon"
|
54
|
+
:class="item.iconClass"
|
55
|
+
class="mr-3 w-4"
|
56
|
+
/> {{ item.label }}
|
46
57
|
</QItem>
|
47
58
|
</template>
|
48
59
|
</QList>
|
49
60
|
</QMenu>
|
50
61
|
</a>
|
51
62
|
</template>
|
52
|
-
<script setup>
|
63
|
+
<script setup lang="ts">
|
53
64
|
import { DotsVerticalIcon as MenuIcon } from "@heroicons/vue/outline";
|
54
65
|
import { QSpinner } from "quasar";
|
66
|
+
import { ResourceAction } from "../../../types";
|
55
67
|
import { RenderComponent } from "../Tools";
|
56
68
|
|
69
|
+
export interface PopoverMenuProps {
|
70
|
+
items: ResourceAction;
|
71
|
+
tooltip?: string;
|
72
|
+
disabled?: boolean;
|
73
|
+
loading?: boolean;
|
74
|
+
loadingComponent?: any;
|
75
|
+
}
|
76
|
+
|
57
77
|
const emit = defineEmits(["action", "action-item"]);
|
58
|
-
defineProps({
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
}
|
65
|
-
},
|
66
|
-
tooltip: {
|
67
|
-
type: String,
|
68
|
-
default: null
|
69
|
-
},
|
70
|
-
disabled: Boolean,
|
71
|
-
loading: Boolean,
|
72
|
-
loadingComponent: {
|
73
|
-
type: [Function, Object],
|
74
|
-
default: () => ({
|
75
|
-
is: QSpinner,
|
76
|
-
props: { class: "w-4 h-4" }
|
77
|
-
})
|
78
|
-
}
|
78
|
+
withDefaults(defineProps<PopoverMenuProps>(), {
|
79
|
+
tooltip: null,
|
80
|
+
loadingComponent: () => ({
|
81
|
+
is: QSpinner,
|
82
|
+
props: { class: "w-4 h-4" }
|
83
|
+
})
|
79
84
|
});
|
80
85
|
|
81
86
|
function onAction(item) {
|
82
|
-
|
83
|
-
|
87
|
+
emit("action", item.name || item.action);
|
88
|
+
emit("action-item", item);
|
84
89
|
}
|
85
90
|
</script>
|
@@ -11,7 +11,7 @@ const RenderVnode = (props) => {
|
|
11
11
|
}
|
12
12
|
|
13
13
|
if (typeof props.vnode === "function") {
|
14
|
-
return props.vnode(props.props);
|
14
|
+
return props.vnode(props.props, props.params);
|
15
15
|
}
|
16
16
|
|
17
17
|
return null;
|
@@ -24,6 +24,10 @@ RenderVnode.props = {
|
|
24
24
|
props: {
|
25
25
|
type: Object,
|
26
26
|
default: () => ({})
|
27
|
+
},
|
28
|
+
params: {
|
29
|
+
type: Object,
|
30
|
+
default: null
|
27
31
|
}
|
28
32
|
};
|
29
33
|
export default RenderVnode;
|
package/src/config/index.ts
CHANGED
@@ -11,7 +11,8 @@ export const danxOptions = shallowRef<DanxOptions>({
|
|
11
11
|
fileUpload: {
|
12
12
|
directory: "file-upload",
|
13
13
|
createPresignedUpload: null,
|
14
|
-
completePresignedUpload: null
|
14
|
+
completePresignedUpload: null,
|
15
|
+
refreshFile: null
|
15
16
|
},
|
16
17
|
flashMessages: {
|
17
18
|
default: {},
|