@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
package/src/web/app.tsx
CHANGED
|
@@ -35,6 +35,7 @@ import { ExtensionQuickPick } from "@/components/extensions/extension-quickpick"
|
|
|
35
35
|
import { ExtensionInputBox } from "@/components/extensions/extension-inputbox";
|
|
36
36
|
import { useExtensionWs } from "@/hooks/use-extension-ws";
|
|
37
37
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
38
|
+
import { UploadProgressPanel } from "@/components/os-explorer/upload/upload-progress-panel";
|
|
38
39
|
import { cn } from "@/lib/utils";
|
|
39
40
|
|
|
40
41
|
// Lazy: the explorer feature (views, actions, skins, icon map) is the same heavy bundle the
|
|
@@ -366,6 +367,9 @@ export function App() {
|
|
|
366
367
|
{/* Connection lost overlay — shown when API unreachable for >15s */}
|
|
367
368
|
<ConnectionLostOverlay />
|
|
368
369
|
|
|
370
|
+
{/* Explorer upload progress — bottom-right card (desktop) or pill + sheet (mobile) */}
|
|
371
|
+
<UploadProgressPanel />
|
|
372
|
+
|
|
369
373
|
{/* Toast notifications. Bottom-left collides with the mobile dock's
|
|
370
374
|
terminal toolbar — a toast lands directly on the buttons that raised
|
|
371
375
|
it — so phones get them out of the thumb zone instead. */}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure serializing queue behind the collision-prompt dialog.
|
|
3
|
+
*
|
|
4
|
+
* Uploads and copy/move transfers both call `ctx.resolve()` — sometimes from several jobs
|
|
5
|
+
* running at once (`uploadEntries`' bounded-concurrency queue). Before this module, the
|
|
6
|
+
* dialog was a single React state slot: the second concurrent `resolve()` call silently
|
|
7
|
+
* replaced whichever request was already showing, and the first job's promise never
|
|
8
|
+
* settled — the queue stalled forever with a stuck "Uploading…" toast.
|
|
9
|
+
*
|
|
10
|
+
* Every request now queues here and only one shows at a time. "Apply to all" (set from the
|
|
11
|
+
* dialog when a choice is made) short-circuits every request still queued *in the same
|
|
12
|
+
* batch* without opening another prompt — see `startBatch`/`endBatch`.
|
|
13
|
+
*
|
|
14
|
+
* Pure and DOM-free on purpose, like `upload-queue.ts`: `use-collision-prompt.ts` is the
|
|
15
|
+
* thin React hook wrapping this for re-render notifications.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export interface CollisionRequest {
|
|
19
|
+
name: string;
|
|
20
|
+
destination: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type CollisionChoice = "replace" | "keep-both" | "skip";
|
|
24
|
+
|
|
25
|
+
interface QueueItem {
|
|
26
|
+
request: CollisionRequest;
|
|
27
|
+
settle(choice: CollisionChoice): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CollisionQueueSnapshot {
|
|
31
|
+
request: CollisionRequest;
|
|
32
|
+
/** How many more requests are queued behind this one — the dialog's "…and N more". */
|
|
33
|
+
remaining: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface QueueSnapshot<TRequest> {
|
|
37
|
+
request: TRequest;
|
|
38
|
+
remaining: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Generic one-at-a-time prompt queue: every `request()` call queues behind whatever is
|
|
43
|
+
* already showing, `snapshot()` exposes only the head, and `choose()` answers it and
|
|
44
|
+
* advances. Reused by anything that needs a single dialog serialized across concurrent
|
|
45
|
+
* callers — `CollisionPromptQueue` below layers "apply to all" sticky choices on top of one
|
|
46
|
+
* of these for collisions; the rarer permanent-overwrite confirm (no sticky concept needed)
|
|
47
|
+
* uses one directly via `use-permanent-overwrite-prompt.ts`. Both prompts used to be a bare
|
|
48
|
+
* `useState` slot each, with the same "second concurrent caller overwrites the first" race.
|
|
49
|
+
*/
|
|
50
|
+
export class SerialPromptQueue<TRequest, TChoice> {
|
|
51
|
+
private queue: Array<{ request: TRequest; settle(choice: TChoice): void }> = [];
|
|
52
|
+
|
|
53
|
+
/** Called whenever the queue or the visible request changes, so a React wrapper can
|
|
54
|
+
* re-render. Never called synchronously from inside the constructor. */
|
|
55
|
+
constructor(private readonly onChange: () => void) {}
|
|
56
|
+
|
|
57
|
+
request(request: TRequest): Promise<TChoice> {
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
this.queue.push({ request, settle: resolve });
|
|
60
|
+
this.onChange();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
snapshot(): QueueSnapshot<TRequest> | null {
|
|
65
|
+
const head = this.queue[0];
|
|
66
|
+
return head ? { request: head.request, remaining: this.queue.length - 1 } : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Answers the head request (if any) with `choice` and advances to the next. */
|
|
70
|
+
choose(choice: TChoice): void {
|
|
71
|
+
const item = this.queue.shift();
|
|
72
|
+
if (!item) return;
|
|
73
|
+
item.settle(choice);
|
|
74
|
+
this.onChange();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Removes and answers every request currently queued (including the head) with the same
|
|
78
|
+
* `choice` — how "apply to all" resolves the rest of a batch in one step. */
|
|
79
|
+
drain(choice: TChoice): void {
|
|
80
|
+
const rest = this.queue.splice(0);
|
|
81
|
+
if (rest.length === 0) return;
|
|
82
|
+
for (const item of rest) item.settle(choice);
|
|
83
|
+
this.onChange();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export class CollisionPromptQueue {
|
|
88
|
+
private readonly core: SerialPromptQueue<CollisionRequest, CollisionChoice>;
|
|
89
|
+
private stickyChoice: CollisionChoice | null = null;
|
|
90
|
+
private activeBatches = 0;
|
|
91
|
+
|
|
92
|
+
constructor(onChange: () => void) {
|
|
93
|
+
this.core = new SerialPromptQueue(onChange);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Marks the start of one batch of parallel `resolve()` calls that should share a single
|
|
98
|
+
* "apply to all" choice (one drop, one paste, one drag). Batches can overlap (a paste
|
|
99
|
+
* started while a drag is still resolving); "apply to all" only resets once every
|
|
100
|
+
* overlapping batch has called `endBatch`.
|
|
101
|
+
*/
|
|
102
|
+
startBatch(): void {
|
|
103
|
+
this.activeBatches++;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
endBatch(): void {
|
|
107
|
+
this.activeBatches = Math.max(0, this.activeBatches - 1);
|
|
108
|
+
if (this.activeBatches === 0) this.stickyChoice = null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Raises one collision. Resolves immediately (no dialog) if a prior "apply to all" choice
|
|
112
|
+
* is still sticky for the current batch; otherwise queues behind whatever is showing. */
|
|
113
|
+
request(request: CollisionRequest): Promise<CollisionChoice> {
|
|
114
|
+
if (this.stickyChoice) return Promise.resolve(this.stickyChoice);
|
|
115
|
+
return this.core.request(request);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The request currently on screen, or `null` when nothing is queued. */
|
|
119
|
+
snapshot(): CollisionQueueSnapshot | null {
|
|
120
|
+
return this.core.snapshot();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Answers the request on screen. When `applyToAll` is set, the choice also settles every
|
|
124
|
+
* request still queued right now, and sticks for any that arrive before the batch ends. */
|
|
125
|
+
choose(choice: CollisionChoice, applyToAll: boolean): void {
|
|
126
|
+
if (applyToAll) {
|
|
127
|
+
this.stickyChoice = choice;
|
|
128
|
+
this.core.drain(choice);
|
|
129
|
+
} else {
|
|
130
|
+
this.core.choose(choice);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -33,6 +33,13 @@ export interface TransferContext {
|
|
|
33
33
|
* delete would contradict what the user just agreed to. Returning false skips the item.
|
|
34
34
|
*/
|
|
35
35
|
confirmPermanentOverwrite(name: string): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Brackets one batch of parallel `resolve()` calls sharing this context's collision prompt
|
|
38
|
+
* (one paste, one drag, one upload), so "Apply to all" scopes to just that batch. Optional —
|
|
39
|
+
* a caller with no concurrent collisions (or a test building a bare context) can omit them.
|
|
40
|
+
*/
|
|
41
|
+
startBatch?(): void;
|
|
42
|
+
endBatch?(): void;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
export interface TransferResult {
|
|
@@ -56,6 +63,28 @@ export async function transfer(
|
|
|
56
63
|
const result: TransferResult = { succeeded: 0, skipped: 0, failed: 0 };
|
|
57
64
|
const touched = new Set<string>([dstDir]);
|
|
58
65
|
|
|
66
|
+
// `transfer()` itself resolves collisions one source at a time, but two *calls* to it can
|
|
67
|
+
// overlap (a paste fired while a drag-drop transfer is still resolving) sharing the same
|
|
68
|
+
// dialog — bracket this call so "Apply to all" only ever covers its own sources.
|
|
69
|
+
ctx.startBatch?.();
|
|
70
|
+
try {
|
|
71
|
+
await transferSources(sources, dstDir, op, ctx, result, touched);
|
|
72
|
+
} finally {
|
|
73
|
+
ctx.endBatch?.();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
fsChanged(...touched);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function transferSources(
|
|
81
|
+
sources: string[],
|
|
82
|
+
dstDir: string,
|
|
83
|
+
op: "copy" | "move",
|
|
84
|
+
ctx: TransferContext,
|
|
85
|
+
result: TransferResult,
|
|
86
|
+
touched: Set<string>,
|
|
87
|
+
): Promise<void> {
|
|
59
88
|
for (const source of sources) {
|
|
60
89
|
const name = source.split(/[/\\]/).filter(Boolean).pop() ?? source;
|
|
61
90
|
const sourceDir = dirnameOf(source, ctx.sep);
|
|
@@ -155,9 +184,6 @@ export async function transfer(
|
|
|
155
184
|
toast.error(`${name} was removed but the replacement could not be written`, { description: errorDescription(e) });
|
|
156
185
|
}
|
|
157
186
|
}
|
|
158
|
-
|
|
159
|
-
fsChanged(...touched);
|
|
160
|
-
return result;
|
|
161
187
|
}
|
|
162
188
|
|
|
163
189
|
/** Put absolute paths on the shared clipboard. `origin` lets the tree keep its own routes. */
|
|
@@ -2,16 +2,23 @@
|
|
|
2
2
|
* Turns a dropped/picked file batch into `PUT /api/fs/upload` calls, streamed through
|
|
3
3
|
* `uploadFileXhr` with bounded concurrency. Mirrors `transfer()` in
|
|
4
4
|
* `explorer-actions-clipboard.ts`: the same collision prompt (`ctx.resolve`), the same
|
|
5
|
-
* trash-first "Replace" via
|
|
6
|
-
* batch settles — drag-to-upload and the picker both funnel through this one
|
|
5
|
+
* trash-first "Replace" (delete via `/api/fs/delete`, then upload) and a `fsChanged` refresh
|
|
6
|
+
* once the batch settles — drag-to-upload and the picker both funnel through this one
|
|
7
|
+
* function.
|
|
8
|
+
*
|
|
9
|
+
* Per-file state lives in `upload-store.ts`, which backs the persistent progress panel that
|
|
10
|
+
* replaced the old "Uploading N files… NN%" toast — that toast had no room for a cancel
|
|
11
|
+
* button or per-file outcomes, and its single collision-dialog slot is the bug this module
|
|
12
|
+
* used to have (see `use-collision-prompt.ts`).
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
|
-
import {
|
|
10
|
-
import { FsError } from "@/lib/fs-api";
|
|
15
|
+
import { fsApi, FsError } from "@/lib/fs-api";
|
|
11
16
|
import { uploadFileXhr } from "@/lib/fs-upload-xhr";
|
|
12
17
|
import { dirnameOf, joinPath } from "../format-file-meta";
|
|
13
18
|
import { fsChanged } from "../explorer-store";
|
|
14
19
|
import type { DroppedEntry } from "../upload/collect-dropped-entries";
|
|
20
|
+
import { describeUploadError } from "../upload/upload-error-message";
|
|
21
|
+
import { useUploadStore } from "../upload/upload-store";
|
|
15
22
|
import { runUploadQueue, type UploadJob } from "../upload/upload-queue";
|
|
16
23
|
import type { TransferContext } from "./explorer-actions-clipboard";
|
|
17
24
|
import { freeName } from "./explorer-actions-transfer-helpers";
|
|
@@ -35,34 +42,42 @@ async function uploadResolvingCollisions(
|
|
|
35
42
|
dstDir: string,
|
|
36
43
|
sep: string,
|
|
37
44
|
ctx: TransferContext,
|
|
45
|
+
signal: AbortSignal,
|
|
38
46
|
onProgress: (bytesLoaded: number) => void,
|
|
39
|
-
): Promise<
|
|
47
|
+
): Promise<"done" | "skipped"> {
|
|
40
48
|
const destination = destinationFor(dstDir, entry.relativePath, sep);
|
|
41
49
|
try {
|
|
42
|
-
await uploadFileXhr(destination, entry.file, false, (p) => onProgress(p.loaded));
|
|
43
|
-
return;
|
|
50
|
+
await uploadFileXhr(destination, entry.file, false, (p) => onProgress(p.loaded), signal);
|
|
51
|
+
return "done";
|
|
44
52
|
} catch (e) {
|
|
45
53
|
if (!(e instanceof FsError) || e.code !== "EEXIST") throw e;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
const name = nameOf(entry.relativePath);
|
|
49
57
|
const choice = await ctx.resolve({ name, destination });
|
|
50
|
-
if (choice === "skip") return;
|
|
58
|
+
if (choice === "skip") return "skipped";
|
|
51
59
|
if (choice === "keep-both") {
|
|
52
60
|
const containingDir = dirnameOf(destination, sep);
|
|
53
61
|
const alternative = await freeName(containingDir, name, sep);
|
|
54
62
|
if (!alternative) throw new Error("No free name available");
|
|
55
|
-
await uploadFileXhr(joinPath(containingDir, alternative, sep), entry.file, false, (p) => onProgress(p.loaded));
|
|
56
|
-
return;
|
|
63
|
+
await uploadFileXhr(joinPath(containingDir, alternative, sep), entry.file, false, (p) => onProgress(p.loaded), signal);
|
|
64
|
+
return "done";
|
|
57
65
|
}
|
|
58
|
-
// Replace: the server upload route itself makes the overwrite atomic (tmp file + rename),
|
|
59
|
-
// so unlike the copy/move `transfer()` there is no separate trash-then-write step here.
|
|
60
|
-
await uploadFileXhr(destination, entry.file, true, (p) => onProgress(p.loaded));
|
|
61
|
-
}
|
|
62
66
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
// Replace: trash the existing entry first, same as copy/move's `transfer()` — the dialog's
|
|
68
|
+
// "moves the existing item to the Trash first" copy has to hold for every caller of it.
|
|
69
|
+
try {
|
|
70
|
+
await fsApi.remove(destination, false);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (!(e instanceof FsError) || e.code !== "NO_TRASH") throw e;
|
|
73
|
+
// No trash backend on this host — ask before doing something permanent instead of
|
|
74
|
+
// silently downgrading to it (mirrors `transfer()`'s own NO_TRASH handling).
|
|
75
|
+
const proceed = await ctx.confirmPermanentOverwrite(name);
|
|
76
|
+
if (!proceed) return "skipped";
|
|
77
|
+
await fsApi.remove(destination, true);
|
|
78
|
+
}
|
|
79
|
+
await uploadFileXhr(destination, entry.file, true, (p) => onProgress(p.loaded), signal);
|
|
80
|
+
return "done";
|
|
66
81
|
}
|
|
67
82
|
|
|
68
83
|
export async function uploadEntries(
|
|
@@ -73,24 +88,64 @@ export async function uploadEntries(
|
|
|
73
88
|
): Promise<void> {
|
|
74
89
|
if (entries.length === 0) return;
|
|
75
90
|
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
const batchId = crypto.randomUUID();
|
|
92
|
+
const store = useUploadStore.getState();
|
|
93
|
+
store.addBatch(
|
|
94
|
+
batchId,
|
|
95
|
+
dstDir,
|
|
96
|
+
entries.map((entry, index) => ({
|
|
97
|
+
id: String(index),
|
|
98
|
+
name: nameOf(entry.relativePath),
|
|
99
|
+
relativePath: entry.relativePath,
|
|
100
|
+
size: entry.file.size,
|
|
101
|
+
})),
|
|
102
|
+
);
|
|
82
103
|
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
|
|
104
|
+
const controllers = entries.map((_, index) => {
|
|
105
|
+
const controller = new AbortController();
|
|
106
|
+
store.registerCanceller(batchId, String(index), () => controller.abort());
|
|
107
|
+
return controller;
|
|
86
108
|
});
|
|
87
109
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
110
|
+
// Serializes this batch's collision prompts through the shared queue (see
|
|
111
|
+
// `use-collision-prompt.ts`) and scopes "Apply to all" to just these entries.
|
|
112
|
+
ctx.startBatch?.();
|
|
113
|
+
try {
|
|
114
|
+
const jobs: UploadJob<void>[] = entries.map((entry, index) => {
|
|
115
|
+
const id = String(index);
|
|
116
|
+
const controller = controllers[index]!;
|
|
117
|
+
return {
|
|
118
|
+
id,
|
|
119
|
+
size: entry.file.size,
|
|
120
|
+
run: async (onProgress) => {
|
|
121
|
+
if (controller.signal.aborted) {
|
|
122
|
+
store.setItemState(batchId, id, "cancelled");
|
|
123
|
+
throw new DOMException("Upload aborted", "AbortError");
|
|
124
|
+
}
|
|
125
|
+
store.setItemState(batchId, id, "uploading");
|
|
126
|
+
try {
|
|
127
|
+
const outcome = await uploadResolvingCollisions(
|
|
128
|
+
entry, dstDir, sep, ctx, controller.signal,
|
|
129
|
+
(loaded) => {
|
|
130
|
+
onProgress(loaded);
|
|
131
|
+
store.setItemProgress(batchId, id, loaded);
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
store.setItemState(batchId, id, outcome === "skipped" ? "skipped" : "done");
|
|
135
|
+
} catch (e) {
|
|
136
|
+
const cancelled = e instanceof DOMException && e.name === "AbortError";
|
|
137
|
+
store.setItemState(batchId, id, cancelled ? "cancelled" : "failed", cancelled ? undefined : describeUploadError(e));
|
|
138
|
+
throw e;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// The aggregate progress callback is unused here — the panel reads per-item state
|
|
145
|
+
// straight from the store, updated above as each job settles.
|
|
146
|
+
await runUploadQueue(jobs, CONCURRENCY, () => {});
|
|
147
|
+
} finally {
|
|
148
|
+
ctx.endBatch?.();
|
|
149
|
+
fsChanged(dstDir);
|
|
95
150
|
}
|
|
96
151
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React wrapper around `CollisionPromptQueue`. Owns one queue instance per window/surface
|
|
3
|
+
* (`use-explorer-actions.ts` and `use-drop-transfer.tsx` each hold their own), exposing the
|
|
4
|
+
* dialog-facing state plus the `resolve`/`startBatch`/`endBatch` trio that `TransferContext`
|
|
5
|
+
* plugs into `transfer()` and `uploadEntries()`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useMemo, useRef, useState } from "react";
|
|
9
|
+
import { CollisionPromptQueue, type CollisionChoice, type CollisionRequest } from "./collision-prompt-queue";
|
|
10
|
+
|
|
11
|
+
export interface CollisionPromptState extends CollisionRequest {
|
|
12
|
+
/** How many more collisions are queued behind this one. */
|
|
13
|
+
remaining: number;
|
|
14
|
+
applyToAll: boolean;
|
|
15
|
+
setApplyToAll(value: boolean): void;
|
|
16
|
+
resolve(choice: CollisionChoice): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CollisionPrompt {
|
|
20
|
+
/** Render-ready state for the dialog, or `null` when nothing is queued. */
|
|
21
|
+
state: CollisionPromptState | null;
|
|
22
|
+
resolve(request: CollisionRequest): Promise<CollisionChoice>;
|
|
23
|
+
startBatch(): void;
|
|
24
|
+
endBatch(): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function useCollisionPrompt(): CollisionPrompt {
|
|
28
|
+
const [tick, setTick] = useState(0);
|
|
29
|
+
const [applyToAll, setApplyToAll] = useState(false);
|
|
30
|
+
const queueRef = useRef<CollisionPromptQueue | null>(null);
|
|
31
|
+
if (!queueRef.current) {
|
|
32
|
+
queueRef.current = new CollisionPromptQueue(() => setTick((n) => n + 1));
|
|
33
|
+
}
|
|
34
|
+
const queue = queueRef.current;
|
|
35
|
+
|
|
36
|
+
return useMemo(() => {
|
|
37
|
+
const snapshot = queue.snapshot();
|
|
38
|
+
return {
|
|
39
|
+
state: snapshot && {
|
|
40
|
+
...snapshot.request,
|
|
41
|
+
remaining: snapshot.remaining,
|
|
42
|
+
applyToAll,
|
|
43
|
+
setApplyToAll,
|
|
44
|
+
resolve: (choice) => {
|
|
45
|
+
queue.choose(choice, applyToAll);
|
|
46
|
+
setApplyToAll(false);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
resolve: (request) => queue.request(request),
|
|
50
|
+
startBatch: () => queue.startBatch(),
|
|
51
|
+
endBatch: () => queue.endBatch(),
|
|
52
|
+
};
|
|
53
|
+
// `tick` has no direct use below — it exists purely to force this memo to recompute
|
|
54
|
+
// whenever the queue notifies of a change (new request queued, or one resolved).
|
|
55
|
+
}, [queue, applyToAll, tick]);
|
|
56
|
+
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Binds the action modules to one window's slice and owns the state of
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Prompts are modelled as promises resolved by the dialog, so the action modules stay
|
|
7
|
-
* plain async functions with no React inside them.
|
|
2
|
+
* Binds the action modules to one window's slice and owns the state of its interruptions:
|
|
3
|
+
* the collision prompt (`use-collision-prompt.ts`), the permanent-delete confirmation and the
|
|
4
|
+
* properties dialog. Prompts are modelled as promises resolved by the dialog, so the action
|
|
5
|
+
* modules stay plain async functions with no React inside them.
|
|
8
6
|
*/
|
|
9
7
|
|
|
10
8
|
import { useCallback, useMemo, useRef, useState, type ReactNode } from "react";
|
|
@@ -15,12 +13,11 @@ import { useExplorerPinsStore } from "../explorer-pins-store";
|
|
|
15
13
|
import { useExplorerStore, type ExplorerSlice } from "../explorer-store";
|
|
16
14
|
import type { ExplorerNavigation } from "../use-explorer-navigation";
|
|
17
15
|
import { copyTextLines, downloadEntries, openEntryInPpm, openPathInNewWindow } from "./explorer-actions-open";
|
|
18
|
-
import {
|
|
19
|
-
pasteInto, setClipboardPaths, transfer,
|
|
20
|
-
type CollisionChoice, type CollisionRequest,
|
|
21
|
-
} from "./explorer-actions-clipboard";
|
|
16
|
+
import { pasteInto, setClipboardPaths, transfer } from "./explorer-actions-clipboard";
|
|
22
17
|
import { createEntry, deleteEntries, renameEntry, validateEntryName } from "./explorer-actions-mutate";
|
|
23
18
|
import { useExplorerUploadActions } from "./use-explorer-upload-actions";
|
|
19
|
+
import { useCollisionPrompt, type CollisionPromptState } from "./use-collision-prompt";
|
|
20
|
+
import { usePermanentOverwritePrompt, type PermanentOverwritePromptState } from "./use-permanent-overwrite-prompt";
|
|
24
21
|
import type { DroppedEntry } from "../upload/collect-dropped-entries";
|
|
25
22
|
|
|
26
23
|
export interface ExplorerActions {
|
|
@@ -52,10 +49,10 @@ export interface ExplorerActions {
|
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
export interface ExplorerDialogState {
|
|
55
|
-
collision:
|
|
52
|
+
collision: CollisionPromptState | null;
|
|
56
53
|
pendingDelete: { paths: string[]; names: string[] } | null;
|
|
57
54
|
/** "Replace" hit a host with no trash backend — confirm a permanent overwrite instead. */
|
|
58
|
-
permanentOverwrite:
|
|
55
|
+
permanentOverwrite: PermanentOverwritePromptState | null;
|
|
59
56
|
properties: FsEntry | null;
|
|
60
57
|
inlineError: string | null;
|
|
61
58
|
closeDelete(): void;
|
|
@@ -72,43 +69,31 @@ export function useExplorerActions(
|
|
|
72
69
|
platform: string | undefined,
|
|
73
70
|
): { actions: ExplorerActions; dialogs: ExplorerDialogState } {
|
|
74
71
|
const patch = useExplorerStore((s) => s.patch);
|
|
75
|
-
const
|
|
72
|
+
const collisionPrompt = useCollisionPrompt();
|
|
73
|
+
const permanentOverwritePrompt = usePermanentOverwritePrompt();
|
|
76
74
|
const [pendingDelete, setPendingDelete] = useState<ExplorerDialogState["pendingDelete"]>(null);
|
|
77
|
-
const [permanentOverwrite, setPermanentOverwrite] = useState<ExplorerDialogState["permanentOverwrite"]>(null);
|
|
78
75
|
const [properties, setProperties] = useState<FsEntry | null>(null);
|
|
79
76
|
const [inlineError, setInlineError] = useState<string | null>(null);
|
|
80
77
|
|
|
81
78
|
const sliceRef = useRef(slice);
|
|
82
79
|
sliceRef.current = slice;
|
|
83
80
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
new Promise<boolean>((resolve) => {
|
|
101
|
-
setPermanentOverwrite({
|
|
102
|
-
name,
|
|
103
|
-
resolve: (proceed) => {
|
|
104
|
-
setPermanentOverwrite(null);
|
|
105
|
-
resolve(proceed);
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
}),
|
|
109
|
-
}),
|
|
110
|
-
[],
|
|
111
|
-
);
|
|
81
|
+
// Read through refs so `context` keeps a stable identity ([]-deps) even though both
|
|
82
|
+
// prompts' own functions change identity on every queued/resolved request.
|
|
83
|
+
const collisionPromptRef = useRef(collisionPrompt);
|
|
84
|
+
collisionPromptRef.current = collisionPrompt;
|
|
85
|
+
const permanentOverwritePromptRef = useRef(permanentOverwritePrompt);
|
|
86
|
+
permanentOverwritePromptRef.current = permanentOverwritePrompt;
|
|
87
|
+
|
|
88
|
+
const context = useMemo(() => ({
|
|
89
|
+
get sep() {
|
|
90
|
+
return sliceRef.current?.sep ?? "/";
|
|
91
|
+
},
|
|
92
|
+
resolve: (request: Parameters<typeof collisionPrompt.resolve>[0]) => collisionPromptRef.current.resolve(request),
|
|
93
|
+
startBatch: () => collisionPromptRef.current.startBatch(),
|
|
94
|
+
endBatch: () => collisionPromptRef.current.endBatch(),
|
|
95
|
+
confirmPermanentOverwrite: (name: string) => permanentOverwritePromptRef.current.confirm(name),
|
|
96
|
+
}), []);
|
|
112
97
|
|
|
113
98
|
const currentDir = () => sliceRef.current?.path ?? "";
|
|
114
99
|
const upload = useExplorerUploadActions(context, currentDir);
|
|
@@ -212,9 +197,9 @@ export function useExplorerActions(
|
|
|
212
197
|
return {
|
|
213
198
|
actions,
|
|
214
199
|
dialogs: {
|
|
215
|
-
collision,
|
|
200
|
+
collision: collisionPrompt.state,
|
|
216
201
|
pendingDelete,
|
|
217
|
-
permanentOverwrite,
|
|
202
|
+
permanentOverwrite: permanentOverwritePrompt.state,
|
|
218
203
|
properties,
|
|
219
204
|
inlineError,
|
|
220
205
|
closeDelete: () => setPendingDelete(null),
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serializes the "no Trash on this host" confirm (`ctx.confirmPermanentOverwrite`) behind the
|
|
3
|
+
* same one-at-a-time queue as the collision prompt (`SerialPromptQueue`, see
|
|
4
|
+
* `collision-prompt-queue.ts`). This confirm used to be its own bare `useState` slot with the
|
|
5
|
+
* exact same race: a Replace that hits `NO_TRASH` on two concurrent jobs could have the second
|
|
6
|
+
* call silently overwrite the first's dialog, leaving the first job's promise unresolved
|
|
7
|
+
* forever. No "apply to all" here — it is a much rarer prompt than a name collision, so a
|
|
8
|
+
* plain FIFO queue is all it needs.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useMemo, useRef, useState } from "react";
|
|
12
|
+
import { SerialPromptQueue } from "./collision-prompt-queue";
|
|
13
|
+
|
|
14
|
+
export interface PermanentOverwritePromptState {
|
|
15
|
+
name: string;
|
|
16
|
+
/** How many more confirms are queued behind this one. */
|
|
17
|
+
remaining: number;
|
|
18
|
+
resolve(proceed: boolean): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PermanentOverwritePrompt {
|
|
22
|
+
state: PermanentOverwritePromptState | null;
|
|
23
|
+
confirm(name: string): Promise<boolean>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function usePermanentOverwritePrompt(): PermanentOverwritePrompt {
|
|
27
|
+
const [tick, setTick] = useState(0);
|
|
28
|
+
const queueRef = useRef<SerialPromptQueue<string, boolean> | null>(null);
|
|
29
|
+
if (!queueRef.current) {
|
|
30
|
+
queueRef.current = new SerialPromptQueue(() => setTick((n) => n + 1));
|
|
31
|
+
}
|
|
32
|
+
const queue = queueRef.current;
|
|
33
|
+
|
|
34
|
+
return useMemo(() => {
|
|
35
|
+
const snapshot = queue.snapshot();
|
|
36
|
+
return {
|
|
37
|
+
state: snapshot && {
|
|
38
|
+
name: snapshot.request,
|
|
39
|
+
remaining: snapshot.remaining,
|
|
40
|
+
resolve: (proceed) => queue.choose(proceed),
|
|
41
|
+
},
|
|
42
|
+
confirm: (name) => queue.request(name),
|
|
43
|
+
};
|
|
44
|
+
// `tick` has no direct use below — it forces this memo to recompute whenever the queue
|
|
45
|
+
// notifies of a change (new confirm queued, or one resolved).
|
|
46
|
+
}, [queue, tick]);
|
|
47
|
+
}
|