quasar-ui-danx 0.4.10 → 0.4.13
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 +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: {},
|