@hienlh/ppm 0.18.4 → 0.18.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/.release-run.sh +5 -0
- package/CHANGELOG.md +11 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +4 -4
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/dist/web/assets/{ai-resource-editor-Bb-NNlp1.js → ai-resource-editor-C5GpzwXP.js} +1 -1
- package/dist/web/assets/{angular-html-jfMqUuRd.js → angular-html-B7ZS6cwZ.js} +1 -1
- package/dist/web/assets/{angular-ts-wytn9PMm.js → angular-ts-CiibjfLJ.js} +1 -1
- package/dist/web/assets/{apl-i-ahKx6O.js → apl-DsChgbBI.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-9f0Fi2Cw.js +1 -0
- package/dist/web/assets/{astro-DVBCJfG6.js → astro-CcrkAzRn.js} +1 -1
- package/dist/web/assets/{audio-preview-DG2Tw0cO.js → audio-preview-C0m1G-ba.js} +1 -1
- package/dist/web/assets/{blade-BHlL8SqU.js → blade-BW0AyHqd.js} +1 -1
- package/dist/web/assets/{bsl-D-UOfR1F.js → bsl-D3fsZX-N.js} +1 -1
- package/dist/web/assets/{cairo-Bn6rucli.js → cairo-BioL0Hpg.js} +1 -1
- package/dist/web/assets/chat-tab-Ca-jDA8G.js +15 -0
- package/dist/web/assets/click-outside-container-CbfvEfTe.js +1 -0
- package/dist/web/assets/{cobol-Byn9FYKu.js → cobol-SQ7BUEjA.js} +1 -1
- package/dist/web/assets/code-editor-Biyx46no.js +10 -0
- package/dist/web/assets/{coffee-DjBl4gm-.js → coffee-BwiA2ABt.js} +1 -1
- package/dist/web/assets/{conflict-editor-B6hVffX3.js → conflict-editor-CItgFyu9.js} +1 -1
- package/dist/web/assets/{cpp-CKzic-3E.js → cpp-CsiyAYfZ.js} +1 -1
- package/dist/web/assets/{crystal-Bkg-Ly_g.js → crystal-new71ISE.js} +1 -1
- package/dist/web/assets/{csv-preview-hL4Z9HS5.js → csv-preview-Dz6hI2BO.js} +1 -1
- package/dist/web/assets/data-grid-overlay-editor-Pq5c1EeR.js +1 -0
- package/dist/web/assets/database-viewer-7oDAd4kH.js +1 -0
- package/dist/web/assets/{diff-viewer-DZxnANY4.js → diff-viewer-B-3Byzuf.js} +1 -1
- package/dist/web/assets/dist-BbSJO7hs.js +11 -0
- package/dist/web/assets/{docx-preview-CrAc7Qjv.js → docx-preview-CM9l_r9q.js} +1 -1
- package/dist/web/assets/{edge-DVbZnd3Y.js → edge-CtcrJXZt.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-HTZPC9VP.js → edit-diff-preview-yY096y6H.js} +1 -1
- package/dist/web/assets/{elixir-Dwi8Wu7W.js → elixir-vH8AlwfK.js} +1 -1
- package/dist/web/assets/{elm-DtIv-HXJ.js → elm-vhtnbbrd.js} +1 -1
- package/dist/web/assets/{erb-D3HEDz1x.js → erb-xsqc-5NV.js} +1 -1
- package/dist/web/assets/{erlang-2MjTCzGz.js → erlang-CqCTsrl7.js} +1 -1
- package/dist/web/assets/esm-D3_gA6XW.js +2 -0
- package/dist/web/assets/explorer-body-DHOiBqiL.js +2 -0
- package/dist/web/assets/{explorer-window-content-NS8Y0v8c.js → explorer-window-content-CTBHwDgN.js} +1 -1
- package/dist/web/assets/{extension-webview-D6eywKPN.js → extension-webview-D3LOkB2x.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-BLyAtyPM.js → fortran-fixed-form-Ctm2ZKzo.js} +1 -1
- package/dist/web/assets/{fsharp-Bxbn_gbr.js → fsharp-DLTMpL2s.js} +1 -1
- package/dist/web/assets/{gdresource-sFMY4D2Q.js → gdresource-D3G0lQVG.js} +1 -1
- package/dist/web/assets/{git-commit-BzB9Lio5.js → git-commit-CScOErgk.js} +1 -1
- package/dist/web/assets/{git-log-panel-DAogZ9QV.js → git-log-panel-Bs9ShnYL.js} +1 -1
- package/dist/web/assets/{git-rebase-CKSVd848.js → git-rebase-C1SMSliG.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-B25fnH9y.js +1 -0
- package/dist/web/assets/glide-data-grid-DhqJzEAm.js +138 -0
- package/dist/web/assets/{glimmer-js-BY8aExAJ.js → glimmer-js-DsQZnICF.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Bak54dBX.js → glimmer-ts-DrhpAdWj.js} +1 -1
- package/dist/web/assets/{glsl-nR2eaJQe.js → glsl-eYL0pYVx.js} +1 -1
- package/dist/web/assets/{graphql-CGPPFAU6.js → graphql-DBRFyWue.js} +1 -1
- package/dist/web/assets/group-chat-tab-CwaKPj6c.js +1 -0
- package/dist/web/assets/{hack-CXIMX-Ol.js → hack-CMqVlAxr.js} +1 -1
- package/dist/web/assets/{haml-tPY-9Pfm.js → haml-GfSHNbBz.js} +1 -1
- package/dist/web/assets/{handlebars-BuW1LT7T.js → handlebars-rg_XF64o.js} +1 -1
- package/dist/web/assets/{html-bgVcwZ-t.js → html-BEmGwpcK.js} +1 -1
- package/dist/web/assets/{html-derivative-D6-IxTgn.js → html-derivative-BIu-fSXa.js} +1 -1
- package/dist/web/assets/{http-CJ1zyB-h.js → http-DpxX0ibD.js} +1 -1
- package/dist/web/assets/{hurl-CmoT7l3T.js → hurl-DUVEpqRc.js} +1 -1
- package/dist/web/assets/{hxml-CztOsL8o.js → hxml-BrTwgLjo.js} +1 -1
- package/dist/web/assets/{image-preview-Byjx8MoL.js → image-preview-BLDXptaD.js} +1 -1
- package/dist/web/assets/index-CtGTj9re.js +52 -0
- package/dist/web/assets/index-oxsyfDa0.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Dqg_TKq-.js +1 -0
- package/dist/web/assets/{jinja-B1pq3jJq.js → jinja-x3_G86rb.js} +1 -1
- package/dist/web/assets/{jison-D8B5pCwA.js → jison-4I0KPKa0.js} +1 -1
- package/dist/web/assets/{julia-D0K3bwpU.js → julia-DIztIJlK.js} +1 -1
- package/dist/web/assets/{just-B9Jlddu-.js → just-CIbbsxt1.js} +1 -1
- package/dist/web/assets/keybindings-store-BgBfamMQ.js +1 -0
- package/dist/web/assets/{latex-BPJSSE6j.js → latex-BSW7Iz55.js} +1 -1
- package/dist/web/assets/{liquid-lv10fo4U.js → liquid-DXEQy61g.js} +1 -1
- package/dist/web/assets/{lua-PNuaIBEF.js → lua-mPzZWOOd.js} +1 -1
- package/dist/web/assets/{markdown-renderer-CXlEe3ma.js → markdown-renderer-IZiQd8mi.js} +3 -3
- package/dist/web/assets/markdown-renderer-PTkraBMz.js +1 -0
- package/dist/web/assets/{marko-Du3frD6X.js → marko-DT4otfeN.js} +1 -1
- package/dist/web/assets/{mdc-BeDVcFRl.js → mdc-DF7GteQU.js} +1 -1
- package/dist/web/assets/{mobile-explorer-sheet-BuqBlP3o.js → mobile-explorer-sheet-BQ7FISPT.js} +1 -1
- package/dist/web/assets/{nextflow-DkcXP71s.js → nextflow-inS4q6JA.js} +1 -1
- package/dist/web/assets/{nginx-C5n8N_bt.js → nginx-C4sxDsIU.js} +1 -1
- package/dist/web/assets/{nim-CIhyfs-A.js → nim-B_KZbdbl.js} +1 -1
- package/dist/web/assets/notification-store-Cd1oQeCL.js +1 -0
- package/dist/web/assets/{number-overlay-editor-NgOsWJ7x.js → number-overlay-editor-J9_h14Jy.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-91mSm_sq.js +1 -0
- package/dist/web/assets/{pdf-preview-F2DWVB85.js → pdf-preview-D_QDoRAJ.js} +1 -1
- package/dist/web/assets/{perl-Cf31sZYE.js → perl-Bzur_Tvg.js} +1 -1
- package/dist/web/assets/{php-Dzl_MDIW.js → php-BjcXxjIr.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-D5-d4-hV.js +1 -0
- package/dist/web/assets/postgres-viewer-CHrw1Asz.js +3 -0
- package/dist/web/assets/{pug-CxSYHrB3.js → pug-DFgERppY.js} +1 -1
- package/dist/web/assets/{qml-DlEjGhwN.js → qml-CTl3rUgs.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-D_wSb0Qz.js +1 -0
- package/dist/web/assets/{razor-KVt5uW14.js → razor-B8YTmkEh.js} +1 -1
- package/dist/web/assets/{rst-D5BwNTnw.js → rst-KkcstADZ.js} +1 -1
- package/dist/web/assets/{ruby-D5fejQwd.js → ruby-BkGX_9bp.js} +1 -1
- package/dist/web/assets/{sas-Bj7B1CB2.js → sas-CXAPcIB7.js} +1 -1
- package/dist/web/assets/{scss-BB0CUoCL.js → scss-BstsOLPG.js} +1 -1
- package/dist/web/assets/{settings-store-CY-4_U3H.js → settings-store-DQcBuCyF.js} +2 -2
- package/dist/web/assets/{settings-tab-Khj-w6Sr.js → settings-tab-CsevraI7.js} +1 -1
- package/dist/web/assets/{shaderlab-BdjEZUMc.js → shaderlab-w3VeqhHk.js} +1 -1
- package/dist/web/assets/{shellsession-Bp1OA1rR.js → shellsession-CM2BxxEn.js} +1 -1
- package/dist/web/assets/{soy-DlYz3wsC.js → soy-1-aTOd4S.js} +1 -1
- package/dist/web/assets/{sparql-CdfII5Jd.js → sparql-CqmJ7XJo.js} +1 -1
- package/dist/web/assets/{sql-query-editor-Cp0_e2P1.js → sql-query-editor-CzOTxy85.js} +1 -1
- package/dist/web/assets/sqlite-viewer-COHO7wsr.js +1 -0
- package/dist/web/assets/{stata-BvUA6GUW.js → stata-BbcHuOrm.js} +1 -1
- package/dist/web/assets/{surrealql--DLNbTyI.js → surrealql-DYt8SBBH.js} +1 -1
- package/dist/web/assets/{svelte-CBfSsG4n.js → svelte-CBrmuJZk.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-CrssTOf4.js → system-monitor-tab-CS9DhJrA.js} +1 -1
- package/dist/web/assets/{team-member-window-content-D-hXax3c.js → team-member-window-content-DSlnO1ay.js} +1 -1
- package/dist/web/assets/{templ-Dv8zpKZW.js → templ-BWg_73eT.js} +1 -1
- package/dist/web/assets/{terminal-tab-BIQDG-Ti.js → terminal-tab-DCOinj6h.js} +3 -3
- package/dist/web/assets/{tex-CsrOiqh1.js → tex-ChwnYbaL.js} +1 -1
- package/dist/web/assets/tool-cards-DYQChJ7V.js +8 -0
- package/dist/web/assets/treemap-KZPCXAKY-DiNFF0Uo.js +1 -0
- package/dist/web/assets/{ts-tags-3T98k4EJ.js → ts-tags-CFZZJcMc.js} +1 -1
- package/dist/web/assets/{twig-u6QZ45FH.js → twig-DbRbrfbf.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-Djp1_fKi.js → use-monaco-theme-DcRjryA0.js} +1 -1
- package/dist/web/assets/{use-websocket-CCYA9-a2.js → use-websocket-CX3F55gy.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Bg4vE8QA.js → vendor-mermaid-Dede75gn.js} +193 -193
- package/dist/web/assets/video-preview-DaYONH41.js +1 -0
- package/dist/web/assets/{vue-html-BGL5qnuH.js → vue-html-vP_PPbWw.js} +1 -1
- package/dist/web/assets/{vue-CxIsxtwn.js → vue-iynf6DnU.js} +1 -1
- package/dist/web/assets/{vue-vine-DHPh_gMr.js → vue-vine-BycfyDpl.js} +1 -1
- package/dist/web/assets/{xml-C-vM2m7J.js → xml-BRgRn6xu.js} +1 -1
- package/dist/web/assets/{xsl-DvTXNwuO.js → xsl-CWrfaC1u.js} +1 -1
- package/dist/web/index.html +16 -16
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/package.json +1 -1
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/windows-process-tree.ts +351 -351
- package/src/web/app.tsx +4 -0
- package/src/web/components/os-explorer/actions/collision-prompt-queue.ts +133 -0
- package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +29 -3
- package/src/web/components/os-explorer/actions/explorer-actions-upload.ts +88 -33
- package/src/web/components/os-explorer/actions/use-collision-prompt.ts +56 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +29 -44
- package/src/web/components/os-explorer/actions/use-permanent-overwrite-prompt.ts +47 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +19 -27
- package/src/web/components/os-explorer/explorer-dialogs.tsx +12 -1
- package/src/web/components/os-explorer/upload/capture-picked-files.ts +18 -0
- package/src/web/components/os-explorer/upload/upload-batch-card.tsx +64 -0
- package/src/web/components/os-explorer/upload/upload-batch-progress.ts +34 -0
- package/src/web/components/os-explorer/upload/upload-batch-row.tsx +79 -0
- package/src/web/components/os-explorer/upload/upload-error-message.ts +19 -0
- package/src/web/components/os-explorer/upload/upload-progress-panel.tsx +72 -0
- package/src/web/components/os-explorer/upload/upload-store.ts +109 -0
- package/src/web/components/os-explorer/upload/use-upload-picker.tsx +7 -5
- package/src/web/lib/fs-upload-xhr.ts +13 -2
- package/dist/web/assets/architecture-PBZL5I3N-Bs7wzj-W.js +0 -1
- package/dist/web/assets/chat-tab-CgAv-crh.js +0 -15
- package/dist/web/assets/code-editor-bX4jlgMy.js +0 -10
- package/dist/web/assets/data-grid-overlay-editor-B1mTl3KS.js +0 -1
- package/dist/web/assets/data-grid-types-BXwlFMzs.js +0 -1
- package/dist/web/assets/database-viewer-CTK5kMaA.js +0 -1
- package/dist/web/assets/dist-CUPbqNnL.js +0 -11
- package/dist/web/assets/esm-CZ8B8Ba2.js +0 -2
- package/dist/web/assets/explorer-body-DykJXCTw.js +0 -2
- package/dist/web/assets/gitGraph-HDMCJU4V-D7i54kbM.js +0 -1
- package/dist/web/assets/glide-data-grid-DxfJRN-r.js +0 -138
- package/dist/web/assets/group-chat-tab-DkVi6qL2.js +0 -1
- package/dist/web/assets/index-Bucbpby-.css +0 -2
- package/dist/web/assets/index-CcN0TBm4.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-BV41AzR9.js +0 -1
- package/dist/web/assets/keybindings-store-DJIHqbLM.js +0 -1
- package/dist/web/assets/markdown-renderer-pYznJDbh.js +0 -1
- package/dist/web/assets/notification-store-Bcl5efY2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-CaJoP44m.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-DSGZmNw2.js +0 -1
- package/dist/web/assets/postgres-viewer-KZqPYfOB.js +0 -3
- package/dist/web/assets/radar-KQ55EAFF-Be4VUTs0.js +0 -1
- package/dist/web/assets/sqlite-viewer-CIv3sagZ.js +0 -1
- package/dist/web/assets/tool-cards-Dx-Rrpri.js +0 -8
- package/dist/web/assets/treemap-KZPCXAKY-Cu2zgbxU.js +0 -1
- package/dist/web/assets/video-preview-cvjNMjWA.js +0 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- /package/dist/web/assets/{abap-C1K0-wzc.js → abap-aW7-Jvmb.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CJhQVjLA.js → actionscript-3-DTKrKcIk.js} +0 -0
- /package/dist/web/assets/{ada-Bt8r9fME.js → ada-BAPZyMF6.js} +0 -0
- /package/dist/web/assets/{andromeeda-DF1I8qi3.js → andromeeda-B7tKiRbo.js} +0 -0
- /package/dist/web/assets/{apache-CnlleE6P.js → apache-DzlxyZL6.js} +0 -0
- /package/dist/web/assets/{apex-j0C6Xmk6.js → apex-YMuKkRzC.js} +0 -0
- /package/dist/web/assets/{api-themes-CEyYK7AK.js → api-themes-BEEwU8CU.js} +0 -0
- /package/dist/web/assets/{applescript-DO7AnO8U.js → applescript-CA5lfLCT.js} +0 -0
- /package/dist/web/assets/{ara-i83AydZj.js → ara--oyOaCyG.js} +0 -0
- /package/dist/web/assets/{asciidoc-DbBA6-sF.js → asciidoc-DnOF36RG.js} +0 -0
- /package/dist/web/assets/{asm-B5S47Rgr.js → asm-BBlzivHM.js} +0 -0
- /package/dist/web/assets/{aurora-x-De_IaSKw.js → aurora-x-Vcb-h5c-.js} +0 -0
- /package/dist/web/assets/{awk-CfyLHXqS.js → awk-DIGedLLX.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DTLdZ1Si.js → ayu-dark-CpqJlEms.js} +0 -0
- /package/dist/web/assets/{ayu-light-DTOfkzhu.js → ayu-light-C_Ao3JZA.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-DsAECCMv.js → ayu-mirage-BYWxo0wh.js} +0 -0
- /package/dist/web/assets/{ballerina-CumGp9A7.js → ballerina-Ba65P_0B.js} +0 -0
- /package/dist/web/assets/{bat-Ca2SBbYN.js → bat-CsJlH9w5.js} +0 -0
- /package/dist/web/assets/{beancount-CqyXBwwF.js → beancount-BPC5j2kY.js} +0 -0
- /package/dist/web/assets/{berry-yl-kJr_H.js → berry-FDw8JA3_.js} +0 -0
- /package/dist/web/assets/{bibtex-DoFFszjV.js → bibtex-7QrVi7O0.js} +0 -0
- /package/dist/web/assets/{bicep-CWPi4nfK.js → bicep-Ch0xl3Bs.js} +0 -0
- /package/dist/web/assets/{bird2-DXew-ut0.js → bird2-DCEMu9Mp.js} +0 -0
- /package/dist/web/assets/{c-clbySDvd.js → c-CUVHE-5K.js} +0 -0
- /package/dist/web/assets/{c3-B65jvSM1.js → c3-DSTfto87.js} +0 -0
- /package/dist/web/assets/{cadence-Cm8tQTHN.js → cadence-C7hktLdb.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-Cu-XYIgu.js → can-open-in-ppm-C1RSbZ7O.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DLeGslh_.js → catppuccin-frappe-DkyYFCSH.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CR5GeHws.js → catppuccin-latte-BKnWZFTb.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-p-XgjxhP.js → catppuccin-macchiato-C1Ab5ZLR.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-B8QOAZLl.js → catppuccin-mocha-COMc04WB.js} +0 -0
- /package/dist/web/assets/{clarity-DPulbvKH.js → clarity-8PE1fP_c.js} +0 -0
- /package/dist/web/assets/{clojure-DFuWS20u.js → clojure-Bz98WDQt.js} +0 -0
- /package/dist/web/assets/{cmake-DejCqcu4.js → cmake-DU6g1qMF.js} +0 -0
- /package/dist/web/assets/{codeowners-BwAyqWIH.js → codeowners-2Gm3RtID.js} +0 -0
- /package/dist/web/assets/{codeql-DgSwFjqI.js → codeql-B3FnVaRc.js} +0 -0
- /package/dist/web/assets/{common-lisp-CxIVJCtf.js → common-lisp-zYi9oKAF.js} +0 -0
- /package/dist/web/assets/{coq-DLSjSnmf.js → coq-W0vNtu9V.js} +0 -0
- /package/dist/web/assets/{csharp-C1HHzXBp.js → csharp-DlFGO-Bd.js} +0 -0
- /package/dist/web/assets/{css-VXsMY--y.js → css-BLcoafWU.js} +0 -0
- /package/dist/web/assets/{csv-BEbcJdHz.js → csv-BpMytiYh.js} +0 -0
- /package/dist/web/assets/{csv-parser-r-Nk1oHT.js → csv-parser-JohiZK_a.js} +0 -0
- /package/dist/web/assets/{cue-BkDnFqUD.js → cue-Ckjfj5Fx.js} +0 -0
- /package/dist/web/assets/{cypher-1cMqojSQ.js → cypher-D9y8enlA.js} +0 -0
- /package/dist/web/assets/{d-BnYjRXtH.js → d-BQt_xM-A.js} +0 -0
- /package/dist/web/assets/{dark-plus-XfbaH7kZ.js → dark-plus-YVOVVElU.js} +0 -0
- /package/dist/web/assets/{dart-Ca7s-8hK.js → dart-CbQZvIly.js} +0 -0
- /package/dist/web/assets/{dax-CmuOw-mY.js → dax-U83MmotH.js} +0 -0
- /package/dist/web/assets/{desktop-C2gJ6Ovb.js → desktop-vhxlER7T.js} +0 -0
- /package/dist/web/assets/{diff-BCEo1Ej7.js → diff-qIh1K-1r.js} +0 -0
- /package/dist/web/assets/{dist-DOlBnxFX.js → dist-k8y79TJM.js} +0 -0
- /package/dist/web/assets/{docker-DN2jlyAh.js → docker-CugomxC1.js} +0 -0
- /package/dist/web/assets/{dotenv-NExMY2kS.js → dotenv-Bjf6iJkB.js} +0 -0
- /package/dist/web/assets/{dracula-B9lbE01y.js → dracula-Cz9sO5UW.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CMBBfKF_.js → dracula-soft-DY0S2NVH.js} +0 -0
- /package/dist/web/assets/{dream-maker-DEyDwFsd.js → dream-maker-B8LMBotO.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-D_qP2zS6.js → emacs-lisp-DuYCGVqQ.js} +0 -0
- /package/dist/web/assets/{everforest-dark-DbHa2U-p.js → everforest-dark-WezKGzP_.js} +0 -0
- /package/dist/web/assets/{everforest-light-BISkJchP.js → everforest-light-Do7h7sGp.js} +0 -0
- /package/dist/web/assets/{fennel-6B22SfJR.js → fennel-DPc0PrDK.js} +0 -0
- /package/dist/web/assets/{fish-CubIIvJD.js → fish-Boq_hlR9.js} +0 -0
- /package/dist/web/assets/{fluent-DF55zQ09.js → fluent-LAzaxkrP.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-DvOu8jAt.js → fortran-free-form-CeCg8jZE.js} +0 -0
- /package/dist/web/assets/{gdscript-DCPxdLpq.js → gdscript-CKnfKnN6.js} +0 -0
- /package/dist/web/assets/{gdshader-CTZ381FO.js → gdshader-C_ofnb9K.js} +0 -0
- /package/dist/web/assets/{genie-C_QDtvgU.js → genie-DhagrbXp.js} +0 -0
- /package/dist/web/assets/{gherkin-BcvK42tF.js → gherkin-1uujaZW0.js} +0 -0
- /package/dist/web/assets/{github-dark-C9afs_4w.js → github-dark-DIV5FCeV.js} +0 -0
- /package/dist/web/assets/{github-dark-default-CKGVrl3Z.js → github-dark-default-CcP66ogT.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-C3dQZ_LC.js → github-dark-dimmed-Bj80ZBcI.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-DW78QukY.js → github-dark-high-contrast-CyI7oCCT.js} +0 -0
- /package/dist/web/assets/{github-light-BnP2dznS.js → github-light-CqqVK-2E.js} +0 -0
- /package/dist/web/assets/{github-light-default-C2fPZzPG.js → github-light-default-DEhIDABz.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-BzIuUFR2.js → github-light-high-contrast-BqtUZSo1.js} +0 -0
- /package/dist/web/assets/{gleam-BpeKwEIO.js → gleam-Bv3z0NLi.js} +0 -0
- /package/dist/web/assets/{gn-nUb_Mit0.js → gn-knjS3hKu.js} +0 -0
- /package/dist/web/assets/{gnuplot-D7I0v20p.js → gnuplot-g56b7rpz.js} +0 -0
- /package/dist/web/assets/{go-D7Qqska7.js → go-DX8QO6z_.js} +0 -0
- /package/dist/web/assets/{groovy-DS-pne5-.js → groovy-DuYa0c1A.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-C0W01fsq.js → gruvbox-dark-hard-D7IRkjrx.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Bqo6fQbg.js → gruvbox-dark-medium-UJ8YMtdx.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-D-XSM_fu.js → gruvbox-dark-soft-I2k_O22-.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-IVAwMJRl.js → gruvbox-light-hard-CAJEeL1I.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-38ATiG7i.js → gruvbox-light-medium-BzfBTG9R.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DnZr3c_0.js → gruvbox-light-soft-B4lq6YrU.js} +0 -0
- /package/dist/web/assets/{haskell-uQcywkDH.js → haskell-BG7xy9_5.js} +0 -0
- /package/dist/web/assets/{haxe-c6bI4_GT.js → haxe-CyusqGGx.js} +0 -0
- /package/dist/web/assets/{hcl-CQXTiEXq.js → hcl-BewE7tWl.js} +0 -0
- /package/dist/web/assets/{hjson-ChETf2bz.js → hjson-Ds8lzmRZ.js} +0 -0
- /package/dist/web/assets/{hlsl-BMC4JGoE.js → hlsl-ZFwRfL2h.js} +0 -0
- /package/dist/web/assets/{horizon-BusFlhLW.js → horizon-VAwIHbzU.js} +0 -0
- /package/dist/web/assets/{horizon-bright-2oD0Mz7W.js → horizon-bright-wJOIDqMt.js} +0 -0
- /package/dist/web/assets/{houston-DdbbjqFg.js → houston-CeohYS24.js} +0 -0
- /package/dist/web/assets/{hy-D_SOuqw9.js → hy-CUv6HDwS.js} +0 -0
- /package/dist/web/assets/{image-gallery-CrM5fHKy.js → image-gallery-CbX4JjyT.js} +0 -0
- /package/dist/web/assets/{imba-BxLOHO5I.js → imba-D8TAYKw4.js} +0 -0
- /package/dist/web/assets/{ini-ox-CFcg0.js → ini-BAKG__sb.js} +0 -0
- /package/dist/web/assets/{java-Bzo8yIal.js → java-CTxgEFQr.js} +0 -0
- /package/dist/web/assets/{javascript-DHuz9VXn.js → javascript-CyPQ_iPD.js} +0 -0
- /package/dist/web/assets/{json-0_y80FD5.js → json-atTPgPUT.js} +0 -0
- /package/dist/web/assets/{json5-CEQ50pGM.js → json5-LGxGeNhP.js} +0 -0
- /package/dist/web/assets/{jsonc-CfrLEAKA.js → jsonc-Bitk_sll.js} +0 -0
- /package/dist/web/assets/{jsonl-fKbrANC2.js → jsonl-DdyTPXqv.js} +0 -0
- /package/dist/web/assets/{jsonnet-DKbkPaQ2.js → jsonnet-GniiqKoY.js} +0 -0
- /package/dist/web/assets/{jssm-Dxxx1Hqu.js → jssm-CW6QRmkd.js} +0 -0
- /package/dist/web/assets/{jsx-DlJL-Oj3.js → jsx-Cr_EAVnd.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-CwN4SV1c.js → kanagawa-dragon-N1CxlLFc.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-BHltayiE.js → kanagawa-lotus-DuDdnVEr.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-D-KXPjw6.js → kanagawa-wave-BEcB8wPC.js} +0 -0
- /package/dist/web/assets/{kdl-DvtoJdFo.js → kdl-YdJlGKnl.js} +0 -0
- /package/dist/web/assets/{kotlin-CTS4MLcx.js → kotlin-D8Ou1-Vz.js} +0 -0
- /package/dist/web/assets/{kusto-HtSE_sKm.js → kusto-CfNzHOLh.js} +0 -0
- /package/dist/web/assets/{laserwave-DaXZG9ge.js → laserwave-Dw-UwgjO.js} +0 -0
- /package/dist/web/assets/{lean-BYtGUfWx.js → lean-Cob-4uj9.js} +0 -0
- /package/dist/web/assets/{less-BoxmU_Mk.js → less-D6UGsQ-F.js} +0 -0
- /package/dist/web/assets/{lib-BzxL1ARZ.js → lib-D6yys864.js} +0 -0
- /package/dist/web/assets/{libesm-ah-mr-sh.js → libesm-BNnDqFup.js} +0 -0
- /package/dist/web/assets/{light-plus-Bt5CyUuo.js → light-plus-g6c1YK9w.js} +0 -0
- /package/dist/web/assets/{llvm-CSdk9q9V.js → llvm-BCk7-7xF.js} +0 -0
- /package/dist/web/assets/{log-B-kp_3CL.js → log-DyyODQCQ.js} +0 -0
- /package/dist/web/assets/{logo-DdIsXFDO.js → logo-z3PSmZ0Q.js} +0 -0
- /package/dist/web/assets/{luau-DUBpLlzQ.js → luau-CuwlNMKd.js} +0 -0
- /package/dist/web/assets/{make-B3JVjoW_.js → make-B2ZcjXHI.js} +0 -0
- /package/dist/web/assets/{markdown-Seon0vHs.js → markdown-Bh7F2dEA.js} +0 -0
- /package/dist/web/assets/{material-theme-Nq-ChX1P.js → material-theme-DIOuD6li.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-CPV4ayXJ.js → material-theme-darker-Cg3vOVsZ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-0kyjYqEd.js → material-theme-lighter-B3plW7F5.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-DGpAp5co.js → material-theme-ocean-BC3W6Qbp.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-B0AUn0NG.js → material-theme-palenight-cn2b8irZ.js} +0 -0
- /package/dist/web/assets/{matlab-C2waytxs.js → matlab-CbKDDcme.js} +0 -0
- /package/dist/web/assets/{mdx-DAis6R6I.js → mdx-DfoNkGc0.js} +0 -0
- /package/dist/web/assets/{media-url-C5vA0ha9.js → media-url-FbGzzFQa.js} +0 -0
- /package/dist/web/assets/{mermaid-_rnT2YGD.js → mermaid-BpGcccah.js} +0 -0
- /package/dist/web/assets/{min-dark-BSSjchUB.js → min-dark-C-1-xt-i.js} +0 -0
- /package/dist/web/assets/{min-light-DomAFQsa.js → min-light-DkYGd6ZF.js} +0 -0
- /package/dist/web/assets/{mipsasm-bBgtvPyG.js → mipsasm-Dk87orqA.js} +0 -0
- /package/dist/web/assets/{mojo-Cp9BayW0.js → mojo-Ctfni3Xg.js} +0 -0
- /package/dist/web/assets/{monokai-CL_9gbmn.js → monokai-dRz4X-9c.js} +0 -0
- /package/dist/web/assets/{moonbit-BS_fcr-P.js → moonbit-BenWvd8O.js} +0 -0
- /package/dist/web/assets/{move-C79AH45U.js → move-CDhttBEa.js} +0 -0
- /package/dist/web/assets/{narrat-CJ5s3BDf.js → narrat-2qOoPAIA.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-ZHa4JrPJ.js → nextflow-groovy-C4CoTM6k.js} +0 -0
- /package/dist/web/assets/{night-owl-Jkf9zFQK.js → night-owl-CC7ba6ex.js} +0 -0
- /package/dist/web/assets/{night-owl-light-DRPeaG5J.js → night-owl-light-su9bVa5K.js} +0 -0
- /package/dist/web/assets/{nix-D2Jet9DN.js → nix-BOfpg6oF.js} +0 -0
- /package/dist/web/assets/{nord-CREVs22j.js → nord-Cup4UQrN.js} +0 -0
- /package/dist/web/assets/{nushell-CAiDi_mR.js → nushell-CV3Ed_Pd.js} +0 -0
- /package/dist/web/assets/{objective-c-CVkmJkC1.js → objective-c-DNKsz1d1.js} +0 -0
- /package/dist/web/assets/{objective-cpp-DIKDO1Ly.js → objective-cpp-BT5L7lhS.js} +0 -0
- /package/dist/web/assets/{ocaml-R9qtGQhs.js → ocaml-Qj-27E4B.js} +0 -0
- /package/dist/web/assets/{odin-DTYNQqVr.js → odin-C2scSLLk.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-BSLWUJn5.js → one-dark-pro-C1HWasX_.js} +0 -0
- /package/dist/web/assets/{one-light-DbkRXG3t.js → one-light-B3bElFuJ.js} +0 -0
- /package/dist/web/assets/{openscad-BlPcSnmT.js → openscad-C6UAQ5Px.js} +0 -0
- /package/dist/web/assets/{pascal-B1Y46ZZB.js → pascal-DICllhaG.js} +0 -0
- /package/dist/web/assets/{pkl-BhJSJbFv.js → pkl-qghE3sfq.js} +0 -0
- /package/dist/web/assets/{plastic-BzxsCTpi.js → plastic-ClZHBkI2.js} +0 -0
- /package/dist/web/assets/{plsql-DBba7dyP.js → plsql-CKw-M6SO.js} +0 -0
- /package/dist/web/assets/{po-BvgLlnNo.js → po-BKro6MCB.js} +0 -0
- /package/dist/web/assets/{poimandres-DUaI_1JP.js → poimandres-Bdo7Kn_m.js} +0 -0
- /package/dist/web/assets/{polar-C7uILwJn.js → polar-B1rAV5le.js} +0 -0
- /package/dist/web/assets/{postcss-BgJlpFdw.js → postcss-BsA5TXYz.js} +0 -0
- /package/dist/web/assets/{powerquery-Dn1MIpZu.js → powerquery-hxd8aiZs.js} +0 -0
- /package/dist/web/assets/{powershell-Bo-DYzOS.js → powershell-D83XmNBi.js} +0 -0
- /package/dist/web/assets/{prisma-CTLs5IdT.js → prisma-jpc3Lfer.js} +0 -0
- /package/dist/web/assets/{prolog-QRGun3eG.js → prolog-By7loPcz.js} +0 -0
- /package/dist/web/assets/{proto-B7PI7apn.js → proto-B03P8I7L.js} +0 -0
- /package/dist/web/assets/{puppet-CBq0tLrP.js → puppet-BLEutFx-.js} +0 -0
- /package/dist/web/assets/{purescript-DFewOQb2.js → purescript-DdCIvtj7.js} +0 -0
- /package/dist/web/assets/{python-iWuxKXvT.js → python-DwlqCvPN.js} +0 -0
- /package/dist/web/assets/{qmldir-cXiW3AsF.js → qmldir-CkEpGrJr.js} +0 -0
- /package/dist/web/assets/{qss-DASwAbBL.js → qss-B6SOQ6U9.js} +0 -0
- /package/dist/web/assets/{r-TU2eaIPw.js → r-Y0KMI7Y5.js} +0 -0
- /package/dist/web/assets/{racket-SkRKqrLY.js → racket-BF5spJBc.js} +0 -0
- /package/dist/web/assets/{raku-D31lthkZ.js → raku-xNANhvUv.js} +0 -0
- /package/dist/web/assets/{red-CTcQZevb.js → red-DnHsBzi8.js} +0 -0
- /package/dist/web/assets/{reg-D8YAsTGf.js → reg-CWhYjQ7O.js} +0 -0
- /package/dist/web/assets/{regexp-CD2uvAWx.js → regexp-CouLuX6r.js} +0 -0
- /package/dist/web/assets/{rel-CAaI-1fg.js → rel-Ctf_fx-q.js} +0 -0
- /package/dist/web/assets/{riscv-DR-hJVIj.js → riscv-Dji9ndNu.js} +0 -0
- /package/dist/web/assets/{ron-CuUCJ0n4.js → ron-BgHqfYU8.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-Bqz2sBRY.js → rose-pine-dawn-B5US25HC.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-wqyh0an4.js → rose-pine-moon-vC-4_Eb_.js} +0 -0
- /package/dist/web/assets/{rose-pine-aFHxW48i.js → rose-pine-rIa8chEL.js} +0 -0
- /package/dist/web/assets/{rosmsg-CI1ywimO.js → rosmsg-Bfxbgaxi.js} +0 -0
- /package/dist/web/assets/{run-in-terminal-B8TDtaM_.js → run-in-terminal-D-Q9wPF_.js} +0 -0
- /package/dist/web/assets/{rust-p7Imk4G_.js → rust-DSRCI-l0.js} +0 -0
- /package/dist/web/assets/{sass-C8JfaqCV.js → sass-BaMwafsT.js} +0 -0
- /package/dist/web/assets/{scala-DNxCW1By.js → scala-CnoxeIBD.js} +0 -0
- /package/dist/web/assets/{scheme-BYuUPJoL.js → scheme-BmSziQ83.js} +0 -0
- /package/dist/web/assets/{sdbl-DbPeJPUr.js → sdbl-CGDFOQ4F.js} +0 -0
- /package/dist/web/assets/{send-to-chat-CuBAVC8l.js → send-to-chat-BPeYO9Pv.js} +0 -0
- /package/dist/web/assets/{shellscript-DMiHlR3q.js → shellscript-D3_gthD5.js} +0 -0
- /package/dist/web/assets/{slack-dark-C4xGcQXI.js → slack-dark-Cy4dNR2g.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BzgRt_wm.js → slack-ochin-B8hNeONl.js} +0 -0
- /package/dist/web/assets/{smalltalk-LDeL9PYH.js → smalltalk-B5OQnpLF.js} +0 -0
- /package/dist/web/assets/{snazzy-light-CXE7a5j_.js → snazzy-light-DDL1ZQgf.js} +0 -0
- /package/dist/web/assets/{solarized-dark-DgUhY74x.js → solarized-dark-COUdbN1J.js} +0 -0
- /package/dist/web/assets/{solarized-light-DD9C-H-9.js → solarized-light-CLHIejSq.js} +0 -0
- /package/dist/web/assets/{solidity-CAbDB4D4.js → solidity-DpQHaveQ.js} +0 -0
- /package/dist/web/assets/{splunk-kQREPfIc.js → splunk-BGT75_a1.js} +0 -0
- /package/dist/web/assets/{sql-CBzeNAD8.js → sql-ZPjZoKEG.js} +0 -0
- /package/dist/web/assets/{ssh-config-L1euxrsM.js → ssh-config-B6oU9nqL.js} +0 -0
- /package/dist/web/assets/{stylus-DvBnYniP.js → stylus-Bbgm8BDk.js} +0 -0
- /package/dist/web/assets/{swift-DF5NDQp6.js → swift-7OUZlXRr.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DbBhB_Hq.js → synthwave-84-Dp7xsQqz.js} +0 -0
- /package/dist/web/assets/{system-verilog-D8s2ZW_k.js → system-verilog-COlHqI_R.js} +0 -0
- /package/dist/web/assets/{systemd-a5-XZJsG.js → systemd-DXL9nsy_.js} +0 -0
- /package/dist/web/assets/{talonscript-BLa0URTW.js → talonscript-Bkpiqjjy.js} +0 -0
- /package/dist/web/assets/{tasl-DIoV4XcC.js → tasl-Bpra97jM.js} +0 -0
- /package/dist/web/assets/{tcl-ztYAdXfQ.js → tcl-BcDm6PGE.js} +0 -0
- /package/dist/web/assets/{terraform-DvpCCiOZ.js → terraform-DGQKmd9G.js} +0 -0
- /package/dist/web/assets/{tokyo-night-BkHlYUv0.js → tokyo-night-CyLasBR5.js} +0 -0
- /package/dist/web/assets/{toml-acIrxos6.js → toml-D-01EUMm.js} +0 -0
- /package/dist/web/assets/{tsv-BJB5EVCT.js → tsv-DlZx-hGD.js} +0 -0
- /package/dist/web/assets/{tsx-B1MmXGT3.js → tsx-Bu211EOh.js} +0 -0
- /package/dist/web/assets/{turtle-DFceTiqd.js → turtle-CwM--Nm2.js} +0 -0
- /package/dist/web/assets/{typescript-Dz9wU5Y1.js → typescript-wOPax6Gk.js} +0 -0
- /package/dist/web/assets/{typespec-CPuYZijj.js → typespec-C5n5j5Cf.js} +0 -0
- /package/dist/web/assets/{typst-CfsCIYv8.js → typst-DQS-vHSj.js} +0 -0
- /package/dist/web/assets/{use-blob-url-BmlYYZ8L.js → use-blob-url-DdEfzVb8.js} +0 -0
- /package/dist/web/assets/{v-CGo0jcXA.js → v-BMQHRcJ5.js} +0 -0
- /package/dist/web/assets/{vala-AsSR0hrb.js → vala-By6oCVpL.js} +0 -0
- /package/dist/web/assets/{vb-C7tvbXv1.js → vb-BSmHxRqj.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BeaIjRrv.js → vendor-xterm-BHWghY3d.js} +0 -0
- /package/dist/web/assets/{verilog--a33ND-p.js → verilog-Bt-zbfre.js} +0 -0
- /package/dist/web/assets/{vesper-B_Z0h4pn.js → vesper-Bz7pxhsL.js} +0 -0
- /package/dist/web/assets/{vhdl-CWL5dZPN.js → vhdl-BXjhSHk-.js} +0 -0
- /package/dist/web/assets/{viml-BglmT14Y.js → viml-B4m7rYZk.js} +0 -0
- /package/dist/web/assets/{vitesse-black-RKFsSqQI.js → vitesse-black-BsmvsuHB.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-D7zDWNmc.js → vitesse-dark-Cqnvi8YY.js} +0 -0
- /package/dist/web/assets/{vitesse-light-MAp87W5b.js → vitesse-light-KbYJh0ae.js} +0 -0
- /package/dist/web/assets/{vyper-CgCBYg1G.js → vyper-BaHPAgL_.js} +0 -0
- /package/dist/web/assets/{wasm-BOG7ZeTT.js → wasm-Cfq9I1o-.js} +0 -0
- /package/dist/web/assets/{wasm-Bwl4txwp.js → wasm-Dpw0fYUw.js} +0 -0
- /package/dist/web/assets/{wenyan-DpShPKXZ.js → wenyan-DZsJG8fl.js} +0 -0
- /package/dist/web/assets/{wgsl-BakcCblK.js → wgsl-CVc3AdMI.js} +0 -0
- /package/dist/web/assets/{wikitext-NroRWVAO.js → wikitext-D9TsdERw.js} +0 -0
- /package/dist/web/assets/{wit-B5CM2pg6.js → wit-B55b-kBe.js} +0 -0
- /package/dist/web/assets/{wolfram-7MTFBJpR.js → wolfram-BMCiuBhR.js} +0 -0
- /package/dist/web/assets/{yaml-Bwr01G2K.js → yaml-T9AUQgxg.js} +0 -0
- /package/dist/web/assets/{zenscript-Dj8GATvi.js → zenscript-C2NuU93Q.js} +0 -0
- /package/dist/web/assets/{zig-LAE-fbS-.js → zig-B4cWz8iD.js} +0 -0
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
* only the promise wiring is local — so a collision looks identical wherever the drop landed.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { useEffect, useMemo, useRef,
|
|
12
|
-
import type {
|
|
11
|
+
import { useEffect, useMemo, useRef, type ReactNode } from "react";
|
|
12
|
+
import type { TransferContext } from "../actions/explorer-actions-clipboard";
|
|
13
13
|
import { uploadEntries } from "../actions/explorer-actions-upload";
|
|
14
|
-
import
|
|
14
|
+
import { useCollisionPrompt } from "../actions/use-collision-prompt";
|
|
15
|
+
import { usePermanentOverwritePrompt } from "../actions/use-permanent-overwrite-prompt";
|
|
15
16
|
import { ExplorerDialogs } from "../explorer-dialogs";
|
|
16
17
|
import type { DroppedEntry } from "../upload/collect-dropped-entries";
|
|
17
18
|
import { transferRunner, type DropRunner } from "./entry-drop-executor";
|
|
@@ -25,8 +26,8 @@ export interface DropTransfer {
|
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export function useDropTransfer(sep: string, platform?: string): DropTransfer {
|
|
28
|
-
const
|
|
29
|
-
const
|
|
29
|
+
const collisionPrompt = useCollisionPrompt();
|
|
30
|
+
const permanentOverwritePrompt = usePermanentOverwritePrompt();
|
|
30
31
|
|
|
31
32
|
// The separator can change under a long-lived surface (the tree follows the active
|
|
32
33
|
// project), so the context reads it live rather than closing over the first value.
|
|
@@ -34,31 +35,22 @@ export function useDropTransfer(sep: string, platform?: string): DropTransfer {
|
|
|
34
35
|
const sepRef = useRef(sep);
|
|
35
36
|
useEffect(() => { sepRef.current = sep; }, [sep]);
|
|
36
37
|
|
|
38
|
+
// Read through refs so `context`'s identity stays stable across renders — see the same
|
|
39
|
+
// pattern in `use-explorer-actions.ts`.
|
|
40
|
+
const collisionPromptRef = useRef(collisionPrompt);
|
|
41
|
+
collisionPromptRef.current = collisionPrompt;
|
|
42
|
+
const permanentOverwritePromptRef = useRef(permanentOverwritePrompt);
|
|
43
|
+
permanentOverwritePromptRef.current = permanentOverwritePrompt;
|
|
44
|
+
|
|
37
45
|
const context = useMemo<TransferContext>(
|
|
38
46
|
() => ({
|
|
39
47
|
get sep() {
|
|
40
48
|
return sepRef.current;
|
|
41
49
|
},
|
|
42
|
-
resolve: (request
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
resolve: (choice) => {
|
|
47
|
-
setCollision(null);
|
|
48
|
-
resolve(choice);
|
|
49
|
-
},
|
|
50
|
-
});
|
|
51
|
-
}),
|
|
52
|
-
confirmPermanentOverwrite: (name: string) =>
|
|
53
|
-
new Promise<boolean>((resolve) => {
|
|
54
|
-
setPermanentOverwrite({
|
|
55
|
-
name,
|
|
56
|
-
resolve: (proceed) => {
|
|
57
|
-
setPermanentOverwrite(null);
|
|
58
|
-
resolve(proceed);
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
}),
|
|
50
|
+
resolve: (request) => collisionPromptRef.current.resolve(request),
|
|
51
|
+
startBatch: () => collisionPromptRef.current.startBatch(),
|
|
52
|
+
endBatch: () => collisionPromptRef.current.endBatch(),
|
|
53
|
+
confirmPermanentOverwrite: (name: string) => permanentOverwritePromptRef.current.confirm(name),
|
|
62
54
|
}),
|
|
63
55
|
[],
|
|
64
56
|
);
|
|
@@ -72,8 +64,8 @@ export function useDropTransfer(sep: string, platform?: string): DropTransfer {
|
|
|
72
64
|
const prompts = (
|
|
73
65
|
<ExplorerDialogs
|
|
74
66
|
dialogs={{
|
|
75
|
-
collision,
|
|
76
|
-
permanentOverwrite,
|
|
67
|
+
collision: collisionPrompt.state,
|
|
68
|
+
permanentOverwrite: permanentOverwritePrompt.state,
|
|
77
69
|
pendingDelete: null,
|
|
78
70
|
properties: null,
|
|
79
71
|
inlineError: null,
|
|
@@ -31,7 +31,7 @@ export function ExplorerDialogs({ dialogs, platform, sep }: ExplorerDialogsProps
|
|
|
31
31
|
// Dismissing without choosing must not silently overwrite anything.
|
|
32
32
|
onClose={() => collision.resolve("skip")}
|
|
33
33
|
title="An item with that name already exists"
|
|
34
|
-
description={collision.name}
|
|
34
|
+
description={collision.remaining > 0 ? `${collision.name} — and ${collision.remaining} more` : collision.name}
|
|
35
35
|
footer={
|
|
36
36
|
<>
|
|
37
37
|
<Button variant="outline" onClick={() => collision.resolve("skip")}>Skip</Button>
|
|
@@ -44,6 +44,17 @@ export function ExplorerDialogs({ dialogs, platform, sep }: ExplorerDialogsProps
|
|
|
44
44
|
Replacing moves the existing item to the Trash first, so it stays recoverable.
|
|
45
45
|
Keep both adds a numbered suffix.
|
|
46
46
|
</p>
|
|
47
|
+
{collision.remaining > 0 && (
|
|
48
|
+
<label className="flex min-h-11 items-center gap-2 text-sm text-text-2">
|
|
49
|
+
<input
|
|
50
|
+
type="checkbox"
|
|
51
|
+
className="size-4"
|
|
52
|
+
checked={collision.applyToAll}
|
|
53
|
+
onChange={(e) => collision.setApplyToAll(e.target.checked)}
|
|
54
|
+
/>
|
|
55
|
+
Apply to all {collision.remaining} remaining item{collision.remaining === 1 ? "" : "s"}
|
|
56
|
+
</label>
|
|
57
|
+
)}
|
|
47
58
|
</ExplorerModalShell>
|
|
48
59
|
)}
|
|
49
60
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pulls every file out of a (possibly live) `FileList`-shaped object into a plain array,
|
|
3
|
+
* BEFORE the caller resets the `<input>`'s value. Some Chromium versions empty the input's
|
|
4
|
+
* *live* `FileList` the instant `value` is reset, so calling `resetInput` first silently
|
|
5
|
+
* drops every picked file — this ordering is the one thing that must never be inlined and
|
|
6
|
+
* reshuffled, hence its own pure, unit-testable function.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface PickableFileList {
|
|
10
|
+
length: number;
|
|
11
|
+
item(index: number): File | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function capturePickedFiles(list: PickableFileList | null, resetInput: () => void): File[] {
|
|
15
|
+
const files = list ? Array.from({ length: list.length }, (_, i) => list.item(i)).filter((f): f is File => f != null) : [];
|
|
16
|
+
resetInput();
|
|
17
|
+
return files;
|
|
18
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One batch's contents: header (destination + overall progress + Cancel all/Close) and the
|
|
3
|
+
* per-file rows. Shared by the desktop floating card and the mobile sheet — only the outer
|
|
4
|
+
* chrome differs between them (see `upload-progress-panel.tsx`).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { X } from "lucide-react";
|
|
8
|
+
import { Button } from "@/components/ui/button";
|
|
9
|
+
import { summarizeBatch } from "./upload-batch-progress";
|
|
10
|
+
import { UploadBatchRow } from "./upload-batch-row";
|
|
11
|
+
import type { UploadBatch } from "./upload-store";
|
|
12
|
+
import { useUploadStore } from "./upload-store";
|
|
13
|
+
|
|
14
|
+
function formatMb(bytes: number): string {
|
|
15
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface UploadBatchCardProps {
|
|
19
|
+
batch: UploadBatch;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function UploadBatchCard({ batch }: UploadBatchCardProps) {
|
|
23
|
+
const cancelItem = useUploadStore((s) => s.cancelItem);
|
|
24
|
+
const cancelBatch = useUploadStore((s) => s.cancelBatch);
|
|
25
|
+
const dismissBatch = useUploadStore((s) => s.dismissBatch);
|
|
26
|
+
const summary = summarizeBatch(batch.items);
|
|
27
|
+
const settled = batch.settled;
|
|
28
|
+
const pct = summary.bytesTotal > 0 ? Math.round((summary.bytesLoaded / summary.bytesTotal) * 100) : 0;
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className="w-full overflow-hidden rounded-lg border border-border bg-panel shadow-lg" data-testid="upload-batch-card">
|
|
32
|
+
<div className="flex items-center gap-2 border-b border-border px-3 py-2">
|
|
33
|
+
<div className="min-w-0 flex-1">
|
|
34
|
+
<p className="truncate text-sm font-medium text-text">
|
|
35
|
+
{settled
|
|
36
|
+
? `${summary.doneCount}/${summary.totalCount} uploaded${summary.failedCount > 0 ? `, ${summary.failedCount} failed` : ""}`
|
|
37
|
+
: `Uploading ${summary.totalCount} item${summary.totalCount === 1 ? "" : "s"}… ${pct}%`}
|
|
38
|
+
</p>
|
|
39
|
+
<p className="truncate text-xs text-text-2">{formatMb(summary.bytesLoaded)} / {formatMb(summary.bytesTotal)}</p>
|
|
40
|
+
</div>
|
|
41
|
+
{settled ? (
|
|
42
|
+
<Button variant="ghost" size="sm" className="h-11 shrink-0" onClick={() => dismissBatch(batch.id)}>
|
|
43
|
+
Close
|
|
44
|
+
</Button>
|
|
45
|
+
) : (
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
aria-label="Cancel all"
|
|
49
|
+
onClick={() => cancelBatch(batch.id)}
|
|
50
|
+
className="flex size-11 shrink-0 items-center justify-center rounded-md can-hover:hover:bg-surface-elevated"
|
|
51
|
+
>
|
|
52
|
+
<X className="size-4 text-text-2" />
|
|
53
|
+
</button>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div className="max-h-64 divide-y divide-border overflow-y-auto">
|
|
58
|
+
{batch.items.map((item) => (
|
|
59
|
+
<UploadBatchRow key={item.id} item={item} onCancel={() => cancelItem(batch.id, item.id)} />
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure aggregation over one batch's items — overall bytes and counts for the panel header.
|
|
3
|
+
* Split out of the store/UI so the arithmetic (weighting in-flight bytes, treating "skipped"
|
|
4
|
+
* as complete) is unit-testable without zustand or a browser.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { UploadItem } from "./upload-store";
|
|
8
|
+
|
|
9
|
+
export interface BatchProgressSummary {
|
|
10
|
+
bytesLoaded: number;
|
|
11
|
+
bytesTotal: number;
|
|
12
|
+
/** Uploaded or intentionally skipped — both count toward "how much of the batch is over". */
|
|
13
|
+
doneCount: number;
|
|
14
|
+
failedCount: number;
|
|
15
|
+
totalCount: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function summarizeBatch(items: UploadItem[]): BatchProgressSummary {
|
|
19
|
+
let bytesLoaded = 0;
|
|
20
|
+
let bytesTotal = 0;
|
|
21
|
+
let doneCount = 0;
|
|
22
|
+
let failedCount = 0;
|
|
23
|
+
|
|
24
|
+
for (const item of items) {
|
|
25
|
+
bytesTotal += item.size;
|
|
26
|
+
bytesLoaded += item.state === "done" || item.state === "skipped"
|
|
27
|
+
? item.size
|
|
28
|
+
: Math.min(item.bytesLoaded, item.size);
|
|
29
|
+
if (item.state === "done" || item.state === "skipped") doneCount++;
|
|
30
|
+
if (item.state === "failed" || item.state === "cancelled") failedCount++;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { bytesLoaded, bytesTotal, doneCount, failedCount, totalCount: items.length };
|
|
34
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One file row inside the upload progress panel/sheet: name, relative path, size, progress
|
|
3
|
+
* bar, state, and a per-row Cancel while it is still queued/uploading.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { AlertTriangle, Check, X } from "lucide-react";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
import type { UploadItem } from "./upload-store";
|
|
9
|
+
|
|
10
|
+
function formatBytes(bytes: number): string {
|
|
11
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
12
|
+
const units = ["KB", "MB", "GB"];
|
|
13
|
+
let value = bytes / 1024;
|
|
14
|
+
let unitIndex = 0;
|
|
15
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
16
|
+
value /= 1024;
|
|
17
|
+
unitIndex++;
|
|
18
|
+
}
|
|
19
|
+
return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unitIndex]}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UploadBatchRowProps {
|
|
23
|
+
item: UploadItem;
|
|
24
|
+
onCancel(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function UploadBatchRow({ item, onCancel }: UploadBatchRowProps) {
|
|
28
|
+
const pct = item.size > 0 ? Math.min(100, Math.round((item.bytesLoaded / item.size) * 100)) : 0;
|
|
29
|
+
const active = item.state === "queued" || item.state === "uploading";
|
|
30
|
+
const showsPath = item.relativePath !== item.name;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex flex-col gap-1 px-3 py-2" data-testid="upload-row" data-state={item.state}>
|
|
34
|
+
<div className="flex items-center gap-2">
|
|
35
|
+
<div className="min-w-0 flex-1">
|
|
36
|
+
<p className="truncate text-sm text-text">{item.name}</p>
|
|
37
|
+
{showsPath && <p className="truncate text-xs text-text-2">{item.relativePath}</p>}
|
|
38
|
+
</div>
|
|
39
|
+
<span className="shrink-0 text-xs text-text-2">{formatBytes(item.size)}</span>
|
|
40
|
+
{active ? (
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
aria-label={`Cancel ${item.name}`}
|
|
44
|
+
onClick={onCancel}
|
|
45
|
+
className="flex size-11 shrink-0 items-center justify-center rounded-md can-hover:hover:bg-surface-elevated"
|
|
46
|
+
>
|
|
47
|
+
<X className="size-4 text-text-2" />
|
|
48
|
+
</button>
|
|
49
|
+
) : (
|
|
50
|
+
<span className="flex size-11 shrink-0 items-center justify-center" aria-label={item.state}>
|
|
51
|
+
{item.state === "done" && <Check className="size-4 text-success" />}
|
|
52
|
+
{item.state === "skipped" && <span className="text-xs text-text-2">Skipped</span>}
|
|
53
|
+
{(item.state === "failed" || item.state === "cancelled") && (
|
|
54
|
+
<AlertTriangle className="size-4 text-destructive" />
|
|
55
|
+
)}
|
|
56
|
+
</span>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
{active && (
|
|
61
|
+
<div className="h-1 w-full overflow-hidden rounded-full bg-surface-elevated">
|
|
62
|
+
<div
|
|
63
|
+
className={cn(
|
|
64
|
+
"h-full rounded-full bg-primary transition-[width]",
|
|
65
|
+
item.state === "queued" && "w-0 opacity-40",
|
|
66
|
+
)}
|
|
67
|
+
style={item.state === "uploading" ? { width: `${pct}%` } : undefined}
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{(item.state === "failed" || item.state === "cancelled") && (
|
|
73
|
+
<p className="text-xs text-destructive">
|
|
74
|
+
{item.state === "cancelled" ? "Cancelled" : item.errorMessage ?? "Upload failed"}
|
|
75
|
+
</p>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns an upload failure into a short, human row message for the progress panel. A fresh
|
|
3
|
+
* 409/EEXIST never reaches here — `uploadResolvingCollisions` already resolves that through
|
|
4
|
+
* the collision prompt before the job can fail.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { FsError } from "@/lib/fs-api";
|
|
8
|
+
|
|
9
|
+
export function describeUploadError(error: unknown): string {
|
|
10
|
+
if (error instanceof FsError) {
|
|
11
|
+
if (error.status === 403) return "Not allowed here";
|
|
12
|
+
// Cloudflare's free-tier request-body cap when the app is reached through a tunnel — a
|
|
13
|
+
// client-observed HTTP status, not a code PPM's own upload route ever emits itself.
|
|
14
|
+
if (error.status === 413) return "File too large for the tunnel (100 MB)";
|
|
15
|
+
return error.message || "Upload failed";
|
|
16
|
+
}
|
|
17
|
+
if (error instanceof Error) return "Connection lost";
|
|
18
|
+
return "Upload failed";
|
|
19
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global upload progress surface — mounted once at the app root (`app.tsx`), reading straight
|
|
3
|
+
* from `upload-store.ts`. Replaces the old "Uploading N files… NN%" toast: desktop gets a
|
|
4
|
+
* persistent bottom-right card per batch (a normal fixed panel, not a modal — `z-30` keeps it
|
|
5
|
+
* under the floating-window band's own max of `z-38` and well under Radix's `z-50`); mobile
|
|
6
|
+
* collapses every batch behind a small pinned pill that opens a bottom sheet, so the keyboard
|
|
7
|
+
* dock and thumb zone stay clear.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useState } from "react";
|
|
11
|
+
import { useShallow } from "zustand/react/shallow";
|
|
12
|
+
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
13
|
+
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
14
|
+
import { UploadBatchCard } from "./upload-batch-card";
|
|
15
|
+
import { useUploadStore, type UploadBatch } from "./upload-store";
|
|
16
|
+
|
|
17
|
+
// `useShallow` keeps this selector's returned array reference-stable across renders that
|
|
18
|
+
// don't actually change any batch — the plain `.map().filter()` used before it built a new
|
|
19
|
+
// array identity on every call, and zustand's `useSyncExternalStore` subscription then saw
|
|
20
|
+
// a "changed" snapshot on every single render, looping forever ("Maximum update depth
|
|
21
|
+
// exceeded") the moment any batch existed.
|
|
22
|
+
function useActiveBatches(): UploadBatch[] {
|
|
23
|
+
return useUploadStore(
|
|
24
|
+
useShallow((s) => s.order.map((id) => s.batches[id]).filter((b): b is UploadBatch => b != null)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function UploadProgressPanel() {
|
|
29
|
+
const isMobile = useIsMobile();
|
|
30
|
+
const batches = useActiveBatches();
|
|
31
|
+
const [sheetOpen, setSheetOpen] = useState(false);
|
|
32
|
+
|
|
33
|
+
if (batches.length === 0) return null;
|
|
34
|
+
|
|
35
|
+
if (!isMobile) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
className="fixed bottom-4 right-4 z-30 flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2"
|
|
39
|
+
data-testid="upload-progress-panel"
|
|
40
|
+
>
|
|
41
|
+
{batches.map((batch) => (
|
|
42
|
+
<UploadBatchCard key={batch.id} batch={batch} />
|
|
43
|
+
))}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const uploading = batches.filter((b) => !b.settled).length;
|
|
49
|
+
const label = uploading > 0
|
|
50
|
+
? `Uploading ${batches.reduce((n, b) => n + b.items.length, 0)}…`
|
|
51
|
+
: "Uploads finished";
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
onClick={() => setSheetOpen(true)}
|
|
58
|
+
data-testid="upload-progress-pill"
|
|
59
|
+
className="fixed bottom-20 right-3 z-30 flex h-11 items-center gap-2 rounded-full border border-border bg-panel px-4 text-sm text-text shadow-lg"
|
|
60
|
+
>
|
|
61
|
+
{label}
|
|
62
|
+
</button>
|
|
63
|
+
<BottomSheet open={sheetOpen} onClose={() => setSheetOpen(false)} className="max-h-[70vh] overflow-y-auto p-2">
|
|
64
|
+
<div className="flex flex-col gap-2 px-2 pb-2">
|
|
65
|
+
{batches.map((batch) => (
|
|
66
|
+
<UploadBatchCard key={batch.id} batch={batch} />
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
</BottomSheet>
|
|
70
|
+
</>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global upload progress state — batches of in-flight uploads (a drop or a picker pick), each
|
|
3
|
+
* with per-file rows. Backs the persistent upload panel that replaced the old
|
|
4
|
+
* "Uploading N files… NN%" toast, which had no room for per-file outcomes or a cancel button.
|
|
5
|
+
*
|
|
6
|
+
* Cancel hooks are kept in a side map (`cancellers`), not on the serializable item/batch
|
|
7
|
+
* state — an `AbortController.abort` closure has no business living next to plain data a
|
|
8
|
+
* test might snapshot or log.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { create } from "zustand";
|
|
12
|
+
|
|
13
|
+
export type UploadItemState = "queued" | "uploading" | "done" | "skipped" | "failed" | "cancelled";
|
|
14
|
+
|
|
15
|
+
export interface UploadItem {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
relativePath: string;
|
|
19
|
+
size: number;
|
|
20
|
+
bytesLoaded: number;
|
|
21
|
+
state: UploadItemState;
|
|
22
|
+
errorMessage?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UploadBatch {
|
|
26
|
+
id: string;
|
|
27
|
+
dstDir: string;
|
|
28
|
+
items: UploadItem[];
|
|
29
|
+
/** True once every item has left "queued"/"uploading" — the panel swaps Cancel for Close. */
|
|
30
|
+
settled: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type NewUploadItem = Pick<UploadItem, "id" | "name" | "relativePath" | "size">;
|
|
34
|
+
|
|
35
|
+
function deriveSettled(items: UploadItem[]): boolean {
|
|
36
|
+
return items.every((i) => i.state !== "queued" && i.state !== "uploading");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface UploadStoreState {
|
|
40
|
+
order: string[];
|
|
41
|
+
batches: Record<string, UploadBatch>;
|
|
42
|
+
cancellers: Record<string, () => void>;
|
|
43
|
+
|
|
44
|
+
addBatch(id: string, dstDir: string, items: NewUploadItem[]): void;
|
|
45
|
+
registerCanceller(batchId: string, itemId: string, cancel: () => void): void;
|
|
46
|
+
setItemState(batchId: string, itemId: string, state: UploadItemState, errorMessage?: string): void;
|
|
47
|
+
setItemProgress(batchId: string, itemId: string, bytesLoaded: number): void;
|
|
48
|
+
cancelItem(batchId: string, itemId: string): void;
|
|
49
|
+
cancelBatch(batchId: string): void;
|
|
50
|
+
dismissBatch(batchId: string): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const useUploadStore = create<UploadStoreState>((set, get) => ({
|
|
54
|
+
order: [],
|
|
55
|
+
batches: {},
|
|
56
|
+
cancellers: {},
|
|
57
|
+
|
|
58
|
+
addBatch: (id, dstDir, items) => {
|
|
59
|
+
const batch: UploadBatch = {
|
|
60
|
+
id,
|
|
61
|
+
dstDir,
|
|
62
|
+
items: items.map((item) => ({ ...item, bytesLoaded: 0, state: "queued" })),
|
|
63
|
+
settled: false,
|
|
64
|
+
};
|
|
65
|
+
set((s) => ({ order: [...s.order, id], batches: { ...s.batches, [id]: batch } }));
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
registerCanceller: (batchId, itemId, cancel) => {
|
|
69
|
+
set((s) => ({ cancellers: { ...s.cancellers, [`${batchId}:${itemId}`]: cancel } }));
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
setItemState: (batchId, itemId, state, errorMessage) => {
|
|
73
|
+
set((s) => {
|
|
74
|
+
const batch = s.batches[batchId];
|
|
75
|
+
if (!batch) return s;
|
|
76
|
+
const items = batch.items.map((i) => (i.id === itemId ? { ...i, state, errorMessage } : i));
|
|
77
|
+
return { batches: { ...s.batches, [batchId]: { ...batch, items, settled: deriveSettled(items) } } };
|
|
78
|
+
});
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
setItemProgress: (batchId, itemId, bytesLoaded) => {
|
|
82
|
+
set((s) => {
|
|
83
|
+
const batch = s.batches[batchId];
|
|
84
|
+
if (!batch) return s;
|
|
85
|
+
const items = batch.items.map((i) => (i.id === itemId ? { ...i, bytesLoaded } : i));
|
|
86
|
+
return { batches: { ...s.batches, [batchId]: { ...batch, items } } };
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
// Cancelling only ever *asks* the in-flight job to stop (abort the XHR, or skip a queued
|
|
91
|
+
// job before it starts) — the resulting "cancelled" state is set by the job itself once its
|
|
92
|
+
// promise actually rejects, not here, so state and reality never disagree.
|
|
93
|
+
cancelItem: (batchId, itemId) => {
|
|
94
|
+
get().cancellers[`${batchId}:${itemId}`]?.();
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
cancelBatch: (batchId) => {
|
|
98
|
+
const batch = get().batches[batchId];
|
|
99
|
+
if (!batch) return;
|
|
100
|
+
for (const item of batch.items) get().cancelItem(batchId, item.id);
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
dismissBatch: (batchId) => {
|
|
104
|
+
set((s) => {
|
|
105
|
+
const { [batchId]: _removed, ...batches } = s.batches;
|
|
106
|
+
return { batches, order: s.order.filter((id) => id !== batchId) };
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
}));
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { useRef, type ChangeEvent, type ReactNode } from "react";
|
|
11
|
+
import { capturePickedFiles } from "./capture-picked-files";
|
|
11
12
|
import type { DroppedEntry } from "./collect-dropped-entries";
|
|
12
13
|
|
|
13
14
|
export interface UploadPicker {
|
|
@@ -30,11 +31,12 @@ export function useUploadPicker(onFiles: (entries: DroppedEntry[]) => void): Upl
|
|
|
30
31
|
typeof document !== "undefined" && "webkitdirectory" in document.createElement("input");
|
|
31
32
|
|
|
32
33
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
// Captures the live FileList into a plain array BEFORE clearing `value` (see
|
|
35
|
+
// `capture-picked-files.ts`) — clearing first, so picking the exact same file/folder
|
|
36
|
+
// again still fires `onChange`, would otherwise race Chromium emptying the list itself.
|
|
37
|
+
const picked = capturePickedFiles(e.target.files, () => { e.target.value = ""; });
|
|
38
|
+
if (picked.length === 0) return;
|
|
39
|
+
const entries: DroppedEntry[] = picked.map((file) => ({
|
|
38
40
|
file,
|
|
39
41
|
// The folder picker sets `webkitRelativePath` ("folder/sub/file.txt"); a plain file
|
|
40
42
|
// picker leaves it empty, so the bare name is the relative path.
|
|
@@ -29,8 +29,14 @@ export function uploadFileXhr(
|
|
|
29
29
|
file: File,
|
|
30
30
|
overwrite: boolean,
|
|
31
31
|
onProgress: (progress: UploadProgress) => void,
|
|
32
|
+
signal?: AbortSignal,
|
|
32
33
|
): Promise<{ path: string; size: number }> {
|
|
33
34
|
return new Promise((resolve, reject) => {
|
|
35
|
+
if (signal?.aborted) {
|
|
36
|
+
reject(new DOMException("Upload aborted", "AbortError"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
const xhr = new XMLHttpRequest();
|
|
35
41
|
xhr.open("PUT", fsApi.uploadUrl(path, overwrite));
|
|
36
42
|
const token = getAuthToken();
|
|
@@ -41,7 +47,12 @@ export function uploadFileXhr(
|
|
|
41
47
|
if (e.lengthComputable) onProgress({ loaded: e.loaded, total: e.total });
|
|
42
48
|
};
|
|
43
49
|
|
|
50
|
+
const onAbortSignal = () => xhr.abort();
|
|
51
|
+
signal?.addEventListener("abort", onAbortSignal);
|
|
52
|
+
const detach = () => signal?.removeEventListener("abort", onAbortSignal);
|
|
53
|
+
|
|
44
54
|
xhr.onload = () => {
|
|
55
|
+
detach();
|
|
45
56
|
let json: UploadResponseBody = {};
|
|
46
57
|
try {
|
|
47
58
|
json = JSON.parse(xhr.responseText);
|
|
@@ -54,8 +65,8 @@ export function uploadFileXhr(
|
|
|
54
65
|
}
|
|
55
66
|
reject(new FsError(json.error ?? `HTTP ${xhr.status}`, json.code ?? "EUNKNOWN", xhr.status));
|
|
56
67
|
};
|
|
57
|
-
xhr.onerror = () => reject(new Error("Network error during upload"));
|
|
58
|
-
xhr.onabort = () => reject(new DOMException("Upload aborted", "AbortError"));
|
|
68
|
+
xhr.onerror = () => { detach(); reject(new Error("Network error during upload")); };
|
|
69
|
+
xhr.onabort = () => { detach(); reject(new DOMException("Upload aborted", "AbortError")); };
|
|
59
70
|
|
|
60
71
|
xhr.send(file);
|
|
61
72
|
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{K as e}from"./vendor-mermaid-Bg4vE8QA.js";export{e as createArchitectureServices};
|