@knime/product-features 1.2.3 → 2.0.0

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +46 -0
  3. package/dist/_plugin-vue_export-helper-BOaGB7Aw.js +8 -0
  4. package/dist/ai.js +180 -191
  5. package/dist/ai.js.map +1 -1
  6. package/dist/dataTransfer.js +1158 -1104
  7. package/dist/dataTransfer.js.map +1 -1
  8. package/dist/dist-BrNh9vG-.js +2560 -0
  9. package/dist/dist-BrNh9vG-.js.map +1 -0
  10. package/dist/nodes.css +33 -67
  11. package/dist/nodes.js +538 -614
  12. package/dist/nodes.js.map +1 -1
  13. package/dist/rfcErrors-Dw7NLSDp.js +172 -0
  14. package/dist/rfcErrors-Dw7NLSDp.js.map +1 -0
  15. package/dist/rfcErrors.js +1 -222
  16. package/dist/src/ai/KdsPromptInput/InlineStatusBanner.vue.d.ts +3 -2
  17. package/dist/src/ai/KdsPromptInput/InlineStatusBanner.vue.d.ts.map +1 -1
  18. package/dist/src/ai/KdsPromptInput/KdsPromptInput.vue.d.ts +6 -6
  19. package/dist/src/ai/KdsPromptInput/KdsPromptInput.vue.d.ts.map +1 -1
  20. package/dist/src/dataTransfer/download/ui/DownloadProgressPanel.vue.d.ts +6 -4
  21. package/dist/src/dataTransfer/download/ui/DownloadProgressPanel.vue.d.ts.map +1 -1
  22. package/dist/src/dataTransfer/download/ui/DownloadProgressPanelItem.vue.d.ts +3 -2
  23. package/dist/src/dataTransfer/download/ui/DownloadProgressPanelItem.vue.d.ts.map +1 -1
  24. package/dist/src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue.d.ts +11 -14
  25. package/dist/src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue.d.ts.map +1 -1
  26. package/dist/src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue.d.ts +2 -1
  27. package/dist/src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue.d.ts.map +1 -1
  28. package/dist/src/dataTransfer/shared/ui/ProgressList/ProgressList.vue.d.ts +7 -11
  29. package/dist/src/dataTransfer/shared/ui/ProgressList/ProgressList.vue.d.ts.map +1 -1
  30. package/dist/src/dataTransfer/upload/ui/UploadProgressPanel.vue.d.ts +6 -4
  31. package/dist/src/dataTransfer/upload/ui/UploadProgressPanel.vue.d.ts.map +1 -1
  32. package/dist/src/dataTransfer/upload/ui/UploadProgressPanelItem.vue.d.ts +3 -2
  33. package/dist/src/dataTransfer/upload/ui/UploadProgressPanelItem.vue.d.ts.map +1 -1
  34. package/dist/src/nodes/NodeDescriptionTabs/NodeDescriptionTabs.vue.d.ts +2 -1
  35. package/dist/src/nodes/NodeDescriptionTabs/NodeDescriptionTabs.vue.d.ts.map +1 -1
  36. package/dist/src/nodes/NodeDescriptionTabs/OptionsTab.vue.d.ts +2 -1
  37. package/dist/src/nodes/NodeDescriptionTabs/OptionsTab.vue.d.ts.map +1 -1
  38. package/dist/src/nodes/NodeDescriptionTabs/PortGroup.vue.d.ts +2 -1
  39. package/dist/src/nodes/NodeDescriptionTabs/PortGroup.vue.d.ts.map +1 -1
  40. package/dist/src/nodes/NodeDescriptionTabs/PortTemplateInfo.vue.d.ts +2 -1
  41. package/dist/src/nodes/NodeDescriptionTabs/PortTemplateInfo.vue.d.ts.map +1 -1
  42. package/dist/src/nodes/NodeDescriptionTabs/PortsTab.vue.d.ts +2 -1
  43. package/dist/src/nodes/NodeDescriptionTabs/PortsTab.vue.d.ts.map +1 -1
  44. package/dist/src/nodes/NodeDescriptionTabs/ViewsTab.vue.d.ts +2 -1
  45. package/dist/src/nodes/NodeDescriptionTabs/ViewsTab.vue.d.ts.map +1 -1
  46. package/dist/src/nodes/NodeDescriptionTabs/types.d.ts +2 -2
  47. package/dist/src/nodes/NodeDescriptionTabs/types.d.ts.map +1 -1
  48. package/dist/src/nodes/NodePreview/NodePreview.vue.d.ts +2 -1
  49. package/dist/src/nodes/NodePreview/NodePreview.vue.d.ts.map +1 -1
  50. package/dist/src/nodes/NodePreview/SVGNodeTorso.vue.d.ts +2 -1
  51. package/dist/src/nodes/NodePreview/SVGNodeTorso.vue.d.ts.map +1 -1
  52. package/dist/src/nodes/NodePreview/enums.d.ts +2 -1
  53. package/dist/src/nodes/NodePreview/enums.d.ts.map +1 -1
  54. package/dist/src/nodes/NodePreview/types.d.ts +2 -3
  55. package/dist/src/nodes/NodePreview/types.d.ts.map +1 -1
  56. package/dist/src/nodes/index.d.ts +0 -2
  57. package/dist/src/nodes/index.d.ts.map +1 -1
  58. package/dist/src/rfcErrors/RFCErrorToastTemplate.vue.d.ts +3 -2
  59. package/dist/src/rfcErrors/RFCErrorToastTemplate.vue.d.ts.map +1 -1
  60. package/dist/src/toasts/Toast/Toast.vue.d.ts +3 -4
  61. package/dist/src/toasts/Toast/Toast.vue.d.ts.map +1 -1
  62. package/dist/src/toasts/Toast/ToastButton.vue.d.ts +2 -1
  63. package/dist/src/toasts/Toast/ToastButton.vue.d.ts.map +1 -1
  64. package/dist/src/toasts/ToastStack/ToastStack.vue.d.ts +2 -1
  65. package/dist/src/toasts/ToastStack/ToastStack.vue.d.ts.map +1 -1
  66. package/dist/src/versions/CreateVersionForm/CreateVersionForm.vue.d.ts +3 -2
  67. package/dist/src/versions/CreateVersionForm/CreateVersionForm.vue.d.ts.map +1 -1
  68. package/dist/src/versions/VersionHistory/CurrentState.vue.d.ts +3 -2
  69. package/dist/src/versions/VersionHistory/CurrentState.vue.d.ts.map +1 -1
  70. package/dist/src/versions/VersionHistory/HistoryEvent.vue.d.ts +7 -11
  71. package/dist/src/versions/VersionHistory/HistoryEvent.vue.d.ts.map +1 -1
  72. package/dist/src/versions/VersionHistory/HistoryEventAuthor.vue.d.ts +2 -1
  73. package/dist/src/versions/VersionHistory/HistoryEventAuthor.vue.d.ts.map +1 -1
  74. package/dist/src/versions/VersionHistory/HistoryEventFooter.vue.d.ts +2 -1
  75. package/dist/src/versions/VersionHistory/HistoryEventFooter.vue.d.ts.map +1 -1
  76. package/dist/src/versions/VersionHistory/VersionHistory.vue.d.ts +3 -2
  77. package/dist/src/versions/VersionHistory/VersionHistory.vue.d.ts.map +1 -1
  78. package/dist/src/versions/VersionHistory/VersionItem.vue.d.ts +3 -2
  79. package/dist/src/versions/VersionHistory/VersionItem.vue.d.ts.map +1 -1
  80. package/dist/src/versions/VersionHistory/VersionLabel.vue.d.ts +6 -141
  81. package/dist/src/versions/VersionHistory/VersionLabel.vue.d.ts.map +1 -1
  82. package/dist/src/versions/VersionHistory/VersionLimitInfo.vue.d.ts +2 -1
  83. package/dist/src/versions/VersionHistory/VersionLimitInfo.vue.d.ts.map +1 -1
  84. package/dist/toasts.css +13 -13
  85. package/dist/toasts.js +4700 -475
  86. package/dist/toasts.js.map +1 -1
  87. package/dist/versions.js +583 -597
  88. package/dist/versions.js.map +1 -1
  89. package/package.json +5 -5
  90. package/dist/_plugin-vue_export-helper-pcqpp-6-.js +0 -10
  91. package/dist/_plugin-vue_export-helper-pcqpp-6-.js.map +0 -1
  92. package/dist/index-B3ql6hun.js +0 -2695
  93. package/dist/index-B3ql6hun.js.map +0 -1
  94. package/dist/rfcErrors.js.map +0 -1
  95. package/dist/src/nodes/PortIcon/PortIcon.vue.d.ts +0 -4
  96. package/dist/src/nodes/PortIcon/PortIcon.vue.d.ts.map +0 -1
  97. package/dist/src/nodes/PortIcon/SVGPortIcon.vue.d.ts +0 -4
  98. package/dist/src/nodes/PortIcon/SVGPortIcon.vue.d.ts.map +0 -1
  99. package/dist/src/nodes/PortIcon/constants.d.ts +0 -3
  100. package/dist/src/nodes/PortIcon/constants.d.ts.map +0 -1
  101. package/dist/src/nodes/PortIcon/enums.d.ts +0 -8
  102. package/dist/src/nodes/PortIcon/enums.d.ts.map +0 -1
  103. package/dist/src/nodes/PortIcon/index.d.ts +0 -3
  104. package/dist/src/nodes/PortIcon/index.d.ts.map +0 -1
  105. package/dist/src/nodes/PortIcon/types.d.ts +0 -19
  106. package/dist/src/nodes/PortIcon/types.d.ts.map +0 -1
@@ -1,452 +1,466 @@
1
- import { computed, defineComponent, useModel, useId, openBlock, createElementBlock, normalizeClass, createElementVNode, createTextVNode, toDisplayString, createVNode, unref, normalizeStyle, createBlock, createCommentVNode, Transition, withCtx, withDirectives, renderSlot, vShow, mergeModels, Fragment, renderList, watch, ref, reactive, readonly } from 'vue';
2
- import { KdsButton, KdsIcon, vKdsTooltip, KdsBadge, KdsProgressBar, KdsLoadingSkeleton } from '@knime/kds-components';
3
- import { _ as _export_sfc } from './_plugin-vue_export-helper-pcqpp-6-.js';
4
- import { f as formatFileSize, g as getFileExtension, k as knimeFileFormats, i as index, a as getFileMimeType, s as sleep } from './index-B3ql6hun.js';
5
- import { rfcErrors } from './rfcErrors.js';
6
- import { ofetch, FetchError } from 'ofetch';
7
-
8
- import './dataTransfer.css';const useIconFlipper = (options) => {
9
- const iconFlipStyles = computed(() => {
10
- const declaration = {
11
- transition: "transform 0.2s linear",
12
- transform: "scaleY(1)"
13
- };
14
- if (options.initialRotation) {
15
- declaration.rotate = `${options.initialRotation}`;
16
- }
17
- const transform = options.active.value ? "scaleY(-1)" : void 0;
18
- return { ...declaration, transform };
19
- });
20
- return { iconFlipStyles };
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, o as getFileMimeType, r as formatFileSize, s as knimeFileFormats } from "./dist-BrNh9vG-.js";
3
+ import { t as rfcErrors } from "./rfcErrors-Dw7NLSDp.js";
4
+ import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeModels, normalizeClass, normalizeStyle, openBlock, reactive, readonly, ref, renderList, renderSlot, toDisplayString, unref, useId, useModel, vShow, watch, withCtx, withDirectives } from "vue";
5
+ import { KdsBadge, KdsButton, KdsIcon, KdsLoadingSkeleton, KdsProgressBar, vKdsTooltip } from "@knime/kds-components";
6
+ import { FetchError, ofetch } from "ofetch";
7
+ import './dataTransfer.css';//#region src/dataTransfer/shared/ui/CollapsiblePanel/useIconFlipper.ts
8
+ var useIconFlipper = (options) => {
9
+ return { iconFlipStyles: computed(() => {
10
+ const declaration = {
11
+ transition: "transform 0.2s linear",
12
+ transform: "scaleY(1)"
13
+ };
14
+ if (options.initialRotation) declaration.rotate = `${options.initialRotation}`;
15
+ const transform = options.active.value ? "scaleY(-1)" : void 0;
16
+ return {
17
+ ...declaration,
18
+ transform
19
+ };
20
+ }) };
21
21
  };
22
-
23
- const _hoisted_1$4 = { class: "title-bar" };
24
- const _hoisted_2$2 = { class: "actions" };
25
- const _hoisted_3$1 = ["id"];
26
- const _sfc_main$6 = /* @__PURE__ */ defineComponent({
27
- __name: "CollapsiblePanel",
28
- props: /* @__PURE__ */ mergeModels({
29
- headline: {},
30
- canBeDismissed: { type: Boolean, default: false }
31
- }, {
32
- "modelValue": { type: Boolean, ...{ default: true } },
33
- "modelModifiers": {}
34
- }),
35
- emits: /* @__PURE__ */ mergeModels(["close"], ["update:modelValue"]),
36
- setup(__props, { emit: __emit }) {
37
- const emit = __emit;
38
- const modelValue = useModel(__props, "modelValue");
39
- const { iconFlipStyles } = useIconFlipper({
40
- active: computed(() => modelValue.value),
41
- initialRotation: "180deg"
42
- });
43
- const panelContentId = useId();
44
- return (_ctx, _cache) => {
45
- return openBlock(), createElementBlock("div", {
46
- class: normalizeClass(["collapsible-panel", { collapsed: !modelValue.value }])
47
- }, [
48
- createElementVNode("div", _hoisted_1$4, [
49
- createTextVNode(toDisplayString(__props.headline) + " ", 1),
50
- createElementVNode("div", _hoisted_2$2, [
51
- createVNode(unref(KdsButton), {
52
- size: "small",
53
- variant: "transparent",
54
- "leading-icon": "chevron-up",
55
- "aria-expanded": modelValue.value,
56
- "aria-controls": unref(panelContentId),
57
- ariaLabel: modelValue.value ? "Collapse" : "Expand",
58
- style: normalizeStyle(unref(iconFlipStyles)),
59
- onClick: _cache[0] || (_cache[0] = ($event) => modelValue.value = !modelValue.value)
60
- }, null, 8, ["aria-expanded", "aria-controls", "ariaLabel", "style"]),
61
- __props.canBeDismissed ? (openBlock(), createBlock(unref(KdsButton), {
62
- key: 0,
63
- size: "small",
64
- variant: "transparent",
65
- ariaLabel: "Close",
66
- "leading-icon": "x-close",
67
- onClick: _cache[1] || (_cache[1] = ($event) => emit("close"))
68
- })) : createCommentVNode("", true)
69
- ])
70
- ]),
71
- createVNode(Transition, { name: "expand" }, {
72
- default: withCtx(() => [
73
- withDirectives(createElementVNode("div", {
74
- id: unref(panelContentId),
75
- class: "panel-content"
76
- }, [
77
- renderSlot(_ctx.$slots, "default", {}, void 0, true)
78
- ], 8, _hoisted_3$1), [
79
- [vShow, modelValue.value]
80
- ])
81
- ]),
82
- _: 3
83
- })
84
- ], 2);
85
- };
86
- }
87
- });
88
-
89
- const CollapsiblePanel = /* @__PURE__ */ _export_sfc(_sfc_main$6, [["__scopeId", "data-v-59d71b0b"]]);
90
-
91
- const _hoisted_1$3 = { class: "progress-wrapper" };
92
- const _hoisted_2$1 = { class: "item-info" };
93
- const _hoisted_3 = { class: "item-name" };
94
- const _hoisted_4 = { class: "title" };
95
- const _hoisted_5 = {
96
- key: 0,
97
- class: "subtitle"
22
+ //#endregion
23
+ //#region src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue?vue&type=script&setup=true&lang.ts
24
+ var _hoisted_1$4 = { class: "title-bar" };
25
+ var _hoisted_2$2 = { class: "actions" };
26
+ var _hoisted_3$1 = ["id"];
27
+ //#endregion
28
+ //#region src/dataTransfer/shared/ui/CollapsiblePanel/CollapsiblePanel.vue
29
+ var CollapsiblePanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
30
+ __name: "CollapsiblePanel",
31
+ props: /*@__PURE__*/ mergeModels({
32
+ headline: {},
33
+ canBeDismissed: {
34
+ type: Boolean,
35
+ default: false
36
+ }
37
+ }, {
38
+ "modelValue": {
39
+ type: Boolean,
40
+ default: true
41
+ },
42
+ "modelModifiers": {}
43
+ }),
44
+ emits: /*@__PURE__*/ mergeModels(["close"], ["update:modelValue"]),
45
+ setup(__props, { emit: __emit }) {
46
+ const emit = __emit;
47
+ const modelValue = useModel(__props, "modelValue");
48
+ const { iconFlipStyles } = useIconFlipper({
49
+ active: computed(() => modelValue.value),
50
+ initialRotation: "180deg"
51
+ });
52
+ const panelContentId = useId();
53
+ return (_ctx, _cache) => {
54
+ return openBlock(), createElementBlock("div", { class: normalizeClass(["collapsible-panel", { collapsed: !modelValue.value }]) }, [createElementVNode("div", _hoisted_1$4, [createTextVNode(toDisplayString(__props.headline) + " ", 1), createElementVNode("div", _hoisted_2$2, [createVNode(unref(KdsButton), {
55
+ size: "small",
56
+ variant: "transparent",
57
+ "leading-icon": "chevron-up",
58
+ "aria-expanded": modelValue.value,
59
+ "aria-controls": unref(panelContentId),
60
+ ariaLabel: modelValue.value ? "Collapse" : "Expand",
61
+ style: normalizeStyle(unref(iconFlipStyles)),
62
+ onClick: _cache[0] || (_cache[0] = ($event) => modelValue.value = !modelValue.value)
63
+ }, null, 8, [
64
+ "aria-expanded",
65
+ "aria-controls",
66
+ "ariaLabel",
67
+ "style"
68
+ ]), __props.canBeDismissed ? (openBlock(), createBlock(unref(KdsButton), {
69
+ key: 0,
70
+ size: "small",
71
+ variant: "transparent",
72
+ ariaLabel: "Close",
73
+ "leading-icon": "x-close",
74
+ onClick: _cache[1] || (_cache[1] = ($event) => emit("close"))
75
+ })) : createCommentVNode("", true)])]), createVNode(Transition, { name: "expand" }, {
76
+ default: withCtx(() => [withDirectives(createElementVNode("div", {
77
+ id: unref(panelContentId),
78
+ class: "panel-content"
79
+ }, [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 8, _hoisted_3$1), [[vShow, modelValue.value]])]),
80
+ _: 3
81
+ })], 2);
82
+ };
83
+ }
84
+ }), [["__scopeId", "data-v-59d71b0b"]]);
85
+ //#endregion
86
+ //#region src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue?vue&type=script&setup=true&lang.ts
87
+ var _hoisted_1$3 = { class: "progress-wrapper" };
88
+ var _hoisted_2$1 = { class: "item-info" };
89
+ var _hoisted_3 = { class: "item-name" };
90
+ var _hoisted_4 = { class: "title" };
91
+ var _hoisted_5 = {
92
+ key: 0,
93
+ class: "subtitle"
98
94
  };
99
- const _hoisted_6 = { class: "item-action" };
100
- const _hoisted_7 = {
101
- key: 0,
102
- class: "progress"
95
+ var _hoisted_6 = { class: "item-action" };
96
+ var _hoisted_7 = {
97
+ key: 0,
98
+ class: "progress"
103
99
  };
104
- const _sfc_main$5 = /* @__PURE__ */ defineComponent({
105
- __name: "ProgressItem",
106
- props: {
107
- id: {},
108
- headline: {},
109
- icon: {},
110
- actions: {},
111
- subtitle: {},
112
- progress: {},
113
- badge: {}
114
- },
115
- setup(__props) {
116
- return (_ctx, _cache) => {
117
- return openBlock(), createElementBlock("div", _hoisted_1$3, [
118
- createElementVNode("div", {
119
- class: normalizeClass(["progress-item", { padded: __props.progress === void 0 }])
120
- }, [
121
- createElementVNode("div", _hoisted_2$1, [
122
- createVNode(unref(KdsIcon), { name: __props.icon }, null, 8, ["name"]),
123
- createElementVNode("div", _hoisted_3, [
124
- withDirectives((openBlock(), createElementBlock("div", _hoisted_4, [
125
- createTextVNode(toDisplayString(__props.headline), 1)
126
- ])), [
127
- [unref(vKdsTooltip), __props.headline, "truncated"]
128
- ]),
129
- __props.subtitle ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)
130
- ])
131
- ]),
132
- createElementVNode("div", _hoisted_6, [
133
- __props.badge ? (openBlock(), createBlock(unref(KdsBadge), {
134
- key: 0,
135
- variant: __props.badge.variant,
136
- label: __props.badge.text,
137
- "leading-icon": __props.badge.icon,
138
- size: "xxsmall"
139
- }, null, 8, ["variant", "label", "leading-icon"])) : createCommentVNode("", true),
140
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.actions.slice(0, 2), (action) => {
141
- return openBlock(), createBlock(unref(KdsButton), {
142
- key: action.id,
143
- "leading-icon": action.icon,
144
- ariaLabel: action.label,
145
- variant: "transparent",
146
- onClick: action.handler
147
- }, null, 8, ["leading-icon", "ariaLabel", "onClick"]);
148
- }), 128))
149
- ])
150
- ], 2),
151
- typeof __props.progress === "number" ? (openBlock(), createElementBlock("div", _hoisted_7, [
152
- createVNode(unref(KdsProgressBar), {
153
- percentage: __props.progress,
154
- size: "medium"
155
- }, null, 8, ["percentage"])
156
- ])) : createCommentVNode("", true)
157
- ]);
158
- };
159
- }
160
- });
161
-
162
- const ProgressItem = /* @__PURE__ */ _export_sfc(_sfc_main$5, [["__scopeId", "data-v-902ce8bb"]]);
163
-
164
- const _sfc_main$4 = { };
165
-
166
- const _hoisted_1$2 = {
167
- class: "progress-list",
168
- tabindex: "0"
100
+ //#endregion
101
+ //#region src/dataTransfer/shared/ui/ProgressItem/ProgressItem.vue
102
+ var ProgressItem_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
103
+ __name: "ProgressItem",
104
+ props: {
105
+ id: {},
106
+ headline: {},
107
+ icon: {},
108
+ actions: {},
109
+ subtitle: {},
110
+ progress: {},
111
+ badge: {}
112
+ },
113
+ setup(__props) {
114
+ return (_ctx, _cache) => {
115
+ return openBlock(), createElementBlock("div", _hoisted_1$3, [createElementVNode("div", { class: normalizeClass(["progress-item", { padded: __props.progress === void 0 }]) }, [createElementVNode("div", _hoisted_2$1, [createVNode(unref(KdsIcon), { name: __props.icon }, null, 8, ["name"]), createElementVNode("div", _hoisted_3, [withDirectives((openBlock(), createElementBlock("div", _hoisted_4, [createTextVNode(toDisplayString(__props.headline), 1)])), [[
116
+ unref(vKdsTooltip),
117
+ __props.headline,
118
+ "truncated"
119
+ ]]), __props.subtitle ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(__props.subtitle), 1)) : createCommentVNode("", true)])]), createElementVNode("div", _hoisted_6, [__props.badge ? (openBlock(), createBlock(unref(KdsBadge), {
120
+ key: 0,
121
+ variant: __props.badge.variant,
122
+ label: __props.badge.text,
123
+ "leading-icon": __props.badge.icon,
124
+ size: "xxsmall"
125
+ }, null, 8, [
126
+ "variant",
127
+ "label",
128
+ "leading-icon"
129
+ ])) : createCommentVNode("", true), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.actions.slice(0, 2), (action) => {
130
+ return openBlock(), createBlock(unref(KdsButton), {
131
+ key: action.id,
132
+ "leading-icon": action.icon,
133
+ ariaLabel: action.label,
134
+ variant: "transparent",
135
+ onClick: action.handler
136
+ }, null, 8, [
137
+ "leading-icon",
138
+ "ariaLabel",
139
+ "onClick"
140
+ ]);
141
+ }), 128))])], 2), typeof __props.progress === "number" ? (openBlock(), createElementBlock("div", _hoisted_7, [createVNode(unref(KdsProgressBar), {
142
+ percentage: __props.progress,
143
+ size: "medium"
144
+ }, null, 8, ["percentage"])])) : createCommentVNode("", true)]);
145
+ };
146
+ }
147
+ }), [["__scopeId", "data-v-902ce8bb"]]);
148
+ //#endregion
149
+ //#region src/dataTransfer/shared/ui/ProgressList/ProgressList.vue
150
+ var _sfc_main = {};
151
+ var _hoisted_1$2 = {
152
+ class: "progress-list",
153
+ tabindex: "0"
169
154
  };
170
-
171
155
  function _sfc_render(_ctx, _cache) {
172
- return (openBlock(), createElementBlock("div", _hoisted_1$2, [
173
- renderSlot(_ctx.$slots, "default", {}, undefined, true)
174
- ]))
156
+ return openBlock(), createElementBlock("div", _hoisted_1$2, [renderSlot(_ctx.$slots, "default", {}, void 0, true)]);
175
157
  }
176
- const ProgressList = /*#__PURE__*/_export_sfc(_sfc_main$4, [['render',_sfc_render],['__scopeId',"data-v-8f14a3cb"]]);
177
-
178
- const _sfc_main$3 = /* @__PURE__ */ defineComponent({
179
- __name: "UploadProgressPanelItem",
180
- props: {
181
- item: {},
182
- allowCancel: { type: Boolean }
183
- },
184
- emits: ["cancel"],
185
- setup(__props, { emit: __emit }) {
186
- const props = __props;
187
- const emit = __emit;
188
- const hasProgress = (item) => {
189
- return item.status === "inprogress" || item.status === "complete";
190
- };
191
- const totalFormatted = computed(() => {
192
- return formatFileSize(props.item.size);
193
- });
194
- const progressedFormatted = computed(() => {
195
- if (hasProgress(props.item)) {
196
- const sizePercentage = props.item.size / 100 * (props.item.progress ?? 0);
197
- return formatFileSize(sizePercentage);
198
- }
199
- return 0;
200
- });
201
- const subtitle = computed(
202
- () => props.item.status === "inprogress" ? `${progressedFormatted.value} of ${totalFormatted.value}` : `${totalFormatted.value}`
203
- );
204
- const statusMapper = {
205
- inprogress: ["Uploading", "info", "reload"],
206
- failed: ["Failed", "error", "circle-error"],
207
- complete: ["Uploaded", "success", "circle-success"],
208
- cancelled: ["Cancelled", "error", "circle-error"],
209
- processing: ["Processing", "info", "reload"]
210
- };
211
- const itemIcon = computed(() => {
212
- const extension = getFileExtension(props.item.name);
213
- return extension === knimeFileFormats.KNWF.extension ? "workflow" : "file";
214
- });
215
- const badge = computed(() => {
216
- if (!props.item.status) {
217
- return void 0;
218
- }
219
- const [text, variant, icon] = statusMapper[props.item.status];
220
- return { text, variant, icon };
221
- });
222
- const shouldShowCancelAction = computed(
223
- () => props.allowCancel && props.item.status === "inprogress"
224
- );
225
- const actions = computed(() => {
226
- return shouldShowCancelAction.value ? [
227
- {
228
- id: "cancel",
229
- icon: "x-close",
230
- label: "Cancel",
231
- handler: () => emit("cancel")
232
- }
233
- ] : [];
234
- });
235
- return (_ctx, _cache) => {
236
- return openBlock(), createBlock(unref(ProgressItem), {
237
- id: __props.item.id,
238
- headline: __props.item.name,
239
- subtitle: subtitle.value,
240
- icon: itemIcon.value,
241
- progress: hasProgress(__props.item) ? __props.item.progress : void 0,
242
- badge: badge.value,
243
- actions: actions.value
244
- }, null, 8, ["id", "headline", "subtitle", "icon", "progress", "badge", "actions"]);
245
- };
246
- }
247
- });
248
-
249
- const _hoisted_1$1 = { class: "upload-panel-content" };
250
- const _hoisted_2 = { class: "skeleton-text-content" };
251
- const _sfc_main$2 = /* @__PURE__ */ defineComponent({
252
- __name: "UploadProgressPanel",
253
- props: /* @__PURE__ */ mergeModels({
254
- items: {},
255
- allowCancel: { type: Boolean, default: true },
256
- allowRemove: { type: Boolean },
257
- placeholderItems: { default: 0 }
258
- }, {
259
- "expanded": { type: Boolean, ...{ default: true } },
260
- "expandedModifiers": {}
261
- }),
262
- emits: /* @__PURE__ */ mergeModels(["cancel", "close"], ["update:expanded"]),
263
- setup(__props, { emit: __emit }) {
264
- const emit = __emit;
265
- const expanded = useModel(__props, "expanded");
266
- const totalItems = computed(() => __props.items.length);
267
- const uploadedItems = computed(
268
- () => __props.items.filter(({ status }) => status === "complete").length
269
- );
270
- const headline = computed(() => {
271
- const suffix = `file${totalItems.value > 1 ? "s" : ""}`;
272
- return `Uploaded ${uploadedItems.value} of ${totalItems.value} ${suffix}`;
273
- });
274
- const hasSomeItemInProgress = computed(
275
- () => __props.items.some(
276
- ({ status }) => status === "inprogress" || status === "processing"
277
- ) || __props.placeholderItems > 0
278
- );
279
- return (_ctx, _cache) => {
280
- return openBlock(), createBlock(unref(CollapsiblePanel), {
281
- modelValue: expanded.value,
282
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => expanded.value = $event),
283
- headline: headline.value,
284
- class: "upload-panel",
285
- "can-be-dismissed": !hasSomeItemInProgress.value,
286
- onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
287
- }, {
288
- default: withCtx(() => [
289
- createElementVNode("div", _hoisted_1$1, [
290
- createVNode(unref(ProgressList), null, {
291
- default: withCtx(() => [
292
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
293
- return openBlock(), createBlock(_sfc_main$3, {
294
- key: item.id,
295
- item,
296
- "allow-cancel": __props.allowCancel,
297
- onCancel: ($event) => emit("cancel", item)
298
- }, null, 8, ["item", "allow-cancel", "onCancel"]);
299
- }), 128))
300
- ]),
301
- _: 1
302
- }),
303
- __props.placeholderItems > 0 ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(__props.placeholderItems, (skeleton) => {
304
- return openBlock(), createElementBlock("div", {
305
- key: skeleton,
306
- class: "skeleton-container"
307
- }, [
308
- createVNode(unref(KdsLoadingSkeleton), { variant: "icon-large" }),
309
- createElementVNode("div", _hoisted_2, [
310
- createVNode(unref(KdsLoadingSkeleton), { variant: "label" }),
311
- createVNode(unref(KdsLoadingSkeleton), { variant: "text" })
312
- ])
313
- ]);
314
- }), 128)) : createCommentVNode("", true)
315
- ])
316
- ]),
317
- _: 1
318
- }, 8, ["modelValue", "headline", "can-be-dismissed"]);
319
- };
320
- }
321
- });
322
-
323
- const UploadProgressPanel = /* @__PURE__ */ _export_sfc(_sfc_main$2, [["__scopeId", "data-v-15b3eb7d"]]);
324
-
325
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
326
- __name: "DownloadProgressPanelItem",
327
- props: {
328
- item: {}
329
- },
330
- emits: ["cancel", "download"],
331
- setup(__props, { emit: __emit }) {
332
- const props = __props;
333
- const emit = __emit;
334
- const statusMapper = {
335
- IN_PROGRESS: ["Zipping", "info", "reload"],
336
- READY: ["Ready", "success", "circle-info"],
337
- CANCELLED: ["Cancelled", "error", "circle-error"],
338
- FAILED: ["Failed", "error", "circle-error"]
339
- };
340
- const badge = computed(() => {
341
- const [text, variant, icon] = statusMapper[props.item.status];
342
- return {
343
- text,
344
- variant,
345
- icon
346
- };
347
- });
348
- const isCancellable = computed(() => props.item.status === "IN_PROGRESS");
349
- const isReady = computed(() => props.item.status === "READY");
350
- const actions = computed(() => {
351
- const actions2 = [];
352
- if (isCancellable.value) {
353
- actions2.push({
354
- id: "cancel",
355
- icon: "x-close",
356
- label: "Cancel",
357
- handler: () => emit("cancel")
358
- });
359
- }
360
- if (isReady.value) {
361
- actions2.push({
362
- id: "download",
363
- icon: "cloud-download",
364
- label: "Download again",
365
- handler: () => emit("download")
366
- });
367
- }
368
- return actions2;
369
- });
370
- return (_ctx, _cache) => {
371
- return openBlock(), createBlock(unref(ProgressItem), {
372
- id: __props.item.downloadId,
373
- icon: "file",
374
- headline: __props.item.name,
375
- badge: badge.value,
376
- actions: actions.value
377
- }, null, 8, ["id", "headline", "badge", "actions"]);
378
- };
379
- }
158
+ var ProgressList_default = /*#__PURE__*/ _plugin_vue_export_helper_default(_sfc_main, [["render", _sfc_render], ["__scopeId", "data-v-8f14a3cb"]]);
159
+ //#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
+ }
380
250
  });
381
-
382
- const _hoisted_1 = { class: "download-panel-content" };
383
- const _sfc_main = /* @__PURE__ */ defineComponent({
384
- __name: "DownloadProgressPanel",
385
- props: /* @__PURE__ */ mergeModels({
386
- items: {}
387
- }, {
388
- "expanded": { type: Boolean, ...{ default: true } },
389
- "expandedModifiers": {}
390
- }),
391
- emits: /* @__PURE__ */ mergeModels(["cancel", "download", "close"], ["update:expanded"]),
392
- setup(__props, { emit: __emit }) {
393
- const props = __props;
394
- const emit = __emit;
395
- const expanded = useModel(__props, "expanded");
396
- const totalItems = computed(() => props.items.length);
397
- const totalDownloadedItems = computed(
398
- () => props.items.filter(({ status }) => status === "READY").length
399
- );
400
- const headline = computed(() => {
401
- const suffix = `file${totalItems.value > 1 ? "s" : ""}`;
402
- return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;
403
- });
404
- const hasItemsInProgress = computed(
405
- () => props.items.some(({ status }) => status === "IN_PROGRESS")
406
- );
407
- watch(
408
- () => props.items?.length,
409
- (newValue, oldValue) => {
410
- if (oldValue > 0 && !newValue) {
411
- emit("close");
412
- }
413
- }
414
- );
415
- return (_ctx, _cache) => {
416
- return openBlock(), createBlock(unref(CollapsiblePanel), {
417
- modelValue: expanded.value,
418
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => expanded.value = $event),
419
- headline: headline.value,
420
- class: "download-panel",
421
- "can-be-dismissed": !hasItemsInProgress.value,
422
- onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
423
- }, {
424
- default: withCtx(() => [
425
- createElementVNode("div", _hoisted_1, [
426
- createVNode(unref(ProgressList), null, {
427
- default: withCtx(() => [
428
- (openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
429
- return openBlock(), createBlock(_sfc_main$1, {
430
- key: item.downloadId,
431
- item,
432
- onCancel: ($event) => emit("cancel", item),
433
- onDownload: ($event) => emit("download", item)
434
- }, null, 8, ["item", "onCancel", "onDownload"]);
435
- }), 128))
436
- ]),
437
- _: 1
438
- })
439
- ])
440
- ]),
441
- _: 1
442
- }, 8, ["modelValue", "headline", "can-be-dismissed"]);
443
- };
444
- }
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
+ //#region src/dataTransfer/download/ui/DownloadProgressPanelItem.vue
325
+ var DownloadProgressPanelItem_default = /* @__PURE__ */ defineComponent({
326
+ __name: "DownloadProgressPanelItem",
327
+ props: { item: {} },
328
+ emits: ["cancel", "download"],
329
+ setup(__props, { emit: __emit }) {
330
+ const props = __props;
331
+ const emit = __emit;
332
+ const statusMapper = {
333
+ IN_PROGRESS: [
334
+ "Zipping",
335
+ "info",
336
+ "reload"
337
+ ],
338
+ READY: [
339
+ "Ready",
340
+ "success",
341
+ "circle-info"
342
+ ],
343
+ CANCELLED: [
344
+ "Cancelled",
345
+ "error",
346
+ "circle-error"
347
+ ],
348
+ FAILED: [
349
+ "Failed",
350
+ "error",
351
+ "circle-error"
352
+ ]
353
+ };
354
+ const badge = computed(() => {
355
+ const [text, variant, icon] = statusMapper[props.item.status];
356
+ return {
357
+ text,
358
+ variant,
359
+ icon
360
+ };
361
+ });
362
+ const isCancellable = computed(() => props.item.status === "IN_PROGRESS");
363
+ const isReady = computed(() => props.item.status === "READY");
364
+ const actions = computed(() => {
365
+ const actions = [];
366
+ if (isCancellable.value) actions.push({
367
+ id: "cancel",
368
+ icon: "x-close",
369
+ label: "Cancel",
370
+ handler: () => emit("cancel")
371
+ });
372
+ if (isReady.value) actions.push({
373
+ id: "download",
374
+ icon: "cloud-download",
375
+ label: "Download again",
376
+ handler: () => emit("download")
377
+ });
378
+ return actions;
379
+ });
380
+ return (_ctx, _cache) => {
381
+ return openBlock(), createBlock(unref(ProgressItem_default), {
382
+ id: __props.item.downloadId,
383
+ icon: "file",
384
+ headline: __props.item.name,
385
+ badge: badge.value,
386
+ actions: actions.value
387
+ }, null, 8, [
388
+ "id",
389
+ "headline",
390
+ "badge",
391
+ "actions"
392
+ ]);
393
+ };
394
+ }
445
395
  });
446
-
447
- const DownloadProgressPanel = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-53e10d80"]]);
448
-
449
- //#region src/predicate/isPlainObject.ts
396
+ //#endregion
397
+ //#region src/dataTransfer/download/ui/DownloadProgressPanel.vue?vue&type=script&setup=true&lang.ts
398
+ var _hoisted_1 = { class: "download-panel-content" };
399
+ //#endregion
400
+ //#region src/dataTransfer/download/ui/DownloadProgressPanel.vue
401
+ var DownloadProgressPanel_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PURE__ */ defineComponent({
402
+ __name: "DownloadProgressPanel",
403
+ props: /*@__PURE__*/ mergeModels({ items: {} }, {
404
+ "expanded": {
405
+ type: Boolean,
406
+ default: true
407
+ },
408
+ "expandedModifiers": {}
409
+ }),
410
+ emits: /*@__PURE__*/ mergeModels([
411
+ "cancel",
412
+ "download",
413
+ "close"
414
+ ], ["update:expanded"]),
415
+ setup(__props, { emit: __emit }) {
416
+ const props = __props;
417
+ const emit = __emit;
418
+ const expanded = useModel(__props, "expanded");
419
+ const totalItems = computed(() => props.items.length);
420
+ const totalDownloadedItems = computed(() => props.items.filter(({ status }) => status === "READY").length);
421
+ const headline = computed(() => {
422
+ const suffix = `file${totalItems.value > 1 ? "s" : ""}`;
423
+ return `Downloaded ${totalDownloadedItems.value} of ${totalItems.value} ${suffix}`;
424
+ });
425
+ const hasItemsInProgress = computed(() => props.items.some(({ status }) => status === "IN_PROGRESS"));
426
+ watch(() => props.items?.length, (newValue, oldValue) => {
427
+ if (oldValue > 0 && !newValue) emit("close");
428
+ });
429
+ return (_ctx, _cache) => {
430
+ return openBlock(), createBlock(unref(CollapsiblePanel_default), {
431
+ modelValue: expanded.value,
432
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => expanded.value = $event),
433
+ headline: headline.value,
434
+ class: "download-panel",
435
+ "can-be-dismissed": !hasItemsInProgress.value,
436
+ onClose: _cache[1] || (_cache[1] = ($event) => emit("close"))
437
+ }, {
438
+ default: withCtx(() => [createElementVNode("div", _hoisted_1, [createVNode(unref(ProgressList_default), null, {
439
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(__props.items, (item) => {
440
+ return openBlock(), createBlock(DownloadProgressPanelItem_default, {
441
+ key: item.downloadId,
442
+ item,
443
+ onCancel: ($event) => emit("cancel", item),
444
+ onDownload: ($event) => emit("download", item)
445
+ }, null, 8, [
446
+ "item",
447
+ "onCancel",
448
+ "onDownload"
449
+ ]);
450
+ }), 128))]),
451
+ _: 1
452
+ })])]),
453
+ _: 1
454
+ }, 8, [
455
+ "modelValue",
456
+ "headline",
457
+ "can-be-dismissed"
458
+ ]);
459
+ };
460
+ }
461
+ }), [["__scopeId", "data-v-53e10d80"]]);
462
+ //#endregion
463
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
450
464
  /**
451
465
  * Checks if a given value is a plain object.
452
466
  *
@@ -495,8 +509,8 @@ function isPlainObject(value) {
495
509
  if (!(proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null)) return false;
496
510
  return Object.prototype.toString.call(value) === "[object Object]";
497
511
  }
498
-
499
- //#region src/_internal/isUnsafeProperty.ts
512
+ //#endregion
513
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
500
514
  /**
501
515
  * Checks if a property key is unsafe to modify directly.
502
516
  *
@@ -510,8 +524,8 @@ function isPlainObject(value) {
510
524
  function isUnsafeProperty(key) {
511
525
  return key === "__proto__";
512
526
  }
513
-
514
- //#region src/object/merge.ts
527
+ //#endregion
528
+ //#region ../../node_modules/.pnpm/es-toolkit@1.49.0/node_modules/es-toolkit/dist/object/merge.mjs
515
529
  /**
516
530
  * Merges the properties of the source object into the target object.
517
531
  *
@@ -569,671 +583,711 @@ function merge(target, source) {
569
583
  function isMergeableValue(value) {
570
584
  return isPlainObject(value) || Array.isArray(value);
571
585
  }
572
-
573
- const API_BASE_URL = "/_/api";
574
-
575
- const defaultConfig = {
576
- baseURL: API_BASE_URL,
577
- headers: {
578
- "Content-Type": "application/json",
579
- // see: https://knime-com.atlassian.net/wiki/spaces/SPECS/pages/4126769212/API+Errors#Client-Request-Headers
580
- Accept: "application/json; application/problem+json"
581
- }
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
+ }
582
597
  };
583
- const createHttpClient = (customOptions) => {
584
- const { baseURL = API_BASE_URL, ...otherCustomOptions } = customOptions ?? {};
585
- return ofetch.create(
586
- merge({ baseURL, ...otherCustomOptions }, defaultConfig)
587
- );
598
+ var createHttpClient = (customOptions) => {
599
+ const { baseURL = API_BASE_URL, ...otherCustomOptions } = customOptions ?? {};
600
+ return ofetch.create(merge({
601
+ baseURL,
602
+ ...otherCustomOptions
603
+ }, defaultConfig));
588
604
  };
589
-
590
- const CHUNK_SIZE = 50 * 1024 * 1024;
591
- const DEFAULT_RETRY_COUNT = 5;
592
- const DEFAULT_RETRY_DELAY_MS$1 = 50;
593
- const uploadChunkWithProgress = (params) => {
594
- const { method, url, chunk, chunkIndex, onProgress, abortSignal } = params;
595
- const OK = 200;
596
- const CREATED = 201;
597
- return new Promise((resolve, reject) => {
598
- const xhr = new XMLHttpRequest();
599
- xhr.open(method, url, true);
600
- abortSignal.addEventListener("abort", () => xhr.abort());
601
- xhr.upload.onprogress = (event) => {
602
- if (event.lengthComputable) {
603
- onProgress(event.loaded);
604
- }
605
- };
606
- xhr.onload = () => {
607
- if (xhr.status === OK || xhr.status === CREATED) {
608
- const etag = xhr.getResponseHeader("ETag");
609
- resolve({ partId: etag?.replace(/^(?:W\/)?"|"$/g, "") ?? "" });
610
- } else {
611
- consola.error("Failed or unexpected XHR response", {
612
- xhrStatus: xhr.status,
613
- xhrResponse: xhr.response,
614
- chunkIndex
615
- });
616
- reject(new Error("Failed to upload file"));
617
- }
618
- };
619
- xhr.onerror = (event) => {
620
- consola.error("Failed to upload chunk", {
621
- xhrErrorEvent: event,
622
- chunkIndex
623
- });
624
- reject(new Error("Failed to upload file"));
625
- };
626
- xhr.send(chunk);
627
- });
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
+ });
628
641
  };
629
- const createUploadManager = (config) => {
630
- const {
631
- resolveFilePartUploadURL: getUploadFilePartUrl,
632
- onFileUploadComplete,
633
- onFileUploadFailed
634
- } = config;
635
- const uploadStateMap = /* @__PURE__ */ new Map();
636
- const completedUploads = /* @__PURE__ */ new Set();
637
- const cancelledUploads = /* @__PURE__ */ new Set();
638
- const failedUploads = /* @__PURE__ */ new Set();
639
- const assertState = (uploadId, state) => uploadStateMap.has(uploadId) && uploadStateMap.get(uploadId).state === state;
640
- const getUploadState = (uploadId) => uploadStateMap.get(uploadId);
641
- const setUploadState = (uploadId, state) => {
642
- uploadStateMap.set(uploadId, {
643
- ...uploadStateMap.get(uploadId),
644
- ...state
645
- });
646
- };
647
- const isFailed = (uploadId) => assertState(uploadId, "failed");
648
- const isCancelled = (uploadId) => assertState(uploadId, "cancelled");
649
- const getTotalFileParts = (file) => Math.ceil(file.size / CHUNK_SIZE);
650
- const abortInFlightUpload = (uploadId, reason) => {
651
- const currentState = uploadStateMap.get(uploadId);
652
- if (!currentState) {
653
- return;
654
- }
655
- currentState.abortController.abort(
656
- new index.AbortError(
657
- `Upload (${currentState.uploadId}) for file "${currentState.file.name}" aborted. Reason: "${reason}"`
658
- )
659
- );
660
- };
661
- const cancel = (uploadId) => {
662
- if (!uploadStateMap.has(uploadId)) {
663
- consola.error("cancel:: Invalid upload id");
664
- return;
665
- }
666
- setUploadState(uploadId, { state: "cancelled" });
667
- abortInFlightUpload(uploadId, "upload cancelled");
668
- cancelledUploads.add(uploadId);
669
- };
670
- const setFailed = (uploadId, error) => {
671
- if (!uploadStateMap.has(uploadId)) {
672
- consola.error("failed:: Invalid upload id");
673
- return;
674
- }
675
- setUploadState(uploadId, { state: "failed" });
676
- onFileUploadFailed?.({ uploadId, error });
677
- failedUploads.add(uploadId);
678
- if (completedUploads.has(uploadId)) {
679
- completedUploads.delete(uploadId);
680
- }
681
- };
682
- const uploadFileFromChunkIndex = async (uploadId, file, initialChunkIndex = 0) => {
683
- const totalChunks = getTotalFileParts(file);
684
- const totalBytes = file.size;
685
- const completedPartIds = {};
686
- const { abortController, runAbortablePromise } = index.createAbortablePromise();
687
- uploadStateMap.set(uploadId, {
688
- uploadId,
689
- file,
690
- state: "inprogress",
691
- uploadedByteCount: uploadStateMap.get(uploadId)?.uploadedByteCount ?? 0,
692
- completedChunkCount: uploadStateMap.get(uploadId)?.completedChunkCount ?? 0,
693
- abortController
694
- });
695
- let chunkIndex = initialChunkIndex;
696
- let uploadedBytes = uploadStateMap.get(uploadId)?.uploadedByteCount;
697
- while (chunkIndex < totalChunks) {
698
- if (isCancelled(uploadId) || isFailed(uploadId)) {
699
- break;
700
- }
701
- const start = chunkIndex * CHUNK_SIZE;
702
- const end = Math.min(start + CHUNK_SIZE, file.size);
703
- const chunk = file.slice(start, end);
704
- try {
705
- const { method, url } = await index.retryPromise({
706
- fn: () => getUploadFilePartUrl(uploadId, chunkIndex + 1),
707
- retryCount: DEFAULT_RETRY_COUNT,
708
- retryDelayMS: DEFAULT_RETRY_DELAY_MS$1
709
- });
710
- consola.trace("File part upload url resolved", { method, url });
711
- const onChunkUploadProgress = (uploadedChunkBytes) => {
712
- uploadedBytes = uploadedChunkBytes + start;
713
- setUploadState(uploadId, { uploadedByteCount: uploadedBytes });
714
- const progress = Math.round(uploadedBytes / totalBytes * 100);
715
- config.onProgress?.(uploadId, progress);
716
- };
717
- const filePartResponse = await runAbortablePromise(
718
- () => index.retryPromise({
719
- fn: () => uploadChunkWithProgress({
720
- method,
721
- url,
722
- chunk,
723
- chunkIndex,
724
- onProgress: onChunkUploadProgress,
725
- abortSignal: abortController.signal
726
- }),
727
- retryCount: DEFAULT_RETRY_COUNT,
728
- retryDelayMS: DEFAULT_RETRY_DELAY_MS$1
729
- })
730
- );
731
- consola.trace(
732
- `${file.name}: Chunk ${chunkIndex + 1} of ${totalChunks} uploaded successfully`
733
- );
734
- completedPartIds[chunkIndex + 1] = filePartResponse.partId;
735
- setUploadState(uploadId, {
736
- completedChunkCount: uploadStateMap.get(uploadId).completedChunkCount + 1
737
- });
738
- } catch (error) {
739
- if (error instanceof index.AbortError) {
740
- consola.info("Aborted::", error);
741
- return;
742
- }
743
- setFailed(
744
- uploadId,
745
- error instanceof Error ? error : new Error(error)
746
- );
747
- return;
748
- }
749
- chunkIndex++;
750
- }
751
- if (isCancelled(uploadId) || isFailed(uploadId)) {
752
- return;
753
- }
754
- setUploadState(uploadId, {
755
- completedChunkCount: totalChunks,
756
- state: "complete"
757
- });
758
- completedUploads.add(uploadId);
759
- onFileUploadComplete?.({ uploadId, filePartIds: completedPartIds });
760
- };
761
- const uploadFiles = (files) => {
762
- const result = [];
763
- for (const { uploadId, file } of files) {
764
- result.push(uploadFileFromChunkIndex(uploadId, file));
765
- }
766
- return Promise.allSettled(result).then(() => {
767
- const completed = [...completedUploads.values()];
768
- const cancelled = [...cancelledUploads.values()];
769
- const failed = [...failedUploads.values()];
770
- [...completed, ...cancelled, ...failed].forEach(
771
- (uploadId) => uploadStateMap.delete(uploadId)
772
- );
773
- completedUploads.clear();
774
- cancelledUploads.clear();
775
- failedUploads.clear();
776
- return {
777
- completed,
778
- cancelled,
779
- failed
780
- };
781
- });
782
- };
783
- return {
784
- uploadFiles,
785
- getUploadState,
786
- isFailed,
787
- isCancelled,
788
- cancel,
789
- setFailed
790
- };
642
+ /**
643
+ * Creates and returns an `uploadManager`, a comprehensive multi-file, multi-part upload handler.
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:
649
+ *
650
+ * - **Chunked File Uploads**: Splits provided files into manageable chunks and initiates the upload
651
+ * for each part using the `resolveFilePartUploadURL` function provided in the configuration.
652
+ *
653
+ * - **Retry Mechanism**: Automatically retries failed upload attempts up to a maximum of 5 times
654
+ * per failed request.
655
+ *
656
+ * - **Upload Cancellation**: Allows cancellation of ongoing uploads, giving users control over the process.
657
+ *
658
+ * - **Progress Tracking**: Monitors the progress of each file part upload and triggers the `onProgress`
659
+ * callback to provide updates.
660
+ *
661
+ * - **Error Handling**: The returned `uploadFiles` function does not throw errors directly. Instead,
662
+ * it manages retries and stops any failed uploads after the retry limit is reached, marking them as failed.
663
+ * This ensures that the promise it returns completes gracefully without uncaught exceptions.
664
+ *
665
+ */
666
+ var createUploadManager = (config) => {
667
+ const { resolveFilePartUploadURL: getUploadFilePartUrl, onFileUploadComplete, onFileUploadFailed } = config;
668
+ const uploadStateMap = /* @__PURE__ */ new Map();
669
+ const completedUploads = /* @__PURE__ */ new Set();
670
+ const cancelledUploads = /* @__PURE__ */ new Set();
671
+ const failedUploads = /* @__PURE__ */ new Set();
672
+ const assertState = (uploadId, state) => uploadStateMap.has(uploadId) && uploadStateMap.get(uploadId).state === state;
673
+ const getUploadState = (uploadId) => uploadStateMap.get(uploadId);
674
+ const setUploadState = (uploadId, state) => {
675
+ uploadStateMap.set(uploadId, {
676
+ ...uploadStateMap.get(uploadId),
677
+ ...state
678
+ });
679
+ };
680
+ const isFailed = (uploadId) => assertState(uploadId, "failed");
681
+ const isCancelled = (uploadId) => assertState(uploadId, "cancelled");
682
+ const getTotalFileParts = (file) => Math.ceil(file.size / CHUNK_SIZE);
683
+ const abortInFlightUpload = (uploadId, reason) => {
684
+ const currentState = uploadStateMap.get(uploadId);
685
+ if (!currentState) return;
686
+ currentState.abortController.abort(new promise_exports.AbortError(`Upload (${currentState.uploadId}) for file "${currentState.file.name}" aborted. Reason: "${reason}"`));
687
+ };
688
+ const cancel = (uploadId) => {
689
+ if (!uploadStateMap.has(uploadId)) {
690
+ consola.error("cancel:: Invalid upload id");
691
+ return;
692
+ }
693
+ setUploadState(uploadId, { state: "cancelled" });
694
+ abortInFlightUpload(uploadId, "upload cancelled");
695
+ cancelledUploads.add(uploadId);
696
+ };
697
+ const setFailed = (uploadId, error) => {
698
+ if (!uploadStateMap.has(uploadId)) {
699
+ consola.error("failed:: Invalid upload id");
700
+ return;
701
+ }
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
+ };
710
+ const uploadFileFromChunkIndex = async (uploadId, file, initialChunkIndex = 0) => {
711
+ const totalChunks = getTotalFileParts(file);
712
+ const totalBytes = file.size;
713
+ const completedPartIds = {};
714
+ const { abortController, runAbortablePromise } = promise_exports.createAbortablePromise();
715
+ uploadStateMap.set(uploadId, {
716
+ uploadId,
717
+ file,
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));
767
+ return;
768
+ }
769
+ chunkIndex++;
770
+ }
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
+ };
782
+ const uploadFiles = (files) => {
783
+ const result = [];
784
+ for (const { uploadId, file } of files) result.push(uploadFileFromChunkIndex(uploadId, file));
785
+ return Promise.allSettled(result).then(() => {
786
+ const completed = [...completedUploads.values()];
787
+ const cancelled = [...cancelledUploads.values()];
788
+ const failed = [...failedUploads.values()];
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
+ });
803
+ };
804
+ return {
805
+ uploadFiles,
806
+ getUploadState,
807
+ isFailed,
808
+ isCancelled,
809
+ cancel,
810
+ setFailed
811
+ };
791
812
  };
792
-
793
- const useUploadManager = (options) => {
794
- const uploadState = ref({});
795
- const { uploadFiles, cancel, setFailed } = createUploadManager({
796
- ...options,
797
- onFileUploadComplete: (params) => {
798
- const { parentId, name } = uploadState.value[params.uploadId];
799
- uploadState.value[params.uploadId].status = "complete";
800
- options.onFileUploadComplete?.({ ...params, parentId, name });
801
- },
802
- onFileUploadFailed: ({ uploadId, error }) => {
803
- const { parentId } = uploadState.value[uploadId];
804
- uploadState.value[uploadId] = {
805
- ...uploadState.value[uploadId],
806
- status: "failed",
807
- failureDetails: error.message
808
- };
809
- options.onFileUploadFailed?.({ uploadId, error, parentId });
810
- },
811
- onProgress: (uploadId, progress) => {
812
- if (uploadState.value[uploadId].status === "inprogress") {
813
- uploadState.value[uploadId].progress = progress;
814
- }
815
- }
816
- });
817
- const start = async (parentId, uploadPayload) => {
818
- try {
819
- uploadPayload.forEach(({ uploadId, file }) => {
820
- uploadState.value[uploadId] = {
821
- id: uploadId,
822
- name: file.name,
823
- size: file.size,
824
- progress: 0,
825
- status: "inprogress",
826
- parentId
827
- };
828
- });
829
- await uploadFiles(uploadPayload);
830
- } catch (error) {
831
- consola.error("Error uploading files", error);
832
- }
833
- };
834
- const canCancel = (uploadId) => uploadState.value[uploadId]?.status === "inprogress" || uploadState.value[uploadId]?.status === "failed";
835
- const removeItem = (uploadId) => {
836
- if (canCancel(uploadId)) {
837
- cancel(uploadId);
838
- }
839
- delete uploadState.value[uploadId];
840
- };
841
- const resetState = () => {
842
- uploadState.value = {};
843
- };
844
- const uploadItems = computed(() => Object.values(uploadState.value));
845
- const hasPendingUploads = computed(
846
- () => uploadItems.value.some(({ status }) => status === "inprogress")
847
- );
848
- const totalPendingUploads = computed(
849
- () => uploadItems.value.filter(({ status }) => status === "inprogress").length
850
- );
851
- return {
852
- uploadState,
853
- hasPendingUploads,
854
- totalPendingUploads,
855
- start,
856
- cancel: (uploadId) => {
857
- cancel(uploadId);
858
- uploadState.value[uploadId].status = "cancelled";
859
- },
860
- canCancel,
861
- removeItem,
862
- resetState,
863
- setFailed: (uploadId, error) => {
864
- uploadState.value[uploadId] = {
865
- ...uploadState.value[uploadId],
866
- status: "failed",
867
- failureDetails: error.message
868
- };
869
- setFailed(uploadId, error);
870
- }
871
- };
813
+ //#endregion
814
+ //#region src/dataTransfer/upload/ui/useUploadManager.ts
815
+ /**
816
+ * This composable wraps the `uploadManager` utility
817
+ * in order to provide reactive state which will work tightly with the `UploadProgressPanel` component
818
+ *
819
+ * The reactive state returned (`uploadItems`) will update whenever the progress
820
+ * or status of each item being uploaded changes
821
+ */
822
+ var useUploadManager = (options) => {
823
+ const uploadState = ref({});
824
+ const { uploadFiles, cancel, setFailed } = createUploadManager({
825
+ ...options,
826
+ onFileUploadComplete: (params) => {
827
+ const { parentId, name } = uploadState.value[params.uploadId];
828
+ uploadState.value[params.uploadId].status = "complete";
829
+ options.onFileUploadComplete?.({
830
+ ...params,
831
+ parentId,
832
+ name
833
+ });
834
+ },
835
+ onFileUploadFailed: ({ uploadId, error }) => {
836
+ const { parentId } = uploadState.value[uploadId];
837
+ uploadState.value[uploadId] = {
838
+ ...uploadState.value[uploadId],
839
+ status: "failed",
840
+ failureDetails: error.message
841
+ };
842
+ options.onFileUploadFailed?.({
843
+ uploadId,
844
+ error,
845
+ parentId
846
+ });
847
+ },
848
+ onProgress: (uploadId, progress) => {
849
+ if (uploadState.value[uploadId].status === "inprogress") uploadState.value[uploadId].progress = progress;
850
+ }
851
+ });
852
+ const start = async (parentId, uploadPayload) => {
853
+ try {
854
+ uploadPayload.forEach(({ uploadId, file }) => {
855
+ uploadState.value[uploadId] = {
856
+ id: uploadId,
857
+ name: file.name,
858
+ size: file.size,
859
+ progress: 0,
860
+ status: "inprogress",
861
+ parentId
862
+ };
863
+ });
864
+ await uploadFiles(uploadPayload);
865
+ } catch (error) {
866
+ consola.error("Error uploading files", error);
867
+ }
868
+ };
869
+ const canCancel = (uploadId) => uploadState.value[uploadId]?.status === "inprogress" || uploadState.value[uploadId]?.status === "failed";
870
+ const removeItem = (uploadId) => {
871
+ if (canCancel(uploadId)) cancel(uploadId);
872
+ delete uploadState.value[uploadId];
873
+ };
874
+ const resetState = () => {
875
+ uploadState.value = {};
876
+ };
877
+ const uploadItems = computed(() => Object.values(uploadState.value));
878
+ return {
879
+ uploadState,
880
+ hasPendingUploads: computed(() => uploadItems.value.some(({ status }) => status === "inprogress")),
881
+ totalPendingUploads: computed(() => uploadItems.value.filter(({ status }) => status === "inprogress").length),
882
+ start,
883
+ cancel: (uploadId) => {
884
+ cancel(uploadId);
885
+ uploadState.value[uploadId].status = "cancelled";
886
+ },
887
+ canCancel,
888
+ removeItem,
889
+ resetState,
890
+ setFailed: (uploadId, error) => {
891
+ uploadState.value[uploadId] = {
892
+ ...uploadState.value[uploadId],
893
+ status: "failed",
894
+ failureDetails: error.message
895
+ };
896
+ setFailed(uploadId, error);
897
+ }
898
+ };
872
899
  };
873
-
874
- const DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 10;
875
- const DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;
876
- const DEFAULT_RETRY_DELAY_MS = 50;
877
- const getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
878
- const useFileUpload = (options = {}) => {
879
- const $ofetch = createHttpClient(options.customFetchClientOptions);
880
- const prepareUpload = (parentId, files) => {
881
- const fileDictionary = Object.fromEntries(
882
- files.map((file) => {
883
- const name = knimeFileFormats.KNWF.getNameOrDefault(file, file.name);
884
- return [name, file];
885
- })
886
- );
887
- const items = Object.fromEntries(
888
- Object.entries(fileDictionary).map(([name, file]) => [
889
- name,
890
- {
891
- itemContentType: getFileMimeType(file),
892
- itemContentSize: file.size
893
- }
894
- ])
895
- );
896
- return $ofetch(`/repository/${parentId}/manifest`, {
897
- method: "POST",
898
- body: { items }
899
- }).then(({ items: items2 }) => {
900
- return Object.keys(items2).map((name) => {
901
- const { uploadId } = items2[name];
902
- const file = fileDictionary[name];
903
- return { uploadId, file };
904
- });
905
- });
906
- };
907
- const resolveFilePartUploadURL = (uploadId, partNumber) => {
908
- return $ofetch(
909
- `/uploads/${uploadId}/parts/?partNumber=${partNumber}`,
910
- { method: "POST" }
911
- );
912
- };
913
- const completeUpload = (uploadId, partIds) => {
914
- return $ofetch(`/uploads/${uploadId}`, {
915
- method: "POST",
916
- body: partIds
917
- });
918
- };
919
- const cancelUpload = (uploadId) => {
920
- return $ofetch(`/uploads/${uploadId}`, {
921
- method: "DELETE"
922
- });
923
- };
924
- const unprocessedUploads = reactive(/* @__PURE__ */ new Set());
925
- const isKNWFFile = (file) => knimeFileFormats.KNWF.matches(file);
926
- let useUploadManagerResult = null;
927
- const setProcessingCompleted = ({ uploadId }) => {
928
- unprocessedUploads.delete(uploadId);
929
- };
930
- const setProcessingFailed = ({
931
- uploadId,
932
- error
933
- }) => {
934
- unprocessedUploads.delete(uploadId);
935
- useUploadManagerResult?.setFailed(
936
- uploadId,
937
- error ?? new Error("An error occurred when processing the file")
938
- );
939
- };
940
- useUploadManagerResult = useUploadManager({
941
- resolveFilePartUploadURL,
942
- onFileUploadComplete: ({ uploadId, filePartIds, parentId, name }) => {
943
- index.retryPromise({ fn: () => completeUpload(uploadId, filePartIds) }).then(() => {
944
- options.onFileUploadComplete?.({
945
- uploadId,
946
- filePartIds,
947
- parentId,
948
- name
949
- });
950
- }).catch((error) => {
951
- consola.error("Error attempting to complete upload", { error });
952
- setProcessingFailed({ uploadId, error });
953
- });
954
- },
955
- onFileUploadFailed: ({ uploadId, error, parentId }) => {
956
- options?.onFileUploadFailed?.({ uploadId, error, parentId });
957
- }
958
- });
959
- const getEnqueueableFiles = (files) => {
960
- const { totalPendingUploads } = useUploadManagerResult;
961
- const { maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE } = options;
962
- if (files.length + totalPendingUploads.value > maxUploadQueueSize) {
963
- options.onUploadQueueSizeExceeded?.(maxUploadQueueSize);
964
- }
965
- const effectiveQueueSize = Math.max(
966
- 0,
967
- maxUploadQueueSize - totalPendingUploads.value
968
- );
969
- return files.slice(0, effectiveQueueSize);
970
- };
971
- const { uploadState, ...uploadManagerResultRest } = useUploadManagerResult;
972
- const uploadQueue = computed(() => {
973
- return Object.values(uploadState.value).map((uploadItem) => {
974
- if (uploadItem.status === "complete" && unprocessedUploads.has(uploadItem.id)) {
975
- return {
976
- id: uploadItem.id,
977
- name: uploadItem.name,
978
- parentId: uploadItem.parentId,
979
- size: uploadItem.size,
980
- status: "processing"
981
- };
982
- }
983
- return uploadItem;
984
- });
985
- });
986
- const prepareQueueSize = ref(0);
987
- return {
988
- ...uploadManagerResultRest,
989
- uploadQueue,
990
- unprocessedUploads: readonly(unprocessedUploads),
991
- setProcessingCompleted,
992
- setProcessingFailed,
993
- isPreparingUpload: computed(() => prepareQueueSize.value > 0),
994
- totalFilesBeingPrepared: computed(() => prepareQueueSize.value),
995
- start: async (parentId, files, options2 = {}) => {
996
- const { isFileWithProcessing = isKNWFFile } = options2;
997
- const enqueueableFiles = getEnqueueableFiles(files);
998
- const uploadSizeLimitBytes = getUploadSizeLimitBytes();
999
- const oversizedFiles = enqueueableFiles.filter(
1000
- (file) => file.size > uploadSizeLimitBytes
1001
- );
1002
- if (oversizedFiles.length > 0) {
1003
- throw new rfcErrors.RFCError({
1004
- title: "Upload size limit exceeded",
1005
- status: 413,
1006
- details: oversizedFiles.map(
1007
- (file) => `${file.name} (${formatFileSize(file.size)}) exceeds the upload limit of ${formatFileSize(uploadSizeLimitBytes)}.`
1008
- )
1009
- });
1010
- }
1011
- try {
1012
- if (enqueueableFiles.length === 0) {
1013
- return [];
1014
- }
1015
- prepareQueueSize.value += enqueueableFiles.length;
1016
- const uploadPayload = await index.retryPromise({
1017
- fn: () => prepareUpload(parentId, enqueueableFiles),
1018
- excludeError: (error) => (
1019
- // eslint-disable-next-line no-magic-numbers
1020
- Boolean(error.statusCode && error.statusCode < 500)
1021
- ),
1022
- retryDelayMS: DEFAULT_RETRY_DELAY_MS
1023
- });
1024
- uploadPayload.forEach(({ uploadId, file }) => {
1025
- if (isFileWithProcessing(file)) {
1026
- unprocessedUploads.add(uploadId);
1027
- }
1028
- });
1029
- const uploadIds = uploadPayload.map((upload) => upload.uploadId);
1030
- useUploadManagerResult.start(parentId, uploadPayload);
1031
- return uploadIds;
1032
- } catch (error) {
1033
- throw rfcErrors.tryParse(error) ?? error;
1034
- } finally {
1035
- prepareQueueSize.value -= enqueueableFiles.length;
1036
- }
1037
- },
1038
- cancel: (uploadId) => {
1039
- useUploadManagerResult.cancel(uploadId);
1040
- cancelUpload(uploadId).catch((error) => {
1041
- consola.error("There was a problem cancelling the upload", { error });
1042
- });
1043
- }
1044
- };
900
+ //#endregion
901
+ //#region src/dataTransfer/upload/useFileUpload.ts
902
+ var DEFAULT_MAX_UPLOAD_QUEUE_SIZE = 10;
903
+ var DEFAULT_UPLOAD_SIZE_LIMIT_BYTES = 5 * 1024 * 1024 * 1024;
904
+ var DEFAULT_RETRY_DELAY_MS = 50;
905
+ var getUploadSizeLimitBytes = () => DEFAULT_UPLOAD_SIZE_LIMIT_BYTES;
906
+ /**
907
+ * This composable handles uploads of data to a hub repository item (space or workflow group).
908
+ * It is a multi-step process which is described as follows:
909
+ *
910
+ * Step 1:
911
+ * First we need to prepare an upload. The items will be uploaded to a `parent` container
912
+ * identified by its id. Then, for each file we want to upload, we must provide paths relative
913
+ * to the parent that identify each file. Example:
914
+ * ```
915
+ * {
916
+ * "items": {
917
+ * "my-file.txt": { itemContentType: "text/plain", itemContentSize: 5000 },
918
+ * "folder/my-file.zip": { itemContentType: "application/zip", itemContentSize: 5000 },
919
+ * }
920
+ * }
921
+ * ```
922
+ * This describes 2 items, one placed in the parent's root and another inside a folder.
923
+ * The response for this would look like:
924
+ * ```
925
+ * {
926
+ * "items": {
927
+ * "my-file.txt": { uploadId: "id1" },
928
+ * "folder/my-file.zip": { uploadId: "id2" },
929
+ * }
930
+ * }
931
+ * ```
932
+ * Now, each corresponding `uploadId` uniquely identifies the upload process for
933
+ * their respective files.
934
+ *
935
+ * Step 2:
936
+ * Next, we need to start uploading the file parts. Technically, you _can_ just upload
937
+ * each file as one big file "part", but practically that is not recommended because a hiccup
938
+ * in the connection means you would have to start the upload from scratch; instead, we split
939
+ * the files in smaller parts.
940
+ * The upload manager helper will take care of splitting the file. However, the responsibility
941
+ * of this composable is to supply a function to resolve the upload URL. This upload URL is obtained
942
+ * by POSTing to an endpoint with the `uploadId` and the `partNumber` and this will return
943
+ * the data required to make an upload to a presigned URL; this will be called for each file part.
944
+ *
945
+ * Step 3:
946
+ * Completion:
947
+ * Once the upload of a single file is complete, the upload manager will trigger the `onFileUploadComplete`
948
+ * callback, which then will supply the ids of all the different parts that got uploaded for a given `uploadId`
949
+ * Then we need to "complete" the upload by sending a request which provides the `uploadId` and a list of
950
+ * the ids of all the parts. Note that some backends don't return the ids of the file parts after a successful
951
+ * upload of a part, in this case empty strings or null can be used instead of the part id.
952
+ *
953
+ * Cancellation:
954
+ * Alternative to completion, you can also cancel an upload. For this, the composable makes a DELETE request
955
+ * providing the corresponding `uploadId` which will effectively cancel it.
956
+ *
957
+ *
958
+ * For a usage example, refer to the Docs page in storybook.
959
+ */
960
+ var useFileUpload = (options = {}) => {
961
+ const $ofetch = createHttpClient(options.customFetchClientOptions);
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);
997
+ let useUploadManagerResult = null;
998
+ const setProcessingCompleted = ({ uploadId }) => {
999
+ unprocessedUploads.delete(uploadId);
1000
+ };
1001
+ const setProcessingFailed = ({ uploadId, error }) => {
1002
+ unprocessedUploads.delete(uploadId);
1003
+ useUploadManagerResult?.setFailed(uploadId, error ?? /* @__PURE__ */ new Error("An error occurred when processing the file"));
1004
+ };
1005
+ useUploadManagerResult = useUploadManager({
1006
+ resolveFilePartUploadURL,
1007
+ onFileUploadComplete: ({ uploadId, filePartIds, parentId, name }) => {
1008
+ promise_exports.retryPromise({ fn: () => completeUpload(uploadId, filePartIds) }).then(() => {
1009
+ options.onFileUploadComplete?.({
1010
+ uploadId,
1011
+ filePartIds,
1012
+ parentId,
1013
+ name
1014
+ });
1015
+ }).catch((error) => {
1016
+ consola.error("Error attempting to complete upload", { error });
1017
+ setProcessingFailed({
1018
+ uploadId,
1019
+ error
1020
+ });
1021
+ });
1022
+ },
1023
+ onFileUploadFailed: ({ uploadId, error, parentId }) => {
1024
+ options?.onFileUploadFailed?.({
1025
+ uploadId,
1026
+ error,
1027
+ parentId
1028
+ });
1029
+ }
1030
+ });
1031
+ const getEnqueueableFiles = (files) => {
1032
+ const { totalPendingUploads } = useUploadManagerResult;
1033
+ const { maxUploadQueueSize = DEFAULT_MAX_UPLOAD_QUEUE_SIZE } = options;
1034
+ if (files.length + totalPendingUploads.value > maxUploadQueueSize) options.onUploadQueueSizeExceeded?.(maxUploadQueueSize);
1035
+ const effectiveQueueSize = Math.max(0, maxUploadQueueSize - totalPendingUploads.value);
1036
+ return files.slice(0, effectiveQueueSize);
1037
+ };
1038
+ 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
+ const prepareQueueSize = ref(0);
1052
+ return {
1053
+ ...uploadManagerResultRest,
1054
+ uploadQueue,
1055
+ unprocessedUploads: readonly(unprocessedUploads),
1056
+ setProcessingCompleted,
1057
+ setProcessingFailed,
1058
+ isPreparingUpload: computed(() => prepareQueueSize.value > 0),
1059
+ totalFilesBeingPrepared: computed(() => prepareQueueSize.value),
1060
+ start: async (parentId, files, options = {}) => {
1061
+ const { isFileWithProcessing = isKNWFFile } = options;
1062
+ const enqueueableFiles = getEnqueueableFiles(files);
1063
+ const uploadSizeLimitBytes = getUploadSizeLimitBytes();
1064
+ const oversizedFiles = enqueueableFiles.filter((file) => file.size > uploadSizeLimitBytes);
1065
+ if (oversizedFiles.length > 0) throw new rfcErrors.RFCError({
1066
+ title: "Upload size limit exceeded",
1067
+ status: 413,
1068
+ details: oversizedFiles.map((file) => `${file.name} (${formatFileSize(file.size)}) exceeds the upload limit of ${formatFileSize(uploadSizeLimitBytes)}.`)
1069
+ });
1070
+ try {
1071
+ if (enqueueableFiles.length === 0) return [];
1072
+ prepareQueueSize.value += enqueueableFiles.length;
1073
+ const uploadPayload = await promise_exports.retryPromise({
1074
+ fn: () => prepareUpload(parentId, enqueueableFiles),
1075
+ excludeError: (error) => Boolean(error.statusCode && error.statusCode < 500),
1076
+ retryDelayMS: DEFAULT_RETRY_DELAY_MS
1077
+ });
1078
+ uploadPayload.forEach(({ uploadId, file }) => {
1079
+ if (isFileWithProcessing(file)) unprocessedUploads.add(uploadId);
1080
+ });
1081
+ const uploadIds = uploadPayload.map((upload) => upload.uploadId);
1082
+ useUploadManagerResult.start(parentId, uploadPayload);
1083
+ return uploadIds;
1084
+ } catch (error) {
1085
+ throw rfcErrors.tryParse(error) ?? error;
1086
+ } finally {
1087
+ prepareQueueSize.value -= enqueueableFiles.length;
1088
+ }
1089
+ },
1090
+ cancel: (uploadId) => {
1091
+ useUploadManagerResult.cancel(uploadId);
1092
+ cancelUpload(uploadId).catch((error) => {
1093
+ consola.error("There was a problem cancelling the upload", { error });
1094
+ });
1095
+ }
1096
+ };
1045
1097
  };
1046
-
1047
- const DEFAULT_MAX_RETRIES = null;
1048
- const DEFAULT_POLLING_INTERVAL = 2e3;
1049
- const hasDownloadUrl = (response) => {
1050
- return "downloadUrl" in response;
1098
+ //#endregion
1099
+ //#region src/dataTransfer/download/useDownloadArtifact.ts
1100
+ var DEFAULT_MAX_RETRIES = null;
1101
+ var DEFAULT_POLLING_INTERVAL = 2e3;
1102
+ var hasDownloadUrl = (response) => {
1103
+ return "downloadUrl" in response;
1051
1104
  };
1052
- const isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
1053
- const useDownloadArtifact = (options = {}) => {
1054
- const $ofetch = createHttpClient(options.customFetchClientOptions);
1055
- const downloadItems = ref({});
1056
- const abortControllers = {};
1057
- const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
1058
- const pollingInterval = options.pollingInterval ?? DEFAULT_POLLING_INTERVAL;
1059
- const cancel = (downloadId) => {
1060
- const itemController = abortControllers[downloadId];
1061
- if (itemController) {
1062
- itemController.abort();
1063
- delete abortControllers[downloadId];
1064
- }
1065
- };
1066
- const removeItem = (downloadId) => {
1067
- cancel(downloadId);
1068
- delete downloadItems.value[downloadId];
1069
- };
1070
- const fetchDownloadStatus = ({
1071
- downloadId,
1072
- signal
1073
- }) => {
1074
- return $ofetch(`/downloads/${downloadId}/status`, {
1075
- method: "GET",
1076
- signal
1077
- });
1078
- };
1079
- const openDownload = (downloadUrl) => {
1080
- const a = document.createElement("a");
1081
- a.target = "_blank";
1082
- a.href = downloadUrl;
1083
- a.download = "";
1084
- a.style.display = "none";
1085
- document.body.appendChild(a);
1086
- a.click();
1087
- document.body.removeChild(a);
1088
- };
1089
- const handlePollingResult = async ({
1090
- downloadId,
1091
- signal
1092
- }) => {
1093
- try {
1094
- const statusResponse = await fetchDownloadStatus({
1095
- downloadId,
1096
- signal
1097
- });
1098
- if (statusResponse?.status === "READY" && statusResponse?.downloadUrl) {
1099
- downloadItems.value[downloadId].status = "READY";
1100
- downloadItems.value[downloadId].downloadUrl = statusResponse.downloadUrl;
1101
- openDownload(statusResponse.downloadUrl);
1102
- return true;
1103
- } else if (statusResponse?.status === "FAILED") {
1104
- let details = "Download failed";
1105
- if (statusResponse?.statusMessage) {
1106
- details += `: ${statusResponse.statusMessage}`;
1107
- }
1108
- throw new Error(details);
1109
- }
1110
- } catch (error) {
1111
- consola.error("Error fetching status:", error);
1112
- const isAborted = isAbortError(error) || error instanceof FetchError && isAbortError(error.cause);
1113
- if (!isAborted) {
1114
- downloadItems.value[downloadId].status = "FAILED";
1115
- downloadItems.value[downloadId].failureDetails = error instanceof Error ? error.message : error;
1116
- }
1117
- return true;
1118
- }
1119
- return false;
1120
- };
1121
- const pollDownloadItem = async ({
1122
- itemId,
1123
- downloadId,
1124
- version,
1125
- name
1126
- }) => {
1127
- const controller = new AbortController();
1128
- abortControllers[downloadId] = controller;
1129
- const signal = controller.signal;
1130
- let itemName = `${name}`;
1131
- if (version) {
1132
- itemName += ` (${version})`;
1133
- }
1134
- downloadItems.value[downloadId] = {
1135
- itemId,
1136
- ...version && { version },
1137
- downloadId,
1138
- name: itemName,
1139
- status: "IN_PROGRESS"
1140
- };
1141
- const abortListener = () => {
1142
- controller.signal.removeEventListener("abort", abortListener);
1143
- if (!downloadItems.value[downloadId]) {
1144
- return;
1145
- }
1146
- downloadItems.value[downloadId].status = "CANCELLED";
1147
- downloadItems.value[downloadId].failureDetails = "Download was cancelled manually";
1148
- };
1149
- controller.signal.addEventListener("abort", abortListener);
1150
- let tries = 0;
1151
- while (maxRetries === null || tries < maxRetries) {
1152
- tries++;
1153
- await sleep(pollingInterval);
1154
- const isTerminalState = await handlePollingResult({ downloadId, signal });
1155
- if (isTerminalState) {
1156
- break;
1157
- }
1158
- }
1159
- delete abortControllers[downloadId];
1160
- if (maxRetries !== null && tries >= maxRetries) {
1161
- downloadItems.value[downloadId].status = "FAILED";
1162
- downloadItems.value[downloadId].failureDetails = "Download timed out";
1163
- }
1164
- };
1165
- const start = async ({
1166
- itemId,
1167
- version,
1168
- name
1169
- }) => {
1170
- try {
1171
- const response = await $ofetch(
1172
- `/repository/${itemId}/artifact`,
1173
- {
1174
- method: "GET",
1175
- query: { version }
1176
- }
1177
- );
1178
- if (hasDownloadUrl(response)) {
1179
- openDownload(response.downloadUrl);
1180
- return;
1181
- }
1182
- await pollDownloadItem({
1183
- itemId,
1184
- version,
1185
- downloadId: response.downloadId,
1186
- name
1187
- });
1188
- } catch (error) {
1189
- throw rfcErrors.tryParse(error) ?? error;
1190
- }
1191
- };
1192
- const totalItemsBeingZipped = computed(
1193
- () => Object.values(downloadItems.value).filter(
1194
- ({ status }) => status === "IN_PROGRESS"
1195
- ).length
1196
- );
1197
- const resetState = () => {
1198
- for (const [downloadId, controller] of Object.entries(abortControllers)) {
1199
- controller?.abort();
1200
- delete abortControllers[downloadId];
1201
- }
1202
- downloadItems.value = {};
1203
- };
1204
- const downloadItemsArray = computed(() => Object.values(downloadItems.value));
1205
- return {
1206
- /**
1207
- * Initiates a download of the given itemId and version.
1208
- */
1209
- start,
1210
- /**
1211
- * Cancels the ongoing download for a given itemId (if any).
1212
- * Sets the status in `downloadItems` to `CANCELLED`.
1213
- */
1214
- cancel,
1215
- /**
1216
- * Removes item from `downloadItems`.
1217
- */
1218
- removeItem,
1219
- /**
1220
- * A computed array of all current items that are being zipped and prepared for download.
1221
- */
1222
- downloadItems: downloadItemsArray,
1223
- /**
1224
- * The total number of zipping items in progress.
1225
- */
1226
- totalItemsBeingZipped,
1227
- /**
1228
- * Resets the state of the composable, clearing all downloads and controllers.
1229
- */
1230
- resetState,
1231
- /**
1232
- * Opens a download from the given url
1233
- */
1234
- openDownload
1235
- };
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
+ };
1236
1289
  };
1290
+ //#endregion
1291
+ export { API_BASE_URL, DownloadProgressPanel_default as DownloadProgressPanel, UploadProgressPanel_default as UploadProgressPanel, createHttpClient, defaultConfig, useDownloadArtifact, useFileUpload };
1237
1292
 
1238
- export { API_BASE_URL, DownloadProgressPanel, UploadProgressPanel, createHttpClient, defaultConfig, useDownloadArtifact, useFileUpload };
1239
- //# sourceMappingURL=dataTransfer.js.map
1293
+ //# sourceMappingURL=dataTransfer.js.map