@hienlh/ppm 0.21.0 → 0.22.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/CHANGELOG.md +41 -0
- package/CLAUDE.md +29 -4
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +1 -1
- package/bun.lock +16 -1
- package/dist/web/assets/accounts-settings-section-BVr9-FZJ.js +1 -0
- package/dist/web/assets/{ai-resource-editor-F09pf8Hd.js → ai-resource-editor-CdDpOwjh.js} +1 -1
- package/dist/web/assets/ai-settings-section-B1hQyjAd.js +1 -0
- package/dist/web/assets/ai-settings-section-X2UOfSaS.js +1 -0
- package/dist/web/assets/{angular-html-DkR4iCth.js → angular-html-G66FNlyn.js} +1 -1
- package/dist/web/assets/{angular-ts-DcyL4Chx.js → angular-ts-B-PnUMyt.js} +1 -1
- package/dist/web/assets/{api-client-bfB7cXRM.js → api-client-C1ZwRL4Y.js} +1 -1
- package/dist/web/assets/api-client-pQL53cDQ.js +1 -0
- package/dist/web/assets/{api-settings-BcNjZIvG.js → api-settings-_7Q-ULxp.js} +1 -1
- package/dist/web/assets/{api-themes-DBfyVUtX.js → api-themes-DRAERo4Q.js} +1 -1
- package/dist/web/assets/{apl-BVlrVtjt.js → apl-BmDqqwE8.js} +1 -1
- package/dist/web/assets/{appearance-settings-section-whgnq3ss.js → appearance-settings-section-DLoPK0LD.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Dx6nBEKT.js +1 -0
- package/dist/web/assets/{astro-MjJlozLj.js → astro-BfwMNfxr.js} +1 -1
- package/dist/web/assets/{audio-preview-Bc54GCAV.js → audio-preview-DsYvgKqj.js} +1 -1
- package/dist/web/assets/{blade-DnJWi1fz.js → blade-BsFV4pnN.js} +1 -1
- package/dist/web/assets/branch-review-tab-G84Ojzae.js +1 -0
- package/dist/web/assets/{bsl-DCmJxSIz.js → bsl-Cw9Outng.js} +1 -1
- package/dist/web/assets/{button-VW4QGKCn.js → button-B9CTrsEf.js} +1 -1
- package/dist/web/assets/{cairo-C8dDenub.js → cairo-DOK5Wqnd.js} +1 -1
- package/dist/web/assets/chat-tab-DQJfjm8E.js +15 -0
- package/dist/web/assets/{cobol-DPx_nifl.js → cobol-DRaRy4FK.js} +1 -1
- package/dist/web/assets/code-editor-D04iUPsZ.js +7 -0
- package/dist/web/assets/{codex-daily-guard-CfybHv_c.js → codex-daily-guard-BSccLRGT.js} +1 -1
- package/dist/web/assets/{coffee-DvFzqhgN.js → coffee-DvXuBmEJ.js} +1 -1
- package/dist/web/assets/{conflict-editor-DenQUOT2.js → conflict-editor-C4ay676e.js} +2 -2
- package/dist/web/assets/context-menu-C6pIsf83.js +1 -0
- package/dist/web/assets/{cpp-BLJN0umi.js → cpp-zCmm8rKA.js} +1 -1
- package/dist/web/assets/{crystal-BXR-9fGw.js → crystal-CdQx6lB6.js} +1 -1
- package/dist/web/assets/{csv-preview-CClrJOuR.js → csv-preview-DbwEmG7o.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-D4fhcMfT.js → data-grid-overlay-editor-6xUCeLHh.js} +1 -1
- package/dist/web/assets/{database-viewer-DItjm8wB.js → database-viewer-S6LsALjX.js} +1 -1
- package/dist/web/assets/{dialog-DQTn55Mt.js → dialog-B84_DJQ8.js} +1 -1
- package/dist/web/assets/diff-viewer-3oBA7vgQ.js +4 -0
- package/dist/web/assets/{docx-preview-DYBkiUaE.js → docx-preview-CXuk-emm.js} +1 -1
- package/dist/web/assets/{edge-rWOlCmXT.js → edge-E8QLt7oi.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-COtmo54G.js → edit-diff-preview-BGGGecVw.js} +1 -1
- package/dist/web/assets/{editor-language-service-DxkZ51E5.js → editor-language-service-DJmaKVY0.js} +1 -1
- package/dist/web/assets/{elixir-E7Y82okW.js → elixir-DPb_-72P.js} +1 -1
- package/dist/web/assets/{elm-DTPGbc-w.js → elm-BMzJY5b0.js} +1 -1
- package/dist/web/assets/{erb-DHqLHrE6.js → erb-AABQNtMK.js} +1 -1
- package/dist/web/assets/{erlang-Byg0nNiV.js → erlang-CEIKOkfP.js} +1 -1
- package/dist/web/assets/{esm-D_DmOM7n.js → esm-B9RVMpwV.js} +1 -1
- package/dist/web/assets/explorer-body-BQ0jyAGv.js +2 -0
- package/dist/web/assets/explorer-window-content-jdkWt34J.js +1 -0
- package/dist/web/assets/{extension-manager-section-BhwB6nqr.js → extension-manager-section-DG1guILi.js} +1 -1
- package/dist/web/assets/{extension-webview-DI4VL2tJ.js → extension-webview-BGSDUbdK.js} +1 -1
- package/dist/web/assets/file-icons-BXdvggK2.js +1 -0
- package/dist/web/assets/file-icons.generated-BtBErEwb.css +1 -0
- package/dist/web/assets/file-store-ojgMXgAw.js +1 -0
- package/dist/web/assets/files-settings-section-Gnyd7han.js +1 -0
- package/dist/web/assets/{format-file-meta-DOpzJJhS.js → format-file-meta-C77h04cZ.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-CY1n0Xf6.js → fortran-fixed-form-CrXvLHyi.js} +1 -1
- package/dist/web/assets/{fsharp-_vEDSCJz.js → fsharp-DCI4qAz_.js} +1 -1
- package/dist/web/assets/{gdresource-B6XT0wAC.js → gdresource-Vb2kO1MG.js} +1 -1
- package/dist/web/assets/{general-settings-section-BcxwiZ0X.js → general-settings-section-V6Khh15V.js} +1 -1
- package/dist/web/assets/{git-commit-Wk9ovq59.js → git-commit-BMI_OWn-.js} +1 -1
- package/dist/web/assets/git-log-panel-Bi513qjT.js +1 -0
- package/dist/web/assets/{git-rebase-DSarGPvK.js → git-rebase-D-d_2iqZ.js} +1 -1
- package/dist/web/assets/{git-repo-store-Dy69YCHN.js → git-repo-store-88Z8v-pF.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C6qRruHI.js +1 -0
- package/dist/web/assets/{glide-data-grid-BvgAqSUr.js → glide-data-grid-DnQwYSmQ.js} +7 -7
- package/dist/web/assets/{glimmer-js-CkYI35_P.js → glimmer-js-BmNNiR0Z.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BqmIZcds.js → glimmer-ts-DQE2Ufha.js} +1 -1
- package/dist/web/assets/{glsl-Be-yBCF5.js → glsl-BcIDsxRJ.js} +1 -1
- package/dist/web/assets/{graphql-a5DrENHY.js → graphql-DGKSLmGH.js} +1 -1
- package/dist/web/assets/group-chat-tab-DpczdwyV.js +1 -0
- package/dist/web/assets/{hack-BLAg20aD.js → hack-YiC8bEIb.js} +1 -1
- package/dist/web/assets/{haml-Dj6bGOQH.js → haml-DjblxAsz.js} +1 -1
- package/dist/web/assets/{handlebars-dmEa36Oh.js → handlebars-BQepOXaU.js} +1 -1
- package/dist/web/assets/{html-yacZYzSE.js → html-Cayj_2GP.js} +1 -1
- package/dist/web/assets/{html-derivative-lLi3-RNT.js → html-derivative-Weq79bL6.js} +1 -1
- package/dist/web/assets/{http-1KaxcE4e.js → http-BeLfdGdz.js} +1 -1
- package/dist/web/assets/{hurl-ST-dEEZR.js → hurl-D9rypCLK.js} +1 -1
- package/dist/web/assets/{hxml-CpDtuq__.js → hxml-clG8mmSH.js} +1 -1
- package/dist/web/assets/{image-preview-CJVxL0uH.js → image-preview-CzCc4_Do.js} +1 -1
- package/dist/web/assets/image-preview-geometry-9lgeLYB3.js +1 -0
- package/dist/web/assets/index-BH99TIE0.css +1 -0
- package/dist/web/assets/index-DU68xk06.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-BOSpdwlz.js +1 -0
- package/dist/web/assets/{jinja-Cjw-evAn.js → jinja-BShBWfs5.js} +1 -1
- package/dist/web/assets/{jira-watcher-section-DIC1yIV3.js → jira-watcher-section-f2HUbGDY.js} +1 -1
- package/dist/web/assets/{jison-DfATMYoY.js → jison-dN2Nf_Dr.js} +1 -1
- package/dist/web/assets/{julia-DDCaxiqT.js → julia-5U0vR-uJ.js} +1 -1
- package/dist/web/assets/{just-CJL529u6.js → just-BMppVRy6.js} +1 -1
- package/dist/web/assets/keybindings-store-DCRsnKg5.js +1 -0
- package/dist/web/assets/{keyboard-shortcuts-section-BW03P3AF.js → keyboard-shortcuts-section-BpvVEw_Y.js} +1 -1
- package/dist/web/assets/{latex-Zc4EpLpW.js → latex-DSSOKjis.js} +1 -1
- package/dist/web/assets/{liquid-B_6KMNYS.js → liquid-C24dKoyI.js} +1 -1
- package/dist/web/assets/{lua-BtS2idK7.js → lua-DNOsnyhn.js} +1 -1
- package/dist/web/assets/markdown-renderer-Bcaf9_bA.js +1 -0
- package/dist/web/assets/markdown-renderer-DGJp8HC3.js +3 -0
- package/dist/web/assets/{marko-Wk6LBnck.js → marko-26MHO_Q9.js} +1 -1
- package/dist/web/assets/{mdc-cRaXBrzL.js → mdc-Dhw9mGrC.js} +1 -1
- package/dist/web/assets/{media-url-DL8OCGGg.js → media-url-yfqHpPz0.js} +1 -1
- package/dist/web/assets/{mobile-bottom-sheet-CoUauvLF.js → mobile-bottom-sheet-C3TniEP-.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-Bol3XAO1.js +1 -0
- package/dist/web/assets/{nextflow-Bos2wvDV.js → nextflow-xH4oJTex.js} +1 -1
- package/dist/web/assets/{nginx-DMlOwzdr.js → nginx-CMz4w0M8.js} +1 -1
- package/dist/web/assets/{nim-D2dZQA88.js → nim-D8FnIYsx.js} +1 -1
- package/dist/web/assets/notification-store-BXt54VIn.js +1 -0
- package/dist/web/assets/{notifications-settings-section-DPUROMw-.js → notifications-settings-section-DqlPDfNX.js} +1 -1
- package/dist/web/assets/{number-overlay-editor-DZ2i_Bnb.js → number-overlay-editor-CRGwULhR.js} +1 -1
- package/dist/web/assets/{overview-panel-R146OIom.js → overview-panel-Di-3aV9p.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-BGIE1QdU.js +1 -0
- package/dist/web/assets/panel-store-CboGJAwh.js +1 -0
- package/dist/web/assets/{pdf-preview-BWUPGLp6.js → pdf-preview-CmBI6E7d.js} +1 -1
- package/dist/web/assets/{perl-C5-Z6fPn.js → perl-D9iNWYwb.js} +1 -1
- package/dist/web/assets/{php-BXNNhkMH.js → php-Bw3Qo0Zv.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-Dg-QTbXX.js +1 -0
- package/dist/web/assets/{postgres-viewer-CjOvaruz.js → postgres-viewer-8ZAqEALv.js} +3 -3
- package/dist/web/assets/{ppmbot-settings-section-DrvWh8l8.js → ppmbot-settings-section-DKJUA3Kq.js} +1 -1
- package/dist/web/assets/problems-panel-CFcoUx8h.js +1 -0
- package/dist/web/assets/{process-table-BRMS1MrX.js → process-table-EKr2dqG6.js} +1 -1
- package/dist/web/assets/project-framework-store-B70Q3dNM.js +1 -0
- package/dist/web/assets/project-store-B87hLRSk.js +1 -0
- package/dist/web/assets/{proxy-settings-section-Bv0Ny-Yy.js → proxy-settings-section-B7qRVl8w.js} +1 -1
- package/dist/web/assets/{pug-C1zf1yWy.js → pug-fT5JC6Vm.js} +1 -1
- package/dist/web/assets/{qml-OW58jGpD.js → qml-P_wEDB1w.js} +1 -1
- package/dist/web/assets/{query-audit-section-BcHJZcVC.js → query-audit-section-By6qyqdB.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-DZ6IVu2s.js +1 -0
- package/dist/web/assets/{razor-DzjSaobM.js → razor-Drrt2_Ph.js} +1 -1
- package/dist/web/assets/remote-desktop-clipboard-notice-0WDYsyCA.js +1 -0
- package/dist/web/assets/remote-desktop-mobile-sheet-COYDg16H.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-2DqSuS2U.js +1 -0
- package/dist/web/assets/{remote-desktop-readiness-gate-BTzss2wX.js → remote-desktop-readiness-gate-Dm_qMJMH.js} +1 -1
- package/dist/web/assets/remote-desktop-window-content-UodhsF7W.js +1 -0
- package/dist/web/assets/{rst-RfuihOUW.js → rst-Dr2mW4-h.js} +1 -1
- package/dist/web/assets/{ruby-CNO3pli1.js → ruby-C6EpUsTY.js} +1 -1
- package/dist/web/assets/{run-in-terminal-Cb_hVU8x.js → run-in-terminal-CdefEe_R.js} +1 -1
- package/dist/web/assets/{sas-DZY5BtXx.js → sas-D8ZEeP6v.js} +1 -1
- package/dist/web/assets/{schedules-settings-section-DO8xAOVj.js → schedules-settings-section-bG2lYfGv.js} +1 -1
- package/dist/web/assets/{scroll-area-LtNV8g5o.js → scroll-area-BzeiOd-B.js} +1 -1
- package/dist/web/assets/{scss--4g-REcN.js → scss-ITL7jQ1f.js} +1 -1
- package/dist/web/assets/{send-to-chat-Cnpru1M0.js → send-to-chat-F207yH3u.js} +1 -1
- package/dist/web/assets/{separator-Cds1yxy8.js → separator-TRMsJUYe.js} +1 -1
- package/dist/web/assets/settings-body-C06bFarv.js +2 -0
- package/dist/web/assets/settings-store-krC6bc79.js +2 -0
- package/dist/web/assets/settings-tab-34RSaASd.js +1 -0
- package/dist/web/assets/settings-window-content-Dr_2lmHH.js +1 -0
- package/dist/web/assets/{shaderlab-Cu4sBiVn.js → shaderlab-mW-IIlhk.js} +1 -1
- package/dist/web/assets/{shellsession-DWzJfEv7.js → shellsession-DhNl5STt.js} +1 -1
- package/dist/web/assets/{soy-BnqE-5Ff.js → soy-SdY0Qjtr.js} +1 -1
- package/dist/web/assets/{sparql-Ctg__NhZ.js → sparql-ftqZo2i-.js} +1 -1
- package/dist/web/assets/{sql-query-editor-D9k19T_M.js → sql-query-editor-BgRH0cHD.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-DoV9hnWJ.js → sqlite-viewer-DUxGslpa.js} +1 -1
- package/dist/web/assets/{stata-Qk2h66XW.js → stata-Bj7Yjb4b.js} +1 -1
- package/dist/web/assets/{surrealql-8QEX7EfH.js → surrealql-CLhYGAfB.js} +1 -1
- package/dist/web/assets/{svelte-D_mubC5-.js → svelte-DTAlRHhK.js} +1 -1
- package/dist/web/assets/system-monitor-body-IZuLn7Qh.js +2 -0
- package/dist/web/assets/system-monitor-tab-DiSA490Z.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-qXLcicFM.js → system-monitor-window-content-CEaFISgj.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-7fPZNEni.js → tab-host-window-content-D0TN8Wrq.js} +1 -1
- package/dist/web/assets/{tab-store-DGJGnbwv.js → tab-store-OJa1GPCq.js} +1 -1
- package/dist/web/assets/team-member-sheet-DDPNcCjd.js +2 -0
- package/dist/web/assets/{team-member-window-content-BgvJFypM.js → team-member-window-content-DCiW_1-E.js} +1 -1
- package/dist/web/assets/{templ-C1jV0fsO.js → templ-BMfhACxb.js} +1 -1
- package/dist/web/assets/{terminal-tab-Cs0sGljx.js → terminal-tab-4ypP0NDY.js} +3 -3
- package/dist/web/assets/{tex-D_StFXy_.js → tex-BcssM-QA.js} +1 -1
- package/dist/web/assets/tool-cards-Dd6E7Q3k.js +9 -0
- package/dist/web/assets/treemap-KZPCXAKY-CE30wCXa.js +1 -0
- package/dist/web/assets/{ts-tags-B5m47rEm.js → ts-tags-CrR5IYI8.js} +1 -1
- package/dist/web/assets/{twig-C_br6GIl.js → twig-DchfUmyI.js} +1 -1
- package/dist/web/assets/{use-blob-url-C-SGi_Hb.js → use-blob-url-CPtCcY3i.js} +1 -1
- package/dist/web/assets/{use-inline-blame-B4trxGcR.js → use-inline-blame-vrfFHuaR.js} +1 -1
- package/dist/web/assets/{use-lsp-CV0vzILO.js → use-lsp-CsuZu49X.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-DBqbWjWX.js → use-monaco-theme-BIVYJBIn.js} +1 -1
- package/dist/web/assets/{use-open-team-member-DENsjp0s.js → use-open-team-member-C3mrUzIC.js} +1 -1
- package/dist/web/assets/{use-websocket-LgXiX1qN.js → use-websocket-BMbufhX7.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-Cbig5zA6.js → vendor-mermaid-BitiXJlL.js} +1 -1
- package/dist/web/assets/{vendor-ui-BBdkypKW.js → vendor-ui-DmZ-Mb-H.js} +1 -1
- package/dist/web/assets/video-preview-Byj8P_jA.js +1 -0
- package/dist/web/assets/{vue-DQwCnLdr.js → vue-1EL6Pw1h.js} +1 -1
- package/dist/web/assets/{vue-html-T_EoGpPJ.js → vue-html-BPQNwPC6.js} +1 -1
- package/dist/web/assets/{vue-vine-DmxLqX50.js → vue-vine-Bb_dsWLt.js} +1 -1
- package/dist/web/assets/{window-pip-registry-3l4hoxuD.js → window-pip-registry-yD6esSju.js} +1 -1
- package/dist/web/assets/ws-auth-CEYVQ8NH.js +1 -0
- package/dist/web/assets/{xml-Bs6Bw8is.js → xml-dxkLNA0K.js} +1 -1
- package/dist/web/assets/{xsl-f5ZNhRCc.js → xsl-Bw2adwrl.js} +1 -1
- package/dist/web/index.html +27 -26
- package/dist/web/sw.js +1 -1
- package/docs/architecture/plugins-and-tracing.md +285 -0
- package/docs/lessons-learned.md +31 -0
- package/docs/system-architecture.md +1 -0
- package/package.json +2 -1
- package/packages/ext-git-graph/src/compare-diff-base.test.ts +173 -0
- package/packages/ext-git-graph/src/compare-view.ts +22 -3
- package/packages/ext-git-graph/src/extension.ts +6 -0
- package/packages/ext-git-graph/src/types.ts +19 -0
- package/packages/ext-git-graph/src/webview-html.test.ts +134 -1
- package/packages/ext-git-graph/src/webview-html.ts +277 -27
- package/scripts/gen-file-icons.ts +493 -242
- package/src/providers/claude-agent-sdk.ts +42 -1
- package/src/providers/codex-app-server/codex-history.ts +77 -23
- package/src/server/index.ts +42 -7
- package/src/server/routes/chat.ts +15 -11
- package/src/server/routes/git.ts +83 -3
- package/src/server/routes/remote-desktop.ts +7 -2
- package/src/server/ws/global.ts +1 -1
- package/src/server/ws/remote-desktop.ts +12 -5
- package/src/services/binary-content.ts +25 -0
- package/src/services/chat-search.service.ts +1 -1
- package/src/services/chat.service.ts +9 -0
- package/src/services/event-loop-lag.ts +208 -0
- package/src/services/file-lines.ts +114 -0
- package/src/services/file-watcher/recreated-dir-poller.ts +108 -6
- package/src/services/file-watcher/watch-tree.ts +131 -10
- package/src/services/file-watcher.service.ts +73 -23
- package/src/services/git-branch-diff/branch-diff-parse.ts +151 -0
- package/src/services/git-branch-diff/branch-diff.service.ts +79 -0
- package/src/services/git-hunks/git-hunks.service.ts +26 -11
- package/src/services/git-hunks/unified-diff.ts +15 -1
- package/src/services/git.service.ts +74 -24
- package/src/services/jsonl-transcript-parser.ts +138 -6
- package/src/services/log-rotate.ts +142 -0
- package/src/services/media-transcode/ffmpeg-capabilities.ts +72 -2
- package/src/services/remote-desktop/remote-desktop-audio.ts +193 -0
- package/src/services/remote-desktop/remote-desktop-capture-input.ts +87 -14
- package/src/services/remote-desktop/remote-desktop-capture.ts +40 -7
- package/src/services/remote-desktop/remote-desktop-clipboard.ts +166 -0
- package/src/services/remote-desktop/remote-desktop-displays.ts +77 -1
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +25 -6
- package/src/services/remote-desktop/remote-desktop-evdev-key-map.ts +69 -0
- package/src/services/remote-desktop/remote-desktop-input-linux.ts +253 -0
- package/src/services/remote-desktop/remote-desktop-input-uinput.ts +242 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +40 -8
- package/src/services/remote-desktop/remote-desktop-linux-session.ts +146 -0
- package/src/services/remote-desktop/remote-desktop-ogg-opus.ts +144 -0
- package/src/services/remote-desktop/remote-desktop-privacy.ts +163 -0
- package/src/services/remote-desktop/remote-desktop-quality.ts +138 -0
- package/src/services/remote-desktop/remote-desktop-requirements.ts +158 -5
- package/src/services/remote-desktop/remote-desktop-resolution.ts +302 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +523 -21
- package/src/services/remote-desktop/remote-desktop-x11.ts +250 -0
- package/src/services/supervisor.ts +47 -4
- package/src/shared/remote-desktop-custom-quality.ts +117 -0
- package/src/shared/remote-desktop-quality.ts +127 -0
- package/src/types/chat.ts +4 -0
- package/src/types/git.ts +52 -0
- package/src/web/components/branch-review/branch-review-tab.tsx +360 -0
- package/src/web/components/branch-review/branch-review-tree-row.tsx +138 -0
- package/src/web/components/chat/attachment-chips.tsx +30 -6
- package/src/web/components/chat/chat-tab.tsx +12 -1
- package/src/web/components/chat/message-events.tsx +273 -0
- package/src/web/components/chat/message-list.tsx +107 -861
- package/src/web/components/chat/message-markdown.tsx +34 -0
- package/src/web/components/chat/message-media.tsx +174 -0
- package/src/web/components/chat/message-user-bubble.tsx +399 -0
- package/src/web/components/chat/provider-selector.tsx +30 -22
- package/src/web/components/chat/session-history-list.tsx +2 -2
- package/src/web/components/chat/session-list-panel.tsx +3 -2
- package/src/web/components/editor/binary-diff-view.tsx +212 -0
- package/src/web/components/editor/diff-viewer.tsx +76 -3
- package/src/web/components/explorer/file-tree.tsx +235 -134
- package/src/web/components/explorer/flatten-visible-tree.ts +35 -40
- package/src/web/components/explorer/tree-node.tsx +69 -69
- package/src/web/components/explorer/use-tree-row-dnd.ts +3 -4
- package/src/web/components/git/git-status-panel.tsx +25 -0
- package/src/web/components/layout/draggable-tab.tsx +4 -2
- package/src/web/components/layout/nav-section-rail.tsx +12 -3
- package/src/web/components/layout/status-bar.tsx +10 -6
- package/src/web/components/layout/tab-content.tsx +5 -0
- package/src/web/components/layout/tab-pool.tsx +1 -0
- package/src/web/components/layout/upgrade-button.tsx +5 -1
- package/src/web/components/remote-desktop/remote-desktop-clipboard-client.ts +67 -0
- package/src/web/components/remote-desktop/remote-desktop-clipboard-notice.tsx +127 -0
- package/src/web/components/remote-desktop/remote-desktop-codec-labels.ts +28 -0
- package/src/web/components/remote-desktop/remote-desktop-custom-quality-dialog.tsx +144 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +13 -2
- package/src/web/components/remote-desktop/remote-desktop-mobile-settings.tsx +232 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +21 -8
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +82 -3
- package/src/web/components/remote-desktop/remote-desktop-recording.ts +76 -0
- package/src/web/components/remote-desktop/remote-desktop-resolution-list.ts +100 -0
- package/src/web/components/remote-desktop/remote-desktop-toolbar.tsx +497 -0
- package/src/web/components/remote-desktop/remote-desktop-view-style.ts +145 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +126 -35
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +11 -0
- package/src/web/components/remote-desktop/use-opus-audio-player.ts +148 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +284 -1
- package/src/web/components/remote-desktop/use-remote-desktop-readiness.ts +75 -1
- package/src/web/components/remote-desktop/use-remote-desktop-recorder.ts +103 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +19 -2
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +39 -3
- package/src/web/components/settings/theme-picker.tsx +2 -1
- package/src/web/components/ui/tooltip.tsx +15 -2
- package/src/web/hooks/use-delayed-flag.ts +35 -0
- package/src/web/lib/branch-review-state.ts +175 -0
- package/src/web/lib/file-icon-name.ts +62 -0
- package/src/web/lib/file-icons.generated.ts +3287 -25
- package/src/web/lib/file-icons.tsx +71 -73
- package/src/web/lib/flatten-expansions.ts +18 -5
- package/src/web/lib/message-ordinals.ts +23 -0
- package/src/web/lib/provider-logos.tsx +143 -0
- package/src/web/lib/tab-type-icons.ts +11 -0
- package/src/web/stores/file-store.ts +73 -35
- package/src/web/stores/panel-utils.ts +5 -0
- package/src/web/stores/project-framework-store.ts +82 -0
- package/src/web/stores/project-store.ts +7 -1
- package/src/web/stores/settings-store.ts +128 -0
- package/src/web/stores/tab-store.ts +1 -0
- package/src/web/stores/window-panel-persistence.ts +1 -0
- package/src/web/styles/file-icons.generated.css +3282 -234
- package/src/web/styles/globals.css +26 -0
- package/dist/web/assets/accounts-settings-section-BuxD9zH3.js +0 -1
- package/dist/web/assets/ai-settings-section-Dd9tOUp0.js +0 -1
- package/dist/web/assets/ai-settings-section-FrU2X8Fu.js +0 -1
- package/dist/web/assets/api-client-BUCo1lms.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-5K0wEkAn.js +0 -1
- package/dist/web/assets/chat-tab-Cr6_KSGO.js +0 -15
- package/dist/web/assets/code-editor-DfmlGamv.js +0 -7
- package/dist/web/assets/context-menu-DOODKZdF.js +0 -1
- package/dist/web/assets/diff-viewer-BpNj7pN9.js +0 -4
- package/dist/web/assets/explorer-body-D-5z464e.js +0 -2
- package/dist/web/assets/explorer-window-content-7-1wuRul.js +0 -1
- package/dist/web/assets/file-icons-EzPL0q-k.js +0 -1
- package/dist/web/assets/file-icons.generated-CfaJuvZu.css +0 -1
- package/dist/web/assets/file-store-DMABxOkV.js +0 -1
- package/dist/web/assets/files-settings-section-CZzABuKz.js +0 -1
- package/dist/web/assets/git-log-panel-CRN4JVOx.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-DkVT2MAM.js +0 -1
- package/dist/web/assets/group-chat-tab-DFa5mAx5.js +0 -1
- package/dist/web/assets/index-D7DbXD3G.css +0 -1
- package/dist/web/assets/index-ZtfxZOyb.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-BBsmKGzA.js +0 -1
- package/dist/web/assets/keybindings-store-tVfhPANS.js +0 -1
- package/dist/web/assets/markdown-renderer-CvL1pWeK.js +0 -3
- package/dist/web/assets/markdown-renderer-sJNNVhlS.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-CZ0HZ2E_.js +0 -1
- package/dist/web/assets/notification-store-BZbsPnW0.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DwmYPOlx.js +0 -1
- package/dist/web/assets/panel-store-BVLBRn6M.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-D0oogIFi.js +0 -1
- package/dist/web/assets/problems-panel-B7BWVhdy.js +0 -1
- package/dist/web/assets/project-store-BhMbVln3.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-CQIJOQGa.js +0 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-ByjaeZKX.js +0 -2
- package/dist/web/assets/remote-desktop-mobile-view-D13mtr9F.js +0 -1
- package/dist/web/assets/remote-desktop-window-content-CbqTCaC6.js +0 -1
- package/dist/web/assets/settings-body-Rnu-FcX-.js +0 -2
- package/dist/web/assets/settings-store-DZPgPtEA.js +0 -2
- package/dist/web/assets/settings-tab-C6PHxs0Z.js +0 -1
- package/dist/web/assets/settings-window-content-CQ4gT2qj.js +0 -1
- package/dist/web/assets/system-monitor-body-2vQ1kMxE.js +0 -2
- package/dist/web/assets/system-monitor-tab-krAcvwF7.js +0 -1
- package/dist/web/assets/team-member-sheet-2FnRmk1a.js +0 -2
- package/dist/web/assets/tool-cards-DU_S2o51.js +0 -9
- package/dist/web/assets/treemap-KZPCXAKY-BhEtjljr.js +0 -1
- package/dist/web/assets/use-remote-desktop-display-choice-BK_f787P.js +0 -1
- package/dist/web/assets/video-preview-BDNwumbW.js +0 -1
- package/dist/web/assets/ws-auth-DEAJiGCR.js +0 -1
- /package/dist/web/assets/{abap-tkptwpsK.js → abap-IN2yINdN.js} +0 -0
- /package/dist/web/assets/{actionscript-3-Di9TMGwg.js → actionscript-3-C_t772eR.js} +0 -0
- /package/dist/web/assets/{ada-CB9F079D.js → ada-DPme3emm.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dcl8jpb7.js → andromeeda-DSV4i4mZ.js} +0 -0
- /package/dist/web/assets/{apache-D0Nk0qsH.js → apache-CWr8s8I8.js} +0 -0
- /package/dist/web/assets/{apex-mQIoWncD.js → apex-Z20JnDF5.js} +0 -0
- /package/dist/web/assets/{applescript-C6ljbcsI.js → applescript-BKNPu3ga.js} +0 -0
- /package/dist/web/assets/{apply-theme-BwIMNXyX.js → apply-theme-CbR31K6I.js} +0 -0
- /package/dist/web/assets/{ara-WFqRPGPe.js → ara-hWdY3O96.js} +0 -0
- /package/dist/web/assets/{asciidoc-bNd8w4nf.js → asciidoc-eynhq496.js} +0 -0
- /package/dist/web/assets/{asm-BQ1_F580.js → asm-Bu02Kq4P.js} +0 -0
- /package/dist/web/assets/{aurora-x-Ciqs_Dbu.js → aurora-x-BHBGXbIZ.js} +0 -0
- /package/dist/web/assets/{awk-GCR-6KZV.js → awk-B4ojezyv.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DxJXuj99.js → ayu-dark-D0GQzFi9.js} +0 -0
- /package/dist/web/assets/{ayu-light-jvSyJWDV.js → ayu-light-BVYb5Yn8.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Cg3BPO6H.js → ayu-mirage-BGFW6oNb.js} +0 -0
- /package/dist/web/assets/{ballerina-BPz4CrX6.js → ballerina-B6Og3IKT.js} +0 -0
- /package/dist/web/assets/{bat-CSTs2hIG.js → bat-D8YFILfx.js} +0 -0
- /package/dist/web/assets/{beancount-DMateAZX.js → beancount-BjlbBBuX.js} +0 -0
- /package/dist/web/assets/{berry-9lXg0GRM.js → berry-Cz1ygeoj.js} +0 -0
- /package/dist/web/assets/{bibtex-DIbqmvFg.js → bibtex-CVkJceJh.js} +0 -0
- /package/dist/web/assets/{bicep-02WGsoJ8.js → bicep-DrhYI5GX.js} +0 -0
- /package/dist/web/assets/{bird2-BY0tbu3k.js → bird2-BV-KJtmc.js} +0 -0
- /package/dist/web/assets/{c-jMBX_9Je.js → c-CuLlvxbG.js} +0 -0
- /package/dist/web/assets/{c3-B8JVbNjW.js → c3-CI7-1CJ0.js} +0 -0
- /package/dist/web/assets/{cadence-DUIf1tvL.js → cadence-DjxhNP3Q.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-DGEc0s4P.js → can-open-in-ppm-cv5--yHX.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-FhUCVxex.js → catppuccin-frappe-CEozpJRh.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-CzfrtmgL.js → catppuccin-latte-C4sXQFMc.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-6xL0lOtj.js → catppuccin-macchiato-Ci-fDhXy.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-D39-xS3P.js → catppuccin-mocha-CH7YXw8d.js} +0 -0
- /package/dist/web/assets/{clarity-D8TeGvoT.js → clarity-BWrJ7UmT.js} +0 -0
- /package/dist/web/assets/{clojure-Dx_acSX7.js → clojure-C94fh2hb.js} +0 -0
- /package/dist/web/assets/{cmake-CyeYPq8o.js → cmake-CtvCkMsG.js} +0 -0
- /package/dist/web/assets/{codeowners-DrTI0lmI.js → codeowners-CJnubqzH.js} +0 -0
- /package/dist/web/assets/{codeql-CPkFxVW-.js → codeql-BOoP5HY2.js} +0 -0
- /package/dist/web/assets/{common-lisp-B0k-3LTj.js → common-lisp-B9nlMFJd.js} +0 -0
- /package/dist/web/assets/{coq-uygpo7to.js → coq-CgzIQiSH.js} +0 -0
- /package/dist/web/assets/{csharp-DKUYGfSp.js → csharp-4xAIx3jw.js} +0 -0
- /package/dist/web/assets/{css-DoOUp7v4.js → css-D4YoLYGn.js} +0 -0
- /package/dist/web/assets/{csv-B3YE13gi.js → csv-C2v-ZakW.js} +0 -0
- /package/dist/web/assets/{csv-parser-DXePqmXe.js → csv-parser-_oDBUkwQ.js} +0 -0
- /package/dist/web/assets/{cue-DViO2wvn.js → cue-Bbznh_an.js} +0 -0
- /package/dist/web/assets/{cypher-4BMCIW5N.js → cypher-BKhVEARF.js} +0 -0
- /package/dist/web/assets/{d-BT_sTHas.js → d-D6IhWopl.js} +0 -0
- /package/dist/web/assets/{dark-plus-DVVXu-1N.js → dark-plus-xAWu790O.js} +0 -0
- /package/dist/web/assets/{dart-DHz6f6_6.js → dart-Bj6S9O1j.js} +0 -0
- /package/dist/web/assets/{data-grid-types-OgUxMLdQ.js → data-grid-types-C5bthITf.js} +0 -0
- /package/dist/web/assets/{dax-ChYCjweK.js → dax-CF2zvaqq.js} +0 -0
- /package/dist/web/assets/{desktop-D6Lt4p04.js → desktop-CnmyxMKE.js} +0 -0
- /package/dist/web/assets/{diff-D8TDe4hP.js → diff-Qi8WuJsI.js} +0 -0
- /package/dist/web/assets/{dist-DOTlFQJA.js → dist-Bjseolz4.js} +0 -0
- /package/dist/web/assets/{dist-CrBOCtCW.js → dist-Czq7BUMy.js} +0 -0
- /package/dist/web/assets/{docker-Bsp0YbPx.js → docker-vLbbNP5d.js} +0 -0
- /package/dist/web/assets/{dotenv-B_7giSY_.js → dotenv-DJMvvNzn.js} +0 -0
- /package/dist/web/assets/{dracula-TrQL2iB5.js → dracula-CtAh2rhB.js} +0 -0
- /package/dist/web/assets/{dracula-soft-n2XOlgh-.js → dracula-soft-CXtuAAf6.js} +0 -0
- /package/dist/web/assets/{dream-maker-CdIEdhpT.js → dream-maker-D5YQyK49.js} +0 -0
- /package/dist/web/assets/{editor-font-BDrdiGf1.js → editor-font-ClTC9gaD.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CSbixs6u.js → emacs-lisp-CGTjbiqF.js} +0 -0
- /package/dist/web/assets/{everforest-dark-BuZahZYp.js → everforest-dark-D59p7vAQ.js} +0 -0
- /package/dist/web/assets/{everforest-light-DPH53z5H.js → everforest-light-B3Qtud3V.js} +0 -0
- /package/dist/web/assets/{fennel-B1-K_hlJ.js → fennel-BCyHNUwf.js} +0 -0
- /package/dist/web/assets/{fish-Cy12Dbua.js → fish-DDDlLV2G.js} +0 -0
- /package/dist/web/assets/{fluent-B_82ys8E.js → fluent-By54R9tl.js} +0 -0
- /package/dist/web/assets/{format-bytes-D5sBgIqU.js → format-bytes-D1AQyUCt.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-BRQdNzZF.js → fortran-free-form-tP8mRym_.js} +0 -0
- /package/dist/web/assets/{gdscript-XBDmLUm9.js → gdscript-nHC2z6Te.js} +0 -0
- /package/dist/web/assets/{gdshader-DhVfB3u4.js → gdshader-K-cSmPlW.js} +0 -0
- /package/dist/web/assets/{genie-X3K29Y49.js → genie-BAcRhvEe.js} +0 -0
- /package/dist/web/assets/{gherkin-DKIx-6cX.js → gherkin-CcifxMLb.js} +0 -0
- /package/dist/web/assets/{github-dark-Bf7zdp5s.js → github-dark-67KKlMdB.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DqsBBV11.js → github-dark-default-tja8pidR.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-X2gqRz6w.js → github-dark-dimmed-CtjhYQtx.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-Z7tvbGX4.js → github-dark-high-contrast-Bf-LPFgn.js} +0 -0
- /package/dist/web/assets/{github-light-BKxt-7zZ.js → github-light-CbJxE7n3.js} +0 -0
- /package/dist/web/assets/{github-light-default-B0liF4d4.js → github-light-default-DRQmdHxt.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-F4encJwr.js → github-light-high-contrast-CJkDgTeM.js} +0 -0
- /package/dist/web/assets/{gleam-Pz2bHgBB.js → gleam-D4LCl2ri.js} +0 -0
- /package/dist/web/assets/{gn-CgmdNsPU.js → gn-COY8zZmC.js} +0 -0
- /package/dist/web/assets/{gnuplot-qLNF5yZ9.js → gnuplot-ROk1VitR.js} +0 -0
- /package/dist/web/assets/{go-CSDUNhE7.js → go-H30c3p0C.js} +0 -0
- /package/dist/web/assets/{groovy-B3NEnOHF.js → groovy-DBLCaelV.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DcoftiUX.js → gruvbox-dark-hard-DJy4irps.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-D1wVSuD8.js → gruvbox-dark-medium-Dhfw1QTO.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-DWpunymH.js → gruvbox-dark-soft-BncJU-8w.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-rHMvyUdS.js → gruvbox-light-hard-Cq12_YvM.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-3O2KiXO7.js → gruvbox-light-medium-DqVja5_v.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-lNlXJPwH.js → gruvbox-light-soft-IvwhveGF.js} +0 -0
- /package/dist/web/assets/{haskell-aaG3aC0h.js → haskell-DA1JV-MC.js} +0 -0
- /package/dist/web/assets/{haxe-CPcHhUDd.js → haxe-V9RTRc5E.js} +0 -0
- /package/dist/web/assets/{hcl-CMQNlR_4.js → hcl-hr1lyZ10.js} +0 -0
- /package/dist/web/assets/{hjson-DeuklPFj.js → hjson-D3vW3i7X.js} +0 -0
- /package/dist/web/assets/{hlsl-BI3hlsaM.js → hlsl-BB85gfJT.js} +0 -0
- /package/dist/web/assets/{horizon-C7-gOcjs.js → horizon-DU8OOnj8.js} +0 -0
- /package/dist/web/assets/{horizon-bright-Cxt9bRW7.js → horizon-bright-DNMUclW6.js} +0 -0
- /package/dist/web/assets/{houston-DbF2G7iD.js → houston-DAiZKf6T.js} +0 -0
- /package/dist/web/assets/{hy-DRG6Hs2I.js → hy-C6Zv0LMe.js} +0 -0
- /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-CGvlv8zR.js} +0 -0
- /package/dist/web/assets/{imba-CxKv_2TW.js → imba-Didnp6Y-.js} +0 -0
- /package/dist/web/assets/{ini-BiHsyCdO.js → ini-m0T7q4gQ.js} +0 -0
- /package/dist/web/assets/{input-D-dR8RI6.js → input-C7Q8FxeL.js} +0 -0
- /package/dist/web/assets/{java-D3RZzgDB.js → java-Buy07jGn.js} +0 -0
- /package/dist/web/assets/{javascript-4FmSqG-T.js → javascript-Cjq3CbRL.js} +0 -0
- /package/dist/web/assets/{json-CjB236_E.js → json-BSVu8IqD.js} +0 -0
- /package/dist/web/assets/{json5-bkfO-Rco.js → json5-NN_Vb7e6.js} +0 -0
- /package/dist/web/assets/{jsonc-BwaTeLyv.js → jsonc-BdyZhBUw.js} +0 -0
- /package/dist/web/assets/{jsonl-CfxNSwGd.js → jsonl-BsDSNvwi.js} +0 -0
- /package/dist/web/assets/{jsonnet-CQETCYrp.js → jsonnet-Gm963F-U.js} +0 -0
- /package/dist/web/assets/{jssm-D5o3Zzdz.js → jssm-Ds3bKWgJ.js} +0 -0
- /package/dist/web/assets/{jsx-BAMILPLo.js → jsx-BT5mW58X.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-BhGVxu3R.js → kanagawa-dragon-BvNvf7xd.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-FH4Y59ro.js → kanagawa-lotus-CuSiliSn.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-eilvP9MB.js → kanagawa-wave-C6g-d6jT.js} +0 -0
- /package/dist/web/assets/{kdl-BQ-yvXUK.js → kdl-Bx4kZef-.js} +0 -0
- /package/dist/web/assets/{kotlin-BuCx0-VT.js → kotlin-qvaVvCcm.js} +0 -0
- /package/dist/web/assets/{kusto-DAN_n_Cy.js → kusto-BPLyNCN6.js} +0 -0
- /package/dist/web/assets/{laserwave-Cq2BlxrT.js → laserwave-Sf8ZEhWP.js} +0 -0
- /package/dist/web/assets/{lean-Dhq7uXKw.js → lean-DGe6uqgk.js} +0 -0
- /package/dist/web/assets/{less-CxaMQb8j.js → less-CqM85x-9.js} +0 -0
- /package/dist/web/assets/{lib-BVwmtv2i.js → lib-CD6pOS4u.js} +0 -0
- /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-CPh1Av0r.js} +0 -0
- /package/dist/web/assets/{light-plus-B4sMAend.js → light-plus-DBtKwhFA.js} +0 -0
- /package/dist/web/assets/{llvm-DQ6-B_6Y.js → llvm-Dpbq3gky.js} +0 -0
- /package/dist/web/assets/{log-Bb_vqvu7.js → log-qKMcPm9S.js} +0 -0
- /package/dist/web/assets/{logo-BzfYv3A_.js → logo-DFo7i5KH.js} +0 -0
- /package/dist/web/assets/{luau-BnAeNR77.js → luau-DGlqWFyL.js} +0 -0
- /package/dist/web/assets/{make-B6IFuxpC.js → make-CixdvUQe.js} +0 -0
- /package/dist/web/assets/{markdown-Z0xi77Fn.js → markdown-CmRnbngE.js} +0 -0
- /package/dist/web/assets/{material-theme-1scy1SYn.js → material-theme-WDUbafB-.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-G-JykXQP.js → material-theme-darker-u8y2xpOk.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BtKkCMVq.js → material-theme-lighter-cyQZK3Tg.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-irrDC89L.js → material-theme-ocean-LBAVvw7k.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-Eq5_K4LM.js → material-theme-palenight-BJqhBxL0.js} +0 -0
- /package/dist/web/assets/{matlab-BE3oCXZC.js → matlab-B38sOJee.js} +0 -0
- /package/dist/web/assets/{mdx-DYiuFTGw.js → mdx-C8QrE_IW.js} +0 -0
- /package/dist/web/assets/{mermaid-DVlaEvtw.js → mermaid-BQ-eeU8A.js} +0 -0
- /package/dist/web/assets/{min-dark-DSkeTpQa.js → min-dark-D4gNa5tv.js} +0 -0
- /package/dist/web/assets/{min-light-CFv2Q0uF.js → min-light-Dfbhn_PL.js} +0 -0
- /package/dist/web/assets/{mipsasm-CiAFuZQ2.js → mipsasm-bLOhJkdm.js} +0 -0
- /package/dist/web/assets/{mojo-ARshcaSQ.js → mojo-B0BCOMQW.js} +0 -0
- /package/dist/web/assets/{monaco-dotenv-language-BDYnKcnr.js → monaco-dotenv-language-uB-wok6C.js} +0 -0
- /package/dist/web/assets/{monokai-CPTV-SOg.js → monokai-Bdw_07OM.js} +0 -0
- /package/dist/web/assets/{moonbit-D57B0dZr.js → moonbit-BFdGaeuk.js} +0 -0
- /package/dist/web/assets/{move-qgy3G_o9.js → move-CyXQFGPM.js} +0 -0
- /package/dist/web/assets/{narrat-CZ3rC-oc.js → narrat-9DvTD3_3.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BxxIOcI4.js → nextflow-groovy-C4VY3kNY.js} +0 -0
- /package/dist/web/assets/{night-owl-B1ryGCda.js → night-owl-feYm66V0.js} +0 -0
- /package/dist/web/assets/{night-owl-light-BZvNnvMF.js → night-owl-light-DALqFjvx.js} +0 -0
- /package/dist/web/assets/{nix-BVBQi8yv.js → nix-C1kPA9Sw.js} +0 -0
- /package/dist/web/assets/{nord-3NXw613h.js → nord-1dvrj1Al.js} +0 -0
- /package/dist/web/assets/{nushell-BMOLRalb.js → nushell-DSQ_9Uqj.js} +0 -0
- /package/dist/web/assets/{objective-c-AxruEUgh.js → objective-c-Dhcn5ZK8.js} +0 -0
- /package/dist/web/assets/{objective-cpp-cFT0qRsD.js → objective-cpp-CGVYCSrp.js} +0 -0
- /package/dist/web/assets/{ocaml-_8JH6O8J.js → ocaml-rxXW21An.js} +0 -0
- /package/dist/web/assets/{odin-nwYvR0O6.js → odin-ClSJX2DE.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-C1LaYJaH.js → one-dark-pro-3gbWDfFW.js} +0 -0
- /package/dist/web/assets/{one-light-B7rMc__c.js → one-light-C2q5ZZAw.js} +0 -0
- /package/dist/web/assets/{openscad-Cwes8bKO.js → openscad-eHXeafBl.js} +0 -0
- /package/dist/web/assets/{pascal-DMt-6tdZ.js → pascal-kKha3Txs.js} +0 -0
- /package/dist/web/assets/{pkl-QBTyYQmB.js → pkl-CTiPytg-.js} +0 -0
- /package/dist/web/assets/{plastic-BTSUp8Fk.js → plastic-D1Ygezi4.js} +0 -0
- /package/dist/web/assets/{plsql-DQv3IFXP.js → plsql-D_gbgZIr.js} +0 -0
- /package/dist/web/assets/{po-CNwZx4tW.js → po-BkhbDUnq.js} +0 -0
- /package/dist/web/assets/{poimandres-8Os7Qequ.js → poimandres-Do6NywrA.js} +0 -0
- /package/dist/web/assets/{polar-b4yK8oD8.js → polar-B3B23wca.js} +0 -0
- /package/dist/web/assets/{postcss-sFctwpxx.js → postcss-Dv_YBvWd.js} +0 -0
- /package/dist/web/assets/{powerquery-Cg_WrtuN.js → powerquery-fKJWI-Sh.js} +0 -0
- /package/dist/web/assets/{powershell-C7Ifz95h.js → powershell-B4Y7KIgx.js} +0 -0
- /package/dist/web/assets/{prisma-Z52HUJzQ.js → prisma-J--XdTXX.js} +0 -0
- /package/dist/web/assets/{prolog-DLqW-zR1.js → prolog-BBTg4Vb3.js} +0 -0
- /package/dist/web/assets/{proto-BOIzxCCJ.js → proto-DWlum3T8.js} +0 -0
- /package/dist/web/assets/{puppet-De7TjZIf.js → puppet-ZiUfc9JV.js} +0 -0
- /package/dist/web/assets/{purescript-R76ou1WT.js → purescript-CmT8SCHB.js} +0 -0
- /package/dist/web/assets/{python-Dprm0tNP.js → python-BURpj-fq.js} +0 -0
- /package/dist/web/assets/{qmldir-Clp_jpNw.js → qmldir-CpaiJywQ.js} +0 -0
- /package/dist/web/assets/{qss-eQPYYzSo.js → qss-C-zdz0i5.js} +0 -0
- /package/dist/web/assets/{r-CCcRHIf5.js → r-K56BeS-s.js} +0 -0
- /package/dist/web/assets/{racket-CzE472yp.js → racket-gkn6Z_Jw.js} +0 -0
- /package/dist/web/assets/{raku-DQwXrTSi.js → raku-C0ZBuKlx.js} +0 -0
- /package/dist/web/assets/{red-KCS6wyop.js → red-BWZ6x-3p.js} +0 -0
- /package/dist/web/assets/{reg-CcrAddUh.js → reg-Dt_EsyGR.js} +0 -0
- /package/dist/web/assets/{regexp-DDbud-Ri.js → regexp-CT6BLG2C.js} +0 -0
- /package/dist/web/assets/{rel-99GDgaHp.js → rel-MJ2LcyzJ.js} +0 -0
- /package/dist/web/assets/{resolve-theme-CW2niGV2.js → resolve-theme-B_ukrg_8.js} +0 -0
- /package/dist/web/assets/{riscv-Dm2eNv0R.js → riscv-BZYWzMAA.js} +0 -0
- /package/dist/web/assets/{ron-CzFt0ygH.js → ron-CLbwHWMn.js} +0 -0
- /package/dist/web/assets/{rose-pine-Ck6lCrCP.js → rose-pine-BPiI_daR.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-vtALRJNj.js → rose-pine-dawn-BjkJc7dW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-DPto8qXw.js → rose-pine-moon-BQSxkt7g.js} +0 -0
- /package/dist/web/assets/{rosmsg-NtS6429H.js → rosmsg-yt4151pJ.js} +0 -0
- /package/dist/web/assets/{rust-BjIXvF9h.js → rust-1dAcengL.js} +0 -0
- /package/dist/web/assets/{sass-CQ23wf5E.js → sass-DUgtQJsg.js} +0 -0
- /package/dist/web/assets/{scala-LfKLM143.js → scala-CxoGYpS0.js} +0 -0
- /package/dist/web/assets/{scheme-D3SLBXlF.js → scheme-B2K3u9e3.js} +0 -0
- /package/dist/web/assets/{sdbl-ODRd2rGN.js → sdbl-C0Ygk0n6.js} +0 -0
- /package/dist/web/assets/{shellscript-3KB1G_oA.js → shellscript-C2CvhqMk.js} +0 -0
- /package/dist/web/assets/{slack-dark-DTtyWmIy.js → slack-dark-DYdgzRGj.js} +0 -0
- /package/dist/web/assets/{slack-ochin-Bu0PApk3.js → slack-ochin-X-WD0WCg.js} +0 -0
- /package/dist/web/assets/{smalltalk-haMT-Sbe.js → smalltalk-Dp7g_zb9.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DXJzefE9.js → snazzy-light-DWHWr-a6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-B6LyYa18.js → solarized-dark-CxdLsjvb.js} +0 -0
- /package/dist/web/assets/{solarized-light-QLADebkv.js → solarized-light-Cz9rAvG5.js} +0 -0
- /package/dist/web/assets/{solidity-mzfq1aDc.js → solidity-D33gHHdn.js} +0 -0
- /package/dist/web/assets/{splunk-CyH4RWBp.js → splunk-BFbOroYw.js} +0 -0
- /package/dist/web/assets/{sql-CWVlT5D3.js → sql-ChpxvmdY.js} +0 -0
- /package/dist/web/assets/{ssh-config-DNvQJ1Qx.js → ssh-config-CUuSQ6JG.js} +0 -0
- /package/dist/web/assets/{streaming-store-uDu3PoN1.js → streaming-store-BYukdB56.js} +0 -0
- /package/dist/web/assets/{stylus-ByQQdtmf.js → stylus-DDihP0_W.js} +0 -0
- /package/dist/web/assets/{swift-D1cpY4ow.js → swift-XoalUn4x.js} +0 -0
- /package/dist/web/assets/{synthwave-84-BDF_-QiW.js → synthwave-84-DjkG0Bff.js} +0 -0
- /package/dist/web/assets/{system-verilog-BYrkMydC.js → system-verilog-f_pLKMYd.js} +0 -0
- /package/dist/web/assets/{systemd-CQm3zyK0.js → systemd-DjDcLfxp.js} +0 -0
- /package/dist/web/assets/{talonscript-DMW5CzE1.js → talonscript-BpUzYcDl.js} +0 -0
- /package/dist/web/assets/{tasl-BhFzMCr2.js → tasl-DP-cUbg8.js} +0 -0
- /package/dist/web/assets/{tcl-D7VEAF-9.js → tcl-BHoy-dfE.js} +0 -0
- /package/dist/web/assets/{terraform-DdT5A0Vy.js → terraform-vCVxN25B.js} +0 -0
- /package/dist/web/assets/{tokyo-night-WGLMM6Q9.js → tokyo-night-D0u4xZba.js} +0 -0
- /package/dist/web/assets/{toml-CZ7ipXHZ.js → toml-BS8D0__A.js} +0 -0
- /package/dist/web/assets/{tsv-B1zVvoTr.js → tsv-DCb-NQh3.js} +0 -0
- /package/dist/web/assets/{tsx-D4HwRUBq.js → tsx-DF8YeRgG.js} +0 -0
- /package/dist/web/assets/{turtle-B4r8KEJH.js → turtle-Bhx1GlIy.js} +0 -0
- /package/dist/web/assets/{typescript-BnHhxMtp.js → typescript-CAVD2Wb6.js} +0 -0
- /package/dist/web/assets/{typespec-DP2q46GT.js → typespec-D7mchdY_.js} +0 -0
- /package/dist/web/assets/{typst-DFsD_yaD.js → typst-CeXzq6Yw.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-BQ9Bv5Ga.js → use-is-mobile-TkcwP1Gx.js} +0 -0
- /package/dist/web/assets/{use-visual-viewport-CbwnktSG.js → use-visual-viewport-Lp878uzs.js} +0 -0
- /package/dist/web/assets/{v-Bji8hSVT.js → v-DhzO54je.js} +0 -0
- /package/dist/web/assets/{vala-D3TIgGmU.js → vala-Dw_RAk3E.js} +0 -0
- /package/dist/web/assets/{vb-DvcxFS2q.js → vb-CiXdNh2Y.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-BqPDsE9b.js → vendor-xterm-BHEY6Ctu.js} +0 -0
- /package/dist/web/assets/{verilog-Dy7cF35d.js → verilog-fITdxqwV.js} +0 -0
- /package/dist/web/assets/{vesper-BzjwUv9J.js → vesper-B1wm_At7.js} +0 -0
- /package/dist/web/assets/{vhdl-DuFnFNmW.js → vhdl-ebHjqKp9.js} +0 -0
- /package/dist/web/assets/{viml-yAPgKlbz.js → viml-Dgmnoxoo.js} +0 -0
- /package/dist/web/assets/{vitesse-black-oSSQdyIZ.js → vitesse-black-BZ5gMb7l.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-BZxwPihB.js → vitesse-dark-Lij7kkOt.js} +0 -0
- /package/dist/web/assets/{vitesse-light-uIigycvt.js → vitesse-light-DW6Wq7VC.js} +0 -0
- /package/dist/web/assets/{vyper-CX3NAxm5.js → vyper-6FxDnVNg.js} +0 -0
- /package/dist/web/assets/{wasm-6mEOOvVa.js → wasm-BDxaNPCj.js} +0 -0
- /package/dist/web/assets/{wasm-D9td37qO.js → wasm-DKhAtzfe.js} +0 -0
- /package/dist/web/assets/{wenyan-lwNIHgk6.js → wenyan-C0k0DYG_.js} +0 -0
- /package/dist/web/assets/{wgsl-DQAzCfog.js → wgsl-Y2zagbdn.js} +0 -0
- /package/dist/web/assets/{wikitext-DTXmt5NE.js → wikitext-C4QGWzkY.js} +0 -0
- /package/dist/web/assets/{wit-Bdw_7WDH.js → wit-CQESzjHy.js} +0 -0
- /package/dist/web/assets/{wolfram-DqyHDZ07.js → wolfram-B-ia69Ia.js} +0 -0
- /package/dist/web/assets/{yaml-CPXhdByd.js → yaml-CEYYTQPm.js} +0 -0
- /package/dist/web/assets/{zenscript-6s_i5zek.js → zenscript-BggWXIeJ.js} +0 -0
- /package/dist/web/assets/{zig-B2htezJ_.js → zig-CbaUfBQe.js} +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RustDesk's "Custom image quality" dialog: a bitrate-percentage slider, an fps slider, a
|
|
3
|
+
* "More" checkbox that raises the bitrate ceiling from 100% to 2000%, and Close.
|
|
4
|
+
*
|
|
5
|
+
* Both sliders commit on a debounce, not per tick: each commit respawns the host's ffmpeg
|
|
6
|
+
* (`-b:v`/`-r` are fixed at spawn), so a dragged slider would otherwise restart the capture
|
|
7
|
+
* dozens of times and the picture would strobe. RustDesk gets away with per-tick updates
|
|
8
|
+
* because its encoder is retunable in place; ours is not, and that difference is the reason
|
|
9
|
+
* for the delay rather than an arbitrary choice.
|
|
10
|
+
*
|
|
11
|
+
* Shown as a bottom sheet below `md` per `docs/design-guidelines.md`.
|
|
12
|
+
*/
|
|
13
|
+
import { useEffect, useRef, useState } from "react";
|
|
14
|
+
import {
|
|
15
|
+
Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle,
|
|
16
|
+
} from "@/components/ui/dialog";
|
|
17
|
+
import { Button } from "@/components/ui/button";
|
|
18
|
+
import {
|
|
19
|
+
CUSTOM_FPS_MAX, CUSTOM_FPS_MIN, CUSTOM_QUALITY_MAX_MORE_PERCENT, CUSTOM_QUALITY_MAX_PERCENT,
|
|
20
|
+
CUSTOM_QUALITY_MIN_PERCENT, clampCustomFps, clampCustomQualityPercent, customBitrateKbps,
|
|
21
|
+
} from "../../../shared/remote-desktop-custom-quality";
|
|
22
|
+
|
|
23
|
+
/** Long enough that a drag is one respawn, short enough to feel like a live control. */
|
|
24
|
+
const COMMIT_DEBOUNCE_MS = 400;
|
|
25
|
+
|
|
26
|
+
export interface RemoteDesktopCustomQualityDialogProps {
|
|
27
|
+
open: boolean;
|
|
28
|
+
onOpenChange: (open: boolean) => void;
|
|
29
|
+
percent: number;
|
|
30
|
+
fps: number;
|
|
31
|
+
more: boolean;
|
|
32
|
+
onSetMore: (more: boolean) => void;
|
|
33
|
+
/** Commit both values; also selects the custom rung. */
|
|
34
|
+
onCommit: (percent: number, fps: number) => void;
|
|
35
|
+
/** The capture's own size, so the dialog can say what the percentage really costs. */
|
|
36
|
+
frameSize: { width: number; height: number };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function RemoteDesktopCustomQualityDialog({
|
|
40
|
+
open, onOpenChange, percent, fps, more, onSetMore, onCommit, frameSize,
|
|
41
|
+
}: RemoteDesktopCustomQualityDialogProps) {
|
|
42
|
+
const [localPercent, setLocalPercent] = useState(percent);
|
|
43
|
+
const [localFps, setLocalFps] = useState(fps);
|
|
44
|
+
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
45
|
+
|
|
46
|
+
// Adopt the stored values whenever the dialog is opened, so it never shows a stale drag.
|
|
47
|
+
useEffect(() => { if (open) { setLocalPercent(percent); setLocalFps(fps); } }, [open, percent, fps]);
|
|
48
|
+
useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);
|
|
49
|
+
|
|
50
|
+
const max = more ? CUSTOM_QUALITY_MAX_MORE_PERCENT : CUSTOM_QUALITY_MAX_PERCENT;
|
|
51
|
+
|
|
52
|
+
const commitLater = (nextPercent: number, nextFps: number) => {
|
|
53
|
+
if (timer.current) clearTimeout(timer.current);
|
|
54
|
+
timer.current = setTimeout(() => onCommit(nextPercent, nextFps), COMMIT_DEBOUNCE_MS);
|
|
55
|
+
};
|
|
56
|
+
const changePercent = (raw: number) => {
|
|
57
|
+
const next = clampCustomQualityPercent(raw, more);
|
|
58
|
+
setLocalPercent(next);
|
|
59
|
+
commitLater(next, localFps);
|
|
60
|
+
};
|
|
61
|
+
const changeFps = (raw: number) => {
|
|
62
|
+
const next = clampCustomFps(raw);
|
|
63
|
+
setLocalFps(next);
|
|
64
|
+
commitLater(localPercent, next);
|
|
65
|
+
};
|
|
66
|
+
const toggleMore = (next: boolean) => {
|
|
67
|
+
onSetMore(next);
|
|
68
|
+
// Turning it off strands a percent above the new ceiling; commit the clamped value so the
|
|
69
|
+
// host is never left encoding at a rate the dialog no longer shows.
|
|
70
|
+
const clamped = clampCustomQualityPercent(localPercent, next);
|
|
71
|
+
if (clamped !== localPercent) { setLocalPercent(clamped); commitLater(clamped, localFps); }
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const kbps = customBitrateKbps(localPercent, frameSize.width, frameSize.height);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
78
|
+
<DialogContent className="sm:max-w-md" data-testid="remote-desktop-custom-quality-dialog">
|
|
79
|
+
<DialogHeader>
|
|
80
|
+
<DialogTitle>Custom image quality</DialogTitle>
|
|
81
|
+
<DialogDescription>
|
|
82
|
+
{/* RustDesk shows the bare percentage. The real figure is worth showing because the
|
|
83
|
+
percentage is not a ratio — 50% is the resolution's *base* bitrate, not half of
|
|
84
|
+
anything — so the number alone reads as "half quality" and is not. */}
|
|
85
|
+
{frameSize.width > 0
|
|
86
|
+
? `${localPercent}% of the base rate for ${frameSize.width}×${frameSize.height} — about ${(kbps / 1000).toFixed(1)} Mbit/s`
|
|
87
|
+
: "Waiting for the first frame"}
|
|
88
|
+
</DialogDescription>
|
|
89
|
+
</DialogHeader>
|
|
90
|
+
|
|
91
|
+
<div className="space-y-4 py-2">
|
|
92
|
+
<div className="flex items-center gap-3">
|
|
93
|
+
<input
|
|
94
|
+
type="range"
|
|
95
|
+
min={CUSTOM_QUALITY_MIN_PERCENT}
|
|
96
|
+
max={max}
|
|
97
|
+
step={1}
|
|
98
|
+
value={Math.min(localPercent, max)}
|
|
99
|
+
onChange={(e) => changePercent(Number(e.target.value))}
|
|
100
|
+
aria-label="Bitrate"
|
|
101
|
+
data-testid="remote-desktop-custom-bitrate"
|
|
102
|
+
className="h-1 min-w-0 flex-1 cursor-pointer appearance-none rounded bg-accent accent-primary"
|
|
103
|
+
/>
|
|
104
|
+
<span className="w-14 text-right text-xs tabular-nums">{localPercent}%</span>
|
|
105
|
+
<span className="w-14 text-xs text-muted-foreground">Bitrate</span>
|
|
106
|
+
<label className="flex shrink-0 items-center gap-1.5 text-xs">
|
|
107
|
+
<input
|
|
108
|
+
type="checkbox"
|
|
109
|
+
checked={more}
|
|
110
|
+
onChange={(e) => toggleMore(e.target.checked)}
|
|
111
|
+
data-testid="remote-desktop-custom-more"
|
|
112
|
+
className="size-3.5 accent-primary"
|
|
113
|
+
/>
|
|
114
|
+
More
|
|
115
|
+
</label>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="flex items-center gap-3">
|
|
119
|
+
<input
|
|
120
|
+
type="range"
|
|
121
|
+
min={CUSTOM_FPS_MIN}
|
|
122
|
+
max={CUSTOM_FPS_MAX}
|
|
123
|
+
step={1}
|
|
124
|
+
value={localFps}
|
|
125
|
+
onChange={(e) => changeFps(Number(e.target.value))}
|
|
126
|
+
aria-label="FPS"
|
|
127
|
+
data-testid="remote-desktop-custom-fps"
|
|
128
|
+
className="h-1 min-w-0 flex-1 cursor-pointer appearance-none rounded bg-accent accent-primary"
|
|
129
|
+
/>
|
|
130
|
+
<span className="w-14 text-right text-xs tabular-nums">{localFps}</span>
|
|
131
|
+
<span className="w-14 text-xs text-muted-foreground">FPS</span>
|
|
132
|
+
{/* Keeps the two rows' sliders the same length as the row above, which carries an
|
|
133
|
+
extra checkbox. */}
|
|
134
|
+
<span className="w-[4.25rem] shrink-0" aria-hidden />
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
<DialogFooter>
|
|
139
|
+
<Button onClick={() => onOpenChange(false)}>Close</Button>
|
|
140
|
+
</DialogFooter>
|
|
141
|
+
</DialogContent>
|
|
142
|
+
</Dialog>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -14,8 +14,18 @@
|
|
|
14
14
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
15
|
import { cn } from "@/lib/utils";
|
|
16
16
|
|
|
17
|
+
/** Ctrl/Cmd + C or X, by `code` rather than by button label so renaming a button cannot
|
|
18
|
+
* silently stop the host clipboard being fetched. */
|
|
19
|
+
function isCopyChord(codes: readonly string[]): boolean {
|
|
20
|
+
const mod = codes.includes("ControlLeft") || codes.includes("MetaLeft");
|
|
21
|
+
return mod && (codes.includes("KeyC") || codes.includes("KeyX"));
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
export interface RemoteDesktopMobileKeyBarProps {
|
|
18
25
|
sendMessage: (msg: Record<string, unknown>) => void;
|
|
26
|
+
/** Fired after a copy/cut combo — the touch equivalent of the desktop's Ctrl+C cue, since
|
|
27
|
+
* none of these buttons goes through `use-remote-input-capture`'s key handler. */
|
|
28
|
+
onCopyCombo?: () => void;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
const MODIFIERS = [
|
|
@@ -73,7 +83,7 @@ function KeyBarButton({
|
|
|
73
83
|
);
|
|
74
84
|
}
|
|
75
85
|
|
|
76
|
-
export function RemoteDesktopMobileKeyBar({ sendMessage }: RemoteDesktopMobileKeyBarProps) {
|
|
86
|
+
export function RemoteDesktopMobileKeyBar({ sendMessage, onCopyCombo }: RemoteDesktopMobileKeyBarProps) {
|
|
77
87
|
const [held, setHeld] = useState<ReadonlySet<string>>(new Set());
|
|
78
88
|
const heldRef = useRef(held);
|
|
79
89
|
heldRef.current = held;
|
|
@@ -106,7 +116,8 @@ export function RemoteDesktopMobileKeyBar({ sendMessage }: RemoteDesktopMobileKe
|
|
|
106
116
|
// chord (last key down first key up would read as a different shortcut on some apps).
|
|
107
117
|
codes.forEach((code) => sendMessage({ type: "key", code, down: true }));
|
|
108
118
|
[...codes].reverse().forEach((code) => sendMessage({ type: "key", code, down: false }));
|
|
109
|
-
|
|
119
|
+
if (isCopyChord(codes)) onCopyCombo?.();
|
|
120
|
+
}, [sendMessage, onCopyCombo]);
|
|
110
121
|
|
|
111
122
|
return (
|
|
112
123
|
<div
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mobile viewer's settings panel — the toggles the desktop keeps in its `Settings2`
|
|
3
|
+
* dropdown, which has no room in a thumb-zone row of 44px buttons.
|
|
4
|
+
*
|
|
5
|
+
* Rendered *in flow* inside the bottom bar rather than through `mobile-bottom-sheet`, exactly
|
|
6
|
+
* like `remote-desktop-mobile-key-bar.tsx`: the viewer already lives in its own full-viewport
|
|
7
|
+
* portal, so a second portal would have to win a z-index argument with it, and a panel in the
|
|
8
|
+
* bottom bar rides that bar's `keyboardInset` lift for free.
|
|
9
|
+
*
|
|
10
|
+
* Scale modes are deliberately absent: pinch-zoom and the toolbar's Reset zoom already own the
|
|
11
|
+
* transform here, and they do it continuously rather than in three fixed steps.
|
|
12
|
+
*/
|
|
13
|
+
import { useMemo } from "react";
|
|
14
|
+
import { Circle, Clipboard, EyeOff, Gauge, Image, MousePointer2, Square, Volume2 } from "@/lib/icons";
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
import { fitLocalMode, localScreenSize, resolutionChoices } from "./remote-desktop-resolution-list";
|
|
17
|
+
import type { HostMode } from "./use-remote-desktop-readiness";
|
|
18
|
+
|
|
19
|
+
export interface RemoteDesktopMobileSettingsProps {
|
|
20
|
+
statsVisible: boolean;
|
|
21
|
+
onToggleStats: () => void;
|
|
22
|
+
showCursor: boolean;
|
|
23
|
+
onSetShowCursor: (show: boolean) => void;
|
|
24
|
+
clipboardEnabled: boolean;
|
|
25
|
+
onSetClipboardEnabled: (enabled: boolean) => void;
|
|
26
|
+
audioOn: boolean;
|
|
27
|
+
/** Why audio cannot be turned on, or null when it can. */
|
|
28
|
+
audioReason: string | null;
|
|
29
|
+
onSetAudioOn: (on: boolean) => void;
|
|
30
|
+
privacyOn: boolean;
|
|
31
|
+
privacyReason: string | null;
|
|
32
|
+
onSetPrivacyOn: (on: boolean) => void;
|
|
33
|
+
/** The host's own display modes, collapsed to one row per size by the panel. Empty on a host
|
|
34
|
+
* whose resolution cannot be changed, which renders no section at all. */
|
|
35
|
+
hostModes: HostMode[];
|
|
36
|
+
hostModeId: string | null;
|
|
37
|
+
/** The mode the host was on before this session changed it, for the "Original" chip. */
|
|
38
|
+
hostOriginalModeId: string | null;
|
|
39
|
+
onSetHostMode: (modeId: string) => void;
|
|
40
|
+
/** Input injection available. Resizing the host is host control, so a view-only session must
|
|
41
|
+
* not offer it — RustDesk gates the same submenu on `ffiModel.keyboard`. */
|
|
42
|
+
canResizeHost: boolean;
|
|
43
|
+
resolutionError: string | null;
|
|
44
|
+
recording: boolean;
|
|
45
|
+
recordingSupported: boolean;
|
|
46
|
+
onToggleRecording: () => void;
|
|
47
|
+
onScreenshot: () => void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function SettingRow({
|
|
51
|
+
icon, label, hint, checked, onToggle, testId, disabled,
|
|
52
|
+
}: {
|
|
53
|
+
icon: React.ReactNode;
|
|
54
|
+
label: string;
|
|
55
|
+
hint?: string;
|
|
56
|
+
checked: boolean;
|
|
57
|
+
onToggle: () => void;
|
|
58
|
+
testId: string;
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
}) {
|
|
61
|
+
return (
|
|
62
|
+
<button
|
|
63
|
+
type="button"
|
|
64
|
+
role="switch"
|
|
65
|
+
aria-checked={checked}
|
|
66
|
+
disabled={disabled}
|
|
67
|
+
onClick={onToggle}
|
|
68
|
+
data-testid={testId}
|
|
69
|
+
className="flex min-h-11 w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-white/80 active:bg-white/10 disabled:opacity-50"
|
|
70
|
+
>
|
|
71
|
+
<span className="shrink-0 opacity-80">{icon}</span>
|
|
72
|
+
<span className="min-w-0 flex-1">
|
|
73
|
+
<span className="block truncate text-xs">{label}</span>
|
|
74
|
+
{hint && <span className="block truncate text-[10px] text-white/45">{hint}</span>}
|
|
75
|
+
</span>
|
|
76
|
+
{/* A drawn switch rather than the `ui/switch` primitive: this panel sits on black inside
|
|
77
|
+
the viewer's own portal, where the themed component's surface colours disappear. */}
|
|
78
|
+
<span
|
|
79
|
+
className={cn(
|
|
80
|
+
"relative h-5 w-9 shrink-0 rounded-full transition-colors",
|
|
81
|
+
checked ? "bg-primary" : "bg-white/20",
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
<span
|
|
85
|
+
className={cn(
|
|
86
|
+
"absolute top-0.5 size-4 rounded-full bg-white transition-all",
|
|
87
|
+
checked ? "left-[1.125rem]" : "left-0.5",
|
|
88
|
+
)}
|
|
89
|
+
/>
|
|
90
|
+
</span>
|
|
91
|
+
</button>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function RemoteDesktopMobileSettings({
|
|
96
|
+
statsVisible, onToggleStats, showCursor, onSetShowCursor,
|
|
97
|
+
clipboardEnabled, onSetClipboardEnabled,
|
|
98
|
+
audioOn, audioReason, onSetAudioOn,
|
|
99
|
+
privacyOn, privacyReason, onSetPrivacyOn,
|
|
100
|
+
hostModes, hostModeId, hostOriginalModeId, onSetHostMode, canResizeHost, resolutionError,
|
|
101
|
+
recording, recordingSupported, onToggleRecording, onScreenshot,
|
|
102
|
+
}: RemoteDesktopMobileSettingsProps) {
|
|
103
|
+
const resolutions = useMemo(() => resolutionChoices(hostModes), [hostModes]);
|
|
104
|
+
const localScreen = useMemo(() => localScreenSize(), []);
|
|
105
|
+
const fitLocal = useMemo(
|
|
106
|
+
() => fitLocalMode(hostModes, localScreen, hostModeId),
|
|
107
|
+
[hostModes, localScreen, hostModeId],
|
|
108
|
+
);
|
|
109
|
+
return (
|
|
110
|
+
<div
|
|
111
|
+
className="mx-2 mb-1 space-y-0.5 rounded-xl border border-white/10 bg-black/90 p-1"
|
|
112
|
+
data-testid="remote-desktop-mobile-settings"
|
|
113
|
+
>
|
|
114
|
+
<SettingRow
|
|
115
|
+
icon={<Gauge className="size-4" />}
|
|
116
|
+
label="Quality monitor"
|
|
117
|
+
checked={statsVisible}
|
|
118
|
+
onToggle={onToggleStats}
|
|
119
|
+
testId="remote-desktop-mobile-stats-toggle"
|
|
120
|
+
/>
|
|
121
|
+
<SettingRow
|
|
122
|
+
icon={<MousePointer2 className="size-4" />}
|
|
123
|
+
label="Show remote cursor"
|
|
124
|
+
hint="Restarts the stream"
|
|
125
|
+
checked={showCursor}
|
|
126
|
+
onToggle={() => onSetShowCursor(!showCursor)}
|
|
127
|
+
testId="remote-desktop-mobile-cursor-toggle"
|
|
128
|
+
/>
|
|
129
|
+
<SettingRow
|
|
130
|
+
icon={<Clipboard className="size-4" />}
|
|
131
|
+
label="Sync clipboard"
|
|
132
|
+
hint="Off: Ctrl+V pastes the host's own clipboard"
|
|
133
|
+
checked={clipboardEnabled}
|
|
134
|
+
onToggle={() => onSetClipboardEnabled(!clipboardEnabled)}
|
|
135
|
+
testId="remote-desktop-mobile-clipboard-toggle"
|
|
136
|
+
/>
|
|
137
|
+
{/* The tap on this row is the user gesture the autoplay policy needs to resume the
|
|
138
|
+
`AudioContext`, which is why audio can only ever be started from a control. */}
|
|
139
|
+
<SettingRow
|
|
140
|
+
icon={<Volume2 className="size-4" />}
|
|
141
|
+
label="Play host audio"
|
|
142
|
+
hint={audioReason ?? undefined}
|
|
143
|
+
checked={audioOn}
|
|
144
|
+
disabled={audioReason !== null}
|
|
145
|
+
onToggle={() => onSetAudioOn(!audioOn)}
|
|
146
|
+
testId="remote-desktop-mobile-audio-toggle"
|
|
147
|
+
/>
|
|
148
|
+
<SettingRow
|
|
149
|
+
icon={<EyeOff className="size-4" />}
|
|
150
|
+
label="Privacy mode"
|
|
151
|
+
hint={privacyReason ?? "Blocks the host's own keyboard and mouse"}
|
|
152
|
+
checked={privacyOn}
|
|
153
|
+
disabled={privacyReason !== null && !privacyOn}
|
|
154
|
+
onToggle={() => onSetPrivacyOn(!privacyOn)}
|
|
155
|
+
testId="remote-desktop-mobile-privacy-toggle"
|
|
156
|
+
/>
|
|
157
|
+
{/* A scrolling list of chips rather than a select: a native `<select>` on a phone opens
|
|
158
|
+
the OS picker *over* the viewer's own portal, and this panel is inside it. */}
|
|
159
|
+
{canResizeHost && resolutions.length > 1 && (
|
|
160
|
+
<div className="px-3 pt-1">
|
|
161
|
+
<div className="pb-1.5 text-[10px] uppercase tracking-wide text-white/40">Resolution</div>
|
|
162
|
+
<div className="flex max-h-28 flex-wrap gap-1.5 overflow-y-auto">
|
|
163
|
+
{hostOriginalModeId && hostOriginalModeId !== hostModeId && (
|
|
164
|
+
<button
|
|
165
|
+
type="button"
|
|
166
|
+
onClick={() => onSetHostMode(hostOriginalModeId)}
|
|
167
|
+
data-testid="remote-desktop-mobile-resolution-original"
|
|
168
|
+
className="min-h-9 rounded-md bg-white/10 px-2.5 py-1 text-[11px] text-white/70 active:bg-white/20"
|
|
169
|
+
>
|
|
170
|
+
Original
|
|
171
|
+
</button>
|
|
172
|
+
)}
|
|
173
|
+
{/* A phone's screen is almost never one of the host's advertised sizes, so this chip
|
|
174
|
+
is normally absent — the same as RustDesk on the same pair. */}
|
|
175
|
+
{fitLocal && (
|
|
176
|
+
<button
|
|
177
|
+
type="button"
|
|
178
|
+
onClick={() => onSetHostMode(fitLocal.id)}
|
|
179
|
+
data-testid="remote-desktop-mobile-resolution-fit-local"
|
|
180
|
+
className="min-h-9 rounded-md bg-white/10 px-2.5 py-1 text-[11px] text-white/70 active:bg-white/20"
|
|
181
|
+
>
|
|
182
|
+
Fit local
|
|
183
|
+
</button>
|
|
184
|
+
)}
|
|
185
|
+
{resolutions.map((r) => {
|
|
186
|
+
const active = (hostModeId ?? resolutions.find((c) => c.current)?.id) === r.id;
|
|
187
|
+
return (
|
|
188
|
+
<button
|
|
189
|
+
key={r.id}
|
|
190
|
+
type="button"
|
|
191
|
+
onClick={() => onSetHostMode(r.id)}
|
|
192
|
+
data-testid={`remote-desktop-mobile-resolution-${r.width}x${r.height}`}
|
|
193
|
+
className={cn(
|
|
194
|
+
"min-h-9 rounded-md px-2.5 py-1 text-[11px] tabular-nums",
|
|
195
|
+
active ? "bg-white/25 text-white" : "bg-white/10 text-white/70 active:bg-white/20",
|
|
196
|
+
)}
|
|
197
|
+
>
|
|
198
|
+
{r.width}×{r.height}
|
|
199
|
+
</button>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
</div>
|
|
203
|
+
{resolutionError && (
|
|
204
|
+
<div className="pt-1.5 text-[10px] leading-tight text-red-400">{resolutionError}</div>
|
|
205
|
+
)}
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
{/* Actions, not switches — a plain row each, so the drawn switch is never shown for
|
|
209
|
+
something that does not stay on. */}
|
|
210
|
+
<button
|
|
211
|
+
type="button"
|
|
212
|
+
onClick={onScreenshot}
|
|
213
|
+
data-testid="remote-desktop-mobile-screenshot"
|
|
214
|
+
className="flex min-h-11 w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-xs text-white/80 active:bg-white/10"
|
|
215
|
+
>
|
|
216
|
+
<Image className="size-4 opacity-80" /> Save screenshot
|
|
217
|
+
</button>
|
|
218
|
+
{recordingSupported && (
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
onClick={onToggleRecording}
|
|
222
|
+
data-testid="remote-desktop-mobile-record"
|
|
223
|
+
className="flex min-h-11 w-full items-center gap-3 rounded-lg px-3 py-2 text-left text-xs text-white/80 active:bg-white/10"
|
|
224
|
+
>
|
|
225
|
+
{recording
|
|
226
|
+
? <><Square className="size-4 text-red-500" /> Stop recording</>
|
|
227
|
+
: <><Circle className="size-4 opacity-80" /> Start recording</>}
|
|
228
|
+
</button>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bottom (thumb-zone) toolbar for the mobile remote-desktop viewer: Touch/Mouse mode toggle,
|
|
3
3
|
* virtual keyboard, zoom reset, close. Every button is a 44px+ touch target per
|
|
4
|
-
* `docs/design-guidelines.md`'s Mobile-First UI Rules
|
|
4
|
+
* `docs/design-guidelines.md`'s Mobile-First UI Rules — which is also why the three on/off
|
|
5
|
+
* settings live behind the More button in `remote-desktop-mobile-settings.tsx` instead of as
|
|
6
|
+
* three more buttons here: the row is `flex-1` per button, and at eight of them each target
|
|
7
|
+
* falls under 44px on a small phone.
|
|
5
8
|
*/
|
|
6
|
-
import { Hand, MousePointer2, Keyboard, ZoomOut,
|
|
9
|
+
import { Hand, MousePointer2, Keyboard, ZoomOut, Settings2, Monitor, Wifi, X } from "@/lib/icons";
|
|
7
10
|
import { cn } from "@/lib/utils";
|
|
8
|
-
import { useSettingsStore } from "@/stores/settings-store";
|
|
9
11
|
import type { RemoteDesktopInputMode } from "./use-remote-desktop-touch";
|
|
10
12
|
|
|
11
13
|
export interface RemoteDesktopMobileToolbarProps {
|
|
@@ -13,10 +15,17 @@ export interface RemoteDesktopMobileToolbarProps {
|
|
|
13
15
|
displayLabel: string | null;
|
|
14
16
|
/** Cycle to the next display — one tap per hop beats a dropdown in the thumb zone. */
|
|
15
17
|
onNextDisplay: () => void;
|
|
18
|
+
/** Short name of the rung being streamed ("Auto", "480p"…) for the quality button's label. */
|
|
19
|
+
qualityLabel: string;
|
|
20
|
+
/** Cycle auto → tiny → low → balanced → high → auto, same one-tap-per-hop reasoning. */
|
|
21
|
+
onNextQuality: () => void;
|
|
16
22
|
mode: RemoteDesktopInputMode;
|
|
17
23
|
onToggleMode: () => void;
|
|
18
24
|
onOpenKeyboard: () => void;
|
|
19
25
|
onResetZoom: () => void;
|
|
26
|
+
/** Show/hide the settings panel above this row (stats, remote cursor, clipboard sync). */
|
|
27
|
+
settingsOpen: boolean;
|
|
28
|
+
onToggleSettings: () => void;
|
|
20
29
|
onClose: () => void;
|
|
21
30
|
}
|
|
22
31
|
|
|
@@ -51,15 +60,16 @@ function ToolbarButton({
|
|
|
51
60
|
export function RemoteDesktopMobileToolbar({
|
|
52
61
|
displayLabel,
|
|
53
62
|
onNextDisplay,
|
|
63
|
+
qualityLabel,
|
|
64
|
+
onNextQuality,
|
|
54
65
|
mode,
|
|
55
66
|
onToggleMode,
|
|
56
67
|
onOpenKeyboard,
|
|
57
68
|
onResetZoom,
|
|
69
|
+
settingsOpen,
|
|
70
|
+
onToggleSettings,
|
|
58
71
|
onClose,
|
|
59
72
|
}: RemoteDesktopMobileToolbarProps) {
|
|
60
|
-
const statsVisible = useSettingsStore((s) => s.remoteDesktopStatsVisible);
|
|
61
|
-
const toggleStats = useSettingsStore((s) => s.toggleRemoteDesktopStatsVisible);
|
|
62
|
-
|
|
63
73
|
return (
|
|
64
74
|
<div
|
|
65
75
|
className="flex shrink-0 items-stretch gap-1 border-t border-white/10 bg-black/90 px-2 pb-[max(0.375rem,env(safe-area-inset-bottom))] pt-1.5"
|
|
@@ -79,8 +89,11 @@ export function RemoteDesktopMobileToolbar({
|
|
|
79
89
|
<Monitor className="size-5" />
|
|
80
90
|
</ToolbarButton>
|
|
81
91
|
)}
|
|
82
|
-
<ToolbarButton onClick={
|
|
83
|
-
<
|
|
92
|
+
<ToolbarButton onClick={onNextQuality} label={qualityLabel}>
|
|
93
|
+
<Wifi className="size-5" />
|
|
94
|
+
</ToolbarButton>
|
|
95
|
+
<ToolbarButton onClick={onToggleSettings} label="More" active={settingsOpen}>
|
|
96
|
+
<Settings2 className="size-5" />
|
|
84
97
|
</ToolbarButton>
|
|
85
98
|
<ToolbarButton onClick={onClose} label="Close">
|
|
86
99
|
<X className="size-5" />
|
|
@@ -9,14 +9,23 @@
|
|
|
9
9
|
import { useRef, useState, useCallback } from "react";
|
|
10
10
|
import { RotateCw, MonitorX } from "@/lib/icons";
|
|
11
11
|
import { useVisualViewport } from "@/hooks/use-visual-viewport";
|
|
12
|
+
import { useSettingsStore } from "@/stores/settings-store";
|
|
12
13
|
import { useRemoteDesktopConnection } from "./use-remote-desktop-connection";
|
|
13
14
|
import { useRemoteDesktopTouch, type RemoteDesktopInputMode } from "./use-remote-desktop-touch";
|
|
14
15
|
import { useRemoteDesktopVirtualKeyboard } from "./use-remote-desktop-virtual-keyboard";
|
|
15
16
|
import { RemoteDesktopMobileToolbar } from "./remote-desktop-mobile-toolbar";
|
|
17
|
+
import {
|
|
18
|
+
nextQualityChoice, shortQualityLabel, type QualityChoice,
|
|
19
|
+
} from "../../../shared/remote-desktop-quality";
|
|
16
20
|
import { RemoteDesktopMobileKeyBar } from "./remote-desktop-mobile-key-bar";
|
|
21
|
+
import { RemoteDesktopMobileSettings } from "./remote-desktop-mobile-settings";
|
|
22
|
+
import { useRemoteDesktopRecorder } from "./use-remote-desktop-recorder";
|
|
23
|
+
import { saveCanvasScreenshot } from "./remote-desktop-recording";
|
|
17
24
|
import { RemoteDesktopStatsOverlay } from "./remote-desktop-stats-overlay";
|
|
18
25
|
import { letterboxedContentRect } from "./remote-desktop-coords";
|
|
19
26
|
import { useRemoteDesktopDisplayChoice } from "./use-remote-desktop-display-choice";
|
|
27
|
+
import { RemoteDesktopClipboardNotice } from "./remote-desktop-clipboard-notice";
|
|
28
|
+
import { useRemoteDesktopReadiness } from "./use-remote-desktop-readiness";
|
|
20
29
|
|
|
21
30
|
export interface RemoteDesktopMobileViewProps {
|
|
22
31
|
onClose: () => void;
|
|
@@ -27,12 +36,21 @@ export default function RemoteDesktopMobileView({ onClose }: RemoteDesktopMobile
|
|
|
27
36
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
28
37
|
const [mode, setMode] = useState<RemoteDesktopInputMode>("mouse");
|
|
29
38
|
const [keyBarOpen, setKeyBarOpen] = useState(false);
|
|
39
|
+
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
30
40
|
const display = useRemoteDesktopDisplayChoice();
|
|
31
41
|
|
|
32
42
|
const {
|
|
33
43
|
connState, errorMessage, decoderStatus, decoderErrorMessage, sendMessage, reconnect,
|
|
34
|
-
getTotalBytes, getFrameCount,
|
|
44
|
+
getTotalBytes, getFrameCount, quality, setQuality, showCursor, setShowCursor,
|
|
45
|
+
audioOn, setAudioOn, getAudioTracks, privacyOn, privacyError, setPrivacyOn,
|
|
46
|
+
hostModeId, hostOriginalModeId, resolutionError, setHostMode,
|
|
47
|
+
pendingHostClipboard, clearHostClipboard, sendClipboard, requestHostClipboard,
|
|
35
48
|
} = useRemoteDesktopConnection(canvasRef, { displayId: display.displayId });
|
|
49
|
+
const statsVisible = useSettingsStore((s) => s.remoteDesktopStatsVisible);
|
|
50
|
+
const toggleStats = useSettingsStore((s) => s.toggleRemoteDesktopStatsVisible);
|
|
51
|
+
const clipboardSync = useSettingsStore((s) => s.remoteDesktopClipboardSync);
|
|
52
|
+
const setClipboardSync = useSettingsStore((s) => s.setRemoteDesktopClipboardSync);
|
|
53
|
+
const qualityChoice = useSettingsStore((st) => st.remoteDesktopQuality);
|
|
36
54
|
const streaming = connState === "streaming";
|
|
37
55
|
|
|
38
56
|
const { transform, virtualCursor, resetZoom } = useRemoteDesktopTouch({
|
|
@@ -42,7 +60,24 @@ export default function RemoteDesktopMobileView({ onClose }: RemoteDesktopMobile
|
|
|
42
60
|
sendMessage,
|
|
43
61
|
enabled: streaming,
|
|
44
62
|
});
|
|
45
|
-
|
|
63
|
+
// A phone paste goes through the *host clipboard* rather than being typed character by
|
|
64
|
+
// character: typing is subject to the host's IME (a Telex host rewrites "World" as "ửold"),
|
|
65
|
+
// and a pasted URL or token is exactly the text that must survive verbatim. The server falls
|
|
66
|
+
// back to typing by itself when the host has no clipboard tool.
|
|
67
|
+
const [pasteAttempted, setPasteAttempted] = useState(false);
|
|
68
|
+
// Undefined with sync off, which is how the toggle is implemented on this surface: the
|
|
69
|
+
// virtual keyboard then lets the `paste` event's own default action stand (i.e. nothing
|
|
70
|
+
// leaves the device), and the key bar's Ctrl+V button still pastes the *host's* clipboard.
|
|
71
|
+
const onPasteText = useCallback((text: string) => {
|
|
72
|
+
setPasteAttempted(true);
|
|
73
|
+
sendClipboard(text, true);
|
|
74
|
+
}, [sendClipboard]);
|
|
75
|
+
// One fetch, no polling — read only to explain a paste that had to fall back to typing, and
|
|
76
|
+
// an audio toggle the host cannot honour.
|
|
77
|
+
const { caps } = useRemoteDesktopReadiness(false);
|
|
78
|
+
const recorder = useRemoteDesktopRecorder(canvasRef, getAudioTracks);
|
|
79
|
+
const { inputRef: keyboardInputRef, show: showKeyboard } =
|
|
80
|
+
useRemoteDesktopVirtualKeyboard(sendMessage, streaming, clipboardSync ? onPasteText : undefined);
|
|
46
81
|
|
|
47
82
|
// Lifts the toolbar/key-bar row above the on-screen keyboard instead of the whole sheet
|
|
48
83
|
// shrinking to make room for it (that was the previous, since-reverted behavior — see
|
|
@@ -169,14 +204,58 @@ export default function RemoteDesktopMobileView({ onClose }: RemoteDesktopMobile
|
|
|
169
204
|
style={keyboardInset > 0 ? { transform: `translateY(-${keyboardInset}px)` } : undefined}
|
|
170
205
|
data-testid="remote-desktop-mobile-bottom-bar"
|
|
171
206
|
>
|
|
172
|
-
{
|
|
207
|
+
{/* In flow above the key bar so it rides the same `keyboardInset` lift and is never
|
|
208
|
+
hidden behind the toolbar. */}
|
|
209
|
+
<RemoteDesktopClipboardNotice
|
|
210
|
+
pendingText={pendingHostClipboard}
|
|
211
|
+
onDismiss={clearHostClipboard}
|
|
212
|
+
missingToolAction={pasteAttempted && caps && !caps.clipboard.available ? caps.clipboard.action : null}
|
|
213
|
+
onDismissMissingTool={() => setPasteAttempted(false)}
|
|
214
|
+
positionClassName="mx-2 mb-1 max-w-[calc(100%-1rem)]"
|
|
215
|
+
/>
|
|
216
|
+
{settingsOpen && (
|
|
217
|
+
<RemoteDesktopMobileSettings
|
|
218
|
+
statsVisible={statsVisible}
|
|
219
|
+
onToggleStats={toggleStats}
|
|
220
|
+
showCursor={showCursor}
|
|
221
|
+
onSetShowCursor={setShowCursor}
|
|
222
|
+
clipboardEnabled={clipboardSync}
|
|
223
|
+
onSetClipboardEnabled={setClipboardSync}
|
|
224
|
+
audioOn={audioOn}
|
|
225
|
+
audioReason={caps && !caps.audio.available ? caps.audio.reason : null}
|
|
226
|
+
onSetAudioOn={setAudioOn}
|
|
227
|
+
privacyOn={privacyOn}
|
|
228
|
+
privacyReason={privacyError ?? (caps && !caps.privacy.available ? caps.privacy.reason : null)}
|
|
229
|
+
hostModes={caps?.resolutions?.modes ?? []}
|
|
230
|
+
hostModeId={hostModeId}
|
|
231
|
+
hostOriginalModeId={hostOriginalModeId}
|
|
232
|
+
onSetHostMode={setHostMode}
|
|
233
|
+
canResizeHost={caps?.inputReady ?? false}
|
|
234
|
+
resolutionError={resolutionError}
|
|
235
|
+
onSetPrivacyOn={setPrivacyOn}
|
|
236
|
+
recording={recorder.recording}
|
|
237
|
+
recordingSupported={recorder.supported}
|
|
238
|
+
onToggleRecording={recorder.toggle}
|
|
239
|
+
onScreenshot={() => void saveCanvasScreenshot(canvasRef.current)}
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
242
|
+
{keyBarOpen && (
|
|
243
|
+
<RemoteDesktopMobileKeyBar
|
|
244
|
+
sendMessage={sendMessage}
|
|
245
|
+
onCopyCombo={clipboardSync ? requestHostClipboard : undefined}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
173
248
|
<RemoteDesktopMobileToolbar
|
|
174
249
|
displayLabel={display.displays.length > 1 ? display.current?.label ?? null : null}
|
|
175
250
|
onNextDisplay={display.next}
|
|
251
|
+
qualityLabel={shortQualityLabel(qualityChoice)}
|
|
252
|
+
onNextQuality={() => setQuality(nextQualityChoice(qualityChoice))}
|
|
176
253
|
mode={mode}
|
|
177
254
|
onToggleMode={toggleMode}
|
|
178
255
|
onOpenKeyboard={openKeyboard}
|
|
179
256
|
onResetZoom={resetZoom}
|
|
257
|
+
settingsOpen={settingsOpen}
|
|
258
|
+
onToggleSettings={() => setSettingsOpen((open) => !open)}
|
|
180
259
|
onClose={onClose}
|
|
181
260
|
/>
|
|
182
261
|
</div>
|