@hienlh/ppm 0.17.56 → 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 +21 -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-DL011EDu.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-H16VO99g.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-B_wAb0Kb.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-awmnuDCt.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-DDw3duw9.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-DkTI51P3.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-Bpgp72Hg.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-Dwg0EuJ2.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-C7dlPs2S.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-CEWYMxdp.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-D7PggslU.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-ZnKcxCyj.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-Ck3REHUg.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-D6tYDFSF.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-wKbyNwIn.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-DwrfenNA.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/providers/claude-agent-sdk.ts +6 -0
- 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/jsonl-transcript-parser.ts +6 -0
- package/src/services/sqlite-types.ts +26 -0
- package/src/services/sqlite.service.ts +38 -29
- package/src/services/subagent-transcript-merger.ts +144 -0
- 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/hooks/use-chat.ts +22 -4
- 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-D2nZCpAo.js +0 -13
- package/dist/web/assets/code-editor-DqQy2Qm8.js +0 -10
- package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
- package/dist/web/assets/csv-preview-BL0I3CKz.js +0 -2
- package/dist/web/assets/dialog-HHdNInEH.js +0 -1
- package/dist/web/assets/diff-viewer-_yQtutHR.js +0 -4
- package/dist/web/assets/git-log-panel-NkghB_rO.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
- package/dist/web/assets/group-chat-tab-CQOVBmVp.js +0 -1
- package/dist/web/assets/index-CJ0Wp_rW.js +0 -50
- package/dist/web/assets/index-CVJmlKhp.css +0 -2
- package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
- package/dist/web/assets/keybindings-store-ClNYe9vD.js +0 -1
- package/dist/web/assets/markdown-renderer-CwpP5Ola.js +0 -1
- package/dist/web/assets/notification-store-DwAhNzTy.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-CeSJQIfF.js +0 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
- package/dist/web/assets/sqlite-viewer-8fk_wut8.js +0 -1
- package/dist/web/assets/tool-cards-C3ka1Y7I.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,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy, viewport-gated thumbnail for image files.
|
|
3
|
+
*
|
|
4
|
+
* A bare `<img src="/api/fs/raw?...">` cannot carry the Authorization header the API
|
|
5
|
+
* requires, so the bytes are fetched manually and turned into an object URL — the same
|
|
6
|
+
* trick `useBlobUrl` uses — but gated by an `IntersectionObserver` (nothing fetches until
|
|
7
|
+
* the tile scrolls near the viewport) and a module-wide concurrency cap, so opening a
|
|
8
|
+
* folder of a thousand photos does not fire a thousand requests at once.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useRef, useState } from "react";
|
|
12
|
+
import { getAuthToken } from "@/lib/api-client";
|
|
13
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import { FileTypeIcon } from "./file-type-icon";
|
|
16
|
+
import { acquireSlot } from "./thumbnail-fetch-semaphore";
|
|
17
|
+
|
|
18
|
+
export interface ThumbnailImageProps {
|
|
19
|
+
entry: Pick<FsEntry, "name" | "path" | "kind">;
|
|
20
|
+
/** Square box the thumbnail (or its icon fallback) renders into, in px. */
|
|
21
|
+
size: number;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ThumbnailImage({ entry, size, className }: ThumbnailImageProps) {
|
|
26
|
+
const [src, setSrc] = useState<string | null>(null);
|
|
27
|
+
const [failed, setFailed] = useState(false);
|
|
28
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
29
|
+
const urlRef = useRef<string | null>(null);
|
|
30
|
+
|
|
31
|
+
// A different entry (row recycled by the virtualizer, or the user navigated) starts over.
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setSrc(null);
|
|
34
|
+
setFailed(false);
|
|
35
|
+
}, [entry.path]);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const el = rootRef.current;
|
|
39
|
+
if (!el || src || failed) return;
|
|
40
|
+
// A scrolled-past tile must stop downloading (the server has no thumbnail endpoint —
|
|
41
|
+
// `/api/fs/raw` streams the whole original file) and give its semaphore slot back
|
|
42
|
+
// immediately rather than waiting for a fetch nobody will render.
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
let release: (() => void) | null = null;
|
|
45
|
+
|
|
46
|
+
const observer = new IntersectionObserver(
|
|
47
|
+
(observed) => {
|
|
48
|
+
if (!observed[0]?.isIntersecting) return;
|
|
49
|
+
observer.disconnect();
|
|
50
|
+
void (async () => {
|
|
51
|
+
release = await acquireSlot();
|
|
52
|
+
if (controller.signal.aborted) {
|
|
53
|
+
release();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const token = getAuthToken();
|
|
58
|
+
const res = await fetch(`/api/fs/raw?path=${encodeURIComponent(entry.path)}`, {
|
|
59
|
+
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
|
60
|
+
signal: controller.signal,
|
|
61
|
+
});
|
|
62
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
63
|
+
const blob = await res.blob();
|
|
64
|
+
if (controller.signal.aborted) return;
|
|
65
|
+
const url = URL.createObjectURL(blob);
|
|
66
|
+
urlRef.current = url;
|
|
67
|
+
setSrc(url);
|
|
68
|
+
} catch {
|
|
69
|
+
if (!controller.signal.aborted) setFailed(true);
|
|
70
|
+
} finally {
|
|
71
|
+
// Idempotent: harmless if this fires again from the cleanup below for the same
|
|
72
|
+
// completed acquisition.
|
|
73
|
+
release?.();
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
},
|
|
77
|
+
// Start the fetch a little before the tile is actually visible so it is usually
|
|
78
|
+
// ready by the time it scrolls fully into view.
|
|
79
|
+
{ rootMargin: "200px" },
|
|
80
|
+
);
|
|
81
|
+
observer.observe(el);
|
|
82
|
+
return () => {
|
|
83
|
+
controller.abort();
|
|
84
|
+
observer.disconnect();
|
|
85
|
+
release?.();
|
|
86
|
+
};
|
|
87
|
+
}, [entry.path, src, failed]);
|
|
88
|
+
|
|
89
|
+
useEffect(
|
|
90
|
+
() => () => {
|
|
91
|
+
if (urlRef.current) URL.revokeObjectURL(urlRef.current);
|
|
92
|
+
},
|
|
93
|
+
[],
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (failed || !src) {
|
|
97
|
+
return (
|
|
98
|
+
<div
|
|
99
|
+
ref={rootRef}
|
|
100
|
+
className={cn("flex items-center justify-center", className)}
|
|
101
|
+
style={{ width: size, height: size }}
|
|
102
|
+
>
|
|
103
|
+
<FileTypeIcon name={entry.name} kind={entry.kind} className="size-1/2" />
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div
|
|
110
|
+
ref={rootRef}
|
|
111
|
+
className={cn("flex items-center justify-center overflow-hidden", className)}
|
|
112
|
+
style={{ width: size, height: size }}
|
|
113
|
+
>
|
|
114
|
+
{/* eslint-disable-next-line @next/next/no-img-element -- plain img, not next/image */}
|
|
115
|
+
<img
|
|
116
|
+
src={src}
|
|
117
|
+
alt=""
|
|
118
|
+
loading="lazy"
|
|
119
|
+
className="max-h-full max-w-full rounded object-contain"
|
|
120
|
+
onError={() => setFailed(true)}
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bottom toolbar: every frequent action lives here, in the thumb zone. Two rows share the
|
|
3
|
+
* one bar — the normal row (navigate / create / paste / enter Select) and, once "Select" is
|
|
4
|
+
* tapped, a row of bulk actions over the current selection. "Delete permanently" is
|
|
5
|
+
* deliberately not here (it lives in the More sheet) — Trash is the default and the one-tap
|
|
6
|
+
* row should not make the irreversible action as easy to reach as the reversible one.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState } from "react";
|
|
10
|
+
import {
|
|
11
|
+
ArrowLeft, Check, ClipboardPaste, Copy, Download, FilePlus, FolderPlus, Info, MoreHorizontal, Plus, Scissors, Trash2, X,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
14
|
+
import { BottomSheet, BottomSheetItem } from "@/components/ui/mobile-bottom-sheet";
|
|
15
|
+
import type { ExplorerActions } from "../actions/use-explorer-actions";
|
|
16
|
+
import type { ExplorerSlice } from "../explorer-store";
|
|
17
|
+
import type { ExplorerNavigation } from "../use-explorer-navigation";
|
|
18
|
+
import { useMobileExplorerOpenState } from "../use-explorer-open-state";
|
|
19
|
+
import { MobileMoreSheet } from "./mobile-more-sheet";
|
|
20
|
+
|
|
21
|
+
function ToolbarButton({
|
|
22
|
+
icon: Icon, label, onClick, disabled, destructive,
|
|
23
|
+
}: {
|
|
24
|
+
icon: typeof Plus; label: string; onClick(): void; disabled?: boolean; destructive?: boolean;
|
|
25
|
+
}) {
|
|
26
|
+
return (
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
aria-label={label}
|
|
30
|
+
title={label}
|
|
31
|
+
disabled={disabled}
|
|
32
|
+
onClick={onClick}
|
|
33
|
+
className={`flex min-w-[44px] flex-1 flex-col items-center gap-0.5 rounded-lg py-1.5 text-[10px] active:bg-surface-elevated disabled:opacity-30 ${destructive ? "text-error" : "text-text-2"}`}
|
|
34
|
+
>
|
|
35
|
+
<Icon className="size-5" />
|
|
36
|
+
<span className="truncate">{label}</span>
|
|
37
|
+
</button>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface MobileBottomToolbarProps {
|
|
42
|
+
slice: ExplorerSlice;
|
|
43
|
+
nav: ExplorerNavigation;
|
|
44
|
+
actions: ExplorerActions;
|
|
45
|
+
entries: FsEntry[];
|
|
46
|
+
hasClipboard: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function MobileBottomToolbar({ slice, nav, actions, entries, hasClipboard }: MobileBottomToolbarProps) {
|
|
50
|
+
const selectMode = useMobileExplorerOpenState((s) => s.selectMode);
|
|
51
|
+
const setSelectMode = useMobileExplorerOpenState((s) => s.setSelectMode);
|
|
52
|
+
const [newOpen, setNewOpen] = useState(false);
|
|
53
|
+
const [moreOpen, setMoreOpen] = useState(false);
|
|
54
|
+
|
|
55
|
+
const selected = entries.filter((e) => slice.selection.has(e.path));
|
|
56
|
+
|
|
57
|
+
if (selectMode) {
|
|
58
|
+
const exitSelect = () => { setSelectMode(false); };
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex shrink-0 items-center gap-1 border-t border-border px-1 py-1">
|
|
61
|
+
<ToolbarButton icon={X} label="Cancel" onClick={exitSelect} />
|
|
62
|
+
<span className="px-1 text-xs text-text-subtle">{selected.length} selected</span>
|
|
63
|
+
<div className="flex-1" />
|
|
64
|
+
<ToolbarButton icon={Scissors} label="Cut" disabled={selected.length === 0} onClick={() => actions.cut(selected)} />
|
|
65
|
+
<ToolbarButton icon={Copy} label="Copy" disabled={selected.length === 0} onClick={() => actions.copy(selected)} />
|
|
66
|
+
<ToolbarButton icon={Trash2} label="Trash" disabled={selected.length === 0} destructive onClick={() => actions.trash(selected)} />
|
|
67
|
+
<ToolbarButton
|
|
68
|
+
icon={Download}
|
|
69
|
+
label="Download"
|
|
70
|
+
disabled={!selected.some((e) => e.type === "file")}
|
|
71
|
+
onClick={() => actions.download(selected)}
|
|
72
|
+
/>
|
|
73
|
+
{selected.length === 1 && (
|
|
74
|
+
<ToolbarButton icon={Info} label="Info" onClick={() => actions.showProperties(selected[0]!)} />
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<div className="flex shrink-0 items-center gap-1 border-t border-border px-1 py-1">
|
|
83
|
+
<ToolbarButton icon={ArrowLeft} label="Back" disabled={!nav.canGoBack} onClick={nav.back} />
|
|
84
|
+
<ToolbarButton icon={Plus} label="New" onClick={() => setNewOpen(true)} />
|
|
85
|
+
<ToolbarButton icon={ClipboardPaste} label="Paste" disabled={!hasClipboard} onClick={() => actions.paste()} />
|
|
86
|
+
<ToolbarButton icon={Check} label="Select" onClick={() => setSelectMode(true)} />
|
|
87
|
+
<ToolbarButton icon={MoreHorizontal} label="More" onClick={() => setMoreOpen(true)} />
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<BottomSheet open={newOpen} onClose={() => setNewOpen(false)} className="p-2">
|
|
91
|
+
<BottomSheetItem onClick={() => actions.startCreate("new-file")}>
|
|
92
|
+
<FilePlus className="size-4" /> New File
|
|
93
|
+
</BottomSheetItem>
|
|
94
|
+
<BottomSheetItem onClick={() => actions.startCreate("new-folder")}>
|
|
95
|
+
<FolderPlus className="size-4" /> New Folder
|
|
96
|
+
</BottomSheetItem>
|
|
97
|
+
</BottomSheet>
|
|
98
|
+
|
|
99
|
+
<MobileMoreSheet open={moreOpen} onClose={() => setMoreOpen(false)} slice={slice} actions={actions} entries={entries} />
|
|
100
|
+
</>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full-screen mobile presentation of the explorer: a `BottomSheet` sized to the whole
|
|
3
|
+
* visible viewport, hosting the exact same `ExplorerBody` every desktop window uses
|
|
4
|
+
* (`variant="sheet"`), so file-type icons, the active OS skin's vocabulary and every mutation
|
|
5
|
+
* action are shared code, not a parallel mobile implementation.
|
|
6
|
+
*
|
|
7
|
+
* Self-gated singleton, mounted once at the app root next to the other overlay singletons
|
|
8
|
+
* (`ComparePicker`, `ImageOverlay`, …) — renders nothing until `openMobileExplorer` is called.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, type TouchEvent } from "react";
|
|
12
|
+
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
13
|
+
import { ExplorerBody } from "../explorer-body";
|
|
14
|
+
import { useExplorerStore } from "../explorer-store";
|
|
15
|
+
import { MOBILE_EXPLORER_WINDOW_ID, useMobileExplorerOpenState } from "../use-explorer-open-state";
|
|
16
|
+
|
|
17
|
+
export function MobileExplorerSheet() {
|
|
18
|
+
const isOpen = useMobileExplorerOpenState((s) => s.isOpen);
|
|
19
|
+
const close = useMobileExplorerOpenState((s) => s.close);
|
|
20
|
+
const setSelectMode = useMobileExplorerOpenState((s) => s.setSelectMode);
|
|
21
|
+
const path = useExplorerStore((s) => s.slices[MOBILE_EXPLORER_WINDOW_ID]?.path);
|
|
22
|
+
const renaming = useExplorerStore(
|
|
23
|
+
(s) => s.slices[MOBILE_EXPLORER_WINDOW_ID]?.inlineEdit?.kind === "rename",
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Navigating (forward, back, or via a place/breadcrumb) already clears the shared
|
|
27
|
+
// selection — Select mode's own checkbox/bottom-bar state must follow it, or the toolbar
|
|
28
|
+
// is left on Cut/Copy/Trash with 0 items and no way back short of the explicit Cancel.
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setSelectMode(false);
|
|
31
|
+
}, [path, setSelectMode]);
|
|
32
|
+
|
|
33
|
+
// Swallowed only while an inline rename is active: a drag meant to clear the soft keyboard
|
|
34
|
+
// (or just reach a field below it) would otherwise also read as a swipe-to-dismiss, since
|
|
35
|
+
// `BottomSheet` wires its drag handlers on the panel — an ancestor of everything here.
|
|
36
|
+
const suppressSwipe = (e: TouchEvent) => {
|
|
37
|
+
if (renaming) e.stopPropagation();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (!isOpen) return null;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
// Below the default sheet z-index (50): the explorer opens further sheets on top of
|
|
44
|
+
// itself (row actions, "New", "More", "Places") that all rely on that default, and a
|
|
45
|
+
// higher value here would silently sit above every one of them instead. 45 still clears
|
|
46
|
+
// the mobile bottom nav (z-40) so the sheet reads as full-screen underneath it.
|
|
47
|
+
<BottomSheet open={isOpen} onClose={close} zIndex={45} className="flex h-[var(--sheet-vh)] flex-col p-0">
|
|
48
|
+
<div
|
|
49
|
+
className="flex min-h-0 flex-1 flex-col"
|
|
50
|
+
onTouchStart={suppressSwipe}
|
|
51
|
+
onTouchMove={suppressSwipe}
|
|
52
|
+
>
|
|
53
|
+
{path ? (
|
|
54
|
+
<ExplorerBody windowId={MOBILE_EXPLORER_WINDOW_ID} initialPath={path} variant="sheet" />
|
|
55
|
+
) : (
|
|
56
|
+
<div className="flex flex-1 items-center justify-center text-sm text-text-2">Loading…</div>
|
|
57
|
+
)}
|
|
58
|
+
</div>
|
|
59
|
+
</BottomSheet>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mobile sheet header: current folder name, nothing else. Every frequent action lives in
|
|
3
|
+
* the bottom toolbar (the thumb zone) — putting anything interactive up here would be the
|
|
4
|
+
* one part of a one-handed grip that is hardest to reach.
|
|
5
|
+
*
|
|
6
|
+
* The close button is the one addition beyond "title only": a sheet that fills the whole
|
|
7
|
+
* screen leaves no backdrop to tap, so swipe-to-dismiss would otherwise be the only way out.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { X } from "lucide-react";
|
|
11
|
+
import type { ExplorerSlice } from "../explorer-store";
|
|
12
|
+
|
|
13
|
+
export interface MobileExplorerTopBarProps {
|
|
14
|
+
slice: ExplorerSlice;
|
|
15
|
+
onClose(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The folder name to show for the current path — the last breadcrumb is the folder itself. */
|
|
19
|
+
function currentFolderName(slice: ExplorerSlice): string {
|
|
20
|
+
const last = slice.breadcrumbs[slice.breadcrumbs.length - 1];
|
|
21
|
+
return last?.name ?? slice.path;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function MobileExplorerTopBar({ slice, onClose }: MobileExplorerTopBarProps) {
|
|
25
|
+
return (
|
|
26
|
+
<div className="flex shrink-0 items-center gap-2 border-b border-border px-3 py-2.5">
|
|
27
|
+
<span className="min-w-0 flex-1 truncate text-[15px] font-semibold text-text">
|
|
28
|
+
{currentFolderName(slice)}
|
|
29
|
+
</span>
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
aria-label="Close file explorer"
|
|
33
|
+
title="Close"
|
|
34
|
+
onClick={onClose}
|
|
35
|
+
className="flex size-11 shrink-0 items-center justify-center rounded-full text-text-2 active:bg-surface-elevated"
|
|
36
|
+
>
|
|
37
|
+
<X className="size-5" />
|
|
38
|
+
</button>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mobile toolbar's overflow sheet: view mode, sort, hidden files, permanent delete and
|
|
3
|
+
* properties of the *current folder* (not the selection — that's what the row/tile menu and
|
|
4
|
+
* the selection-mode toolbar are for).
|
|
5
|
+
*
|
|
6
|
+
* "Delete permanently" lives here rather than on the one-tap selection toolbar so the
|
|
7
|
+
* irreversible action never sits as close at hand as Trash.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
Columns3, Eye, EyeOff, Info, LayoutGrid, List, Trash2,
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
14
|
+
import { BottomSheet, BottomSheetItem, BottomSheetSeparator, BottomSheetSubLabel } from "@/components/ui/mobile-bottom-sheet";
|
|
15
|
+
import type { ExplorerActions } from "../actions/use-explorer-actions";
|
|
16
|
+
import { useExplorerStore, type ExplorerSlice, type ViewMode } from "../explorer-store";
|
|
17
|
+
import { SORT_KEY_OPTIONS } from "../sort-options";
|
|
18
|
+
import { activeEntries } from "../use-explorer-keyboard";
|
|
19
|
+
import { AVAILABLE_VIEW_MODES } from "../views/explorer-view-registry";
|
|
20
|
+
import { cn } from "@/lib/utils";
|
|
21
|
+
|
|
22
|
+
const VIEW_ICON: Record<ViewMode, typeof List> = { list: List, icons: LayoutGrid, columns: Columns3 };
|
|
23
|
+
const VIEW_LABEL: Record<ViewMode, string> = { list: "List", icons: "Icons", columns: "Columns" };
|
|
24
|
+
|
|
25
|
+
/** The current folder, in the shape `showProperties`/`PropertiesDialog` need — the dialog
|
|
26
|
+
* fetches everything else itself from `/api/fs/stat`. */
|
|
27
|
+
function currentFolderEntry(slice: ExplorerSlice): FsEntry {
|
|
28
|
+
const name = slice.breadcrumbs[slice.breadcrumbs.length - 1]?.name ?? slice.path;
|
|
29
|
+
return { name, path: slice.path, type: "directory", kind: "directory", modified: "" };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface MobileMoreSheetProps {
|
|
33
|
+
open: boolean;
|
|
34
|
+
onClose(): void;
|
|
35
|
+
slice: ExplorerSlice;
|
|
36
|
+
actions: ExplorerActions;
|
|
37
|
+
entries: FsEntry[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function MobileMoreSheet({ open, onClose, slice, actions, entries }: MobileMoreSheetProps) {
|
|
41
|
+
const viewMode = useExplorerStore((s) => s.viewMode);
|
|
42
|
+
const sort = useExplorerStore((s) => s.sort);
|
|
43
|
+
const showHidden = useExplorerStore((s) => s.showHidden);
|
|
44
|
+
const setPrefs = useExplorerStore((s) => s.setPrefs);
|
|
45
|
+
const targets = activeEntries(slice, entries);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<BottomSheet open={open} onClose={onClose} className="p-2">
|
|
49
|
+
<BottomSheetSubLabel>View</BottomSheetSubLabel>
|
|
50
|
+
<div className="flex gap-1 px-2 pb-2">
|
|
51
|
+
{AVAILABLE_VIEW_MODES.map((mode) => {
|
|
52
|
+
const Icon = VIEW_ICON[mode];
|
|
53
|
+
return (
|
|
54
|
+
<button
|
|
55
|
+
key={mode}
|
|
56
|
+
type="button"
|
|
57
|
+
aria-pressed={viewMode === mode}
|
|
58
|
+
onClick={() => { setPrefs({ viewMode: mode }); onClose(); }}
|
|
59
|
+
className={cn(
|
|
60
|
+
"flex flex-1 flex-col items-center gap-1 rounded-lg py-2 text-xs active:bg-surface-elevated",
|
|
61
|
+
viewMode === mode ? "bg-accent-wash text-primary" : "text-text-2",
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<Icon className="size-4" />
|
|
65
|
+
{VIEW_LABEL[mode]}
|
|
66
|
+
</button>
|
|
67
|
+
);
|
|
68
|
+
})}
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<BottomSheetSeparator />
|
|
72
|
+
<BottomSheetSubLabel>Sort by</BottomSheetSubLabel>
|
|
73
|
+
<div className="flex flex-wrap gap-1 px-2 pb-2">
|
|
74
|
+
{SORT_KEY_OPTIONS.map(({ key, label }) => (
|
|
75
|
+
<button
|
|
76
|
+
key={key}
|
|
77
|
+
type="button"
|
|
78
|
+
aria-pressed={sort.key === key}
|
|
79
|
+
onClick={() => {
|
|
80
|
+
setPrefs({ sort: { key, dir: sort.key === key && sort.dir === "asc" ? "desc" : "asc" } });
|
|
81
|
+
onClose();
|
|
82
|
+
}}
|
|
83
|
+
className={cn(
|
|
84
|
+
// min-h-11 (44px) keeps the tap target compliant without inflating the pill's
|
|
85
|
+
// padding to match — inline-flex + items-center re-centers the small text
|
|
86
|
+
// inside the taller box.
|
|
87
|
+
"inline-flex min-h-11 items-center justify-center rounded-full border px-3 py-1.5 text-xs active:bg-surface-elevated",
|
|
88
|
+
sort.key === key ? "border-primary bg-accent-wash text-text" : "border-border text-text-2",
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
{label}{sort.key === key ? (sort.dir === "asc" ? " ↑" : " ↓") : ""}
|
|
92
|
+
</button>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<BottomSheetSeparator />
|
|
97
|
+
<BottomSheetItem onClick={() => setPrefs({ showHidden: !showHidden })}>
|
|
98
|
+
{showHidden ? <Eye className="size-4" /> : <EyeOff className="size-4" />}
|
|
99
|
+
{showHidden ? "Hide hidden files" : "Show hidden files"}
|
|
100
|
+
</BottomSheetItem>
|
|
101
|
+
|
|
102
|
+
<BottomSheetSeparator />
|
|
103
|
+
<BottomSheetItem
|
|
104
|
+
variant="destructive"
|
|
105
|
+
disabled={targets.length === 0}
|
|
106
|
+
onClick={() => actions.confirmPermanentDelete(targets)}
|
|
107
|
+
>
|
|
108
|
+
<Trash2 className="size-4" />
|
|
109
|
+
Delete permanently{targets.length > 1 ? ` (${targets.length})` : ""}
|
|
110
|
+
</BottomSheetItem>
|
|
111
|
+
<BottomSheetItem onClick={() => actions.showProperties(currentFolderEntry(slice))}>
|
|
112
|
+
<Info className="size-4" />
|
|
113
|
+
Properties of this folder
|
|
114
|
+
</BottomSheetItem>
|
|
115
|
+
</BottomSheet>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Full places list behind the chip strip's "All places" chip: PPM pins, OS pinned folders,
|
|
3
|
+
* known folders and drives, each sectioned exactly like the desktop sidebar — the strip only
|
|
4
|
+
* condenses the *common case*, this sheet is where everything still lives.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { ReactNode } from "react";
|
|
8
|
+
import { HardDrive, Network, Pin, Usb } from "lucide-react";
|
|
9
|
+
import type { Drive, HostInfo } from "../../../../types/system";
|
|
10
|
+
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
11
|
+
import { useExplorerPinsStore } from "../explorer-pins-store";
|
|
12
|
+
import { FileTypeIcon } from "../icons/file-type-icon";
|
|
13
|
+
import type { SkinVocab } from "../skins/skin-types";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
|
|
16
|
+
const ICON_CLASS = "size-4 shrink-0 text-text-subtle";
|
|
17
|
+
const DRIVE_ICON: Record<Drive["kind"], typeof HardDrive> = {
|
|
18
|
+
fixed: HardDrive,
|
|
19
|
+
removable: Usb,
|
|
20
|
+
network: Network,
|
|
21
|
+
unknown: HardDrive,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Folder-type place rows draw the current skin's folder glyph, same as the desktop sidebar. */
|
|
25
|
+
function folderIcon(): ReactNode {
|
|
26
|
+
return <FileTypeIcon name="" kind="directory" className={ICON_CLASS} />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function Row({
|
|
30
|
+
label, path, active, icon, onNavigate,
|
|
31
|
+
}: {
|
|
32
|
+
label: string; path: string; active: boolean; icon: ReactNode; onNavigate(path: string): void;
|
|
33
|
+
}) {
|
|
34
|
+
return (
|
|
35
|
+
<button
|
|
36
|
+
type="button"
|
|
37
|
+
title={path}
|
|
38
|
+
onClick={() => onNavigate(path)}
|
|
39
|
+
className={cn(
|
|
40
|
+
"flex min-h-[44px] w-full items-center gap-2.5 rounded-lg px-3 text-left text-sm active:bg-surface-elevated",
|
|
41
|
+
active ? "bg-accent-wash text-text" : "text-text-2",
|
|
42
|
+
)}
|
|
43
|
+
>
|
|
44
|
+
{icon}
|
|
45
|
+
<span className="truncate">{label}</span>
|
|
46
|
+
</button>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function Section({ title, children }: { title: string; children: ReactNode }) {
|
|
51
|
+
return (
|
|
52
|
+
<div className="pb-2">
|
|
53
|
+
<p className="px-3 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-text-subtle">{title}</p>
|
|
54
|
+
{children}
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface MobilePlacesSheetProps {
|
|
60
|
+
open: boolean;
|
|
61
|
+
onClose(): void;
|
|
62
|
+
host: HostInfo | null;
|
|
63
|
+
currentPath: string;
|
|
64
|
+
vocab: SkinVocab;
|
|
65
|
+
onNavigate(path: string): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function MobilePlacesSheet({ open, onClose, host, currentPath, vocab, onNavigate }: MobilePlacesSheetProps) {
|
|
69
|
+
const pins = useExplorerPinsStore((s) => s.pins);
|
|
70
|
+
const isActive = (path: string) => path === currentPath;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<BottomSheet open={open} onClose={onClose} zIndex={65} className="p-1">
|
|
74
|
+
<div className="max-h-[70vh] overflow-y-auto px-1 pb-2">
|
|
75
|
+
{pins.length > 0 && (
|
|
76
|
+
<Section title="Pinned">
|
|
77
|
+
{pins.map((pin) => (
|
|
78
|
+
<Row key={pin.path} label={pin.name} path={pin.path} icon={<Pin className={ICON_CLASS} />} active={isActive(pin.path)} onNavigate={onNavigate} />
|
|
79
|
+
))}
|
|
80
|
+
</Section>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
{host && host.pinned.length > 0 && (
|
|
84
|
+
<Section title={vocab.pinned}>
|
|
85
|
+
{host.pinned.map((entry) => (
|
|
86
|
+
<Row key={entry.path} label={entry.name} path={entry.path} icon={folderIcon()} active={isActive(entry.path)} onNavigate={onNavigate} />
|
|
87
|
+
))}
|
|
88
|
+
</Section>
|
|
89
|
+
)}
|
|
90
|
+
|
|
91
|
+
{host && (
|
|
92
|
+
<Section title={vocab.known}>
|
|
93
|
+
<Row label={vocab.home} path={host.homedir} icon={folderIcon()} active={isActive(host.homedir)} onNavigate={onNavigate} />
|
|
94
|
+
{host.knownFolders.map((folder) => (
|
|
95
|
+
<Row key={folder.path} label={folder.name} path={folder.path} icon={folderIcon()} active={isActive(folder.path)} onNavigate={onNavigate} />
|
|
96
|
+
))}
|
|
97
|
+
</Section>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{host && host.drives.length > 0 && (
|
|
101
|
+
<Section title={vocab.drives}>
|
|
102
|
+
{host.drives.map((drive) => {
|
|
103
|
+
const DriveIcon = DRIVE_ICON[drive.kind];
|
|
104
|
+
return (
|
|
105
|
+
<Row
|
|
106
|
+
key={drive.path}
|
|
107
|
+
label={drive.label ? `${drive.label} (${drive.name})` : drive.name}
|
|
108
|
+
path={drive.path}
|
|
109
|
+
icon={<DriveIcon className={ICON_CLASS} />}
|
|
110
|
+
active={isActive(drive.path)}
|
|
111
|
+
onNavigate={onNavigate}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
})}
|
|
115
|
+
</Section>
|
|
116
|
+
)}
|
|
117
|
+
</div>
|
|
118
|
+
</BottomSheet>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sidebar → horizontally scrollable "places" chip strip on mobile: PPM pins, the host's own
|
|
3
|
+
* pinned folders, Home, and drives — condensed to fit under the title instead of a side
|
|
4
|
+
* column. The full list (known folders included) lives in a "Places" sheet behind the last
|
|
5
|
+
* chip.
|
|
6
|
+
*
|
|
7
|
+
* `touch-pan-x` keeps the browser's native scroll-chaining to the horizontal axis only, so a
|
|
8
|
+
* drag that starts on a chip does not also read as a vertical page scroll.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState, type ComponentType } from "react";
|
|
12
|
+
import { HardDrive, MoreHorizontal, Network, Pin, Usb } from "lucide-react";
|
|
13
|
+
import type { Drive, HostInfo } from "../../../../types/system";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import { useExplorerPinsStore } from "../explorer-pins-store";
|
|
16
|
+
import { FileTypeIcon } from "../icons/file-type-icon";
|
|
17
|
+
import type { SkinVocab } from "../skins/skin-types";
|
|
18
|
+
import { MobilePlacesSheet } from "./mobile-places-sheet";
|
|
19
|
+
|
|
20
|
+
function FolderChipIcon({ className }: { className?: string }) {
|
|
21
|
+
return <FileTypeIcon name="" kind="directory" className={className} />;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const DRIVE_ICON: Record<Drive["kind"], ComponentType<{ className?: string }>> = {
|
|
25
|
+
fixed: HardDrive,
|
|
26
|
+
removable: Usb,
|
|
27
|
+
network: Network,
|
|
28
|
+
unknown: HardDrive,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
interface Chip {
|
|
32
|
+
key: string;
|
|
33
|
+
label: string;
|
|
34
|
+
path: string;
|
|
35
|
+
icon: ComponentType<{ className?: string }>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function Chip({ chip, active, onNavigate }: { chip: Chip; active: boolean; onNavigate(path: string): void }) {
|
|
39
|
+
const Icon = chip.icon;
|
|
40
|
+
return (
|
|
41
|
+
<button
|
|
42
|
+
type="button"
|
|
43
|
+
onClick={() => onNavigate(chip.path)}
|
|
44
|
+
title={chip.path}
|
|
45
|
+
className={cn(
|
|
46
|
+
"flex h-11 shrink-0 items-center gap-1.5 rounded-full border px-3 text-[13px] active:bg-surface-elevated",
|
|
47
|
+
active ? "border-primary bg-accent-wash text-text" : "border-border text-text-2",
|
|
48
|
+
)}
|
|
49
|
+
>
|
|
50
|
+
<Icon className="size-4 shrink-0 text-text-subtle" />
|
|
51
|
+
<span className="max-w-[9rem] truncate">{chip.label}</span>
|
|
52
|
+
</button>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface MobilePlacesStripProps {
|
|
57
|
+
host: HostInfo | null;
|
|
58
|
+
currentPath: string;
|
|
59
|
+
onNavigate(path: string): void;
|
|
60
|
+
vocab: SkinVocab;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function MobilePlacesStrip({ host, currentPath, onNavigate, vocab }: MobilePlacesStripProps) {
|
|
64
|
+
const pins = useExplorerPinsStore((s) => s.pins);
|
|
65
|
+
const [placesOpen, setPlacesOpen] = useState(false);
|
|
66
|
+
|
|
67
|
+
const chips: Chip[] = [
|
|
68
|
+
...pins.map((pin): Chip => ({ key: `pin:${pin.path}`, label: pin.name, path: pin.path, icon: Pin })),
|
|
69
|
+
...(host ? [{ key: "home", label: vocab.home, path: host.homedir, icon: FolderChipIcon }] : []),
|
|
70
|
+
...(host?.pinned.map((p): Chip => ({ key: `os:${p.path}`, label: p.name, path: p.path, icon: FolderChipIcon })) ?? []),
|
|
71
|
+
...(host?.drives.map((d): Chip => ({
|
|
72
|
+
key: `drive:${d.path}`,
|
|
73
|
+
label: d.label ? `${d.label} (${d.name})` : d.name,
|
|
74
|
+
path: d.path,
|
|
75
|
+
icon: DRIVE_ICON[d.kind],
|
|
76
|
+
})) ?? []),
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<>
|
|
81
|
+
<div className="flex shrink-0 items-center gap-1.5 overflow-x-auto touch-pan-x border-b border-border px-2 py-1.5">
|
|
82
|
+
{chips.map((chip) => (
|
|
83
|
+
<Chip key={chip.key} chip={chip} active={chip.path === currentPath} onNavigate={onNavigate} />
|
|
84
|
+
))}
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
aria-label="All places"
|
|
88
|
+
title="All places"
|
|
89
|
+
onClick={() => setPlacesOpen(true)}
|
|
90
|
+
className="flex h-11 shrink-0 items-center gap-1 rounded-full border border-border px-3 text-[13px] text-text-2 active:bg-surface-elevated"
|
|
91
|
+
>
|
|
92
|
+
<MoreHorizontal className="size-4" />
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<MobilePlacesSheet
|
|
97
|
+
open={placesOpen}
|
|
98
|
+
onClose={() => setPlacesOpen(false)}
|
|
99
|
+
host={host}
|
|
100
|
+
currentPath={currentPath}
|
|
101
|
+
vocab={vocab}
|
|
102
|
+
onNavigate={(path) => {
|
|
103
|
+
setPlacesOpen(false);
|
|
104
|
+
onNavigate(path);
|
|
105
|
+
}}
|
|
106
|
+
/>
|
|
107
|
+
</>
|
|
108
|
+
);
|
|
109
|
+
}
|