@spacecorexdev/core 0.0.2 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +20 -38
- package/public/cmaps/78-EUC-H.bcmap +0 -0
- package/public/cmaps/78-EUC-V.bcmap +0 -0
- package/public/cmaps/78-H.bcmap +0 -0
- package/public/cmaps/78-RKSJ-H.bcmap +0 -0
- package/public/cmaps/78-RKSJ-V.bcmap +0 -0
- package/public/cmaps/78-V.bcmap +0 -0
- package/public/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/public/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/public/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/public/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/public/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/public/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/public/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/public/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/public/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/public/cmaps/Add-H.bcmap +0 -0
- package/public/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/public/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/public/cmaps/Add-V.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/public/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/public/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/public/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/public/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/public/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/public/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/public/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/public/cmaps/B5-H.bcmap +0 -0
- package/public/cmaps/B5-V.bcmap +0 -0
- package/public/cmaps/B5pc-H.bcmap +0 -0
- package/public/cmaps/B5pc-V.bcmap +0 -0
- package/public/cmaps/CNS-EUC-H.bcmap +0 -0
- package/public/cmaps/CNS-EUC-V.bcmap +0 -0
- package/public/cmaps/CNS1-H.bcmap +0 -0
- package/public/cmaps/CNS1-V.bcmap +0 -0
- package/public/cmaps/CNS2-H.bcmap +0 -0
- package/public/cmaps/CNS2-V.bcmap +3 -0
- package/public/cmaps/ETHK-B5-H.bcmap +0 -0
- package/public/cmaps/ETHK-B5-V.bcmap +0 -0
- package/public/cmaps/ETen-B5-H.bcmap +0 -0
- package/public/cmaps/ETen-B5-V.bcmap +0 -0
- package/public/cmaps/ETenms-B5-H.bcmap +3 -0
- package/public/cmaps/ETenms-B5-V.bcmap +0 -0
- package/public/cmaps/EUC-H.bcmap +0 -0
- package/public/cmaps/EUC-V.bcmap +0 -0
- package/public/cmaps/Ext-H.bcmap +0 -0
- package/public/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/public/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/public/cmaps/Ext-V.bcmap +0 -0
- package/public/cmaps/GB-EUC-H.bcmap +0 -0
- package/public/cmaps/GB-EUC-V.bcmap +0 -0
- package/public/cmaps/GB-H.bcmap +4 -0
- package/public/cmaps/GB-V.bcmap +0 -0
- package/public/cmaps/GBK-EUC-H.bcmap +0 -0
- package/public/cmaps/GBK-EUC-V.bcmap +0 -0
- package/public/cmaps/GBK2K-H.bcmap +0 -0
- package/public/cmaps/GBK2K-V.bcmap +0 -0
- package/public/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/public/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/public/cmaps/GBT-EUC-H.bcmap +0 -0
- package/public/cmaps/GBT-EUC-V.bcmap +0 -0
- package/public/cmaps/GBT-H.bcmap +0 -0
- package/public/cmaps/GBT-V.bcmap +0 -0
- package/public/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/public/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/public/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/public/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/public/cmaps/H.bcmap +0 -0
- package/public/cmaps/HKdla-B5-H.bcmap +0 -0
- package/public/cmaps/HKdla-B5-V.bcmap +0 -0
- package/public/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/public/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/public/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/public/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/public/cmaps/HKm314-B5-H.bcmap +0 -0
- package/public/cmaps/HKm314-B5-V.bcmap +0 -0
- package/public/cmaps/HKm471-B5-H.bcmap +0 -0
- package/public/cmaps/HKm471-B5-V.bcmap +0 -0
- package/public/cmaps/HKscs-B5-H.bcmap +0 -0
- package/public/cmaps/HKscs-B5-V.bcmap +0 -0
- package/public/cmaps/Hankaku.bcmap +0 -0
- package/public/cmaps/Hiragana.bcmap +0 -0
- package/public/cmaps/KSC-EUC-H.bcmap +0 -0
- package/public/cmaps/KSC-EUC-V.bcmap +0 -0
- package/public/cmaps/KSC-H.bcmap +0 -0
- package/public/cmaps/KSC-Johab-H.bcmap +0 -0
- package/public/cmaps/KSC-Johab-V.bcmap +0 -0
- package/public/cmaps/KSC-V.bcmap +0 -0
- package/public/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/public/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/public/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/public/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/public/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/public/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/public/cmaps/Katakana.bcmap +0 -0
- package/public/cmaps/LICENSE +36 -0
- package/public/cmaps/NWP-H.bcmap +0 -0
- package/public/cmaps/NWP-V.bcmap +0 -0
- package/public/cmaps/RKSJ-H.bcmap +0 -0
- package/public/cmaps/RKSJ-V.bcmap +0 -0
- package/public/cmaps/Roman.bcmap +0 -0
- package/public/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/public/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/public/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/public/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/public/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/public/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/public/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/public/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/public/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/public/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/public/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/public/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/public/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/public/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/public/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/public/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/public/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/public/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/public/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/public/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/public/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/public/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/public/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/public/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/public/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/public/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/public/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/public/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/public/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/public/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/public/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/public/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/public/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/public/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/public/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/public/cmaps/V.bcmap +0 -0
- package/public/cmaps/WP-Symbol.bcmap +0 -0
- package/public/polyfills/polyfills.js +802 -0
- package/public/vendor/docx/docx.worker.js +5 -0
- package/public/vendor/docx/jszip.min.js +13 -0
- package/public/vendor/libarchive/libarchive.wasm +0 -0
- package/public/vendor/libarchive/worker-bundle.js +6 -0
- package/public/vendor/pptx/pptx.worker.js +26 -0
- package/public/wasm/cad/0.8.2/dwfv-render.wasm +0 -0
- package/public/wasm/cad/0.8.2/dwg-worker.js +24 -0
- package/public/wasm/cad/0.8.2/dwg-worker.js.map +1 -0
- package/public/wasm/cad/0.8.2/libredwg-web.js +2 -0
- package/public/wasm/cad/0.8.2/libredwg-web.wasm +0 -0
- package/public/wasm/cad/dwfv-render.wasm +0 -0
- package/public/wasm/cad/dwg-worker.js +24 -0
- package/public/wasm/cad/dwg-worker.js.map +1 -0
- package/public/wasm/cad/libredwg-web.js +2 -0
- package/public/wasm/cad/libredwg-web.wasm +0 -0
- package/public/wasm/data/sql-wasm.wasm +0 -0
- package/public/wasm/hdiff.wasm +0 -0
- package/public/wasm/model/LICENSE.occt-import-js.txt +504 -0
- package/public/wasm/model/LICENSE.occt.txt +502 -0
- package/public/wasm/model/occt-import-js.js +19 -0
- package/public/wasm/model/occt-import-js.wasm +0 -0
- package/public/wasm/model/occt-worker.js +83 -0
- package/public/wasm/typst/fonts/DejaVuSansMono-Bold.ttf +0 -0
- package/public/wasm/typst/fonts/DejaVuSansMono-BoldOblique.ttf +0 -0
- package/public/wasm/typst/fonts/DejaVuSansMono-Oblique.ttf +0 -0
- package/public/wasm/typst/fonts/DejaVuSansMono.ttf +0 -0
- package/public/wasm/typst/fonts/LibertinusSerif-Bold.otf +0 -0
- package/public/wasm/typst/fonts/LibertinusSerif-BoldItalic.otf +0 -0
- package/public/wasm/typst/fonts/LibertinusSerif-Italic.otf +0 -0
- package/public/wasm/typst/fonts/LibertinusSerif-Regular.otf +0 -0
- package/public/wasm/typst/fonts/LibertinusSerif-Semibold.otf +0 -0
- package/public/wasm/typst/fonts/LibertinusSerif-SemiboldItalic.otf +0 -0
- package/public/wasm/typst/fonts/NewCM10-Bold.otf +0 -0
- package/public/wasm/typst/fonts/NewCM10-BoldItalic.otf +0 -0
- package/public/wasm/typst/fonts/NewCM10-Italic.otf +0 -0
- package/public/wasm/typst/fonts/NewCM10-Regular.otf +0 -0
- package/public/wasm/typst/fonts/NewCMMath-Bold.otf +0 -0
- package/public/wasm/typst/fonts/NewCMMath-Book.otf +0 -0
- package/public/wasm/typst/fonts/NewCMMath-Regular.otf +0 -0
- package/public/wasm/typst/fonts/README.md +7 -0
- package/public/wasm/typst/typst_ts_renderer_bg.wasm +0 -0
- package/public/wasm/typst/typst_ts_web_compiler_bg.wasm +0 -0
- package/scripts/patch-docx-grid.cjs +412 -0
- package/src/api/_base.ts +243 -0
- package/src/api/index.ts +17 -0
- package/src/api/project.ts +1345 -0
- package/src/api/user.ts +1174 -0
- package/src/contract.ts +50 -0
- package/src/hooks/index.ts +2 -0
- package/{dist/hooks/index.js → src/hooks/useLongPress.ts} +53 -49
- package/src/hooks/useOnlineStatus.ts +22 -0
- package/src/index.ts +14 -0
- package/src/types/id.ts +2 -0
- package/src/types/index.ts +5 -0
- package/src/types/project/base.ts +63 -0
- package/src/types/project/contract.ts +79 -0
- package/src/types/project/contractStatus.tsx +15 -0
- package/src/types/project/index.ts +5 -0
- package/src/types/project/member.ts +46 -0
- package/src/types/project/progress.ts +43 -0
- package/src/types/projectRole.ts +17 -0
- package/src/types/theme.ts +1 -0
- package/src/types/user/index.ts +1 -0
- package/src/types/user/role.ts +7 -0
- package/src/ui/CommentTimeline/index.tsx +330 -0
- package/src/ui/FileManager/FileContextMenu.tsx +628 -0
- package/src/ui/FileManager/FileGridView.tsx +529 -0
- package/src/ui/FileManager/FileTableView.tsx +405 -0
- package/src/ui/FileManager/FileToolbar.tsx +119 -0
- package/src/ui/FileManager/FileVersionHistoryModal.tsx +175 -0
- package/src/ui/FileManager/MoveCopyToProjectModal.tsx +119 -0
- package/src/ui/FileManager/ShareToWechatModal.tsx +419 -0
- package/src/ui/FileManager/hooks/useFileManagerActions.ts +2532 -0
- package/src/ui/FileManager/hooks/useFileManagerState.ts +419 -0
- package/src/ui/FileManager/index.tsx +580 -0
- package/src/ui/FileManager/projectFileManagerApi.ts +57 -0
- package/src/ui/FileManager/types.ts +184 -0
- package/src/ui/FileManager/utils/fileDeltaWorker.ts +193 -0
- package/src/ui/FileManager/utils/fileTree.ts +178 -0
- package/src/ui/FileManager/utils/fileUpload.ts +714 -0
- package/src/ui/FileManager/utils/fileUtils.ts +65 -0
- package/src/ui/InvoiceRecordsList/index.tsx +813 -0
- package/src/ui/MyFileSelectorModal.tsx +261 -0
- package/src/ui/ProjectSelector/index.tsx +472 -0
- package/src/ui/ProjectSelector/projectCache.ts +40 -0
- package/src/ui/RoleSelector/index.tsx +473 -0
- package/src/ui/RoleSelector/roleCache.ts +40 -0
- package/src/ui/UIAlert.tsx +399 -0
- package/src/ui/UIAlertExample.tsx +129 -0
- package/src/ui/UIConfirm.tsx +335 -0
- package/src/ui/UIConfirmExample.tsx +165 -0
- package/src/ui/UIDatePicker.tsx +146 -0
- package/src/ui/UIDateRangePicker.tsx +38 -0
- package/src/ui/UIFilePreview/ExcelEditor/index.tsx +617 -0
- package/src/ui/UIFilePreview/ExcelEditor/types.ts +24 -0
- package/src/ui/UIFilePreview/ExportDropdown.css +85 -0
- package/src/ui/UIFilePreview/ExportDropdown.tsx +109 -0
- package/src/ui/UIFilePreview/IndexedDBUtils.ts +564 -0
- package/src/ui/UIFilePreview/MarkdownEditor/exportMarkdownPdf.ts +576 -0
- package/src/ui/UIFilePreview/MarkdownEditor/index.tsx +677 -0
- package/src/ui/UIFilePreview/MarkdownEditor/markdownEditor.css +1005 -0
- package/src/ui/UIFilePreview/MarkdownEditor/types.ts +15 -0
- package/src/ui/UIFilePreview/PPTXEditor/index.tsx +602 -0
- package/src/ui/UIFilePreview/RichDocEditor/exportRDocMarkdown.ts +77 -0
- package/src/ui/UIFilePreview/RichDocEditor/exportRDocPdf.ts +374 -0
- package/src/ui/UIFilePreview/RichDocEditor/index.tsx +730 -0
- package/src/ui/UIFilePreview/RichDocEditor/richdoc-editor.css +59 -0
- package/src/ui/UIFilePreview/UIFilePreview.tsx +611 -0
- package/src/ui/UIFilePreview/WordEditor/index.tsx +3933 -0
- package/src/ui/UIFilePreview/WordEditor/style.ts +1213 -0
- package/src/ui/UIFilePreview/WordEditor/word-editor.css +999 -0
- package/src/ui/UIFilePreview/filePreview.css +193 -0
- package/src/ui/UIFilePreview/filetypes/archive.tsx +66 -0
- package/src/ui/UIFilePreview/filetypes/cad.tsx +287 -0
- package/src/ui/UIFilePreview/filetypes/excel.tsx +355 -0
- package/src/ui/UIFilePreview/filetypes/image.tsx +20 -0
- package/src/ui/UIFilePreview/filetypes/imageLoader.ts +63 -0
- package/src/ui/UIFilePreview/filetypes/pdf.tsx +156 -0
- package/src/ui/UIFilePreview/filetypes/ppt.tsx +535 -0
- package/src/ui/UIFilePreview/filetypes/pptExportPdf.ts +523 -0
- package/src/ui/UIFilePreview/filetypes/video.tsx +482 -0
- package/src/ui/UIFilePreview/filetypes/word.tsx +245 -0
- package/src/ui/UIFilePreview/filetypes/wordExportMarkdown.ts +86 -0
- package/src/ui/UIFilePreview/filetypes/wordExportPdf.tsx +427 -0
- package/src/ui/UIFilePreview/index.ts +35 -0
- package/src/ui/UIFilePreview/pdfPagination.ts +143 -0
- package/src/ui/UIFilePreview/previewUtils.tsx +531 -0
- package/src/ui/UIFileUpload.tsx +382 -0
- package/src/ui/UIImageUpload.tsx +195 -0
- package/src/ui/UILoading.tsx +255 -0
- package/src/ui/UILoadingExample.tsx +139 -0
- package/src/ui/UISelect.tsx +105 -0
- package/src/ui/UIToast.tsx +381 -0
- package/src/ui/UIToastExample.tsx +69 -0
- package/src/ui/UIToastSingle.tsx +279 -0
- package/src/ui/UIViewToggle.tsx +64 -0
- package/src/ui/UserSelector/index.tsx +583 -0
- package/src/ui/index.ts +84 -0
- package/src/ui/upload/image.ts +135 -0
- package/src/ui/upload/index.ts +73 -0
- package/src/ui/upload/submitFile.ts +106 -0
- package/src/ui/upload/submitForm.ts +124 -0
- package/src/ui/upload/upload.err.ts +15 -0
- package/src/ui/upload/upload.ts +250 -0
- package/src/utils/CacheDepartmentInfo.ts +372 -0
- package/src/utils/CacheFileContent.ts +315 -0
- package/src/utils/CachePermissionInfo.ts +235 -0
- package/src/utils/CacheRoleInfo.ts +303 -0
- package/src/utils/CacheUserInfo.ts +818 -0
- package/src/utils/CacheViewToggleInfo.ts +182 -0
- package/src/utils/MessageContentUtils.ts +268 -0
- package/src/utils/clipboard.ts +70 -0
- package/src/utils/companyEmail.ts +19 -0
- package/src/utils/forbidDebugger.ts +59 -0
- package/src/utils/formatters.ts +94 -0
- package/src/utils/htmlUtils.tsx +59 -0
- package/src/utils/index.ts +18 -0
- package/src/utils/keyboard.ts +114 -0
- package/src/utils/mammothApi.ts +16 -0
- package/src/utils/polyfills.ts +928 -0
- package/src/utils/regex.ts +14 -0
- package/src/utils/storage.ts +95 -0
- package/src/utils/workerBootstrap.ts +87 -0
- package/dist/api/index.js +0 -1553
- package/dist/contract.js +0 -5
- package/dist/index.js +0 -4492
- package/dist/types/index.js +0 -49
- package/dist/ui/index.css +0 -1876
- package/dist/ui/index.js +0 -22245
- package/dist/utils/index.js +0 -4296
- /package/{dist → src}/styles/ant-modal.css +0 -0
- /package/{dist → src}/styles/ant-spin.css +0 -0
- /package/{dist → src}/styles/ant-table.css +0 -0
- /package/{dist → src}/styles/index.css +0 -0
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
import * as febs from 'febs-browser';
|
|
2
|
+
import pako from 'pako';
|
|
3
|
+
import { FileUploadHandler } from '../../upload/index';
|
|
4
|
+
import { ResponseCode } from '../../../api/_base';
|
|
5
|
+
import { file as fileAPI } from '../../../api/project';
|
|
6
|
+
import type { FileManagerApi } from '../types';
|
|
7
|
+
|
|
8
|
+
import { API } from '../../../api/index';
|
|
9
|
+
|
|
10
|
+
// 导入 Worker
|
|
11
|
+
import FileDeltaWorker from './fileDeltaWorker?worker';
|
|
12
|
+
import uiToast from '../../UIToast';
|
|
13
|
+
|
|
14
|
+
// ==================== 配置常量 ====================
|
|
15
|
+
/**
|
|
16
|
+
* 文件大小阈值配置
|
|
17
|
+
* - MAX_FILE_SIZE: 最大文件大小(100MB)
|
|
18
|
+
* - MAX_DELTA_FILE_SIZE: 超过此大小(20MB)的文件将跳过差分计算,直接完整上传
|
|
19
|
+
* - MAX_DIFF_SIZE: 差分压缩后超过此大小(2MB),将回退到完整上传
|
|
20
|
+
*/
|
|
21
|
+
const MAX_FILE_SIZE = 100 * 1024 * 1024; // 100MB
|
|
22
|
+
const MAX_DELTA_FILE_SIZE = 20 * 1024 * 1024; // 20MB
|
|
23
|
+
const MAX_DIFF_SIZE = 2 * 1024 * 1024; // 2MB
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 超时配置(毫秒)
|
|
27
|
+
* - DELTA_COMPUTE_TIMEOUT: 差分计算超时时间(30秒)
|
|
28
|
+
* - 如果差分计算超时,自动回退到完整上传
|
|
29
|
+
*/
|
|
30
|
+
const DELTA_COMPUTE_TIMEOUT = 30000; // 30秒
|
|
31
|
+
|
|
32
|
+
// ==================== Worker 管理模块 ====================
|
|
33
|
+
/**
|
|
34
|
+
* Worker 任务信息
|
|
35
|
+
*/
|
|
36
|
+
interface WorkerTask {
|
|
37
|
+
id: string;
|
|
38
|
+
worker: Worker;
|
|
39
|
+
resolve: (result: { compressedPatch: ArrayBuffer; patchSize: number }) => void;
|
|
40
|
+
reject: (error: Error) => void;
|
|
41
|
+
onProgress?: (percent: number, message: string) => void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Worker 管理器
|
|
46
|
+
*/
|
|
47
|
+
class DeltaWorkerManager {
|
|
48
|
+
private workers: Map<string, WorkerTask> = new Map();
|
|
49
|
+
private worker: Worker | null = null;
|
|
50
|
+
private isReady: boolean = false;
|
|
51
|
+
private readyPromise: Promise<void>;
|
|
52
|
+
private resolveReady!: () => void;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
this.readyPromise = new Promise((resolve) => {
|
|
56
|
+
this.resolveReady = resolve;
|
|
57
|
+
});
|
|
58
|
+
this.initWorker();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 初始化 Worker
|
|
63
|
+
*/
|
|
64
|
+
private initWorker() {
|
|
65
|
+
try {
|
|
66
|
+
this.worker = new FileDeltaWorker();
|
|
67
|
+
|
|
68
|
+
this.worker.onmessage = (e: MessageEvent) => {
|
|
69
|
+
const { type, id, data, error } = e.data;
|
|
70
|
+
|
|
71
|
+
switch (type) {
|
|
72
|
+
case 'ready': {
|
|
73
|
+
this.isReady = true;
|
|
74
|
+
this.resolveReady();
|
|
75
|
+
console.log('[Worker] 差分计算模块已就绪');
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
case 'progress': {
|
|
80
|
+
const task = this.workers.get(id);
|
|
81
|
+
if (task && task.onProgress) {
|
|
82
|
+
task.onProgress(data.percent, data.message);
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
case 'success': {
|
|
88
|
+
const successTask = this.workers.get(id);
|
|
89
|
+
if (successTask) {
|
|
90
|
+
successTask.resolve({
|
|
91
|
+
compressedPatch: data.compressedPatch,
|
|
92
|
+
patchSize: data.patchSize
|
|
93
|
+
});
|
|
94
|
+
this.workers.delete(id);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case 'error': {
|
|
100
|
+
const errorTask = this.workers.get(id);
|
|
101
|
+
if (errorTask) {
|
|
102
|
+
errorTask.reject(new Error(error || 'Worker 计算失败'));
|
|
103
|
+
this.workers.delete(id);
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case 'aborted':
|
|
109
|
+
this.workers.delete(id);
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
this.worker.onerror = (error) => {
|
|
115
|
+
console.error('[Worker] Worker 错误:', error);
|
|
116
|
+
// 遇到错误时重新初始化
|
|
117
|
+
this.terminate();
|
|
118
|
+
setTimeout(() => this.initWorker(), 1000);
|
|
119
|
+
};
|
|
120
|
+
} catch (err) {
|
|
121
|
+
console.error('[Worker] 初始化 Worker 失败:', err);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 等待 Worker 就绪
|
|
127
|
+
*/
|
|
128
|
+
async waitForReady(): Promise<void> {
|
|
129
|
+
if (this.isReady) return;
|
|
130
|
+
return this.readyPromise;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 执行差分计算
|
|
135
|
+
* @param oldBuffer 旧文件数据
|
|
136
|
+
* @param newBuffer 新文件数据
|
|
137
|
+
* @param onProgress 进度回调
|
|
138
|
+
* @returns 压缩后的差分数据
|
|
139
|
+
*/
|
|
140
|
+
async computeDelta(
|
|
141
|
+
oldBuffer: ArrayBuffer,
|
|
142
|
+
newBuffer: ArrayBuffer,
|
|
143
|
+
onProgress?: (percent: number, message: string) => void
|
|
144
|
+
): Promise<{ compressedPatch: ArrayBuffer; patchSize: number }> {
|
|
145
|
+
await this.waitForReady();
|
|
146
|
+
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
if (!this.worker) {
|
|
149
|
+
reject(new Error('Worker 未初始化'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const taskId = `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
154
|
+
|
|
155
|
+
const task: WorkerTask = {
|
|
156
|
+
id: taskId,
|
|
157
|
+
worker: this.worker,
|
|
158
|
+
resolve,
|
|
159
|
+
reject,
|
|
160
|
+
onProgress
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
this.workers.set(taskId, task);
|
|
164
|
+
|
|
165
|
+
this.worker.postMessage({
|
|
166
|
+
type: 'computeDelta',
|
|
167
|
+
id: taskId,
|
|
168
|
+
data: {
|
|
169
|
+
oldBuffer,
|
|
170
|
+
newBuffer,
|
|
171
|
+
taskId
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 终止所有任务
|
|
179
|
+
*/
|
|
180
|
+
terminate() {
|
|
181
|
+
this.workers.forEach((task, id) => {
|
|
182
|
+
task.worker.postMessage({ type: 'abort', data: { taskId: id } });
|
|
183
|
+
});
|
|
184
|
+
this.workers.clear();
|
|
185
|
+
|
|
186
|
+
if (this.worker) {
|
|
187
|
+
this.worker.terminate();
|
|
188
|
+
this.worker = null;
|
|
189
|
+
}
|
|
190
|
+
this.isReady = false;
|
|
191
|
+
this.readyPromise = new Promise((resolve) => {
|
|
192
|
+
this.resolveReady = resolve;
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 创建全局 Worker 管理器实例
|
|
198
|
+
const workerManager = new DeltaWorkerManager();
|
|
199
|
+
|
|
200
|
+
// ==================== 辅助函数 ====================
|
|
201
|
+
/**
|
|
202
|
+
* 带超时的 Promise
|
|
203
|
+
* @param promise 要执行的 Promise
|
|
204
|
+
* @param timeoutMs 超时时间(毫秒)
|
|
205
|
+
* @param errorMessage 超时时的错误信息
|
|
206
|
+
*/
|
|
207
|
+
const withTimeout = <T>(promise: Promise<T>, timeoutMs: number, errorMessage: string): Promise<T> => {
|
|
208
|
+
return Promise.race([
|
|
209
|
+
promise,
|
|
210
|
+
new Promise<T>((_, reject) =>
|
|
211
|
+
setTimeout(() => reject(new Error(errorMessage)), timeoutMs)
|
|
212
|
+
)
|
|
213
|
+
]);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// 预加载 WASM 模块
|
|
217
|
+
let wasmInstance: WebAssembly.Instance | null = null;
|
|
218
|
+
|
|
219
|
+
interface BsdiffExports {
|
|
220
|
+
memory: WebAssembly.Memory;
|
|
221
|
+
alloc: (len: number) => number;
|
|
222
|
+
dealloc: (ptr: number, len: number) => void;
|
|
223
|
+
create_patch: (basePtr: number, baseLen: number, nextPtr: number, nextLen: number) => number;
|
|
224
|
+
apply_patch: (basePtr: number, baseLen: number, patchPtr: number, patchLen: number) => number;
|
|
225
|
+
output_ptr: () => number;
|
|
226
|
+
output_len: () => number;
|
|
227
|
+
free_output: () => void;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const loadWasm = async (): Promise<BsdiffExports> => {
|
|
231
|
+
if (wasmInstance) return wasmInstance.exports as unknown as BsdiffExports;
|
|
232
|
+
|
|
233
|
+
const response = await fetch('/wasm/hdiff.wasm');
|
|
234
|
+
const wasmBuffer = await response.arrayBuffer();
|
|
235
|
+
const wasmModule = new WebAssembly.Module(wasmBuffer);
|
|
236
|
+
const instance = await WebAssembly.instantiate(wasmModule);
|
|
237
|
+
wasmInstance = instance;
|
|
238
|
+
return instance.exports as unknown as BsdiffExports;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// 创建补丁
|
|
242
|
+
const createPatch = async (base: Uint8Array, next: Uint8Array): Promise<Uint8Array> => {
|
|
243
|
+
const wasm = await loadWasm();
|
|
244
|
+
|
|
245
|
+
const basePtr = wasm.alloc(base.byteLength);
|
|
246
|
+
const nextPtr = wasm.alloc(next.byteLength);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
new Uint8Array(wasm.memory.buffer, basePtr, base.byteLength).set(base);
|
|
250
|
+
new Uint8Array(wasm.memory.buffer, nextPtr, next.byteLength).set(next);
|
|
251
|
+
|
|
252
|
+
const status = wasm.create_patch(basePtr, base.byteLength, nextPtr, next.byteLength);
|
|
253
|
+
|
|
254
|
+
if (status !== 0) {
|
|
255
|
+
throw new Error(`BSDiff failed with status: ${status}`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const outPtr = wasm.output_ptr();
|
|
259
|
+
const outLen = wasm.output_len();
|
|
260
|
+
|
|
261
|
+
if (outLen === 0) {
|
|
262
|
+
wasm.free_output();
|
|
263
|
+
return new Uint8Array();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const output = new Uint8Array(wasm.memory.buffer, outPtr, outLen);
|
|
267
|
+
const copied = new Uint8Array(output);
|
|
268
|
+
wasm.free_output();
|
|
269
|
+
return copied;
|
|
270
|
+
} finally {
|
|
271
|
+
if (base.byteLength > 0) wasm.dealloc(basePtr, base.byteLength);
|
|
272
|
+
if (next.byteLength > 0) wasm.dealloc(nextPtr, next.byteLength);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// 封装的 delta 计算函数
|
|
277
|
+
const computeDelta = async (oldBuffer: ArrayBuffer, newBuffer: ArrayBuffer): Promise<ArrayBuffer> => {
|
|
278
|
+
const oldArray = new Uint8Array(oldBuffer);
|
|
279
|
+
const newArray = new Uint8Array(newBuffer);
|
|
280
|
+
const patch = await createPatch(oldArray, newArray);
|
|
281
|
+
return patch.buffer as ArrayBuffer;
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const calculateCRC32 = (file: File): Promise<number> => {
|
|
285
|
+
return new Promise((resolve, reject) => {
|
|
286
|
+
febs.crypt.crc32_file(file as unknown as File, (crc: number) => {
|
|
287
|
+
if (crc === 0) {
|
|
288
|
+
reject(new Error('CRC32 计算失败'));
|
|
289
|
+
} else {
|
|
290
|
+
resolve(crc);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const getMimeType = (fileName: string): string => {
|
|
297
|
+
const ext = fileName.toLowerCase().split('.').pop() || '';
|
|
298
|
+
switch (ext) {
|
|
299
|
+
case 'pptx':
|
|
300
|
+
return 'application/vnd.openxmlformats-officedocument.presentationml.presentation';
|
|
301
|
+
case 'xlsx':
|
|
302
|
+
return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
303
|
+
case 'md':
|
|
304
|
+
return 'text/markdown';
|
|
305
|
+
default:
|
|
306
|
+
return 'application/octet-stream';
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
export const sanitizeFileName = (fileName: string): string => {
|
|
311
|
+
let result = fileName.trim();
|
|
312
|
+
result = febs.string.replace(result, '/', '');
|
|
313
|
+
result = febs.string.replace(result, '\\', '');
|
|
314
|
+
return result;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
// 获取文件扩展名(包含点号),如 ".txt";无扩展名或以点开头的隐藏文件返回空字符串
|
|
318
|
+
export const getFileExtension = (fileName: string): string => {
|
|
319
|
+
const dot = fileName.lastIndexOf('.');
|
|
320
|
+
if (dot <= 0) return '';
|
|
321
|
+
return fileName.slice(dot);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// 去掉文件扩展名,保留名称部分
|
|
325
|
+
export const stripFileExtension = (fileName: string): string => {
|
|
326
|
+
const ext = getFileExtension(fileName);
|
|
327
|
+
return ext ? fileName.slice(0, fileName.length - ext.length) : fileName;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
export interface UploadFileResult {
|
|
331
|
+
crc32: number;
|
|
332
|
+
serverFileName: string;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export const uploadFileBlob = async (blob: Blob, fileName: string,
|
|
336
|
+
progress: (percent: number) => void,
|
|
337
|
+
finish?: (success: boolean) => void
|
|
338
|
+
): Promise<UploadFileResult> => {
|
|
339
|
+
const mimeType = getMimeType(fileName);
|
|
340
|
+
const file = new File([blob], fileName, { type: mimeType });
|
|
341
|
+
const crc32 = await calculateCRC32(file);
|
|
342
|
+
|
|
343
|
+
if (blob.size > MAX_FILE_SIZE) {
|
|
344
|
+
return new Promise((resolve, reject) => reject('文件大小超过100MB,无法上传'));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return new Promise<UploadFileResult>((resolve, reject) => {
|
|
348
|
+
FileUploadHandler.uploadSingle(
|
|
349
|
+
file,
|
|
350
|
+
crc32,
|
|
351
|
+
progress,
|
|
352
|
+
(err) => {
|
|
353
|
+
reject(err);
|
|
354
|
+
},
|
|
355
|
+
(data) => {
|
|
356
|
+
if (data.code === ResponseCode.SUCCESS) {
|
|
357
|
+
finish?.(true);
|
|
358
|
+
// 服务器可能因重名自动重命名文件(如 "新建文档.md" → "新建文档 (11).md"),
|
|
359
|
+
// 返回服务器上真实的文件名,用于后续 addFile/updateFile 的 fileResourcePath。
|
|
360
|
+
const serverFileName = typeof data.data === 'string' ? data.data : fileName;
|
|
361
|
+
resolve({ crc32, serverFileName });
|
|
362
|
+
} else {
|
|
363
|
+
finish?.(false);
|
|
364
|
+
uiToast.error(data.message || '文件上传失败');
|
|
365
|
+
reject(new Error(data.message || '文件上传失败'));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
export const uploadMarkdownContent = async (content: string, fileName: string,
|
|
373
|
+
progress: (percent: number) => void,
|
|
374
|
+
finish?: (success: boolean) => void
|
|
375
|
+
): Promise<UploadFileResult> => {
|
|
376
|
+
const blob = new Blob([content], { type: 'text/markdown' });
|
|
377
|
+
return uploadFileBlob(blob, fileName, progress, finish);
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 增量上传文件(通过bsdiff计算差异并压缩后上传)
|
|
382
|
+
* 优化点:
|
|
383
|
+
* 1. 文件大小预检查:超过10MB的文件直接完整上传
|
|
384
|
+
* 2. Worker 线程:差分计算在 Worker 线程执行,防止主线程卡顿
|
|
385
|
+
* 3. 实时进度显示:显示差分计算的各个阶段
|
|
386
|
+
* 4. 超时回退机制:差分计算超过30秒自动回退到完整上传
|
|
387
|
+
*
|
|
388
|
+
* @param fileId 文件ID
|
|
389
|
+
* @param fileName 文件名
|
|
390
|
+
* @param oldBlob 旧文件Blob
|
|
391
|
+
* @param newBlob 新文件Blob
|
|
392
|
+
* @param progress 进度回调 (0-1)
|
|
393
|
+
* @param finish 完成回调
|
|
394
|
+
*/
|
|
395
|
+
export const uploadFileDelta = async (
|
|
396
|
+
fileId: string,
|
|
397
|
+
fileName: string,
|
|
398
|
+
oldBlob: Blob,
|
|
399
|
+
newBlob: Blob,
|
|
400
|
+
progress: (percent: number) => void,
|
|
401
|
+
finish: (success: boolean) => void,
|
|
402
|
+
forceFullUpload: boolean = false,
|
|
403
|
+
/**
|
|
404
|
+
* 差分上传失败时的回调(如 CRC32 不匹配)。
|
|
405
|
+
* 返回 true 表示确认全量上传,返回 false 表示取消。
|
|
406
|
+
* 未提供时自动回退全量上传(默认行为)。
|
|
407
|
+
*/
|
|
408
|
+
onDeltaFail?: (response: { code: number; message?: string }) => Promise<boolean>,
|
|
409
|
+
/**
|
|
410
|
+
* 文件更新接口注入(CRM 等自定义数据源场景)。
|
|
411
|
+
* 传入后若缺少 updateFileDelta,则视为不支持差分上传,强制走全量上传。
|
|
412
|
+
*/
|
|
413
|
+
apis?: Pick<FileManagerApi, 'updateFile' | 'updateFileDelta'>,
|
|
414
|
+
): Promise<UploadFileResult> => {
|
|
415
|
+
try {
|
|
416
|
+
// 接口注入:缺省使用项目文件 API;自定义 apis 未提供 updateFileDelta 时不支持差分
|
|
417
|
+
const updateFileImpl = apis?.updateFile || ((data: Parameters<typeof fileAPI.updateFile>[0]) => API.project.file.updateFile(data));
|
|
418
|
+
const updateDeltaImpl = apis ? apis.updateFileDelta : ((data: Parameters<typeof fileAPI.updateFileDelta>[0]) => fileAPI.updateFileDelta(data));
|
|
419
|
+
const needFullUpload = forceFullUpload || !updateDeltaImpl;
|
|
420
|
+
|
|
421
|
+
const oldSize = oldBlob.size;
|
|
422
|
+
const newSize = newBlob.size;
|
|
423
|
+
|
|
424
|
+
const mimeType = getMimeType(fileName);
|
|
425
|
+
const newFile = new File([newBlob], fileName, { type: mimeType });
|
|
426
|
+
const crc32 = await calculateCRC32(newFile);
|
|
427
|
+
|
|
428
|
+
// ==================== 强制全量上传 ====================
|
|
429
|
+
// 用于 Word 等需要完整替换 styles.xml 的场景,跳过差分计算
|
|
430
|
+
if (needFullUpload) {
|
|
431
|
+
console.log('[上传优化] 强制全量上传');
|
|
432
|
+
progress?.(0.05);
|
|
433
|
+
|
|
434
|
+
const { serverFileName } = await uploadFileBlob(newBlob, fileName, percent => progress?.(0.05 + percent * 0.95));
|
|
435
|
+
|
|
436
|
+
const res = await updateFileImpl({
|
|
437
|
+
fileId,
|
|
438
|
+
fileName,
|
|
439
|
+
fileSize: newSize,
|
|
440
|
+
crc32,
|
|
441
|
+
fileResourcePath: serverFileName
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
if (res.code !== ResponseCode.SUCCESS) {
|
|
445
|
+
throw new Error(res.message || '更新文件元数据失败');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
progress?.(1);
|
|
449
|
+
finish?.(true);
|
|
450
|
+
return { crc32, serverFileName };
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ==================== 优化点1: 文件大小预检查 ====================
|
|
454
|
+
// 超过阈值的大文件直接使用完整上传,跳过耗时的差分计算
|
|
455
|
+
if (oldSize > MAX_DELTA_FILE_SIZE || newSize > MAX_DELTA_FILE_SIZE) {
|
|
456
|
+
console.log(`[上传优化] 文件过大(旧: ${(oldSize / 1024 / 1024).toFixed(2)}MB, 新: ${(newSize / 1024 / 1024).toFixed(2)}MB),跳过差分计算`);
|
|
457
|
+
progress?.(0.05);
|
|
458
|
+
|
|
459
|
+
const { serverFileName } = await uploadFileBlob(newBlob, fileName, percent => progress?.(0.05 + percent * 0.95));
|
|
460
|
+
|
|
461
|
+
const res = await updateFileImpl({
|
|
462
|
+
fileId,
|
|
463
|
+
fileName,
|
|
464
|
+
fileSize: newSize,
|
|
465
|
+
crc32,
|
|
466
|
+
fileResourcePath: serverFileName
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
if (res.code !== ResponseCode.SUCCESS) {
|
|
470
|
+
throw new Error(res.message || '更新文件元数据失败');
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
progress?.(1);
|
|
474
|
+
finish?.(true);
|
|
475
|
+
return { crc32, serverFileName };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ==================== 正常差分上传流程 ====================
|
|
479
|
+
progress?.(0.02);
|
|
480
|
+
|
|
481
|
+
// 将 Blob 转换为 ArrayBuffer
|
|
482
|
+
const oldBuffer = await oldBlob.arrayBuffer();
|
|
483
|
+
const newBuffer = await newBlob.arrayBuffer();
|
|
484
|
+
|
|
485
|
+
progress?.(0.05);
|
|
486
|
+
|
|
487
|
+
// ==================== 优化点2 & 3: 使用 Worker 进行差分计算,并实时显示进度 ====================
|
|
488
|
+
let compressedPatch: Uint8Array;
|
|
489
|
+
try {
|
|
490
|
+
// 使用 withTimeout 包装 Worker 计算,设置30秒超时
|
|
491
|
+
const result = await withTimeout(
|
|
492
|
+
workerManager.computeDelta(
|
|
493
|
+
oldBuffer,
|
|
494
|
+
newBuffer,
|
|
495
|
+
(workerPercent: number, message: string) => {
|
|
496
|
+
// 将 Worker 的 0-1 进度映射到 0.05-0.5 的主进度
|
|
497
|
+
const mainPercent = 0.05 + workerPercent * 0.45;
|
|
498
|
+
progress?.(mainPercent);
|
|
499
|
+
console.log(`[差分计算] ${message} (${(workerPercent * 100).toFixed(0)}%)`);
|
|
500
|
+
}
|
|
501
|
+
),
|
|
502
|
+
DELTA_COMPUTE_TIMEOUT,
|
|
503
|
+
`差分计算超时(${DELTA_COMPUTE_TIMEOUT / 1000}秒),自动回退到完整上传`
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
compressedPatch = new Uint8Array(result.compressedPatch);
|
|
507
|
+
|
|
508
|
+
console.log(`[上传优化] 差分计算完成,压缩后大小: ${(result.patchSize / 1024).toFixed(2)}KB`);
|
|
509
|
+
|
|
510
|
+
} catch (deltaError) {
|
|
511
|
+
console.warn('[上传优化] 差分计算失败,自动回退到完整上传。原因:', deltaError instanceof Error ? deltaError.message : deltaError);
|
|
512
|
+
progress?.(0.5);
|
|
513
|
+
|
|
514
|
+
const { serverFileName } = await uploadFileBlob(newBlob, fileName, percent => progress?.(0.5 + percent * 0.5));
|
|
515
|
+
|
|
516
|
+
const res = await updateFileImpl({
|
|
517
|
+
fileId,
|
|
518
|
+
fileName,
|
|
519
|
+
fileSize: newSize,
|
|
520
|
+
crc32,
|
|
521
|
+
fileResourcePath: serverFileName
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (res.code !== ResponseCode.SUCCESS) {
|
|
525
|
+
throw new Error(res.message || '更新文件元数据失败');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
progress?.(1);
|
|
529
|
+
finish?.(true);
|
|
530
|
+
return { crc32, serverFileName };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
progress?.(0.5);
|
|
534
|
+
|
|
535
|
+
// ==================== 优化点4: 差异大小检查 ====================
|
|
536
|
+
if (compressedPatch.length === 0) {
|
|
537
|
+
progress?.(1);
|
|
538
|
+
console.log('[上传优化] 文件未变化,无需上传');
|
|
539
|
+
finish?.(true);
|
|
540
|
+
return { crc32, serverFileName: fileName };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 差异过大时回退到完整上传
|
|
544
|
+
if (compressedPatch.length > MAX_DIFF_SIZE) {
|
|
545
|
+
console.log(`[上传优化] 差异过大(${(compressedPatch.length / 1024).toFixed(2)}KB > 300KB),使用完整上传`);
|
|
546
|
+
progress?.(0.55);
|
|
547
|
+
|
|
548
|
+
const { serverFileName } = await uploadFileBlob(newBlob, fileName, percent => progress?.(0.55 + percent * 0.45));
|
|
549
|
+
|
|
550
|
+
const res = await updateFileImpl({
|
|
551
|
+
fileId,
|
|
552
|
+
fileName,
|
|
553
|
+
fileSize: newSize,
|
|
554
|
+
crc32,
|
|
555
|
+
fileResourcePath: serverFileName
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
if (res.code !== ResponseCode.SUCCESS) {
|
|
559
|
+
throw new Error(res.message || '更新文件元数据失败');
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
progress?.(1);
|
|
563
|
+
finish?.(true);
|
|
564
|
+
return { crc32, serverFileName };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
progress?.(0.6);
|
|
568
|
+
|
|
569
|
+
// 转换为 base64 并上传差异
|
|
570
|
+
// 使用分块处理避免展开运算符导致的栈溢出(当 compressedPatch 较大时)
|
|
571
|
+
const CHUNK_SIZE = 8192;
|
|
572
|
+
let binaryString = '';
|
|
573
|
+
for (let i = 0; i < compressedPatch.length; i += CHUNK_SIZE) {
|
|
574
|
+
const chunk = compressedPatch.subarray(i, Math.min(i + CHUNK_SIZE, compressedPatch.length));
|
|
575
|
+
binaryString += String.fromCharCode(...chunk);
|
|
576
|
+
}
|
|
577
|
+
const deltaData = btoa(binaryString);
|
|
578
|
+
|
|
579
|
+
progress?.(0.7);
|
|
580
|
+
|
|
581
|
+
const response = await updateDeltaImpl!({
|
|
582
|
+
fileId,
|
|
583
|
+
deltaData,
|
|
584
|
+
deltaSize: compressedPatch.length,
|
|
585
|
+
newFileSize: newSize,
|
|
586
|
+
crc32,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
console.log('[上传诊断] updateFileDelta 响应:', {
|
|
590
|
+
code: response.code,
|
|
591
|
+
message: response.message,
|
|
592
|
+
data: response.data,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
progress?.(1);
|
|
596
|
+
|
|
597
|
+
if (response.code !== ResponseCode.SUCCESS) {
|
|
598
|
+
// 差分上传失败(基准文件不存在、CRC32 不匹配等)
|
|
599
|
+
console.warn(`[上传优化] 差分上传失败(code=${response.code}),回退全量上传:`, response.message);
|
|
600
|
+
|
|
601
|
+
// 如果提供了 onDeltaFail 回调,由调用方决定是否继续全量上传
|
|
602
|
+
if (onDeltaFail) {
|
|
603
|
+
const confirmed = await onDeltaFail({ code: response.code, message: response.message });
|
|
604
|
+
if (!confirmed) {
|
|
605
|
+
progress?.(1);
|
|
606
|
+
finish?.(false);
|
|
607
|
+
throw new Error('用户取消上传');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const { serverFileName } = await uploadFileBlob(newBlob, fileName, percent => progress?.(0.05 + percent * 0.95));
|
|
612
|
+
|
|
613
|
+
const res = await updateFileImpl({
|
|
614
|
+
fileId,
|
|
615
|
+
fileName,
|
|
616
|
+
fileSize: newSize,
|
|
617
|
+
crc32,
|
|
618
|
+
fileResourcePath: serverFileName
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
if (res.code !== ResponseCode.SUCCESS) {
|
|
622
|
+
throw new Error(res.message || '更新文件元数据失败');
|
|
623
|
+
}
|
|
624
|
+
progress?.(1);
|
|
625
|
+
finish?.(true);
|
|
626
|
+
return { crc32, serverFileName };
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// 差分上传成功 — 使用服务器返回的 serverFileName(如果有),否则返回原始文件名
|
|
630
|
+
const serverFileName = response.data || fileName;
|
|
631
|
+
console.log('[上传优化] 差分上传成功');
|
|
632
|
+
progress?.(1);
|
|
633
|
+
finish?.(true);
|
|
634
|
+
return { crc32, serverFileName };
|
|
635
|
+
} catch (error) {
|
|
636
|
+
finish?.(false);
|
|
637
|
+
console.error('增量上传失败:', error);
|
|
638
|
+
throw error;
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* 增量上传 Markdown 内容
|
|
644
|
+
* @param fileId 文件ID
|
|
645
|
+
* @param oldContent 旧内容
|
|
646
|
+
* @param newContent 新内容
|
|
647
|
+
* @param progress 进度回调
|
|
648
|
+
* @param finish 完成回调
|
|
649
|
+
* @param forceFullUpload 是否强制全量上传(用于冲突覆盖场景)
|
|
650
|
+
*/
|
|
651
|
+
export const uploadMarkdownDelta = async (
|
|
652
|
+
fileId: string,
|
|
653
|
+
fileName: string,
|
|
654
|
+
oldContent: string,
|
|
655
|
+
newContent: string,
|
|
656
|
+
progress: (percent: number) => void,
|
|
657
|
+
finish: (success: boolean) => void,
|
|
658
|
+
forceFullUpload: boolean = false,
|
|
659
|
+
onDeltaFail?: (response: { code: number; message?: string }) => Promise<boolean>,
|
|
660
|
+
apis?: Pick<FileManagerApi, 'updateFile' | 'updateFileDelta'>,
|
|
661
|
+
): Promise<UploadFileResult> => {
|
|
662
|
+
const oldBlob = new Blob([oldContent], { type: 'text/markdown' });
|
|
663
|
+
const newBlob = new Blob([newContent], { type: 'text/markdown' });
|
|
664
|
+
return uploadFileDelta(fileId, fileName, oldBlob, newBlob, progress, finish, forceFullUpload, onDeltaFail, apis);
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
export interface DroppedFile {
|
|
668
|
+
file: File;
|
|
669
|
+
relativePath: string;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export const extractFilesFromDrop = async (items: DataTransferItemList): Promise<DroppedFile[]> => {
|
|
673
|
+
const files: DroppedFile[] = [];
|
|
674
|
+
|
|
675
|
+
const processEntry = (entry: FileSystemEntry, parentPath: string = ''): Promise<void> => {
|
|
676
|
+
return new Promise((resolve) => {
|
|
677
|
+
if (entry.isFile) {
|
|
678
|
+
(entry as FileSystemFileEntry).file((file) => {
|
|
679
|
+
const relativePath = parentPath ? `${parentPath}/${file.name}` : file.name;
|
|
680
|
+
files.push({ file, relativePath });
|
|
681
|
+
resolve();
|
|
682
|
+
}, () => resolve());
|
|
683
|
+
} else if (entry.isDirectory) {
|
|
684
|
+
const directoryReader = (entry as FileSystemDirectoryEntry).createReader();
|
|
685
|
+
const dirPath = parentPath ? `${parentPath}/${entry.name}` : entry.name;
|
|
686
|
+
|
|
687
|
+
const readEntries = () => {
|
|
688
|
+
directoryReader.readEntries((entries) => {
|
|
689
|
+
if (entries.length === 0) {
|
|
690
|
+
resolve();
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
Promise.all(entries.map((e) => processEntry(e, dirPath))).then(() => resolve());
|
|
694
|
+
}, () => resolve());
|
|
695
|
+
};
|
|
696
|
+
readEntries();
|
|
697
|
+
} else {
|
|
698
|
+
resolve();
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const promises: Promise<void>[] = [];
|
|
704
|
+
for (let i = 0; i < items.length; i++) {
|
|
705
|
+
const item = items[i];
|
|
706
|
+
const entry = item.webkitGetAsEntry?.();
|
|
707
|
+
if (entry) {
|
|
708
|
+
promises.push(processEntry(entry));
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
await Promise.all(promises);
|
|
713
|
+
return files;
|
|
714
|
+
};
|