@hienlh/ppm 0.17.57 → 0.18.1
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 +24 -0
- package/CLAUDE.md +4 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +16 -2
- package/bun.lock +3 -0
- package/dist/web/assets/{ai-resource-editor-CXWQcx3_.js → ai-resource-editor-Bmk4P0YS.js} +1 -1
- package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-C-Kjr43S.js} +1 -1
- package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-CaDDbZqm.js} +1 -1
- package/dist/web/assets/api-client-CaRE4HYh.js +1 -0
- package/dist/web/assets/{api-client-COQtP-dB.js → api-client-rTAGdEKo.js} +1 -1
- package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-CAhcNpj5.js} +1 -1
- package/dist/web/assets/api-settings-DBcXfeJ8.js +1 -0
- package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-CAjg_Bt9.js} +1 -1
- package/dist/web/assets/{apl-DSANFLx0.js → apl-B1WsQFAb.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
- package/dist/web/assets/{astro-CXIotiNS.js → astro-Bdnz7nVp.js} +1 -1
- package/dist/web/assets/{audio-preview-D7jNc8mi.js → audio-preview-CikvnAOI.js} +1 -1
- package/dist/web/assets/{blade-BvrP2gPd.js → blade-B2euh8sS.js} +1 -1
- package/dist/web/assets/{bot-E9mcVZnr.js → bot-nk1k3ELu.js} +1 -1
- package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-GNeMaxd0.js} +1 -1
- package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-D7G0W7-s.js} +1 -1
- package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
- package/dist/web/assets/chat-tab-B3-KcCn2.js +13 -0
- package/dist/web/assets/chevron-right-BLOtX8Vy.js +1 -0
- package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-CENpx4oA.js} +1 -1
- package/dist/web/assets/clock-D493FU_D.js +1 -0
- package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Q6YgysnI.js} +1 -1
- package/dist/web/assets/code-CLF5x7KK.js +1 -0
- package/dist/web/assets/code-editor-BpbZw13T.js +10 -0
- package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-BjufWxVc.js} +1 -1
- package/dist/web/assets/{conflict-editor-CpGcSopK.js → conflict-editor-DC3liBm_.js} +3 -3
- package/dist/web/assets/context-menu-q-RHHAWz.js +1 -0
- package/dist/web/assets/copy-BApI9v7Z.js +1 -0
- package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CqWU0gCR.js} +1 -1
- package/dist/web/assets/{createLucideIcon-ConhwJnb.js → createLucideIcon-D8pyde1A.js} +1 -1
- package/dist/web/assets/{crystal-DMjCL--0.js → crystal-ylayVajF.js} +1 -1
- package/dist/web/assets/csv-preview-BhCOjgq1.js +2 -0
- package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-BzPfsCfd.js} +1 -1
- package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-CUx8cusN.js} +1 -1
- package/dist/web/assets/{database-COYAMZaw.js → database-B9okYLx6.js} +1 -1
- package/dist/web/assets/{database-viewer-DTMmic31.js → database-viewer-Z6uLyZVO.js} +1 -1
- package/dist/web/assets/dialog-CkI7rwrh.js +1 -0
- package/dist/web/assets/diff-viewer-CT1ALWt2.js +4 -0
- package/dist/web/assets/{dist-m_oJqIDk.js → dist-DduBIVkD.js} +1 -1
- package/dist/web/assets/{docx-preview-CR2ZtvNP.js → docx-preview-C_fSjfy_.js} +1 -1
- package/dist/web/assets/{edge-CTom35R_.js → edge-DX4NxQfh.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-DfX-9om9.js} +1 -1
- package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-CSh3NjbV.js} +1 -1
- package/dist/web/assets/{elm-CpD28xsW.js → elm-DBzwFYq9.js} +1 -1
- package/dist/web/assets/{erb-DHX2IIEs.js → erb-yxVOb8aB.js} +1 -1
- package/dist/web/assets/{erlang-B_etLcPG.js → erlang-5XIDkYrM.js} +1 -1
- package/dist/web/assets/{esm-PmlN1RTs.js → esm-DO4el2OJ.js} +1 -1
- package/dist/web/assets/explorer-body-CQCyGEet.js +2 -0
- package/dist/web/assets/explorer-window-content-EtyTAZSh.js +1 -0
- package/dist/web/assets/{extension-webview-BTCFWY2A.js → extension-webview-ePIM2fwM.js} +1 -1
- package/dist/web/assets/{eye-off-Rq1n2-oe.js → eye-off-CUdzgmX8.js} +1 -1
- package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-COAwgNGw.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-D4tE5VWY.js} +1 -1
- package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-By8soj6-.js} +1 -1
- package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-DoSwM7Hv.js} +1 -1
- package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-_ZiciHIx.js} +1 -1
- package/dist/web/assets/git-log-panel-BRWmTzX1.js +1 -0
- package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-C3Y5N9-s.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
- package/dist/web/assets/glide-data-grid-DCJCz4XB.js +138 -0
- package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BPBTXVlC.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-BQSZdy_D.js} +1 -1
- package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-CbQdxpU-.js} +1 -1
- package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-GMlKikpY.js} +1 -1
- package/dist/web/assets/group-chat-tab-C3UD2bcm.js +1 -0
- package/dist/web/assets/{hack-sxywdIAQ.js → hack-DyshcLec.js} +1 -1
- package/dist/web/assets/{haml-Dts9JlPU.js → haml-rqCqWzsq.js} +1 -1
- package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BCKwSWjR.js} +1 -1
- package/dist/web/assets/{html-2p0RvXi4.js → html-Cg29xPR1.js} +1 -1
- package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-geltIVg8.js} +1 -1
- package/dist/web/assets/{http-CHd2gYPf.js → http-pG7Vp237.js} +1 -1
- package/dist/web/assets/{hurl-boMPgDm9.js → hurl-Di3Uu_Mc.js} +1 -1
- package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CRy4lbv8.js} +1 -1
- package/dist/web/assets/{image-preview-0SGb7_tH.js → image-preview-CP82lor8.js} +1 -1
- package/dist/web/assets/index-BYetJoXQ.js +52 -0
- package/dist/web/assets/index-D-xwjNyX.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
- package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-C8GyVnvd.js} +1 -1
- package/dist/web/assets/{jison-C2vJYjbJ.js → jison-BywnqPn6.js} +1 -1
- package/dist/web/assets/{julia-DJ8MrQPA.js → julia-DTrO3T3B.js} +1 -1
- package/dist/web/assets/{just-BvQHq7VE.js → just-CA3KSHPi.js} +1 -1
- package/dist/web/assets/keybindings-store-yBWU8ZYT.js +1 -0
- package/dist/web/assets/{latex-ChNeshUC.js → latex-BC5xUMpc.js} +1 -1
- package/dist/web/assets/{lib-DTaN9riD.js → lib-BFetOULu.js} +1 -1
- package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-Chd8tSyQ.js} +1 -1
- package/dist/web/assets/{lua-D8NNsLbC.js → lua-DHaEtwLv.js} +1 -1
- package/dist/web/assets/markdown-renderer-B2D4gdok.js +1 -0
- package/dist/web/assets/markdown-renderer-DDgl5zDc.js +3 -0
- package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Desx_bC-.js} +1 -1
- package/dist/web/assets/{mdc-omkodQB5.js → mdc-DahWHc1t.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-BQt45q9T.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-BtxyynR3.js +1 -0
- package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-3A7lutCA.js} +1 -1
- package/dist/web/assets/{nginx-ByvWruGD.js → nginx-XQda6Hdy.js} +1 -1
- package/dist/web/assets/{nim-BrMojX4O.js → nim-DHqlpwP1.js} +1 -1
- package/dist/web/assets/notification-store-DKw_mGED.js +1 -0
- package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-DgZtViPO.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
- package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-s6Qx49RK.js} +1 -1
- package/dist/web/assets/{pdf-preview-DEn8amrO.js → pdf-preview-BXDOe7bG.js} +1 -1
- package/dist/web/assets/{perl-Bd_0l-b4.js → perl-M-ncjACb.js} +1 -1
- package/dist/web/assets/{php-D-DLTnWJ.js → php-CKIajrZi.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
- package/dist/web/assets/{plus-DcuJuDC3.js → plus-9LatwJMx.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CjrM15gz.js → postgres-viewer-DkHYV33h.js} +3 -3
- package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-DyaEpHVG.js} +1 -1
- package/dist/web/assets/{pug-D2aO-l2T.js → pug-DO0u_gO4.js} +1 -1
- package/dist/web/assets/{qml-CyQVzNXS.js → qml-UEje1PAA.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
- package/dist/web/assets/{razor-8tvJXsO3.js → razor-lCs5T1Ib.js} +1 -1
- package/dist/web/assets/{react-8hrD3-kv.js → react-w6wINGnd.js} +1 -1
- package/dist/web/assets/{rst-C4AZyQQl.js → rst-CZX_O0z4.js} +1 -1
- package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-idycVAyE.js} +1 -1
- package/dist/web/assets/{sas-BtnF4zaV.js → sas-o5gTfUwp.js} +1 -1
- package/dist/web/assets/{save-BM4p9_9n.js → save-C4z1p51e.js} +1 -1
- package/dist/web/assets/{scss-GAyp5euH.js → scss-CRTk3iBQ.js} +1 -1
- package/dist/web/assets/settings-store-ChqHdT2V.js +2 -0
- package/dist/web/assets/settings-tab-ESL-6MAA.js +1 -0
- package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-3e5ln8ci.js} +1 -1
- package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-CGQjQ75I.js} +1 -1
- package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-C5laObJv.js} +1 -1
- package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-DULFtyLS.js} +1 -1
- package/dist/web/assets/{soy-DrLNhqfp.js → soy-CxY0VwX2.js} +1 -1
- package/dist/web/assets/sparkles-n1hzh65o.js +1 -0
- package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CBAGAFuq.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CT5M9PKQ.js → sql-query-editor-CZdZuawP.js} +1 -1
- package/dist/web/assets/sqlite-viewer-Cpq06-nz.js +1 -0
- package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BoezhOvk.js} +1 -1
- package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql-CHEzAhu7.js} +1 -1
- package/dist/web/assets/{svelte-23si1KV5.js → svelte-y6xtu2zX.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-De3oz9zD.js → system-monitor-tab-XR8NXHpv.js} +1 -1
- package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-C42sktPo.js} +1 -1
- package/dist/web/assets/{table-Di8pv2Y_.js → table-CHmxuh6M.js} +1 -1
- package/dist/web/assets/{templ-CPxlP4vF.js → templ-DbG2y5yD.js} +1 -1
- package/dist/web/assets/{terminal-tab-CciNqtMc.js → terminal-tab-CCCB-_50.js} +3 -3
- package/dist/web/assets/{tex-ugJpgYPc.js → tex-CeKQhrQG.js} +1 -1
- package/dist/web/assets/tool-cards-CsTs5go2.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
- package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-iKKOXJjG.js} +1 -1
- package/dist/web/assets/{twig-B7VUm2Ca.js → twig-DK8Vrp-G.js} +1 -1
- package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-DYtOHK7u.js} +1 -1
- package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-DnFSkUG2.js} +1 -1
- package/dist/web/assets/use-monaco-theme-CZh7xTJZ.js +1 -0
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +291 -0
- package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
- package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CaeeFO0B.js} +2 -2
- package/dist/web/assets/{video-preview-D-S_YnG6.js → video-preview-KbRcyuAA.js} +1 -1
- package/dist/web/assets/{vue-m75pWowk.js → vue-JkIEObMV.js} +1 -1
- package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-D8SqnU86.js} +1 -1
- package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DJj7N4WD.js} +1 -1
- package/dist/web/assets/x-B1qHmLdR.js +1 -0
- package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-00w9Rv.js} +1 -1
- package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-CVdCwPHV.js} +1 -1
- package/dist/web/index.html +29 -26
- 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/docs/codebase-summary.md +10 -1
- package/docs/design-guidelines.md +31 -0
- package/docs/project-roadmap.md +10 -0
- package/docs/system-architecture.md +59 -0
- package/package.json +2 -1
- package/src/server/index.ts +8 -0
- package/src/server/routes/files.ts +2 -2
- package/src/server/routes/fs-browse.ts +99 -74
- package/src/server/routes/fs-ops.ts +118 -0
- package/src/server/routes/fs-sqlite.ts +129 -0
- package/src/server/routes/host-info.ts +17 -0
- package/src/server/routes/settings.ts +2 -0
- package/src/services/download-token.service.ts +21 -5
- package/src/services/file.service.ts +8 -12
- package/src/services/fs-browse.service.ts +104 -161
- package/src/services/fs-ops/fs-content-disposition.ts +17 -0
- package/src/services/fs-ops/fs-core-ops.ts +149 -0
- package/src/services/fs-ops/fs-error-response.ts +24 -0
- package/src/services/fs-ops/fs-git-init.service.ts +23 -0
- package/src/services/fs-ops/fs-hidden-names.ts +17 -0
- package/src/services/fs-ops/fs-list-files.service.ts +68 -0
- package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
- package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
- package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
- package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
- package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
- package/src/services/fs-ops/fs-real-path.ts +44 -0
- package/src/services/fs-ops/sql-row-reader.ts +26 -0
- package/src/services/fs-ops/sql-statement-guard.ts +63 -0
- package/src/services/fs-path-guard.service.ts +196 -0
- package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
- package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
- package/src/services/host-info/concurrency-limit.ts +20 -0
- package/src/services/host-info/host-drives.ts +190 -0
- package/src/services/host-info/host-info.service.ts +171 -0
- package/src/services/host-info/host-known-folders.ts +180 -0
- package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
- package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
- package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
- package/src/services/host-info/spawn-runner.ts +41 -0
- package/src/services/sqlite-types.ts +26 -0
- package/src/services/sqlite.service.ts +38 -29
- package/src/services/subagent-transcript-merger.ts +1 -1
- package/src/types/system.ts +50 -0
- package/src/web/app.tsx +19 -3
- package/src/web/components/editor/code-editor.tsx +3 -9
- package/src/web/components/explorer/file-tree.tsx +103 -21
- package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
- package/src/web/components/explorer/tree-node.tsx +34 -93
- package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
- package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
- package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
- package/src/web/components/floating-window/floating-window.tsx +167 -0
- package/src/web/components/floating-window/use-window-drag.ts +28 -0
- package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
- package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
- package/src/web/components/floating-window/use-window-resize.ts +30 -0
- package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
- package/src/web/components/floating-window/window-content-registry.ts +51 -0
- package/src/web/components/floating-window/window-geometry.ts +121 -0
- package/src/web/components/floating-window/window-layer.tsx +92 -0
- package/src/web/components/floating-window/window-persistence.ts +104 -0
- package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
- package/src/web/components/floating-window/window-store-types.ts +20 -0
- package/src/web/components/floating-window/window-store.ts +192 -0
- package/src/web/components/group-chat/group-list.tsx +2 -3
- package/src/web/components/layout/command-palette.tsx +10 -0
- package/src/web/components/layout/mobile-drawer-tab-bar.tsx +3 -10
- package/src/web/components/layout/mobile-drawer.tsx +13 -2
- package/src/web/components/layout/nav-section-rail.tsx +16 -11
- package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
- package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
- package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
- package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
- package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
- package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
- package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
- package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
- package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
- package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
- package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
- package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
- package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
- package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
- package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
- package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
- package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
- package/src/web/components/os-explorer/explorer-body.tsx +153 -0
- package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
- package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
- package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
- package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
- package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
- package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
- package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
- package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
- package/src/web/components/os-explorer/explorer-store.ts +179 -0
- package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
- package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
- package/src/web/components/os-explorer/format-file-meta.ts +74 -0
- package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
- package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
- package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
- package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
- package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
- package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
- package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
- package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
- package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
- package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
- package/src/web/components/os-explorer/open-explorer.ts +35 -0
- package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
- package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
- package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
- package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
- package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
- package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
- package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
- package/src/web/components/os-explorer/skins/skins.css +25 -0
- package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
- package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
- package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
- package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
- package/src/web/components/os-explorer/sort-options.ts +29 -0
- package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
- package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
- package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
- package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
- package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
- package/src/web/components/os-explorer/use-host-info.ts +85 -0
- package/src/web/components/os-explorer/views/column-row.tsx +126 -0
- package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
- package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
- package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
- package/src/web/components/os-explorer/views/column-view.tsx +204 -0
- package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
- package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
- package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
- package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
- package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
- package/src/web/components/os-explorer/views/list-row.tsx +162 -0
- package/src/web/components/os-explorer/views/list-view.tsx +177 -0
- package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
- package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
- package/src/web/components/settings/settings-tab.tsx +29 -2
- package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
- package/src/web/components/sqlite/use-sqlite.ts +7 -2
- package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
- package/src/web/components/ui/feature-badge.tsx +40 -0
- package/src/web/components/ui/file-browser-picker.tsx +1 -18
- package/src/web/lib/feature-badges.ts +22 -0
- package/src/web/lib/file-download.ts +6 -4
- package/src/web/lib/fs-api.ts +185 -0
- package/src/web/lib/sidebar-tabs/tab-registry.ts +4 -3
- package/src/web/stores/file-store.ts +38 -1
- package/src/web/stores/settings-store.ts +14 -0
- package/src/web/styles/globals.css +1 -0
- package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
- package/dist/web/assets/api-settings-De--HNHp.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
- package/dist/web/assets/chat-tab-C5U7a_rP.js +0 -13
- package/dist/web/assets/chevron-right-DeUue2aK.js +0 -1
- package/dist/web/assets/code-TekEnDFM.js +0 -1
- package/dist/web/assets/code-editor-CqlzLQ7T.js +0 -10
- package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
- package/dist/web/assets/csv-preview-DD94T9ff.js +0 -2
- package/dist/web/assets/dialog-HHdNInEH.js +0 -1
- package/dist/web/assets/diff-viewer-DyQLYP7J.js +0 -4
- package/dist/web/assets/git-log-panel-CrUgsLY5.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
- package/dist/web/assets/glide-data-grid-LinTYl0_.js +0 -138
- package/dist/web/assets/group-chat-tab-BqNJW7y2.js +0 -1
- package/dist/web/assets/index-CVJmlKhp.css +0 -2
- package/dist/web/assets/index-I2ik0ZF7.js +0 -50
- package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
- package/dist/web/assets/keybindings-store-C46tNxJE.js +0 -1
- package/dist/web/assets/markdown-renderer-DSxSupDq.js +0 -3
- package/dist/web/assets/markdown-renderer-xzyhJklS.js +0 -1
- package/dist/web/assets/notification-store-Dek7ClI2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
- package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
- package/dist/web/assets/settings-tab-DIuR1VIc.js +0 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CEObRQ7n.js +0 -1
- package/dist/web/assets/tool-cards-Bdx04yYD.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
- package/dist/web/assets/use-monaco-theme-k-17kQqf.js +0 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +0 -291
- package/dist/web/assets/x-BsFLg-T8.js +0 -1
- /package/dist/web/assets/{abap-BM5PMK0-.js → abap-DrMyiWmK.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-BlSOyi0y.js} +0 -0
- /package/dist/web/assets/{ada-DzO1tMqV.js → ada-B_m96icB.js} +0 -0
- /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-Ch3vOiZB.js} +0 -0
- /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CdsaXc1n.js} +0 -0
- /package/dist/web/assets/{apex-3O2170Ju.js → apex-0AnsBevk.js} +0 -0
- /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-CSxLolzy.js} +0 -0
- /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
- /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-DXZS0jA5.js} +0 -0
- /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DDy3pH2A.js} +0 -0
- /package/dist/web/assets/{asm-p1-PUkh3.js → asm-Bd17hhqg.js} +0 -0
- /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-C62HJRs-.js} +0 -0
- /package/dist/web/assets/{awk-Cj93pRL8.js → awk-Cc-nazni.js} +0 -0
- /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-BsfmNWCU.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-BZ4HLa4X.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-Bhyjyiqr.js} +0 -0
- /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-QiWgir3v.js} +0 -0
- /package/dist/web/assets/{bat-Cg8hlSct.js → bat-DxtlkwN3.js} +0 -0
- /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-Bt21X7M_.js} +0 -0
- /package/dist/web/assets/{berry-BqVQYZZr.js → berry-Cksre7lO.js} +0 -0
- /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DkakgDlR.js} +0 -0
- /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-8crmcyXU.js} +0 -0
- /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-CewQ2I_Z.js} +0 -0
- /package/dist/web/assets/{c-BQFvJ6Ux.js → c-C_6zE8Ha.js} +0 -0
- /package/dist/web/assets/{c3-Da53KeG6.js → c3-DLWB9-dp.js} +0 -0
- /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-D8qYS6pR.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-Bh95lNRy.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-C1PlN3G0.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-CldtgdK4.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-BiwqxX9A.js} +0 -0
- /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-CGiU22jY.js} +0 -0
- /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-COIC3doh.js} +0 -0
- /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DWUEQfHv.js} +0 -0
- /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BNHiAQea.js} +0 -0
- /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-BuKaoS0j.js} +0 -0
- /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-D5IKbxsd.js} +0 -0
- /package/dist/web/assets/{coq-D2bX6-8g.js → coq-BMVS-p94.js} +0 -0
- /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-DMlbXbM4.js} +0 -0
- /package/dist/web/assets/{css-CIjciIIk.js → css-CjkJC89X.js} +0 -0
- /package/dist/web/assets/{csv-DW48Z5d-.js → csv-DoNVBBf0.js} +0 -0
- /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
- /package/dist/web/assets/{cue-Byh1xHko.js → cue-DoTRbBg-.js} +0 -0
- /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-DVsNdTT3.js} +0 -0
- /package/dist/web/assets/{d-CxaScvxf.js → d-EX7BBSrv.js} +0 -0
- /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-P_YTHwlh.js} +0 -0
- /package/dist/web/assets/{dart-BmpakMe-.js → dart-Do-rgU4S.js} +0 -0
- /package/dist/web/assets/{dax-B1aRUMXT.js → dax-DxOieacu.js} +0 -0
- /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-CvdV7SBf.js} +0 -0
- /package/dist/web/assets/{diff-CupGmT1a.js → diff-CNOetGen.js} +0 -0
- /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CMfLpPW_.js} +0 -0
- /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DAlG4_q-.js} +0 -0
- /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-Dh3qf6yx.js} +0 -0
- /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-baAGmJYG.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-l7ZIyums.js} +0 -0
- /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DeRjdBn8.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-CB05-jQA.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-CT-llQBT.js} +0 -0
- /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-ByPJkTT7.js} +0 -0
- /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-CeMarbvn.js} +0 -0
- /package/dist/web/assets/{fish-Vr62C2vP.js → fish-BYrt6Xtb.js} +0 -0
- /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-Cr5VcAwh.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DQmKtrLa.js} +0 -0
- /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-QTDKjzhY.js} +0 -0
- /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-Chr-5UYt.js} +0 -0
- /package/dist/web/assets/{genie-B4hkIXNU.js → genie-kWHMWnt0.js} +0 -0
- /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-DtEBQFUo.js} +0 -0
- /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-B5aiUta2.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-BkxtsTqR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-B9QjR_0K.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-D6bO-5c0.js} +0 -0
- /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-Cg2ZW_CJ.js} +0 -0
- /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-eD7qUVlQ.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-CFnlSVjd.js} +0 -0
- /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpYoOqIT.js} +0 -0
- /package/dist/web/assets/{gn-DuO2D2eW.js → gn-BxwtZPau.js} +0 -0
- /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-CRyiZDfl.js} +0 -0
- /package/dist/web/assets/{go-BL16cMJO.js → go-Dw1Qx94W.js} +0 -0
- /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-zq_SbWk-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-DbbNy8-Q.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-BQZ7Kj6C.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-vNavErXi.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-AOFbCd7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-DqtxND6H.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-Cg0basu6.js} +0 -0
- /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-CcsmcOez.js} +0 -0
- /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-BimYY9X4.js} +0 -0
- /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CTrXCK40.js} +0 -0
- /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-CmqqU6Qg.js} +0 -0
- /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-CuJtX_uf.js} +0 -0
- /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BGeSjDYJ.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-GYMgd3R0.js} +0 -0
- /package/dist/web/assets/{houston-Ddm8AgpV.js → houston--JD_5r2q.js} +0 -0
- /package/dist/web/assets/{hy-DzZa6alE.js → hy-C9ILOiqk.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
- /package/dist/web/assets/{imba-aiXalPnK.js → imba-B6F3yus5.js} +0 -0
- /package/dist/web/assets/{ini-CAvrVa5R.js → ini-BfkQswMZ.js} +0 -0
- /package/dist/web/assets/{java-PRMSQp24.js → java-DEH5avQ1.js} +0 -0
- /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-Dr4yHuV2.js} +0 -0
- /package/dist/web/assets/{json-uWUKqOxq.js → json-BuR35ewf.js} +0 -0
- /package/dist/web/assets/{json5-R_LXJXL0.js → json5-Ny9ZiJAC.js} +0 -0
- /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-zYVuoZkR.js} +0 -0
- /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-B9xthb_H.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-CCoLJbMP.js} +0 -0
- /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-W0GDlQXi.js} +0 -0
- /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-BrLJpWC-.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CuVwt_MO.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-B3W_617P.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-CEx4MP5W.js} +0 -0
- /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DilRejnB.js} +0 -0
- /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-Bq5JtKaa.js} +0 -0
- /package/dist/web/assets/{kusto-Ba87977j.js → kusto-Bhp2KYog.js} +0 -0
- /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-WA_7c5hu.js} +0 -0
- /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-AEGcXwS-.js} +0 -0
- /package/dist/web/assets/{less-Dfn9YC_2.js → less-NPqH-26u.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
- /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-B4xW7cgY.js} +0 -0
- /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CPyWNcDE.js} +0 -0
- /package/dist/web/assets/{log-QW3dqr1D.js → log-CeQmiQSz.js} +0 -0
- /package/dist/web/assets/{logo-DUinJheI.js → logo-BO0VrjBm.js} +0 -0
- /package/dist/web/assets/{luau-B1vcBzl8.js → luau-B6GT9kA1.js} +0 -0
- /package/dist/web/assets/{make-CqNL10gS.js → make-CwfmN7e_.js} +0 -0
- /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Bbe4FlLp.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-u7M3XGe7.js} +0 -0
- /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-iD9XbfsB.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-BGdI48zn.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-_mk62F5q.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-CxFbDyAu.js} +0 -0
- /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-Z0RUE3V_.js} +0 -0
- /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-BZ5wPYzV.js} +0 -0
- /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-CY1_aP-h.js} +0 -0
- /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-DKWtxOre.js} +0 -0
- /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-e1iwHDfP.js} +0 -0
- /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-BiDfQ4ir.js} +0 -0
- /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-DteFEHsj.js} +0 -0
- /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-BgPbE9xZ.js} +0 -0
- /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-mEbRp-mA.js} +0 -0
- /package/dist/web/assets/{move-BvhjG2ic.js → move-CogqZw7A.js} +0 -0
- /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-BD2qjmy1.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-BNX8GOD_.js} +0 -0
- /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-ed3GbziD.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-B0j7O0Wn.js} +0 -0
- /package/dist/web/assets/{nix-B9EAdptu.js → nix-DFDbcQQm.js} +0 -0
- /package/dist/web/assets/{nord-DxASd4qG.js → nord-D93TxEQi.js} +0 -0
- /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-3_H_XKja.js} +0 -0
- /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-BM3u6nlm.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DZ73h42g.js} +0 -0
- /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-B1PU5gmh.js} +0 -0
- /package/dist/web/assets/{odin-B8pz4gIL.js → odin-D14L6aRL.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-Bj-94Z8v.js} +0 -0
- /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DTdGWyOZ.js} +0 -0
- /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-Ciq554Sj.js} +0 -0
- /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-Bfd4qdGi.js} +0 -0
- /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-CTurDUfj.js} +0 -0
- /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BLQfZXQp.js} +0 -0
- /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DyYfP9kQ.js} +0 -0
- /package/dist/web/assets/{po-Dz4OaDNi.js → po-CgXdT8vQ.js} +0 -0
- /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DOg5fK6s.js} +0 -0
- /package/dist/web/assets/{polar-Che1XC2Z.js → polar-Dv4aoL6X.js} +0 -0
- /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-DxugHnJD.js} +0 -0
- /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-CR3kzRyX.js} +0 -0
- /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-BE91xJQB.js} +0 -0
- /package/dist/web/assets/{prisma-DCmjceML.js → prisma-B_3yUlRQ.js} +0 -0
- /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-DcT781Ht.js} +0 -0
- /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-CSf-w5k2.js} +0 -0
- /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CLGO6QWl.js} +0 -0
- /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-Bah2McKd.js} +0 -0
- /package/dist/web/assets/{python-Ctyr4faP.js → python-DUH8qy7N.js} +0 -0
- /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-YIsHnjKq.js} +0 -0
- /package/dist/web/assets/{qss-LbRHzb21.js → qss-CLmoVNA4.js} +0 -0
- /package/dist/web/assets/{r-Dvfjp0wc.js → r-Pon-b-vz.js} +0 -0
- /package/dist/web/assets/{racket-DI8v8nzD.js → racket-BA09P5Kg.js} +0 -0
- /package/dist/web/assets/{raku-CX1q9GW0.js → raku-DlpzU7Fe.js} +0 -0
- /package/dist/web/assets/{red-CSz3QEOw.js → red-l7VuCR6d.js} +0 -0
- /package/dist/web/assets/{reg-FMjhUQE-.js → reg-g_k7gQEr.js} +0 -0
- /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CT17B7I7.js} +0 -0
- /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-DkPCS7Bp.js} +0 -0
- /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
- /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-CYuFTw30.js} +0 -0
- /package/dist/web/assets/{ron-D0t4kvJG.js → ron-cY7u_yEu.js} +0 -0
- /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-Cr_h_Ubo.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-rRewaSZg.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-3VX5Xwca.js} +0 -0
- /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-Du4ie5PU.js} +0 -0
- /package/dist/web/assets/{rust-2jBzrC0l.js → rust-B7xWL-Eh.js} +0 -0
- /package/dist/web/assets/{sass-DD75Izu_.js → sass-oXbOG8de.js} +0 -0
- /package/dist/web/assets/{scala-DpBKROIu.js → scala-nlZJhKHf.js} +0 -0
- /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-FZ9_Zy8D.js} +0 -0
- /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-D_lyvXiu.js} +0 -0
- /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DRWRboAd.js} +0 -0
- /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-BGk2uVHu.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-PAoSX041.js} +0 -0
- /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-qP0zvICo.js} +0 -0
- /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-B7uccpH6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-CbICLZs3.js} +0 -0
- /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-CprWhyjj.js} +0 -0
- /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-B7pcm2K6.js} +0 -0
- /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-Co2LL4J_.js} +0 -0
- /package/dist/web/assets/{sql-CoqObhgo.js → sql-cETIdGnT.js} +0 -0
- /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-DETG6JCD.js} +0 -0
- /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-tmTre2-O.js} +0 -0
- /package/dist/web/assets/{swift-DtMMgvRI.js → swift-BeKQbULV.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-eCL4-Xdn.js} +0 -0
- /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-CviwWvEx.js} +0 -0
- /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-WuoFIbBS.js} +0 -0
- /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-B9VBu7VI.js} +0 -0
- /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-CN7r11ky.js} +0 -0
- /package/dist/web/assets/{tcl-caTR_frn.js → tcl-e1cdhe3h.js} +0 -0
- /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-BEP0mfa_.js} +0 -0
- /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BfA_dA37.js} +0 -0
- /package/dist/web/assets/{toml-B7D2mIDU.js → toml-BrDIfNnt.js} +0 -0
- /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-CyfGyE4i.js} +0 -0
- /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-BrFA8Iwf.js} +0 -0
- /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-9GBtR0mP.js} +0 -0
- /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-BuTRZ65V.js} +0 -0
- /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CRiNNRXF.js} +0 -0
- /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CmpTnnkl.js} +0 -0
- /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
- /package/dist/web/assets/{v-D4997FGs.js → v-DnIi2aab.js} +0 -0
- /package/dist/web/assets/{vala-AFg9DmKE.js → vala-B7VxCNZP.js} +0 -0
- /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-DGoQ3pBT.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
- /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog-zGOC4qwV.js} +0 -0
- /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-DXR9-06q.js} +0 -0
- /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CCO0oz0l.js} +0 -0
- /package/dist/web/assets/{viml-D6EKG66q.js → viml-Dxwr-QNN.js} +0 -0
- /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-Bhah7RxE.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-CGK6XVwc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-ne5kfw8h.js} +0 -0
- /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-ErDFqVoP.js} +0 -0
- /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
- /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-DtWvUWGr.js} +0 -0
- /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-EZe1IEYT.js} +0 -0
- /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-B1o5_I_9.js} +0 -0
- /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-C_rwp8An.js} +0 -0
- /package/dist/web/assets/{wit-DIrx_eCI.js → wit-gbYtF3rY.js} +0 -0
- /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-DrcQFe2n.js} +0 -0
- /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-CzDAObLX.js} +0 -0
- /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-B-J1phtg.js} +0 -0
- /package/dist/web/assets/{zig-CrzwMpQE.js → zig-BlzNia0O.js} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files PPM can display, and in which viewer.
|
|
3
|
+
*
|
|
4
|
+
* The editor tab picks its renderer from the file extension. Keeping those sets here
|
|
5
|
+
* (rather than inside the editor component) lets the explorer answer "will a double-click
|
|
6
|
+
* do anything?" before it opens a tab, and guarantees the two never drift apart: the
|
|
7
|
+
* editor imports the very same sets.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** Image extensions renderable inline */
|
|
11
|
+
export const IMAGE_EXTS = new Set(["png", "jpg", "jpeg", "gif", "webp", "svg", "ico"]);
|
|
12
|
+
/** Video extensions playable inline */
|
|
13
|
+
export const VIDEO_EXTS = new Set(["mp4", "webm", "mov", "ogg", "avi", "mkv"]);
|
|
14
|
+
/** Audio extensions playable inline */
|
|
15
|
+
export const AUDIO_EXTS = new Set(["mp3", "wav", "flac", "aac", "m4a", "wma"]);
|
|
16
|
+
/** SQLite extensions — redirect to sqlite viewer */
|
|
17
|
+
export const SQLITE_EXTS = new Set(["db", "sqlite", "sqlite3"]);
|
|
18
|
+
/** Rendered as formatted markdown (with a raw edit mode) */
|
|
19
|
+
export const MARKDOWN_EXTS = new Set(["md", "mdx"]);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extensions Monaco opens as editable text. Anything outside this list is assumed
|
|
23
|
+
* binary: the server would answer with a base64 body and the editor would only be able
|
|
24
|
+
* to say "cannot be displayed", so the explorer treats it as not openable instead.
|
|
25
|
+
*/
|
|
26
|
+
export const TEXT_EXTS = new Set([
|
|
27
|
+
"txt", "log", "ini", "cfg", "conf", "env", "properties", "editorconfig",
|
|
28
|
+
"js", "jsx", "mjs", "cjs", "ts", "tsx", "mts", "cts",
|
|
29
|
+
"json", "jsonc", "json5", "yaml", "yml", "toml", "xml", "svg",
|
|
30
|
+
"html", "htm", "css", "scss", "sass", "less", "vue", "svelte", "astro",
|
|
31
|
+
"py", "rb", "php", "go", "rs", "java", "kt", "kts", "swift", "c", "h",
|
|
32
|
+
"cpp", "cc", "hpp", "cs", "m", "mm", "scala", "lua", "dart", "r", "pl",
|
|
33
|
+
"sh", "bash", "zsh", "fish", "ps1", "psm1", "bat", "cmd",
|
|
34
|
+
"sql", "graphql", "gql", "proto", "prisma", "tf", "tfvars",
|
|
35
|
+
"dockerfile", "makefile", "gitignore", "gitattributes", "npmrc", "nvmrc",
|
|
36
|
+
"diff", "patch", "lock", "map", "srt", "vtt",
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Extensionless files that are conventionally plain text. Matched on the whole
|
|
41
|
+
* (lower-cased) name so `Dockerfile`, `LICENSE` and friends open in the editor.
|
|
42
|
+
*/
|
|
43
|
+
export const TEXT_FILENAMES = new Set([
|
|
44
|
+
"dockerfile", "makefile", "license", "licence", "readme", "changelog",
|
|
45
|
+
"authors", "contributing", "notice", "codeowners", "procfile", "brewfile",
|
|
46
|
+
"gemfile", "rakefile", "vagrantfile", "justfile",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
/** Which viewer an editor tab will render for this file. */
|
|
50
|
+
export type ViewerKind =
|
|
51
|
+
| "image" | "video" | "audio" | "pdf" | "docx"
|
|
52
|
+
| "sqlite" | "markdown" | "csv" | "text";
|
|
53
|
+
|
|
54
|
+
/** Lower-cased extension without the dot; empty string when there is none. */
|
|
55
|
+
export function extensionOf(name: string): string {
|
|
56
|
+
const base = name.split(/[/\\]/).pop() ?? name;
|
|
57
|
+
const dot = base.lastIndexOf(".");
|
|
58
|
+
// A leading dot is part of the name (".gitignore"), not an extension separator.
|
|
59
|
+
if (dot <= 0) return "";
|
|
60
|
+
return base.slice(dot + 1).toLowerCase();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The viewer PPM would use for this file, or `null` when it cannot show it. */
|
|
64
|
+
export function viewerKindOf(name: string): ViewerKind | null {
|
|
65
|
+
const base = (name.split(/[/\\]/).pop() ?? name).toLowerCase();
|
|
66
|
+
const ext = extensionOf(base);
|
|
67
|
+
|
|
68
|
+
if (IMAGE_EXTS.has(ext)) return "image";
|
|
69
|
+
if (VIDEO_EXTS.has(ext)) return "video";
|
|
70
|
+
if (AUDIO_EXTS.has(ext)) return "audio";
|
|
71
|
+
if (ext === "pdf") return "pdf";
|
|
72
|
+
if (ext === "docx") return "docx";
|
|
73
|
+
if (SQLITE_EXTS.has(ext)) return "sqlite";
|
|
74
|
+
if (MARKDOWN_EXTS.has(ext)) return "markdown";
|
|
75
|
+
if (ext === "csv") return "csv";
|
|
76
|
+
if (TEXT_EXTS.has(ext)) return "text";
|
|
77
|
+
// Dotfiles ('.gitignore') and extensionless names ('Dockerfile') carry their
|
|
78
|
+
// type in the name itself.
|
|
79
|
+
if (!ext) {
|
|
80
|
+
const stem = base.startsWith(".") ? base.slice(1) : base;
|
|
81
|
+
if (TEXT_FILENAMES.has(stem) || TEXT_EXTS.has(stem)) return "text";
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** True when double-clicking the file should open a PPM tab. */
|
|
87
|
+
export function canOpenInPpm(name: string): boolean {
|
|
88
|
+
return viewerKindOf(name) !== null;
|
|
89
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A drag-enter/leave depth counter shared by every hand-rolled dragenter/dragleave pair in
|
|
3
|
+
* the explorer and the project tree: nested children fire their own enter/leave pairs, so
|
|
4
|
+
* only a balanced count means "actually left". Both `enter` and `leave` take the same
|
|
5
|
+
* `accepted` gate the caller already computed for its own drag kind — a leave that forgot to
|
|
6
|
+
* apply that gate the way its own enter did is exactly what drove a counter negative and left
|
|
7
|
+
* a highlight that could never turn on again (a drag kind that never incremented the counter
|
|
8
|
+
* still decremented it on its way out).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export interface DragDepthCounter {
|
|
12
|
+
current: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** True the moment the count crosses from 0 to 1 — start showing the highlight. */
|
|
16
|
+
export function enterDragDepth(counter: DragDepthCounter, accepted: boolean): boolean {
|
|
17
|
+
if (!accepted) return false;
|
|
18
|
+
counter.current++;
|
|
19
|
+
return counter.current === 1;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True once the count returns to 0 — stop showing the highlight. Clamped at 0 rather than
|
|
24
|
+
* trusting every caller's enter/leave pairing to be perfectly balanced; going negative would
|
|
25
|
+
* otherwise require more than one matching `enter` before the count could ever reach 0 again.
|
|
26
|
+
*/
|
|
27
|
+
export function leaveDragDepth(counter: DragDepthCounter, accepted: boolean): boolean {
|
|
28
|
+
if (!accepted) return false;
|
|
29
|
+
counter.current = Math.max(0, counter.current - 1);
|
|
30
|
+
return counter.current === 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Resets after a drop or cancel, when the browser will not send the matching leave. */
|
|
34
|
+
export function resetDragDepth(counter: DragDepthCounter): void {
|
|
35
|
+
counter.current = 0;
|
|
36
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The image dragged under the cursor: the entry's own name for a single item, a count badge
|
|
3
|
+
* ("3 items") for a multi-selection.
|
|
4
|
+
*
|
|
5
|
+
* The browser snapshots the element synchronously during `setDragImage`, but it must be in
|
|
6
|
+
* the document and painted at that moment — hence a real, off-screen node rather than a
|
|
7
|
+
* detached one, removed on the next frame once the snapshot is taken.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const GHOST_OFFSET_X = 12;
|
|
11
|
+
const GHOST_OFFSET_Y = 12;
|
|
12
|
+
|
|
13
|
+
export function setEntryDragGhost(dataTransfer: DataTransfer, names: string[]): void {
|
|
14
|
+
if (typeof document === "undefined" || names.length === 0) return;
|
|
15
|
+
|
|
16
|
+
const label = names.length === 1 ? names[0]! : `${names.length} items`;
|
|
17
|
+
const ghost = document.createElement("div");
|
|
18
|
+
ghost.textContent = label;
|
|
19
|
+
// Inline styles, not classes: the node lives outside any themed subtree, and the snapshot
|
|
20
|
+
// is taken before a stylesheet-driven class could apply.
|
|
21
|
+
ghost.style.cssText = [
|
|
22
|
+
"position:fixed",
|
|
23
|
+
"top:-1000px",
|
|
24
|
+
"left:-1000px",
|
|
25
|
+
"padding:4px 10px",
|
|
26
|
+
"border-radius:6px",
|
|
27
|
+
"font:500 12px/1.4 var(--x-font, system-ui, sans-serif)",
|
|
28
|
+
"color:var(--text, #fff)",
|
|
29
|
+
"background:var(--panel-2, #2a2a2a)",
|
|
30
|
+
"border:1px solid var(--border, #444)",
|
|
31
|
+
"box-shadow:0 2px 8px rgba(0,0,0,.35)",
|
|
32
|
+
"white-space:nowrap",
|
|
33
|
+
"pointer-events:none",
|
|
34
|
+
].join(";");
|
|
35
|
+
document.body.appendChild(ghost);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
dataTransfer.setDragImage(ghost, GHOST_OFFSET_X, GHOST_OFFSET_Y);
|
|
39
|
+
} catch {
|
|
40
|
+
/* setDragImage is unsupported in some embedded webviews — the default ghost is fine */
|
|
41
|
+
}
|
|
42
|
+
// The snapshot is synchronous, so the node can go as soon as this task yields.
|
|
43
|
+
requestAnimationFrame(() => ghost.remove());
|
|
44
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one pure function every drop target asks "may this land here, and as what?".
|
|
3
|
+
*
|
|
4
|
+
* It runs twice per drop: on `dragover`, where Chromium exposes only `dataTransfer.types`
|
|
5
|
+
* and the in-flight payload has to come from the module ref set at `dragstart`, and again
|
|
6
|
+
* on `drop`, where the real payload is readable. Keeping the rule in one testable place is
|
|
7
|
+
* what stops the highlight and the actual outcome from disagreeing.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
allAlreadyIn, ENTRY_DRAG_MIME, isSelfOrDescendant, resolveDropOperation,
|
|
12
|
+
type DropOperation, type EntryDragPayload,
|
|
13
|
+
} from "./entry-drag-payload";
|
|
14
|
+
|
|
15
|
+
export type DropRejection = "not-an-entry-drag" | "no-target" | "self-or-descendant" | "same-directory";
|
|
16
|
+
|
|
17
|
+
export type DropDecision =
|
|
18
|
+
| { accept: true; op: DropOperation }
|
|
19
|
+
| { accept: false; reason: DropRejection };
|
|
20
|
+
|
|
21
|
+
export interface DropDecisionInput {
|
|
22
|
+
/** `dataTransfer.types` — the only thing readable during `dragover`. */
|
|
23
|
+
types: readonly string[];
|
|
24
|
+
/** Payload from the module ref (dragover) or from `dataTransfer` (drop); null when unknown. */
|
|
25
|
+
payload: EntryDragPayload | null;
|
|
26
|
+
/** Directory the drop would write into; null when this element is not a directory. */
|
|
27
|
+
targetDir: string | null;
|
|
28
|
+
modifiers: { ctrlKey: boolean; altKey: boolean };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function decideDrop({ types, payload, targetDir, modifiers }: DropDecisionInput): DropDecision {
|
|
32
|
+
if (!types.includes(ENTRY_DRAG_MIME)) return { accept: false, reason: "not-an-entry-drag" };
|
|
33
|
+
if (targetDir == null || targetDir === "") return { accept: false, reason: "no-target" };
|
|
34
|
+
|
|
35
|
+
const op = resolveDropOperation(modifiers);
|
|
36
|
+
// A drag started in another browser tab has the MIME but no readable payload here; accept
|
|
37
|
+
// optimistically so the target still highlights, and let the drop handler re-check.
|
|
38
|
+
if (!payload) return { accept: true, op };
|
|
39
|
+
|
|
40
|
+
if (isSelfOrDescendant(payload.paths, targetDir)) return { accept: false, reason: "self-or-descendant" };
|
|
41
|
+
// Copying into the source folder is a legitimate "duplicate"; moving there is a no-op.
|
|
42
|
+
if (op === "move" && allAlreadyIn(payload.paths, targetDir)) {
|
|
43
|
+
return { accept: false, reason: "same-directory" };
|
|
44
|
+
}
|
|
45
|
+
return { accept: true, op };
|
|
46
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one highlight every claimed drop target draws — a row, a tile, a sidebar place, a
|
|
3
|
+
* breadcrumb crumb, a view background or a project-tree node all use this same ring so the
|
|
4
|
+
* user learns one visual cue instead of a different one per surface. Matches the ring the
|
|
5
|
+
* project tree's own internal drag-over state already drew before this module existed.
|
|
6
|
+
*/
|
|
7
|
+
export const DROP_TARGET_CLASS = "ring-1 ring-dashed ring-primary bg-primary/10";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The payload every PPM entry drag carries, and the pure path guards that decide whether a
|
|
3
|
+
* drop is allowed at all.
|
|
4
|
+
*
|
|
5
|
+
* One MIME type for the whole app: a tab drag (`application/ppm-tab`) and an entry drag are
|
|
6
|
+
* told apart by `dataTransfer.types` alone, so a tab dropped on a folder and a folder
|
|
7
|
+
* dropped on the tab bar both do nothing instead of doing the wrong thing.
|
|
8
|
+
*
|
|
9
|
+
* Paths are always absolute host paths, including the ones a project tree contributes —
|
|
10
|
+
* that is the only address space the explorer and the tree share.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const ENTRY_DRAG_MIME = "application/x-ppm-paths";
|
|
14
|
+
|
|
15
|
+
/** Legacy single-path type the project tree already used for its own internal moves. */
|
|
16
|
+
export const TREE_LEGACY_DRAG_MIME = "application/x-ppm-path";
|
|
17
|
+
|
|
18
|
+
export type EntryDragOrigin = "explorer" | "tree";
|
|
19
|
+
|
|
20
|
+
export type DropOperation = "copy" | "move";
|
|
21
|
+
|
|
22
|
+
export interface EntryDragPayload {
|
|
23
|
+
/** Absolute host paths. */
|
|
24
|
+
paths: string[];
|
|
25
|
+
origin: EntryDragOrigin;
|
|
26
|
+
/** Present when the drag started in a project tree — a tree-internal drop keeps its own routes. */
|
|
27
|
+
projectName?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Windows paths are case-insensitive; a drive letter or a UNC prefix is the cheap tell. */
|
|
31
|
+
function isWindowsPath(path: string): boolean {
|
|
32
|
+
return /^[a-zA-Z]:[\\/]/.test(path) || path.startsWith("\\\\");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Comparable form of a path: one separator style, no trailing separator, case-folded on
|
|
37
|
+
* Windows. Only ever used for comparisons — never for building a path to send to the server.
|
|
38
|
+
*/
|
|
39
|
+
export function normalisePath(path: string): string {
|
|
40
|
+
const unified = path.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41
|
+
return isWindowsPath(path) ? unified.toLowerCase() : unified;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Host separator implied by a path, for the helpers that have no `HostInfo` at hand. */
|
|
45
|
+
export function sepOf(path: string): string {
|
|
46
|
+
return isWindowsPath(path) || path.includes("\\") ? "\\" : "/";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function encodeEntryDrag(payload: EntryDragPayload): string {
|
|
50
|
+
return JSON.stringify(payload);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Tolerant decode — a foreign or truncated payload is "not our drag", never a throw. */
|
|
54
|
+
export function decodeEntryDrag(raw: string | null | undefined): EntryDragPayload | null {
|
|
55
|
+
if (!raw) return null;
|
|
56
|
+
let parsed: unknown;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(raw);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
63
|
+
const candidate = parsed as Partial<EntryDragPayload>;
|
|
64
|
+
if (!Array.isArray(candidate.paths)) return null;
|
|
65
|
+
const paths = candidate.paths.filter((p): p is string => typeof p === "string" && p.length > 0);
|
|
66
|
+
if (paths.length === 0) return null;
|
|
67
|
+
const payload: EntryDragPayload = {
|
|
68
|
+
paths,
|
|
69
|
+
origin: candidate.origin === "tree" ? "tree" : "explorer",
|
|
70
|
+
};
|
|
71
|
+
if (typeof candidate.projectName === "string") payload.projectName = candidate.projectName;
|
|
72
|
+
return payload;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* True when `targetDir` is one of the dragged entries or lives inside one — moving a folder
|
|
77
|
+
* into itself would either delete it or produce infinite nesting, so it is refused outright.
|
|
78
|
+
*/
|
|
79
|
+
export function isSelfOrDescendant(paths: string[], targetDir: string): boolean {
|
|
80
|
+
const target = normalisePath(targetDir);
|
|
81
|
+
return paths.some((path) => {
|
|
82
|
+
const source = normalisePath(path);
|
|
83
|
+
return target === source || target.startsWith(`${source}/`);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** True when every dragged entry already sits directly in `targetDir` — a move would be a no-op. */
|
|
88
|
+
export function allAlreadyIn(paths: string[], targetDir: string): boolean {
|
|
89
|
+
const target = normalisePath(targetDir);
|
|
90
|
+
return paths.every((path) => {
|
|
91
|
+
const source = normalisePath(path);
|
|
92
|
+
const cut = source.lastIndexOf("/");
|
|
93
|
+
const parent = cut <= 0 ? source.slice(0, cut + 1) : source.slice(0, cut);
|
|
94
|
+
return parent === target;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Move is the default; `Ctrl` (and `Alt`, which is macOS's own copy modifier) switches to
|
|
100
|
+
* copy. Read again at drop time, because the user is allowed to change their mind mid-drag.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveDropOperation(modifiers: { ctrlKey: boolean; altKey: boolean }): DropOperation {
|
|
103
|
+
return modifiers.ctrlKey || modifiers.altKey ? "copy" : "move";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** `text/uri-list` mirror of the payload, so non-PPM drop targets see something meaningful. */
|
|
107
|
+
export function toFileUriList(paths: string[]): string {
|
|
108
|
+
return paths
|
|
109
|
+
.map((path) => {
|
|
110
|
+
const unified = path.replace(/\\/g, "/");
|
|
111
|
+
const absolute = unified.startsWith("/") ? unified : `/${unified}`;
|
|
112
|
+
return `file://${absolute.split("/").map(encodeURIComponent).join("/")}`;
|
|
113
|
+
})
|
|
114
|
+
.join("\r\n");
|
|
115
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The payload of the drag currently in flight, kept in a module-level ref.
|
|
3
|
+
*
|
|
4
|
+
* Chromium refuses to read `dataTransfer` during `dragover` (only `types` is exposed, for
|
|
5
|
+
* privacy), so a drop target that has to decide "is this folder one of the dragged ones?"
|
|
6
|
+
* before the drop cannot get the paths from the event. Every drag in this app starts in
|
|
7
|
+
* this app, so stashing the payload here at `dragstart` and clearing it at `dragend` gives
|
|
8
|
+
* hover-time validation without weakening the drop-time check, which still reads the real
|
|
9
|
+
* `dataTransfer`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { EntryDragPayload } from "./entry-drag-payload";
|
|
13
|
+
|
|
14
|
+
let inFlight: EntryDragPayload | null = null;
|
|
15
|
+
|
|
16
|
+
export function setInFlightDrag(payload: EntryDragPayload): void {
|
|
17
|
+
inFlight = payload;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getInFlightDrag(): EntryDragPayload | null {
|
|
21
|
+
return inFlight;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function clearInFlightDrag(): void {
|
|
25
|
+
inFlight = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Last-resort net: a source row that unmounts mid-drag (e.g. a spring-load navigation) can
|
|
29
|
+
// skip its own `dragend`, and the row would otherwise never clear this ref itself. A
|
|
30
|
+
// window-level bubble listener still clears it for any drop/dragend that reaches this far —
|
|
31
|
+
// every drop target that actually claims one already clears it itself first.
|
|
32
|
+
if (typeof window !== "undefined") {
|
|
33
|
+
window.addEventListener("dragend", clearInFlightDrag);
|
|
34
|
+
window.addEventListener("drop", clearInFlightDrag);
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The last gate a dropped payload passes before it touches the filesystem.
|
|
3
|
+
*
|
|
4
|
+
* Everything funnels through the clipboard module's `transfer()`, the same call Paste
|
|
5
|
+
* makes, so drag-and-drop inherits the collision prompt, the trash-first Replace, the
|
|
6
|
+
* cross-device (`EXDEV`) fallback handled server-side and the `fsChanged` refresh of both
|
|
7
|
+
* the source and the destination directory. There are deliberately no optimistic updates:
|
|
8
|
+
* the panes only move once the server has confirmed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import { transfer, type TransferContext } from "../actions/explorer-actions-clipboard";
|
|
13
|
+
import { isSelfOrDescendant, type DropOperation, type EntryDragPayload } from "./entry-drag-payload";
|
|
14
|
+
|
|
15
|
+
/** How a surface performs the transfer — an explorer window passes its bound action. */
|
|
16
|
+
export type DropRunner = (paths: string[], dstDir: string, op: DropOperation) => Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Re-checks the self/descendant guard the hover-time decision already applied. The modifier
|
|
20
|
+
* and the pointer can both change between the last `dragover` and the `drop`, and a payload
|
|
21
|
+
* that arrives without ever having been hovered (a target reached by keyboard-driven drop in
|
|
22
|
+
* some browsers) never went through that check at all.
|
|
23
|
+
*/
|
|
24
|
+
export async function executeEntryDrop(
|
|
25
|
+
payload: EntryDragPayload,
|
|
26
|
+
dstDir: string,
|
|
27
|
+
op: DropOperation,
|
|
28
|
+
run: DropRunner,
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
if (payload.paths.length === 0) return;
|
|
31
|
+
if (isSelfOrDescendant(payload.paths, dstDir)) {
|
|
32
|
+
toast.error("Cannot place an item inside itself");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await run(payload.paths, dstDir, op);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A `DropRunner` for surfaces that own a `TransferContext` instead of an explorer action set. */
|
|
39
|
+
export function transferRunner(context: TransferContext): DropRunner {
|
|
40
|
+
return async (paths, dstDir, op) => {
|
|
41
|
+
await transfer(paths, dstDir, op, context);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrolls a container while an entry drag hovers near its edge, so a destination that is
|
|
3
|
+
* off-screen is still reachable without letting go.
|
|
4
|
+
*
|
|
5
|
+
* Listens natively on the element rather than through React props: `dragover` bubbles from
|
|
6
|
+
* whatever row the pointer is really over, and the row's own React handler stops the
|
|
7
|
+
* synthetic event — which would hide every edge hover from a JSX-level handler here.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, type RefObject } from "react";
|
|
11
|
+
import { ENTRY_DRAG_MIME } from "./entry-drag-payload";
|
|
12
|
+
|
|
13
|
+
/** Distance from an edge at which scrolling starts. */
|
|
14
|
+
const EDGE_ZONE_PX = 48;
|
|
15
|
+
/** Fastest scroll step per frame, reached at the very edge. */
|
|
16
|
+
const MAX_STEP_PX = 16;
|
|
17
|
+
|
|
18
|
+
function stepFor(distance: number): number {
|
|
19
|
+
if (distance >= EDGE_ZONE_PX) return 0;
|
|
20
|
+
const intensity = Math.min(1, Math.max(0, (EDGE_ZONE_PX - distance) / EDGE_ZONE_PX));
|
|
21
|
+
return Math.ceil(intensity * MAX_STEP_PX);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useDragAutoScroll(ref: RefObject<HTMLElement | null>): void {
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const element = ref.current;
|
|
27
|
+
if (!element) return;
|
|
28
|
+
|
|
29
|
+
let frame: number | null = null;
|
|
30
|
+
let velocity = { x: 0, y: 0 };
|
|
31
|
+
|
|
32
|
+
const stop = () => {
|
|
33
|
+
if (frame !== null) cancelAnimationFrame(frame);
|
|
34
|
+
frame = null;
|
|
35
|
+
velocity = { x: 0, y: 0 };
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const tick = () => {
|
|
39
|
+
if (velocity.x === 0 && velocity.y === 0) {
|
|
40
|
+
frame = null;
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
element.scrollBy(velocity.x, velocity.y);
|
|
44
|
+
frame = requestAnimationFrame(tick);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const onDragOver = (event: DragEvent) => {
|
|
48
|
+
if (!event.dataTransfer?.types.includes(ENTRY_DRAG_MIME)) return;
|
|
49
|
+
const box = element.getBoundingClientRect();
|
|
50
|
+
const up = stepFor(event.clientY - box.top);
|
|
51
|
+
const down = stepFor(box.bottom - event.clientY);
|
|
52
|
+
const left = stepFor(event.clientX - box.left);
|
|
53
|
+
const right = stepFor(box.right - event.clientX);
|
|
54
|
+
velocity = { x: right - left, y: down - up };
|
|
55
|
+
if (frame === null && (velocity.x !== 0 || velocity.y !== 0)) frame = requestAnimationFrame(tick);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
element.addEventListener("dragover", onDragOver);
|
|
59
|
+
element.addEventListener("dragleave", stop);
|
|
60
|
+
element.addEventListener("drop", stop);
|
|
61
|
+
window.addEventListener("dragend", stop);
|
|
62
|
+
return () => {
|
|
63
|
+
stop();
|
|
64
|
+
element.removeEventListener("dragover", onDragOver);
|
|
65
|
+
element.removeEventListener("dragleave", stop);
|
|
66
|
+
element.removeEventListener("drop", stop);
|
|
67
|
+
window.removeEventListener("dragend", stop);
|
|
68
|
+
};
|
|
69
|
+
}, [ref]);
|
|
70
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A self-contained transfer runner for the drop surfaces that live outside an explorer
|
|
3
|
+
* window's action set: the places sidebar, the breadcrumb and the project file tree.
|
|
4
|
+
*
|
|
5
|
+
* Those three are rendered by components that never receive `ExplorerActions`, so they own
|
|
6
|
+
* the two interruptions `transfer()` can raise (name collision, and a Replace on a host with
|
|
7
|
+
* no Trash) themselves. The dialogs are the very same components the window body renders —
|
|
8
|
+
* only the promise wiring is local — so a collision looks identical wherever the drop landed.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
|
12
|
+
import type { CollisionChoice, CollisionRequest, TransferContext } from "../actions/explorer-actions-clipboard";
|
|
13
|
+
import type { ExplorerDialogState } from "../actions/use-explorer-actions";
|
|
14
|
+
import { ExplorerDialogs } from "../explorer-dialogs";
|
|
15
|
+
import { transferRunner, type DropRunner } from "./entry-drop-executor";
|
|
16
|
+
|
|
17
|
+
export interface DropTransfer {
|
|
18
|
+
run: DropRunner;
|
|
19
|
+
/** Render this somewhere inside the surface — it is empty until a prompt is raised. */
|
|
20
|
+
prompts: ReactNode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useDropTransfer(sep: string, platform?: string): DropTransfer {
|
|
24
|
+
const [collision, setCollision] = useState<ExplorerDialogState["collision"]>(null);
|
|
25
|
+
const [permanentOverwrite, setPermanentOverwrite] = useState<ExplorerDialogState["permanentOverwrite"]>(null);
|
|
26
|
+
|
|
27
|
+
// The separator can change under a long-lived surface (the tree follows the active
|
|
28
|
+
// project), so the context reads it live rather than closing over the first value.
|
|
29
|
+
// Assigned in an effect, not during render — React may discard a render before commit.
|
|
30
|
+
const sepRef = useRef(sep);
|
|
31
|
+
useEffect(() => { sepRef.current = sep; }, [sep]);
|
|
32
|
+
|
|
33
|
+
const context = useMemo<TransferContext>(
|
|
34
|
+
() => ({
|
|
35
|
+
get sep() {
|
|
36
|
+
return sepRef.current;
|
|
37
|
+
},
|
|
38
|
+
resolve: (request: CollisionRequest) =>
|
|
39
|
+
new Promise<CollisionChoice>((resolve) => {
|
|
40
|
+
setCollision({
|
|
41
|
+
...request,
|
|
42
|
+
resolve: (choice) => {
|
|
43
|
+
setCollision(null);
|
|
44
|
+
resolve(choice);
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}),
|
|
48
|
+
confirmPermanentOverwrite: (name: string) =>
|
|
49
|
+
new Promise<boolean>((resolve) => {
|
|
50
|
+
setPermanentOverwrite({
|
|
51
|
+
name,
|
|
52
|
+
resolve: (proceed) => {
|
|
53
|
+
setPermanentOverwrite(null);
|
|
54
|
+
resolve(proceed);
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}),
|
|
58
|
+
}),
|
|
59
|
+
[],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const run = useMemo<DropRunner>(() => transferRunner(context), [context]);
|
|
63
|
+
|
|
64
|
+
const prompts = (
|
|
65
|
+
<ExplorerDialogs
|
|
66
|
+
dialogs={{
|
|
67
|
+
collision,
|
|
68
|
+
permanentOverwrite,
|
|
69
|
+
pendingDelete: null,
|
|
70
|
+
properties: null,
|
|
71
|
+
inlineError: null,
|
|
72
|
+
closeDelete: () => {},
|
|
73
|
+
runPermanentDelete: () => {},
|
|
74
|
+
closeProperties: () => {},
|
|
75
|
+
}}
|
|
76
|
+
platform={platform}
|
|
77
|
+
sep={sep}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return { run, prompts };
|
|
82
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Makes a row, tile or tree node the source of an entry drag.
|
|
3
|
+
*
|
|
4
|
+
* Returns nothing at all on a coarse pointer: HTML5 drag has no touch equivalent, and the
|
|
5
|
+
* guideline reserves a long press for the context menu, so a phone must not even get the
|
|
6
|
+
* `draggable` attribute (it would swallow the press). Cut/Copy/Paste stays the touch path.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useCallback, useMemo, type DragEvent } from "react";
|
|
10
|
+
import { setEntryDragGhost } from "./drag-ghost";
|
|
11
|
+
import { clearInFlightDrag, setInFlightDrag } from "./entry-drag-state";
|
|
12
|
+
import {
|
|
13
|
+
encodeEntryDrag, ENTRY_DRAG_MIME, toFileUriList,
|
|
14
|
+
type EntryDragOrigin,
|
|
15
|
+
} from "./entry-drag-payload";
|
|
16
|
+
import { usePrefersCoarsePointer } from "../use-coarse-long-press";
|
|
17
|
+
|
|
18
|
+
export interface EntryDragSourceOptions {
|
|
19
|
+
/** Absolute host paths this drag carries — the whole selection when the row is selected. */
|
|
20
|
+
paths: string[];
|
|
21
|
+
/** Ghost labels; defaults to the basenames of `paths`. */
|
|
22
|
+
names?: string[];
|
|
23
|
+
origin: EntryDragOrigin;
|
|
24
|
+
projectName?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Extra `dataTransfer` entries written alongside the shared payload. The project tree
|
|
27
|
+
* keeps writing its own legacy single-path type here so tree-internal drops, which have
|
|
28
|
+
* their own project-scoped route, behave exactly as they did before.
|
|
29
|
+
*/
|
|
30
|
+
extraData?: Record<string, string>;
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Runs before the payload is written — used to make the dragged row the selected one. */
|
|
33
|
+
onDragStarted?(): void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface EntryDragSourceProps {
|
|
37
|
+
draggable?: true;
|
|
38
|
+
onDragStart?(event: DragEvent): void;
|
|
39
|
+
onDragEnd?(event: DragEvent): void;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function basenameOf(path: string): string {
|
|
43
|
+
return path.split(/[/\\]/).filter(Boolean).pop() ?? path;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useEntryDragSource(options: EntryDragSourceOptions): EntryDragSourceProps {
|
|
47
|
+
const { paths, names, origin, projectName, extraData, disabled, onDragStarted } = options;
|
|
48
|
+
const coarse = usePrefersCoarsePointer();
|
|
49
|
+
const enabled = !coarse && !disabled && paths.length > 0;
|
|
50
|
+
|
|
51
|
+
const handleDragStart = useCallback(
|
|
52
|
+
(event: DragEvent) => {
|
|
53
|
+
onDragStarted?.();
|
|
54
|
+
const payload = { paths, origin, ...(projectName ? { projectName } : {}) };
|
|
55
|
+
const data = event.dataTransfer;
|
|
56
|
+
data.setData(ENTRY_DRAG_MIME, encodeEntryDrag(payload));
|
|
57
|
+
// Accessibility / interop mirror: anything that understands dropped files sees paths.
|
|
58
|
+
data.setData("text/uri-list", toFileUriList(paths));
|
|
59
|
+
data.setData("text/plain", paths.join("\n"));
|
|
60
|
+
for (const [type, value] of Object.entries(extraData ?? {})) data.setData(type, value);
|
|
61
|
+
data.effectAllowed = "copyMove";
|
|
62
|
+
setEntryDragGhost(data, names ?? paths.map(basenameOf));
|
|
63
|
+
// Hover-time validation cannot read `dataTransfer`; see `entry-drag-state.ts`.
|
|
64
|
+
setInFlightDrag(payload);
|
|
65
|
+
},
|
|
66
|
+
[paths, names, origin, projectName, extraData, onDragStarted],
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
return useMemo<EntryDragSourceProps>(
|
|
70
|
+
() => (enabled ? { draggable: true, onDragStart: handleDragStart, onDragEnd: clearInFlightDrag } : {}),
|
|
71
|
+
[enabled, handleDragStart],
|
|
72
|
+
);
|
|
73
|
+
}
|