@hienlh/ppm 0.17.57 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-run.sh +5 -0
- package/CHANGELOG.md +16 -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-BLYgg033.js} +1 -1
- package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-jfMqUuRd.js} +1 -1
- package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-wytn9PMm.js} +1 -1
- package/dist/web/assets/{api-client-COQtP-dB.js → api-client-B9OFv3nY.js} +1 -1
- package/dist/web/assets/api-client-CKo60P6G.js +1 -0
- package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-DGhshDRg.js} +1 -1
- package/dist/web/assets/api-settings-fdkhqLSF.js +1 -0
- package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-okiyhhp9.js} +1 -1
- package/dist/web/assets/{apl-DSANFLx0.js → apl-i-ahKx6O.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
- package/dist/web/assets/{astro-CXIotiNS.js → astro-DVBCJfG6.js} +1 -1
- package/dist/web/assets/{audio-preview-D7jNc8mi.js → audio-preview-y8XpCNrh.js} +1 -1
- package/dist/web/assets/{blade-BvrP2gPd.js → blade-BHlL8SqU.js} +1 -1
- package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-D-UOfR1F.js} +1 -1
- package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-Bn6rucli.js} +1 -1
- package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
- package/dist/web/assets/chat-tab-BcR-3_6R.js +13 -0
- package/dist/web/assets/clock-B1CPBp2g.js +1 -0
- package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Byn9FYKu.js} +1 -1
- package/dist/web/assets/code-editor-CJ8g7dqm.js +10 -0
- package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-DjBl4gm-.js} +1 -1
- package/dist/web/assets/{conflict-editor-CpGcSopK.js → conflict-editor-BP7JFZzG.js} +3 -3
- package/dist/web/assets/context-menu-BSE5yuMG.js +1 -0
- package/dist/web/assets/copy-Su9m2utd.js +1 -0
- package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CKzic-3E.js} +1 -1
- package/dist/web/assets/{crystal-DMjCL--0.js → crystal-Bkg-Ly_g.js} +1 -1
- package/dist/web/assets/csv-preview-xAwUDNat.js +2 -0
- package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-DMTCHIG9.js} +1 -1
- package/dist/web/assets/{database-viewer-DTMmic31.js → database-viewer-BVLWYPa4.js} +1 -1
- package/dist/web/assets/dialog-D__LRQTn.js +1 -0
- package/dist/web/assets/diff-viewer-BheSOHfl.js +4 -0
- package/dist/web/assets/{docx-preview-CR2ZtvNP.js → docx-preview-CGlMqltP.js} +1 -1
- package/dist/web/assets/{edge-CTom35R_.js → edge-DVbZnd3Y.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-ClJAJwlL.js} +1 -1
- package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-Dwi8Wu7W.js} +1 -1
- package/dist/web/assets/{elm-CpD28xsW.js → elm-DtIv-HXJ.js} +1 -1
- package/dist/web/assets/{erb-DHX2IIEs.js → erb-D3HEDz1x.js} +1 -1
- package/dist/web/assets/{erlang-B_etLcPG.js → erlang-2MjTCzGz.js} +1 -1
- package/dist/web/assets/{esm-PmlN1RTs.js → esm-BfqDE09P.js} +1 -1
- package/dist/web/assets/explorer-body-DE5339g9.js +2 -0
- package/dist/web/assets/explorer-window-content-BrTHAoXM.js +1 -0
- package/dist/web/assets/{extension-webview-BTCFWY2A.js → extension-webview-DRq1tWC1.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
- package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-Bxbn_gbr.js} +1 -1
- package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-sFMY4D2Q.js} +1 -1
- package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-BzB9Lio5.js} +1 -1
- package/dist/web/assets/git-log-panel-pjyOjchU.js +1 -0
- package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-CKSVd848.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
- package/dist/web/assets/{glide-data-grid-LinTYl0_.js → glide-data-grid-LX3N4SsJ.js} +9 -9
- package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BY8aExAJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-Bak54dBX.js} +1 -1
- package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-nR2eaJQe.js} +1 -1
- package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-CGPPFAU6.js} +1 -1
- package/dist/web/assets/group-chat-tab-CVQfptkT.js +1 -0
- package/dist/web/assets/{hack-sxywdIAQ.js → hack-CXIMX-Ol.js} +1 -1
- package/dist/web/assets/{haml-Dts9JlPU.js → haml-tPY-9Pfm.js} +1 -1
- package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BuW1LT7T.js} +1 -1
- package/dist/web/assets/{html-2p0RvXi4.js → html-bgVcwZ-t.js} +1 -1
- package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-D6-IxTgn.js} +1 -1
- package/dist/web/assets/{http-CHd2gYPf.js → http-CJ1zyB-h.js} +1 -1
- package/dist/web/assets/{hurl-boMPgDm9.js → hurl-CmoT7l3T.js} +1 -1
- package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CztOsL8o.js} +1 -1
- package/dist/web/assets/{image-preview-0SGb7_tH.js → image-preview-BDySmiim.js} +1 -1
- package/dist/web/assets/index-XoDY9bc0.js +52 -0
- package/dist/web/assets/index-YCTraslE.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
- package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-B1pq3jJq.js} +1 -1
- package/dist/web/assets/{jison-C2vJYjbJ.js → jison-D8B5pCwA.js} +1 -1
- package/dist/web/assets/{julia-DJ8MrQPA.js → julia-D0K3bwpU.js} +1 -1
- package/dist/web/assets/{just-BvQHq7VE.js → just-B9Jlddu-.js} +1 -1
- package/dist/web/assets/keybindings-store-BE1jVUZW.js +1 -0
- package/dist/web/assets/{latex-ChNeshUC.js → latex-BPJSSE6j.js} +1 -1
- package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-lv10fo4U.js} +1 -1
- package/dist/web/assets/{lua-D8NNsLbC.js → lua-PNuaIBEF.js} +1 -1
- package/dist/web/assets/{markdown-renderer-DSxSupDq.js → markdown-renderer-B8Kz5rvP.js} +3 -3
- package/dist/web/assets/markdown-renderer-Btync7yg.js +1 -0
- package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Du3frD6X.js} +1 -1
- package/dist/web/assets/{mdc-omkodQB5.js → mdc-BeDVcFRl.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-CzRXE6mY.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-CDU8xriw.js +1 -0
- package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-DkcXP71s.js} +1 -1
- package/dist/web/assets/{nginx-ByvWruGD.js → nginx-C5n8N_bt.js} +1 -1
- package/dist/web/assets/{nim-BrMojX4O.js → nim-CIhyfs-A.js} +1 -1
- package/dist/web/assets/notification-store-Bi7VHAy4.js +1 -0
- package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-B8P26qGC.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
- package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-gbNJvZB_.js} +1 -1
- package/dist/web/assets/{pdf-preview-DEn8amrO.js → pdf-preview-CtZWvujY.js} +1 -1
- package/dist/web/assets/{perl-Bd_0l-b4.js → perl-Cf31sZYE.js} +1 -1
- package/dist/web/assets/{php-D-DLTnWJ.js → php-Dzl_MDIW.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
- package/dist/web/assets/{postgres-viewer-CjrM15gz.js → postgres-viewer-fqMUi0xS.js} +3 -3
- package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-D1D1MN03.js} +1 -1
- package/dist/web/assets/{pug-D2aO-l2T.js → pug-CxSYHrB3.js} +1 -1
- package/dist/web/assets/{qml-CyQVzNXS.js → qml-DlEjGhwN.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
- package/dist/web/assets/{razor-8tvJXsO3.js → razor-KVt5uW14.js} +1 -1
- package/dist/web/assets/{rst-C4AZyQQl.js → rst-D5BwNTnw.js} +1 -1
- package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-D5fejQwd.js} +1 -1
- package/dist/web/assets/{sas-BtnF4zaV.js → sas-Bj7B1CB2.js} +1 -1
- package/dist/web/assets/{scss-GAyp5euH.js → scss-BB0CUoCL.js} +1 -1
- package/dist/web/assets/settings-store-Bm7uL8VA.js +2 -0
- package/dist/web/assets/settings-tab-DxtBG0DW.js +1 -0
- package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-BdjEZUMc.js} +1 -1
- package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-Bp1OA1rR.js} +1 -1
- package/dist/web/assets/{soy-DrLNhqfp.js → soy-DlYz3wsC.js} +1 -1
- package/dist/web/assets/sparkles-BvJ1EGWd.js +1 -0
- package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CdfII5Jd.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CT5M9PKQ.js → sql-query-editor-D2UxRvG7.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CTBwzT-r.js +1 -0
- package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BvUA6GUW.js} +1 -1
- package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql--DLNbTyI.js} +1 -1
- package/dist/web/assets/{svelte-23si1KV5.js → svelte-CBfSsG4n.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-De3oz9zD.js → system-monitor-tab-Bscf6tzK.js} +1 -1
- package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-DnNnaAcE.js} +1 -1
- package/dist/web/assets/{templ-CPxlP4vF.js → templ-Dv8zpKZW.js} +1 -1
- package/dist/web/assets/{terminal-tab-CciNqtMc.js → terminal-tab-CTKU5gaF.js} +3 -3
- package/dist/web/assets/{tex-ugJpgYPc.js → tex-CsrOiqh1.js} +1 -1
- package/dist/web/assets/tool-cards-xstpayaW.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
- package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-3T98k4EJ.js} +1 -1
- package/dist/web/assets/{twig-B7VUm2Ca.js → twig-u6QZ45FH.js} +1 -1
- package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-BLRmhlZI.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-k-17kQqf.js → use-monaco-theme-DxYstgYa.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
- package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CXJtBULr.js} +1 -1
- package/dist/web/assets/{video-preview-D-S_YnG6.js → video-preview-cfaoRxG3.js} +1 -1
- package/dist/web/assets/{vue-m75pWowk.js → vue-CxIsxtwn.js} +1 -1
- package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-BGL5qnuH.js} +1 -1
- package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DHPh_gMr.js} +1 -1
- package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-vM2m7J.js} +1 -1
- package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-DvTXNwuO.js} +1 -1
- package/dist/web/index.html +26 -23
- 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/layout/command-palette.tsx +10 -0
- package/src/web/components/layout/mobile-drawer.tsx +11 -2
- package/src/web/components/layout/nav-section-rail.tsx +7 -1
- 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/file-browser-picker.tsx +1 -18
- package/src/web/lib/file-download.ts +6 -4
- package/src/web/lib/fs-api.ts +185 -0
- 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/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/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-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/{abap-BM5PMK0-.js → abap-C1K0-wzc.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-CJhQVjLA.js} +0 -0
- /package/dist/web/assets/{ada-DzO1tMqV.js → ada-Bt8r9fME.js} +0 -0
- /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-DF1I8qi3.js} +0 -0
- /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CnlleE6P.js} +0 -0
- /package/dist/web/assets/{apex-3O2170Ju.js → apex-j0C6Xmk6.js} +0 -0
- /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-DO7AnO8U.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-i83AydZj.js} +0 -0
- /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DbBA6-sF.js} +0 -0
- /package/dist/web/assets/{asm-p1-PUkh3.js → asm-B5S47Rgr.js} +0 -0
- /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-De_IaSKw.js} +0 -0
- /package/dist/web/assets/{awk-Cj93pRL8.js → awk-CfyLHXqS.js} +0 -0
- /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-DTLdZ1Si.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-DTOfkzhu.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-DsAECCMv.js} +0 -0
- /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-CumGp9A7.js} +0 -0
- /package/dist/web/assets/{bat-Cg8hlSct.js → bat-Ca2SBbYN.js} +0 -0
- /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-CqyXBwwF.js} +0 -0
- /package/dist/web/assets/{berry-BqVQYZZr.js → berry-yl-kJr_H.js} +0 -0
- /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DoFFszjV.js} +0 -0
- /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-CWPi4nfK.js} +0 -0
- /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-DXew-ut0.js} +0 -0
- /package/dist/web/assets/{bot-E9mcVZnr.js → bot-BHXjpQ7x.js} +0 -0
- /package/dist/web/assets/{c-BQFvJ6Ux.js → c-clbySDvd.js} +0 -0
- /package/dist/web/assets/{c3-Da53KeG6.js → c3-B65jvSM1.js} +0 -0
- /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-Cm8tQTHN.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-DLeGslh_.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-CR5GeHws.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
- /package/dist/web/assets/{chevron-right-DeUue2aK.js → chevron-right-CtcHmZg9.js} +0 -0
- /package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-EVgisosn.js} +0 -0
- /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-DPulbvKH.js} +0 -0
- /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-DFuWS20u.js} +0 -0
- /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DejCqcu4.js} +0 -0
- /package/dist/web/assets/{code-TekEnDFM.js → code-Dupqw5Fr.js} +0 -0
- /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BwAyqWIH.js} +0 -0
- /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-DgSwFjqI.js} +0 -0
- /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-CxIVJCtf.js} +0 -0
- /package/dist/web/assets/{coq-D2bX6-8g.js → coq-DLSjSnmf.js} +0 -0
- /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-C1HHzXBp.js} +0 -0
- /package/dist/web/assets/{css-CIjciIIk.js → css-VXsMY--y.js} +0 -0
- /package/dist/web/assets/{csv-DW48Z5d-.js → csv-BEbcJdHz.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-BkDnFqUD.js} +0 -0
- /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-1cMqojSQ.js} +0 -0
- /package/dist/web/assets/{d-CxaScvxf.js → d-BnYjRXtH.js} +0 -0
- /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-XfbaH7kZ.js} +0 -0
- /package/dist/web/assets/{dart-BmpakMe-.js → dart-Ca7s-8hK.js} +0 -0
- /package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-DP-ZexEN.js} +0 -0
- /package/dist/web/assets/{database-COYAMZaw.js → database-Dj0DxgtX.js} +0 -0
- /package/dist/web/assets/{dax-B1aRUMXT.js → dax-CmuOw-mY.js} +0 -0
- /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-C2gJ6Ovb.js} +0 -0
- /package/dist/web/assets/{diff-CupGmT1a.js → diff-BCEo1Ej7.js} +0 -0
- /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CipvJy9w.js} +0 -0
- /package/dist/web/assets/{dist-m_oJqIDk.js → dist-D__U4ZJz.js} +0 -0
- /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DN2jlyAh.js} +0 -0
- /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-NExMY2kS.js} +0 -0
- /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-B9lbE01y.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-CMBBfKF_.js} +0 -0
- /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DEyDwFsd.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-D_qP2zS6.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-DbHa2U-p.js} +0 -0
- /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-BISkJchP.js} +0 -0
- /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-6B22SfJR.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-D-ZORzYB.js} +0 -0
- /package/dist/web/assets/{fish-Vr62C2vP.js → fish-CubIIvJD.js} +0 -0
- /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-DF55zQ09.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DvOu8jAt.js} +0 -0
- /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-DCPxdLpq.js} +0 -0
- /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-CTZ381FO.js} +0 -0
- /package/dist/web/assets/{genie-B4hkIXNU.js → genie-C_QDtvgU.js} +0 -0
- /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-BcvK42tF.js} +0 -0
- /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-C9afs_4w.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-CKGVrl3Z.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-DW78QukY.js} +0 -0
- /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-BnP2dznS.js} +0 -0
- /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-C2fPZzPG.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
- /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpeKwEIO.js} +0 -0
- /package/dist/web/assets/{gn-DuO2D2eW.js → gn-nUb_Mit0.js} +0 -0
- /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-D7I0v20p.js} +0 -0
- /package/dist/web/assets/{go-BL16cMJO.js → go-D7Qqska7.js} +0 -0
- /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-DS-pne5-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
- /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-uQcywkDH.js} +0 -0
- /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-c6bI4_GT.js} +0 -0
- /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CQXTiEXq.js} +0 -0
- /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-ChETf2bz.js} +0 -0
- /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-BMC4JGoE.js} +0 -0
- /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BusFlhLW.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-2oD0Mz7W.js} +0 -0
- /package/dist/web/assets/{houston-Ddm8AgpV.js → houston-DdbbjqFg.js} +0 -0
- /package/dist/web/assets/{hy-DzZa6alE.js → hy-D_SOuqw9.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-BxLOHO5I.js} +0 -0
- /package/dist/web/assets/{ini-CAvrVa5R.js → ini-ox-CFcg0.js} +0 -0
- /package/dist/web/assets/{java-PRMSQp24.js → java-Bzo8yIal.js} +0 -0
- /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-DHuz9VXn.js} +0 -0
- /package/dist/web/assets/{json-uWUKqOxq.js → json-0_y80FD5.js} +0 -0
- /package/dist/web/assets/{json5-R_LXJXL0.js → json5-CEQ50pGM.js} +0 -0
- /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-CfrLEAKA.js} +0 -0
- /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-fKbrANC2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-DKbkPaQ2.js} +0 -0
- /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-Dxxx1Hqu.js} +0 -0
- /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-DlJL-Oj3.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-BHltayiE.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-D-KXPjw6.js} +0 -0
- /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DvtoJdFo.js} +0 -0
- /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-CTS4MLcx.js} +0 -0
- /package/dist/web/assets/{kusto-Ba87977j.js → kusto-HtSE_sKm.js} +0 -0
- /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-DaXZG9ge.js} +0 -0
- /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-BYtGUfWx.js} +0 -0
- /package/dist/web/assets/{less-Dfn9YC_2.js → less-BoxmU_Mk.js} +0 -0
- /package/dist/web/assets/{lib-DTaN9riD.js → lib-Bpbn00xM.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-Bt5CyUuo.js} +0 -0
- /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CSdk9q9V.js} +0 -0
- /package/dist/web/assets/{log-QW3dqr1D.js → log-B-kp_3CL.js} +0 -0
- /package/dist/web/assets/{logo-DUinJheI.js → logo-DdIsXFDO.js} +0 -0
- /package/dist/web/assets/{luau-B1vcBzl8.js → luau-DUBpLlzQ.js} +0 -0
- /package/dist/web/assets/{make-CqNL10gS.js → make-B3JVjoW_.js} +0 -0
- /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Seon0vHs.js} +0 -0
- /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-Nq-ChX1P.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-CPV4ayXJ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-0kyjYqEd.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-DGpAp5co.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-B0AUn0NG.js} +0 -0
- /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-C2waytxs.js} +0 -0
- /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-DAis6R6I.js} +0 -0
- /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-_rnT2YGD.js} +0 -0
- /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-BSSjchUB.js} +0 -0
- /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-DomAFQsa.js} +0 -0
- /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-bBgtvPyG.js} +0 -0
- /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-Cp9BayW0.js} +0 -0
- /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-CL_9gbmn.js} +0 -0
- /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-BS_fcr-P.js} +0 -0
- /package/dist/web/assets/{move-BvhjG2ic.js → move-C79AH45U.js} +0 -0
- /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-CJ5s3BDf.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
- /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-Jkf9zFQK.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-DRPeaG5J.js} +0 -0
- /package/dist/web/assets/{nix-B9EAdptu.js → nix-D2Jet9DN.js} +0 -0
- /package/dist/web/assets/{nord-DxASd4qG.js → nord-CREVs22j.js} +0 -0
- /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-CAiDi_mR.js} +0 -0
- /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-CVkmJkC1.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DIKDO1Ly.js} +0 -0
- /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-R9qtGQhs.js} +0 -0
- /package/dist/web/assets/{odin-B8pz4gIL.js → odin-DTYNQqVr.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-BSLWUJn5.js} +0 -0
- /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DbkRXG3t.js} +0 -0
- /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-BlPcSnmT.js} +0 -0
- /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-B1Y46ZZB.js} +0 -0
- /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-BhJSJbFv.js} +0 -0
- /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BzxsCTpi.js} +0 -0
- /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DBba7dyP.js} +0 -0
- /package/dist/web/assets/{plus-DcuJuDC3.js → plus-Dm36mgLh.js} +0 -0
- /package/dist/web/assets/{po-Dz4OaDNi.js → po-BvgLlnNo.js} +0 -0
- /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DUaI_1JP.js} +0 -0
- /package/dist/web/assets/{polar-Che1XC2Z.js → polar-C7uILwJn.js} +0 -0
- /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-BgJlpFdw.js} +0 -0
- /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-Dn1MIpZu.js} +0 -0
- /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-Bo-DYzOS.js} +0 -0
- /package/dist/web/assets/{prisma-DCmjceML.js → prisma-CTLs5IdT.js} +0 -0
- /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-QRGun3eG.js} +0 -0
- /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-B7PI7apn.js} +0 -0
- /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CBq0tLrP.js} +0 -0
- /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-DFewOQb2.js} +0 -0
- /package/dist/web/assets/{python-Ctyr4faP.js → python-iWuxKXvT.js} +0 -0
- /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-cXiW3AsF.js} +0 -0
- /package/dist/web/assets/{qss-LbRHzb21.js → qss-DASwAbBL.js} +0 -0
- /package/dist/web/assets/{r-Dvfjp0wc.js → r-TU2eaIPw.js} +0 -0
- /package/dist/web/assets/{racket-DI8v8nzD.js → racket-SkRKqrLY.js} +0 -0
- /package/dist/web/assets/{raku-CX1q9GW0.js → raku-D31lthkZ.js} +0 -0
- /package/dist/web/assets/{react-8hrD3-kv.js → react-DjdYfcEe.js} +0 -0
- /package/dist/web/assets/{red-CSz3QEOw.js → red-CTcQZevb.js} +0 -0
- /package/dist/web/assets/{reg-FMjhUQE-.js → reg-D8YAsTGf.js} +0 -0
- /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CD2uvAWx.js} +0 -0
- /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-CAaI-1fg.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-DR-hJVIj.js} +0 -0
- /package/dist/web/assets/{ron-D0t4kvJG.js → ron-CuUCJ0n4.js} +0 -0
- /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-aFHxW48i.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-wqyh0an4.js} +0 -0
- /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-CI1ywimO.js} +0 -0
- /package/dist/web/assets/{rust-2jBzrC0l.js → rust-p7Imk4G_.js} +0 -0
- /package/dist/web/assets/{sass-DD75Izu_.js → sass-C8JfaqCV.js} +0 -0
- /package/dist/web/assets/{save-BM4p9_9n.js → save-ignW_jk2.js} +0 -0
- /package/dist/web/assets/{scala-DpBKROIu.js → scala-DNxCW1By.js} +0 -0
- /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-BYuUPJoL.js} +0 -0
- /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-DbPeJPUr.js} +0 -0
- /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DMiHlR3q.js} +0 -0
- /package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-BSA9P3nI.js} +0 -0
- /package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-Cb9J9x_n.js} +0 -0
- /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-C4xGcQXI.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-BzgRt_wm.js} +0 -0
- /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-LDeL9PYH.js} +0 -0
- /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-CXE7a5j_.js} +0 -0
- /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-DgUhY74x.js} +0 -0
- /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-DD9C-H-9.js} +0 -0
- /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-CAbDB4D4.js} +0 -0
- /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-kQREPfIc.js} +0 -0
- /package/dist/web/assets/{sql-CoqObhgo.js → sql-CBzeNAD8.js} +0 -0
- /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-L1euxrsM.js} +0 -0
- /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-DvBnYniP.js} +0 -0
- /package/dist/web/assets/{swift-DtMMgvRI.js → swift-DF5NDQp6.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-DbBhB_Hq.js} +0 -0
- /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-D8s2ZW_k.js} +0 -0
- /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-a5-XZJsG.js} +0 -0
- /package/dist/web/assets/{table-Di8pv2Y_.js → table-4P8kS7sg.js} +0 -0
- /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-BLa0URTW.js} +0 -0
- /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-DIoV4XcC.js} +0 -0
- /package/dist/web/assets/{tcl-caTR_frn.js → tcl-ztYAdXfQ.js} +0 -0
- /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-DvpCCiOZ.js} +0 -0
- /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BkHlYUv0.js} +0 -0
- /package/dist/web/assets/{toml-B7D2mIDU.js → toml-acIrxos6.js} +0 -0
- /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-BJB5EVCT.js} +0 -0
- /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-B1MmXGT3.js} +0 -0
- /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-DFceTiqd.js} +0 -0
- /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-Dz9wU5Y1.js} +0 -0
- /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CPuYZijj.js} +0 -0
- /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CfsCIYv8.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-K1a38BwI.js} +0 -0
- /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
- /package/dist/web/assets/{v-D4997FGs.js → v-CGo0jcXA.js} +0 -0
- /package/dist/web/assets/{vala-AFg9DmKE.js → vala-AsSR0hrb.js} +0 -0
- /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-C7tvbXv1.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--a33ND-p.js} +0 -0
- /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-B_Z0h4pn.js} +0 -0
- /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CWL5dZPN.js} +0 -0
- /package/dist/web/assets/{viml-D6EKG66q.js → viml-BglmT14Y.js} +0 -0
- /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-RKFsSqQI.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-D7zDWNmc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-MAp87W5b.js} +0 -0
- /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-CgCBYg1G.js} +0 -0
- /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-BOG7ZeTT.js} +0 -0
- /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
- /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-DpShPKXZ.js} +0 -0
- /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-BakcCblK.js} +0 -0
- /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-NroRWVAO.js} +0 -0
- /package/dist/web/assets/{wit-DIrx_eCI.js → wit-B5CM2pg6.js} +0 -0
- /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-7MTFBJpR.js} +0 -0
- /package/dist/web/assets/{x-BsFLg-T8.js → x-BtzqXv1z.js} +0 -0
- /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-Bwr01G2K.js} +0 -0
- /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-Dj8GATvi.js} +0 -0
- /package/dist/web/assets/{zig-CrzwMpQE.js → zig-LAE-fbS-.js} +0 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disk mutations: new file / new folder, rename, and the two delete flavours.
|
|
3
|
+
*
|
|
4
|
+
* Deleting goes to the OS trash by default. When the host has no trash backend the server
|
|
5
|
+
* answers `NO_TRASH` and the caller is told to offer a permanent delete instead of failing
|
|
6
|
+
* with a message the user cannot act on.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { toast } from "sonner";
|
|
10
|
+
import { fsApi, FsError } from "@/lib/fs-api";
|
|
11
|
+
import { dirnameOf, errorDescription, joinPath } from "../format-file-meta";
|
|
12
|
+
import { fsChanged } from "../explorer-store";
|
|
13
|
+
|
|
14
|
+
/** Characters Windows refuses in a file name. */
|
|
15
|
+
const WINDOWS_ILLEGAL = /[<>:"|?*]/;
|
|
16
|
+
/** Control characters are illegal in a name on every platform. */
|
|
17
|
+
const CONTROL_CHARS = /[\u0000-\u001f]/;
|
|
18
|
+
/** Device names Windows reserves, with or without an extension. */
|
|
19
|
+
const WINDOWS_RESERVED = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Validate a bare entry name. Returns an error message, or null when the name is usable.
|
|
23
|
+
* The server enforces the same rules; checking here keeps the inline input responsive.
|
|
24
|
+
*/
|
|
25
|
+
export function validateEntryName(name: string, platform: string | undefined): string | null {
|
|
26
|
+
if (!name) return "Name cannot be empty";
|
|
27
|
+
if (name === "." || name === "..") return "Reserved name";
|
|
28
|
+
if (name.includes("/") || name.includes("\\")) return "Name cannot contain path separators";
|
|
29
|
+
if (CONTROL_CHARS.test(name)) return "Invalid character in name";
|
|
30
|
+
if (platform === "win32") {
|
|
31
|
+
if (WINDOWS_ILLEGAL.test(name)) return 'Cannot contain < > : " | ? *';
|
|
32
|
+
if (WINDOWS_RESERVED.test(name)) return `"${name}" is a reserved Windows name`;
|
|
33
|
+
// Windows silently strips these, so the created entry would not be the one asked for.
|
|
34
|
+
if (name.endsWith(".") || name.endsWith(" ")) return "Cannot end with a dot or space";
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function createEntry(
|
|
40
|
+
dir: string,
|
|
41
|
+
name: string,
|
|
42
|
+
kind: "file" | "folder",
|
|
43
|
+
sep: string,
|
|
44
|
+
): Promise<boolean> {
|
|
45
|
+
const path = joinPath(dir, name, sep);
|
|
46
|
+
try {
|
|
47
|
+
// `gitInit: false` — an explorer folder is a folder, not a new repository.
|
|
48
|
+
if (kind === "folder") await fsApi.mkdir(path, false);
|
|
49
|
+
else await fsApi.touch(path);
|
|
50
|
+
fsChanged(dir);
|
|
51
|
+
return true;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
toast.error(kind === "folder" ? "Could not create folder" : "Could not create file", {
|
|
54
|
+
description: errorDescription(e),
|
|
55
|
+
});
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function renameEntry(path: string, newName: string, sep: string): Promise<boolean> {
|
|
61
|
+
try {
|
|
62
|
+
await fsApi.rename(path, newName);
|
|
63
|
+
fsChanged(dirnameOf(path, sep));
|
|
64
|
+
return true;
|
|
65
|
+
} catch (e) {
|
|
66
|
+
toast.error("Rename failed", { description: errorDescription(e) });
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface DeleteOutcome {
|
|
72
|
+
removed: number;
|
|
73
|
+
/** Paths the host could not trash — the caller offers a permanent delete for these. */
|
|
74
|
+
needsPermanent: string[];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Delete `paths`. With `permanent` false this moves them to the OS trash and any path the
|
|
79
|
+
* host cannot trash is reported back rather than removed behind the user's back.
|
|
80
|
+
*/
|
|
81
|
+
export async function deleteEntries(
|
|
82
|
+
paths: string[],
|
|
83
|
+
permanent: boolean,
|
|
84
|
+
sep: string,
|
|
85
|
+
): Promise<DeleteOutcome> {
|
|
86
|
+
const outcome: DeleteOutcome = { removed: 0, needsPermanent: [] };
|
|
87
|
+
const touched = new Set<string>();
|
|
88
|
+
|
|
89
|
+
for (const path of paths) {
|
|
90
|
+
touched.add(dirnameOf(path, sep));
|
|
91
|
+
try {
|
|
92
|
+
await fsApi.remove(path, permanent);
|
|
93
|
+
outcome.removed++;
|
|
94
|
+
} catch (e) {
|
|
95
|
+
if (e instanceof FsError && e.code === "NO_TRASH") {
|
|
96
|
+
outcome.needsPermanent.push(path);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
toast.error("Delete failed", { description: errorDescription(e) });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fsChanged(...touched);
|
|
104
|
+
if (outcome.removed > 0) {
|
|
105
|
+
const noun = `${outcome.removed} item${outcome.removed === 1 ? "" : "s"}`;
|
|
106
|
+
toast.success(permanent ? `Deleted ${noun}` : `Moved ${noun} to Trash`);
|
|
107
|
+
}
|
|
108
|
+
return outcome;
|
|
109
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening entries and the read-only menu commands: open in a PPM tab, open a folder in a
|
|
3
|
+
* second explorer window, download, copy path / name.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { toast } from "sonner";
|
|
7
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
8
|
+
import { fsApi } from "@/lib/fs-api";
|
|
9
|
+
import { copyToClipboard } from "@/lib/clipboard";
|
|
10
|
+
import { triggerDownload } from "@/lib/file-download";
|
|
11
|
+
import { useTabStore } from "@/stores/tab-store";
|
|
12
|
+
import { useWindowStore } from "@/components/floating-window/window-store";
|
|
13
|
+
import { canOpenInPpm, viewerKindOf } from "../can-open-in-ppm";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Open a file in the tab that can display it.
|
|
17
|
+
*
|
|
18
|
+
* `projectName` is deliberately absent: these are absolute host paths, and the viewers
|
|
19
|
+
* branch on that to reach `/api/fs/*` instead of the project-scoped routes.
|
|
20
|
+
*/
|
|
21
|
+
export function openEntryInPpm(entry: FsEntry): boolean {
|
|
22
|
+
if (!canOpenInPpm(entry.name)) return false;
|
|
23
|
+
useTabStore.getState().openTab({
|
|
24
|
+
type: viewerKindOf(entry.name) === "sqlite" ? "sqlite" : "editor",
|
|
25
|
+
title: entry.name,
|
|
26
|
+
projectId: null,
|
|
27
|
+
metadata: { filePath: entry.path },
|
|
28
|
+
closable: true,
|
|
29
|
+
});
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Spawn another explorer window rooted at `path`. */
|
|
34
|
+
export function openPathInNewWindow(path: string): string {
|
|
35
|
+
return useWindowStore.getState().open("explorer", { path });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Download files one at a time. Each download needs its own single-use token, and
|
|
40
|
+
* directories have no zip route outside a project, so folders are reported rather than
|
|
41
|
+
* silently skipped.
|
|
42
|
+
*/
|
|
43
|
+
export async function downloadEntries(entries: FsEntry[]): Promise<void> {
|
|
44
|
+
const files = entries.filter((e) => e.type === "file");
|
|
45
|
+
const skipped = entries.length - files.length;
|
|
46
|
+
if (skipped > 0) {
|
|
47
|
+
toast.warning(skipped === 1 ? "Folders cannot be downloaded" : `${skipped} folders skipped`);
|
|
48
|
+
}
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
try {
|
|
51
|
+
triggerDownload(await fsApi.downloadUrl(file.path), file.name);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
toast.error(`Download failed: ${file.name}`, {
|
|
54
|
+
description: e instanceof Error ? e.message : undefined,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Copy plain text to the system clipboard, one value per line, and confirm it. */
|
|
61
|
+
export async function copyTextLines(values: string[], noun: "Path" | "Name"): Promise<void> {
|
|
62
|
+
if (values.length === 0) return;
|
|
63
|
+
await copyToClipboard(values.join("\n"));
|
|
64
|
+
toast.success(
|
|
65
|
+
values.length === 1 ? `${noun} copied` : `${values.length} ${noun.toLowerCase()}s copied`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers shared by `transfer()`: finding a free "name (n)" for Keep-both/same-folder
|
|
3
|
+
* copy, running one copy/move call, and turning a failure into a toast description. Split
|
|
4
|
+
* out of `explorer-actions-clipboard.ts` to keep that file under the line cap.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { fsApi, FsError } from "@/lib/fs-api";
|
|
8
|
+
import { joinPath, suffixName } from "../format-file-meta";
|
|
9
|
+
|
|
10
|
+
/** Highest "name (n)" suffix tried before giving up on a free name. */
|
|
11
|
+
const MAX_KEEP_BOTH_ATTEMPTS = 99;
|
|
12
|
+
|
|
13
|
+
async function pathExists(path: string): Promise<boolean> {
|
|
14
|
+
try {
|
|
15
|
+
await fsApi.stat(path);
|
|
16
|
+
return true;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
if (e instanceof FsError && e.code === "ENOENT") return false;
|
|
19
|
+
// Anything else (permission, protected root) is not a free name either.
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function freeName(dir: string, name: string, sep: string): Promise<string | null> {
|
|
25
|
+
for (let n = 2; n <= MAX_KEEP_BOTH_ATTEMPTS; n++) {
|
|
26
|
+
const candidate = suffixName(name, n);
|
|
27
|
+
if (!(await pathExists(joinPath(dir, candidate, sep)))) return candidate;
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function runOne(source: string, destination: string, op: "copy" | "move"): Promise<void> {
|
|
33
|
+
if (op === "copy") await fsApi.copy(source, destination);
|
|
34
|
+
else await fsApi.move(source, destination);
|
|
35
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Binds the action modules to one window's slice and owns the state of the three
|
|
3
|
+
* interruptions they need: the collision prompt, the permanent-delete confirmation and
|
|
4
|
+
* the properties dialog.
|
|
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.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
13
|
+
import { useExplorerPinsStore } from "../explorer-pins-store";
|
|
14
|
+
import { useExplorerStore, type ExplorerSlice } from "../explorer-store";
|
|
15
|
+
import type { ExplorerNavigation } from "../use-explorer-navigation";
|
|
16
|
+
import { copyTextLines, downloadEntries, openEntryInPpm, openPathInNewWindow } from "./explorer-actions-open";
|
|
17
|
+
import {
|
|
18
|
+
pasteInto, setClipboardPaths, transfer,
|
|
19
|
+
type CollisionChoice, type CollisionRequest,
|
|
20
|
+
} from "./explorer-actions-clipboard";
|
|
21
|
+
import { createEntry, deleteEntries, renameEntry, validateEntryName } from "./explorer-actions-mutate";
|
|
22
|
+
|
|
23
|
+
export interface ExplorerActions {
|
|
24
|
+
openEntry(entry: FsEntry): void;
|
|
25
|
+
openInNewWindow(entry: FsEntry): void;
|
|
26
|
+
cut(entries: FsEntry[]): void;
|
|
27
|
+
copy(entries: FsEntry[]): void;
|
|
28
|
+
paste(targetDir?: string): void;
|
|
29
|
+
startRename(entry: FsEntry): void;
|
|
30
|
+
startCreate(kind: "new-file" | "new-folder"): void;
|
|
31
|
+
commitInline(value: string): Promise<void>;
|
|
32
|
+
cancelInline(): void;
|
|
33
|
+
trash(entries: FsEntry[]): void;
|
|
34
|
+
confirmPermanentDelete(entries: FsEntry[]): void;
|
|
35
|
+
download(entries: FsEntry[]): void;
|
|
36
|
+
copyPath(paths: string[]): void;
|
|
37
|
+
copyName(names: string[]): void;
|
|
38
|
+
togglePin(path: string, name: string): void;
|
|
39
|
+
showProperties(entry: FsEntry): void;
|
|
40
|
+
/** Move or copy arbitrary paths into a directory — also the drag-and-drop entry point. */
|
|
41
|
+
transferInto(paths: string[], dstDir: string, op: "copy" | "move"): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ExplorerDialogState {
|
|
45
|
+
collision: (CollisionRequest & { resolve(choice: CollisionChoice): void }) | null;
|
|
46
|
+
pendingDelete: { paths: string[]; names: string[] } | null;
|
|
47
|
+
/** "Replace" hit a host with no trash backend — confirm a permanent overwrite instead. */
|
|
48
|
+
permanentOverwrite: { name: string; resolve(proceed: boolean): void } | null;
|
|
49
|
+
properties: FsEntry | null;
|
|
50
|
+
inlineError: string | null;
|
|
51
|
+
closeDelete(): void;
|
|
52
|
+
runPermanentDelete(): void;
|
|
53
|
+
closeProperties(): void;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function useExplorerActions(
|
|
57
|
+
windowId: string,
|
|
58
|
+
slice: ExplorerSlice | undefined,
|
|
59
|
+
nav: ExplorerNavigation,
|
|
60
|
+
platform: string | undefined,
|
|
61
|
+
): { actions: ExplorerActions; dialogs: ExplorerDialogState } {
|
|
62
|
+
const patch = useExplorerStore((s) => s.patch);
|
|
63
|
+
const [collision, setCollision] = useState<ExplorerDialogState["collision"]>(null);
|
|
64
|
+
const [pendingDelete, setPendingDelete] = useState<ExplorerDialogState["pendingDelete"]>(null);
|
|
65
|
+
const [permanentOverwrite, setPermanentOverwrite] = useState<ExplorerDialogState["permanentOverwrite"]>(null);
|
|
66
|
+
const [properties, setProperties] = useState<FsEntry | null>(null);
|
|
67
|
+
const [inlineError, setInlineError] = useState<string | null>(null);
|
|
68
|
+
|
|
69
|
+
const sliceRef = useRef(slice);
|
|
70
|
+
sliceRef.current = slice;
|
|
71
|
+
|
|
72
|
+
const context = useMemo(
|
|
73
|
+
() => ({
|
|
74
|
+
get sep() {
|
|
75
|
+
return sliceRef.current?.sep ?? "/";
|
|
76
|
+
},
|
|
77
|
+
resolve: (request: CollisionRequest) =>
|
|
78
|
+
new Promise<CollisionChoice>((resolve) => {
|
|
79
|
+
setCollision({
|
|
80
|
+
...request,
|
|
81
|
+
resolve: (choice) => {
|
|
82
|
+
setCollision(null);
|
|
83
|
+
resolve(choice);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}),
|
|
87
|
+
confirmPermanentOverwrite: (name: string) =>
|
|
88
|
+
new Promise<boolean>((resolve) => {
|
|
89
|
+
setPermanentOverwrite({
|
|
90
|
+
name,
|
|
91
|
+
resolve: (proceed) => {
|
|
92
|
+
setPermanentOverwrite(null);
|
|
93
|
+
resolve(proceed);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}),
|
|
97
|
+
}),
|
|
98
|
+
[],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const currentDir = () => sliceRef.current?.path ?? "";
|
|
102
|
+
|
|
103
|
+
const actions = useMemo<ExplorerActions>(() => ({
|
|
104
|
+
openEntry: (entry) => {
|
|
105
|
+
if (entry.type === "directory") nav.go(entry.path);
|
|
106
|
+
else openEntryInPpm(entry);
|
|
107
|
+
},
|
|
108
|
+
openInNewWindow: (entry) => {
|
|
109
|
+
openPathInNewWindow(entry.type === "directory" ? entry.path : (sliceRef.current?.path ?? entry.path));
|
|
110
|
+
},
|
|
111
|
+
cut: (entries) => setClipboardPaths(entries.map((e) => e.path), "cut"),
|
|
112
|
+
copy: (entries) => setClipboardPaths(entries.map((e) => e.path), "copy"),
|
|
113
|
+
paste: (targetDir) => void pasteInto(targetDir ?? currentDir(), context),
|
|
114
|
+
transferInto: async (paths, dstDir, op) => {
|
|
115
|
+
// Paste toasts its own result; a drag-move/copy silently completing otherwise gives no
|
|
116
|
+
// feedback at all for a long multi-file drag.
|
|
117
|
+
const result = await transfer(paths, dstDir, op, context);
|
|
118
|
+
if (result.succeeded > 0) {
|
|
119
|
+
const verb = op === "copy" ? "Copied" : "Moved";
|
|
120
|
+
toast.success(`${verb} ${result.succeeded} item${result.succeeded === 1 ? "" : "s"}`);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
startRename: (entry) => {
|
|
125
|
+
setInlineError(null);
|
|
126
|
+
patch(windowId, { inlineEdit: { kind: "rename", path: entry.path, initial: entry.name } });
|
|
127
|
+
},
|
|
128
|
+
startCreate: (kind) => {
|
|
129
|
+
setInlineError(null);
|
|
130
|
+
patch(windowId, { inlineEdit: { kind, initial: "" } });
|
|
131
|
+
},
|
|
132
|
+
cancelInline: () => {
|
|
133
|
+
setInlineError(null);
|
|
134
|
+
patch(windowId, { inlineEdit: null });
|
|
135
|
+
},
|
|
136
|
+
commitInline: async (value) => {
|
|
137
|
+
const current = sliceRef.current;
|
|
138
|
+
const edit = current?.inlineEdit;
|
|
139
|
+
if (!current || !edit) return;
|
|
140
|
+
const name = value.trim();
|
|
141
|
+
if (edit.kind === "rename" && name === edit.initial) {
|
|
142
|
+
patch(windowId, { inlineEdit: null });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const problem = validateEntryName(name, platform);
|
|
146
|
+
if (problem) {
|
|
147
|
+
setInlineError(problem);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
setInlineError(null);
|
|
151
|
+
const done = edit.kind === "rename"
|
|
152
|
+
? await renameEntry(edit.path, name, current.sep)
|
|
153
|
+
: await createEntry(current.path, name, edit.kind === "new-folder" ? "folder" : "file", current.sep);
|
|
154
|
+
if (done) patch(windowId, { inlineEdit: null });
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
trash: (entries) => {
|
|
158
|
+
void deleteEntries(entries.map((e) => e.path), false, sliceRef.current?.sep ?? "/").then((outcome) => {
|
|
159
|
+
// The host has no trash backend for these paths; permanent deletion is the only
|
|
160
|
+
// remaining option, so offer it instead of leaving the entries in place silently.
|
|
161
|
+
if (outcome.needsPermanent.length > 0) {
|
|
162
|
+
setPendingDelete({
|
|
163
|
+
paths: outcome.needsPermanent,
|
|
164
|
+
names: outcome.needsPermanent.map((p) => p.split(/[/\\]/).pop() ?? p),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
confirmPermanentDelete: (entries) => {
|
|
170
|
+
if (entries.length === 0) return;
|
|
171
|
+
setPendingDelete({ paths: entries.map((e) => e.path), names: entries.map((e) => e.name) });
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
download: (entries) => void downloadEntries(entries),
|
|
175
|
+
copyPath: (paths) => void copyTextLines(paths, "Path"),
|
|
176
|
+
copyName: (names) => void copyTextLines(names, "Name"),
|
|
177
|
+
togglePin: (path, name) => {
|
|
178
|
+
const pins = useExplorerPinsStore.getState();
|
|
179
|
+
if (pins.isPinned(path)) pins.unpin(path);
|
|
180
|
+
else pins.pin({ path, name });
|
|
181
|
+
},
|
|
182
|
+
showProperties: (entry) => setProperties(entry),
|
|
183
|
+
}), [windowId, patch, nav, context, platform]);
|
|
184
|
+
|
|
185
|
+
const runPermanentDelete = useCallback(() => {
|
|
186
|
+
const target = pendingDelete;
|
|
187
|
+
setPendingDelete(null);
|
|
188
|
+
if (!target) return;
|
|
189
|
+
void deleteEntries(target.paths, true, sliceRef.current?.sep ?? "/");
|
|
190
|
+
}, [pendingDelete]);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
actions,
|
|
194
|
+
dialogs: {
|
|
195
|
+
collision,
|
|
196
|
+
pendingDelete,
|
|
197
|
+
permanentOverwrite,
|
|
198
|
+
properties,
|
|
199
|
+
inlineError,
|
|
200
|
+
closeDelete: () => setPendingDelete(null),
|
|
201
|
+
runPermanentDelete,
|
|
202
|
+
closeProperties: () => setProperties(null),
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
@@ -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";
|