quasar-ui-danx 0.4.10 → 0.4.12
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 +5954 -5721
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +6 -6
- 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 +8 -3
- 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 +11 -10
- 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/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/config/index.ts +2 -1
- package/src/helpers/FileUpload.ts +59 -8
- package/src/helpers/actions.ts +27 -27
- package/src/helpers/download.ts +8 -2
- 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 +9 -0
- 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 +13 -4
- package/src/types/controls.d.ts +4 -4
- package/src/types/files.d.ts +10 -5
- 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
@@ -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;
|
@@ -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>
|
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: {},
|
@@ -11,6 +11,8 @@ import {
|
|
11
11
|
} from "../types";
|
12
12
|
import { resolveFileLocation } from "./files";
|
13
13
|
import { FlashMessages } from "./FlashMessages";
|
14
|
+
import { storeObject } from "./objectStore";
|
15
|
+
import { sleep } from "./utils";
|
14
16
|
|
15
17
|
|
16
18
|
export class FileUpload {
|
@@ -22,7 +24,8 @@ export class FileUpload {
|
|
22
24
|
onAllCompleteCb: FileUploadAllCompleteCallback | null = null;
|
23
25
|
options: FileUploadOptions;
|
24
26
|
|
25
|
-
constructor(files: UploadedFile[] | UploadedFile, options?: FileUploadOptions) {
|
27
|
+
constructor(files: UploadedFile[] | UploadedFile, options?: FileUploadOptions | null) {
|
28
|
+
/* @ts-expect-error Files is an array */
|
26
29
|
this.files = !Array.isArray(files) && !(files instanceof FileList) ? [files] : files;
|
27
30
|
this.fileUploads = [];
|
28
31
|
this.onErrorCb = null;
|
@@ -33,6 +36,7 @@ export class FileUpload {
|
|
33
36
|
this.options = {
|
34
37
|
createPresignedUpload: null,
|
35
38
|
completePresignedUpload: null,
|
39
|
+
refreshFile: null,
|
36
40
|
...danxOptions.value.fileUpload,
|
37
41
|
...options
|
38
42
|
};
|
@@ -40,7 +44,6 @@ export class FileUpload {
|
|
40
44
|
if (!this.options.createPresignedUpload || !this.options.completePresignedUpload) {
|
41
45
|
throw new Error("Please configure danxOptions.fileUpload: import { configure } from 'quasar-ui-danx';");
|
42
46
|
}
|
43
|
-
this.prepare();
|
44
47
|
}
|
45
48
|
|
46
49
|
/**
|
@@ -69,6 +72,8 @@ export class FileUpload {
|
|
69
72
|
isComplete: false
|
70
73
|
});
|
71
74
|
}
|
75
|
+
|
76
|
+
return this;
|
72
77
|
}
|
73
78
|
|
74
79
|
/**
|
@@ -110,7 +115,7 @@ export class FileUpload {
|
|
110
115
|
/**
|
111
116
|
* Handles the error events / fires the callback if it is set
|
112
117
|
*/
|
113
|
-
errorHandler(e: InputEvent, file: UploadedFile, error = null) {
|
118
|
+
errorHandler(e: InputEvent | ProgressEvent, file: UploadedFile, error = null) {
|
114
119
|
if (this.onErrorCb) {
|
115
120
|
this.onErrorCb({ e, file, error });
|
116
121
|
}
|
@@ -179,7 +184,8 @@ export class FileUpload {
|
|
179
184
|
progress: file.progress,
|
180
185
|
location: file.location,
|
181
186
|
blobUrl: file.blobUrl,
|
182
|
-
url: ""
|
187
|
+
url: "",
|
188
|
+
__type: "BrowserFile"
|
183
189
|
};
|
184
190
|
}
|
185
191
|
|
@@ -219,12 +225,13 @@ export class FileUpload {
|
|
219
225
|
async (e) => {
|
220
226
|
try {
|
221
227
|
// First complete the presigned upload to get the updated file resource data
|
222
|
-
|
223
|
-
|
228
|
+
let storedFile = await this.completePresignedUpload(fileUpload);
|
229
|
+
storedFile = storeObject(storedFile);
|
224
230
|
// Fire the file complete callbacks
|
225
|
-
this.fireCompleteCallback(fileUpload,
|
231
|
+
this.fireCompleteCallback(fileUpload, storedFile);
|
226
232
|
this.checkAllComplete();
|
227
|
-
|
233
|
+
await this.waitForTranscode(storedFile);
|
234
|
+
} catch (error: any) {
|
228
235
|
this.errorHandler(e, fileUpload.file, error);
|
229
236
|
}
|
230
237
|
},
|
@@ -252,6 +259,49 @@ export class FileUpload {
|
|
252
259
|
return await this.options.completePresignedUpload(fileUpload.file.resource_id);
|
253
260
|
}
|
254
261
|
|
262
|
+
/**
|
263
|
+
* Refresh the file data, in case transcoding or some transient state is needed to be refreshed on the file
|
264
|
+
*/
|
265
|
+
async refreshFile(file: UploadedFile): Promise<UploadedFile | null> {
|
266
|
+
if (!this.options.refreshFile) return null;
|
267
|
+
|
268
|
+
const storedFile = await this.options.refreshFile(file.id);
|
269
|
+
|
270
|
+
if (storedFile) {
|
271
|
+
return storeObject(storedFile);
|
272
|
+
}
|
273
|
+
return storedFile;
|
274
|
+
}
|
275
|
+
|
276
|
+
/**
|
277
|
+
* Checks if the file has a transcode in progress or pending
|
278
|
+
*/
|
279
|
+
isTranscoding(file: UploadedFile) {
|
280
|
+
const metaTranscodes = file?.meta?.transcodes || [];
|
281
|
+
|
282
|
+
for (const transcodeName of Object.keys(metaTranscodes)) {
|
283
|
+
const transcode = metaTranscodes[transcodeName];
|
284
|
+
if (transcode.status === "Pending" || transcode.status === "In Progress") {
|
285
|
+
return true;
|
286
|
+
}
|
287
|
+
}
|
288
|
+
return false;
|
289
|
+
}
|
290
|
+
|
291
|
+
/**
|
292
|
+
* Keeps refreshing the file while there is transcoding in progress
|
293
|
+
*/
|
294
|
+
async waitForTranscode(file: UploadedFile) {
|
295
|
+
// Only allow waiting for transcode 1 time per file
|
296
|
+
if (!file.meta || file.meta.is_waiting_transcode) return;
|
297
|
+
file.meta.is_waiting_transcode = true;
|
298
|
+
let currentFile: UploadedFile | null = file;
|
299
|
+
while (currentFile && this.isTranscoding(currentFile)) {
|
300
|
+
await sleep(1000);
|
301
|
+
currentFile = await this.refreshFile(currentFile);
|
302
|
+
}
|
303
|
+
}
|
304
|
+
|
255
305
|
/**
|
256
306
|
* Start uploading all files
|
257
307
|
*/
|
@@ -297,6 +347,7 @@ export class FileUpload {
|
|
297
347
|
|
298
348
|
// Send all the XHR file uploads
|
299
349
|
for (const fileUpload of this.fileUploads) {
|
350
|
+
// @ts-expect-error XHRFileUpload has a xhr property
|
300
351
|
fileUpload.xhr?.send(fileUpload.body);
|
301
352
|
}
|
302
353
|
}
|