@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,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-window explorer state, keyed by floating-window id.
|
|
3
|
+
*
|
|
4
|
+
* Each window browses independently (own path, history, selection, filter), so the state
|
|
5
|
+
* cannot live in a single global slice. View preferences are the exception: sort, view
|
|
6
|
+
* mode and the hidden-files toggle are user settings and are shared plus persisted.
|
|
7
|
+
*
|
|
8
|
+
* Windows also have to agree about the disk. After any mutation the acting window calls
|
|
9
|
+
* `fsChanged(dir)` and every window currently showing that directory refetches, which is
|
|
10
|
+
* what makes "cut here, paste there" look instantaneous in both frames.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { create } from "zustand";
|
|
14
|
+
import type { FsBreadcrumb, FsEntry } from "@/lib/fs-api";
|
|
15
|
+
|
|
16
|
+
export type SortKey = "name" | "size" | "modified" | "kind";
|
|
17
|
+
export type SortDir = "asc" | "desc";
|
|
18
|
+
export type ViewMode = "list" | "icons" | "columns";
|
|
19
|
+
|
|
20
|
+
export interface ExplorerError {
|
|
21
|
+
message: string;
|
|
22
|
+
code: string;
|
|
23
|
+
hint?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Inline editing state: renaming an existing entry, or naming a new one. */
|
|
27
|
+
export type InlineEdit =
|
|
28
|
+
| { kind: "rename"; path: string; initial: string }
|
|
29
|
+
| { kind: "new-file" | "new-folder"; initial: string };
|
|
30
|
+
|
|
31
|
+
export interface ExplorerSlice {
|
|
32
|
+
path: string;
|
|
33
|
+
/** Visited paths; `historyIndex` points at the current one. */
|
|
34
|
+
history: string[];
|
|
35
|
+
historyIndex: number;
|
|
36
|
+
entries: FsEntry[];
|
|
37
|
+
breadcrumbs: FsBreadcrumb[];
|
|
38
|
+
parent: string | null;
|
|
39
|
+
sep: string;
|
|
40
|
+
truncated: boolean;
|
|
41
|
+
loading: boolean;
|
|
42
|
+
error: ExplorerError | null;
|
|
43
|
+
selection: Set<string>;
|
|
44
|
+
/** Range-selection anchor; shift-click extends from here. */
|
|
45
|
+
anchor: string | null;
|
|
46
|
+
filter: string;
|
|
47
|
+
inlineEdit: InlineEdit | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ExplorerViewPrefs {
|
|
51
|
+
sort: { key: SortKey; dir: SortDir };
|
|
52
|
+
viewMode: ViewMode;
|
|
53
|
+
showHidden: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const VIEW_PREFS_KEY = "ppm-explorer-view";
|
|
57
|
+
|
|
58
|
+
const DEFAULT_PREFS: ExplorerViewPrefs = {
|
|
59
|
+
sort: { key: "name", dir: "asc" },
|
|
60
|
+
viewMode: "list",
|
|
61
|
+
showHidden: false,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function loadPrefs(): ExplorerViewPrefs {
|
|
65
|
+
try {
|
|
66
|
+
const raw = localStorage.getItem(VIEW_PREFS_KEY);
|
|
67
|
+
if (!raw) return DEFAULT_PREFS;
|
|
68
|
+
const parsed = JSON.parse(raw) as Partial<ExplorerViewPrefs>;
|
|
69
|
+
return {
|
|
70
|
+
sort: parsed.sort?.key ? parsed.sort : DEFAULT_PREFS.sort,
|
|
71
|
+
viewMode: parsed.viewMode ?? DEFAULT_PREFS.viewMode,
|
|
72
|
+
showHidden: parsed.showHidden ?? DEFAULT_PREFS.showHidden,
|
|
73
|
+
};
|
|
74
|
+
} catch {
|
|
75
|
+
return DEFAULT_PREFS;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function savePrefs(prefs: ExplorerViewPrefs): void {
|
|
80
|
+
try {
|
|
81
|
+
localStorage.setItem(VIEW_PREFS_KEY, JSON.stringify(prefs));
|
|
82
|
+
} catch {
|
|
83
|
+
/* quota or private mode — preferences simply do not persist */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function emptySlice(path: string): ExplorerSlice {
|
|
88
|
+
return {
|
|
89
|
+
path,
|
|
90
|
+
history: [path],
|
|
91
|
+
historyIndex: 0,
|
|
92
|
+
entries: [],
|
|
93
|
+
breadcrumbs: [],
|
|
94
|
+
parent: null,
|
|
95
|
+
sep: "/",
|
|
96
|
+
truncated: false,
|
|
97
|
+
loading: true,
|
|
98
|
+
error: null,
|
|
99
|
+
selection: new Set(),
|
|
100
|
+
anchor: null,
|
|
101
|
+
filter: "",
|
|
102
|
+
inlineEdit: null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
interface ExplorerStore extends ExplorerViewPrefs {
|
|
107
|
+
slices: Record<string, ExplorerSlice>;
|
|
108
|
+
/** Create the slice for a window if it does not exist yet. */
|
|
109
|
+
ensure(id: string, path: string): void;
|
|
110
|
+
/** Drop a window's slice when it closes. */
|
|
111
|
+
discard(id: string): void;
|
|
112
|
+
patch(id: string, partial: Partial<ExplorerSlice>): void;
|
|
113
|
+
setPrefs(partial: Partial<ExplorerViewPrefs>): void;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const useExplorerStore = create<ExplorerStore>((set, get) => ({
|
|
117
|
+
...loadPrefs(),
|
|
118
|
+
slices: {},
|
|
119
|
+
|
|
120
|
+
ensure: (id, path) => {
|
|
121
|
+
if (get().slices[id]) return;
|
|
122
|
+
set((s) => ({ slices: { ...s.slices, [id]: emptySlice(path) } }));
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
discard: (id) => {
|
|
126
|
+
set((s) => {
|
|
127
|
+
if (!s.slices[id]) return s;
|
|
128
|
+
const slices = { ...s.slices };
|
|
129
|
+
delete slices[id];
|
|
130
|
+
return { slices };
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
patch: (id, partial) => {
|
|
135
|
+
set((s) => {
|
|
136
|
+
const current = s.slices[id];
|
|
137
|
+
if (!current) return s;
|
|
138
|
+
return { slices: { ...s.slices, [id]: { ...current, ...partial } } };
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
setPrefs: (partial) => {
|
|
143
|
+
set((s) => {
|
|
144
|
+
const next: ExplorerViewPrefs = {
|
|
145
|
+
sort: partial.sort ?? s.sort,
|
|
146
|
+
viewMode: partial.viewMode ?? s.viewMode,
|
|
147
|
+
showHidden: partial.showHidden ?? s.showHidden,
|
|
148
|
+
};
|
|
149
|
+
savePrefs(next);
|
|
150
|
+
return next;
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
/** Read a window's slice outside React (action modules, keyboard handlers). */
|
|
156
|
+
export function sliceOf(id: string): ExplorerSlice | undefined {
|
|
157
|
+
return useExplorerStore.getState().slices[id];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── Cross-window refresh ───────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
type DirListener = (dir: string) => void;
|
|
163
|
+
const dirListeners = new Set<DirListener>();
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Announce that `dir`'s contents changed on disk. Every window showing that
|
|
167
|
+
* directory — including the one that caused the change — refetches.
|
|
168
|
+
*/
|
|
169
|
+
export function fsChanged(...dirs: (string | null | undefined)[]): void {
|
|
170
|
+
for (const dir of dirs) {
|
|
171
|
+
if (!dir) continue;
|
|
172
|
+
for (const listener of dirListeners) listener(dir);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function onFsChanged(listener: DirListener): () => void {
|
|
177
|
+
dirListeners.add(listener);
|
|
178
|
+
return () => dirListeners.delete(listener);
|
|
179
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Window toolbar: history navigation, the breadcrumb (switchable to a raw path field),
|
|
3
|
+
* a name filter, the view switch and the hidden-files toggle.
|
|
4
|
+
*
|
|
5
|
+
* The path field is a deliberate toggle rather than a click-to-edit breadcrumb: pasting a
|
|
6
|
+
* path is the fastest way to reach a deep folder, and a breadcrumb that turns into a field
|
|
7
|
+
* on click makes the crumbs themselves hard to hit.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import {
|
|
12
|
+
ArrowLeft, ArrowRight, ArrowUp, Columns3, Eye, EyeOff, LayoutGrid, List, PencilLine, RefreshCw, Search, X,
|
|
13
|
+
} from "lucide-react";
|
|
14
|
+
import { cn } from "@/lib/utils";
|
|
15
|
+
import { ExplorerBreadcrumb } from "./explorer-breadcrumb";
|
|
16
|
+
import { ExplorerSortMenu } from "./explorer-sort-menu";
|
|
17
|
+
import { useExplorerStore, type ExplorerSlice, type ViewMode } from "./explorer-store";
|
|
18
|
+
import { toolbarButtonClass as buttonClass } from "./toolbar-icon-button";
|
|
19
|
+
import { usePrefersCoarsePointer } from "./use-coarse-long-press";
|
|
20
|
+
import type { ExplorerNavigation } from "./use-explorer-navigation";
|
|
21
|
+
import { AVAILABLE_VIEW_MODES } from "./views/explorer-view-registry";
|
|
22
|
+
|
|
23
|
+
const VIEW_ICON: Record<ViewMode, typeof List> = { list: List, icons: LayoutGrid, columns: Columns3 };
|
|
24
|
+
const VIEW_LABEL: Record<ViewMode, string> = { list: "List view", icons: "Icons view", columns: "Column view" };
|
|
25
|
+
|
|
26
|
+
export interface ExplorerToolbarProps {
|
|
27
|
+
windowId: string;
|
|
28
|
+
slice: ExplorerSlice;
|
|
29
|
+
nav: ExplorerNavigation;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function ExplorerToolbar({ windowId, slice, nav }: ExplorerToolbarProps) {
|
|
33
|
+
const patch = useExplorerStore((s) => s.patch);
|
|
34
|
+
const showHidden = useExplorerStore((s) => s.showHidden);
|
|
35
|
+
const viewMode = useExplorerStore((s) => s.viewMode);
|
|
36
|
+
const setPrefs = useExplorerStore((s) => s.setPrefs);
|
|
37
|
+
|
|
38
|
+
const [editingPath, setEditingPath] = useState(false);
|
|
39
|
+
const [draftPath, setDraftPath] = useState(slice.path);
|
|
40
|
+
useEffect(() => setDraftPath(slice.path), [slice.path]);
|
|
41
|
+
const coarse = usePrefersCoarsePointer();
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex shrink-0 flex-wrap items-center gap-1 border-b border-border bg-[var(--x-toolbar-bg,var(--panel-2))] px-1.5 py-1">
|
|
45
|
+
<button type="button" aria-label="Back" title="Back" disabled={!nav.canGoBack} onClick={nav.back} className={buttonClass(coarse)}>
|
|
46
|
+
<ArrowLeft className="size-4" />
|
|
47
|
+
</button>
|
|
48
|
+
<button type="button" aria-label="Forward" title="Forward" disabled={!nav.canGoForward} onClick={nav.forward} className={buttonClass(coarse)}>
|
|
49
|
+
<ArrowRight className="size-4" />
|
|
50
|
+
</button>
|
|
51
|
+
<button type="button" aria-label="Up one level" title="Up" disabled={!slice.parent} onClick={nav.up} className={buttonClass(coarse)}>
|
|
52
|
+
<ArrowUp className="size-4" />
|
|
53
|
+
</button>
|
|
54
|
+
<button type="button" aria-label="Refresh" title="Refresh" onClick={nav.refresh} className={buttonClass(coarse)}>
|
|
55
|
+
<RefreshCw className={cn("size-4", slice.loading && "animate-spin")} />
|
|
56
|
+
</button>
|
|
57
|
+
|
|
58
|
+
<div className="flex min-w-[8rem] flex-1 items-center gap-1 rounded border border-border bg-panel px-1">
|
|
59
|
+
{editingPath ? (
|
|
60
|
+
<input
|
|
61
|
+
autoFocus
|
|
62
|
+
aria-label="Path"
|
|
63
|
+
value={draftPath}
|
|
64
|
+
onChange={(e) => setDraftPath(e.target.value)}
|
|
65
|
+
onBlur={() => setEditingPath(false)}
|
|
66
|
+
onKeyDown={(e) => {
|
|
67
|
+
e.stopPropagation();
|
|
68
|
+
if (e.key === "Enter") { nav.go(draftPath.trim()); setEditingPath(false); }
|
|
69
|
+
else if (e.key === "Escape") { setDraftPath(slice.path); setEditingPath(false); }
|
|
70
|
+
}}
|
|
71
|
+
className="min-w-0 flex-1 bg-transparent py-1 font-mono text-xs text-text outline-none"
|
|
72
|
+
/>
|
|
73
|
+
) : (
|
|
74
|
+
<ExplorerBreadcrumb crumbs={slice.breadcrumbs} onNavigate={nav.go} sep={slice.sep} className="flex-1 overflow-hidden py-0.5" />
|
|
75
|
+
)}
|
|
76
|
+
<button
|
|
77
|
+
type="button"
|
|
78
|
+
aria-label={editingPath ? "Show breadcrumb" : "Edit path"}
|
|
79
|
+
title={editingPath ? "Show breadcrumb" : "Edit path"}
|
|
80
|
+
onClick={() => setEditingPath((v) => !v)}
|
|
81
|
+
className={buttonClass(coarse)}
|
|
82
|
+
>
|
|
83
|
+
<PencilLine className="size-3.5" />
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
<div className="flex items-center gap-1 rounded border border-border bg-panel px-1">
|
|
88
|
+
<Search className="size-3.5 shrink-0 text-text-subtle" />
|
|
89
|
+
<input
|
|
90
|
+
aria-label="Filter entries"
|
|
91
|
+
placeholder="Filter"
|
|
92
|
+
value={slice.filter}
|
|
93
|
+
onChange={(e) => patch(windowId, { filter: e.target.value })}
|
|
94
|
+
onKeyDown={(e) => {
|
|
95
|
+
e.stopPropagation();
|
|
96
|
+
if (e.key === "Escape") patch(windowId, { filter: "" });
|
|
97
|
+
}}
|
|
98
|
+
className="w-24 min-w-0 bg-transparent py-1 text-xs text-text outline-none placeholder:text-text-subtle"
|
|
99
|
+
/>
|
|
100
|
+
{slice.filter && (
|
|
101
|
+
<button type="button" aria-label="Clear filter" onClick={() => patch(windowId, { filter: "" })} className={buttonClass(coarse)}>
|
|
102
|
+
<X className="size-3.5" />
|
|
103
|
+
</button>
|
|
104
|
+
)}
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{AVAILABLE_VIEW_MODES.length > 1 &&
|
|
108
|
+
AVAILABLE_VIEW_MODES.map((mode) => {
|
|
109
|
+
const Icon = VIEW_ICON[mode];
|
|
110
|
+
return (
|
|
111
|
+
<button
|
|
112
|
+
key={mode}
|
|
113
|
+
type="button"
|
|
114
|
+
aria-label={VIEW_LABEL[mode]}
|
|
115
|
+
title={VIEW_LABEL[mode]}
|
|
116
|
+
aria-pressed={viewMode === mode}
|
|
117
|
+
onClick={() => setPrefs({ viewMode: mode })}
|
|
118
|
+
className={cn(buttonClass(coarse), viewMode === mode && "bg-accent-wash text-primary")}
|
|
119
|
+
>
|
|
120
|
+
<Icon className="size-4" />
|
|
121
|
+
</button>
|
|
122
|
+
);
|
|
123
|
+
})}
|
|
124
|
+
|
|
125
|
+
<ExplorerSortMenu coarse={coarse} />
|
|
126
|
+
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
aria-label={showHidden ? "Hide hidden files" : "Show hidden files"}
|
|
130
|
+
title={showHidden ? "Hide hidden files" : "Show hidden files"}
|
|
131
|
+
aria-pressed={showHidden}
|
|
132
|
+
onClick={() => setPrefs({ showHidden: !showHidden })}
|
|
133
|
+
className={cn(buttonClass(coarse), showHidden && "bg-accent-wash text-primary")}
|
|
134
|
+
>
|
|
135
|
+
{showHidden ? <Eye className="size-4" /> : <EyeOff className="size-4" />}
|
|
136
|
+
</button>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `WINDOW_CONTENT["explorer"]` — the adapter between the generic floating-window frame and
|
|
3
|
+
* the explorer body.
|
|
4
|
+
*
|
|
5
|
+
* It owns only two things the body should not care about: turning the window payload into
|
|
6
|
+
* a starting path, and dropping the window's slice when the window closes.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useEffect } from "react";
|
|
10
|
+
import type { WindowContentProps } from "@/components/floating-window/window-content-registry";
|
|
11
|
+
import { ExplorerBody } from "./explorer-body";
|
|
12
|
+
import { useExplorerStore } from "./explorer-store";
|
|
13
|
+
import { cachedHomedir, useHostInfo } from "./use-host-info";
|
|
14
|
+
|
|
15
|
+
export default function ExplorerWindowContent({ id, payload }: WindowContentProps) {
|
|
16
|
+
const { host, error } = useHostInfo();
|
|
17
|
+
const discard = useExplorerStore((s) => s.discard);
|
|
18
|
+
|
|
19
|
+
// A window's state is per-window; leaving it behind would leak one slice per open.
|
|
20
|
+
useEffect(() => () => discard(id), [id, discard]);
|
|
21
|
+
|
|
22
|
+
const requested = typeof payload?.path === "string" ? payload.path : "";
|
|
23
|
+
// Without a path (or before host info lands) there is nothing safe to browse: "/" does
|
|
24
|
+
// not exist on Windows, so wait for the home directory rather than showing a bogus error.
|
|
25
|
+
const initialPath = requested || cachedHomedir() || host?.homedir || "";
|
|
26
|
+
|
|
27
|
+
if (!initialPath) {
|
|
28
|
+
return (
|
|
29
|
+
<div className="flex h-full items-center justify-center p-4 text-xs text-text-2">
|
|
30
|
+
{error ? `Could not reach the host: ${error}` : "Loading…"}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return <ExplorerBody windowId={id} initialPath={initialPath} />;
|
|
36
|
+
}
|
|
@@ -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
|
+
}
|