@hienlh/ppm 0.17.57 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-run.sh +5 -0
- package/CHANGELOG.md +16 -0
- package/CLAUDE.md +4 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +16 -2
- package/bun.lock +3 -0
- package/dist/web/assets/{ai-resource-editor-CXWQcx3_.js → ai-resource-editor-BLYgg033.js} +1 -1
- package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-jfMqUuRd.js} +1 -1
- package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-wytn9PMm.js} +1 -1
- package/dist/web/assets/{api-client-COQtP-dB.js → api-client-B9OFv3nY.js} +1 -1
- package/dist/web/assets/api-client-CKo60P6G.js +1 -0
- package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-DGhshDRg.js} +1 -1
- package/dist/web/assets/api-settings-fdkhqLSF.js +1 -0
- package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-okiyhhp9.js} +1 -1
- package/dist/web/assets/{apl-DSANFLx0.js → apl-i-ahKx6O.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
- package/dist/web/assets/{astro-CXIotiNS.js → astro-DVBCJfG6.js} +1 -1
- package/dist/web/assets/{audio-preview-D7jNc8mi.js → audio-preview-y8XpCNrh.js} +1 -1
- package/dist/web/assets/{blade-BvrP2gPd.js → blade-BHlL8SqU.js} +1 -1
- package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-D-UOfR1F.js} +1 -1
- package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-Bn6rucli.js} +1 -1
- package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
- package/dist/web/assets/chat-tab-BcR-3_6R.js +13 -0
- package/dist/web/assets/clock-B1CPBp2g.js +1 -0
- package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Byn9FYKu.js} +1 -1
- package/dist/web/assets/code-editor-CJ8g7dqm.js +10 -0
- package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-DjBl4gm-.js} +1 -1
- package/dist/web/assets/{conflict-editor-CpGcSopK.js → conflict-editor-BP7JFZzG.js} +3 -3
- package/dist/web/assets/context-menu-BSE5yuMG.js +1 -0
- package/dist/web/assets/copy-Su9m2utd.js +1 -0
- package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CKzic-3E.js} +1 -1
- package/dist/web/assets/{crystal-DMjCL--0.js → crystal-Bkg-Ly_g.js} +1 -1
- package/dist/web/assets/csv-preview-xAwUDNat.js +2 -0
- package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-DMTCHIG9.js} +1 -1
- package/dist/web/assets/{database-viewer-DTMmic31.js → database-viewer-BVLWYPa4.js} +1 -1
- package/dist/web/assets/dialog-D__LRQTn.js +1 -0
- package/dist/web/assets/diff-viewer-BheSOHfl.js +4 -0
- package/dist/web/assets/{docx-preview-CR2ZtvNP.js → docx-preview-CGlMqltP.js} +1 -1
- package/dist/web/assets/{edge-CTom35R_.js → edge-DVbZnd3Y.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-ClJAJwlL.js} +1 -1
- package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-Dwi8Wu7W.js} +1 -1
- package/dist/web/assets/{elm-CpD28xsW.js → elm-DtIv-HXJ.js} +1 -1
- package/dist/web/assets/{erb-DHX2IIEs.js → erb-D3HEDz1x.js} +1 -1
- package/dist/web/assets/{erlang-B_etLcPG.js → erlang-2MjTCzGz.js} +1 -1
- package/dist/web/assets/{esm-PmlN1RTs.js → esm-BfqDE09P.js} +1 -1
- package/dist/web/assets/explorer-body-DE5339g9.js +2 -0
- package/dist/web/assets/explorer-window-content-BrTHAoXM.js +1 -0
- package/dist/web/assets/{extension-webview-BTCFWY2A.js → extension-webview-DRq1tWC1.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
- package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-Bxbn_gbr.js} +1 -1
- package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-sFMY4D2Q.js} +1 -1
- package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-BzB9Lio5.js} +1 -1
- package/dist/web/assets/git-log-panel-pjyOjchU.js +1 -0
- package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-CKSVd848.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
- package/dist/web/assets/{glide-data-grid-LinTYl0_.js → glide-data-grid-LX3N4SsJ.js} +9 -9
- package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BY8aExAJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-Bak54dBX.js} +1 -1
- package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-nR2eaJQe.js} +1 -1
- package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-CGPPFAU6.js} +1 -1
- package/dist/web/assets/group-chat-tab-CVQfptkT.js +1 -0
- package/dist/web/assets/{hack-sxywdIAQ.js → hack-CXIMX-Ol.js} +1 -1
- package/dist/web/assets/{haml-Dts9JlPU.js → haml-tPY-9Pfm.js} +1 -1
- package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BuW1LT7T.js} +1 -1
- package/dist/web/assets/{html-2p0RvXi4.js → html-bgVcwZ-t.js} +1 -1
- package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-D6-IxTgn.js} +1 -1
- package/dist/web/assets/{http-CHd2gYPf.js → http-CJ1zyB-h.js} +1 -1
- package/dist/web/assets/{hurl-boMPgDm9.js → hurl-CmoT7l3T.js} +1 -1
- package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CztOsL8o.js} +1 -1
- package/dist/web/assets/{image-preview-0SGb7_tH.js → image-preview-BDySmiim.js} +1 -1
- package/dist/web/assets/index-XoDY9bc0.js +52 -0
- package/dist/web/assets/index-YCTraslE.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
- package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-B1pq3jJq.js} +1 -1
- package/dist/web/assets/{jison-C2vJYjbJ.js → jison-D8B5pCwA.js} +1 -1
- package/dist/web/assets/{julia-DJ8MrQPA.js → julia-D0K3bwpU.js} +1 -1
- package/dist/web/assets/{just-BvQHq7VE.js → just-B9Jlddu-.js} +1 -1
- package/dist/web/assets/keybindings-store-BE1jVUZW.js +1 -0
- package/dist/web/assets/{latex-ChNeshUC.js → latex-BPJSSE6j.js} +1 -1
- package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-lv10fo4U.js} +1 -1
- package/dist/web/assets/{lua-D8NNsLbC.js → lua-PNuaIBEF.js} +1 -1
- package/dist/web/assets/{markdown-renderer-DSxSupDq.js → markdown-renderer-B8Kz5rvP.js} +3 -3
- package/dist/web/assets/markdown-renderer-Btync7yg.js +1 -0
- package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Du3frD6X.js} +1 -1
- package/dist/web/assets/{mdc-omkodQB5.js → mdc-BeDVcFRl.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-CzRXE6mY.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-CDU8xriw.js +1 -0
- package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-DkcXP71s.js} +1 -1
- package/dist/web/assets/{nginx-ByvWruGD.js → nginx-C5n8N_bt.js} +1 -1
- package/dist/web/assets/{nim-BrMojX4O.js → nim-CIhyfs-A.js} +1 -1
- package/dist/web/assets/notification-store-Bi7VHAy4.js +1 -0
- package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-B8P26qGC.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
- package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-gbNJvZB_.js} +1 -1
- package/dist/web/assets/{pdf-preview-DEn8amrO.js → pdf-preview-CtZWvujY.js} +1 -1
- package/dist/web/assets/{perl-Bd_0l-b4.js → perl-Cf31sZYE.js} +1 -1
- package/dist/web/assets/{php-D-DLTnWJ.js → php-Dzl_MDIW.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
- package/dist/web/assets/{postgres-viewer-CjrM15gz.js → postgres-viewer-fqMUi0xS.js} +3 -3
- package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-D1D1MN03.js} +1 -1
- package/dist/web/assets/{pug-D2aO-l2T.js → pug-CxSYHrB3.js} +1 -1
- package/dist/web/assets/{qml-CyQVzNXS.js → qml-DlEjGhwN.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
- package/dist/web/assets/{razor-8tvJXsO3.js → razor-KVt5uW14.js} +1 -1
- package/dist/web/assets/{rst-C4AZyQQl.js → rst-D5BwNTnw.js} +1 -1
- package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-D5fejQwd.js} +1 -1
- package/dist/web/assets/{sas-BtnF4zaV.js → sas-Bj7B1CB2.js} +1 -1
- package/dist/web/assets/{scss-GAyp5euH.js → scss-BB0CUoCL.js} +1 -1
- package/dist/web/assets/settings-store-Bm7uL8VA.js +2 -0
- package/dist/web/assets/settings-tab-DxtBG0DW.js +1 -0
- package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-BdjEZUMc.js} +1 -1
- package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-Bp1OA1rR.js} +1 -1
- package/dist/web/assets/{soy-DrLNhqfp.js → soy-DlYz3wsC.js} +1 -1
- package/dist/web/assets/sparkles-BvJ1EGWd.js +1 -0
- package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CdfII5Jd.js} +1 -1
- package/dist/web/assets/{sql-query-editor-CT5M9PKQ.js → sql-query-editor-D2UxRvG7.js} +1 -1
- package/dist/web/assets/sqlite-viewer-CTBwzT-r.js +1 -0
- package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BvUA6GUW.js} +1 -1
- package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql--DLNbTyI.js} +1 -1
- package/dist/web/assets/{svelte-23si1KV5.js → svelte-CBfSsG4n.js} +1 -1
- package/dist/web/assets/{system-monitor-tab-De3oz9zD.js → system-monitor-tab-Bscf6tzK.js} +1 -1
- package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-DnNnaAcE.js} +1 -1
- package/dist/web/assets/{templ-CPxlP4vF.js → templ-Dv8zpKZW.js} +1 -1
- package/dist/web/assets/{terminal-tab-CciNqtMc.js → terminal-tab-CTKU5gaF.js} +3 -3
- package/dist/web/assets/{tex-ugJpgYPc.js → tex-CsrOiqh1.js} +1 -1
- package/dist/web/assets/tool-cards-xstpayaW.js +7 -0
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
- package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-3T98k4EJ.js} +1 -1
- package/dist/web/assets/{twig-B7VUm2Ca.js → twig-u6QZ45FH.js} +1 -1
- package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-BLRmhlZI.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-k-17kQqf.js → use-monaco-theme-DxYstgYa.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
- package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CXJtBULr.js} +1 -1
- package/dist/web/assets/{video-preview-D-S_YnG6.js → video-preview-cfaoRxG3.js} +1 -1
- package/dist/web/assets/{vue-m75pWowk.js → vue-CxIsxtwn.js} +1 -1
- package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-BGL5qnuH.js} +1 -1
- package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DHPh_gMr.js} +1 -1
- package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-vM2m7J.js} +1 -1
- package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-DvTXNwuO.js} +1 -1
- package/dist/web/index.html +26 -23
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +10 -1
- package/docs/design-guidelines.md +31 -0
- package/docs/project-roadmap.md +10 -0
- package/docs/system-architecture.md +59 -0
- package/package.json +2 -1
- package/src/server/index.ts +8 -0
- package/src/server/routes/files.ts +2 -2
- package/src/server/routes/fs-browse.ts +99 -74
- package/src/server/routes/fs-ops.ts +118 -0
- package/src/server/routes/fs-sqlite.ts +129 -0
- package/src/server/routes/host-info.ts +17 -0
- package/src/server/routes/settings.ts +2 -0
- package/src/services/download-token.service.ts +21 -5
- package/src/services/file.service.ts +8 -12
- package/src/services/fs-browse.service.ts +104 -161
- package/src/services/fs-ops/fs-content-disposition.ts +17 -0
- package/src/services/fs-ops/fs-core-ops.ts +149 -0
- package/src/services/fs-ops/fs-error-response.ts +24 -0
- package/src/services/fs-ops/fs-git-init.service.ts +23 -0
- package/src/services/fs-ops/fs-hidden-names.ts +17 -0
- package/src/services/fs-ops/fs-list-files.service.ts +68 -0
- package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
- package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
- package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
- package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
- package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
- package/src/services/fs-ops/fs-real-path.ts +44 -0
- package/src/services/fs-ops/sql-row-reader.ts +26 -0
- package/src/services/fs-ops/sql-statement-guard.ts +63 -0
- package/src/services/fs-path-guard.service.ts +196 -0
- package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
- package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
- package/src/services/host-info/concurrency-limit.ts +20 -0
- package/src/services/host-info/host-drives.ts +190 -0
- package/src/services/host-info/host-info.service.ts +171 -0
- package/src/services/host-info/host-known-folders.ts +180 -0
- package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
- package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
- package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
- package/src/services/host-info/spawn-runner.ts +41 -0
- package/src/services/sqlite-types.ts +26 -0
- package/src/services/sqlite.service.ts +38 -29
- package/src/services/subagent-transcript-merger.ts +1 -1
- package/src/types/system.ts +50 -0
- package/src/web/app.tsx +19 -3
- package/src/web/components/editor/code-editor.tsx +3 -9
- package/src/web/components/explorer/file-tree.tsx +103 -21
- package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
- package/src/web/components/explorer/tree-node.tsx +34 -93
- package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
- package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
- package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
- package/src/web/components/floating-window/floating-window.tsx +167 -0
- package/src/web/components/floating-window/use-window-drag.ts +28 -0
- package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
- package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
- package/src/web/components/floating-window/use-window-resize.ts +30 -0
- package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
- package/src/web/components/floating-window/window-content-registry.ts +51 -0
- package/src/web/components/floating-window/window-geometry.ts +121 -0
- package/src/web/components/floating-window/window-layer.tsx +92 -0
- package/src/web/components/floating-window/window-persistence.ts +104 -0
- package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
- package/src/web/components/floating-window/window-store-types.ts +20 -0
- package/src/web/components/floating-window/window-store.ts +192 -0
- package/src/web/components/layout/command-palette.tsx +10 -0
- package/src/web/components/layout/mobile-drawer.tsx +11 -2
- package/src/web/components/layout/nav-section-rail.tsx +7 -1
- package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
- package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
- package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
- package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
- package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
- package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
- package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
- package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
- package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
- package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
- package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
- package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
- package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
- package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
- package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
- package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
- package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
- package/src/web/components/os-explorer/explorer-body.tsx +153 -0
- package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
- package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
- package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
- package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
- package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
- package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
- package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
- package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
- package/src/web/components/os-explorer/explorer-store.ts +179 -0
- package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
- package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
- package/src/web/components/os-explorer/format-file-meta.ts +74 -0
- package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
- package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
- package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
- package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
- package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
- package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
- package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
- package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
- package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
- package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
- package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
- package/src/web/components/os-explorer/open-explorer.ts +35 -0
- package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
- package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
- package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
- package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
- package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
- package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
- package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
- package/src/web/components/os-explorer/skins/skins.css +25 -0
- package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
- package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
- package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
- package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
- package/src/web/components/os-explorer/sort-options.ts +29 -0
- package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
- package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
- package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
- package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
- package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
- package/src/web/components/os-explorer/use-host-info.ts +85 -0
- package/src/web/components/os-explorer/views/column-row.tsx +126 -0
- package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
- package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
- package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
- package/src/web/components/os-explorer/views/column-view.tsx +204 -0
- package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
- package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
- package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
- package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
- package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
- package/src/web/components/os-explorer/views/list-row.tsx +162 -0
- package/src/web/components/os-explorer/views/list-view.tsx +177 -0
- package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
- package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
- package/src/web/components/settings/settings-tab.tsx +29 -2
- package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
- package/src/web/components/sqlite/use-sqlite.ts +7 -2
- package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
- package/src/web/components/ui/file-browser-picker.tsx +1 -18
- package/src/web/lib/file-download.ts +6 -4
- package/src/web/lib/fs-api.ts +185 -0
- package/src/web/stores/file-store.ts +38 -1
- package/src/web/stores/settings-store.ts +14 -0
- package/src/web/styles/globals.css +1 -0
- package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
- package/dist/web/assets/api-settings-De--HNHp.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
- package/dist/web/assets/chat-tab-C5U7a_rP.js +0 -13
- package/dist/web/assets/code-editor-CqlzLQ7T.js +0 -10
- package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
- package/dist/web/assets/csv-preview-DD94T9ff.js +0 -2
- package/dist/web/assets/dialog-HHdNInEH.js +0 -1
- package/dist/web/assets/diff-viewer-DyQLYP7J.js +0 -4
- package/dist/web/assets/git-log-panel-CrUgsLY5.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
- package/dist/web/assets/group-chat-tab-BqNJW7y2.js +0 -1
- package/dist/web/assets/index-CVJmlKhp.css +0 -2
- package/dist/web/assets/index-I2ik0ZF7.js +0 -50
- package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
- package/dist/web/assets/keybindings-store-C46tNxJE.js +0 -1
- package/dist/web/assets/markdown-renderer-xzyhJklS.js +0 -1
- package/dist/web/assets/notification-store-Dek7ClI2.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
- package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
- package/dist/web/assets/settings-tab-DIuR1VIc.js +0 -1
- package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
- package/dist/web/assets/sqlite-viewer-CEObRQ7n.js +0 -1
- package/dist/web/assets/tool-cards-Bdx04yYD.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
- /package/dist/web/assets/{abap-BM5PMK0-.js → abap-C1K0-wzc.js} +0 -0
- /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-CJhQVjLA.js} +0 -0
- /package/dist/web/assets/{ada-DzO1tMqV.js → ada-Bt8r9fME.js} +0 -0
- /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-DF1I8qi3.js} +0 -0
- /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CnlleE6P.js} +0 -0
- /package/dist/web/assets/{apex-3O2170Ju.js → apex-j0C6Xmk6.js} +0 -0
- /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-DO7AnO8U.js} +0 -0
- /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
- /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-i83AydZj.js} +0 -0
- /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DbBA6-sF.js} +0 -0
- /package/dist/web/assets/{asm-p1-PUkh3.js → asm-B5S47Rgr.js} +0 -0
- /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-De_IaSKw.js} +0 -0
- /package/dist/web/assets/{awk-Cj93pRL8.js → awk-CfyLHXqS.js} +0 -0
- /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-DTLdZ1Si.js} +0 -0
- /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-DTOfkzhu.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-DsAECCMv.js} +0 -0
- /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-CumGp9A7.js} +0 -0
- /package/dist/web/assets/{bat-Cg8hlSct.js → bat-Ca2SBbYN.js} +0 -0
- /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-CqyXBwwF.js} +0 -0
- /package/dist/web/assets/{berry-BqVQYZZr.js → berry-yl-kJr_H.js} +0 -0
- /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DoFFszjV.js} +0 -0
- /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-CWPi4nfK.js} +0 -0
- /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-DXew-ut0.js} +0 -0
- /package/dist/web/assets/{bot-E9mcVZnr.js → bot-BHXjpQ7x.js} +0 -0
- /package/dist/web/assets/{c-BQFvJ6Ux.js → c-clbySDvd.js} +0 -0
- /package/dist/web/assets/{c3-Da53KeG6.js → c3-B65jvSM1.js} +0 -0
- /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-Cm8tQTHN.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-DLeGslh_.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-CR5GeHws.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
- /package/dist/web/assets/{chevron-right-DeUue2aK.js → chevron-right-CtcHmZg9.js} +0 -0
- /package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-EVgisosn.js} +0 -0
- /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-DPulbvKH.js} +0 -0
- /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-DFuWS20u.js} +0 -0
- /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DejCqcu4.js} +0 -0
- /package/dist/web/assets/{code-TekEnDFM.js → code-Dupqw5Fr.js} +0 -0
- /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BwAyqWIH.js} +0 -0
- /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-DgSwFjqI.js} +0 -0
- /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-CxIVJCtf.js} +0 -0
- /package/dist/web/assets/{coq-D2bX6-8g.js → coq-DLSjSnmf.js} +0 -0
- /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-C1HHzXBp.js} +0 -0
- /package/dist/web/assets/{css-CIjciIIk.js → css-VXsMY--y.js} +0 -0
- /package/dist/web/assets/{csv-DW48Z5d-.js → csv-BEbcJdHz.js} +0 -0
- /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
- /package/dist/web/assets/{cue-Byh1xHko.js → cue-BkDnFqUD.js} +0 -0
- /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-1cMqojSQ.js} +0 -0
- /package/dist/web/assets/{d-CxaScvxf.js → d-BnYjRXtH.js} +0 -0
- /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-XfbaH7kZ.js} +0 -0
- /package/dist/web/assets/{dart-BmpakMe-.js → dart-Ca7s-8hK.js} +0 -0
- /package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-DP-ZexEN.js} +0 -0
- /package/dist/web/assets/{database-COYAMZaw.js → database-Dj0DxgtX.js} +0 -0
- /package/dist/web/assets/{dax-B1aRUMXT.js → dax-CmuOw-mY.js} +0 -0
- /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-C2gJ6Ovb.js} +0 -0
- /package/dist/web/assets/{diff-CupGmT1a.js → diff-BCEo1Ej7.js} +0 -0
- /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CipvJy9w.js} +0 -0
- /package/dist/web/assets/{dist-m_oJqIDk.js → dist-D__U4ZJz.js} +0 -0
- /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DN2jlyAh.js} +0 -0
- /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-NExMY2kS.js} +0 -0
- /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-B9lbE01y.js} +0 -0
- /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-CMBBfKF_.js} +0 -0
- /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DEyDwFsd.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-D_qP2zS6.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-DbHa2U-p.js} +0 -0
- /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-BISkJchP.js} +0 -0
- /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-6B22SfJR.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-D-ZORzYB.js} +0 -0
- /package/dist/web/assets/{fish-Vr62C2vP.js → fish-CubIIvJD.js} +0 -0
- /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-DF55zQ09.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DvOu8jAt.js} +0 -0
- /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-DCPxdLpq.js} +0 -0
- /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-CTZ381FO.js} +0 -0
- /package/dist/web/assets/{genie-B4hkIXNU.js → genie-C_QDtvgU.js} +0 -0
- /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-BcvK42tF.js} +0 -0
- /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-C9afs_4w.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-CKGVrl3Z.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-DW78QukY.js} +0 -0
- /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-BnP2dznS.js} +0 -0
- /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-C2fPZzPG.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
- /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpeKwEIO.js} +0 -0
- /package/dist/web/assets/{gn-DuO2D2eW.js → gn-nUb_Mit0.js} +0 -0
- /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-D7I0v20p.js} +0 -0
- /package/dist/web/assets/{go-BL16cMJO.js → go-D7Qqska7.js} +0 -0
- /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-DS-pne5-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
- /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-uQcywkDH.js} +0 -0
- /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-c6bI4_GT.js} +0 -0
- /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CQXTiEXq.js} +0 -0
- /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-ChETf2bz.js} +0 -0
- /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-BMC4JGoE.js} +0 -0
- /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BusFlhLW.js} +0 -0
- /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-2oD0Mz7W.js} +0 -0
- /package/dist/web/assets/{houston-Ddm8AgpV.js → houston-DdbbjqFg.js} +0 -0
- /package/dist/web/assets/{hy-DzZa6alE.js → hy-D_SOuqw9.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
- /package/dist/web/assets/{imba-aiXalPnK.js → imba-BxLOHO5I.js} +0 -0
- /package/dist/web/assets/{ini-CAvrVa5R.js → ini-ox-CFcg0.js} +0 -0
- /package/dist/web/assets/{java-PRMSQp24.js → java-Bzo8yIal.js} +0 -0
- /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-DHuz9VXn.js} +0 -0
- /package/dist/web/assets/{json-uWUKqOxq.js → json-0_y80FD5.js} +0 -0
- /package/dist/web/assets/{json5-R_LXJXL0.js → json5-CEQ50pGM.js} +0 -0
- /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-CfrLEAKA.js} +0 -0
- /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-fKbrANC2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-DKbkPaQ2.js} +0 -0
- /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-Dxxx1Hqu.js} +0 -0
- /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-DlJL-Oj3.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-BHltayiE.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-D-KXPjw6.js} +0 -0
- /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DvtoJdFo.js} +0 -0
- /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-CTS4MLcx.js} +0 -0
- /package/dist/web/assets/{kusto-Ba87977j.js → kusto-HtSE_sKm.js} +0 -0
- /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-DaXZG9ge.js} +0 -0
- /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-BYtGUfWx.js} +0 -0
- /package/dist/web/assets/{less-Dfn9YC_2.js → less-BoxmU_Mk.js} +0 -0
- /package/dist/web/assets/{lib-DTaN9riD.js → lib-Bpbn00xM.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
- /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-Bt5CyUuo.js} +0 -0
- /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CSdk9q9V.js} +0 -0
- /package/dist/web/assets/{log-QW3dqr1D.js → log-B-kp_3CL.js} +0 -0
- /package/dist/web/assets/{logo-DUinJheI.js → logo-DdIsXFDO.js} +0 -0
- /package/dist/web/assets/{luau-B1vcBzl8.js → luau-DUBpLlzQ.js} +0 -0
- /package/dist/web/assets/{make-CqNL10gS.js → make-B3JVjoW_.js} +0 -0
- /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Seon0vHs.js} +0 -0
- /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-Nq-ChX1P.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-CPV4ayXJ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-0kyjYqEd.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-DGpAp5co.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-B0AUn0NG.js} +0 -0
- /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-C2waytxs.js} +0 -0
- /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-DAis6R6I.js} +0 -0
- /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-_rnT2YGD.js} +0 -0
- /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-BSSjchUB.js} +0 -0
- /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-DomAFQsa.js} +0 -0
- /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-bBgtvPyG.js} +0 -0
- /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-Cp9BayW0.js} +0 -0
- /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-CL_9gbmn.js} +0 -0
- /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-BS_fcr-P.js} +0 -0
- /package/dist/web/assets/{move-BvhjG2ic.js → move-C79AH45U.js} +0 -0
- /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-CJ5s3BDf.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
- /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-Jkf9zFQK.js} +0 -0
- /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-DRPeaG5J.js} +0 -0
- /package/dist/web/assets/{nix-B9EAdptu.js → nix-D2Jet9DN.js} +0 -0
- /package/dist/web/assets/{nord-DxASd4qG.js → nord-CREVs22j.js} +0 -0
- /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-CAiDi_mR.js} +0 -0
- /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-CVkmJkC1.js} +0 -0
- /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DIKDO1Ly.js} +0 -0
- /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-R9qtGQhs.js} +0 -0
- /package/dist/web/assets/{odin-B8pz4gIL.js → odin-DTYNQqVr.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-BSLWUJn5.js} +0 -0
- /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DbkRXG3t.js} +0 -0
- /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-BlPcSnmT.js} +0 -0
- /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-B1Y46ZZB.js} +0 -0
- /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-BhJSJbFv.js} +0 -0
- /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BzxsCTpi.js} +0 -0
- /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DBba7dyP.js} +0 -0
- /package/dist/web/assets/{plus-DcuJuDC3.js → plus-Dm36mgLh.js} +0 -0
- /package/dist/web/assets/{po-Dz4OaDNi.js → po-BvgLlnNo.js} +0 -0
- /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DUaI_1JP.js} +0 -0
- /package/dist/web/assets/{polar-Che1XC2Z.js → polar-C7uILwJn.js} +0 -0
- /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-BgJlpFdw.js} +0 -0
- /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-Dn1MIpZu.js} +0 -0
- /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-Bo-DYzOS.js} +0 -0
- /package/dist/web/assets/{prisma-DCmjceML.js → prisma-CTLs5IdT.js} +0 -0
- /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-QRGun3eG.js} +0 -0
- /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-B7PI7apn.js} +0 -0
- /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CBq0tLrP.js} +0 -0
- /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-DFewOQb2.js} +0 -0
- /package/dist/web/assets/{python-Ctyr4faP.js → python-iWuxKXvT.js} +0 -0
- /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-cXiW3AsF.js} +0 -0
- /package/dist/web/assets/{qss-LbRHzb21.js → qss-DASwAbBL.js} +0 -0
- /package/dist/web/assets/{r-Dvfjp0wc.js → r-TU2eaIPw.js} +0 -0
- /package/dist/web/assets/{racket-DI8v8nzD.js → racket-SkRKqrLY.js} +0 -0
- /package/dist/web/assets/{raku-CX1q9GW0.js → raku-D31lthkZ.js} +0 -0
- /package/dist/web/assets/{react-8hrD3-kv.js → react-DjdYfcEe.js} +0 -0
- /package/dist/web/assets/{red-CSz3QEOw.js → red-CTcQZevb.js} +0 -0
- /package/dist/web/assets/{reg-FMjhUQE-.js → reg-D8YAsTGf.js} +0 -0
- /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CD2uvAWx.js} +0 -0
- /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-CAaI-1fg.js} +0 -0
- /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
- /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-DR-hJVIj.js} +0 -0
- /package/dist/web/assets/{ron-D0t4kvJG.js → ron-CuUCJ0n4.js} +0 -0
- /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-aFHxW48i.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-wqyh0an4.js} +0 -0
- /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-CI1ywimO.js} +0 -0
- /package/dist/web/assets/{rust-2jBzrC0l.js → rust-p7Imk4G_.js} +0 -0
- /package/dist/web/assets/{sass-DD75Izu_.js → sass-C8JfaqCV.js} +0 -0
- /package/dist/web/assets/{save-BM4p9_9n.js → save-ignW_jk2.js} +0 -0
- /package/dist/web/assets/{scala-DpBKROIu.js → scala-DNxCW1By.js} +0 -0
- /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-BYuUPJoL.js} +0 -0
- /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-DbPeJPUr.js} +0 -0
- /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DMiHlR3q.js} +0 -0
- /package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-BSA9P3nI.js} +0 -0
- /package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-Cb9J9x_n.js} +0 -0
- /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-C4xGcQXI.js} +0 -0
- /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-BzgRt_wm.js} +0 -0
- /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-LDeL9PYH.js} +0 -0
- /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-CXE7a5j_.js} +0 -0
- /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-DgUhY74x.js} +0 -0
- /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-DD9C-H-9.js} +0 -0
- /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-CAbDB4D4.js} +0 -0
- /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-kQREPfIc.js} +0 -0
- /package/dist/web/assets/{sql-CoqObhgo.js → sql-CBzeNAD8.js} +0 -0
- /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-L1euxrsM.js} +0 -0
- /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-DvBnYniP.js} +0 -0
- /package/dist/web/assets/{swift-DtMMgvRI.js → swift-DF5NDQp6.js} +0 -0
- /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-DbBhB_Hq.js} +0 -0
- /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-D8s2ZW_k.js} +0 -0
- /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-a5-XZJsG.js} +0 -0
- /package/dist/web/assets/{table-Di8pv2Y_.js → table-4P8kS7sg.js} +0 -0
- /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-BLa0URTW.js} +0 -0
- /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-DIoV4XcC.js} +0 -0
- /package/dist/web/assets/{tcl-caTR_frn.js → tcl-ztYAdXfQ.js} +0 -0
- /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-DvpCCiOZ.js} +0 -0
- /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BkHlYUv0.js} +0 -0
- /package/dist/web/assets/{toml-B7D2mIDU.js → toml-acIrxos6.js} +0 -0
- /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-BJB5EVCT.js} +0 -0
- /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-B1MmXGT3.js} +0 -0
- /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-DFceTiqd.js} +0 -0
- /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-Dz9wU5Y1.js} +0 -0
- /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CPuYZijj.js} +0 -0
- /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CfsCIYv8.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-K1a38BwI.js} +0 -0
- /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
- /package/dist/web/assets/{v-D4997FGs.js → v-CGo0jcXA.js} +0 -0
- /package/dist/web/assets/{vala-AFg9DmKE.js → vala-AsSR0hrb.js} +0 -0
- /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-C7tvbXv1.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
- /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog--a33ND-p.js} +0 -0
- /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-B_Z0h4pn.js} +0 -0
- /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CWL5dZPN.js} +0 -0
- /package/dist/web/assets/{viml-D6EKG66q.js → viml-BglmT14Y.js} +0 -0
- /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-RKFsSqQI.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-D7zDWNmc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-MAp87W5b.js} +0 -0
- /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-CgCBYg1G.js} +0 -0
- /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-BOG7ZeTT.js} +0 -0
- /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
- /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-DpShPKXZ.js} +0 -0
- /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-BakcCblK.js} +0 -0
- /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-NroRWVAO.js} +0 -0
- /package/dist/web/assets/{wit-DIrx_eCI.js → wit-B5CM2pg6.js} +0 -0
- /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-7MTFBJpR.js} +0 -0
- /package/dist/web/assets/{x-BsFLg-T8.js → x-BtzqXv1z.js} +0 -0
- /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-Bwr01G2K.js} +0 -0
- /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-Dj8GATvi.js} +0 -0
- /package/dist/web/assets/{zig-CrzwMpQE.js → zig-LAE-fbS-.js} +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One floating window: geometry, gestures, keyboard, and a chrome slot.
|
|
3
|
+
*
|
|
4
|
+
* Geometry is written straight to the element instead of through React state — a drag emits
|
|
5
|
+
* a pointermove per frame and re-rendering the window (and its content) on each one is what
|
|
6
|
+
* makes a window manager feel heavy. The store is written only when a gesture ends, which is
|
|
7
|
+
* also the moment the rect is rounded to whole pixels.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { useCallback, useLayoutEffect, useRef, useState, type ReactNode } from "react";
|
|
11
|
+
import { cn } from "@/lib/utils";
|
|
12
|
+
import { DefaultWindowChrome } from "./default-window-chrome";
|
|
13
|
+
import { TITLEBAR_HEIGHT, type WindowChrome } from "./window-chrome-contract";
|
|
14
|
+
import { windowZIndex, type Bounds, type Rect } from "./window-geometry";
|
|
15
|
+
import { useWindowStore, type WindowRuntimeState } from "./window-store";
|
|
16
|
+
import { useWindowDrag } from "./use-window-drag";
|
|
17
|
+
import { useWindowKeyboard } from "./use-window-keyboard";
|
|
18
|
+
import { useWindowResize } from "./use-window-resize";
|
|
19
|
+
import { WindowResizeHandles } from "./window-resize-handles";
|
|
20
|
+
|
|
21
|
+
export interface FloatingWindowProps {
|
|
22
|
+
win: WindowRuntimeState;
|
|
23
|
+
/** Size of the layer container; a maximized window fills exactly this. */
|
|
24
|
+
bounds: Bounds;
|
|
25
|
+
title: string;
|
|
26
|
+
focused: boolean;
|
|
27
|
+
/** Reads the layer's CSS scale so pointer deltas can be corrected. */
|
|
28
|
+
getScale: () => number;
|
|
29
|
+
/** Told when a gesture starts/ends so the layer can raise its capture overlay. */
|
|
30
|
+
onGestureActive: (active: boolean) => void;
|
|
31
|
+
/** Skin component for the titlebar; owns its own hooks and state. */
|
|
32
|
+
chrome?: WindowChrome;
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function applyRect(el: HTMLElement | null, rect: Rect, collapsed: boolean): void {
|
|
37
|
+
if (!el) return;
|
|
38
|
+
el.style.transform = `translate3d(${Math.round(rect.x)}px, ${Math.round(rect.y)}px, 0)`;
|
|
39
|
+
el.style.width = `${Math.round(rect.w)}px`;
|
|
40
|
+
el.style.height = collapsed ? `${TITLEBAR_HEIGHT}px` : `${Math.round(rect.h)}px`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function FloatingWindow({
|
|
44
|
+
win,
|
|
45
|
+
bounds,
|
|
46
|
+
title,
|
|
47
|
+
focused,
|
|
48
|
+
getScale,
|
|
49
|
+
onGestureActive,
|
|
50
|
+
chrome,
|
|
51
|
+
children,
|
|
52
|
+
}: FloatingWindowProps) {
|
|
53
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
54
|
+
const [gesturing, setGesturing] = useState(false);
|
|
55
|
+
|
|
56
|
+
const maximized = win.state === "maximized";
|
|
57
|
+
const minimized = win.state === "minimized";
|
|
58
|
+
const rect: Rect = maximized ? { x: 0, y: 0, w: bounds.w, h: bounds.h } : win.rect;
|
|
59
|
+
|
|
60
|
+
// Latest committed geometry, read at the first frame of a gesture (never mid-gesture,
|
|
61
|
+
// where it would compound the cumulative movement).
|
|
62
|
+
const rectRef = useRef(rect);
|
|
63
|
+
rectRef.current = rect;
|
|
64
|
+
const boundsRef = useRef(bounds);
|
|
65
|
+
boundsRef.current = bounds;
|
|
66
|
+
|
|
67
|
+
useLayoutEffect(() => {
|
|
68
|
+
applyRect(rootRef.current, rectRef.current, minimized);
|
|
69
|
+
}, [rect.x, rect.y, rect.w, rect.h, minimized]);
|
|
70
|
+
|
|
71
|
+
const setActive = useCallback(
|
|
72
|
+
(active: boolean) => {
|
|
73
|
+
setGesturing(active);
|
|
74
|
+
onGestureActive(active);
|
|
75
|
+
},
|
|
76
|
+
[onGestureActive],
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const commitMove = useCallback((next: Rect, committed: boolean) => {
|
|
80
|
+
applyRect(rootRef.current, next, useWindowStore.getState().windows[win.id]?.state === "minimized");
|
|
81
|
+
if (committed) useWindowStore.getState().move(win.id, { x: next.x, y: next.y });
|
|
82
|
+
}, [win.id]);
|
|
83
|
+
|
|
84
|
+
const commitResize = useCallback((next: Rect, committed: boolean) => {
|
|
85
|
+
applyRect(rootRef.current, next, false);
|
|
86
|
+
if (committed) useWindowStore.getState().resize(win.id, next);
|
|
87
|
+
}, [win.id]);
|
|
88
|
+
|
|
89
|
+
const shared = {
|
|
90
|
+
getRect: () => rectRef.current,
|
|
91
|
+
getBounds: () => boundsRef.current,
|
|
92
|
+
getScale,
|
|
93
|
+
onGestureActive: setActive,
|
|
94
|
+
};
|
|
95
|
+
const bindDrag = useWindowDrag({ ...shared, onChange: commitMove, disabled: maximized });
|
|
96
|
+
const bindResize = useWindowResize({ ...shared, onChange: commitResize, disabled: maximized || minimized });
|
|
97
|
+
|
|
98
|
+
const close = useCallback(() => useWindowStore.getState().close(win.id), [win.id]);
|
|
99
|
+
const toggleMaximize = useCallback(() => {
|
|
100
|
+
useWindowStore.getState().setState(win.id, maximized ? "normal" : "maximized");
|
|
101
|
+
}, [win.id, maximized]);
|
|
102
|
+
const minimize = useCallback(() => {
|
|
103
|
+
useWindowStore.getState().setState(win.id, minimized ? "normal" : "minimized");
|
|
104
|
+
}, [win.id, minimized]);
|
|
105
|
+
|
|
106
|
+
const onKeyDown = useWindowKeyboard({
|
|
107
|
+
getRect: () => rectRef.current,
|
|
108
|
+
getBounds: () => boundsRef.current,
|
|
109
|
+
onMove: (next) => useWindowStore.getState().move(win.id, { x: next.x, y: next.y }),
|
|
110
|
+
onToggleMaximize: toggleMaximize,
|
|
111
|
+
onClose: close,
|
|
112
|
+
movable: !maximized,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const Chrome = chrome ?? DefaultWindowChrome;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div
|
|
119
|
+
ref={rootRef}
|
|
120
|
+
role="group"
|
|
121
|
+
aria-roledescription="window"
|
|
122
|
+
aria-label={title}
|
|
123
|
+
// Any pointer inside the window raises it, including clicks on its content.
|
|
124
|
+
onPointerDownCapture={() => useWindowStore.getState().focus(win.id)}
|
|
125
|
+
style={{ zIndex: windowZIndex(win.rank) }}
|
|
126
|
+
className={cn(
|
|
127
|
+
"absolute left-0 top-0 flex flex-col pointer-events-auto",
|
|
128
|
+
"rounded-[8px] border border-border bg-panel overflow-visible",
|
|
129
|
+
focused ? "shadow-2xl shadow-black/40" : "shadow-lg shadow-black/20",
|
|
130
|
+
// Transitions are for maximize/restore only; during a gesture they would lag the pointer.
|
|
131
|
+
gesturing ? "transition-none" : "transition-[transform,width,height] duration-150 motion-reduce:transition-none",
|
|
132
|
+
)}
|
|
133
|
+
>
|
|
134
|
+
<Chrome
|
|
135
|
+
id={win.id}
|
|
136
|
+
kind={win.kind}
|
|
137
|
+
title={title}
|
|
138
|
+
state={win.state}
|
|
139
|
+
focused={focused}
|
|
140
|
+
titlebarProps={{
|
|
141
|
+
...(bindDrag() as Record<string, unknown>),
|
|
142
|
+
onKeyDown,
|
|
143
|
+
tabIndex: 0,
|
|
144
|
+
role: "toolbar",
|
|
145
|
+
"aria-label": `${title} title bar`,
|
|
146
|
+
// Scoped here rather than on document: a global user-select hack leaks into
|
|
147
|
+
// unrelated panels and survives a gesture that ends outside the window.
|
|
148
|
+
style: { touchAction: "none", userSelect: "none" },
|
|
149
|
+
}}
|
|
150
|
+
onMinimize={minimize}
|
|
151
|
+
onToggleMaximize={toggleMaximize}
|
|
152
|
+
onClose={close}
|
|
153
|
+
/>
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
{/* Always mounted, even minimized — content owns per-window state (explorer history,
|
|
157
|
+
selection, filter) that a remount would wipe. Hidden via CSS, not unmounted. */}
|
|
158
|
+
<div className={cn("relative flex-1 min-h-0 overflow-hidden rounded-b-[8px] bg-panel", minimized && "hidden")}>
|
|
159
|
+
{children}
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
{!maximized && !minimized && (
|
|
163
|
+
<WindowResizeHandles bind={(handle) => bindResize(handle) as Record<string, unknown>} />
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Titlebar drag: moves a window, committing to the store only when the gesture ends. */
|
|
2
|
+
|
|
3
|
+
import { useDrag } from "@use-gesture/react";
|
|
4
|
+
import { clampRect, type Rect } from "./window-geometry";
|
|
5
|
+
import {
|
|
6
|
+
gestureDisplacement,
|
|
7
|
+
WINDOW_DRAG_CONFIG,
|
|
8
|
+
type WindowGestureContext,
|
|
9
|
+
} from "./use-window-gesture-context";
|
|
10
|
+
|
|
11
|
+
export function useWindowDrag(ctx: WindowGestureContext) {
|
|
12
|
+
return useDrag(
|
|
13
|
+
({ xy, initial, first, last, memo }) => {
|
|
14
|
+
// The rect is captured once per gesture: the displacement is cumulative from the
|
|
15
|
+
// start, so re-reading a rect that we ourselves are mutating would compound it.
|
|
16
|
+
const base: Rect = first ? { ...ctx.getRect() } : (memo as Rect);
|
|
17
|
+
if (first) ctx.onGestureActive(true);
|
|
18
|
+
|
|
19
|
+
const { dx, dy } = gestureDisplacement(xy, initial, ctx.getScale());
|
|
20
|
+
const next = clampRect({ ...base, x: base.x + dx, y: base.y + dy }, ctx.getBounds());
|
|
21
|
+
ctx.onChange(next, last);
|
|
22
|
+
|
|
23
|
+
if (last) ctx.onGestureActive(false);
|
|
24
|
+
return base;
|
|
25
|
+
},
|
|
26
|
+
{ ...WINDOW_DRAG_CONFIG, enabled: !ctx.disabled },
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Shared plumbing between the window drag and resize gestures. */
|
|
2
|
+
|
|
3
|
+
import type { Bounds, Rect } from "./window-geometry";
|
|
4
|
+
|
|
5
|
+
export interface WindowGestureContext {
|
|
6
|
+
/** Rect at the moment the gesture starts — read lazily so it is never stale. */
|
|
7
|
+
getRect: () => Rect;
|
|
8
|
+
getBounds: () => Bounds;
|
|
9
|
+
/**
|
|
10
|
+
* Layer scale when an ancestor applies a CSS transform/zoom. Pointer deltas are in
|
|
11
|
+
* screen pixels, layout coordinates are not, so deltas must be divided by it or the
|
|
12
|
+
* window drifts away from the cursor.
|
|
13
|
+
*/
|
|
14
|
+
getScale: () => number;
|
|
15
|
+
/** `committed` is true on the last frame of the gesture — the only time the store is written. */
|
|
16
|
+
onChange: (rect: Rect, committed: boolean) => void;
|
|
17
|
+
/** Raises the transparent capture overlay so iframes/editors cannot swallow the pointer. */
|
|
18
|
+
onGestureActive: (active: boolean) => void;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Gesture config shared by both hooks: keep the pointer captured, ignore taps. */
|
|
23
|
+
export const WINDOW_DRAG_CONFIG = {
|
|
24
|
+
filterTaps: true,
|
|
25
|
+
pointer: { capture: true },
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pointer displacement since the gesture started, corrected for layer scale.
|
|
30
|
+
*
|
|
31
|
+
* Derived from the raw pointer coordinates (`xy` minus `initial`) rather than the
|
|
32
|
+
* recogniser's `movement`: with a tap threshold in play, `movement` has those first pixels
|
|
33
|
+
* subtracted, which would leave the window trailing the pointer by the threshold for the
|
|
34
|
+
* rest of the gesture. Both values are cumulative from the gesture's start, so applying
|
|
35
|
+
* them to the rect captured at that moment can never drift the way per-frame deltas do.
|
|
36
|
+
*/
|
|
37
|
+
export function gestureDisplacement(
|
|
38
|
+
xy: readonly [number, number],
|
|
39
|
+
initial: readonly [number, number],
|
|
40
|
+
scale: number,
|
|
41
|
+
): { dx: number; dy: number } {
|
|
42
|
+
const s = scale > 0 ? scale : 1;
|
|
43
|
+
return { dx: (xy[0] - initial[0]) / s, dy: (xy[1] - initial[1]) / s };
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyboard operation for a focused titlebar — the accessible path for users who cannot drag.
|
|
3
|
+
*
|
|
4
|
+
* Arrows nudge (Shift = 1 px), Alt+Enter toggles maximize, Escape closes. There is
|
|
5
|
+
* deliberately no Ctrl/Cmd+W: Chromium reserves it for the browser tab outside PWA and
|
|
6
|
+
* fullscreen contexts and ignores preventDefault, so binding it would close the app instead.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useCallback } from "react";
|
|
10
|
+
import type { KeyboardEvent } from "react";
|
|
11
|
+
import { nudgeRect, type Bounds, type Rect } from "./window-geometry";
|
|
12
|
+
|
|
13
|
+
const NUDGE = 10;
|
|
14
|
+
const NUDGE_FINE = 1;
|
|
15
|
+
|
|
16
|
+
const ARROWS: Record<string, [number, number]> = {
|
|
17
|
+
ArrowLeft: [-1, 0],
|
|
18
|
+
ArrowRight: [1, 0],
|
|
19
|
+
ArrowUp: [0, -1],
|
|
20
|
+
ArrowDown: [0, 1],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export interface WindowKeyboardActions {
|
|
24
|
+
getRect: () => Rect;
|
|
25
|
+
getBounds: () => Bounds;
|
|
26
|
+
onMove: (rect: Rect) => void;
|
|
27
|
+
onToggleMaximize: () => void;
|
|
28
|
+
onClose: () => void;
|
|
29
|
+
/** Nudging a maximized window would fight the layout, so it is ignored. */
|
|
30
|
+
movable: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useWindowKeyboard(actions: WindowKeyboardActions) {
|
|
34
|
+
return useCallback(
|
|
35
|
+
(e: KeyboardEvent<HTMLElement>) => {
|
|
36
|
+
if (e.key === "Escape") {
|
|
37
|
+
e.stopPropagation();
|
|
38
|
+
actions.onClose();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (e.altKey && e.key === "Enter") {
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
actions.onToggleMaximize();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const dir = ARROWS[e.key];
|
|
47
|
+
if (!dir || !actions.movable) return;
|
|
48
|
+
// Arrows would otherwise scroll the panel behind the window.
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
const step = e.shiftKey ? NUDGE_FINE : NUDGE;
|
|
51
|
+
actions.onMove(nudgeRect(actions.getRect(), dir[0] * step, dir[1] * step, actions.getBounds()));
|
|
52
|
+
},
|
|
53
|
+
[actions],
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eight-direction resize. One `useDrag` instance is bound per handle via `bind(handle)`;
|
|
3
|
+
* the handle id arrives in `args`, so all eight edges share a single recogniser.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useDrag } from "@use-gesture/react";
|
|
7
|
+
import { applyResize, clampRect, type Rect, type ResizeHandle } from "./window-geometry";
|
|
8
|
+
import {
|
|
9
|
+
gestureDisplacement,
|
|
10
|
+
WINDOW_DRAG_CONFIG,
|
|
11
|
+
type WindowGestureContext,
|
|
12
|
+
} from "./use-window-gesture-context";
|
|
13
|
+
|
|
14
|
+
export function useWindowResize(ctx: WindowGestureContext) {
|
|
15
|
+
return useDrag(
|
|
16
|
+
({ args, xy, initial, first, last, memo }) => {
|
|
17
|
+
const handle = args[0] as ResizeHandle;
|
|
18
|
+
const base: Rect = first ? { ...ctx.getRect() } : (memo as Rect);
|
|
19
|
+
if (first) ctx.onGestureActive(true);
|
|
20
|
+
|
|
21
|
+
const { dx, dy } = gestureDisplacement(xy, initial, ctx.getScale());
|
|
22
|
+
const next = clampRect(applyResize(base, handle, dx, dy), ctx.getBounds());
|
|
23
|
+
ctx.onChange(next, last);
|
|
24
|
+
|
|
25
|
+
if (last) ctx.onGestureActive(false);
|
|
26
|
+
return base;
|
|
27
|
+
},
|
|
28
|
+
{ ...WINDOW_DRAG_CONFIG, enabled: !ctx.disabled },
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract between the window frame and whatever draws its titlebar.
|
|
3
|
+
*
|
|
4
|
+
* The frame owns geometry, gestures and keyboard handling; a chrome implementation owns
|
|
5
|
+
* only the look. Everything a titlebar needs to be draggable and operable arrives in
|
|
6
|
+
* `titlebarProps` — a chrome that spreads it gets drag, focus and keyboard for free.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ComponentType, HTMLAttributes } from "react";
|
|
10
|
+
import type { WindowKind, WindowVisualState } from "./window-store-types";
|
|
11
|
+
|
|
12
|
+
export interface WindowChromeProps {
|
|
13
|
+
id: string;
|
|
14
|
+
kind: WindowKind;
|
|
15
|
+
title: string;
|
|
16
|
+
state: WindowVisualState;
|
|
17
|
+
/** True when this window is frontmost — chrome should dim its titlebar otherwise. */
|
|
18
|
+
focused: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Spread on the titlebar element. Carries the drag recogniser, the keyboard handler,
|
|
21
|
+
* `tabIndex`, and the `touch-action`/`user-select` suppression the gesture needs.
|
|
22
|
+
* A chrome may append its own className/style; it must not drop these.
|
|
23
|
+
*/
|
|
24
|
+
titlebarProps: HTMLAttributes<HTMLElement> & { tabIndex: number };
|
|
25
|
+
onMinimize: () => void;
|
|
26
|
+
onToggleMaximize: () => void;
|
|
27
|
+
onClose: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rendered as an element by the frame, so a skin owns its own hooks and state (hover and
|
|
32
|
+
* focus handling for traffic lights, animation, its own effects) without touching the
|
|
33
|
+
* frame's hook order.
|
|
34
|
+
*/
|
|
35
|
+
export type WindowChrome = ComponentType<WindowChromeProps>;
|
|
36
|
+
|
|
37
|
+
/** Titlebar height the frame reserves when a window is collapsed (minimised). */
|
|
38
|
+
export const TITLEBAR_HEIGHT = 36;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps a window `kind` to the component that fills its body.
|
|
3
|
+
*
|
|
4
|
+
* The registry is the only coupling point between the generic window layer and real
|
|
5
|
+
* features: adding a kind means adding an entry here plus a title resolver, and nothing in
|
|
6
|
+
* the frame, store or gestures changes. Entries are lazy so a window's content (and its
|
|
7
|
+
* icon/virtualisation deps) never lands in the initial bundle.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { lazy, type ComponentType, type LazyExoticComponent } from "react";
|
|
11
|
+
import { ExplorerSkinChrome } from "@/components/os-explorer/skins/explorer-skin-chrome";
|
|
12
|
+
import type { WindowChrome } from "./window-chrome-contract";
|
|
13
|
+
import type { WindowKind } from "./window-store-types";
|
|
14
|
+
|
|
15
|
+
export interface WindowContentProps {
|
|
16
|
+
/** Window id — content may call the store to close/retitle itself. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Kind-specific state persisted with the window (e.g. the explorer's current path). */
|
|
19
|
+
payload?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentType<WindowContentProps>>> = {
|
|
23
|
+
explorer: lazy(() => import("@/components/os-explorer/explorer-window-content")),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Per-kind titlebar chrome. `FloatingWindow` renders `chrome` directly with no
|
|
28
|
+
* `<Suspense>` around it, so — unlike `WINDOW_CONTENT` — entries here must be plain,
|
|
29
|
+
* synchronously-renderable components, not `lazy()`.
|
|
30
|
+
*/
|
|
31
|
+
export const WINDOW_CHROME: Partial<Record<WindowKind, WindowChrome>> = {
|
|
32
|
+
explorer: ExplorerSkinChrome,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Chrome for a window kind, or `undefined` to fall back to `DefaultWindowChrome`. */
|
|
36
|
+
export function chromeFor(kind: WindowKind): WindowChrome | undefined {
|
|
37
|
+
return WINDOW_CHROME[kind];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Titlebar text for a window. Falls back to the kind's generic name. */
|
|
41
|
+
export function windowTitle(kind: WindowKind, payload?: Record<string, unknown>): string {
|
|
42
|
+
const explicit = payload?.title;
|
|
43
|
+
if (typeof explicit === "string" && explicit.trim()) return explicit;
|
|
44
|
+
const path = payload?.path;
|
|
45
|
+
if (kind === "explorer" && typeof path === "string" && path) {
|
|
46
|
+
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
47
|
+
// A drive or POSIX root has no last segment worth showing; the raw path is the name.
|
|
48
|
+
return segments[segments.length - 1] ?? path;
|
|
49
|
+
}
|
|
50
|
+
return "Explorer";
|
|
51
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure geometry for the floating window layer — no DOM, no React, so every rule
|
|
3
|
+
* (min size, on-screen clamping, per-handle resize, spawn cascade) is unit-testable.
|
|
4
|
+
*
|
|
5
|
+
* All coordinates are relative to the layer container (the app content area), not the
|
|
6
|
+
* browser viewport: the nav rail must stay usable while a window is maximised.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface Rect {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
w: number;
|
|
13
|
+
h: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Size of the layer container the rects live in. */
|
|
17
|
+
export interface Bounds {
|
|
18
|
+
w: number;
|
|
19
|
+
h: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type ResizeHandle = "n" | "s" | "e" | "w" | "ne" | "nw" | "se" | "sw";
|
|
23
|
+
|
|
24
|
+
export const RESIZE_HANDLES: ResizeHandle[] = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
25
|
+
|
|
26
|
+
export const MIN_SIZE = { w: 360, h: 240 } as const;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* How much of the titlebar must remain reachable after a clamp. A window dragged past an
|
|
30
|
+
* edge keeps this many pixels inside the layer so it can always be grabbed back.
|
|
31
|
+
*/
|
|
32
|
+
export const KEEP_VISIBLE = 48;
|
|
33
|
+
|
|
34
|
+
/** Offset between consecutive spawns, matching desktop window managers. */
|
|
35
|
+
export const CASCADE_STEP = 24;
|
|
36
|
+
|
|
37
|
+
/** Hard cap on simultaneous windows so the z-band stays inside 30..38. */
|
|
38
|
+
export const MAX_WINDOWS = 8;
|
|
39
|
+
|
|
40
|
+
/** Base z-index of the layer; strictly below the app's z-40 backdrops and z-50 popovers. */
|
|
41
|
+
export const Z_BASE = 30;
|
|
42
|
+
|
|
43
|
+
const PREFERRED = { w: 880, h: 560 } as const;
|
|
44
|
+
const SPAWN_MARGIN = 48;
|
|
45
|
+
|
|
46
|
+
const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));
|
|
47
|
+
|
|
48
|
+
/** Inline z-index for a window at `rank` (0 = backmost). Never exceeds Z_BASE + 8. */
|
|
49
|
+
export function windowZIndex(rank: number): number {
|
|
50
|
+
return Z_BASE + clamp(Math.trunc(rank), 0, MAX_WINDOWS);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Fit a rect inside the layer: at least MIN_SIZE, never wider/taller than the layer, and
|
|
55
|
+
* positioned so KEEP_VISIBLE px of the titlebar row stay inside. Values are rounded to
|
|
56
|
+
* whole pixels — sub-pixel transforms blur text on high-DPI screens.
|
|
57
|
+
*/
|
|
58
|
+
export function clampRect(rect: Rect, bounds: Bounds): Rect {
|
|
59
|
+
const w = Math.max(MIN_SIZE.w, Math.min(rect.w, Math.max(bounds.w, MIN_SIZE.w)));
|
|
60
|
+
const h = Math.max(MIN_SIZE.h, Math.min(rect.h, Math.max(bounds.h, MIN_SIZE.h)));
|
|
61
|
+
// Left edge may go negative (window hangs off the left) as long as its right edge
|
|
62
|
+
// still shows KEEP_VISIBLE px; the top edge may never go above the layer, or the
|
|
63
|
+
// titlebar becomes unreachable.
|
|
64
|
+
const x = clamp(rect.x, KEEP_VISIBLE - w, Math.max(KEEP_VISIBLE - w, bounds.w - KEEP_VISIBLE));
|
|
65
|
+
const y = clamp(rect.y, 0, Math.max(0, bounds.h - KEEP_VISIBLE));
|
|
66
|
+
return { x: Math.round(x), y: Math.round(y), w: Math.round(w), h: Math.round(h) };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Apply a drag delta to one of the 8 handles.
|
|
71
|
+
*
|
|
72
|
+
* Edges anchored by the handle stay put; the opposite edge moves. Shrinking past the
|
|
73
|
+
* minimum stops the moving edge instead of pushing the anchored one, which is what makes
|
|
74
|
+
* a resize feel like a native window instead of sliding away under the cursor.
|
|
75
|
+
*/
|
|
76
|
+
export function applyResize(
|
|
77
|
+
rect: Rect,
|
|
78
|
+
handle: ResizeHandle,
|
|
79
|
+
dx: number,
|
|
80
|
+
dy: number,
|
|
81
|
+
min: { w: number; h: number } = MIN_SIZE,
|
|
82
|
+
): Rect {
|
|
83
|
+
let { x, y, w, h } = rect;
|
|
84
|
+
|
|
85
|
+
if (handle.includes("e")) {
|
|
86
|
+
w = Math.max(min.w, rect.w + dx);
|
|
87
|
+
} else if (handle.includes("w")) {
|
|
88
|
+
const right = rect.x + rect.w;
|
|
89
|
+
w = Math.max(min.w, rect.w - dx);
|
|
90
|
+
x = right - w;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (handle.includes("s")) {
|
|
94
|
+
h = Math.max(min.h, rect.h + dy);
|
|
95
|
+
} else if (handle.includes("n")) {
|
|
96
|
+
const bottom = rect.y + rect.h;
|
|
97
|
+
h = Math.max(min.h, rect.h - dy);
|
|
98
|
+
y = bottom - h;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { x, y, w, h };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Where a newly opened window goes: cascaded from the top-left by CASCADE_STEP per existing
|
|
106
|
+
* window, wrapping before it would walk off the layer.
|
|
107
|
+
*/
|
|
108
|
+
export function cascadeSpawnRect(existing: Rect[], bounds: Bounds): Rect {
|
|
109
|
+
const w = Math.min(PREFERRED.w, Math.max(MIN_SIZE.w, bounds.w - SPAWN_MARGIN * 2));
|
|
110
|
+
const h = Math.min(PREFERRED.h, Math.max(MIN_SIZE.h, bounds.h - SPAWN_MARGIN * 2));
|
|
111
|
+
const step = existing.length % MAX_WINDOWS;
|
|
112
|
+
return clampRect(
|
|
113
|
+
{ x: SPAWN_MARGIN + step * CASCADE_STEP, y: SPAWN_MARGIN + step * CASCADE_STEP, w, h },
|
|
114
|
+
bounds,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Nudge step for arrow keys on a focused titlebar (Shift = fine-grained). */
|
|
119
|
+
export function nudgeRect(rect: Rect, dx: number, dy: number, bounds: Bounds): Rect {
|
|
120
|
+
return clampRect({ ...rect, x: rect.x + dx, y: rect.y + dy }, bounds);
|
|
121
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host for all floating windows, sized to the app's content area.
|
|
3
|
+
*
|
|
4
|
+
* It is deliberately a plain absolutely-positioned child of the content wrapper rather than
|
|
5
|
+
* a body-level portal: window coordinates stay relative to the working area, so a maximized
|
|
6
|
+
* window covers the panels but never the nav rail, and the container's own resize is what
|
|
7
|
+
* triggers re-clamping.
|
|
8
|
+
*
|
|
9
|
+
* The layer is desktop-only. Below the `md` breakpoint it renders nothing and never touches
|
|
10
|
+
* the store, leaving the mobile sheet presentation free to own the same content.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Suspense, useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
14
|
+
import { useIsMobile } from "@/hooks/use-is-mobile";
|
|
15
|
+
import { FloatingWindow } from "./floating-window";
|
|
16
|
+
import { chromeFor, WINDOW_CONTENT, windowTitle } from "./window-content-registry";
|
|
17
|
+
import { Z_BASE, MAX_WINDOWS } from "./window-geometry";
|
|
18
|
+
import { useWindowStore, windowsInRankOrder } from "./window-store";
|
|
19
|
+
|
|
20
|
+
export function WindowLayer() {
|
|
21
|
+
const isMobile = useIsMobile();
|
|
22
|
+
if (isMobile) return null;
|
|
23
|
+
return <DesktopWindowLayer />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function DesktopWindowLayer() {
|
|
27
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
28
|
+
const windows = useWindowStore((s) => s.windows);
|
|
29
|
+
const bounds = useWindowStore((s) => s.bounds);
|
|
30
|
+
const [capturing, setCapturing] = useState(false);
|
|
31
|
+
|
|
32
|
+
useLayoutEffect(() => {
|
|
33
|
+
const el = containerRef.current;
|
|
34
|
+
if (!el) return;
|
|
35
|
+
const measure = () => {
|
|
36
|
+
const r = el.getBoundingClientRect();
|
|
37
|
+
const scale = el.offsetWidth > 0 ? r.width / el.offsetWidth : 1;
|
|
38
|
+
// Report layout pixels, not scaled screen pixels, so rects match the CSS coordinates
|
|
39
|
+
// the windows are positioned in.
|
|
40
|
+
return { w: Math.round(r.width / scale), h: Math.round(r.height / scale) };
|
|
41
|
+
};
|
|
42
|
+
const store = useWindowStore.getState();
|
|
43
|
+
store.setBounds(measure());
|
|
44
|
+
store.restoreAll(measure());
|
|
45
|
+
// Panels, the sidebar and the browser all resize this box; window.resize alone misses
|
|
46
|
+
// the first two and would leave windows stranded outside the layer.
|
|
47
|
+
const observer = new ResizeObserver(() => useWindowStore.getState().setBounds(measure()));
|
|
48
|
+
observer.observe(el);
|
|
49
|
+
return () => observer.disconnect();
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const getScale = useCallback(() => {
|
|
53
|
+
const el = containerRef.current;
|
|
54
|
+
if (!el || el.offsetWidth === 0) return 1;
|
|
55
|
+
return el.getBoundingClientRect().width / el.offsetWidth;
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
const ordered = windowsInRankOrder(windows);
|
|
59
|
+
const frontId = ordered[ordered.length - 1]?.id;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div ref={containerRef} className="absolute inset-0 overflow-hidden pointer-events-none">
|
|
63
|
+
{ordered.map((win) => {
|
|
64
|
+
const Content = WINDOW_CONTENT[win.kind];
|
|
65
|
+
const title = windowTitle(win.kind, win.payload);
|
|
66
|
+
return (
|
|
67
|
+
<FloatingWindow
|
|
68
|
+
key={win.id}
|
|
69
|
+
win={win}
|
|
70
|
+
bounds={bounds}
|
|
71
|
+
title={title}
|
|
72
|
+
focused={win.id === frontId}
|
|
73
|
+
getScale={getScale}
|
|
74
|
+
onGestureActive={setCapturing}
|
|
75
|
+
chrome={chromeFor(win.kind)}
|
|
76
|
+
>
|
|
77
|
+
<Suspense fallback={<div className="p-3 text-xs text-text-2">Loading…</div>}>
|
|
78
|
+
<Content id={win.id} payload={win.payload} />
|
|
79
|
+
</Suspense>
|
|
80
|
+
</FloatingWindow>
|
|
81
|
+
);
|
|
82
|
+
})}
|
|
83
|
+
|
|
84
|
+
{/* Transparent capture surface: iframes and editors swallow pointer events, which
|
|
85
|
+
would stall a drag the moment the cursor crossed one. Stays inside the window
|
|
86
|
+
z-band so app popovers and dialogs remain above it. */}
|
|
87
|
+
{capturing && (
|
|
88
|
+
<div className="absolute inset-0 pointer-events-auto" style={{ zIndex: Z_BASE + MAX_WINDOWS }} />
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|