@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,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column (Miller) view fetch state: one entry per breadcrumb ancestor, each cached and
|
|
3
|
+
* fetched independently so drilling one level deeper never re-fetches the columns already
|
|
4
|
+
* on screen. The deepest column always mirrors the window's own (already fetched, sorted,
|
|
5
|
+
* filtered) entries instead of fetching a second time.
|
|
6
|
+
*
|
|
7
|
+
* The reducer is a plain function — given the same paths/state it does the same thing —
|
|
8
|
+
* so "selecting a divergent branch truncates and aborts every column after it" is
|
|
9
|
+
* unit-testable without a DOM or a real network call. It does not call `.abort()` itself
|
|
10
|
+
* (that would be a side effect inside a reducer, and React may invoke a reducer more than
|
|
11
|
+
* once per dispatch under StrictMode); it only *identifies* which controllers went stale,
|
|
12
|
+
* and the hook's effect is the one thing that actually aborts them. The hook is likewise
|
|
13
|
+
* the only thing that calls `fsApi.browse`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useEffect, useReducer, useRef } from "react";
|
|
17
|
+
import { fsApi, type FsEntry } from "@/lib/fs-api";
|
|
18
|
+
|
|
19
|
+
export interface ColumnState {
|
|
20
|
+
path: string;
|
|
21
|
+
entries: FsEntry[];
|
|
22
|
+
loading: boolean;
|
|
23
|
+
error: string | null;
|
|
24
|
+
abort: AbortController;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ColumnsState {
|
|
28
|
+
columns: ColumnState[];
|
|
29
|
+
/** Controllers the last `sync` replaced or truncated; consumed once by an effect. */
|
|
30
|
+
staleControllers: AbortController[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type ColumnsAction =
|
|
34
|
+
| { type: "sync"; paths: string[]; lastEntries: FsEntry[]; lastLoading: boolean; makeAbort(): AbortController }
|
|
35
|
+
| { type: "loaded"; path: string; entries: FsEntry[] }
|
|
36
|
+
| { type: "error"; path: string; message: string };
|
|
37
|
+
|
|
38
|
+
export function reduceColumns(state: ColumnsState, action: ColumnsAction): ColumnsState {
|
|
39
|
+
switch (action.type) {
|
|
40
|
+
case "sync": {
|
|
41
|
+
const { paths, lastEntries, lastLoading, makeAbort } = action;
|
|
42
|
+
const lastIndex = paths.length - 1;
|
|
43
|
+
const next: ColumnState[] = paths.map((path, index) => {
|
|
44
|
+
const existing = state.columns[index];
|
|
45
|
+
const isLast = index === lastIndex;
|
|
46
|
+
if (existing && existing.path === path) {
|
|
47
|
+
// Unchanged column: keep its cache. The deepest column still mirrors the
|
|
48
|
+
// caller's live copy since sort/filter/hidden-toggle may have changed it.
|
|
49
|
+
return isLast ? { ...existing, entries: lastEntries, loading: lastLoading, error: null } : existing;
|
|
50
|
+
}
|
|
51
|
+
return isLast
|
|
52
|
+
? { path, entries: lastEntries, loading: lastLoading, error: null, abort: makeAbort() }
|
|
53
|
+
: { path, entries: [], loading: true, error: null, abort: makeAbort() };
|
|
54
|
+
});
|
|
55
|
+
// Anything at an index whose path changed — including a truncated tail, where there
|
|
56
|
+
// is no replacement at all — had its own fetch in flight and must stop. Collected
|
|
57
|
+
// here, aborted by the hook's effect (this function stays a pure computation).
|
|
58
|
+
const staleControllers: AbortController[] = [];
|
|
59
|
+
for (let i = 0; i < state.columns.length; i++) {
|
|
60
|
+
const stale = state.columns[i]!;
|
|
61
|
+
const replacement = next[i];
|
|
62
|
+
if (!replacement || replacement.path !== stale.path) staleControllers.push(stale.abort);
|
|
63
|
+
}
|
|
64
|
+
return { columns: next, staleControllers };
|
|
65
|
+
}
|
|
66
|
+
case "loaded": {
|
|
67
|
+
return {
|
|
68
|
+
columns: state.columns.map((c) =>
|
|
69
|
+
c.path === action.path ? { ...c, entries: action.entries, loading: false, error: null } : c,
|
|
70
|
+
),
|
|
71
|
+
staleControllers: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
case "error": {
|
|
75
|
+
return {
|
|
76
|
+
columns: state.columns.map((c) =>
|
|
77
|
+
c.path === action.path ? { ...c, loading: false, error: action.message } : c,
|
|
78
|
+
),
|
|
79
|
+
staleControllers: [],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface UseColumnViewState {
|
|
86
|
+
columns: ColumnState[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Ties the reducer to real fetches. `paths` is the breadcrumb chain (root … current); the
|
|
91
|
+
* last path's entries always come from `lastEntries`/`lastLoading`, already owned by the
|
|
92
|
+
* window's own navigation hook.
|
|
93
|
+
*/
|
|
94
|
+
export function useColumnViewState(
|
|
95
|
+
paths: string[],
|
|
96
|
+
lastEntries: FsEntry[],
|
|
97
|
+
lastLoading: boolean,
|
|
98
|
+
): UseColumnViewState {
|
|
99
|
+
const [state, dispatch] = useReducer(reduceColumns, { columns: [], staleControllers: [] });
|
|
100
|
+
// Keyed by controller identity, not path: re-entering a path whose earlier attempt was
|
|
101
|
+
// aborted (drill A -> B -> back to A before B's abort settles) gets a *fresh* controller
|
|
102
|
+
// from the reducer, so it must not be blocked by the guard entry the stale attempt left
|
|
103
|
+
// behind — a path-keyed Set left that fresh column stuck on "Loading…" forever.
|
|
104
|
+
const fetching = useRef(new Set<AbortController>());
|
|
105
|
+
|
|
106
|
+
// A literal NUL separator (never a legal path character on any OS PPM targets) so two
|
|
107
|
+
// paths whose own text happens to contain a space still produce distinct keys.
|
|
108
|
+
const pathsKey = paths.join("\u0000");
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
dispatch({ type: "sync", paths, lastEntries, lastLoading, makeAbort: () => new AbortController() });
|
|
111
|
+
// `pathsKey` is the real dependency for `paths`; `lastEntries`/`lastLoading` are objects
|
|
112
|
+
// that change on every fetch tick, which is exactly when this should re-run too.
|
|
113
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
114
|
+
}, [pathsKey, lastEntries, lastLoading]);
|
|
115
|
+
|
|
116
|
+
// The one place that actually aborts a stale fetch — the reducer only identified it.
|
|
117
|
+
useEffect(() => {
|
|
118
|
+
for (const controller of state.staleControllers) controller.abort();
|
|
119
|
+
}, [state.staleControllers]);
|
|
120
|
+
|
|
121
|
+
// Whatever is still live when this view unmounts (switched to List/Icons, window closed)
|
|
122
|
+
// must stop fetching too; nothing else ever aborts a column that simply falls out of view.
|
|
123
|
+
const columnsRef = useRef(state.columns);
|
|
124
|
+
columnsRef.current = state.columns;
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
return () => {
|
|
127
|
+
for (const column of columnsRef.current) column.abort.abort();
|
|
128
|
+
};
|
|
129
|
+
}, []);
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
const lastIndex = state.columns.length - 1;
|
|
133
|
+
state.columns.forEach((column, index) => {
|
|
134
|
+
// The deepest column is never fetched here — it mirrors the window's own listing.
|
|
135
|
+
if (index === lastIndex) return;
|
|
136
|
+
if (!column.loading || fetching.current.has(column.abort)) return;
|
|
137
|
+
fetching.current.add(column.abort);
|
|
138
|
+
fsApi
|
|
139
|
+
.browse(column.path, { signal: column.abort.signal })
|
|
140
|
+
.then((result) => {
|
|
141
|
+
fetching.current.delete(column.abort);
|
|
142
|
+
if (column.abort.signal.aborted) return;
|
|
143
|
+
dispatch({ type: "loaded", path: column.path, entries: result.entries });
|
|
144
|
+
})
|
|
145
|
+
.catch((e: unknown) => {
|
|
146
|
+
fetching.current.delete(column.abort);
|
|
147
|
+
if (column.abort.signal.aborted || (e instanceof DOMException && e.name === "AbortError")) return;
|
|
148
|
+
dispatch({
|
|
149
|
+
type: "error",
|
|
150
|
+
path: column.path,
|
|
151
|
+
message: e instanceof Error ? e.message : "Failed to read directory",
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}, [state.columns]);
|
|
156
|
+
|
|
157
|
+
return { columns: state.columns };
|
|
158
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selection semantics for the explorer views — plain click, ctrl/cmd toggle, shift range,
|
|
3
|
+
* arrow-key cursor and type-ahead.
|
|
4
|
+
*
|
|
5
|
+
* The reducer is pure and separate from the hook so the fiddly parts (which row does
|
|
6
|
+
* shift-click extend from after a ctrl-click? does type-ahead wrap?) are unit-testable
|
|
7
|
+
* without a DOM.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useMemo, useRef } from "react";
|
|
11
|
+
import { useExplorerStore } from "../explorer-store";
|
|
12
|
+
|
|
13
|
+
export interface SelectionState {
|
|
14
|
+
selection: Set<string>;
|
|
15
|
+
/** The cursor row: shift-range extends from here and arrows move it. */
|
|
16
|
+
anchor: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type SelectionEvent =
|
|
20
|
+
| { type: "click"; path: string; shift?: boolean; ctrl?: boolean }
|
|
21
|
+
| { type: "select-all" }
|
|
22
|
+
| { type: "clear" }
|
|
23
|
+
| { type: "set"; paths: string[]; anchor?: string | null };
|
|
24
|
+
|
|
25
|
+
/** How long a type-ahead buffer stays alive between keystrokes. */
|
|
26
|
+
export const TYPE_AHEAD_RESET_MS = 800;
|
|
27
|
+
|
|
28
|
+
function rangeBetween(order: string[], from: string | null, to: string): string[] {
|
|
29
|
+
const start = from == null ? -1 : order.indexOf(from);
|
|
30
|
+
const end = order.indexOf(to);
|
|
31
|
+
if (end < 0) return [];
|
|
32
|
+
if (start < 0) return [to];
|
|
33
|
+
const [lo, hi] = start <= end ? [start, end] : [end, start];
|
|
34
|
+
return order.slice(lo, hi + 1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Apply a selection event against the current visible row order. */
|
|
38
|
+
export function applySelection(
|
|
39
|
+
state: SelectionState,
|
|
40
|
+
order: string[],
|
|
41
|
+
event: SelectionEvent,
|
|
42
|
+
): SelectionState {
|
|
43
|
+
switch (event.type) {
|
|
44
|
+
case "clear":
|
|
45
|
+
return { selection: new Set(), anchor: null };
|
|
46
|
+
|
|
47
|
+
case "select-all":
|
|
48
|
+
return { selection: new Set(order), anchor: state.anchor ?? order[0] ?? null };
|
|
49
|
+
|
|
50
|
+
case "set":
|
|
51
|
+
return {
|
|
52
|
+
selection: new Set(event.paths),
|
|
53
|
+
anchor: event.anchor !== undefined ? event.anchor : (event.paths[event.paths.length - 1] ?? null),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
case "click": {
|
|
57
|
+
if (event.ctrl) {
|
|
58
|
+
const selection = new Set(state.selection);
|
|
59
|
+
if (selection.has(event.path)) selection.delete(event.path);
|
|
60
|
+
else selection.add(event.path);
|
|
61
|
+
// Ctrl-click moves the anchor so a following shift-click extends from the row
|
|
62
|
+
// the user just touched — matching Explorer and Finder.
|
|
63
|
+
return { selection, anchor: event.path };
|
|
64
|
+
}
|
|
65
|
+
if (event.shift) {
|
|
66
|
+
const range = rangeBetween(order, state.anchor, event.path);
|
|
67
|
+
// The anchor stays put: repeated shift-clicks grow and shrink one range.
|
|
68
|
+
return { selection: new Set(range), anchor: state.anchor ?? event.path };
|
|
69
|
+
}
|
|
70
|
+
return { selection: new Set([event.path]), anchor: event.path };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Row the cursor moves to for an arrow key. Clamps at both ends (no wrap). */
|
|
76
|
+
export function moveCursor(order: string[], anchor: string | null, delta: number): string | null {
|
|
77
|
+
if (order.length === 0) return null;
|
|
78
|
+
const current = anchor == null ? -1 : order.indexOf(anchor);
|
|
79
|
+
if (current < 0) return delta > 0 ? order[0]! : order[order.length - 1]!;
|
|
80
|
+
const next = Math.min(order.length - 1, Math.max(0, current + delta));
|
|
81
|
+
return order[next]!;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* First name matching the typed prefix, searching after the current row and wrapping.
|
|
86
|
+
* Returns the index in `names`, or -1.
|
|
87
|
+
*/
|
|
88
|
+
export function typeAheadIndex(names: string[], buffer: string, currentIndex: number): number {
|
|
89
|
+
if (!buffer) return -1;
|
|
90
|
+
const needle = buffer.toLowerCase();
|
|
91
|
+
// A repeated single character cycles through the matches instead of sticking on one.
|
|
92
|
+
const start = currentIndex < 0 ? 0 : currentIndex + 1;
|
|
93
|
+
for (let i = 0; i < names.length; i++) {
|
|
94
|
+
const index = (start + i) % names.length;
|
|
95
|
+
if (names[index]!.toLowerCase().startsWith(needle)) return index;
|
|
96
|
+
}
|
|
97
|
+
return -1;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface EntrySelection {
|
|
101
|
+
onRowClick(path: string, event: { shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }): void;
|
|
102
|
+
selectOnly(path: string): void;
|
|
103
|
+
selectAll(): void;
|
|
104
|
+
clear(): void;
|
|
105
|
+
setSelection(paths: string[], anchor?: string | null): void;
|
|
106
|
+
moveTo(delta: number, extend: boolean): void;
|
|
107
|
+
/** Feed a printable key; returns the path it landed on, if any. */
|
|
108
|
+
typeAhead(key: string): string | null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Binds the reducer to one window's slice. `order` is the visible row order. */
|
|
112
|
+
export function useEntrySelection(windowId: string, order: string[]): EntrySelection {
|
|
113
|
+
const patch = useExplorerStore((s) => s.patch);
|
|
114
|
+
const orderRef = useRef(order);
|
|
115
|
+
orderRef.current = order;
|
|
116
|
+
const bufferRef = useRef({ text: "", at: 0 });
|
|
117
|
+
|
|
118
|
+
const dispatch = useCallback(
|
|
119
|
+
(event: SelectionEvent) => {
|
|
120
|
+
const slice = useExplorerStore.getState().slices[windowId];
|
|
121
|
+
if (!slice) return;
|
|
122
|
+
patch(windowId, applySelection(slice, orderRef.current, event));
|
|
123
|
+
},
|
|
124
|
+
[windowId, patch],
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const moveTo = useCallback(
|
|
128
|
+
(delta: number, extend: boolean) => {
|
|
129
|
+
const slice = useExplorerStore.getState().slices[windowId];
|
|
130
|
+
if (!slice) return;
|
|
131
|
+
const target = moveCursor(orderRef.current, slice.anchor, delta);
|
|
132
|
+
if (target == null) return;
|
|
133
|
+
dispatch({ type: "click", path: target, shift: extend });
|
|
134
|
+
},
|
|
135
|
+
[windowId, dispatch],
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const typeAhead = useCallback(
|
|
139
|
+
(key: string) => {
|
|
140
|
+
const slice = useExplorerStore.getState().slices[windowId];
|
|
141
|
+
if (!slice) return null;
|
|
142
|
+
const now = Date.now();
|
|
143
|
+
const buffer = now - bufferRef.current.at > TYPE_AHEAD_RESET_MS ? key : bufferRef.current.text + key;
|
|
144
|
+
bufferRef.current = { text: buffer, at: now };
|
|
145
|
+
|
|
146
|
+
const paths = orderRef.current;
|
|
147
|
+
const names = paths.map((p) => p.split(/[/\\]/).pop() ?? p);
|
|
148
|
+
// With a one-character buffer the search starts after the cursor so pressing the
|
|
149
|
+
// same letter walks through every match.
|
|
150
|
+
const from = buffer.length === 1 && slice.anchor ? paths.indexOf(slice.anchor) : -1;
|
|
151
|
+
const index = typeAheadIndex(names, buffer, from);
|
|
152
|
+
if (index < 0) return null;
|
|
153
|
+
dispatch({ type: "click", path: paths[index]! });
|
|
154
|
+
return paths[index]!;
|
|
155
|
+
},
|
|
156
|
+
[windowId, dispatch],
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const onRowClick = useCallback(
|
|
160
|
+
(path: string, event: { shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }) =>
|
|
161
|
+
dispatch({ type: "click", path, shift: event.shiftKey, ctrl: event.ctrlKey || event.metaKey }),
|
|
162
|
+
[dispatch],
|
|
163
|
+
);
|
|
164
|
+
const selectOnly = useCallback((path: string) => dispatch({ type: "click", path }), [dispatch]);
|
|
165
|
+
const selectAll = useCallback(() => dispatch({ type: "select-all" }), [dispatch]);
|
|
166
|
+
const clear = useCallback(() => dispatch({ type: "clear" }), [dispatch]);
|
|
167
|
+
const setSelection = useCallback(
|
|
168
|
+
(paths: string[], anchor?: string | null) => dispatch({ type: "set", paths, anchor }),
|
|
169
|
+
[dispatch],
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// A fresh object every render would defeat memo(ListRow) (this is a prop several levels
|
|
173
|
+
// down) even though every method here is already a stable useCallback.
|
|
174
|
+
return useMemo(
|
|
175
|
+
() => ({ onRowClick, selectOnly, selectAll, clear, setSelection, moveTo, typeAhead }),
|
|
176
|
+
[onRowClick, selectOnly, selectAll, clear, setSelection, moveTo, typeAhead],
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -9,7 +9,7 @@ import { Switch } from "@/components/ui/switch";
|
|
|
9
9
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
10
10
|
import { Separator } from "@/components/ui/separator";
|
|
11
11
|
import { useShallow } from "zustand/react/shallow";
|
|
12
|
-
import { useSettingsStore, type EditorTabStyle } from "@/stores/settings-store";
|
|
12
|
+
import { useSettingsStore, type EditorTabStyle, type ExplorerSkinPref } from "@/stores/settings-store";
|
|
13
13
|
import { cn } from "@/lib/utils";
|
|
14
14
|
import { AISettingsSection } from "./ai-settings-section";
|
|
15
15
|
import { KeyboardShortcutsSection } from "./keyboard-shortcuts-section";
|
|
@@ -30,6 +30,12 @@ const TAB_STYLE_OPTIONS: { value: EditorTabStyle; label: string }[] = [
|
|
|
30
30
|
{ value: "pill", label: "Pill" },
|
|
31
31
|
];
|
|
32
32
|
|
|
33
|
+
const EXPLORER_SKIN_OPTIONS: { value: ExplorerSkinPref; label: string }[] = [
|
|
34
|
+
{ value: "auto", label: "Auto" },
|
|
35
|
+
{ value: "windows", label: "Windows" },
|
|
36
|
+
{ value: "macos", label: "macOS" },
|
|
37
|
+
];
|
|
38
|
+
|
|
33
39
|
type SettingsCategory = "ai" | "notifications" | "clawbot" | "schedules" | "jira" | "proxy" | "shortcuts" | "extensions" | "files" | "queryAudit";
|
|
34
40
|
|
|
35
41
|
const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; icon: React.ElementType }[] = [
|
|
@@ -46,7 +52,7 @@ const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; ic
|
|
|
46
52
|
];
|
|
47
53
|
|
|
48
54
|
export function SettingsTab() {
|
|
49
|
-
const { theme, setTheme, deviceName, setDeviceName, version, jiraEnabled, setJiraEnabled, tabWrap, toggleTabWrap, editorTabStyle, setEditorTabStyle } = useSettingsStore(useShallow((s) => ({ theme: s.themeMode, setTheme: s.setThemeMode, deviceName: s.deviceName, setDeviceName: s.setDeviceName, version: s.version, jiraEnabled: s.jiraEnabled, setJiraEnabled: s.setJiraEnabled, tabWrap: s.tabWrap, toggleTabWrap: s.toggleTabWrap, editorTabStyle: s.editorTabStyle, setEditorTabStyle: s.setEditorTabStyle })));
|
|
55
|
+
const { theme, setTheme, deviceName, setDeviceName, version, jiraEnabled, setJiraEnabled, tabWrap, toggleTabWrap, editorTabStyle, setEditorTabStyle, explorerSkin, setExplorerSkin } = useSettingsStore(useShallow((s) => ({ theme: s.themeMode, setTheme: s.setThemeMode, deviceName: s.deviceName, setDeviceName: s.setDeviceName, version: s.version, jiraEnabled: s.jiraEnabled, setJiraEnabled: s.setJiraEnabled, tabWrap: s.tabWrap, toggleTabWrap: s.toggleTabWrap, editorTabStyle: s.editorTabStyle, setEditorTabStyle: s.setEditorTabStyle, explorerSkin: s.explorerSkin, setExplorerSkin: s.setExplorerSkin })));
|
|
50
56
|
const [activeCategory, setActiveCategory] = useState<SettingsCategory | null>(null);
|
|
51
57
|
const [nameInput, setNameInput] = useState(deviceName ?? "");
|
|
52
58
|
const [nameSaving, setNameSaving] = useState(false);
|
|
@@ -223,6 +229,27 @@ export function SettingsTab() {
|
|
|
223
229
|
</div>
|
|
224
230
|
</section>
|
|
225
231
|
|
|
232
|
+
{/* Explorer Skin — chrome the OS File Explorer window wears; Auto follows platform */}
|
|
233
|
+
<section className="space-y-2">
|
|
234
|
+
<h3 className="text-xs font-medium text-muted-foreground">Explorer Skin</h3>
|
|
235
|
+
<div className="flex gap-1.5">
|
|
236
|
+
{EXPLORER_SKIN_OPTIONS.map((opt) => (
|
|
237
|
+
<Button
|
|
238
|
+
key={opt.value}
|
|
239
|
+
variant={explorerSkin === opt.value ? "default" : "outline"}
|
|
240
|
+
size="sm"
|
|
241
|
+
onClick={() => setExplorerSkin(opt.value)}
|
|
242
|
+
className={cn(
|
|
243
|
+
"flex-1 text-xs h-8 cursor-pointer",
|
|
244
|
+
explorerSkin === opt.value && "ring-2 ring-primary",
|
|
245
|
+
)}
|
|
246
|
+
>
|
|
247
|
+
{opt.label}
|
|
248
|
+
</Button>
|
|
249
|
+
))}
|
|
250
|
+
</div>
|
|
251
|
+
</section>
|
|
252
|
+
|
|
226
253
|
<Separator />
|
|
227
254
|
|
|
228
255
|
{/* Category navigation list */}
|
|
@@ -33,7 +33,10 @@ export function SqliteViewer({ metadata }: SqliteViewerProps) {
|
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// A database opened from a file-explorer window has no project; its absolute path is
|
|
37
|
+
// enough for the host filesystem SQLite routes, so only a missing path is fatal.
|
|
38
|
+
const isExternalPath = !!filePath && !projectName && /^(\/|[A-Za-z]:[/\\])/.test(filePath);
|
|
39
|
+
if (!filePath || (!projectName && !isExternalPath)) {
|
|
37
40
|
return (
|
|
38
41
|
<div className="flex items-center justify-center h-full text-text-secondary text-sm">
|
|
39
42
|
<Database className="size-5 mr-2" /> No database file selected.
|
|
@@ -43,7 +46,7 @@ export function SqliteViewer({ metadata }: SqliteViewerProps) {
|
|
|
43
46
|
|
|
44
47
|
return (
|
|
45
48
|
<SqliteViewerInner
|
|
46
|
-
projectName={projectName}
|
|
49
|
+
projectName={projectName ?? ""}
|
|
47
50
|
dbPath={filePath}
|
|
48
51
|
queryPanelOpen={queryPanelOpen}
|
|
49
52
|
onToggleQueryPanel={() => setQueryPanelOpen((v) => !v)}
|
|
@@ -19,9 +19,14 @@ export function useSqlite(projectName: string, dbPath: string, connectionId?: nu
|
|
|
19
19
|
const [queryError, setQueryError] = useState<string | null>(null);
|
|
20
20
|
const [queryLoading, setQueryLoading] = useState(false);
|
|
21
21
|
|
|
22
|
-
//
|
|
22
|
+
// Three sources, in order of specificity:
|
|
23
|
+
// - a saved connection → the unified /api/db routes, no path in the query
|
|
24
|
+
// - a file inside a project → the project-scoped route, path relative to the project
|
|
25
|
+
// - an absolute path with no project (opened from a file-explorer window)
|
|
26
|
+
// → the host filesystem route, path absolute
|
|
23
27
|
const unifiedBase = connectionId ? `/api/db/connections/${connectionId}` : null;
|
|
24
|
-
const
|
|
28
|
+
const isExternalPath = !connectionId && !projectName && /^(\/|[A-Za-z]:[/\\])/.test(dbPath);
|
|
29
|
+
const base = unifiedBase ?? (isExternalPath ? "/api/fs/sqlite" : `${projectUrl(projectName)}/sqlite`);
|
|
25
30
|
const qs = unifiedBase ? "" : `path=${encodeURIComponent(dbPath)}`;
|
|
26
31
|
|
|
27
32
|
// Fetch tables on mount — use cache when connectionId (sidebar handles live sync)
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* Same component names, same API — behavior adapts automatically.
|
|
8
8
|
*/
|
|
9
9
|
import React, { useState, useRef, useCallback, type ReactNode } from "react";
|
|
10
|
+
import { CircleIcon } from "lucide-react";
|
|
10
11
|
import * as Radix from "./context-menu";
|
|
11
12
|
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
12
13
|
import { cn } from "@/lib/utils";
|
|
@@ -19,10 +20,15 @@ import {
|
|
|
19
20
|
BottomSheetSubContent,
|
|
20
21
|
} from "./mobile-bottom-sheet";
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
// Shared with the desktop-width coarse-pointer path (use-coarse-long-press.ts) — both must
|
|
24
|
+
// agree so a synthetic contextmenu fired early isn't swallowed by this trigger's own timer.
|
|
25
|
+
const LONG_PRESS_MS = 400;
|
|
23
26
|
|
|
24
27
|
const IsMobileCtx = React.createContext(false);
|
|
25
28
|
|
|
29
|
+
/** Carries a radio group's active value/setter down to its flattened mobile items. */
|
|
30
|
+
const RadioGroupCtx = React.createContext<{ value?: string; onValueChange?(value: string): void }>({});
|
|
31
|
+
|
|
26
32
|
/* ------------------------------------------------------------------ */
|
|
27
33
|
/* Root */
|
|
28
34
|
/* ------------------------------------------------------------------ */
|
|
@@ -187,6 +193,54 @@ function ContextMenuSeparator({
|
|
|
187
193
|
return <BottomSheetSeparator className={className} />;
|
|
188
194
|
}
|
|
189
195
|
|
|
196
|
+
/* ------------------------------------------------------------------ */
|
|
197
|
+
/* Radio group / item (flattened to a checkable list on mobile) */
|
|
198
|
+
/* ------------------------------------------------------------------ */
|
|
199
|
+
|
|
200
|
+
function ContextMenuRadioGroup({
|
|
201
|
+
value,
|
|
202
|
+
onValueChange,
|
|
203
|
+
children,
|
|
204
|
+
...props
|
|
205
|
+
}: React.ComponentProps<typeof Radix.ContextMenuRadioGroup>) {
|
|
206
|
+
const isMobile = React.useContext(IsMobileCtx);
|
|
207
|
+
if (!isMobile) {
|
|
208
|
+
return (
|
|
209
|
+
<Radix.ContextMenuRadioGroup value={value} onValueChange={onValueChange} {...props}>
|
|
210
|
+
{children}
|
|
211
|
+
</Radix.ContextMenuRadioGroup>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return <RadioGroupCtx.Provider value={{ value, onValueChange }}>{children}</RadioGroupCtx.Provider>;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function ContextMenuRadioItem({
|
|
218
|
+
value,
|
|
219
|
+
children,
|
|
220
|
+
className,
|
|
221
|
+
...props
|
|
222
|
+
}: React.ComponentProps<typeof Radix.ContextMenuRadioItem>) {
|
|
223
|
+
const isMobile = React.useContext(IsMobileCtx);
|
|
224
|
+
// Both contexts are read unconditionally so the hook order stays stable when
|
|
225
|
+
// the viewport crosses the mobile breakpoint while a menu is mounted.
|
|
226
|
+
const { value: active, onValueChange } = React.useContext(RadioGroupCtx);
|
|
227
|
+
if (!isMobile) {
|
|
228
|
+
return (
|
|
229
|
+
<Radix.ContextMenuRadioItem value={value} className={className} {...props}>
|
|
230
|
+
{children}
|
|
231
|
+
</Radix.ContextMenuRadioItem>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return (
|
|
235
|
+
<BottomSheetItem className={className} onClick={() => onValueChange?.(value)}>
|
|
236
|
+
<span className="flex size-4 shrink-0 items-center justify-center">
|
|
237
|
+
{active === value && <CircleIcon className="size-2 fill-current" />}
|
|
238
|
+
</span>
|
|
239
|
+
{children}
|
|
240
|
+
</BottomSheetItem>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
190
244
|
/* ------------------------------------------------------------------ */
|
|
191
245
|
/* Sub-menu (flattened on mobile) */
|
|
192
246
|
/* ------------------------------------------------------------------ */
|
|
@@ -239,6 +293,8 @@ export {
|
|
|
239
293
|
ContextMenuContent,
|
|
240
294
|
ContextMenuItem,
|
|
241
295
|
ContextMenuSeparator,
|
|
296
|
+
ContextMenuRadioGroup,
|
|
297
|
+
ContextMenuRadioItem,
|
|
242
298
|
ContextMenuSub,
|
|
243
299
|
ContextMenuSubTrigger,
|
|
244
300
|
ContextMenuSubContent,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
Download, ChevronRight, ArrowLeft, Search, Loader2, Clock, Eye, EyeOff,
|
|
12
12
|
} from "lucide-react";
|
|
13
13
|
import { cn } from "@/lib/utils";
|
|
14
|
+
import { formatRelativeTime, formatSize } from "@/components/os-explorer/format-file-meta";
|
|
14
15
|
|
|
15
16
|
// ── Types ──────────────────────────────────────────────────────────
|
|
16
17
|
|
|
@@ -60,24 +61,6 @@ function saveRecent(dirPath: string): void {
|
|
|
60
61
|
localStorage.setItem(RECENT_KEY, JSON.stringify(updated));
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
function formatSize(bytes?: number): string {
|
|
64
|
-
if (bytes == null) return "";
|
|
65
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
66
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
67
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function formatRelativeTime(iso: string): string {
|
|
71
|
-
const diff = Date.now() - new Date(iso).getTime();
|
|
72
|
-
const mins = Math.floor(diff / 60000);
|
|
73
|
-
if (mins < 1) return "now";
|
|
74
|
-
if (mins < 60) return `${mins}m ago`;
|
|
75
|
-
const hrs = Math.floor(mins / 60);
|
|
76
|
-
if (hrs < 24) return `${hrs}h ago`;
|
|
77
|
-
const days = Math.floor(hrs / 24);
|
|
78
|
-
return `${days}d ago`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
64
|
function fileIcon(entry: BrowseEntry): React.ReactNode {
|
|
82
65
|
if (entry.type === "directory") return <Folder className="size-4 text-primary" />;
|
|
83
66
|
const ext = entry.name.split(".").pop()?.toLowerCase();
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
import { api, projectUrl } from "./api-client";
|
|
2
|
+
import { basename } from "./utils";
|
|
2
3
|
|
|
3
4
|
/** Trigger browser-native file download via hidden <a> tag */
|
|
4
5
|
export async function downloadFile(projectName: string, filePath: string): Promise<void> {
|
|
5
6
|
// Absolute paths (external files opened from filesystem browser) use /api/fs routes
|
|
6
7
|
const isAbsolute = /^(\/|[A-Za-z]:[/\\])/.test(filePath);
|
|
7
8
|
if (isAbsolute) {
|
|
8
|
-
|
|
9
|
+
// The token is issued for this exact file and spent on first use.
|
|
10
|
+
const { token } = await api.post<{ token: string }>("/api/fs/download/token", { path: filePath });
|
|
9
11
|
const url = `/api/fs/raw?path=${encodeURIComponent(filePath)}&download=true&dl_token=${encodeURIComponent(token)}`;
|
|
10
|
-
triggerDownload(url, filePath
|
|
12
|
+
triggerDownload(url, basename(filePath));
|
|
11
13
|
return;
|
|
12
14
|
}
|
|
13
15
|
const { token } = await api.post<{ token: string }>(`${projectUrl(projectName)}/files/download/token`);
|
|
14
16
|
const url = `${projectUrl(projectName)}/files/raw?path=${encodeURIComponent(filePath)}&download=true&dl_token=${encodeURIComponent(token)}`;
|
|
15
|
-
triggerDownload(url, filePath
|
|
17
|
+
triggerDownload(url, basename(filePath));
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/** Trigger browser-native folder zip download */
|
|
19
21
|
export async function downloadFolder(projectName: string, folderPath: string): Promise<void> {
|
|
20
22
|
const { token } = await api.post<{ token: string }>(`${projectUrl(projectName)}/files/download/token`);
|
|
21
|
-
const folderName = folderPath
|
|
23
|
+
const folderName = basename(folderPath) || "folder";
|
|
22
24
|
const url = `${projectUrl(projectName)}/files/download/zip?path=${encodeURIComponent(folderPath)}&dl_token=${encodeURIComponent(token)}`;
|
|
23
25
|
triggerDownload(url, `${folderName}.zip`);
|
|
24
26
|
}
|