@zjlab-fe/data-hub-ui 0.21.6 → 0.22.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 (226) hide show
  1. package/dist/types/components/tip-tap/extensions/index.d.ts +1 -1
  2. package/es/assets/cardImg.png.js +3 -0
  3. package/es/assets/closeBtn.png.js +3 -0
  4. package/es/assets/icon-tag-header.png.js +3 -0
  5. package/es/assets/source.png.js +3 -0
  6. package/es/components/FileUploader/FileUploadModal.js +24 -0
  7. package/es/components/FileUploader/FileUploader.js +20 -0
  8. package/es/components/FileUploader/FileUploaderImpl/FileUploader.js +34 -0
  9. package/es/components/FileUploader/FileUploaderImpl/FileUploaderContextProvider.js +46 -0
  10. package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiver.css.js +36 -0
  11. package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiver.js +57 -0
  12. package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiverContainer.js +24 -0
  13. package/es/components/FileUploader/FileUploaderImpl/FileUploaderFileReceiverDesc.js +9 -0
  14. package/es/components/FileUploader/FileUploaderImpl/FileUploaderList.js +46 -0
  15. package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItem.js +46 -0
  16. package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItemFail.js +21 -0
  17. package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItemPaused.js +20 -0
  18. package/es/components/FileUploader/FileUploaderImpl/FileUploaderListItemUploading.js +43 -0
  19. package/es/components/FileUploader/FileUploaderImpl/FileUploaderlListItemFinished.js +32 -0
  20. package/es/components/FileUploader/Locale/translations.js +52 -0
  21. package/es/components/FileUploader/Locale/useTranslate.js +16 -0
  22. package/es/components/FileUploader/UploadStoreProvider/UploadStoreProvider.js +31 -0
  23. package/es/components/FileUploader/UploadStoreProvider/uploadStoreReducer.js +62 -0
  24. package/es/components/FileUploader/UploadStoreProvider/useOnFileAllUploaded.js +31 -0
  25. package/es/components/FileUploader/UploadStoreProvider/useSaveUnfinishedUploads.js +42 -0
  26. package/es/components/FileUploader/UploadStoreProvider/useSetupMergingEvent.js +45 -0
  27. package/es/components/FileUploader/hooks/useCancelUpload.js +40 -0
  28. package/es/components/FileUploader/hooks/useCurrentFile.js +61 -0
  29. package/es/components/FileUploader/hooks/useCurrentFileOnUploading.js +52 -0
  30. package/es/components/FileUploader/hooks/useCurrentFileSetupAndBackUp.js +39 -0
  31. package/es/components/FileUploader/hooks/useFormatFiles.js +64 -0
  32. package/es/components/FileUploader/hooks/useFormatFilesFindDuplicates.js +75 -0
  33. package/es/components/FileUploader/hooks/useFormatFilesRemoveDuplicates.js +21 -0
  34. package/es/components/FileUploader/hooks/useHandleChunkCompletion.js +31 -0
  35. package/es/components/FileUploader/hooks/useOnMounted.js +24 -0
  36. package/es/components/FileUploader/hooks/useOnMountedResumeUnfinished.js +58 -0
  37. package/es/components/FileUploader/hooks/useRetryUpload.js +33 -0
  38. package/es/components/FileUploader/hooks/useUploadChunk.js +46 -0
  39. package/es/components/FileUploader/icons/CloseFilled.js +7 -0
  40. package/es/components/FileUploader/icons/UnkownFileIcon.js +7 -0
  41. package/es/components/FileUploader/icons/UploadIcon.js +7 -0
  42. package/es/components/FileUploader/index.css.js +532 -0
  43. package/es/components/FileUploader/index.js +23 -0
  44. package/es/components/FileUploader/types.js +13 -0
  45. package/es/components/FileUploader/utils/calcDisplaySize.js +20 -0
  46. package/es/components/FileUploader/utils/checkExpiryTime.js +15 -0
  47. package/es/components/FileUploader/utils/createFileToUpload.js +60 -0
  48. package/es/components/FileUploader/utils/fileDB/addFilesToIndexdb.js +14 -0
  49. package/es/components/FileUploader/utils/fileDB/deleteFileInStore.js +18 -0
  50. package/es/components/FileUploader/utils/fileDB/deleteObjectStore.js +25 -0
  51. package/es/components/FileUploader/utils/fileDB/index.js +77 -0
  52. package/es/components/FileUploader/utils/fileDB/openDBAndRetrieveData.js +90 -0
  53. package/es/components/FileUploader/utils/fileDB/retrieveExistingData.js +49 -0
  54. package/es/components/FileUploader/utils/modalConfig.css.js +20 -0
  55. package/es/components/FileUploader/utils/modalConfig.js +32 -0
  56. package/es/components/FileUploader/utils/retrieveAllFiles.js +70 -0
  57. package/es/components/FileUploader/utils/splitFileIntoChunk.js +15 -0
  58. package/es/components/SDK-modal/document-link.js +8 -0
  59. package/es/components/SDK-modal/index.js +25 -0
  60. package/es/components/SDK-modal/index.module.scss.js +49 -0
  61. package/es/components/SDK-modal/inner-modal.js +59 -0
  62. package/es/components/SDK-modal/locale/translations.js +17 -0
  63. package/es/components/apply-perm-modal/index.js +53 -0
  64. package/es/components/apply-perm-modal/index.module.scss.js +96 -0
  65. package/es/components/auth-tag/index.js +23 -0
  66. package/es/components/bench-card/index.js +20 -0
  67. package/es/components/bench-card/index.module.scss.js +48 -0
  68. package/es/components/confirm-again/index.js +15 -0
  69. package/es/components/confirm-again/index.scss.js +37 -0
  70. package/es/components/copy/index.js +41 -0
  71. package/es/components/copy/index.module.scss.js +15 -0
  72. package/es/components/corpus-card/index.js +88 -0
  73. package/es/components/corpus-card/index.module.scss.js +171 -0
  74. package/es/components/feature-card/classNames.js +72 -0
  75. package/es/components/feature-card/components.js +48 -0
  76. package/es/components/feature-card/context.js +13 -0
  77. package/es/components/feature-card/index.css.js +655 -0
  78. package/es/components/feature-card/index.js +48 -0
  79. package/es/components/file-preview/data-table/index.js +94 -0
  80. package/es/components/file-preview/data-table/index.module.scss.js +54 -0
  81. package/es/components/file-preview/excel-preview/index.js +132 -0
  82. package/es/components/file-preview/excel-preview/index.module.scss.js +11 -0
  83. package/es/components/file-preview/excel-preview/utils.js +46 -0
  84. package/es/components/file-preview/img-preview/index.js +18 -0
  85. package/es/components/file-preview/img-preview/index.module.scss.js +17 -0
  86. package/es/components/file-preview/index.js +124 -0
  87. package/es/components/file-preview/index.module.scss.js +32 -0
  88. package/es/components/file-preview/json-preview/index.js +166 -0
  89. package/es/components/file-preview/markdown-preview/index.js +53 -0
  90. package/es/components/file-preview/markdown-preview/index.module.scss.js +1055 -0
  91. package/es/components/file-preview/parquet-preview/index.js +54 -0
  92. package/es/components/file-preview/pdf-preview/index.js +215 -0
  93. package/es/components/file-preview/pdf-preview/index.module.scss.js +16 -0
  94. package/es/components/file-preview/table-preview/index.js +43 -0
  95. package/es/components/file-preview/table-preview/index.module.scss.js +11 -0
  96. package/es/components/file-preview/txt-preview/index.js +24 -0
  97. package/es/components/file-preview/util.js +94 -0
  98. package/es/components/file-uploader/components/circle-progress.css.js +39 -0
  99. package/es/components/file-uploader/components/circle-progress.js +12 -0
  100. package/es/components/file-uploader/components/hooks/use-drop-zone.js +74 -0
  101. package/es/components/file-uploader/components/hooks/use-file-validation.js +52 -0
  102. package/es/components/file-uploader/components/icons.js +19 -0
  103. package/es/components/file-uploader/components/theme.css.js +64 -0
  104. package/es/components/file-uploader/components/uploader-drop-zone.css.js +66 -0
  105. package/es/components/file-uploader/components/uploader-drop-zone.js +74 -0
  106. package/es/components/file-uploader/components/uploader-file-item.css.js +186 -0
  107. package/es/components/file-uploader/components/uploader-file-item.js +63 -0
  108. package/es/components/file-uploader/components/uploader-file-list.css.js +43 -0
  109. package/es/components/file-uploader/components/uploader-file-list.js +27 -0
  110. package/es/components/file-uploader/components/uploader.css.js +20 -0
  111. package/es/components/file-uploader/components/uploader.js +51 -0
  112. package/es/components/file-uploader/components/uploading-status.css.js +52 -0
  113. package/es/components/file-uploader/components/uploading-status.js +54 -0
  114. package/es/components/file-uploader/components/utils/directory-reader.js +65 -0
  115. package/es/components/file-uploader/constants.js +31 -0
  116. package/es/components/file-uploader/context/action-registry.js +69 -0
  117. package/es/components/file-uploader/context/file-state-store.js +83 -0
  118. package/es/components/file-uploader/context/handler-registry.js +119 -0
  119. package/es/components/file-uploader/context/upload-context.js +36 -0
  120. package/es/components/file-uploader/context/upload-provider.js +77 -0
  121. package/es/components/file-uploader/context/upload-reducer.js +21 -0
  122. package/es/components/file-uploader/context/use-upload-engine.js +440 -0
  123. package/es/components/file-uploader/engine/queue-callbacks.js +257 -0
  124. package/es/components/file-uploader/engine/queue-manager.js +195 -0
  125. package/es/components/file-uploader/engine/queue-uploader.js +25 -0
  126. package/es/components/file-uploader/hooks/use-upload.js +327 -0
  127. package/es/components/file-uploader/utils/chunk-handlers.js +66 -0
  128. package/es/components/file-uploader/utils/ensure-handlers.js +53 -0
  129. package/es/components/file-uploader/utils/error-handler.js +84 -0
  130. package/es/components/file-uploader/utils/upload-controllers.js +43 -0
  131. package/es/components/file-uploader/utils/upload-handler.js +68 -0
  132. package/es/components/file-uploader/utils/validate-handlers.js +16 -0
  133. package/es/components/floating-label-input/index.js +46 -0
  134. package/es/components/floating-label-input/index.module.scss.js +30 -0
  135. package/es/components/floating-layer/index.js +15 -0
  136. package/es/components/floating-layer/index.scss.js +31 -0
  137. package/es/components/header/index.js +10 -0
  138. package/es/components/header/index.module.scss.js +16 -0
  139. package/es/components/input-tag/index.js +65 -0
  140. package/es/components/menu/index.js +96 -0
  141. package/es/components/menu/index.module.scss.js +188 -0
  142. package/es/components/model-card/index.js +20 -0
  143. package/es/components/model-card/index.module.scss.js +116 -0
  144. package/es/components/notion-editor/code-block/index.js +18 -0
  145. package/es/components/notion-editor/code-block/languageDetector.js +134 -0
  146. package/es/components/notion-editor/code-block/languages.js +42 -0
  147. package/es/components/notion-editor/index.js +258 -0
  148. package/es/components/notion-editor/index.scss.js +421 -0
  149. package/es/components/notion-editor/parseMarkdown.js +184 -0
  150. package/es/components/operator-chain/components/AddButton.js +20 -0
  151. package/es/components/operator-chain/components/IOCard.js +8 -0
  152. package/es/components/operator-chain/components/SingleCard.js +12 -0
  153. package/es/components/operator-chain/components/VirtualDropdown.js +56 -0
  154. package/es/components/operator-chain/index.js +83 -0
  155. package/es/components/operator-chain/index.module.scss.js +232 -0
  156. package/es/components/permission-editor/index.js +9 -0
  157. package/es/components/permission-editor/permissionEditModal/index.js +37 -0
  158. package/es/components/permission-editor/permissionEditor/index.js +253 -0
  159. package/es/components/permission-editor/permissionEditor/index.module.scss.js +22 -0
  160. package/es/components/permission-editor/permissionViewPopover/index.js +9 -0
  161. package/es/components/popover-select/index.js +42 -0
  162. package/es/components/popover-select/index.module.scss.js +22 -0
  163. package/es/components/radio-card/index.js +24 -0
  164. package/es/components/radio-card/index.scss.js +58 -0
  165. package/es/components/section-heading/classNames.js +22 -0
  166. package/es/components/section-heading/components.js +27 -0
  167. package/es/components/section-heading/context.js +13 -0
  168. package/es/components/section-heading/index.css.js +648 -0
  169. package/es/components/section-heading/index.js +37 -0
  170. package/es/components/status-tag/index.js +31 -0
  171. package/es/components/tag-group-filter/dataSizeFilter.js +100 -0
  172. package/es/components/tag-group-filter/index.js +53 -0
  173. package/es/components/tag-group-filter/index.module.scss.js +134 -0
  174. package/es/components/tag-group-filter/tagFilter.js +19 -0
  175. package/es/components/tag-view/index.js +138 -0
  176. package/es/components/tag-view/index.module.scss.js +56 -0
  177. package/es/components/tag-view/useSize.js +41 -0
  178. package/es/components/tip-tap/bubble-menu.js +47 -0
  179. package/es/components/tip-tap/editor.js +34 -0
  180. package/es/components/tip-tap/extensions/index.js +133 -0
  181. package/es/components/tip-tap/extensions/markdown-paste.js +148 -0
  182. package/es/components/tip-tap/icons/index.js +29 -0
  183. package/es/components/tip-tap/reader.js +15 -0
  184. package/es/components/tip-tap/styles/editor.css.js +863 -0
  185. package/es/components/tip-tap/toolbar/components/color-picker.js +76 -0
  186. package/es/components/tip-tap/toolbar/components/heading-dropdown.js +44 -0
  187. package/es/components/tip-tap/toolbar/components/image-button.js +26 -0
  188. package/es/components/tip-tap/toolbar/components/link-button.js +107 -0
  189. package/es/components/tip-tap/toolbar/components/table-button.js +37 -0
  190. package/es/components/tip-tap/toolbar/components/toolbar-button.js +7 -0
  191. package/es/components/tip-tap/toolbar/index.js +39 -0
  192. package/es/components/uploadDrawer/UploadStoreProvider.js +61 -0
  193. package/es/components/uploadDrawer/fileUploadDrawer.js +66 -0
  194. package/es/components/uploadDrawer/fileUploadDrawerFileDragger.js +71 -0
  195. package/es/components/uploadDrawer/fileUploadDrawerList.js +54 -0
  196. package/es/components/uploadDrawer/fileUploadDrawerListProgressButton.js +54 -0
  197. package/es/components/uploadDrawer/hooks/useCancelUpload.js +37 -0
  198. package/es/components/uploadDrawer/hooks/useFilterFiles.js +107 -0
  199. package/es/components/uploadDrawer/hooks/useFinishUpload.js +33 -0
  200. package/es/components/uploadDrawer/hooks/useFormatFiles.js +54 -0
  201. package/es/components/uploadDrawer/hooks/useProgressButtonRef.js +22 -0
  202. package/es/components/uploadDrawer/hooks/useRemoveFilesInUploadQueue.js +21 -0
  203. package/es/components/uploadDrawer/hooks/useResumeUnfinishedUploads.js +52 -0
  204. package/es/components/uploadDrawer/hooks/useRetryUpload.js +35 -0
  205. package/es/components/uploadDrawer/hooks/useSaveUnfinishedUploads.js +42 -0
  206. package/es/components/uploadDrawer/hooks/useSetupUpload.js +80 -0
  207. package/es/components/uploadDrawer/hooks/useUploadFileToOSS.js +23 -0
  208. package/es/components/uploadDrawer/index.css.js +511 -0
  209. package/es/components/uploadDrawer/index.js +43 -0
  210. package/es/components/uploadDrawer/utils/calcDisplaySize.js +19 -0
  211. package/es/components/uploadDrawer/utils/constant.js +16 -0
  212. package/es/components/uploadDrawer/utils/createFileToUpload.js +51 -0
  213. package/es/components/uploadDrawer/utils/fileDB/deleteFileInStore.js +28 -0
  214. package/es/components/uploadDrawer/utils/fileDB/deleteObjectStore.js +28 -0
  215. package/es/components/uploadDrawer/utils/fileDB/handleAddFilesToDB.js +37 -0
  216. package/es/components/uploadDrawer/utils/fileDB/index.js +9 -0
  217. package/es/components/uploadDrawer/utils/fileDB/openDB.js +111 -0
  218. package/es/components/uploadDrawer/utils/retrieveAllFiles.js +110 -0
  219. package/es/components/uploadDrawer/utils/shouldCreateNewFile.js +13 -0
  220. package/es/components/uploadDrawer/utils/splitFileIntoChunk.js +15 -0
  221. package/es/index.js +42 -1
  222. package/es/lib/utils.js +8 -0
  223. package/lib/index.js +1 -1
  224. package/package.json +164 -154
  225. package/rollup.es.config.mjs +177 -0
  226. package/tsconfig.rollup.json +7 -0
@@ -0,0 +1,327 @@
1
+ import { __awaiter } from 'tslib';
2
+ import { useUploadContext } from '../context/upload-context.js';
3
+ import { useCallback, useRef, useMemo, useEffect } from 'react';
4
+ import { UploadStatus } from '../constants.js';
5
+ import { handlerRegistry } from '../context/handler-registry.js';
6
+ import { actionRegistry } from '../context/action-registry.js';
7
+ import { calculateChunkSize, createChunks, generateUploadId } from '../utils/chunk-handlers.js';
8
+ import { getMissingHandlers } from '../utils/validate-handlers.js';
9
+ import { useUploadEngine } from '../context/use-upload-engine.js';
10
+
11
+ /**
12
+ * Main consumer hook for file uploads - Core functionality.
13
+ *
14
+ * This hook provides essential upload API with:
15
+ * - `files` - All files in the upload queue
16
+ * - `start`, `pause`, `resume`, `cancel` - Single file controls
17
+ * - `addFiles`, `removeFile` - File operations
18
+ *
19
+ * Use this hook for the most common upload scenarios.
20
+ * For extended functionality (batch operations, file selectors), use `useUploadExtend`.
21
+ *
22
+ * Global config (chunk sizes, concurrency) comes from UploadProvider.
23
+ * Handlers are provided per-file at selection time via addFiles options,
24
+ * allowing runtime-dependent targets (e.g., different folders/datasets).
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * function MyUploader({ datasetId }: { datasetId: string }) {
29
+ * const { files, addFiles, start, pause } = useUpload();
30
+ *
31
+ * return (
32
+ * <>
33
+ * <input
34
+ * type="file"
35
+ * onChange={(e) =>
36
+ * addFiles(e.target.files, {
37
+ * getHandlers: (file) => ({
38
+ * onGetUploadUrls: (fileName, partCount) =>
39
+ * api.getUploadUrls(datasetId, fileName, partCount),
40
+ * onPartComplete: (fileName, uploadId, partNum) =>
41
+ * api.markPartComplete(datasetId, uploadId, partNum),
42
+ * onFileComplete: (fileName, uploadId) =>
43
+ * api.completeUpload(datasetId, uploadId),
44
+ * }),
45
+ * })
46
+ * }
47
+ * />
48
+ * <div>
49
+ * {files.map(f => (
50
+ * <div key={f.id}>
51
+ * {f.fileName}
52
+ * <button onClick={() => start(f.id)}>Start</button>
53
+ * </div>
54
+ * ))}
55
+ * </div>
56
+ * </>
57
+ * );
58
+ * }
59
+ * ```
60
+ */
61
+ function useUpload() {
62
+ const { state, dispatch, config } = useUploadContext();
63
+ const { startUpload, pauseUpload, resumeUpload, cancelUpload } = useUploadEngine();
64
+ // ─────────────────────────────────────────────────────────────────────────────
65
+ // FILE OPERATIONS
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+ /**
68
+ * Add files to the upload queue.
69
+ * Accepts FileWithPath array with relative path information.
70
+ *
71
+ * @param files - Array of FileWithPath objects containing file and relativePath
72
+ * @param addOptions - Upload handlers (required) and optional configuration
73
+ * @param runtime - Optional runtime context (e.g., datasetId, folderId)
74
+ */
75
+ const addFiles = useCallback((files, addOptions, runtime) => {
76
+ if (!files.length)
77
+ return;
78
+ const newFiles = files.map((fileWithPath) => {
79
+ var _a, _b;
80
+ const { file, relativePath } = fileWithPath;
81
+ const resolvedHandlers = (_b = (_a = addOptions.getHandlers) === null || _a === void 0 ? void 0 : _a.call(addOptions, file)) !== null && _b !== void 0 ? _b : addOptions.handlers;
82
+ if (!resolvedHandlers) {
83
+ throw new Error('addFiles requires handlers per file. Provide options.handlers or options.getHandlers.');
84
+ }
85
+ const missing = getMissingHandlers(resolvedHandlers);
86
+ if (missing.length > 0) {
87
+ throw new Error(`Missing required upload handlers: ${missing.join(', ')}.`);
88
+ }
89
+ const chunkSize = calculateChunkSize(file.size, config);
90
+ const chunks = createChunks(file.size, chunkSize);
91
+ const id = generateUploadId();
92
+ // Register per-file handlers immediately so uploads can continue across navigation
93
+ handlerRegistry.register(id, resolvedHandlers, 'addFiles');
94
+ return {
95
+ id,
96
+ file,
97
+ fileName: file.name,
98
+ fileSize: file.size,
99
+ lastModified: file.lastModified,
100
+ status: UploadStatus.PENDING,
101
+ progress: 0,
102
+ uploadId: '',
103
+ chunks,
104
+ chunkSize,
105
+ relativePath,
106
+ runtime,
107
+ createdAt: Date.now(),
108
+ };
109
+ });
110
+ dispatch({ type: 'ADD_FILES', files: newFiles });
111
+ }, [config, dispatch]);
112
+ /**
113
+ * Remove a file from the queue and notify server if needed.
114
+ *
115
+ * @param id - File ID to remove
116
+ */
117
+ const removeFile = useCallback((id) => __awaiter(this, void 0, void 0, function* () {
118
+ // Get file and handlers before removing from state
119
+ const file = state.files.find((f) => f.id === id);
120
+ if (!file)
121
+ return;
122
+ const handlers = handlerRegistry.get(id);
123
+ // Dispatch removal
124
+ dispatch({ type: 'REMOVE_FILE', id });
125
+ handlerRegistry.unregister(id);
126
+ // Call onDeleteFile handler safely (don't block on failure)
127
+ try {
128
+ if (handlers === null || handlers === void 0 ? void 0 : handlers.onDeleteFile) {
129
+ const ctx = actionRegistry.getContext();
130
+ const result = yield handlers.onDeleteFile(file, ctx);
131
+ if (result && !result.success) {
132
+ console.warn(`Delete notification failed for ${file.fileName}:`, result.reason);
133
+ }
134
+ }
135
+ }
136
+ catch (error) {
137
+ console.warn(`Delete notification error for ${file.fileName}:`, error);
138
+ }
139
+ }), [state.files, dispatch]);
140
+ // ─────────────────────────────────────────────────────────────────────────────
141
+ // SINGLE FILE CONTROLS
142
+ // ─────────────────────────────────────────────────────────────────────────────
143
+ /**
144
+ * Start uploading a pending file.
145
+ *
146
+ * @param id - File ID to start
147
+ */
148
+ const start = useCallback((id) => __awaiter(this, void 0, void 0, function* () {
149
+ const file = state.files.find((f) => f.id === id);
150
+ if (file && file.status === UploadStatus.PENDING) {
151
+ yield startUpload(file);
152
+ }
153
+ }), [state.files, startUpload]);
154
+ /**
155
+ * Pause an uploading file.
156
+ * Active chunk uploads will be aborted.
157
+ *
158
+ * @param id - File ID to pause
159
+ */
160
+ const pause = useCallback((id) => pauseUpload(id), [pauseUpload]);
161
+ /**
162
+ * Resume a paused file.
163
+ * Continues from where it left off.
164
+ *
165
+ * @param id - File ID to resume
166
+ */
167
+ const resume = useCallback((id) => __awaiter(this, void 0, void 0, function* () { return resumeUpload(id); }), [resumeUpload]);
168
+ /**
169
+ * Cancel a file upload.
170
+ * Aborts all active uploads and notifies server.
171
+ *
172
+ * @param id - File ID to cancel
173
+ */
174
+ const cancel = useCallback((id) => cancelUpload(id), [cancelUpload]);
175
+ // ─────────────────────────────────────────────────────────────────────────────
176
+ // MUTEX FLAGS - Prevent concurrent batch operations
177
+ // ─────────────────────────────────────────────────────────────────────────────
178
+ const isStartingAllRef = useRef(false);
179
+ const isResumingAllRef = useRef(false);
180
+ // ─────────────────────────────────────────────────────────────────────────────
181
+ // SELECTORS
182
+ // ─────────────────────────────────────────────────────────────────────────────
183
+ const getFileById = useCallback((id) => state.files.find((f) => f.id === id), [state.files]);
184
+ const pendingFiles = useMemo(() => state.files.filter((f) => f.status === UploadStatus.PENDING), [state.files]);
185
+ const uploadingFiles = useMemo(() => state.files.filter((f) => f.status === UploadStatus.UPLOADING), [state.files]);
186
+ const successFiles = useMemo(() => state.files.filter((f) => f.status === UploadStatus.SUCCESS), [state.files]);
187
+ const failedFiles = useMemo(() => state.files.filter((f) => f.status === UploadStatus.FAILED), [state.files]);
188
+ const pausedFiles = useMemo(() => state.files.filter((f) => f.status === UploadStatus.PAUSED), [state.files]);
189
+ // ─────────────────────────────────────────────────────────────────────────────
190
+ // BATCH OPERATIONS
191
+ // ─────────────────────────────────────────────────────────────────────────────
192
+ /**
193
+ * Start all pending files.
194
+ * Files will be uploaded according to concurrency settings.
195
+ * Safe to call multiple times - will skip if already running.
196
+ */
197
+ const startAll = useCallback(() => __awaiter(this, void 0, void 0, function* () {
198
+ // Prevent concurrent executions
199
+ if (isStartingAllRef.current) {
200
+ return;
201
+ }
202
+ isStartingAllRef.current = true;
203
+ try {
204
+ // Capture pending files at the moment of call to avoid issues with state changes
205
+ const pending = state.files.filter((f) => f.status === UploadStatus.PENDING);
206
+ for (const file of pending) {
207
+ yield start(file.id);
208
+ }
209
+ }
210
+ finally {
211
+ isStartingAllRef.current = false;
212
+ }
213
+ }), [state.files, start]);
214
+ /**
215
+ * Pause all currently uploading files.
216
+ */
217
+ const pauseAll = useCallback(() => {
218
+ state.files.filter((f) => f.status === UploadStatus.UPLOADING).forEach((f) => pause(f.id));
219
+ }, [state.files, pause]);
220
+ /**
221
+ * Resume all paused files.
222
+ * Safe to call multiple times - will skip if already running.
223
+ */
224
+ const resumeAll = useCallback(() => __awaiter(this, void 0, void 0, function* () {
225
+ // Prevent concurrent executions
226
+ if (isResumingAllRef.current) {
227
+ return;
228
+ }
229
+ isResumingAllRef.current = true;
230
+ try {
231
+ const paused = state.files.filter((f) => f.status === UploadStatus.PAUSED);
232
+ for (const file of paused) {
233
+ yield resume(file.id);
234
+ }
235
+ }
236
+ finally {
237
+ isResumingAllRef.current = false;
238
+ }
239
+ }), [state.files, resume]);
240
+ /**
241
+ * Cancel all uploads (pending, uploading, and paused).
242
+ */
243
+ const cancelAll = useCallback(() => {
244
+ const cancelableStatuses = [UploadStatus.PENDING, UploadStatus.UPLOADING, UploadStatus.PAUSED];
245
+ state.files
246
+ .filter((f) => cancelableStatuses.includes(f.status))
247
+ .forEach((f) => cancel(f.id));
248
+ }, [state.files, cancel]);
249
+ /**
250
+ * Remove all succeeded files from the queue.
251
+ */
252
+ const clearSucceeded = useCallback(() => __awaiter(this, void 0, void 0, function* () {
253
+ const succeededFiles = state.files.filter((f) => f.status === UploadStatus.SUCCESS);
254
+ for (const file of succeededFiles) {
255
+ yield removeFile(file.id);
256
+ }
257
+ }), [state.files, removeFile]);
258
+ /**
259
+ * Remove all files from the queue (regardless of status).
260
+ */
261
+ const clearAll = useCallback(() => __awaiter(this, void 0, void 0, function* () {
262
+ const allFiles = [...state.files];
263
+ for (const file of allFiles) {
264
+ yield removeFile(file.id);
265
+ }
266
+ }), [state.files, removeFile]);
267
+ // ─────────────────────────────────────────────────────────────────────────────
268
+ // ACTION REGISTRY - Provide fresh context to handlers
269
+ // ─────────────────────────────────────────────────────────────────────────────
270
+ /**
271
+ * Update action registry with fresh references on every render.
272
+ * This ensures handlers always receive current versions of functions/state,
273
+ * solving the stale closure problem.
274
+ */
275
+ useEffect(() => {
276
+ actionRegistry.update({
277
+ files: state.files,
278
+ removeFile,
279
+ start,
280
+ pause,
281
+ resume,
282
+ cancel,
283
+ getFileById,
284
+ });
285
+ });
286
+ // ─────────────────────────────────────────────────────────────────────────────
287
+ // PUBLIC API - Core functionality only
288
+ // ─────────────────────────────────────────────────────────────────────────────
289
+ return {
290
+ // File state (from useUploadFiles)
291
+ files: state.files,
292
+ // File operations (from useUploadOperations)
293
+ addFiles,
294
+ removeFile,
295
+ // Single file controls (from useUploadControl)
296
+ start,
297
+ pause,
298
+ resume,
299
+ cancel,
300
+ /** Get a specific file by ID */
301
+ getFileById,
302
+ /** Files waiting to be uploaded */
303
+ pendingFiles,
304
+ /** Files currently uploading */
305
+ uploadingFiles,
306
+ /** Successfully completed uploads */
307
+ successFiles,
308
+ /** Failed uploads */
309
+ failedFiles,
310
+ /** Paused uploads */
311
+ pausedFiles,
312
+ /** Start all pending files */
313
+ startAll,
314
+ /** Pause all uploading files */
315
+ pauseAll,
316
+ /** Resume all paused files */
317
+ resumeAll,
318
+ /** Cancel all uploads */
319
+ cancelAll,
320
+ /** Clear all succeeded files */
321
+ clearSucceeded,
322
+ /** Clear all files */
323
+ clearAll,
324
+ };
325
+ }
326
+
327
+ export { useUpload };
@@ -0,0 +1,66 @@
1
+ import { DEFAULT_CHUNK_SIZE, ChunkStatus } from '../constants.js';
2
+
3
+ /**
4
+ * Calculate optimal chunk size based on file size and config constraints
5
+ */
6
+ function calculateChunkSize(fileSize, config) {
7
+ const { minChunkSize, maxChunkSize } = config;
8
+ // Start with default chunk size
9
+ let chunkSize = DEFAULT_CHUNK_SIZE;
10
+ // For very large files, increase chunk size to reduce chunk count
11
+ // Target max ~1000 chunks per file
12
+ const maxChunks = 1000;
13
+ const minChunkSizeForFile = Math.ceil(fileSize / maxChunks);
14
+ if (minChunkSizeForFile > chunkSize) {
15
+ chunkSize = minChunkSizeForFile;
16
+ }
17
+ // Clamp to config constraints
18
+ chunkSize = Math.max(minChunkSize, Math.min(maxChunkSize, chunkSize));
19
+ return chunkSize;
20
+ }
21
+ /**
22
+ * Calculate total number of chunks for a file
23
+ */
24
+ function calculateTotalChunks(fileSize, chunkSize) {
25
+ if (fileSize === 0)
26
+ return 1; // Empty file still needs 1 chunk
27
+ return Math.ceil(fileSize / chunkSize);
28
+ }
29
+ /**
30
+ * Create initial chunk states for a file
31
+ * @param fileSize - Total file size in bytes
32
+ * @param chunkSize - Size of each chunk in bytes
33
+ * @returns Array of ChunkState objects with status and progress, partNum will be assigned later based on server response
34
+ */
35
+ function createChunks(fileSize, chunkSize) {
36
+ const totalChunks = calculateTotalChunks(fileSize, chunkSize);
37
+ const chunks = [];
38
+ for (let i = 0; i < totalChunks; i++) {
39
+ chunks.push({
40
+ // partNum will be assigned after server provides upload URLs
41
+ status: ChunkStatus.PENDING,
42
+ progress: 0,
43
+ });
44
+ }
45
+ return chunks;
46
+ }
47
+ /**
48
+ * Extract a specific chunk blob from file
49
+ * @param file - The File object
50
+ * @param partNum - 0-indexed part number
51
+ * @param chunkSize - Size of each chunk in bytes
52
+ * @returns Blob containing the chunk data
53
+ */
54
+ function getChunkBlob(file, partNum, chunkSize) {
55
+ const start = partNum * chunkSize;
56
+ const end = Math.min(start + chunkSize, file.size);
57
+ return file.slice(start, end);
58
+ }
59
+ /**
60
+ * Generate unique upload file ID
61
+ */
62
+ function generateUploadId() {
63
+ return `upload-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
64
+ }
65
+
66
+ export { calculateChunkSize, calculateTotalChunks, createChunks, generateUploadId, getChunkBlob };
@@ -0,0 +1,53 @@
1
+ import { UploadStatus } from '../constants.js';
2
+ import { createUploadError, UPLOAD_ERROR_CODE } from './error-handler.js';
3
+ import { handlerRegistry } from '../context/handler-registry.js';
4
+ import { getHandlerContext } from '../engine/queue-callbacks.js';
5
+
6
+ /**
7
+ * Pure helper to fetch and validate handlers for a file.
8
+ * Returns null and dispatches failure if required handlers are missing.
9
+ */
10
+ function ensureHandlers(uploadFile, dispatch) {
11
+ var _a;
12
+ const handlers = handlerRegistry.get(uploadFile.id);
13
+ if (!handlers) {
14
+ const error = createUploadError(UPLOAD_ERROR_CODE.MISSING_HANDLER, 'Upload handlers not registered for file ' +
15
+ uploadFile.id +
16
+ '. Provide handlers via addFiles(options).');
17
+ dispatch({
18
+ type: 'UPDATE_FILE',
19
+ id: uploadFile.id,
20
+ updates: {
21
+ status: UploadStatus.FAILED,
22
+ error: error.message,
23
+ errorCode: error.code,
24
+ },
25
+ });
26
+ return null;
27
+ }
28
+ const missing = [];
29
+ if (!handlers.onGetUploadUrls)
30
+ missing.push('onGetUploadUrls');
31
+ if (!handlers.onPartComplete)
32
+ missing.push('onPartComplete');
33
+ if (!handlers.onFileComplete)
34
+ missing.push('onFileComplete');
35
+ if (missing.length > 0) {
36
+ const error = createUploadError(UPLOAD_ERROR_CODE.MISSING_HANDLER, `Missing required handlers: ${missing.join(', ')}`);
37
+ dispatch({
38
+ type: 'UPDATE_FILE',
39
+ id: uploadFile.id,
40
+ updates: {
41
+ status: UploadStatus.FAILED,
42
+ error: error.message,
43
+ errorCode: error.code,
44
+ },
45
+ });
46
+ const ctx = getHandlerContext();
47
+ (_a = handlers.onError) === null || _a === void 0 ? void 0 : _a.call(handlers, uploadFile, new Error(error.message), ctx);
48
+ return null;
49
+ }
50
+ return handlers;
51
+ }
52
+
53
+ export { ensureHandlers };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Standard error codes for upload operations.
3
+ * Allows consumers to handle specific error scenarios.
4
+ *
5
+ * Using const object instead of enum for compatibility with --erasableSyntaxOnly
6
+ * (TypeScript 5.8+) and better tree-shaking.
7
+ */
8
+ const UPLOAD_ERROR_CODE = {
9
+ // Network errors
10
+ NETWORK_ERROR: 'NETWORK_ERROR',
11
+ TIMEOUT: 'TIMEOUT',
12
+ ABORT: 'ABORT',
13
+ // Server errors
14
+ URL_REQUEST_FAILED: 'URL_REQUEST_FAILED',
15
+ CHUNK_UPLOAD_FAILED: 'CHUNK_UPLOAD_FAILED',
16
+ MERGE_FAILED: 'MERGE_FAILED',
17
+ SERVER_ERROR: 'SERVER_ERROR',
18
+ // Validation errors
19
+ FILE_TOO_LARGE: 'FILE_TOO_LARGE',
20
+ INVALID_FILE_TYPE: 'INVALID_FILE_TYPE',
21
+ FILE_VALIDATION_FAILED: 'FILE_VALIDATION_FAILED',
22
+ // State errors
23
+ MISSING_FILE: 'MISSING_FILE',
24
+ MISSING_HANDLER: 'MISSING_HANDLER',
25
+ INVALID_STATE: 'INVALID_STATE',
26
+ // Generic
27
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
28
+ };
29
+ /**
30
+ * Create a structured upload error.
31
+ */
32
+ function createUploadError(code, message, originalError, context) {
33
+ return {
34
+ code,
35
+ message,
36
+ originalError,
37
+ context,
38
+ };
39
+ }
40
+ /**
41
+ * Parse an unknown error into a structured UploadError.
42
+ * Attempts to extract meaningful information from various error types.
43
+ */
44
+ function parseError(error, defaultMessage = 'An error occurred') {
45
+ // Already an UploadError
46
+ if (isUploadError(error)) {
47
+ return error;
48
+ }
49
+ // Standard Error object
50
+ if (error instanceof Error) {
51
+ // Network errors
52
+ if (error.name === 'NetworkError' || error.message.includes('network')) {
53
+ return createUploadError(UPLOAD_ERROR_CODE.NETWORK_ERROR, 'Network connection failed', error);
54
+ }
55
+ // Timeout errors
56
+ if (error.name === 'TimeoutError' || error.message.includes('timeout')) {
57
+ return createUploadError(UPLOAD_ERROR_CODE.TIMEOUT, 'Request timed out', error);
58
+ }
59
+ // Abort errors
60
+ if (error.name === 'AbortError' || error.message.includes('abort')) {
61
+ return createUploadError(UPLOAD_ERROR_CODE.ABORT, 'Upload was cancelled', error);
62
+ }
63
+ // Generic error
64
+ return createUploadError(UPLOAD_ERROR_CODE.UNKNOWN_ERROR, error.message, error);
65
+ }
66
+ // String error
67
+ if (typeof error === 'string') {
68
+ return createUploadError(UPLOAD_ERROR_CODE.UNKNOWN_ERROR, error);
69
+ }
70
+ // Unknown error type
71
+ return createUploadError(UPLOAD_ERROR_CODE.UNKNOWN_ERROR, defaultMessage);
72
+ }
73
+ /**
74
+ * Type guard to check if an error is an UploadError.
75
+ */
76
+ function isUploadError(error) {
77
+ return (typeof error === 'object' &&
78
+ error !== null &&
79
+ 'code' in error &&
80
+ 'message' in error &&
81
+ typeof error.code === 'string');
82
+ }
83
+
84
+ export { UPLOAD_ERROR_CODE, createUploadError, isUploadError, parseError };
@@ -0,0 +1,43 @@
1
+ import { ChunkStatus } from '../constants.js';
2
+
3
+ /**
4
+ * Get chunks that need to be uploaded after a resume.
5
+ * Returns pending and failed chunks (excludes completed).
6
+ */
7
+ function getChunksForResume(file) {
8
+ const pendingPartNums = [];
9
+ let completedCount = 0;
10
+ for (const chunk of file.chunks) {
11
+ if (chunk.status === ChunkStatus.COMPLETED) {
12
+ completedCount++;
13
+ }
14
+ else {
15
+ // PENDING, FAILED, or UPLOADING (interrupted) - all need upload
16
+ // Only include if partNum is assigned (after URLs fetched)
17
+ if (chunk.partNum !== undefined) {
18
+ pendingPartNums.push(chunk.partNum);
19
+ }
20
+ }
21
+ }
22
+ return {
23
+ pendingPartNums,
24
+ pendingCount: pendingPartNums.length,
25
+ completedCount,
26
+ };
27
+ }
28
+ /**
29
+ * Reset interrupted chunks to pending state.
30
+ * Called when pausing - any uploading chunks should be marked pending.
31
+ * Returns updated chunks array.
32
+ */
33
+ function resetInterruptedChunks(chunks) {
34
+ return chunks.map((chunk) => {
35
+ // Uploading chunks were interrupted by pause - reset to pending
36
+ if (chunk.status === ChunkStatus.UPLOADING) {
37
+ return Object.assign(Object.assign({}, chunk), { status: ChunkStatus.PENDING, progress: 0 });
38
+ }
39
+ return chunk;
40
+ });
41
+ }
42
+
43
+ export { getChunksForResume, resetInterruptedChunks };
@@ -0,0 +1,68 @@
1
+ import { __awaiter } from 'tslib';
2
+ import axios from 'axios';
3
+
4
+ const DEFAULT_TIMEOUT = 30 * 60 * 1000; // 30 minutes
5
+ const DEFAULT_MAX_RETRIES = 3;
6
+ const BASE_RETRY_DELAY = 1000;
7
+ const uploadAxios = axios.create();
8
+ /** Upload a chunk with automatic retry and exponential backoff */
9
+ function uploadChunkWithRetry(chunk, uploadUrl, partNum, options) {
10
+ return __awaiter(this, void 0, void 0, function* () {
11
+ var _a;
12
+ const { signal, maxRetries = DEFAULT_MAX_RETRIES, requestConfig } = options || {};
13
+ let lastError;
14
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
15
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
16
+ return { success: false, partNum, error: new Error('Upload cancelled') };
17
+ }
18
+ const result = yield uploadChunk(chunk, uploadUrl, partNum, { signal, requestConfig });
19
+ if (result.success)
20
+ return result;
21
+ lastError = result.error;
22
+ if (((_a = result.error) === null || _a === void 0 ? void 0 : _a.message) === 'Upload cancelled')
23
+ return result;
24
+ // Exponential backoff before retry
25
+ if (attempt < maxRetries) {
26
+ yield sleep(BASE_RETRY_DELAY * Math.pow(2, attempt), signal);
27
+ }
28
+ }
29
+ return { success: false, partNum, error: lastError };
30
+ });
31
+ }
32
+ /** Upload a single chunk to the provided URL */
33
+ function uploadChunk(chunk, uploadUrl, partNum, options) {
34
+ return __awaiter(this, void 0, void 0, function* () {
35
+ var _a, _b;
36
+ const { signal, requestConfig = {} } = options || {};
37
+ try {
38
+ const axiosConfig = {
39
+ timeout: (_a = requestConfig.timeout) !== null && _a !== void 0 ? _a : DEFAULT_TIMEOUT,
40
+ withCredentials: (_b = requestConfig.withCredentials) !== null && _b !== void 0 ? _b : false,
41
+ headers: Object.assign({ 'Content-Type': 'application/octet-stream' }, requestConfig.headers),
42
+ signal,
43
+ };
44
+ yield uploadAxios.put(uploadUrl, chunk, axiosConfig);
45
+ return { success: true, partNum };
46
+ }
47
+ catch (error) {
48
+ if (axios.isCancel(error)) {
49
+ return { success: false, partNum, error: new Error('Upload cancelled') };
50
+ }
51
+ return { success: false, partNum, error: error };
52
+ }
53
+ });
54
+ }
55
+ /** Sleep helper that respects abort signal */
56
+ function sleep(ms, signal) {
57
+ return new Promise((resolve) => {
58
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted)
59
+ return resolve();
60
+ const timeout = setTimeout(resolve, ms);
61
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', () => {
62
+ clearTimeout(timeout);
63
+ resolve();
64
+ });
65
+ });
66
+ }
67
+
68
+ export { uploadChunkWithRetry };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Get list of missing required handler names.
3
+ * Returns empty array if all required handlers are present.
4
+ */
5
+ function getMissingHandlers(handlers) {
6
+ const missing = [];
7
+ if (!handlers.onGetUploadUrls)
8
+ missing.push('onGetUploadUrls');
9
+ if (!handlers.onPartComplete)
10
+ missing.push('onPartComplete');
11
+ if (!handlers.onFileComplete)
12
+ missing.push('onFileComplete');
13
+ return missing;
14
+ }
15
+
16
+ export { getMissingHandlers };