@hienlh/ppm 0.17.57 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-run.sh +5 -0
- package/CHANGELOG.md +24 -0
- package/CLAUDE.md +4 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +16 -2
- package/bun.lock +3 -0
- package/dist/web/assets/{ai-resource-editor-CXWQcx3_.js → ai-resource-editor-Bmk4P0YS.js} +1 -1
- package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-C-Kjr43S.js} +1 -1
- package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-CaDDbZqm.js} +1 -1
- package/dist/web/assets/api-client-CaRE4HYh.js +1 -0
- package/dist/web/assets/{api-client-COQtP-dB.js → api-client-rTAGdEKo.js} +1 -1
- package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-CAhcNpj5.js} +1 -1
- package/dist/web/assets/api-settings-DBcXfeJ8.js +1 -0
- package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-CAjg_Bt9.js} +1 -1
- package/dist/web/assets/{apl-DSANFLx0.js → apl-B1WsQFAb.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
- package/dist/web/assets/{astro-CXIotiNS.js → astro-Bdnz7nVp.js} +1 -1
- package/dist/web/assets/{audio-preview-D7jNc8mi.js → audio-preview-CikvnAOI.js} +1 -1
- package/dist/web/assets/{blade-BvrP2gPd.js → blade-B2euh8sS.js} +1 -1
- package/dist/web/assets/{bot-E9mcVZnr.js → bot-nk1k3ELu.js} +1 -1
- package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-GNeMaxd0.js} +1 -1
- package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-D7G0W7-s.js} +1 -1
- package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
- package/dist/web/assets/chat-tab-B3-KcCn2.js +13 -0
- package/dist/web/assets/chevron-right-BLOtX8Vy.js +1 -0
- package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-CENpx4oA.js} +1 -1
- package/dist/web/assets/clock-D493FU_D.js +1 -0
- package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Q6YgysnI.js} +1 -1
- package/dist/web/assets/code-CLF5x7KK.js +1 -0
- package/dist/web/assets/code-editor-BpbZw13T.js +10 -0
- package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-BjufWxVc.js} +1 -1
- package/dist/web/assets/{conflict-editor-CpGcSopK.js → conflict-editor-DC3liBm_.js} +3 -3
- package/dist/web/assets/context-menu-q-RHHAWz.js +1 -0
- package/dist/web/assets/copy-BApI9v7Z.js +1 -0
- package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CqWU0gCR.js} +1 -1
- package/dist/web/assets/{createLucideIcon-ConhwJnb.js → createLucideIcon-D8pyde1A.js} +1 -1
- package/dist/web/assets/{crystal-DMjCL--0.js → crystal-ylayVajF.js} +1 -1
- package/dist/web/assets/csv-preview-BhCOjgq1.js +2 -0
- package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-BzPfsCfd.js} +1 -1
- package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-CUx8cusN.js} +1 -1
- package/dist/web/assets/{database-COYAMZaw.js → database-B9okYLx6.js} +1 -1
- package/dist/web/assets/{database-viewer-DTMmic31.js → database-viewer-Z6uLyZVO.js} +1 -1
- package/dist/web/assets/dialog-CkI7rwrh.js +1 -0
- package/dist/web/assets/diff-viewer-CT1ALWt2.js +4 -0
- package/dist/web/assets/{dist-m_oJqIDk.js → dist-DduBIVkD.js} +1 -1
- package/dist/web/assets/{docx-preview-CR2ZtvNP.js → docx-preview-C_fSjfy_.js} +1 -1
- package/dist/web/assets/{edge-CTom35R_.js → edge-DX4NxQfh.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-DfX-9om9.js} +1 -1
- package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-CSh3NjbV.js} +1 -1
- package/dist/web/assets/{elm-CpD28xsW.js → elm-DBzwFYq9.js} +1 -1
- package/dist/web/assets/{erb-DHX2IIEs.js → erb-yxVOb8aB.js} +1 -1
- package/dist/web/assets/{erlang-B_etLcPG.js → erlang-5XIDkYrM.js} +1 -1
- package/dist/web/assets/{esm-PmlN1RTs.js → esm-DO4el2OJ.js} +1 -1
- package/dist/web/assets/explorer-body-CQCyGEet.js +2 -0
- package/dist/web/assets/explorer-window-content-EtyTAZSh.js +1 -0
- package/dist/web/assets/{extension-webview-BTCFWY2A.js → extension-webview-ePIM2fwM.js} +1 -1
- package/dist/web/assets/{eye-off-Rq1n2-oe.js → eye-off-CUdzgmX8.js} +1 -1
- package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-COAwgNGw.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-D4tE5VWY.js} +1 -1
- package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-By8soj6-.js} +1 -1
- package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-DoSwM7Hv.js} +1 -1
- package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-_ZiciHIx.js} +1 -1
- package/dist/web/assets/git-log-panel-BRWmTzX1.js +1 -0
- package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-C3Y5N9-s.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
- package/dist/web/assets/glide-data-grid-DCJCz4XB.js +138 -0
- package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BPBTXVlC.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-BQSZdy_D.js} +1 -1
- package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-CbQdxpU-.js} +1 -1
- package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-GMlKikpY.js} +1 -1
- package/dist/web/assets/group-chat-tab-C3UD2bcm.js +1 -0
- package/dist/web/assets/{hack-sxywdIAQ.js → hack-DyshcLec.js} +1 -1
- package/dist/web/assets/{haml-Dts9JlPU.js → haml-rqCqWzsq.js} +1 -1
- package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BCKwSWjR.js} +1 -1
- package/dist/web/assets/{html-2p0RvXi4.js → html-Cg29xPR1.js} +1 -1
- package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-geltIVg8.js} +1 -1
- package/dist/web/assets/{http-CHd2gYPf.js → http-pG7Vp237.js} +1 -1
- package/dist/web/assets/{hurl-boMPgDm9.js → hurl-Di3Uu_Mc.js} +1 -1
- package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CRy4lbv8.js} +1 -1
- package/dist/web/assets/{image-preview-0SGb7_tH.js → image-preview-CP82lor8.js} +1 -1
- package/dist/web/assets/index-BYetJoXQ.js +52 -0
- package/dist/web/assets/index-D-xwjNyX.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
- package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-C8GyVnvd.js} +1 -1
- package/dist/web/assets/{jison-C2vJYjbJ.js → jison-BywnqPn6.js} +1 -1
- package/dist/web/assets/{julia-DJ8MrQPA.js → julia-DTrO3T3B.js} +1 -1
- package/dist/web/assets/{just-BvQHq7VE.js → just-CA3KSHPi.js} +1 -1
- package/dist/web/assets/keybindings-store-yBWU8ZYT.js +1 -0
- package/dist/web/assets/{latex-ChNeshUC.js → latex-BC5xUMpc.js} +1 -1
- package/dist/web/assets/{lib-DTaN9riD.js → lib-BFetOULu.js} +1 -1
- package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-Chd8tSyQ.js} +1 -1
- package/dist/web/assets/{lua-D8NNsLbC.js → lua-DHaEtwLv.js} +1 -1
- package/dist/web/assets/markdown-renderer-B2D4gdok.js +1 -0
- package/dist/web/assets/markdown-renderer-DDgl5zDc.js +3 -0
- package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Desx_bC-.js} +1 -1
- package/dist/web/assets/{mdc-omkodQB5.js → mdc-DahWHc1t.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-BQt45q9T.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-BtxyynR3.js +1 -0
- package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-3A7lutCA.js} +1 -1
- package/dist/web/assets/{nginx-ByvWruGD.js → nginx-XQda6Hdy.js} +1 -1
- package/dist/web/assets/{nim-BrMojX4O.js → nim-DHqlpwP1.js} +1 -1
- package/dist/web/assets/notification-store-DKw_mGED.js +1 -0
- package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-DgZtViPO.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
- package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-s6Qx49RK.js} +1 -1
- package/dist/web/assets/{pdf-preview-DEn8amrO.js → pdf-preview-BXDOe7bG.js} +1 -1
- package/dist/web/assets/{perl-Bd_0l-b4.js → perl-M-ncjACb.js} +1 -1
- package/dist/web/assets/{php-D-DLTnWJ.js → php-CKIajrZi.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
- package/dist/web/assets/{plus-DcuJuDC3.js → plus-9LatwJMx.js} +1 -1
- package/dist/web/assets/{postgres-viewer-CjrM15gz.js → postgres-viewer-DkHYV33h.js} +3 -3
- package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-DyaEpHVG.js} +1 -1
- package/dist/web/assets/{pug-D2aO-l2T.js → pug-DO0u_gO4.js} +1 -1
- package/dist/web/assets/{qml-CyQVzNXS.js → qml-UEje1PAA.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
- package/dist/web/assets/{razor-8tvJXsO3.js → razor-lCs5T1Ib.js} +1 -1
- package/dist/web/assets/{react-8hrD3-kv.js → react-w6wINGnd.js} +1 -1
- package/dist/web/assets/{rst-C4AZyQQl.js → rst-CZX_O0z4.js} +1 -1
- package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-idycVAyE.js} +1 -1
- package/dist/web/assets/{sas-BtnF4zaV.js → sas-o5gTfUwp.js} +1 -1
- package/dist/web/assets/{save-BM4p9_9n.js → save-C4z1p51e.js} +1 -1
- package/dist/web/assets/{scss-GAyp5euH.js → scss-CRTk3iBQ.js} +1 -1
- package/dist/web/assets/settings-store-ChqHdT2V.js +2 -0
- package/dist/web/assets/settings-tab-ESL-6MAA.js +1 -0
- package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-3e5ln8ci.js} +1 -1
- package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-CGQjQ75I.js} +1 -1
- package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-C5laObJv.js} +1 -1
- package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-DULFtyLS.js} +1 -1
- package/dist/web/assets/{soy-DrLNhqfp.js → soy-CxY0VwX2.js} +1 -1
- package/dist/web/assets/sparkles-n1hzh65o.js +1 -0
- package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CBAGAFuq.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CT5M9PKQ.js → sql-query-editor-CZdZuawP.js} +1 -1
- package/dist/web/assets/sqlite-viewer-Cpq06-nz.js +1 -0
- package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BoezhOvk.js} +1 -1
- package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql-CHEzAhu7.js} +1 -1
- package/dist/web/assets/{svelte-23si1KV5.js → svelte-y6xtu2zX.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-De3oz9zD.js → system-monitor-tab-XR8NXHpv.js} +1 -1
- package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-C42sktPo.js} +1 -1
- package/dist/web/assets/{table-Di8pv2Y_.js → table-CHmxuh6M.js} +1 -1
- package/dist/web/assets/{templ-CPxlP4vF.js → templ-DbG2y5yD.js} +1 -1
- package/dist/web/assets/{terminal-tab-CciNqtMc.js → terminal-tab-CCCB-_50.js} +3 -3
- package/dist/web/assets/{tex-ugJpgYPc.js → tex-CeKQhrQG.js} +1 -1
- package/dist/web/assets/tool-cards-CsTs5go2.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
- package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-iKKOXJjG.js} +1 -1
- package/dist/web/assets/{twig-B7VUm2Ca.js → twig-DK8Vrp-G.js} +1 -1
- package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-DYtOHK7u.js} +1 -1
- package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-DnFSkUG2.js} +1 -1
- package/dist/web/assets/use-monaco-theme-CZh7xTJZ.js +1 -0
- package/dist/web/assets/vendor-markdown-Ls5AcU91.js +291 -0
- package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
- package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CaeeFO0B.js} +2 -2
- package/dist/web/assets/{video-preview-D-S_YnG6.js → video-preview-KbRcyuAA.js} +1 -1
- package/dist/web/assets/{vue-m75pWowk.js → vue-JkIEObMV.js} +1 -1
- package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-D8SqnU86.js} +1 -1
- package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DJj7N4WD.js} +1 -1
- package/dist/web/assets/x-B1qHmLdR.js +1 -0
- package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-00w9Rv.js} +1 -1
- package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-CVdCwPHV.js} +1 -1
- package/dist/web/index.html +29 -26
- package/dist/web/monacoeditorwork/css.worker.bundle.js +122 -122
- package/dist/web/monacoeditorwork/editor.worker.bundle.js +78 -78
- package/dist/web/monacoeditorwork/html.worker.bundle.js +110 -110
- package/dist/web/monacoeditorwork/json.worker.bundle.js +108 -108
- package/dist/web/monacoeditorwork/ts.worker.bundle.js +81 -81
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +10 -1
- package/docs/design-guidelines.md +31 -0
- package/docs/project-roadmap.md +10 -0
- package/docs/system-architecture.md +59 -0
- package/package.json +2 -1
- package/src/server/index.ts +8 -0
- package/src/server/routes/files.ts +2 -2
- package/src/server/routes/fs-browse.ts +99 -74
- package/src/server/routes/fs-ops.ts +118 -0
- package/src/server/routes/fs-sqlite.ts +129 -0
- package/src/server/routes/host-info.ts +17 -0
- package/src/server/routes/settings.ts +2 -0
- package/src/services/download-token.service.ts +21 -5
- package/src/services/file.service.ts +8 -12
- package/src/services/fs-browse.service.ts +104 -161
- package/src/services/fs-ops/fs-content-disposition.ts +17 -0
- package/src/services/fs-ops/fs-core-ops.ts +149 -0
- package/src/services/fs-ops/fs-error-response.ts +24 -0
- package/src/services/fs-ops/fs-git-init.service.ts +23 -0
- package/src/services/fs-ops/fs-hidden-names.ts +17 -0
- package/src/services/fs-ops/fs-list-files.service.ts +68 -0
- package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
- package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
- package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
- package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
- package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
- package/src/services/fs-ops/fs-real-path.ts +44 -0
- package/src/services/fs-ops/sql-row-reader.ts +26 -0
- package/src/services/fs-ops/sql-statement-guard.ts +63 -0
- package/src/services/fs-path-guard.service.ts +196 -0
- package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
- package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
- package/src/services/host-info/concurrency-limit.ts +20 -0
- package/src/services/host-info/host-drives.ts +190 -0
- package/src/services/host-info/host-info.service.ts +171 -0
- package/src/services/host-info/host-known-folders.ts +180 -0
- package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
- package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
- package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
- package/src/services/host-info/spawn-runner.ts +41 -0
- package/src/services/sqlite-types.ts +26 -0
- package/src/services/sqlite.service.ts +38 -29
- package/src/services/subagent-transcript-merger.ts +1 -1
- package/src/types/system.ts +50 -0
- package/src/web/app.tsx +19 -3
- package/src/web/components/editor/code-editor.tsx +3 -9
- package/src/web/components/explorer/file-tree.tsx +103 -21
- package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
- package/src/web/components/explorer/tree-node.tsx +34 -93
- package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
- package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
- package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
- package/src/web/components/floating-window/floating-window.tsx +167 -0
- package/src/web/components/floating-window/use-window-drag.ts +28 -0
- package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
- package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
- package/src/web/components/floating-window/use-window-resize.ts +30 -0
- package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
- package/src/web/components/floating-window/window-content-registry.ts +51 -0
- package/src/web/components/floating-window/window-geometry.ts +121 -0
- package/src/web/components/floating-window/window-layer.tsx +92 -0
- package/src/web/components/floating-window/window-persistence.ts +104 -0
- package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
- package/src/web/components/floating-window/window-store-types.ts +20 -0
- package/src/web/components/floating-window/window-store.ts +192 -0
- package/src/web/components/group-chat/group-list.tsx +2 -3
- package/src/web/components/layout/command-palette.tsx +10 -0
- package/src/web/components/layout/mobile-drawer-tab-bar.tsx +3 -10
- package/src/web/components/layout/mobile-drawer.tsx +13 -2
- package/src/web/components/layout/nav-section-rail.tsx +16 -11
- package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
- package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
- package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
- package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
- package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
- package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
- package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
- package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
- package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
- package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
- package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
- package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
- package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
- package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
- package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
- package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
- package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
- package/src/web/components/os-explorer/explorer-body.tsx +153 -0
- package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
- package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
- package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
- package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
- package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
- package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
- package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
- package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
- package/src/web/components/os-explorer/explorer-store.ts +179 -0
- package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
- package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
- package/src/web/components/os-explorer/format-file-meta.ts +74 -0
- package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
- package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
- package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
- package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
- package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
- package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
- package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
- package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
- package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
- package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
- package/src/web/components/os-explorer/open-explorer.ts +35 -0
- package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
- package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
- package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
- package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
- package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
- package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
- package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
- package/src/web/components/os-explorer/skins/skins.css +25 -0
- package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
- package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
- package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
- package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
- package/src/web/components/os-explorer/sort-options.ts +29 -0
- package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
- package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
- package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
- package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
- package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
- package/src/web/components/os-explorer/use-host-info.ts +85 -0
- package/src/web/components/os-explorer/views/column-row.tsx +126 -0
- package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
- package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
- package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
- package/src/web/components/os-explorer/views/column-view.tsx +204 -0
- package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
- package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
- package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
- package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
- package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
- package/src/web/components/os-explorer/views/list-row.tsx +162 -0
- package/src/web/components/os-explorer/views/list-view.tsx +177 -0
- package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
- package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
- package/src/web/components/settings/settings-tab.tsx +29 -2
- package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
- package/src/web/components/sqlite/use-sqlite.ts +7 -2
- package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
- package/src/web/components/ui/feature-badge.tsx +40 -0
- package/src/web/components/ui/file-browser-picker.tsx +1 -18
- package/src/web/lib/feature-badges.ts +22 -0
- package/src/web/lib/file-download.ts +6 -4
- package/src/web/lib/fs-api.ts +185 -0
- package/src/web/lib/sidebar-tabs/tab-registry.ts +4 -3
- package/src/web/stores/file-store.ts +38 -1
- package/src/web/stores/settings-store.ts +14 -0
- package/src/web/styles/globals.css +1 -0
- package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
- package/dist/web/assets/api-settings-De--HNHp.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
- package/dist/web/assets/chat-tab-C5U7a_rP.js +0 -13
- package/dist/web/assets/chevron-right-DeUue2aK.js +0 -1
- package/dist/web/assets/code-TekEnDFM.js +0 -1
- package/dist/web/assets/code-editor-CqlzLQ7T.js +0 -10
- package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
- package/dist/web/assets/csv-preview-DD94T9ff.js +0 -2
- package/dist/web/assets/dialog-HHdNInEH.js +0 -1
- package/dist/web/assets/diff-viewer-DyQLYP7J.js +0 -4
- package/dist/web/assets/git-log-panel-CrUgsLY5.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
- package/dist/web/assets/glide-data-grid-LinTYl0_.js +0 -138
- package/dist/web/assets/group-chat-tab-BqNJW7y2.js +0 -1
- package/dist/web/assets/index-CVJmlKhp.css +0 -2
- package/dist/web/assets/index-I2ik0ZF7.js +0 -50
- package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
- package/dist/web/assets/keybindings-store-C46tNxJE.js +0 -1
- package/dist/web/assets/markdown-renderer-DSxSupDq.js +0 -3
- package/dist/web/assets/markdown-renderer-xzyhJklS.js +0 -1
- package/dist/web/assets/notification-store-Dek7ClI2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
- package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
- package/dist/web/assets/settings-tab-DIuR1VIc.js +0 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CEObRQ7n.js +0 -1
- package/dist/web/assets/tool-cards-Bdx04yYD.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
- package/dist/web/assets/use-monaco-theme-k-17kQqf.js +0 -1
- package/dist/web/assets/vendor-markdown-CKTlmXbz.js +0 -291
- package/dist/web/assets/x-BsFLg-T8.js +0 -1
- /package/dist/web/assets/{abap-BM5PMK0-.js → abap-DrMyiWmK.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-BlSOyi0y.js} +0 -0
- /package/dist/web/assets/{ada-DzO1tMqV.js → ada-B_m96icB.js} +0 -0
- /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-Ch3vOiZB.js} +0 -0
- /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CdsaXc1n.js} +0 -0
- /package/dist/web/assets/{apex-3O2170Ju.js → apex-0AnsBevk.js} +0 -0
- /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-CSxLolzy.js} +0 -0
- /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
- /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-DXZS0jA5.js} +0 -0
- /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DDy3pH2A.js} +0 -0
- /package/dist/web/assets/{asm-p1-PUkh3.js → asm-Bd17hhqg.js} +0 -0
- /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-C62HJRs-.js} +0 -0
- /package/dist/web/assets/{awk-Cj93pRL8.js → awk-Cc-nazni.js} +0 -0
- /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-BsfmNWCU.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-BZ4HLa4X.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-Bhyjyiqr.js} +0 -0
- /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-QiWgir3v.js} +0 -0
- /package/dist/web/assets/{bat-Cg8hlSct.js → bat-DxtlkwN3.js} +0 -0
- /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-Bt21X7M_.js} +0 -0
- /package/dist/web/assets/{berry-BqVQYZZr.js → berry-Cksre7lO.js} +0 -0
- /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DkakgDlR.js} +0 -0
- /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-8crmcyXU.js} +0 -0
- /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-CewQ2I_Z.js} +0 -0
- /package/dist/web/assets/{c-BQFvJ6Ux.js → c-C_6zE8Ha.js} +0 -0
- /package/dist/web/assets/{c3-Da53KeG6.js → c3-DLWB9-dp.js} +0 -0
- /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-D8qYS6pR.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-Bh95lNRy.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-C1PlN3G0.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-CldtgdK4.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-BiwqxX9A.js} +0 -0
- /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-CGiU22jY.js} +0 -0
- /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-COIC3doh.js} +0 -0
- /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DWUEQfHv.js} +0 -0
- /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BNHiAQea.js} +0 -0
- /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-BuKaoS0j.js} +0 -0
- /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-D5IKbxsd.js} +0 -0
- /package/dist/web/assets/{coq-D2bX6-8g.js → coq-BMVS-p94.js} +0 -0
- /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-DMlbXbM4.js} +0 -0
- /package/dist/web/assets/{css-CIjciIIk.js → css-CjkJC89X.js} +0 -0
- /package/dist/web/assets/{csv-DW48Z5d-.js → csv-DoNVBBf0.js} +0 -0
- /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
- /package/dist/web/assets/{cue-Byh1xHko.js → cue-DoTRbBg-.js} +0 -0
- /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-DVsNdTT3.js} +0 -0
- /package/dist/web/assets/{d-CxaScvxf.js → d-EX7BBSrv.js} +0 -0
- /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-P_YTHwlh.js} +0 -0
- /package/dist/web/assets/{dart-BmpakMe-.js → dart-Do-rgU4S.js} +0 -0
- /package/dist/web/assets/{dax-B1aRUMXT.js → dax-DxOieacu.js} +0 -0
- /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-CvdV7SBf.js} +0 -0
- /package/dist/web/assets/{diff-CupGmT1a.js → diff-CNOetGen.js} +0 -0
- /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CMfLpPW_.js} +0 -0
- /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DAlG4_q-.js} +0 -0
- /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-Dh3qf6yx.js} +0 -0
- /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-baAGmJYG.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-l7ZIyums.js} +0 -0
- /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DeRjdBn8.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-CB05-jQA.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-CT-llQBT.js} +0 -0
- /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-ByPJkTT7.js} +0 -0
- /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-CeMarbvn.js} +0 -0
- /package/dist/web/assets/{fish-Vr62C2vP.js → fish-BYrt6Xtb.js} +0 -0
- /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-Cr5VcAwh.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DQmKtrLa.js} +0 -0
- /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-QTDKjzhY.js} +0 -0
- /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-Chr-5UYt.js} +0 -0
- /package/dist/web/assets/{genie-B4hkIXNU.js → genie-kWHMWnt0.js} +0 -0
- /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-DtEBQFUo.js} +0 -0
- /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-B5aiUta2.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-BkxtsTqR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-B9QjR_0K.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-D6bO-5c0.js} +0 -0
- /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-Cg2ZW_CJ.js} +0 -0
- /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-eD7qUVlQ.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-CFnlSVjd.js} +0 -0
- /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpYoOqIT.js} +0 -0
- /package/dist/web/assets/{gn-DuO2D2eW.js → gn-BxwtZPau.js} +0 -0
- /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-CRyiZDfl.js} +0 -0
- /package/dist/web/assets/{go-BL16cMJO.js → go-Dw1Qx94W.js} +0 -0
- /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-zq_SbWk-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-DbbNy8-Q.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-BQZ7Kj6C.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-vNavErXi.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-AOFbCd7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-DqtxND6H.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-Cg0basu6.js} +0 -0
- /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-CcsmcOez.js} +0 -0
- /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-BimYY9X4.js} +0 -0
- /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CTrXCK40.js} +0 -0
- /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-CmqqU6Qg.js} +0 -0
- /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-CuJtX_uf.js} +0 -0
- /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BGeSjDYJ.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-GYMgd3R0.js} +0 -0
- /package/dist/web/assets/{houston-Ddm8AgpV.js → houston--JD_5r2q.js} +0 -0
- /package/dist/web/assets/{hy-DzZa6alE.js → hy-C9ILOiqk.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
- /package/dist/web/assets/{imba-aiXalPnK.js → imba-B6F3yus5.js} +0 -0
- /package/dist/web/assets/{ini-CAvrVa5R.js → ini-BfkQswMZ.js} +0 -0
- /package/dist/web/assets/{java-PRMSQp24.js → java-DEH5avQ1.js} +0 -0
- /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-Dr4yHuV2.js} +0 -0
- /package/dist/web/assets/{json-uWUKqOxq.js → json-BuR35ewf.js} +0 -0
- /package/dist/web/assets/{json5-R_LXJXL0.js → json5-Ny9ZiJAC.js} +0 -0
- /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-zYVuoZkR.js} +0 -0
- /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-B9xthb_H.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-CCoLJbMP.js} +0 -0
- /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-W0GDlQXi.js} +0 -0
- /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-BrLJpWC-.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CuVwt_MO.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-B3W_617P.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-CEx4MP5W.js} +0 -0
- /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DilRejnB.js} +0 -0
- /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-Bq5JtKaa.js} +0 -0
- /package/dist/web/assets/{kusto-Ba87977j.js → kusto-Bhp2KYog.js} +0 -0
- /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-WA_7c5hu.js} +0 -0
- /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-AEGcXwS-.js} +0 -0
- /package/dist/web/assets/{less-Dfn9YC_2.js → less-NPqH-26u.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
- /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-B4xW7cgY.js} +0 -0
- /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CPyWNcDE.js} +0 -0
- /package/dist/web/assets/{log-QW3dqr1D.js → log-CeQmiQSz.js} +0 -0
- /package/dist/web/assets/{logo-DUinJheI.js → logo-BO0VrjBm.js} +0 -0
- /package/dist/web/assets/{luau-B1vcBzl8.js → luau-B6GT9kA1.js} +0 -0
- /package/dist/web/assets/{make-CqNL10gS.js → make-CwfmN7e_.js} +0 -0
- /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Bbe4FlLp.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-u7M3XGe7.js} +0 -0
- /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-iD9XbfsB.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-BGdI48zn.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-_mk62F5q.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-CxFbDyAu.js} +0 -0
- /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-Z0RUE3V_.js} +0 -0
- /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-BZ5wPYzV.js} +0 -0
- /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-CY1_aP-h.js} +0 -0
- /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-DKWtxOre.js} +0 -0
- /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-e1iwHDfP.js} +0 -0
- /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-BiDfQ4ir.js} +0 -0
- /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-DteFEHsj.js} +0 -0
- /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-BgPbE9xZ.js} +0 -0
- /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-mEbRp-mA.js} +0 -0
- /package/dist/web/assets/{move-BvhjG2ic.js → move-CogqZw7A.js} +0 -0
- /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-BD2qjmy1.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-BNX8GOD_.js} +0 -0
- /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-ed3GbziD.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-B0j7O0Wn.js} +0 -0
- /package/dist/web/assets/{nix-B9EAdptu.js → nix-DFDbcQQm.js} +0 -0
- /package/dist/web/assets/{nord-DxASd4qG.js → nord-D93TxEQi.js} +0 -0
- /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-3_H_XKja.js} +0 -0
- /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-BM3u6nlm.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DZ73h42g.js} +0 -0
- /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-B1PU5gmh.js} +0 -0
- /package/dist/web/assets/{odin-B8pz4gIL.js → odin-D14L6aRL.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-Bj-94Z8v.js} +0 -0
- /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DTdGWyOZ.js} +0 -0
- /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-Ciq554Sj.js} +0 -0
- /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-Bfd4qdGi.js} +0 -0
- /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-CTurDUfj.js} +0 -0
- /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BLQfZXQp.js} +0 -0
- /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DyYfP9kQ.js} +0 -0
- /package/dist/web/assets/{po-Dz4OaDNi.js → po-CgXdT8vQ.js} +0 -0
- /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DOg5fK6s.js} +0 -0
- /package/dist/web/assets/{polar-Che1XC2Z.js → polar-Dv4aoL6X.js} +0 -0
- /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-DxugHnJD.js} +0 -0
- /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-CR3kzRyX.js} +0 -0
- /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-BE91xJQB.js} +0 -0
- /package/dist/web/assets/{prisma-DCmjceML.js → prisma-B_3yUlRQ.js} +0 -0
- /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-DcT781Ht.js} +0 -0
- /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-CSf-w5k2.js} +0 -0
- /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CLGO6QWl.js} +0 -0
- /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-Bah2McKd.js} +0 -0
- /package/dist/web/assets/{python-Ctyr4faP.js → python-DUH8qy7N.js} +0 -0
- /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-YIsHnjKq.js} +0 -0
- /package/dist/web/assets/{qss-LbRHzb21.js → qss-CLmoVNA4.js} +0 -0
- /package/dist/web/assets/{r-Dvfjp0wc.js → r-Pon-b-vz.js} +0 -0
- /package/dist/web/assets/{racket-DI8v8nzD.js → racket-BA09P5Kg.js} +0 -0
- /package/dist/web/assets/{raku-CX1q9GW0.js → raku-DlpzU7Fe.js} +0 -0
- /package/dist/web/assets/{red-CSz3QEOw.js → red-l7VuCR6d.js} +0 -0
- /package/dist/web/assets/{reg-FMjhUQE-.js → reg-g_k7gQEr.js} +0 -0
- /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CT17B7I7.js} +0 -0
- /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-DkPCS7Bp.js} +0 -0
- /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
- /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-CYuFTw30.js} +0 -0
- /package/dist/web/assets/{ron-D0t4kvJG.js → ron-cY7u_yEu.js} +0 -0
- /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-Cr_h_Ubo.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-rRewaSZg.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-3VX5Xwca.js} +0 -0
- /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-Du4ie5PU.js} +0 -0
- /package/dist/web/assets/{rust-2jBzrC0l.js → rust-B7xWL-Eh.js} +0 -0
- /package/dist/web/assets/{sass-DD75Izu_.js → sass-oXbOG8de.js} +0 -0
- /package/dist/web/assets/{scala-DpBKROIu.js → scala-nlZJhKHf.js} +0 -0
- /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-FZ9_Zy8D.js} +0 -0
- /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-D_lyvXiu.js} +0 -0
- /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DRWRboAd.js} +0 -0
- /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-BGk2uVHu.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-PAoSX041.js} +0 -0
- /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-qP0zvICo.js} +0 -0
- /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-B7uccpH6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-CbICLZs3.js} +0 -0
- /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-CprWhyjj.js} +0 -0
- /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-B7pcm2K6.js} +0 -0
- /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-Co2LL4J_.js} +0 -0
- /package/dist/web/assets/{sql-CoqObhgo.js → sql-cETIdGnT.js} +0 -0
- /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-DETG6JCD.js} +0 -0
- /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-tmTre2-O.js} +0 -0
- /package/dist/web/assets/{swift-DtMMgvRI.js → swift-BeKQbULV.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-eCL4-Xdn.js} +0 -0
- /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-CviwWvEx.js} +0 -0
- /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-WuoFIbBS.js} +0 -0
- /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-B9VBu7VI.js} +0 -0
- /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-CN7r11ky.js} +0 -0
- /package/dist/web/assets/{tcl-caTR_frn.js → tcl-e1cdhe3h.js} +0 -0
- /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-BEP0mfa_.js} +0 -0
- /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BfA_dA37.js} +0 -0
- /package/dist/web/assets/{toml-B7D2mIDU.js → toml-BrDIfNnt.js} +0 -0
- /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-CyfGyE4i.js} +0 -0
- /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-BrFA8Iwf.js} +0 -0
- /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-9GBtR0mP.js} +0 -0
- /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-BuTRZ65V.js} +0 -0
- /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CRiNNRXF.js} +0 -0
- /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CmpTnnkl.js} +0 -0
- /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
- /package/dist/web/assets/{v-D4997FGs.js → v-DnIi2aab.js} +0 -0
- /package/dist/web/assets/{vala-AFg9DmKE.js → vala-B7VxCNZP.js} +0 -0
- /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-DGoQ3pBT.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
- /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog-zGOC4qwV.js} +0 -0
- /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-DXR9-06q.js} +0 -0
- /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CCO0oz0l.js} +0 -0
- /package/dist/web/assets/{viml-D6EKG66q.js → viml-Dxwr-QNN.js} +0 -0
- /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-Bhah7RxE.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-CGK6XVwc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-ne5kfw8h.js} +0 -0
- /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-ErDFqVoP.js} +0 -0
- /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
- /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-DtWvUWGr.js} +0 -0
- /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-EZe1IEYT.js} +0 -0
- /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-B1o5_I_9.js} +0 -0
- /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-C_rwp8An.js} +0 -0
- /package/dist/web/assets/{wit-DIrx_eCI.js → wit-gbYtF3rY.js} +0 -0
- /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-DrcQFe2n.js} +0 -0
- /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-CzDAObLX.js} +0 -0
- /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-B-J1phtg.js} +0 -0
- /package/dist/web/assets/{zig-CrzwMpQE.js → zig-BlzNia0O.js} +0 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One fixed-width, independently virtualised and independently scrolled column of the
|
|
3
|
+
* Miller browser. Column view keeps a single highlighted entry per column rather than the
|
|
4
|
+
* multi-select semantics List/Icons use — Finder's own column browser works the same way,
|
|
5
|
+
* and it is what lets "the highlighted entry" double as "which directory the next column
|
|
6
|
+
* shows" with no extra state.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useRef } from "react";
|
|
10
|
+
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
11
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
12
|
+
import { cn } from "@/lib/utils";
|
|
13
|
+
import type { ExplorerActions } from "../actions/use-explorer-actions";
|
|
14
|
+
import { DROP_TARGET_CLASS } from "../dnd/drop-target-style";
|
|
15
|
+
import { useDragAutoScroll } from "../dnd/use-drag-auto-scroll";
|
|
16
|
+
import { usePathDropTarget } from "../dnd/use-path-drop-target";
|
|
17
|
+
import { ColumnRow } from "./column-row";
|
|
18
|
+
|
|
19
|
+
export const COLUMN_WIDTH = 220;
|
|
20
|
+
/** Mobile's single full-width column needs a 44px+ touch target, bigger than the desktop
|
|
21
|
+
* (fine/coarse-pointer) `rowHeight` threaded in from `ExplorerBody`. Exported so
|
|
22
|
+
* `column-view-mobile.tsx` can pass a real value instead of a duplicated magic number —
|
|
23
|
+
* the `fullWidth` branch here always wins over whatever `rowHeight` prop it receives. */
|
|
24
|
+
export const ROW_HEIGHT_MOBILE = 48;
|
|
25
|
+
|
|
26
|
+
export interface ColumnViewColumnProps {
|
|
27
|
+
path: string;
|
|
28
|
+
entries: FsEntry[];
|
|
29
|
+
loading: boolean;
|
|
30
|
+
error: string | null;
|
|
31
|
+
selectedPath: string | null;
|
|
32
|
+
isFocused: boolean;
|
|
33
|
+
currentDir: string;
|
|
34
|
+
hasClipboard: boolean;
|
|
35
|
+
isPinned(path: string): boolean;
|
|
36
|
+
actions: ExplorerActions;
|
|
37
|
+
/** Row height in px; larger on coarse-pointer devices, same as List/Icons. */
|
|
38
|
+
rowHeight: number;
|
|
39
|
+
onSelect(entry: FsEntry): void;
|
|
40
|
+
onOpen(entry: FsEntry): void;
|
|
41
|
+
onFocus(): void;
|
|
42
|
+
/** Mobile's single-column mode: fill the container instead of the fixed desktop width. */
|
|
43
|
+
fullWidth?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function ColumnViewColumn({
|
|
47
|
+
path, entries, loading, error, selectedPath, isFocused, currentDir,
|
|
48
|
+
hasClipboard, isPinned, actions, rowHeight, onSelect, onOpen, onFocus, fullWidth,
|
|
49
|
+
}: ColumnViewColumnProps) {
|
|
50
|
+
const scrollRef = useRef<HTMLDivElement>(null);
|
|
51
|
+
useDragAutoScroll(scrollRef);
|
|
52
|
+
// Mobile's single full-width column always wants the bigger touch target, regardless of
|
|
53
|
+
// whatever fine/coarse-pointer density the caller threaded in for the desktop Miller strip.
|
|
54
|
+
const effectiveRowHeight = fullWidth ? ROW_HEIGHT_MOBILE : rowHeight;
|
|
55
|
+
const virtualizer = useVirtualizer({
|
|
56
|
+
count: entries.length,
|
|
57
|
+
getScrollElement: () => scrollRef.current,
|
|
58
|
+
estimateSize: () => effectiveRowHeight,
|
|
59
|
+
overscan: 8,
|
|
60
|
+
});
|
|
61
|
+
// The column's own empty area is a drop target for its own directory — dropping below the
|
|
62
|
+
// last row of an ancestor column moves/copies into that ancestor, not just the deepest one.
|
|
63
|
+
// Mobile's single full-width column never gets one: no drag source can ever start on touch.
|
|
64
|
+
const backgroundDrop = usePathDropTarget({ targetDir: path, run: actions.transferInto, disabled: fullWidth });
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className={cn("flex h-full shrink-0 flex-col", !fullWidth && "border-r border-border", isFocused && "bg-panel-2/40")}
|
|
69
|
+
style={fullWidth ? undefined : { width: COLUMN_WIDTH }}
|
|
70
|
+
>
|
|
71
|
+
<div
|
|
72
|
+
ref={scrollRef}
|
|
73
|
+
role="listbox"
|
|
74
|
+
aria-label={path}
|
|
75
|
+
className={cn("flex-1 overflow-y-auto outline-none", backgroundDrop.isOver && DROP_TARGET_CLASS)}
|
|
76
|
+
{...backgroundDrop.handlers}
|
|
77
|
+
>
|
|
78
|
+
{loading && entries.length === 0 && <p className="p-2 text-xs text-text-subtle">Loading…</p>}
|
|
79
|
+
{error && <p className="p-2 text-xs text-error">{error}</p>}
|
|
80
|
+
{!loading && !error && entries.length === 0 && <p className="p-2 text-xs text-text-subtle">Empty</p>}
|
|
81
|
+
<div className="relative w-full" style={{ height: virtualizer.getTotalSize() }}>
|
|
82
|
+
{virtualizer.getVirtualItems().map((item) => {
|
|
83
|
+
const entry = entries[item.index]!;
|
|
84
|
+
const selected = entry.path === selectedPath;
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
key={entry.path}
|
|
88
|
+
className="absolute left-0 top-0 w-full"
|
|
89
|
+
style={{ transform: `translateY(${item.start}px)` }}
|
|
90
|
+
>
|
|
91
|
+
<ColumnRow
|
|
92
|
+
entry={entry}
|
|
93
|
+
selected={selected}
|
|
94
|
+
currentDir={currentDir}
|
|
95
|
+
hasClipboard={hasClipboard}
|
|
96
|
+
isPinned={isPinned}
|
|
97
|
+
actions={actions}
|
|
98
|
+
rowHeight={effectiveRowHeight}
|
|
99
|
+
onSelect={() => {
|
|
100
|
+
onFocus();
|
|
101
|
+
onSelect(entry);
|
|
102
|
+
}}
|
|
103
|
+
onOpen={() => onOpen(entry)}
|
|
104
|
+
/>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
})}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column view's mobile presentation: only the focused column ever renders, full width, with
|
|
3
|
+
* a Back row above it instead of the desktop's side-by-side Miller strip — there is no room
|
|
4
|
+
* for more than one column on a phone, and no preview pane (files open directly on tap, see
|
|
5
|
+
* `column-view-column.tsx`'s mobile branch).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { KeyboardEvent, RefObject } from "react";
|
|
9
|
+
import { ArrowLeft } from "lucide-react";
|
|
10
|
+
import { ContextMenu, ContextMenuTrigger } from "@/components/ui/adaptive-context-menu";
|
|
11
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
12
|
+
import type { ExplorerActions } from "../actions/use-explorer-actions";
|
|
13
|
+
import { ExplorerContextMenu } from "../explorer-context-menu";
|
|
14
|
+
import type { ExplorerSlice } from "../explorer-store";
|
|
15
|
+
import type { LongPressHandlers } from "../use-coarse-long-press";
|
|
16
|
+
import { ColumnViewColumn, ROW_HEIGHT_MOBILE } from "./column-view-column";
|
|
17
|
+
import type { ColumnState } from "./use-column-view-state";
|
|
18
|
+
|
|
19
|
+
export interface ColumnViewMobileProps {
|
|
20
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
21
|
+
onKeyDown(event: KeyboardEvent): void;
|
|
22
|
+
backgroundLongPress: LongPressHandlers;
|
|
23
|
+
columns: ColumnState[];
|
|
24
|
+
focusedIndex: number;
|
|
25
|
+
setFocusedIndex(update: (i: number) => number): void;
|
|
26
|
+
selectedPathFor(index: number): string | null;
|
|
27
|
+
slice: ExplorerSlice;
|
|
28
|
+
hasClipboard: boolean;
|
|
29
|
+
isPinned(path: string): boolean;
|
|
30
|
+
actions: ExplorerActions;
|
|
31
|
+
onSelect(index: number, entry: FsEntry): void;
|
|
32
|
+
onOpen(entry: FsEntry): void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ColumnViewMobile({
|
|
36
|
+
containerRef, onKeyDown, backgroundLongPress, columns, focusedIndex, setFocusedIndex,
|
|
37
|
+
selectedPathFor, slice, hasClipboard, isPinned, actions, onSelect, onOpen,
|
|
38
|
+
}: ColumnViewMobileProps) {
|
|
39
|
+
const current = columns[focusedIndex];
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<ContextMenu>
|
|
43
|
+
<ContextMenuTrigger asChild>
|
|
44
|
+
<div
|
|
45
|
+
ref={containerRef}
|
|
46
|
+
tabIndex={0}
|
|
47
|
+
data-testid="explorer-columns"
|
|
48
|
+
aria-label="Column view"
|
|
49
|
+
onKeyDown={onKeyDown}
|
|
50
|
+
className="flex h-full min-h-0 w-full flex-col outline-none"
|
|
51
|
+
{...backgroundLongPress}
|
|
52
|
+
>
|
|
53
|
+
{focusedIndex > 0 && (
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
onClick={() => setFocusedIndex((i) => i - 1)}
|
|
57
|
+
aria-label="Back"
|
|
58
|
+
className="flex h-11 shrink-0 items-center gap-1.5 border-b border-border px-3 text-sm text-text-2 active:bg-surface-elevated"
|
|
59
|
+
>
|
|
60
|
+
<ArrowLeft className="size-4" /> Back
|
|
61
|
+
</button>
|
|
62
|
+
)}
|
|
63
|
+
{current && (
|
|
64
|
+
<div
|
|
65
|
+
key={current.path}
|
|
66
|
+
className="min-h-0 flex-1 motion-safe:animate-in motion-safe:slide-in-from-right-4 motion-safe:duration-150"
|
|
67
|
+
>
|
|
68
|
+
<ColumnViewColumn
|
|
69
|
+
path={current.path}
|
|
70
|
+
entries={current.entries}
|
|
71
|
+
loading={current.loading}
|
|
72
|
+
error={current.error}
|
|
73
|
+
selectedPath={selectedPathFor(focusedIndex)}
|
|
74
|
+
isFocused
|
|
75
|
+
fullWidth
|
|
76
|
+
rowHeight={ROW_HEIGHT_MOBILE}
|
|
77
|
+
currentDir={slice.path}
|
|
78
|
+
hasClipboard={hasClipboard}
|
|
79
|
+
isPinned={isPinned}
|
|
80
|
+
actions={actions}
|
|
81
|
+
onSelect={(entry) => onSelect(focusedIndex, entry)}
|
|
82
|
+
onOpen={onOpen}
|
|
83
|
+
onFocus={() => {}}
|
|
84
|
+
/>
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
</ContextMenuTrigger>
|
|
89
|
+
<ExplorerContextMenu
|
|
90
|
+
targets={[]}
|
|
91
|
+
currentDir={slice.path}
|
|
92
|
+
hasClipboard={hasClipboard}
|
|
93
|
+
isPinned={isPinned}
|
|
94
|
+
actions={actions}
|
|
95
|
+
/>
|
|
96
|
+
</ContextMenu>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preview pane for Column view — the file currently highlighted anywhere in the columns
|
|
3
|
+
* (deepest column, or an earlier one the user clicked into without drilling).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
7
|
+
import { canOpenInPpm, extensionOf, viewerKindOf } from "../can-open-in-ppm";
|
|
8
|
+
import { formatDateTime, formatSize } from "../format-file-meta";
|
|
9
|
+
import { FileTypeIcon } from "../icons/file-type-icon";
|
|
10
|
+
import { ThumbnailImage } from "../icons/thumbnail-image";
|
|
11
|
+
|
|
12
|
+
export interface ColumnViewPreviewProps {
|
|
13
|
+
entry: FsEntry | null;
|
|
14
|
+
onOpen(entry: FsEntry): void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ColumnViewPreview({ entry, onOpen }: ColumnViewPreviewProps) {
|
|
18
|
+
if (!entry) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex h-full items-center justify-center p-4 text-center text-xs text-text-subtle">
|
|
21
|
+
Select a file to preview
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isImage = entry.type !== "directory" && viewerKindOf(entry.name) === "image";
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
key={entry.path}
|
|
31
|
+
className="flex h-full min-w-0 flex-col items-center gap-3 overflow-y-auto p-4 text-center"
|
|
32
|
+
>
|
|
33
|
+
<div className="flex h-24 w-24 shrink-0 items-center justify-center">
|
|
34
|
+
{isImage ? (
|
|
35
|
+
<ThumbnailImage entry={entry} size={96} />
|
|
36
|
+
) : (
|
|
37
|
+
<FileTypeIcon name={entry.name} kind={entry.kind} className="size-16" />
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
<p className="w-full truncate text-sm font-medium text-text" title={entry.name}>
|
|
41
|
+
{entry.name}
|
|
42
|
+
</p>
|
|
43
|
+
<dl className="w-full space-y-1 text-left text-xs text-text-2">
|
|
44
|
+
<div className="flex justify-between gap-2">
|
|
45
|
+
<dt className="text-text-subtle">Kind</dt>
|
|
46
|
+
<dd className="truncate">{entry.type === "directory" ? "Folder" : extensionOf(entry.name) || "File"}</dd>
|
|
47
|
+
</div>
|
|
48
|
+
{entry.type !== "directory" && (
|
|
49
|
+
<div className="flex justify-between gap-2">
|
|
50
|
+
<dt className="text-text-subtle">Size</dt>
|
|
51
|
+
<dd>{formatSize(entry.size)}</dd>
|
|
52
|
+
</div>
|
|
53
|
+
)}
|
|
54
|
+
<div className="flex justify-between gap-2">
|
|
55
|
+
<dt className="text-text-subtle">Modified</dt>
|
|
56
|
+
<dd className="truncate">{formatDateTime(entry.modified)}</dd>
|
|
57
|
+
</div>
|
|
58
|
+
</dl>
|
|
59
|
+
{canOpenInPpm(entry.name) && (
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
onClick={() => onOpen(entry)}
|
|
63
|
+
className="mt-2 w-full rounded border border-border bg-panel-2 py-1.5 text-xs font-medium text-text can-hover:hover:bg-surface-elevated"
|
|
64
|
+
>
|
|
65
|
+
Open
|
|
66
|
+
</button>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Column (Miller) view: one fixed-width column per breadcrumb ancestor, plus a preview
|
|
3
|
+
* pane for the selected leaf file. Drilling into a directory — by click or arrow key — is
|
|
4
|
+
* just `nav.go`, the same call the toolbar's back/forward/breadcrumb use, so history and
|
|
5
|
+
* the breadcrumb agree with whatever this view is showing and switching to List/Icons
|
|
6
|
+
* never loses the place.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useEffect, useMemo, useRef, useState, type KeyboardEvent } from "react";
|
|
10
|
+
import { ContextMenu, ContextMenuTrigger } from "@/components/ui/adaptive-context-menu";
|
|
11
|
+
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
12
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
13
|
+
import { useDragAutoScroll } from "../dnd/use-drag-auto-scroll";
|
|
14
|
+
import { ExplorerContextMenu } from "../explorer-context-menu";
|
|
15
|
+
import { ColumnViewColumn } from "./column-view-column";
|
|
16
|
+
import { ColumnViewMobile } from "./column-view-mobile";
|
|
17
|
+
import { ColumnViewPreview } from "./column-view-preview";
|
|
18
|
+
import type { ExplorerViewProps } from "./explorer-view-registry";
|
|
19
|
+
import { useColumnViewState } from "./use-column-view-state";
|
|
20
|
+
|
|
21
|
+
/** Below this window width the preview pane would leave no room for the columns themselves. */
|
|
22
|
+
const NARROW_PREVIEW_THRESHOLD = 720;
|
|
23
|
+
|
|
24
|
+
export function ColumnView({
|
|
25
|
+
slice, entries, actions, selection, hasClipboard, isPinned, nav, backgroundLongPress, rowHeight,
|
|
26
|
+
}: ExplorerViewProps) {
|
|
27
|
+
const paths = useMemo(
|
|
28
|
+
() => (slice.breadcrumbs.length > 0 ? slice.breadcrumbs.map((b) => b.path) : [slice.path]),
|
|
29
|
+
[slice.breadcrumbs, slice.path],
|
|
30
|
+
);
|
|
31
|
+
const { columns } = useColumnViewState(paths, entries, slice.loading);
|
|
32
|
+
const isMobile = useIsMobile();
|
|
33
|
+
|
|
34
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
35
|
+
// Horizontal auto-scroll across the Miller strip itself; each column also scrolls
|
|
36
|
+
// vertically on its own (see `column-view-column.tsx`). Harmless on mobile — no drag
|
|
37
|
+
// source ever fires a `dragover` there, since `draggable` is withheld on coarse pointers.
|
|
38
|
+
useDragAutoScroll(containerRef);
|
|
39
|
+
const [focusedIndex, setFocusedIndex] = useState(columns.length - 1);
|
|
40
|
+
const [containerWidth, setContainerWidth] = useState(0);
|
|
41
|
+
const prevLength = useRef(columns.length);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const el = containerRef.current;
|
|
45
|
+
if (!el) return;
|
|
46
|
+
const measure = () => setContainerWidth(el.clientWidth);
|
|
47
|
+
measure();
|
|
48
|
+
const observer = new ResizeObserver(measure);
|
|
49
|
+
observer.observe(el);
|
|
50
|
+
return () => observer.disconnect();
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
// A new column (deeper drill, or a fresh window) moves the keyboard cursor onto it and
|
|
54
|
+
// scrolls it into view — every Miller browser keeps the newest column in frame.
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (columns.length === prevLength.current) return;
|
|
57
|
+
prevLength.current = columns.length;
|
|
58
|
+
setFocusedIndex(columns.length - 1);
|
|
59
|
+
const el = containerRef.current;
|
|
60
|
+
if (!el) return;
|
|
61
|
+
const reduceMotion = window.matchMedia?.("(prefers-reduced-motion: reduce)").matches === true;
|
|
62
|
+
el.scrollTo({ left: el.scrollWidth, behavior: reduceMotion ? "auto" : "smooth" });
|
|
63
|
+
}, [columns.length]);
|
|
64
|
+
|
|
65
|
+
// The preview pane follows the shared window selection wherever it lands, even in an
|
|
66
|
+
// earlier column — the deepest column is just the common case.
|
|
67
|
+
const previewEntry: FsEntry | null = useMemo(() => {
|
|
68
|
+
if (!slice.anchor) return null;
|
|
69
|
+
for (const column of columns) {
|
|
70
|
+
const found = column.entries.find((e) => e.path === slice.anchor);
|
|
71
|
+
if (found) return found;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}, [columns, slice.anchor]);
|
|
75
|
+
|
|
76
|
+
const selectedPathFor = (index: number): string | null =>
|
|
77
|
+
index === columns.length - 1 ? slice.anchor : (columns[index + 1]?.path ?? null);
|
|
78
|
+
|
|
79
|
+
const entryAt = (index: number): FsEntry | null => {
|
|
80
|
+
const column = columns[index];
|
|
81
|
+
if (!column) return null;
|
|
82
|
+
const path = selectedPathFor(index);
|
|
83
|
+
return column.entries.find((e) => e.path === path) ?? null;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleSelect = (index: number, entry: FsEntry) => {
|
|
87
|
+
setFocusedIndex(index);
|
|
88
|
+
if (entry.type === "directory") nav.go(entry.path);
|
|
89
|
+
else selection.selectOnly(entry.path);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleOpen = (entry: FsEntry) => actions.openEntry(entry);
|
|
93
|
+
|
|
94
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
95
|
+
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
96
|
+
|
|
97
|
+
if (event.key === "ArrowRight" && focusedIndex < columns.length - 1) {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
setFocusedIndex(focusedIndex + 1);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (event.key === "ArrowLeft" && focusedIndex > 0) {
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
event.stopPropagation();
|
|
106
|
+
setFocusedIndex(focusedIndex - 1);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
if (event.key === "ArrowUp" || event.key === "ArrowDown") {
|
|
110
|
+
const column = columns[focusedIndex];
|
|
111
|
+
if (!column) return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
event.stopPropagation();
|
|
114
|
+
const current = entryAt(focusedIndex);
|
|
115
|
+
const currentIndex = current ? column.entries.findIndex((e) => e.path === current.path) : -1;
|
|
116
|
+
const delta = event.key === "ArrowDown" ? 1 : -1;
|
|
117
|
+
const nextIndex = Math.min(column.entries.length - 1, Math.max(0, currentIndex < 0 ? 0 : currentIndex + delta));
|
|
118
|
+
const next = column.entries[nextIndex];
|
|
119
|
+
if (next) handleSelect(focusedIndex, next);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (event.key === "Enter") {
|
|
123
|
+
const current = entryAt(focusedIndex);
|
|
124
|
+
if (current) {
|
|
125
|
+
event.preventDefault();
|
|
126
|
+
event.stopPropagation();
|
|
127
|
+
handleOpen(current);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Defaults hidden (not shown) before the ResizeObserver's first report — showing it then
|
|
133
|
+
// hiding it a frame later on a narrow window is a worse flash than the reverse.
|
|
134
|
+
const showPreview = containerWidth >= NARROW_PREVIEW_THRESHOLD;
|
|
135
|
+
|
|
136
|
+
// Mobile has no room for a side-by-side Miller strip — see `column-view-mobile.tsx`.
|
|
137
|
+
if (isMobile) {
|
|
138
|
+
return (
|
|
139
|
+
<ColumnViewMobile
|
|
140
|
+
containerRef={containerRef}
|
|
141
|
+
onKeyDown={onKeyDown}
|
|
142
|
+
backgroundLongPress={backgroundLongPress}
|
|
143
|
+
columns={columns}
|
|
144
|
+
focusedIndex={focusedIndex}
|
|
145
|
+
setFocusedIndex={setFocusedIndex}
|
|
146
|
+
selectedPathFor={selectedPathFor}
|
|
147
|
+
slice={slice}
|
|
148
|
+
hasClipboard={hasClipboard}
|
|
149
|
+
isPinned={isPinned}
|
|
150
|
+
actions={actions}
|
|
151
|
+
onSelect={handleSelect}
|
|
152
|
+
onOpen={handleOpen}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<ContextMenu>
|
|
159
|
+
<ContextMenuTrigger asChild>
|
|
160
|
+
<div
|
|
161
|
+
ref={containerRef}
|
|
162
|
+
tabIndex={0}
|
|
163
|
+
data-testid="explorer-columns"
|
|
164
|
+
aria-label="Column view"
|
|
165
|
+
onKeyDown={onKeyDown}
|
|
166
|
+
className="flex h-full min-h-0 w-full overflow-x-auto outline-none"
|
|
167
|
+
{...backgroundLongPress}
|
|
168
|
+
>
|
|
169
|
+
{columns.map((column, index) => (
|
|
170
|
+
<ColumnViewColumn
|
|
171
|
+
key={column.path}
|
|
172
|
+
path={column.path}
|
|
173
|
+
entries={column.entries}
|
|
174
|
+
loading={column.loading}
|
|
175
|
+
error={column.error}
|
|
176
|
+
selectedPath={selectedPathFor(index)}
|
|
177
|
+
isFocused={index === focusedIndex}
|
|
178
|
+
currentDir={slice.path}
|
|
179
|
+
hasClipboard={hasClipboard}
|
|
180
|
+
isPinned={isPinned}
|
|
181
|
+
actions={actions}
|
|
182
|
+
rowHeight={rowHeight}
|
|
183
|
+
onSelect={(entry) => handleSelect(index, entry)}
|
|
184
|
+
onOpen={handleOpen}
|
|
185
|
+
onFocus={() => setFocusedIndex(index)}
|
|
186
|
+
/>
|
|
187
|
+
))}
|
|
188
|
+
{showPreview && (
|
|
189
|
+
<div className="min-w-[240px] flex-1 border-l border-border bg-panel-2/30">
|
|
190
|
+
<ColumnViewPreview entry={previewEntry} onOpen={handleOpen} />
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
</ContextMenuTrigger>
|
|
195
|
+
<ExplorerContextMenu
|
|
196
|
+
targets={[]}
|
|
197
|
+
currentDir={slice.path}
|
|
198
|
+
hasClipboard={hasClipboard}
|
|
199
|
+
isPinned={isPinned}
|
|
200
|
+
actions={actions}
|
|
201
|
+
/>
|
|
202
|
+
</ContextMenu>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merges the props a parent injects into a row with the row's own props.
|
|
3
|
+
*
|
|
4
|
+
* Radix's `ContextMenuTrigger asChild` clones its child and merges `ref`, `onContextMenu`,
|
|
5
|
+
* `onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerCancel`, `data-state`,
|
|
6
|
+
* `data-disabled` and `style={{ WebkitTouchCallout: "none", … }}` onto it. When the child is
|
|
7
|
+
* a custom component rather than a DOM element, those land in its props object and a fixed
|
|
8
|
+
* destructuring list silently drops every one it does not name — which is how the injected
|
|
9
|
+
* `style` (the only thing suppressing the native long-press callout on iOS) and Radix's own
|
|
10
|
+
* pointer-based long-press fallback disappeared before reaching the real `<div>`.
|
|
11
|
+
*
|
|
12
|
+
* Rules, in the order a row needs them:
|
|
13
|
+
* - two handlers for the same event chain, injected first, so the parent's behaviour (open
|
|
14
|
+
* the menu) happens before the row's own (select, then stop the event travelling further);
|
|
15
|
+
* - `style` and `className` merge, with the row's own values winning per key;
|
|
16
|
+
* - anything else defined by the row wins outright.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { cn } from "@/lib/utils";
|
|
20
|
+
import type { EntryRowDndProps } from "../dnd/use-entry-row-dnd";
|
|
21
|
+
|
|
22
|
+
type RowProps = Record<string, unknown>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The two props every row/tile adds on top of whatever Radix injects: the drag-and-drop
|
|
26
|
+
* handler bag from `useEntryRowDnd`, and whether this row is the currently claimed drop
|
|
27
|
+
* target (draws `DROP_TARGET_CLASS`).
|
|
28
|
+
*
|
|
29
|
+
* Deliberately *not* `extends Record<string, unknown>` — React's `PropsWithoutRef<P>` picks
|
|
30
|
+
* over `keyof P`, and an index signature widens that to `string | number`, which collapses
|
|
31
|
+
* every other named property's type to `unknown` for any component using this as its props
|
|
32
|
+
* type. Radix's own injected props (`onContextMenu`, pointer handlers, `data-state`, `style`,
|
|
33
|
+
* …) still land in the `...rest` destructured alongside this — object rest/spread copies
|
|
34
|
+
* every own enumerable property at runtime regardless of what the static type declares, so
|
|
35
|
+
* they are forwarded correctly whether or not the type says so.
|
|
36
|
+
*/
|
|
37
|
+
export interface InjectedRowProps {
|
|
38
|
+
dnd: EntryRowDndProps;
|
|
39
|
+
dropActive: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Handler = (event: unknown) => void;
|
|
43
|
+
|
|
44
|
+
function isHandlerKey(key: string): boolean {
|
|
45
|
+
return key.startsWith("on") && key.length > 2 && key[2] === key[2]?.toUpperCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function chain(first: unknown, second: unknown): Handler {
|
|
49
|
+
return (event) => {
|
|
50
|
+
(first as Handler | undefined)?.(event);
|
|
51
|
+
(second as Handler | undefined)?.(event);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function composeInteractiveRowProps<P extends RowProps>(injected: P | undefined, own: P): P {
|
|
56
|
+
if (!injected) return own;
|
|
57
|
+
const merged: RowProps = { ...injected };
|
|
58
|
+
|
|
59
|
+
for (const [key, value] of Object.entries(own)) {
|
|
60
|
+
const existing = merged[key];
|
|
61
|
+
if (isHandlerKey(key) && typeof existing === "function" && typeof value === "function") {
|
|
62
|
+
merged[key] = chain(existing, value);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (key === "style" && existing && value) {
|
|
66
|
+
merged[key] = { ...(existing as object), ...(value as object) };
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (key === "className") {
|
|
70
|
+
merged[key] = cn(existing as string | undefined, value as string | undefined);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (value !== undefined) merged[key] = value;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return merged as P;
|
|
77
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* View mode → component. The body renders whatever is registered for the current mode and
|
|
3
|
+
* falls back to List for a mode that is not implemented yet, so a persisted preference
|
|
4
|
+
* can never leave a window blank.
|
|
5
|
+
*
|
|
6
|
+
* Adding Icons or Column view means adding the component file and one entry here; nothing
|
|
7
|
+
* else in the explorer needs to change. The toolbar offers exactly the registered modes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ComponentType } from "react";
|
|
11
|
+
import type { FsEntry } from "@/lib/fs-api";
|
|
12
|
+
import type { ExplorerActions } from "../actions/use-explorer-actions";
|
|
13
|
+
import type { ExplorerSlice, ViewMode } from "../explorer-store";
|
|
14
|
+
import type { LongPressHandlers } from "../use-coarse-long-press";
|
|
15
|
+
import type { ExplorerNavigation } from "../use-explorer-navigation";
|
|
16
|
+
import type { EntrySelection } from "./use-entry-selection";
|
|
17
|
+
import { ColumnView } from "./column-view";
|
|
18
|
+
import { IconsView } from "./icons-view";
|
|
19
|
+
import { ListView } from "./list-view";
|
|
20
|
+
|
|
21
|
+
export interface ExplorerViewProps {
|
|
22
|
+
windowId: string;
|
|
23
|
+
slice: ExplorerSlice;
|
|
24
|
+
/** Already filtered and sorted — every view shares one order. */
|
|
25
|
+
entries: FsEntry[];
|
|
26
|
+
actions: ExplorerActions;
|
|
27
|
+
selection: EntrySelection;
|
|
28
|
+
/** Message from the last rejected inline commit, shown under the input. */
|
|
29
|
+
inlineError: string | null;
|
|
30
|
+
hasClipboard: boolean;
|
|
31
|
+
isPinned(path: string): boolean;
|
|
32
|
+
/** Row height in px; larger on coarse-pointer devices. */
|
|
33
|
+
rowHeight: number;
|
|
34
|
+
/** Column view drills by navigating like every other entry point (history/breadcrumb stay in sync). */
|
|
35
|
+
nav: ExplorerNavigation;
|
|
36
|
+
/**
|
|
37
|
+
* Long-press handlers for the empty-space background menu. Must land on the same element
|
|
38
|
+
* the view's background `ContextMenuTrigger` wraps — a wrapper further up the tree fires
|
|
39
|
+
* the synthetic `contextmenu` event on itself, and it never bubbles down to a descendant
|
|
40
|
+
* trigger.
|
|
41
|
+
*/
|
|
42
|
+
backgroundLongPress: LongPressHandlers;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const EXPLORER_VIEWS: Partial<Record<ViewMode, ComponentType<ExplorerViewProps>>> = {
|
|
46
|
+
list: ListView,
|
|
47
|
+
icons: IconsView,
|
|
48
|
+
columns: ColumnView,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const AVAILABLE_VIEW_MODES = Object.keys(EXPLORER_VIEWS) as ViewMode[];
|
|
52
|
+
|
|
53
|
+
export function viewComponentFor(mode: ViewMode): ComponentType<ExplorerViewProps> {
|
|
54
|
+
return EXPLORER_VIEWS[mode] ?? ListView;
|
|
55
|
+
}
|