@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,74 @@
|
|
|
1
|
+
/** Presentation helpers for file metadata, shared by the explorer views, the
|
|
2
|
+
* properties dialog and the directory picker. */
|
|
3
|
+
|
|
4
|
+
import { FsError } from "@/lib/fs-api";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Description for a failure toast: the server's remediation hint (`EPROTECTED`/`EDENIED`)
|
|
8
|
+
* beats the raw message, matching the inline banner shown for a failed directory listing.
|
|
9
|
+
*/
|
|
10
|
+
export function errorDescription(e: unknown): string | undefined {
|
|
11
|
+
if (e instanceof FsError) return e.hint ?? e.message;
|
|
12
|
+
return e instanceof Error ? e.message : undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Human-readable byte count. Undefined size renders as an empty cell. */
|
|
16
|
+
export function formatSize(bytes?: number): string {
|
|
17
|
+
if (bytes == null) return "";
|
|
18
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
19
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
20
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
21
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Coarse "how long ago" label; good enough for a list column. */
|
|
25
|
+
export function formatRelativeTime(iso: string): string {
|
|
26
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
27
|
+
const mins = Math.floor(diff / 60000);
|
|
28
|
+
if (mins < 1) return "now";
|
|
29
|
+
if (mins < 60) return `${mins}m ago`;
|
|
30
|
+
const hrs = Math.floor(mins / 60);
|
|
31
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
32
|
+
const days = Math.floor(hrs / 24);
|
|
33
|
+
return `${days}d ago`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Absolute local timestamp for the properties dialog. Invalid dates render blank. */
|
|
37
|
+
export function formatDateTime(iso?: string): string {
|
|
38
|
+
if (!iso) return "";
|
|
39
|
+
const date = new Date(iso);
|
|
40
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
41
|
+
return date.toLocaleString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* POSIX permission bits as the familiar three-digit octal. Windows does not encode
|
|
46
|
+
* write permission in the mode, so callers show the read-only flag there instead.
|
|
47
|
+
*/
|
|
48
|
+
export function formatMode(mode?: number): string {
|
|
49
|
+
if (mode == null) return "";
|
|
50
|
+
return (mode & 0o777).toString(8).padStart(3, "0");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Directory name of an absolute path, using the host separator. Root maps to itself. */
|
|
54
|
+
export function dirnameOf(path: string, sep: string): string {
|
|
55
|
+
const trimmed = path.endsWith(sep) && path.length > sep.length ? path.slice(0, -sep.length) : path;
|
|
56
|
+
const idx = trimmed.lastIndexOf(sep);
|
|
57
|
+
if (idx < 0) return trimmed;
|
|
58
|
+
if (idx === 0) return sep; // POSIX root
|
|
59
|
+
const head = trimmed.slice(0, idx);
|
|
60
|
+
// "C:" alone is not browsable — a Windows drive root needs its trailing separator.
|
|
61
|
+
return head.endsWith(":") ? head + sep : head;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Join a directory and a child name with the host separator. */
|
|
65
|
+
export function joinPath(dir: string, name: string, sep: string): string {
|
|
66
|
+
return dir.endsWith(sep) ? `${dir}${name}` : `${dir}${sep}${name}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Insert a " (2)" style suffix before the extension, for "Keep both" pastes. */
|
|
70
|
+
export function suffixName(name: string, n: number): string {
|
|
71
|
+
const dot = name.lastIndexOf(".");
|
|
72
|
+
if (dot <= 0) return `${name} (${n})`;
|
|
73
|
+
return `${name.slice(0, dot)} (${n})${name.slice(dot)}`;
|
|
74
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extension → icon component, using the Symbols set (the VS Code icon theme by Miguel
|
|
3
|
+
* Solorio) so an explorer window reads like a real file manager.
|
|
4
|
+
*
|
|
5
|
+
* Imports come from the `/files` and `/folders` subpaths only. The root barrel pulls the
|
|
6
|
+
* whole library (900+ icons) into the graph, and `/utils` drags in a lookup table that
|
|
7
|
+
* defeats tree-shaking; naming each icon explicitly keeps the built chunk to the ~45
|
|
8
|
+
* glyphs actually referenced here.
|
|
9
|
+
*
|
|
10
|
+
* The map is deliberately curated rather than exhaustive: rare extensions fall back to a
|
|
11
|
+
* neutral glyph, which costs nothing and keeps the chunk inside its size budget.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { FC, ComponentProps } from "react";
|
|
15
|
+
import {
|
|
16
|
+
Astro, Audio, BracketsYellow, CLang, CodeBlue, CodeOrange, Cplus, Csharp, Csv, Dart,
|
|
17
|
+
Database, Docker, Document, Exe, Font, Gear, Gif, Git, Go, Image, Java, Js, Kotlin,
|
|
18
|
+
License, Lock, Lua, Markdown, Notebook, PDF, PHP, Python, Reactjs, Reactts, Ruby,
|
|
19
|
+
Rust, Sass, Shell, SVG, Svelte, Swift, Text, TypeScript, Video, Vue, XML, Yaml, Zip,
|
|
20
|
+
} from "@react-symbols/icons/files";
|
|
21
|
+
import { Folder, FolderOpen } from "@react-symbols/icons/folders";
|
|
22
|
+
|
|
23
|
+
export type SymbolIcon = FC<ComponentProps<"svg">>;
|
|
24
|
+
|
|
25
|
+
export const FOLDER_ICON: SymbolIcon = Folder;
|
|
26
|
+
export const FOLDER_OPEN_ICON: SymbolIcon = FolderOpen;
|
|
27
|
+
|
|
28
|
+
/** One entry per icon; the key list is the set of extensions that share that glyph. */
|
|
29
|
+
const GROUPS: [SymbolIcon, string[]][] = [
|
|
30
|
+
[TypeScript, ["ts", "mts", "cts"]],
|
|
31
|
+
[Reactts, ["tsx"]],
|
|
32
|
+
[Js, ["js", "mjs", "cjs"]],
|
|
33
|
+
[Reactjs, ["jsx"]],
|
|
34
|
+
[Vue, ["vue"]],
|
|
35
|
+
[Svelte, ["svelte"]],
|
|
36
|
+
[Astro, ["astro"]],
|
|
37
|
+
[BracketsYellow, ["json", "jsonc", "json5"]],
|
|
38
|
+
[Markdown, ["md", "mdx"]],
|
|
39
|
+
[CodeOrange, ["html", "htm"]],
|
|
40
|
+
[CodeBlue, ["css"]],
|
|
41
|
+
[Sass, ["scss", "sass", "less"]],
|
|
42
|
+
[Yaml, ["yaml", "yml"]],
|
|
43
|
+
[XML, ["xml", "plist"]],
|
|
44
|
+
[Gear, ["toml", "ini", "cfg", "conf", "env", "properties"]],
|
|
45
|
+
[Python, ["py", "pyi"]],
|
|
46
|
+
[Ruby, ["rb"]],
|
|
47
|
+
[Go, ["go"]],
|
|
48
|
+
[Rust, ["rs"]],
|
|
49
|
+
[Java, ["java", "jar"]],
|
|
50
|
+
[Kotlin, ["kt", "kts"]],
|
|
51
|
+
[Swift, ["swift"]],
|
|
52
|
+
[CLang, ["c", "h"]],
|
|
53
|
+
[Cplus, ["cpp", "cc", "cxx", "hpp"]],
|
|
54
|
+
[Csharp, ["cs"]],
|
|
55
|
+
[PHP, ["php"]],
|
|
56
|
+
[Lua, ["lua"]],
|
|
57
|
+
[Dart, ["dart"]],
|
|
58
|
+
[Shell, ["sh", "bash", "zsh", "fish", "ps1", "psm1", "bat", "cmd"]],
|
|
59
|
+
[Database, ["sql", "db", "sqlite", "sqlite3"]],
|
|
60
|
+
[Csv, ["csv", "tsv"]],
|
|
61
|
+
[PDF, ["pdf"]],
|
|
62
|
+
[Document, ["doc", "docx", "rtf", "odt", "pages"]],
|
|
63
|
+
[Notebook, ["ipynb"]],
|
|
64
|
+
[Text, ["txt", "log", "text"]],
|
|
65
|
+
[Image, ["png", "jpg", "jpeg", "webp", "bmp", "ico", "tif", "tiff", "avif"]],
|
|
66
|
+
[Gif, ["gif"]],
|
|
67
|
+
[SVG, ["svg"]],
|
|
68
|
+
[Video, ["mp4", "webm", "mov", "avi", "mkv", "m4v"]],
|
|
69
|
+
[Audio, ["mp3", "wav", "flac", "aac", "m4a", "ogg", "wma"]],
|
|
70
|
+
[Zip, ["zip", "rar", "7z", "tar", "gz", "tgz", "bz2", "xz"]],
|
|
71
|
+
[Exe, ["exe", "msi", "dmg", "appimage", "deb", "rpm"]],
|
|
72
|
+
[Font, ["ttf", "otf", "woff", "woff2", "eot"]],
|
|
73
|
+
[Lock, ["lock"]],
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const BY_EXTENSION = new Map<string, SymbolIcon>();
|
|
77
|
+
for (const [icon, extensions] of GROUPS) {
|
|
78
|
+
for (const ext of extensions) BY_EXTENSION.set(ext, icon);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Whole-name matches, for files whose type lives in the name rather than an extension. */
|
|
82
|
+
const BY_FILENAME = new Map<string, SymbolIcon>([
|
|
83
|
+
[".gitignore", Git],
|
|
84
|
+
[".gitattributes", Git],
|
|
85
|
+
[".gitmodules", Git],
|
|
86
|
+
["dockerfile", Docker],
|
|
87
|
+
[".dockerignore", Docker],
|
|
88
|
+
["license", License],
|
|
89
|
+
["licence", License],
|
|
90
|
+
["makefile", Gear],
|
|
91
|
+
[".editorconfig", Gear],
|
|
92
|
+
[".npmrc", Gear],
|
|
93
|
+
[".env", Gear],
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
/** Icon for a file name, or `null` when nothing in the curated set matches. */
|
|
97
|
+
export function fileIconFor(name: string): SymbolIcon | null {
|
|
98
|
+
const base = (name.split(/[/\\]/).pop() ?? name).toLowerCase();
|
|
99
|
+
const byName = BY_FILENAME.get(base);
|
|
100
|
+
if (byName) return byName;
|
|
101
|
+
// Compound extensions ("archive.tar.gz") resolve on the last segment, which is right
|
|
102
|
+
// for every entry above.
|
|
103
|
+
const dot = base.lastIndexOf(".");
|
|
104
|
+
if (dot <= 0) return null;
|
|
105
|
+
return BY_EXTENSION.get(base.slice(dot + 1)) ?? null;
|
|
106
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The icon shown next to an entry in every explorer view.
|
|
3
|
+
*
|
|
4
|
+
* Skins need to swap the folder glyph (a Windows 11 folder looks nothing like a Finder
|
|
5
|
+
* one) without touching the per-extension table, so the folder icon is read from a
|
|
6
|
+
* React context with the Symbols folder as the default. File icons stay shared: VS
|
|
7
|
+
* Code-style file glyphs are the same on all platforms.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createContext, useContext, type ComponentProps, type FC } from "react";
|
|
11
|
+
import { File as GenericFile, FolderSymlink, HelpCircle } from "lucide-react";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
import { fileIconFor, FOLDER_ICON, FOLDER_OPEN_ICON, type SymbolIcon } from "./file-type-icon-map";
|
|
14
|
+
|
|
15
|
+
export interface FolderIconSlot {
|
|
16
|
+
closed: SymbolIcon;
|
|
17
|
+
open: SymbolIcon;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const FolderIconContext = createContext<FolderIconSlot>({
|
|
21
|
+
closed: FOLDER_ICON,
|
|
22
|
+
open: FOLDER_OPEN_ICON,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/** Skin hook: wrap a subtree to replace the folder glyphs it renders. */
|
|
26
|
+
export const FolderIconProvider = FolderIconContext.Provider;
|
|
27
|
+
|
|
28
|
+
export interface FileTypeIconProps extends ComponentProps<"svg"> {
|
|
29
|
+
name: string;
|
|
30
|
+
kind: "file" | "directory" | "symlink" | "unknown";
|
|
31
|
+
/** Directories only — draws the open variant (column view, expanded rows). */
|
|
32
|
+
open?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function FileTypeIcon({ name, kind, open, className, ...rest }: FileTypeIconProps) {
|
|
36
|
+
const folder = useContext(FolderIconContext);
|
|
37
|
+
const classes = cn("shrink-0", className);
|
|
38
|
+
|
|
39
|
+
if (kind === "directory") {
|
|
40
|
+
const Icon = open ? folder.open : folder.closed;
|
|
41
|
+
return <Icon className={classes} {...rest} />;
|
|
42
|
+
}
|
|
43
|
+
if (kind === "symlink") {
|
|
44
|
+
// A link's target may be a file or a directory and is never followed, so it gets its
|
|
45
|
+
// own glyph rather than borrowing one of the two.
|
|
46
|
+
return <FolderSymlink className={cn(classes, "text-text-2")} {...rest} />;
|
|
47
|
+
}
|
|
48
|
+
if (kind === "unknown") {
|
|
49
|
+
// The server timed out reading this entry; showing a file icon would be a lie.
|
|
50
|
+
return <HelpCircle className={cn(classes, "text-text-subtle")} {...rest} />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const Icon: FC<ComponentProps<"svg">> | null = fileIconFor(name);
|
|
54
|
+
if (!Icon) return <GenericFile className={cn(classes, "text-text-2")} {...rest} />;
|
|
55
|
+
return <Icon className={classes} {...rest} />;
|
|
56
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bounded concurrency gate for thumbnail fetches, shared by every `ThumbnailImage` instance
|
|
3
|
+
* on screen — a folder of a thousand photos must still cap at `MAX_CONCURRENT` in-flight
|
|
4
|
+
* `/api/fs/raw` requests rather than firing one per tile.
|
|
5
|
+
*
|
|
6
|
+
* `acquireSlot()` resolves once a slot is free; the returned release function is idempotent
|
|
7
|
+
* (safe to call more than once for the same acquisition) — a caller may release both from a
|
|
8
|
+
* `finally` block and from an effect cleanup that fires for the same completed fetch, and a
|
|
9
|
+
* second release must be a no-op rather than decrementing `active` twice.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const MAX_CONCURRENT = 24;
|
|
13
|
+
|
|
14
|
+
let active = 0;
|
|
15
|
+
const queue: (() => void)[] = [];
|
|
16
|
+
|
|
17
|
+
export function acquireSlot(): Promise<() => void> {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let released = false;
|
|
20
|
+
const run = () => {
|
|
21
|
+
active++;
|
|
22
|
+
resolve(() => {
|
|
23
|
+
if (released) return;
|
|
24
|
+
released = true;
|
|
25
|
+
active--;
|
|
26
|
+
const next = queue.shift();
|
|
27
|
+
if (next) next();
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
if (active < MAX_CONCURRENT) run();
|
|
31
|
+
else queue.push(run);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Current in-flight count — exposed for tests only. */
|
|
36
|
+
export function activeCount(): number {
|
|
37
|
+
return active;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resets module state between tests. */
|
|
41
|
+
export function _resetSemaphore(): void {
|
|
42
|
+
active = 0;
|
|
43
|
+
queue.length = 0;
|
|
44
|
+
}
|
|
@@ -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
|
+
}
|