@knime/product-features 2.2.2 → 2.3.1
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/CHANGELOG.md +32 -0
- package/dist/core-CVWznjtP.js +739 -0
- package/dist/core-CVWznjtP.js.map +1 -0
- package/dist/dataTransfer/core.js +2 -0
- package/dist/dataTransfer.css +12 -12
- package/dist/dataTransfer.js +420 -767
- package/dist/dataTransfer.js.map +1 -1
- package/dist/src/dataTransfer/core.d.ts +2 -0
- package/dist/src/dataTransfer/core.d.ts.map +1 -0
- package/dist/src/dataTransfer/index.d.ts +1 -2
- package/dist/src/dataTransfer/index.d.ts.map +1 -1
- package/dist/src/dataTransfer/upload/{ui/UploadProgressPanel.vue.d.ts → UploadProgressPanel.vue.d.ts} +2 -0
- package/dist/src/dataTransfer/upload/UploadProgressPanel.vue.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/{ui/UploadProgressPanelItem.vue.d.ts → UploadProgressPanelItem.vue.d.ts} +2 -1
- package/dist/src/dataTransfer/upload/UploadProgressPanelItem.vue.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/index.d.ts +3 -0
- package/dist/src/dataTransfer/upload/core/index.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.d.ts +26 -0
- package/dist/src/dataTransfer/upload/core/uploadClient/createUploadStatusPoller.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadClient/index.d.ts +3 -0
- package/dist/src/dataTransfer/upload/core/uploadClient/index.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadClient/uploadClient.d.ts +59 -0
- package/dist/src/dataTransfer/upload/core/uploadClient/uploadClient.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/{manager/index.d.ts → core/uploadManager/chunkedUploadManager.d.ts} +21 -60
- package/dist/src/dataTransfer/upload/core/uploadManager/chunkedUploadManager.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/config.d.ts +12 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/config.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/index.d.ts +5 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/index.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/state.d.ts +30 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/state.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/streamingUploadManager.d.ts +52 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/streamingUploadManager.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/types.d.ts +46 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/types.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/uploadPartBytes.d.ts +17 -0
- package/dist/src/dataTransfer/upload/core/uploadManager/uploadPartBytes.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/index.d.ts +6 -0
- package/dist/src/dataTransfer/upload/index.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/types.d.ts +80 -0
- package/dist/src/dataTransfer/upload/types.d.ts.map +1 -0
- package/dist/src/dataTransfer/upload/useFileUpload.d.ts +5 -13
- package/dist/src/dataTransfer/upload/useFileUpload.d.ts.map +1 -1
- package/dist/src/dataTransfer/upload/{ui/useUploadManager.d.ts → useUploadManager.d.ts} +17 -5
- package/dist/src/dataTransfer/upload/useUploadManager.d.ts.map +1 -0
- package/dist/src/versions/VersionHistory/HistoryEventAuthor.vue.d.ts.map +1 -1
- package/dist/toasts.js +508 -508
- package/dist/toasts.js.map +1 -1
- package/dist/versions.css +2 -2
- package/dist/versions.js +3 -12
- package/dist/versions.js.map +1 -1
- package/package.json +8 -4
- package/dist/src/dataTransfer/upload/manager/index.d.ts.map +0 -1
- package/dist/src/dataTransfer/upload/ui/UploadProgressPanel.vue.d.ts.map +0 -1
- package/dist/src/dataTransfer/upload/ui/UploadProgressPanelItem.vue.d.ts.map +0 -1
- package/dist/src/dataTransfer/upload/ui/index.d.ts +0 -5
- package/dist/src/dataTransfer/upload/ui/index.d.ts.map +0 -1
- package/dist/src/dataTransfer/upload/ui/types.d.ts +0 -60
- package/dist/src/dataTransfer/upload/ui/types.d.ts.map +0 -1
- package/dist/src/dataTransfer/upload/ui/useUploadManager.d.ts.map +0 -1
package/dist/dataTransfer.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t as _plugin_vue_export_helper_default } from "./_plugin-vue_export-helper-BOaGB7Aw.js";
|
|
2
|
-
import { a as getFileExtension, c as promise_exports, l as sleep,
|
|
2
|
+
import { a as getFileExtension, c as promise_exports, l as sleep, r as formatFileSize, s as knimeFileFormats } from "./dist-DvDWkkNg.js";
|
|
3
3
|
import { t as rfcErrors } from "./rfcErrors-B2gYd5aG.js";
|
|
4
|
-
import {
|
|
4
|
+
import { a as createUploadManager, c as API_BASE_URL, n as createUploadClient, o as createHttpClient, s as defaultConfig, t as createUploadStatusPoller } from "./core-CVWznjtP.js";
|
|
5
|
+
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, useModel, vShow, watch, withCtx, withDirectives } from "vue";
|
|
5
6
|
import { KdsBadge, KdsButton, KdsIcon, KdsLoadingSkeleton, KdsProgressBar, vKdsTooltip } from "@knime/kds-components";
|
|
6
|
-
import { FetchError
|
|
7
|
+
import { FetchError } from "ofetch";
|
|
7
8
|
import './dataTransfer.css';//#region src/dataTransfer/shared/ui/CollapsiblePanel/useIconFlipper.ts
|
|
8
9
|
var useIconFlipper = (options) => {
|
|
9
10
|
return { iconFlipStyles: computed(() => {
|
|
@@ -157,170 +158,6 @@ function _sfc_render(_ctx, _cache) {
|
|
|
157
158
|
}
|
|
158
159
|
var ProgressList_default = /*#__PURE__*/ _plugin_vue_export_helper_default(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-8f14a3cb"]]);
|
|
159
160
|
//#endregion
|
|
160
|
-
//#region src/dataTransfer/upload/ui/UploadProgressPanelItem.vue
|
|
161
|
-
var UploadProgressPanelItem_default = /* @__PURE__ */ defineComponent({
|
|
162
|
-
__name: "UploadProgressPanelItem",
|
|
163
|
-
props: {
|
|
164
|
-
item: {},
|
|
165
|
-
allowCancel: { type: Boolean }
|
|
166
|
-
},
|
|
167
|
-
emits: ["cancel"],
|
|
168
|
-
setup(__props, { emit: __emit }) {
|
|
169
|
-
const props = __props;
|
|
170
|
-
const emit = __emit;
|
|
171
|
-
const hasProgress = (item) => {
|
|
172
|
-
return item.status === "inprogress" || item.status === "complete";
|
|
173
|
-
};
|
|
174
|
-
const totalFormatted = computed(() => {
|
|
175
|
-
return formatFileSize(props.item.size);
|
|
176
|
-
});
|
|
177
|
-
const progressedFormatted = computed(() => {
|
|
178
|
-
if (hasProgress(props.item)) return formatFileSize(props.item.size / 100 * (props.item.progress ?? 0));
|
|
179
|
-
return 0;
|
|
180
|
-
});
|
|
181
|
-
const subtitle = computed(() => props.item.status === "inprogress" ? `${progressedFormatted.value} of ${totalFormatted.value}` : `${totalFormatted.value}`);
|
|
182
|
-
const statusMapper = {
|
|
183
|
-
inprogress: [
|
|
184
|
-
"Uploading",
|
|
185
|
-
"info",
|
|
186
|
-
"reload"
|
|
187
|
-
],
|
|
188
|
-
failed: [
|
|
189
|
-
"Failed",
|
|
190
|
-
"error",
|
|
191
|
-
"circle-error"
|
|
192
|
-
],
|
|
193
|
-
complete: [
|
|
194
|
-
"Uploaded",
|
|
195
|
-
"success",
|
|
196
|
-
"circle-success"
|
|
197
|
-
],
|
|
198
|
-
cancelled: [
|
|
199
|
-
"Cancelled",
|
|
200
|
-
"error",
|
|
201
|
-
"circle-error"
|
|
202
|
-
],
|
|
203
|
-
processing: [
|
|
204
|
-
"Processing",
|
|
205
|
-
"info",
|
|
206
|
-
"reload"
|
|
207
|
-
]
|
|
208
|
-
};
|
|
209
|
-
const itemIcon = computed(() => {
|
|
210
|
-
return getFileExtension(props.item.name) === knimeFileFormats.KNWF.extension ? "workflow" : "file";
|
|
211
|
-
});
|
|
212
|
-
const badge = computed(() => {
|
|
213
|
-
if (!props.item.status) return;
|
|
214
|
-
const [text, variant, icon] = statusMapper[props.item.status];
|
|
215
|
-
return {
|
|
216
|
-
text,
|
|
217
|
-
variant,
|
|
218
|
-
icon
|
|
219
|
-
};
|
|
220
|
-
});
|
|
221
|
-
const shouldShowCancelAction = computed(() => props.allowCancel && props.item.status === "inprogress");
|
|
222
|
-
const actions = computed(() => {
|
|
223
|
-
return shouldShowCancelAction.value ? [{
|
|
224
|
-
id: "cancel",
|
|
225
|
-
icon: "x-close",
|
|
226
|
-
label: "Cancel",
|
|
227
|
-
handler: () => emit("cancel")
|
|
228
|
-
}] : [];
|
|
229
|
-
});
|
|
230
|
-
return (_ctx, _cache) => {
|
|
231
|
-
return openBlock(), createBlock(unref(ProgressItem_default), {
|
|
232
|
-
id: __props.item.id,
|
|
233
|
-
headline: __props.item.name,
|
|
234
|
-
subtitle: subtitle.value,
|
|
235
|
-
icon: itemIcon.value,
|
|
236
|
-
progress: hasProgress(__props.item) ? __props.item.progress : void 0,
|
|
237
|
-
badge: badge.value,
|
|
238
|
-
actions: actions.value
|
|
239
|
-
}, null, 8, [
|
|
240
|
-
"id",
|
|
241
|
-
"headline",
|
|
242
|
-
"subtitle",
|
|
243
|
-
"icon",
|
|
244
|
-
"progress",
|
|
245
|
-
"badge",
|
|
246
|
-
"actions"
|
|
247
|
-
]);
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
//#endregion
|
|
252
|
-
//#region src/dataTransfer/upload/ui/UploadProgressPanel.vue?vue&type=script&setup=true&lang.ts
|
|
253
|
-
var _hoisted_1$1 = { class: "upload-panel-content" };
|
|
254
|
-
var _hoisted_2 = { class: "skeleton-text-content" };
|
|
255
|
-
//#endregion
|
|
256
|
-
//#region src/dataTransfer/upload/ui/UploadProgressPanel.vue
|
|
257
|
-
var UploadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
258
|
-
__name: "UploadProgressPanel",
|
|
259
|
-
props: /*@__PURE__*/ mergeModels({
|
|
260
|
-
items: {},
|
|
261
|
-
allowCancel: {
|
|
262
|
-
type: Boolean,
|
|
263
|
-
default: true
|
|
264
|
-
},
|
|
265
|
-
allowRemove: { type: Boolean },
|
|
266
|
-
placeholderItems: { default: 0 }
|
|
267
|
-
}, {
|
|
268
|
-
"expanded": {
|
|
269
|
-
type: Boolean,
|
|
270
|
-
default: true
|
|
271
|
-
},
|
|
272
|
-
"expandedModifiers": {}
|
|
273
|
-
}),
|
|
274
|
-
emits: /*@__PURE__*/ mergeModels(["cancel", "close"], ["update:expanded"]),
|
|
275
|
-
setup(__props, { emit: __emit }) {
|
|
276
|
-
const emit = __emit;
|
|
277
|
-
const expanded = useModel(__props, "expanded");
|
|
278
|
-
const totalItems = computed(() => __props.items.length);
|
|
279
|
-
const uploadedItems = computed(() => __props.items.filter(({ status }) => status === "complete").length);
|
|
280
|
-
const headline = computed(() => {
|
|
281
|
-
const suffix = `file${totalItems.value > 1 ? "s" : ""}`;
|
|
282
|
-
return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;
|
|
283
|
-
});
|
|
284
|
-
const hasSomeItemInProgress = computed(() => __props.items.some(({ status }) => status === "inprogress" || status === "processing") || __props.placeholderItems > 0);
|
|
285
|
-
return (_ctx, _cache) => {
|
|
286
|
-
return openBlock(), createBlock(unref(CollapsiblePanel_default), {
|
|
287
|
-
modelValue: expanded.value,
|
|
288
|
-
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => expanded.value = $event),
|
|
289
|
-
headline: headline.value,
|
|
290
|
-
class: "upload-panel",
|
|
291
|
-
"can-be-dismissed": !hasSomeItemInProgress.value,
|
|
292
|
-
onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
|
|
293
|
-
}, {
|
|
294
|
-
default: withCtx(() => [createElementVNode("div", _hoisted_1$1, [createVNode(unref(ProgressList_default), null, {
|
|
295
|
-
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
|
|
296
|
-
return openBlock(), createBlock(UploadProgressPanelItem_default, {
|
|
297
|
-
key: item.id,
|
|
298
|
-
item,
|
|
299
|
-
"allow-cancel": __props.allowCancel,
|
|
300
|
-
onCancel: ($event) => emit("cancel", item)
|
|
301
|
-
}, null, 8, [
|
|
302
|
-
"item",
|
|
303
|
-
"allow-cancel",
|
|
304
|
-
"onCancel"
|
|
305
|
-
]);
|
|
306
|
-
}), 128))]),
|
|
307
|
-
_: 1
|
|
308
|
-
}), __props.placeholderItems > 0 ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(__props.placeholderItems, (skeleton) => {
|
|
309
|
-
return openBlock(), createElementBlock("div", {
|
|
310
|
-
key: skeleton,
|
|
311
|
-
class: "skeleton-container"
|
|
312
|
-
}, [createVNode(unref(KdsLoadingSkeleton), { variant: "icon-large" }), createElementVNode("div", _hoisted_2, [createVNode(unref(KdsLoadingSkeleton), { variant: "label" }), createVNode(unref(KdsLoadingSkeleton), { variant: "text" })])]);
|
|
313
|
-
}), 128)) : createCommentVNode("", true)])]),
|
|
314
|
-
_: 1
|
|
315
|
-
}, 8, [
|
|
316
|
-
"modelValue",
|
|
317
|
-
"headline",
|
|
318
|
-
"can-be-dismissed"
|
|
319
|
-
]);
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
}), [["__scopeId", "data-v-15b3eb7d"]]);
|
|
323
|
-
//#endregion
|
|
324
161
|
//#region src/dataTransfer/download/ui/DownloadProgressPanelItem.vue
|
|
325
162
|
var DownloadProgressPanelItem_default = /* @__PURE__ */ defineComponent({
|
|
326
163
|
__name: "DownloadProgressPanelItem",
|
|
@@ -395,7 +232,7 @@ var DownloadProgressPanelItem_default = /* @__PURE__ */ defineComponent({
|
|
|
395
232
|
});
|
|
396
233
|
//#endregion
|
|
397
234
|
//#region src/dataTransfer/download/ui/DownloadProgressPanel.vue?vue&type=script&setup=true&lang.ts
|
|
398
|
-
var _hoisted_1 = { class: "download-panel-content" };
|
|
235
|
+
var _hoisted_1$1 = { class: "download-panel-content" };
|
|
399
236
|
//#endregion
|
|
400
237
|
//#region src/dataTransfer/download/ui/DownloadProgressPanel.vue
|
|
401
238
|
var DownloadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
@@ -435,7 +272,7 @@ var DownloadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_defa
|
|
|
435
272
|
"can-be-dismissed": !hasItemsInProgress.value,
|
|
436
273
|
onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
|
|
437
274
|
}, {
|
|
438
|
-
default: withCtx(() => [createElementVNode("div", _hoisted_1, [createVNode(unref(ProgressList_default), null, {
|
|
275
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_1$1, [createVNode(unref(ProgressList_default), null, {
|
|
439
276
|
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
|
|
440
277
|
return openBlock(), createBlock(DownloadProgressPanelItem_default, {
|
|
441
278
|
key: item.downloadId,
|
|
@@ -460,358 +297,383 @@ var DownloadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_defa
|
|
|
460
297
|
}
|
|
461
298
|
}), [["__scopeId", "data-v-53e10d80"]]);
|
|
462
299
|
//#endregion
|
|
463
|
-
//#region
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
* @returns True if the value is a plain object, otherwise false.
|
|
469
|
-
*
|
|
470
|
-
* @example
|
|
471
|
-
* ```typescript
|
|
472
|
-
* // ✅👇 True
|
|
473
|
-
*
|
|
474
|
-
* isPlainObject({ }); // ✅
|
|
475
|
-
* isPlainObject({ key: 'value' }); // ✅
|
|
476
|
-
* isPlainObject({ key: new Date() }); // ✅
|
|
477
|
-
* isPlainObject(new Object()); // ✅
|
|
478
|
-
* isPlainObject(Object.create(null)); // ✅
|
|
479
|
-
* isPlainObject({ nested: { key: true} }); // ✅
|
|
480
|
-
* isPlainObject(new Proxy({}, {})); // ✅
|
|
481
|
-
* isPlainObject({ [Symbol('tag')]: 'A' }); // ✅
|
|
482
|
-
*
|
|
483
|
-
* // ✅👇 (cross-realms, node context, workers, ...)
|
|
484
|
-
* const runInNewContext = await import('node:vm').then(
|
|
485
|
-
* (mod) => mod.runInNewContext
|
|
486
|
-
* );
|
|
487
|
-
* isPlainObject(runInNewContext('({})')); // ✅
|
|
488
|
-
*
|
|
489
|
-
* // ❌👇 False
|
|
490
|
-
*
|
|
491
|
-
* class Test { };
|
|
492
|
-
* isPlainObject(new Test()) // ❌
|
|
493
|
-
* isPlainObject(10); // ❌
|
|
494
|
-
* isPlainObject(null); // ❌
|
|
495
|
-
* isPlainObject('hello'); // ❌
|
|
496
|
-
* isPlainObject([]); // ❌
|
|
497
|
-
* isPlainObject(new Date()); // ❌
|
|
498
|
-
* isPlainObject(new Uint8Array([1])); // ❌
|
|
499
|
-
* isPlainObject(Buffer.from('ABC')); // ❌
|
|
500
|
-
* isPlainObject(Promise.resolve({})); // ❌
|
|
501
|
-
* isPlainObject(Object.create({})); // ❌
|
|
502
|
-
* isPlainObject(new (class Cls {})); // ❌
|
|
503
|
-
* isPlainObject(globalThis); // ❌,
|
|
504
|
-
* ```
|
|
505
|
-
*/
|
|
506
|
-
function isPlainObject(value) {
|
|
507
|
-
if (!value || typeof value !== "object") return false;
|
|
508
|
-
const proto = Object.getPrototypeOf(value);
|
|
509
|
-
if (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;
|
|
510
|
-
return Object.prototype.toString.call(value) === "[object Object]";
|
|
511
|
-
}
|
|
512
|
-
//#endregion
|
|
513
|
-
//#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
|
|
514
|
-
/**
|
|
515
|
-
* Checks if a property key is unsafe to modify directly.
|
|
516
|
-
*
|
|
517
|
-
* This function is used in functions like `merge` to prevent prototype pollution attacks
|
|
518
|
-
* by identifying property keys that could modify the object's prototype chain or constructor.
|
|
519
|
-
*
|
|
520
|
-
* @param key - The property key to check
|
|
521
|
-
* @returns `true` if the property is unsafe to modify directly, `false` otherwise
|
|
522
|
-
* @internal
|
|
523
|
-
*/
|
|
524
|
-
function isUnsafeProperty(key) {
|
|
525
|
-
return key === "__proto__";
|
|
526
|
-
}
|
|
527
|
-
//#endregion
|
|
528
|
-
//#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/object/merge.mjs
|
|
529
|
-
/**
|
|
530
|
-
* Merges the properties of the source object into the target object.
|
|
531
|
-
*
|
|
532
|
-
* This function performs a deep merge, meaning nested objects and arrays are merged recursively.
|
|
533
|
-
* If a property in the source object is an array or an object and the corresponding property in the target object is also an array or object, they will be merged.
|
|
534
|
-
* If a property in the source object is undefined, it will not overwrite a defined property in the target object.
|
|
535
|
-
*
|
|
536
|
-
* Note that this function mutates the target object.
|
|
537
|
-
*
|
|
538
|
-
* @param target - The target object into which the source object properties will be merged. This object is modified in place.
|
|
539
|
-
* @param source - The source object whose properties will be merged into the target object.
|
|
540
|
-
* @returns The updated target object with properties from the source object merged in.
|
|
541
|
-
*
|
|
542
|
-
* @template T - Type of the target object.
|
|
543
|
-
* @template S - Type of the source object.
|
|
544
|
-
*
|
|
545
|
-
* @example
|
|
546
|
-
* const target = { a: 1, b: { x: 1, y: 2 } };
|
|
547
|
-
* const source = { b: { y: 3, z: 4 }, c: 5 };
|
|
548
|
-
*
|
|
549
|
-
* const result = merge(target, source);
|
|
550
|
-
* console.log(result);
|
|
551
|
-
* // Output: { a: 1, b: { x: 1, y: 3, z: 4 }, c: 5 }
|
|
552
|
-
*
|
|
553
|
-
* @example
|
|
554
|
-
* const target = { a: [1, 2], b: { x: 1 } };
|
|
555
|
-
* const source = { a: [3], b: { y: 2 } };
|
|
556
|
-
*
|
|
557
|
-
* const result = merge(target, source);
|
|
558
|
-
* console.log(result);
|
|
559
|
-
* // Output: { a: [3, 2], b: { x: 1, y: 2 } }
|
|
560
|
-
*
|
|
561
|
-
* @example
|
|
562
|
-
* const target = { a: null };
|
|
563
|
-
* const source = { a: [1, 2, 3] };
|
|
564
|
-
*
|
|
565
|
-
* const result = merge(target, source);
|
|
566
|
-
* console.log(result);
|
|
567
|
-
* // Output: { a: [1, 2, 3] }
|
|
568
|
-
*/
|
|
569
|
-
function merge(target, source) {
|
|
570
|
-
const sourceKeys = Object.keys(source);
|
|
571
|
-
for (let i = 0; i < sourceKeys.length; i++) {
|
|
572
|
-
const key = sourceKeys[i];
|
|
573
|
-
if (isUnsafeProperty(key)) continue;
|
|
574
|
-
const sourceValue = source[key];
|
|
575
|
-
const targetValue = target[key];
|
|
576
|
-
if (isMergeableValue(sourceValue) && isMergeableValue(targetValue)) target[key] = merge(targetValue, sourceValue);
|
|
577
|
-
else if (Array.isArray(sourceValue)) target[key] = merge([], sourceValue);
|
|
578
|
-
else if (isPlainObject(sourceValue)) target[key] = merge({}, sourceValue);
|
|
579
|
-
else if (targetValue === void 0 || sourceValue !== void 0) target[key] = sourceValue;
|
|
580
|
-
}
|
|
581
|
-
return target;
|
|
582
|
-
}
|
|
583
|
-
function isMergeableValue(value) {
|
|
584
|
-
return isPlainObject(value) || Array.isArray(value);
|
|
585
|
-
}
|
|
586
|
-
//#endregion
|
|
587
|
-
//#region src/dataTransfer/shared/httpClient/constants.ts
|
|
588
|
-
var API_BASE_URL = "/_/api";
|
|
589
|
-
//#endregion
|
|
590
|
-
//#region src/dataTransfer/shared/httpClient/createHttpClient.ts
|
|
591
|
-
var defaultConfig = {
|
|
592
|
-
baseURL: API_BASE_URL,
|
|
593
|
-
headers: {
|
|
594
|
-
"Content-Type": "application/json",
|
|
595
|
-
Accept: "application/json; application/problem+json"
|
|
596
|
-
}
|
|
597
|
-
};
|
|
598
|
-
var createHttpClient = (customOptions) => {
|
|
599
|
-
const { baseURL = API_BASE_URL, ...otherCustomOptions } = customOptions ?? {};
|
|
600
|
-
return ofetch.create(merge({
|
|
601
|
-
baseURL,
|
|
602
|
-
...otherCustomOptions
|
|
603
|
-
}, defaultConfig));
|
|
604
|
-
};
|
|
605
|
-
//#endregion
|
|
606
|
-
//#region src/dataTransfer/upload/manager/index.ts
|
|
607
|
-
var CHUNK_SIZE = 50 * 1024 * 1024;
|
|
608
|
-
var DEFAULT_RETRY_COUNT = 5;
|
|
609
|
-
var DEFAULT_RETRY_DELAY_MS$1 = 50;
|
|
610
|
-
var uploadChunkWithProgress = (params) => {
|
|
611
|
-
const { method, url, chunk, chunkIndex, onProgress, abortSignal } = params;
|
|
612
|
-
const OK = 200;
|
|
613
|
-
const CREATED = 201;
|
|
614
|
-
return new Promise((resolve, reject) => {
|
|
615
|
-
const xhr = new XMLHttpRequest();
|
|
616
|
-
xhr.open(method, url, true);
|
|
617
|
-
abortSignal.addEventListener("abort", () => xhr.abort());
|
|
618
|
-
xhr.upload.onprogress = (event) => {
|
|
619
|
-
if (event.lengthComputable) onProgress(event.loaded);
|
|
620
|
-
};
|
|
621
|
-
xhr.onload = () => {
|
|
622
|
-
if (xhr.status === OK || xhr.status === CREATED) resolve({ partId: xhr.getResponseHeader("ETag")?.replace(/^(?:W\/)?"|"$/g, "") ?? "" });
|
|
623
|
-
else {
|
|
624
|
-
consola.error("Failed or unexpected XHR response", {
|
|
625
|
-
xhrStatus: xhr.status,
|
|
626
|
-
xhrResponse: xhr.response,
|
|
627
|
-
chunkIndex
|
|
628
|
-
});
|
|
629
|
-
reject(/* @__PURE__ */ new Error("Failed to upload file"));
|
|
630
|
-
}
|
|
631
|
-
};
|
|
632
|
-
xhr.onerror = (event) => {
|
|
633
|
-
consola.error("Failed to upload chunk", {
|
|
634
|
-
xhrErrorEvent: event,
|
|
635
|
-
chunkIndex
|
|
636
|
-
});
|
|
637
|
-
reject(/* @__PURE__ */ new Error("Failed to upload file"));
|
|
638
|
-
};
|
|
639
|
-
xhr.send(chunk);
|
|
640
|
-
});
|
|
300
|
+
//#region src/dataTransfer/download/useDownloadArtifact.ts
|
|
301
|
+
var DEFAULT_MAX_RETRIES = null;
|
|
302
|
+
var DEFAULT_POLLING_INTERVAL = 2e3;
|
|
303
|
+
var hasDownloadUrl = (response) => {
|
|
304
|
+
return "downloadUrl" in response;
|
|
641
305
|
};
|
|
306
|
+
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
|
|
642
307
|
/**
|
|
643
|
-
*
|
|
644
|
-
*
|
|
645
|
-
* The `uploadManager` offers functionality for handling file uploads in parts and provides
|
|
646
|
-
* several built-in mechanisms to ensure the process is reliable and adaptable.
|
|
647
|
-
*
|
|
648
|
-
* Key features include:
|
|
308
|
+
* This composable supports multiple concurrent downloads. Each time you call ```start```, the following behavior applies:
|
|
649
309
|
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
310
|
+
* 1. **Immediate-ready artifact**: If the first status check for the artifact returns `READY`, then there's no polling and no need
|
|
311
|
+
* to add it to `downloadItems`. The download URL is opened in the same tab immediately.
|
|
652
312
|
*
|
|
653
|
-
*
|
|
654
|
-
*
|
|
313
|
+
* 2. **Not-ready artifact**: If the artifact is not yet ready (e.g. needs zipping), then we add a record to `downloadItems` with status `IN_PROGRESS`
|
|
314
|
+
* and repeatedly poll until it becomes `READY`, the polling times out, or the user aborts.
|
|
655
315
|
*
|
|
656
|
-
*
|
|
316
|
+
* **Usage**:
|
|
317
|
+
* ```
|
|
318
|
+
* const { start, cancel, downloadItems } = useDownloadArtifact({ ...options });
|
|
657
319
|
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
320
|
+
* // start a download
|
|
321
|
+
* await start({ itemId: "1234", version: 1 });
|
|
660
322
|
*
|
|
661
|
-
*
|
|
662
|
-
*
|
|
663
|
-
* This ensures that the promise it returns completes gracefully without uncaught exceptions.
|
|
323
|
+
* // if needed, cancel it:
|
|
324
|
+
* cancel("1234");
|
|
664
325
|
*
|
|
326
|
+
* // downloadItems (as a computed array) will reflect the current statuses.
|
|
327
|
+
* ```
|
|
665
328
|
*/
|
|
666
|
-
var
|
|
667
|
-
const
|
|
668
|
-
const
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
const
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
329
|
+
var useDownloadArtifact = (options = {}) => {
|
|
330
|
+
const $ofetch = createHttpClient(options.customFetchClientOptions);
|
|
331
|
+
const downloadItems = ref({});
|
|
332
|
+
const abortControllers = {};
|
|
333
|
+
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
334
|
+
const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
335
|
+
const cancel = (downloadId) => {
|
|
336
|
+
const itemController = abortControllers[downloadId];
|
|
337
|
+
if (itemController) {
|
|
338
|
+
itemController.abort();
|
|
339
|
+
delete abortControllers[downloadId];
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const removeItem = (downloadId) => {
|
|
343
|
+
cancel(downloadId);
|
|
344
|
+
delete downloadItems.value[downloadId];
|
|
345
|
+
};
|
|
346
|
+
const fetchDownloadStatus = ({ downloadId, signal }) => {
|
|
347
|
+
return $ofetch(`/downloads/${downloadId}/status`, {
|
|
348
|
+
method: "GET",
|
|
349
|
+
signal
|
|
678
350
|
});
|
|
679
351
|
};
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
352
|
+
const openDownload = (downloadUrl) => {
|
|
353
|
+
const a = document.createElement("a");
|
|
354
|
+
a.target = "_blank";
|
|
355
|
+
a.href = downloadUrl;
|
|
356
|
+
a.download = "";
|
|
357
|
+
a.style.display = "none";
|
|
358
|
+
document.body.appendChild(a);
|
|
359
|
+
a.click();
|
|
360
|
+
document.body.removeChild(a);
|
|
687
361
|
};
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Makes a request to the download status endpoint for the given download item.
|
|
364
|
+
* Returns a boolean that is true if a terminal state has been reached (download is ready,
|
|
365
|
+
* download failed, or user aborted download)
|
|
366
|
+
* @param downloadId the `downloadId`
|
|
367
|
+
* @param signal the abort signal passed to the request to be able to abort it
|
|
368
|
+
* @returns a boolean indicating whether a terminal state has been reached
|
|
369
|
+
*/
|
|
370
|
+
const handlePollingResult = async ({ downloadId, signal }) => {
|
|
371
|
+
try {
|
|
372
|
+
const statusResponse = await fetchDownloadStatus({
|
|
373
|
+
downloadId,
|
|
374
|
+
signal
|
|
375
|
+
});
|
|
376
|
+
if (statusResponse?.status === "READY" && statusResponse?.downloadUrl) {
|
|
377
|
+
downloadItems.value[downloadId].status = "READY";
|
|
378
|
+
downloadItems.value[downloadId].downloadUrl = statusResponse.downloadUrl;
|
|
379
|
+
openDownload(statusResponse.downloadUrl);
|
|
380
|
+
return true;
|
|
381
|
+
} else if (statusResponse?.status === "FAILED") {
|
|
382
|
+
let details = "Download failed";
|
|
383
|
+
if (statusResponse?.statusMessage) details += `: ${statusResponse.statusMessage}`;
|
|
384
|
+
throw new Error(details);
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
consola.error("Error fetching status:", error);
|
|
388
|
+
if (!(isAbortError(error) || error instanceof FetchError && isAbortError(error.cause))) {
|
|
389
|
+
downloadItems.value[downloadId].status = "FAILED";
|
|
390
|
+
downloadItems.value[downloadId].failureDetails = error instanceof Error ? error.message : error;
|
|
391
|
+
}
|
|
392
|
+
return true;
|
|
692
393
|
}
|
|
693
|
-
|
|
694
|
-
abortInFlightUpload(uploadId, "upload cancelled");
|
|
695
|
-
cancelledUploads.add(uploadId);
|
|
394
|
+
return false;
|
|
696
395
|
};
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
396
|
+
const pollDownloadItem = async ({ itemId, downloadId, version, name }) => {
|
|
397
|
+
const controller = new AbortController();
|
|
398
|
+
abortControllers[downloadId] = controller;
|
|
399
|
+
const signal = controller.signal;
|
|
400
|
+
let itemName = `${name}`;
|
|
401
|
+
if (version) itemName += ` (${version})`;
|
|
402
|
+
downloadItems.value[downloadId] = {
|
|
403
|
+
itemId,
|
|
404
|
+
...version && { version },
|
|
405
|
+
downloadId,
|
|
406
|
+
name: itemName,
|
|
407
|
+
status: "IN_PROGRESS"
|
|
408
|
+
};
|
|
409
|
+
const abortListener = () => {
|
|
410
|
+
controller.signal.removeEventListener("abort", abortListener);
|
|
411
|
+
if (!downloadItems.value[downloadId]) return;
|
|
412
|
+
downloadItems.value[downloadId].status = "CANCELLED";
|
|
413
|
+
downloadItems.value[downloadId].failureDetails = "Download was cancelled manually";
|
|
414
|
+
};
|
|
415
|
+
controller.signal.addEventListener("abort", abortListener);
|
|
416
|
+
let tries = 0;
|
|
417
|
+
while (maxRetries === null || tries < maxRetries) {
|
|
418
|
+
tries++;
|
|
419
|
+
await sleep(pollingInterval);
|
|
420
|
+
if (await handlePollingResult({
|
|
421
|
+
downloadId,
|
|
422
|
+
signal
|
|
423
|
+
})) break;
|
|
424
|
+
}
|
|
425
|
+
delete abortControllers[downloadId];
|
|
426
|
+
if (maxRetries !== null && tries >= maxRetries) {
|
|
427
|
+
downloadItems.value[downloadId].status = "FAILED";
|
|
428
|
+
downloadItems.value[downloadId].failureDetails = "Download timed out";
|
|
701
429
|
}
|
|
702
|
-
setUploadState(uploadId, { state: "failed" });
|
|
703
|
-
onFileUploadFailed?.({
|
|
704
|
-
uploadId,
|
|
705
|
-
error
|
|
706
|
-
});
|
|
707
|
-
failedUploads.add(uploadId);
|
|
708
|
-
if (completedUploads.has(uploadId)) completedUploads.delete(uploadId);
|
|
709
430
|
};
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
state: "inprogress",
|
|
719
|
-
uploadedByteCount: uploadStateMap.get(uploadId)?.uploadedByteCount ?? 0,
|
|
720
|
-
completedChunkCount: uploadStateMap.get(uploadId)?.completedChunkCount ?? 0,
|
|
721
|
-
abortController
|
|
722
|
-
});
|
|
723
|
-
let chunkIndex = initialChunkIndex;
|
|
724
|
-
let uploadedBytes = uploadStateMap.get(uploadId)?.uploadedByteCount;
|
|
725
|
-
while (chunkIndex < totalChunks) {
|
|
726
|
-
if (isCancelled(uploadId) || isFailed(uploadId)) break;
|
|
727
|
-
const start = chunkIndex * CHUNK_SIZE;
|
|
728
|
-
const end = Math.min(start + CHUNK_SIZE, file.size);
|
|
729
|
-
const chunk = file.slice(start, end);
|
|
730
|
-
try {
|
|
731
|
-
const { method, url } = await promise_exports.retryPromise({
|
|
732
|
-
fn: () => getUploadFilePartUrl(uploadId, chunkIndex + 1),
|
|
733
|
-
retryCount: DEFAULT_RETRY_COUNT,
|
|
734
|
-
retryDelayMS: DEFAULT_RETRY_DELAY_MS$1
|
|
735
|
-
});
|
|
736
|
-
consola.trace("File part upload url resolved", {
|
|
737
|
-
method,
|
|
738
|
-
url
|
|
739
|
-
});
|
|
740
|
-
const onChunkUploadProgress = (uploadedChunkBytes) => {
|
|
741
|
-
uploadedBytes = uploadedChunkBytes + start;
|
|
742
|
-
setUploadState(uploadId, { uploadedByteCount: uploadedBytes });
|
|
743
|
-
const progress = Math.round(uploadedBytes / totalBytes * 100);
|
|
744
|
-
config.onProgress?.(uploadId, progress);
|
|
745
|
-
};
|
|
746
|
-
const filePartResponse = await runAbortablePromise(() => promise_exports.retryPromise({
|
|
747
|
-
fn: () => uploadChunkWithProgress({
|
|
748
|
-
method,
|
|
749
|
-
url,
|
|
750
|
-
chunk,
|
|
751
|
-
chunkIndex,
|
|
752
|
-
onProgress: onChunkUploadProgress,
|
|
753
|
-
abortSignal: abortController.signal
|
|
754
|
-
}),
|
|
755
|
-
retryCount: DEFAULT_RETRY_COUNT,
|
|
756
|
-
retryDelayMS: DEFAULT_RETRY_DELAY_MS$1
|
|
757
|
-
}));
|
|
758
|
-
consola.trace(`${file.name}: Chunk ${chunkIndex + 1} of ${totalChunks} uploaded successfully`);
|
|
759
|
-
completedPartIds[chunkIndex + 1] = filePartResponse.partId;
|
|
760
|
-
setUploadState(uploadId, { completedChunkCount: uploadStateMap.get(uploadId).completedChunkCount + 1 });
|
|
761
|
-
} catch (error) {
|
|
762
|
-
if (error instanceof promise_exports.AbortError) {
|
|
763
|
-
consola.info("Aborted::", error);
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
setFailed(uploadId, error instanceof Error ? error : new Error(error));
|
|
431
|
+
const start = async ({ itemId, version, name }) => {
|
|
432
|
+
try {
|
|
433
|
+
const response = await $ofetch(`/repository/${itemId}/artifact`, {
|
|
434
|
+
method: "GET",
|
|
435
|
+
query: { version }
|
|
436
|
+
});
|
|
437
|
+
if (hasDownloadUrl(response)) {
|
|
438
|
+
openDownload(response.downloadUrl);
|
|
767
439
|
return;
|
|
768
440
|
}
|
|
769
|
-
|
|
441
|
+
await pollDownloadItem({
|
|
442
|
+
itemId,
|
|
443
|
+
version,
|
|
444
|
+
downloadId: response.downloadId,
|
|
445
|
+
name
|
|
446
|
+
});
|
|
447
|
+
} catch (error) {
|
|
448
|
+
throw rfcErrors.tryParse(error) ?? error;
|
|
770
449
|
}
|
|
771
|
-
if (isCancelled(uploadId) || isFailed(uploadId)) return;
|
|
772
|
-
setUploadState(uploadId, {
|
|
773
|
-
completedChunkCount: totalChunks,
|
|
774
|
-
state: "complete"
|
|
775
|
-
});
|
|
776
|
-
completedUploads.add(uploadId);
|
|
777
|
-
onFileUploadComplete?.({
|
|
778
|
-
uploadId,
|
|
779
|
-
filePartIds: completedPartIds
|
|
780
|
-
});
|
|
781
450
|
};
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
for (const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
[
|
|
790
|
-
...completed,
|
|
791
|
-
...cancelled,
|
|
792
|
-
...failed
|
|
793
|
-
].forEach((uploadId) => uploadStateMap.delete(uploadId));
|
|
794
|
-
completedUploads.clear();
|
|
795
|
-
cancelledUploads.clear();
|
|
796
|
-
failedUploads.clear();
|
|
797
|
-
return {
|
|
798
|
-
completed,
|
|
799
|
-
cancelled,
|
|
800
|
-
failed
|
|
801
|
-
};
|
|
802
|
-
});
|
|
451
|
+
const totalItemsBeingZipped = computed(() => Object.values(downloadItems.value).filter(({ status }) => status === "IN_PROGRESS").length);
|
|
452
|
+
const resetState = () => {
|
|
453
|
+
for (const [downloadId, controller] of Object.entries(abortControllers)) {
|
|
454
|
+
controller?.abort();
|
|
455
|
+
delete abortControllers[downloadId];
|
|
456
|
+
}
|
|
457
|
+
downloadItems.value = {};
|
|
803
458
|
};
|
|
804
459
|
return {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
460
|
+
/**
|
|
461
|
+
* Initiates a download of the given itemId and version.
|
|
462
|
+
*/
|
|
463
|
+
start,
|
|
464
|
+
/**
|
|
465
|
+
* Cancels the ongoing download for a given itemId (if any).
|
|
466
|
+
* Sets the status in `downloadItems` to `CANCELLED`.
|
|
467
|
+
*/
|
|
809
468
|
cancel,
|
|
810
|
-
|
|
469
|
+
/**
|
|
470
|
+
* Removes item from `downloadItems`.
|
|
471
|
+
*/
|
|
472
|
+
removeItem,
|
|
473
|
+
/**
|
|
474
|
+
* A computed array of all current items that are being zipped and prepared for download.
|
|
475
|
+
*/
|
|
476
|
+
downloadItems: computed(() => Object.values(downloadItems.value)),
|
|
477
|
+
/**
|
|
478
|
+
* The total number of zipping items in progress.
|
|
479
|
+
*/
|
|
480
|
+
totalItemsBeingZipped,
|
|
481
|
+
/**
|
|
482
|
+
* Resets the state of the composable, clearing all downloads and controllers.
|
|
483
|
+
*/
|
|
484
|
+
resetState,
|
|
485
|
+
/**
|
|
486
|
+
* Opens a download from the given url
|
|
487
|
+
*/
|
|
488
|
+
openDownload
|
|
811
489
|
};
|
|
812
490
|
};
|
|
813
491
|
//#endregion
|
|
814
|
-
//#region src/dataTransfer/upload/
|
|
492
|
+
//#region src/dataTransfer/upload/UploadProgressPanelItem.vue
|
|
493
|
+
var UploadProgressPanelItem_default = /* @__PURE__ */ defineComponent({
|
|
494
|
+
__name: "UploadProgressPanelItem",
|
|
495
|
+
props: { item: {} },
|
|
496
|
+
emits: ["cancel", "viewDetails"],
|
|
497
|
+
setup(__props, { emit: __emit }) {
|
|
498
|
+
const props = __props;
|
|
499
|
+
const emit = __emit;
|
|
500
|
+
const subtitle = computed(() => {
|
|
501
|
+
if (props.item.type === "file") return `${formatFileSize(props.item.size / 100 * (props.item.progress ?? 0))} of ${formatFileSize(props.item.size)}`;
|
|
502
|
+
if (props.item.type === "directory") {
|
|
503
|
+
const total = props.item.totalEntries;
|
|
504
|
+
return `${props.item.uploadedEntries} of ${total} file${total > 1 ? "s" : ""}`;
|
|
505
|
+
}
|
|
506
|
+
return "";
|
|
507
|
+
});
|
|
508
|
+
const progressPercentageValue = computed(() => {
|
|
509
|
+
if (props.item.type === "file") return props.item.progress;
|
|
510
|
+
if (props.item.type === "directory") return props.item.uploadedEntries * 100 / props.item.totalEntries;
|
|
511
|
+
});
|
|
512
|
+
const statusMapper = {
|
|
513
|
+
inprogress: [
|
|
514
|
+
"Uploading",
|
|
515
|
+
"info",
|
|
516
|
+
"reload"
|
|
517
|
+
],
|
|
518
|
+
failed: [
|
|
519
|
+
"Failed",
|
|
520
|
+
"error",
|
|
521
|
+
"circle-error"
|
|
522
|
+
],
|
|
523
|
+
complete: [
|
|
524
|
+
"Uploaded",
|
|
525
|
+
"success",
|
|
526
|
+
"circle-success"
|
|
527
|
+
],
|
|
528
|
+
cancelled: [
|
|
529
|
+
"Cancelled",
|
|
530
|
+
"error",
|
|
531
|
+
"circle-error"
|
|
532
|
+
],
|
|
533
|
+
processing: [
|
|
534
|
+
"Processing",
|
|
535
|
+
"info",
|
|
536
|
+
"reload"
|
|
537
|
+
],
|
|
538
|
+
"partial-success": [
|
|
539
|
+
"Partial success",
|
|
540
|
+
"warning",
|
|
541
|
+
"warning"
|
|
542
|
+
]
|
|
543
|
+
};
|
|
544
|
+
const itemIcon = computed(() => {
|
|
545
|
+
if (props.item.type === "directory") return "folder";
|
|
546
|
+
return getFileExtension(props.item.name) === knimeFileFormats.KNWF.extension ? "workflow" : "file";
|
|
547
|
+
});
|
|
548
|
+
const badge = computed(() => {
|
|
549
|
+
if (!props.item.status) return;
|
|
550
|
+
const [text, variant, icon] = statusMapper[props.item.status];
|
|
551
|
+
return {
|
|
552
|
+
text,
|
|
553
|
+
variant,
|
|
554
|
+
icon
|
|
555
|
+
};
|
|
556
|
+
});
|
|
557
|
+
const isCancellable = computed(() => {
|
|
558
|
+
if (props.item.actions === void 0 || props.item.actions.cancellable === void 0) return true;
|
|
559
|
+
return props.item.actions.cancellable;
|
|
560
|
+
});
|
|
561
|
+
const shouldShowCancelAction = computed(() => props.item.status === "inprogress" && isCancellable.value);
|
|
562
|
+
const isInspectable = computed(() => {
|
|
563
|
+
if (props.item.actions === void 0 || props.item.actions.inspectable === void 0) return false;
|
|
564
|
+
return props.item.actions.inspectable;
|
|
565
|
+
});
|
|
566
|
+
const shouldShowViewDetailsAction = computed(() => props.item.status === "partial-success" && isInspectable.value);
|
|
567
|
+
const actions = computed(() => {
|
|
568
|
+
const result = [];
|
|
569
|
+
if (shouldShowCancelAction.value) result.push({
|
|
570
|
+
id: "cancel",
|
|
571
|
+
icon: "x-close",
|
|
572
|
+
label: "Cancel",
|
|
573
|
+
handler: () => emit("cancel")
|
|
574
|
+
});
|
|
575
|
+
if (shouldShowViewDetailsAction.value) result.push({
|
|
576
|
+
id: "view-details",
|
|
577
|
+
icon: "circle-info",
|
|
578
|
+
label: "View details",
|
|
579
|
+
handler: () => emit("viewDetails")
|
|
580
|
+
});
|
|
581
|
+
return result;
|
|
582
|
+
});
|
|
583
|
+
return (_ctx, _cache) => {
|
|
584
|
+
return openBlock(), createBlock(unref(ProgressItem_default), {
|
|
585
|
+
id: __props.item.id,
|
|
586
|
+
headline: __props.item.name,
|
|
587
|
+
subtitle: subtitle.value,
|
|
588
|
+
icon: itemIcon.value,
|
|
589
|
+
progress: progressPercentageValue.value,
|
|
590
|
+
badge: badge.value,
|
|
591
|
+
actions: actions.value
|
|
592
|
+
}, null, 8, [
|
|
593
|
+
"id",
|
|
594
|
+
"headline",
|
|
595
|
+
"subtitle",
|
|
596
|
+
"icon",
|
|
597
|
+
"progress",
|
|
598
|
+
"badge",
|
|
599
|
+
"actions"
|
|
600
|
+
]);
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
//#endregion
|
|
605
|
+
//#region src/dataTransfer/upload/UploadProgressPanel.vue?vue&type=script&setup=true&lang.ts
|
|
606
|
+
var _hoisted_1 = { class: "upload-panel-content" };
|
|
607
|
+
var _hoisted_2 = { class: "skeleton-text-content" };
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/dataTransfer/upload/UploadProgressPanel.vue
|
|
610
|
+
var UploadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
|
|
611
|
+
__name: "UploadProgressPanel",
|
|
612
|
+
props: /*@__PURE__*/ mergeModels({
|
|
613
|
+
items: {},
|
|
614
|
+
placeholderItems: { default: 0 }
|
|
615
|
+
}, {
|
|
616
|
+
"expanded": {
|
|
617
|
+
type: Boolean,
|
|
618
|
+
default: true
|
|
619
|
+
},
|
|
620
|
+
"expandedModifiers": {}
|
|
621
|
+
}),
|
|
622
|
+
emits: /*@__PURE__*/ mergeModels([
|
|
623
|
+
"cancel",
|
|
624
|
+
"viewDetails",
|
|
625
|
+
"close"
|
|
626
|
+
], ["update:expanded"]),
|
|
627
|
+
setup(__props, { emit: __emit }) {
|
|
628
|
+
const emit = __emit;
|
|
629
|
+
const expanded = useModel(__props, "expanded");
|
|
630
|
+
const totalItems = computed(() => __props.items.length);
|
|
631
|
+
const uploadedItems = computed(() => __props.items.filter(({ status }) => status === "complete").length);
|
|
632
|
+
const headline = computed(() => {
|
|
633
|
+
const suffix = `item${totalItems.value > 1 ? "s" : ""}`;
|
|
634
|
+
return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;
|
|
635
|
+
});
|
|
636
|
+
const hasSomeItemInProgress = computed(() => __props.items.some(({ status }) => status === "inprogress" || status === "processing") || __props.placeholderItems > 0);
|
|
637
|
+
return (_ctx, _cache) => {
|
|
638
|
+
return openBlock(), createBlock(unref(CollapsiblePanel_default), {
|
|
639
|
+
modelValue: expanded.value,
|
|
640
|
+
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => expanded.value = $event),
|
|
641
|
+
headline: headline.value,
|
|
642
|
+
class: "upload-panel",
|
|
643
|
+
"can-be-dismissed": !hasSomeItemInProgress.value,
|
|
644
|
+
onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
|
|
645
|
+
}, {
|
|
646
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_1, [createVNode(unref(ProgressList_default), null, {
|
|
647
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
|
|
648
|
+
return openBlock(), createBlock(UploadProgressPanelItem_default, {
|
|
649
|
+
key: item.id,
|
|
650
|
+
item,
|
|
651
|
+
onCancel: ($event) => emit("cancel", item),
|
|
652
|
+
onViewDetails: ($event) => emit("viewDetails", item)
|
|
653
|
+
}, null, 8, [
|
|
654
|
+
"item",
|
|
655
|
+
"onCancel",
|
|
656
|
+
"onViewDetails"
|
|
657
|
+
]);
|
|
658
|
+
}), 128))]),
|
|
659
|
+
_: 1
|
|
660
|
+
}), __props.placeholderItems > 0 ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(__props.placeholderItems, (skeleton) => {
|
|
661
|
+
return openBlock(), createElementBlock("div", {
|
|
662
|
+
key: skeleton,
|
|
663
|
+
class: "skeleton-container"
|
|
664
|
+
}, [createVNode(unref(KdsLoadingSkeleton), { variant: "icon-large" }), createElementVNode("div", _hoisted_2, [createVNode(unref(KdsLoadingSkeleton), { variant: "label" }), createVNode(unref(KdsLoadingSkeleton), { variant: "text" })])]);
|
|
665
|
+
}), 128)) : createCommentVNode("", true)])]),
|
|
666
|
+
_: 1
|
|
667
|
+
}, 8, [
|
|
668
|
+
"modelValue",
|
|
669
|
+
"headline",
|
|
670
|
+
"can-be-dismissed"
|
|
671
|
+
]);
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
}), [["__scopeId", "data-v-5b1b5ab8"]]);
|
|
675
|
+
//#endregion
|
|
676
|
+
//#region src/dataTransfer/upload/useUploadManager.ts
|
|
815
677
|
/**
|
|
816
678
|
* This composable wraps the `uploadManager` utility
|
|
817
679
|
* in order to provide reactive state which will work tightly with the `UploadProgressPanel` component
|
|
@@ -820,17 +682,19 @@ var createUploadManager = (config) => {
|
|
|
820
682
|
* or status of each item being uploaded changes
|
|
821
683
|
*/
|
|
822
684
|
var useUploadManager = (options) => {
|
|
685
|
+
const { resolveCompleteStatus, ...restOptions } = options;
|
|
823
686
|
const uploadState = ref({});
|
|
824
687
|
const { uploadFiles, cancel, setFailed } = createUploadManager({
|
|
825
|
-
...
|
|
688
|
+
...restOptions,
|
|
826
689
|
onFileUploadComplete: (params) => {
|
|
827
690
|
const { parentId, name } = uploadState.value[params.uploadId];
|
|
828
|
-
|
|
829
|
-
options.onFileUploadComplete?.({
|
|
691
|
+
const payload = {
|
|
830
692
|
...params,
|
|
831
693
|
parentId,
|
|
832
694
|
name
|
|
833
|
-
}
|
|
695
|
+
};
|
|
696
|
+
uploadState.value[params.uploadId].status = resolveCompleteStatus?.(payload) ?? "complete";
|
|
697
|
+
options.onFileUploadComplete?.(payload);
|
|
834
698
|
},
|
|
835
699
|
onFileUploadFailed: ({ uploadId, error }) => {
|
|
836
700
|
const { parentId } = uploadState.value[uploadId];
|
|
@@ -857,13 +721,15 @@ var useUploadManager = (options) => {
|
|
|
857
721
|
name: file.name,
|
|
858
722
|
size: file.size,
|
|
859
723
|
progress: 0,
|
|
724
|
+
type: "file",
|
|
860
725
|
status: "inprogress",
|
|
861
726
|
parentId
|
|
862
727
|
};
|
|
863
728
|
});
|
|
864
|
-
await uploadFiles(uploadPayload);
|
|
729
|
+
return await uploadFiles(uploadPayload);
|
|
865
730
|
} catch (error) {
|
|
866
731
|
consola.error("Error uploading files", error);
|
|
732
|
+
return [];
|
|
867
733
|
}
|
|
868
734
|
};
|
|
869
735
|
const canCancel = (uploadId) => uploadState.value[uploadId]?.status === "inprogress" || uploadState.value[uploadId]?.status === "failed";
|
|
@@ -893,7 +759,13 @@ var useUploadManager = (options) => {
|
|
|
893
759
|
status: "failed",
|
|
894
760
|
failureDetails: error.message
|
|
895
761
|
};
|
|
896
|
-
setFailed(uploadId
|
|
762
|
+
setFailed(uploadId);
|
|
763
|
+
const { parentId } = uploadState.value[uploadId];
|
|
764
|
+
options.onFileUploadFailed?.({
|
|
765
|
+
parentId,
|
|
766
|
+
uploadId,
|
|
767
|
+
error
|
|
768
|
+
});
|
|
897
769
|
}
|
|
898
770
|
};
|
|
899
771
|
};
|
|
@@ -954,70 +826,55 @@ var getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
|
|
|
954
826
|
* Alternative to completion, you can also cancel an upload. For this, the composable makes a DELETE request
|
|
955
827
|
* providing the corresponding `uploadId` which will effectively cancel it.
|
|
956
828
|
*
|
|
957
|
-
*
|
|
958
829
|
* For a usage example, refer to the Docs page in storybook.
|
|
959
830
|
*/
|
|
960
831
|
var useFileUpload = (options = {}) => {
|
|
961
|
-
const
|
|
962
|
-
const prepareUpload = (parentId, files) => {
|
|
963
|
-
const fileDictionary = Object.fromEntries(files.map((file) => {
|
|
964
|
-
return [knimeFileFormats.KNWF.getNameOrDefault(file, file.name), file];
|
|
965
|
-
}));
|
|
966
|
-
const items = Object.fromEntries(Object.entries(fileDictionary).map(([name, file]) => [name, {
|
|
967
|
-
itemContentType: getFileMimeType(file),
|
|
968
|
-
itemContentSize: file.size
|
|
969
|
-
}]));
|
|
970
|
-
return $ofetch(`/repository/${parentId}/manifest`, {
|
|
971
|
-
method: "POST",
|
|
972
|
-
body: { items }
|
|
973
|
-
}).then(({ items }) => {
|
|
974
|
-
return Object.keys(items).map((name) => {
|
|
975
|
-
const { uploadId } = items[name];
|
|
976
|
-
return {
|
|
977
|
-
uploadId,
|
|
978
|
-
file: fileDictionary[name]
|
|
979
|
-
};
|
|
980
|
-
});
|
|
981
|
-
});
|
|
982
|
-
};
|
|
983
|
-
const resolveFilePartUploadURL = (uploadId, partNumber) => {
|
|
984
|
-
return $ofetch(`/uploads/${uploadId}/parts/?partNumber=${partNumber}`, { method: "POST" });
|
|
985
|
-
};
|
|
986
|
-
const completeUpload = (uploadId, partIds) => {
|
|
987
|
-
return $ofetch(`/uploads/${uploadId}`, {
|
|
988
|
-
method: "POST",
|
|
989
|
-
body: partIds
|
|
990
|
-
});
|
|
991
|
-
};
|
|
992
|
-
const cancelUpload = (uploadId) => {
|
|
993
|
-
return $ofetch(`/uploads/${uploadId}`, { method: "DELETE" });
|
|
994
|
-
};
|
|
995
|
-
const unprocessedUploads = reactive(/* @__PURE__ */ new Set());
|
|
996
|
-
const isKNWFFile = (file) => knimeFileFormats.KNWF.matches(file);
|
|
832
|
+
const uploadClient = createUploadClient({ customFetchClientOptions: options.customFetchClientOptions });
|
|
997
833
|
let useUploadManagerResult = null;
|
|
998
|
-
const
|
|
999
|
-
|
|
834
|
+
const getItemFromQueue = (uploadId) => {
|
|
835
|
+
const { uploadState } = useUploadManagerResult;
|
|
836
|
+
if (!uploadState.value[uploadId]) {
|
|
837
|
+
consola.warn(`Cannot get item in queue. Upload id "${uploadId}" doesn't exist`);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
return uploadState.value[uploadId];
|
|
1000
841
|
};
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
842
|
+
const updateItemInQueue = (uploadId, data) => {
|
|
843
|
+
const { uploadState } = useUploadManagerResult;
|
|
844
|
+
if (!uploadState.value[uploadId]) {
|
|
845
|
+
consola.warn(`Cannot update item in queue. Upload id "${uploadId}" doesn't exist`);
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
uploadState.value = {
|
|
849
|
+
...uploadState.value,
|
|
850
|
+
[uploadId]: {
|
|
851
|
+
...uploadState.value[uploadId],
|
|
852
|
+
...data
|
|
853
|
+
}
|
|
854
|
+
};
|
|
1004
855
|
};
|
|
856
|
+
const statusPoller = createUploadStatusPoller(uploadClient, {
|
|
857
|
+
onComplete: (uploadId) => updateItemInQueue(uploadId, { status: "complete" }),
|
|
858
|
+
onFail: (uploadId, statusMessage) => updateItemInQueue(uploadId, {
|
|
859
|
+
status: "failed",
|
|
860
|
+
failureDetails: statusMessage
|
|
861
|
+
})
|
|
862
|
+
});
|
|
1005
863
|
useUploadManagerResult = useUploadManager({
|
|
1006
|
-
resolveFilePartUploadURL,
|
|
1007
|
-
|
|
1008
|
-
|
|
864
|
+
resolveFilePartUploadURL: uploadClient.resolveFilePartUploadURL,
|
|
865
|
+
resolveCompleteStatus: ({ name }) => options.isFileWithProcessing?.(name) ? "processing" : "complete",
|
|
866
|
+
onFileUploadComplete: ({ uploadId, filePartIds, name, parentId }) => {
|
|
867
|
+
promise_exports.retryPromise({ fn: () => uploadClient.completeUpload(uploadId, filePartIds) }).then(() => {
|
|
868
|
+
if (options.isFileWithProcessing?.(name)) statusPoller.track(uploadId);
|
|
1009
869
|
options.onFileUploadComplete?.({
|
|
1010
870
|
uploadId,
|
|
1011
871
|
filePartIds,
|
|
1012
|
-
|
|
1013
|
-
|
|
872
|
+
name,
|
|
873
|
+
parentId
|
|
1014
874
|
});
|
|
1015
875
|
}).catch((error) => {
|
|
1016
876
|
consola.error("Error attempting to complete upload", { error });
|
|
1017
|
-
|
|
1018
|
-
uploadId,
|
|
1019
|
-
error
|
|
1020
|
-
});
|
|
877
|
+
useUploadManagerResult.setFailed(uploadId, error);
|
|
1021
878
|
});
|
|
1022
879
|
},
|
|
1023
880
|
onFileUploadFailed: ({ uploadId, error, parentId }) => {
|
|
@@ -1036,29 +893,16 @@ var useFileUpload = (options = {}) => {
|
|
|
1036
893
|
return files.slice(0, effectiveQueueSize);
|
|
1037
894
|
};
|
|
1038
895
|
const { uploadState, ...uploadManagerResultRest } = useUploadManagerResult;
|
|
1039
|
-
const uploadQueue = computed(() => {
|
|
1040
|
-
return Object.values(uploadState.value).map((uploadItem) => {
|
|
1041
|
-
if (uploadItem.status === "complete" && unprocessedUploads.has(uploadItem.id)) return {
|
|
1042
|
-
id: uploadItem.id,
|
|
1043
|
-
name: uploadItem.name,
|
|
1044
|
-
parentId: uploadItem.parentId,
|
|
1045
|
-
size: uploadItem.size,
|
|
1046
|
-
status: "processing"
|
|
1047
|
-
};
|
|
1048
|
-
return uploadItem;
|
|
1049
|
-
});
|
|
1050
|
-
});
|
|
1051
896
|
const prepareQueueSize = ref(0);
|
|
897
|
+
const uploadQueue = computed(() => Object.values(uploadState.value));
|
|
1052
898
|
return {
|
|
1053
899
|
...uploadManagerResultRest,
|
|
1054
900
|
uploadQueue,
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
setProcessingFailed,
|
|
901
|
+
getItemFromQueue,
|
|
902
|
+
updateItemInQueue,
|
|
1058
903
|
isPreparingUpload: computed(() => prepareQueueSize.value > 0),
|
|
1059
904
|
totalFilesBeingPrepared: computed(() => prepareQueueSize.value),
|
|
1060
|
-
start: async (parentId, files
|
|
1061
|
-
const { isFileWithProcessing = isKNWFFile } = options;
|
|
905
|
+
start: async (parentId, files) => {
|
|
1062
906
|
const enqueueableFiles = getEnqueueableFiles(files);
|
|
1063
907
|
const uploadSizeLimitBytes = getUploadSizeLimitBytes();
|
|
1064
908
|
const oversizedFiles = enqueueableFiles.filter((file) => file.size > uploadSizeLimitBytes);
|
|
@@ -1070,14 +914,15 @@ var useFileUpload = (options = {}) => {
|
|
|
1070
914
|
try {
|
|
1071
915
|
if (enqueueableFiles.length === 0) return [];
|
|
1072
916
|
prepareQueueSize.value += enqueueableFiles.length;
|
|
917
|
+
const items = enqueueableFiles.map((file) => ({
|
|
918
|
+
path: knimeFileFormats.KNWF.getNameOrDefault(file, file.name),
|
|
919
|
+
file
|
|
920
|
+
}));
|
|
1073
921
|
const uploadPayload = await promise_exports.retryPromise({
|
|
1074
|
-
fn: () => prepareUpload(parentId,
|
|
922
|
+
fn: () => uploadClient.prepareUpload(parentId, items),
|
|
1075
923
|
excludeError: (error) => Boolean(error.statusCode && error.statusCode < 500),
|
|
1076
924
|
retryDelayMS: DEFAULT_RETRY_DELAY_MS
|
|
1077
925
|
});
|
|
1078
|
-
uploadPayload.forEach(({ uploadId, file }) => {
|
|
1079
|
-
if (isFileWithProcessing(file)) unprocessedUploads.add(uploadId);
|
|
1080
|
-
});
|
|
1081
926
|
const uploadIds = uploadPayload.map((upload) => upload.uploadId);
|
|
1082
927
|
useUploadManagerResult.start(parentId, uploadPayload);
|
|
1083
928
|
return uploadIds;
|
|
@@ -1089,205 +934,13 @@ var useFileUpload = (options = {}) => {
|
|
|
1089
934
|
},
|
|
1090
935
|
cancel: (uploadId) => {
|
|
1091
936
|
useUploadManagerResult.cancel(uploadId);
|
|
1092
|
-
cancelUpload(uploadId).catch((error) => {
|
|
937
|
+
uploadClient.cancelUpload(uploadId).catch((error) => {
|
|
1093
938
|
consola.error("There was a problem cancelling the upload", { error });
|
|
1094
939
|
});
|
|
1095
940
|
}
|
|
1096
941
|
};
|
|
1097
942
|
};
|
|
1098
943
|
//#endregion
|
|
1099
|
-
|
|
1100
|
-
var DEFAULT_MAX_RETRIES = null;
|
|
1101
|
-
var DEFAULT_POLLING_INTERVAL = 2e3;
|
|
1102
|
-
var hasDownloadUrl = (response) => {
|
|
1103
|
-
return "downloadUrl" in response;
|
|
1104
|
-
};
|
|
1105
|
-
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
|
|
1106
|
-
/**
|
|
1107
|
-
* This composable supports multiple concurrent downloads. Each time you call ```start```, the following behavior applies:
|
|
1108
|
-
*
|
|
1109
|
-
* 1. **Immediate-ready artifact**: If the first status check for the artifact returns `READY`, then there's no polling and no need
|
|
1110
|
-
* to add it to `downloadItems`. The download URL is opened in the same tab immediately.
|
|
1111
|
-
*
|
|
1112
|
-
* 2. **Not-ready artifact**: If the artifact is not yet ready (e.g. needs zipping), then we add a record to `downloadItems` with status `IN_PROGRESS`
|
|
1113
|
-
* and repeatedly poll until it becomes `READY`, the polling times out, or the user aborts.
|
|
1114
|
-
*
|
|
1115
|
-
* **Usage**:
|
|
1116
|
-
* ```
|
|
1117
|
-
* const { start, cancel, downloadItems } = useDownloadArtifact({ ...options });
|
|
1118
|
-
*
|
|
1119
|
-
* // start a download
|
|
1120
|
-
* await start({ itemId: "1234", version: 1 });
|
|
1121
|
-
*
|
|
1122
|
-
* // if needed, cancel it:
|
|
1123
|
-
* cancel("1234");
|
|
1124
|
-
*
|
|
1125
|
-
* // downloadItems (as a computed array) will reflect the current statuses.
|
|
1126
|
-
* ```
|
|
1127
|
-
*/
|
|
1128
|
-
var useDownloadArtifact = (options = {}) => {
|
|
1129
|
-
const $ofetch = createHttpClient(options.customFetchClientOptions);
|
|
1130
|
-
const downloadItems = ref({});
|
|
1131
|
-
const abortControllers = {};
|
|
1132
|
-
const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1133
|
-
const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
|
|
1134
|
-
const cancel = (downloadId) => {
|
|
1135
|
-
const itemController = abortControllers[downloadId];
|
|
1136
|
-
if (itemController) {
|
|
1137
|
-
itemController.abort();
|
|
1138
|
-
delete abortControllers[downloadId];
|
|
1139
|
-
}
|
|
1140
|
-
};
|
|
1141
|
-
const removeItem = (downloadId) => {
|
|
1142
|
-
cancel(downloadId);
|
|
1143
|
-
delete downloadItems.value[downloadId];
|
|
1144
|
-
};
|
|
1145
|
-
const fetchDownloadStatus = ({ downloadId, signal }) => {
|
|
1146
|
-
return $ofetch(`/downloads/${downloadId}/status`, {
|
|
1147
|
-
method: "GET",
|
|
1148
|
-
signal
|
|
1149
|
-
});
|
|
1150
|
-
};
|
|
1151
|
-
const openDownload = (downloadUrl) => {
|
|
1152
|
-
const a = document.createElement("a");
|
|
1153
|
-
a.target = "_blank";
|
|
1154
|
-
a.href = downloadUrl;
|
|
1155
|
-
a.download = "";
|
|
1156
|
-
a.style.display = "none";
|
|
1157
|
-
document.body.appendChild(a);
|
|
1158
|
-
a.click();
|
|
1159
|
-
document.body.removeChild(a);
|
|
1160
|
-
};
|
|
1161
|
-
/**
|
|
1162
|
-
* Makes a request to the download status endpoint for the given download item.
|
|
1163
|
-
* Returns a boolean that is true if a terminal state has been reached (download is ready,
|
|
1164
|
-
* download failed, or user aborted download)
|
|
1165
|
-
* @param downloadId the `downloadId`
|
|
1166
|
-
* @param signal the abort signal passed to the request to be able to abort it
|
|
1167
|
-
* @returns a boolean indicating whether a terminal state has been reached
|
|
1168
|
-
*/
|
|
1169
|
-
const handlePollingResult = async ({ downloadId, signal }) => {
|
|
1170
|
-
try {
|
|
1171
|
-
const statusResponse = await fetchDownloadStatus({
|
|
1172
|
-
downloadId,
|
|
1173
|
-
signal
|
|
1174
|
-
});
|
|
1175
|
-
if (statusResponse?.status === "READY" && statusResponse?.downloadUrl) {
|
|
1176
|
-
downloadItems.value[downloadId].status = "READY";
|
|
1177
|
-
downloadItems.value[downloadId].downloadUrl = statusResponse.downloadUrl;
|
|
1178
|
-
openDownload(statusResponse.downloadUrl);
|
|
1179
|
-
return true;
|
|
1180
|
-
} else if (statusResponse?.status === "FAILED") {
|
|
1181
|
-
let details = "Download failed";
|
|
1182
|
-
if (statusResponse?.statusMessage) details += `: ${statusResponse.statusMessage}`;
|
|
1183
|
-
throw new Error(details);
|
|
1184
|
-
}
|
|
1185
|
-
} catch (error) {
|
|
1186
|
-
consola.error("Error fetching status:", error);
|
|
1187
|
-
if (!(isAbortError(error) || error instanceof FetchError && isAbortError(error.cause))) {
|
|
1188
|
-
downloadItems.value[downloadId].status = "FAILED";
|
|
1189
|
-
downloadItems.value[downloadId].failureDetails = error instanceof Error ? error.message : error;
|
|
1190
|
-
}
|
|
1191
|
-
return true;
|
|
1192
|
-
}
|
|
1193
|
-
return false;
|
|
1194
|
-
};
|
|
1195
|
-
const pollDownloadItem = async ({ itemId, downloadId, version, name }) => {
|
|
1196
|
-
const controller = new AbortController();
|
|
1197
|
-
abortControllers[downloadId] = controller;
|
|
1198
|
-
const signal = controller.signal;
|
|
1199
|
-
let itemName = `${name}`;
|
|
1200
|
-
if (version) itemName += ` (${version})`;
|
|
1201
|
-
downloadItems.value[downloadId] = {
|
|
1202
|
-
itemId,
|
|
1203
|
-
...version && { version },
|
|
1204
|
-
downloadId,
|
|
1205
|
-
name: itemName,
|
|
1206
|
-
status: "IN_PROGRESS"
|
|
1207
|
-
};
|
|
1208
|
-
const abortListener = () => {
|
|
1209
|
-
controller.signal.removeEventListener("abort", abortListener);
|
|
1210
|
-
if (!downloadItems.value[downloadId]) return;
|
|
1211
|
-
downloadItems.value[downloadId].status = "CANCELLED";
|
|
1212
|
-
downloadItems.value[downloadId].failureDetails = "Download was cancelled manually";
|
|
1213
|
-
};
|
|
1214
|
-
controller.signal.addEventListener("abort", abortListener);
|
|
1215
|
-
let tries = 0;
|
|
1216
|
-
while (maxRetries === null || tries < maxRetries) {
|
|
1217
|
-
tries++;
|
|
1218
|
-
await sleep(pollingInterval);
|
|
1219
|
-
if (await handlePollingResult({
|
|
1220
|
-
downloadId,
|
|
1221
|
-
signal
|
|
1222
|
-
})) break;
|
|
1223
|
-
}
|
|
1224
|
-
delete abortControllers[downloadId];
|
|
1225
|
-
if (maxRetries !== null && tries >= maxRetries) {
|
|
1226
|
-
downloadItems.value[downloadId].status = "FAILED";
|
|
1227
|
-
downloadItems.value[downloadId].failureDetails = "Download timed out";
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
const start = async ({ itemId, version, name }) => {
|
|
1231
|
-
try {
|
|
1232
|
-
const response = await $ofetch(`/repository/${itemId}/artifact`, {
|
|
1233
|
-
method: "GET",
|
|
1234
|
-
query: { version }
|
|
1235
|
-
});
|
|
1236
|
-
if (hasDownloadUrl(response)) {
|
|
1237
|
-
openDownload(response.downloadUrl);
|
|
1238
|
-
return;
|
|
1239
|
-
}
|
|
1240
|
-
await pollDownloadItem({
|
|
1241
|
-
itemId,
|
|
1242
|
-
version,
|
|
1243
|
-
downloadId: response.downloadId,
|
|
1244
|
-
name
|
|
1245
|
-
});
|
|
1246
|
-
} catch (error) {
|
|
1247
|
-
throw rfcErrors.tryParse(error) ?? error;
|
|
1248
|
-
}
|
|
1249
|
-
};
|
|
1250
|
-
const totalItemsBeingZipped = computed(() => Object.values(downloadItems.value).filter(({ status }) => status === "IN_PROGRESS").length);
|
|
1251
|
-
const resetState = () => {
|
|
1252
|
-
for (const [downloadId, controller] of Object.entries(abortControllers)) {
|
|
1253
|
-
controller?.abort();
|
|
1254
|
-
delete abortControllers[downloadId];
|
|
1255
|
-
}
|
|
1256
|
-
downloadItems.value = {};
|
|
1257
|
-
};
|
|
1258
|
-
return {
|
|
1259
|
-
/**
|
|
1260
|
-
* Initiates a download of the given itemId and version.
|
|
1261
|
-
*/
|
|
1262
|
-
start,
|
|
1263
|
-
/**
|
|
1264
|
-
* Cancels the ongoing download for a given itemId (if any).
|
|
1265
|
-
* Sets the status in `downloadItems` to `CANCELLED`.
|
|
1266
|
-
*/
|
|
1267
|
-
cancel,
|
|
1268
|
-
/**
|
|
1269
|
-
* Removes item from `downloadItems`.
|
|
1270
|
-
*/
|
|
1271
|
-
removeItem,
|
|
1272
|
-
/**
|
|
1273
|
-
* A computed array of all current items that are being zipped and prepared for download.
|
|
1274
|
-
*/
|
|
1275
|
-
downloadItems: computed(() => Object.values(downloadItems.value)),
|
|
1276
|
-
/**
|
|
1277
|
-
* The total number of zipping items in progress.
|
|
1278
|
-
*/
|
|
1279
|
-
totalItemsBeingZipped,
|
|
1280
|
-
/**
|
|
1281
|
-
* Resets the state of the composable, clearing all downloads and controllers.
|
|
1282
|
-
*/
|
|
1283
|
-
resetState,
|
|
1284
|
-
/**
|
|
1285
|
-
* Opens a download from the given url
|
|
1286
|
-
*/
|
|
1287
|
-
openDownload
|
|
1288
|
-
};
|
|
1289
|
-
};
|
|
1290
|
-
//#endregion
|
|
1291
|
-
export { API_BASE_URL, CollapsiblePanel_default as CollapsiblePanel, DownloadProgressPanel_default as DownloadProgressPanel, ProgressItem_default as ProgressItem, ProgressList_default as ProgressList, UploadProgressPanel_default as UploadProgressPanel, createHttpClient, defaultConfig, useDownloadArtifact, useFileUpload };
|
|
944
|
+
export { API_BASE_URL, CollapsiblePanel_default as CollapsiblePanel, DownloadProgressPanel_default as DownloadProgressPanel, ProgressItem_default as ProgressItem, ProgressList_default as ProgressList, UploadProgressPanel_default as UploadProgressPanel, createHttpClient, defaultConfig, useDownloadArtifact, useFileUpload, useUploadManager };
|
|
1292
945
|
|
|
1293
946
|
//# sourceMappingURL=dataTransfer.js.map
|