@hienlh/ppm 0.18.2 → 0.18.4
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 +22 -0
- package/CLAUDE.md +1 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/http-api.md +6 -1
- package/dist/web/assets/{ai-resource-editor-B6CODu6u.js → ai-resource-editor-Bb-NNlp1.js} +1 -1
- package/dist/web/assets/{angular-html-C-Kjr43S.js → angular-html-jfMqUuRd.js} +1 -1
- package/dist/web/assets/{angular-ts-CaDDbZqm.js → angular-ts-wytn9PMm.js} +1 -1
- package/dist/web/assets/api-client-CzABTreM.js +1 -0
- package/dist/web/assets/{api-settings-CAhcNpj5.js → api-settings-DfNRPr1q.js} +1 -1
- package/dist/web/assets/api-settings-DlcoR5QT.js +1 -0
- package/dist/web/assets/{api-themes-CAjg_Bt9.js → api-themes-CEyYK7AK.js} +1 -1
- package/dist/web/assets/{apl-B1WsQFAb.js → apl-i-ahKx6O.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-Bs7wzj-W.js +1 -0
- package/dist/web/assets/arrow-right-DubGFkN8.js +1 -0
- package/dist/web/assets/{astro-Bdnz7nVp.js → astro-DVBCJfG6.js} +1 -1
- package/dist/web/assets/audio-preview-DG2Tw0cO.js +1 -0
- package/dist/web/assets/{blade-B2euh8sS.js → blade-BHlL8SqU.js} +1 -1
- package/dist/web/assets/{bsl-GNeMaxd0.js → bsl-D-UOfR1F.js} +1 -1
- package/dist/web/assets/button-B4MWPjWj.js +1 -0
- package/dist/web/assets/{cairo-D7G0W7-s.js → cairo-Bn6rucli.js} +1 -1
- package/dist/web/assets/chat-tab-CgAv-crh.js +15 -0
- package/dist/web/assets/{cobol-Q6YgysnI.js → cobol-Byn9FYKu.js} +1 -1
- package/dist/web/assets/code-editor-bX4jlgMy.js +10 -0
- package/dist/web/assets/{coffee-BjufWxVc.js → coffee-DjBl4gm-.js} +1 -1
- package/dist/web/assets/{conflict-editor-DnSltcyC.js → conflict-editor-B6hVffX3.js} +3 -3
- package/dist/web/assets/{context-menu-q-RHHAWz.js → context-menu-DkQdt6YA.js} +1 -1
- package/dist/web/assets/{cpp-CqWU0gCR.js → cpp-CKzic-3E.js} +1 -1
- package/dist/web/assets/{crystal-ylayVajF.js → crystal-Bkg-Ly_g.js} +1 -1
- package/dist/web/assets/{csv-preview-DnaFdgOB.js → csv-preview-hL4Z9HS5.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-BzPfsCfd.js → data-grid-overlay-editor-B1mTl3KS.js} +1 -1
- package/dist/web/assets/{database-viewer-C7uLy8Z1.js → database-viewer-CTK5kMaA.js} +1 -1
- package/dist/web/assets/diff-viewer-DZxnANY4.js +4 -0
- package/dist/web/assets/{docx-preview-Dd2nTW2v.js → docx-preview-CrAc7Qjv.js} +1 -1
- package/dist/web/assets/download-D9UYwY0-.js +1 -0
- package/dist/web/assets/{edge-DX4NxQfh.js → edge-DVbZnd3Y.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-DfX-9om9.js → edit-diff-preview-HTZPC9VP.js} +1 -1
- package/dist/web/assets/{elixir-CSh3NjbV.js → elixir-Dwi8Wu7W.js} +1 -1
- package/dist/web/assets/{elm-DBzwFYq9.js → elm-DtIv-HXJ.js} +1 -1
- package/dist/web/assets/{erb-yxVOb8aB.js → erb-D3HEDz1x.js} +1 -1
- package/dist/web/assets/{erlang-5XIDkYrM.js → erlang-2MjTCzGz.js} +1 -1
- package/dist/web/assets/{esm-DO4el2OJ.js → esm-CZ8B8Ba2.js} +1 -1
- package/dist/web/assets/explorer-body-DykJXCTw.js +2 -0
- package/dist/web/assets/explorer-window-content-NS8Y0v8c.js +1 -0
- package/dist/web/assets/{extension-webview-Dk_zyoXZ.js → extension-webview-D6eywKPN.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-D4tE5VWY.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
- package/dist/web/assets/{fsharp-By8soj6-.js → fsharp-Bxbn_gbr.js} +1 -1
- package/dist/web/assets/{gdresource-DoSwM7Hv.js → gdresource-sFMY4D2Q.js} +1 -1
- package/dist/web/assets/{git-commit-_ZiciHIx.js → git-commit-BzB9Lio5.js} +1 -1
- package/dist/web/assets/{git-log-panel-BLQSy6rC.js → git-log-panel-DAogZ9QV.js} +1 -1
- package/dist/web/assets/{git-rebase-C3Y5N9-s.js → git-rebase-CKSVd848.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-D7i54kbM.js +1 -0
- package/dist/web/assets/glide-data-grid-DxfJRN-r.js +138 -0
- package/dist/web/assets/{glimmer-js-BPBTXVlC.js → glimmer-js-BY8aExAJ.js} +1 -1
- package/dist/web/assets/{glimmer-ts-BQSZdy_D.js → glimmer-ts-Bak54dBX.js} +1 -1
- package/dist/web/assets/{glsl-CbQdxpU-.js → glsl-nR2eaJQe.js} +1 -1
- package/dist/web/assets/{graphql-GMlKikpY.js → graphql-CGPPFAU6.js} +1 -1
- package/dist/web/assets/group-chat-tab-DkVi6qL2.js +1 -0
- package/dist/web/assets/{hack-DyshcLec.js → hack-CXIMX-Ol.js} +1 -1
- package/dist/web/assets/{haml-rqCqWzsq.js → haml-tPY-9Pfm.js} +1 -1
- package/dist/web/assets/{handlebars-BCKwSWjR.js → handlebars-BuW1LT7T.js} +1 -1
- package/dist/web/assets/{html-Cg29xPR1.js → html-bgVcwZ-t.js} +1 -1
- package/dist/web/assets/{html-derivative-geltIVg8.js → html-derivative-D6-IxTgn.js} +1 -1
- package/dist/web/assets/{http-pG7Vp237.js → http-CJ1zyB-h.js} +1 -1
- package/dist/web/assets/{hurl-Di3Uu_Mc.js → hurl-CmoT7l3T.js} +1 -1
- package/dist/web/assets/{hxml-CRy4lbv8.js → hxml-CztOsL8o.js} +1 -1
- package/dist/web/assets/{image-preview-KubLSLNO.js → image-preview-Byjx8MoL.js} +1 -1
- package/dist/web/assets/index-Bucbpby-.css +2 -0
- package/dist/web/assets/index-CcN0TBm4.js +52 -0
- package/dist/web/assets/info-3K5VOQVL-BV41AzR9.js +1 -0
- package/dist/web/assets/{jinja-C8GyVnvd.js → jinja-B1pq3jJq.js} +1 -1
- package/dist/web/assets/{jison-BywnqPn6.js → jison-D8B5pCwA.js} +1 -1
- package/dist/web/assets/{julia-DTrO3T3B.js → julia-D0K3bwpU.js} +1 -1
- package/dist/web/assets/{just-CA3KSHPi.js → just-B9Jlddu-.js} +1 -1
- package/dist/web/assets/keybindings-store-DJIHqbLM.js +1 -0
- package/dist/web/assets/{latex-BC5xUMpc.js → latex-BPJSSE6j.js} +1 -1
- package/dist/web/assets/{liquid-Chd8tSyQ.js → liquid-lv10fo4U.js} +1 -1
- package/dist/web/assets/{lua-DHaEtwLv.js → lua-PNuaIBEF.js} +1 -1
- package/dist/web/assets/{markdown-renderer-B4uL6GVA.js → markdown-renderer-CXlEe3ma.js} +3 -3
- package/dist/web/assets/markdown-renderer-pYznJDbh.js +1 -0
- package/dist/web/assets/{marko-Desx_bC-.js → marko-Du3frD6X.js} +1 -1
- package/dist/web/assets/{mdc-DahWHc1t.js → mdc-BeDVcFRl.js} +1 -1
- package/dist/web/assets/media-url-C5vA0ha9.js +1 -0
- package/dist/web/assets/{mobile-bottom-sheet-BQt45q9T.js → mobile-bottom-sheet-CS3XMwFw.js} +1 -1
- package/dist/web/assets/mobile-explorer-sheet-BuqBlP3o.js +1 -0
- package/dist/web/assets/{nextflow-3A7lutCA.js → nextflow-DkcXP71s.js} +1 -1
- package/dist/web/assets/{nginx-XQda6Hdy.js → nginx-C5n8N_bt.js} +1 -1
- package/dist/web/assets/{nim-DHqlpwP1.js → nim-CIhyfs-A.js} +1 -1
- package/dist/web/assets/notification-store-Bcl5efY2.js +1 -0
- package/dist/web/assets/{number-overlay-editor-DgZtViPO.js → number-overlay-editor-NgOsWJ7x.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-CaJoP44m.js +1 -0
- package/dist/web/assets/{panel-store-s6Qx49RK.js → panel-store-DCD82zc1.js} +1 -1
- package/dist/web/assets/{pdf-preview-xZQVUtaV.js → pdf-preview-F2DWVB85.js} +1 -1
- package/dist/web/assets/{perl-M-ncjACb.js → perl-Cf31sZYE.js} +1 -1
- package/dist/web/assets/{php-CKIajrZi.js → php-Dzl_MDIW.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-DSGZmNw2.js +1 -0
- package/dist/web/assets/plus-DmjC2iRB.js +1 -0
- package/dist/web/assets/{postgres-viewer-Ds0FxBel.js → postgres-viewer-KZqPYfOB.js} +3 -3
- package/dist/web/assets/{project-store-DyaEpHVG.js → project-store-B8BkHXy5.js} +1 -1
- package/dist/web/assets/{pug-DO0u_gO4.js → pug-CxSYHrB3.js} +1 -1
- package/dist/web/assets/{qml-UEje1PAA.js → qml-DlEjGhwN.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-Be4VUTs0.js +1 -0
- package/dist/web/assets/{razor-lCs5T1Ib.js → razor-KVt5uW14.js} +1 -1
- package/dist/web/assets/{rst-CZX_O0z4.js → rst-D5BwNTnw.js} +1 -1
- package/dist/web/assets/{ruby-idycVAyE.js → ruby-D5fejQwd.js} +1 -1
- package/dist/web/assets/run-in-terminal-B8TDtaM_.js +1 -0
- package/dist/web/assets/{sas-o5gTfUwp.js → sas-Bj7B1CB2.js} +1 -1
- package/dist/web/assets/{scss-CRTk3iBQ.js → scss-BB0CUoCL.js} +1 -1
- package/dist/web/assets/send-to-chat-CuBAVC8l.js +1 -0
- package/dist/web/assets/{settings-store-ChqHdT2V.js → settings-store-CY-4_U3H.js} +2 -2
- package/dist/web/assets/settings-tab-Khj-w6Sr.js +1 -0
- package/dist/web/assets/{shaderlab-3e5ln8ci.js → shaderlab-BdjEZUMc.js} +1 -1
- package/dist/web/assets/{shellsession-CGQjQ75I.js → shellsession-Bp1OA1rR.js} +1 -1
- package/dist/web/assets/{soy-CxY0VwX2.js → soy-DlYz3wsC.js} +1 -1
- package/dist/web/assets/{sparql-CBAGAFuq.js → sparql-CdfII5Jd.js} +1 -1
- package/dist/web/assets/sql-query-editor-Cp0_e2P1.js +1 -0
- package/dist/web/assets/{sqlite-viewer-CZYDknNa.js → sqlite-viewer-CIv3sagZ.js} +1 -1
- package/dist/web/assets/{stata-BoezhOvk.js → stata-BvUA6GUW.js} +1 -1
- package/dist/web/assets/{surrealql-CHEzAhu7.js → surrealql--DLNbTyI.js} +1 -1
- package/dist/web/assets/{svelte-y6xtu2zX.js → svelte-CBfSsG4n.js} +1 -1
- package/dist/web/assets/system-monitor-tab-CrssTOf4.js +1 -0
- package/dist/web/assets/{tab-store-C42sktPo.js → tab-store-DAsvpx4t.js} +1 -1
- package/dist/web/assets/team-member-window-content-D-hXax3c.js +1 -0
- package/dist/web/assets/{templ-DbG2y5yD.js → templ-Dv8zpKZW.js} +1 -1
- package/dist/web/assets/terminal-tab-BIQDG-Ti.js +3 -0
- package/dist/web/assets/{tex-CeKQhrQG.js → tex-CsrOiqh1.js} +1 -1
- package/dist/web/assets/tool-cards-Dx-Rrpri.js +8 -0
- package/dist/web/assets/treemap-KZPCXAKY-Cu2zgbxU.js +1 -0
- package/dist/web/assets/{ts-tags-iKKOXJjG.js → ts-tags-3T98k4EJ.js} +1 -1
- package/dist/web/assets/{twig-DK8Vrp-G.js → twig-u6QZ45FH.js} +1 -1
- package/dist/web/assets/{use-blob-url-DYtOHK7u.js → use-blob-url-BmlYYZ8L.js} +1 -1
- package/dist/web/assets/use-is-mobile-B8FA4kQf.js +1 -0
- package/dist/web/assets/use-monaco-theme-Djp1_fKi.js +1 -0
- package/dist/web/assets/use-websocket-CCYA9-a2.js +1 -0
- package/dist/web/assets/{vendor-mermaid-C977xNeE.js → vendor-mermaid-Bg4vE8QA.js} +1 -1
- package/dist/web/assets/video-preview-cvjNMjWA.js +1 -0
- package/dist/web/assets/{vue-JkIEObMV.js → vue-CxIsxtwn.js} +1 -1
- package/dist/web/assets/{vue-html-D8SqnU86.js → vue-html-BGL5qnuH.js} +1 -1
- package/dist/web/assets/{vue-vine-DJj7N4WD.js → vue-vine-DHPh_gMr.js} +1 -1
- package/dist/web/assets/{xml-C-00w9Rv.js → xml-C-vM2m7J.js} +1 -1
- package/dist/web/assets/{xsl-CVdCwPHV.js → xsl-DvTXNwuO.js} +1 -1
- package/dist/web/index.html +21 -20
- package/dist/web/sw.js +1 -1
- package/docs/codebase-summary.md +1 -0
- package/package.json +1 -1
- package/scripts/dev-quick-tunnel-retry.mjs +51 -0
- package/src/providers/claude-agent-sdk.ts +6 -0
- package/src/server/helpers/media-route-handlers.ts +66 -0
- package/src/server/helpers/range-file-response.ts +86 -0
- package/src/server/index.ts +24 -17
- package/src/server/middleware/auth.ts +3 -2
- package/src/server/routes/files.ts +40 -7
- package/src/server/routes/fs-browse.ts +43 -5
- package/src/server/routes/fs-upload.ts +32 -0
- package/src/server/routes/teams.ts +34 -0
- package/src/server/ws/chat.ts +64 -16
- package/src/server/ws/team-inbox-watcher.ts +61 -22
- package/src/server/ws/terminal.ts +24 -6
- package/src/services/fs-ops/fs-ops-upload.service.ts +120 -0
- package/src/services/media-transcode/ffmpeg-capabilities.ts +89 -0
- package/src/services/media-transcode/media-probe.ts +63 -0
- package/src/services/media-transcode/transcode-stream.ts +129 -0
- package/src/services/subagent-transcript-merger.ts +1 -1
- package/src/services/team-member-activity/member-activity.service.ts +117 -0
- package/src/services/team-member-activity/outbound-message-scanner.ts +121 -0
- package/src/services/team-member-activity/subagent-transcript-index.ts +95 -0
- package/src/services/team-member-activity/transcript-tail-summary.ts +125 -0
- package/src/shared/background-agent-status.ts +79 -0
- package/src/types/api.ts +1 -0
- package/src/types/chat.ts +6 -1
- package/src/web/components/chat/chat-history-bar.tsx +4 -2
- package/src/web/components/chat/chat-tab.tsx +1 -0
- package/src/web/components/chat/message-input.tsx +13 -6
- package/src/web/components/chat/mini-markdown.tsx +17 -0
- package/src/web/components/chat/send-message-card.tsx +134 -0
- package/src/web/components/chat/send-message-parse.ts +159 -0
- package/src/web/components/chat/team-activity-panel.tsx +107 -121
- package/src/web/components/chat/team-member-activity-row.tsx +92 -0
- package/src/web/components/chat/team-member-list.tsx +43 -0
- package/src/web/components/chat/team-member-window-content.tsx +97 -0
- package/src/web/components/chat/team-message-badges.ts +18 -0
- package/src/web/components/chat/team-message-list.tsx +88 -0
- package/src/web/components/chat/team-message-preview.ts +87 -0
- package/src/web/components/chat/tool-cards.tsx +100 -23
- package/src/web/components/editor/audio-preview.tsx +6 -7
- package/src/web/components/editor/video-player/use-video-keyboard-shortcuts.ts +62 -0
- package/src/web/components/editor/video-player/video-player-controls.tsx +103 -0
- package/src/web/components/editor/video-player/video-player.tsx +190 -0
- package/src/web/components/editor/video-player/video-settings-menu.tsx +92 -0
- package/src/web/components/editor/video-player/video-transform.ts +49 -0
- package/src/web/components/editor/video-preview.tsx +75 -14
- package/src/web/components/explorer/file-tree.tsx +6 -0
- package/src/web/components/explorer/tree-node-context-menu.tsx +5 -1
- package/src/web/components/floating-window/window-content-registry.ts +5 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/os-explorer/actions/explorer-actions-upload.ts +96 -0
- package/src/web/components/os-explorer/actions/use-explorer-actions.ts +23 -2
- package/src/web/components/os-explorer/actions/use-explorer-upload-actions.tsx +41 -0
- package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +10 -1
- package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +8 -1
- package/src/web/components/os-explorer/dnd/use-external-file-drop.ts +84 -0
- package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +46 -3
- package/src/web/components/os-explorer/explorer-body.tsx +1 -1
- package/src/web/components/os-explorer/explorer-context-menu.tsx +10 -0
- package/src/web/components/os-explorer/explorer-dialogs.tsx +2 -0
- package/src/web/components/os-explorer/explorer-sidebar.tsx +10 -5
- package/src/web/components/os-explorer/explorer-toolbar.tsx +32 -2
- package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +5 -1
- package/src/web/components/os-explorer/upload/collect-dropped-entries.ts +81 -0
- package/src/web/components/os-explorer/upload/upload-queue.ts +79 -0
- package/src/web/components/os-explorer/upload/use-upload-picker.tsx +74 -0
- package/src/web/components/os-explorer/views/column-row.tsx +3 -1
- package/src/web/components/os-explorer/views/column-view-column.tsx +6 -1
- package/src/web/components/os-explorer/views/icons-view-tile.tsx +1 -1
- package/src/web/components/os-explorer/views/icons-view.tsx +5 -1
- package/src/web/components/os-explorer/views/list-row.tsx +1 -1
- package/src/web/components/os-explorer/views/list-view.tsx +5 -1
- package/src/web/components/shared/markdown-code-block.tsx +3 -7
- package/src/web/components/terminal/terminal-tab.tsx +19 -25
- package/src/web/hooks/use-chat.ts +77 -15
- package/src/web/hooks/use-team-activity-feed.ts +92 -0
- package/src/web/hooks/use-terminal-command-queue.ts +108 -0
- package/src/web/hooks/use-terminal.ts +45 -3
- package/src/web/lib/fs-api.ts +9 -0
- package/src/web/lib/fs-upload-xhr.ts +62 -0
- package/src/web/lib/media-url.ts +51 -0
- package/src/web/lib/open-terminal-at.ts +22 -0
- package/src/web/lib/run-in-terminal.ts +55 -0
- package/src/web/lib/send-to-chat.ts +118 -0
- package/src/web/lib/ws-auth.ts +13 -0
- package/src/web/lib/ws-client.ts +10 -5
- package/.release-run.sh +0 -5
- package/dist/web/assets/api-client-CaRE4HYh.js +0 -1
- package/dist/web/assets/api-settings-DBcXfeJ8.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +0 -1
- package/dist/web/assets/audio-preview-BkNAEqcB.js +0 -1
- package/dist/web/assets/chat-tab-B-MEsHYp.js +0 -13
- package/dist/web/assets/code-editor-bC1KAEUA.js +0 -10
- package/dist/web/assets/dialog-CkI7rwrh.js +0 -1
- package/dist/web/assets/diff-viewer-BwElk4q9.js +0 -4
- package/dist/web/assets/explorer-body-DzdXBlWw.js +0 -2
- package/dist/web/assets/explorer-window-content-Dgl1WTNs.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +0 -1
- package/dist/web/assets/glide-data-grid-QZ_fgjIa.js +0 -138
- package/dist/web/assets/group-chat-tab-BbdVlKFv.js +0 -1
- package/dist/web/assets/index-D-xwjNyX.css +0 -2
- package/dist/web/assets/index-wd3FSHHn.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +0 -1
- package/dist/web/assets/keybindings-store-Bp_c_C3z.js +0 -1
- package/dist/web/assets/markdown-renderer-2asl-3Kk.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-LWUDaNBV.js +0 -1
- package/dist/web/assets/notification-store-BasUnpt7.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +0 -1
- package/dist/web/assets/plus-9LatwJMx.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +0 -1
- package/dist/web/assets/settings-tab-vhatQ51-.js +0 -1
- package/dist/web/assets/sql-query-editor-BblB2Imt.js +0 -1
- package/dist/web/assets/system-monitor-tab-D8dy-RT_.js +0 -1
- package/dist/web/assets/terminal-tab-CfD22q5x.js +0 -3
- package/dist/web/assets/tool-cards-DNgw1yI6.js +0 -7
- package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +0 -1
- package/dist/web/assets/use-is-mobile-DnFSkUG2.js +0 -1
- package/dist/web/assets/use-monaco-theme-DEbn-QT3.js +0 -1
- package/dist/web/assets/video-preview-DcUaFYyv.js +0 -1
- package/src/web/components/chat/team-activity-popover.tsx +0 -202
- /package/dist/web/assets/{abap-DrMyiWmK.js → abap-C1K0-wzc.js} +0 -0
- /package/dist/web/assets/{actionscript-3-BlSOyi0y.js → actionscript-3-CJhQVjLA.js} +0 -0
- /package/dist/web/assets/{ada-B_m96icB.js → ada-Bt8r9fME.js} +0 -0
- /package/dist/web/assets/{andromeeda-Ch3vOiZB.js → andromeeda-DF1I8qi3.js} +0 -0
- /package/dist/web/assets/{apache-CdsaXc1n.js → apache-CnlleE6P.js} +0 -0
- /package/dist/web/assets/{apex-0AnsBevk.js → apex-j0C6Xmk6.js} +0 -0
- /package/dist/web/assets/{api-client-rTAGdEKo.js → api-client-BSB14J4w.js} +0 -0
- /package/dist/web/assets/{applescript-CSxLolzy.js → applescript-DO7AnO8U.js} +0 -0
- /package/dist/web/assets/{apply-theme-CMQXnUAa.js → apply-theme-CNj1iA-v.js} +0 -0
- /package/dist/web/assets/{ara-DXZS0jA5.js → ara-i83AydZj.js} +0 -0
- /package/dist/web/assets/{asciidoc-DDy3pH2A.js → asciidoc-DbBA6-sF.js} +0 -0
- /package/dist/web/assets/{asm-Bd17hhqg.js → asm-B5S47Rgr.js} +0 -0
- /package/dist/web/assets/{aurora-x-C62HJRs-.js → aurora-x-De_IaSKw.js} +0 -0
- /package/dist/web/assets/{awk-Cc-nazni.js → awk-CfyLHXqS.js} +0 -0
- /package/dist/web/assets/{ayu-dark-BsfmNWCU.js → ayu-dark-DTLdZ1Si.js} +0 -0
- /package/dist/web/assets/{ayu-light-BZ4HLa4X.js → ayu-light-DTOfkzhu.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-Bhyjyiqr.js → ayu-mirage-DsAECCMv.js} +0 -0
- /package/dist/web/assets/{ballerina-QiWgir3v.js → ballerina-CumGp9A7.js} +0 -0
- /package/dist/web/assets/{bat-DxtlkwN3.js → bat-Ca2SBbYN.js} +0 -0
- /package/dist/web/assets/{beancount-Bt21X7M_.js → beancount-CqyXBwwF.js} +0 -0
- /package/dist/web/assets/{berry-Cksre7lO.js → berry-yl-kJr_H.js} +0 -0
- /package/dist/web/assets/{bibtex-DkakgDlR.js → bibtex-DoFFszjV.js} +0 -0
- /package/dist/web/assets/{bicep-8crmcyXU.js → bicep-CWPi4nfK.js} +0 -0
- /package/dist/web/assets/{bird2-CewQ2I_Z.js → bird2-DXew-ut0.js} +0 -0
- /package/dist/web/assets/{bot-nk1k3ELu.js → bot-CunPuEdW.js} +0 -0
- /package/dist/web/assets/{c-C_6zE8Ha.js → c-clbySDvd.js} +0 -0
- /package/dist/web/assets/{c3-DLWB9-dp.js → c3-B65jvSM1.js} +0 -0
- /package/dist/web/assets/{cadence-D8qYS6pR.js → cadence-Cm8tQTHN.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-BzVXoV-d.js → can-open-in-ppm-Cu-XYIgu.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-Bh95lNRy.js → catppuccin-frappe-DLeGslh_.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-C1PlN3G0.js → catppuccin-latte-CR5GeHws.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-CldtgdK4.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BiwqxX9A.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
- /package/dist/web/assets/{clarity-CGiU22jY.js → clarity-DPulbvKH.js} +0 -0
- /package/dist/web/assets/{clojure-COIC3doh.js → clojure-DFuWS20u.js} +0 -0
- /package/dist/web/assets/{cmake-DWUEQfHv.js → cmake-DejCqcu4.js} +0 -0
- /package/dist/web/assets/{codeowners-BNHiAQea.js → codeowners-BwAyqWIH.js} +0 -0
- /package/dist/web/assets/{codeql-BuKaoS0j.js → codeql-DgSwFjqI.js} +0 -0
- /package/dist/web/assets/{common-lisp-D5IKbxsd.js → common-lisp-CxIVJCtf.js} +0 -0
- /package/dist/web/assets/{coq-BMVS-p94.js → coq-DLSjSnmf.js} +0 -0
- /package/dist/web/assets/{csharp-DMlbXbM4.js → csharp-C1HHzXBp.js} +0 -0
- /package/dist/web/assets/{css-CjkJC89X.js → css-VXsMY--y.js} +0 -0
- /package/dist/web/assets/{csv-DoNVBBf0.js → csv-BEbcJdHz.js} +0 -0
- /package/dist/web/assets/{csv-parser-DHvoTjMF.js → csv-parser-r-Nk1oHT.js} +0 -0
- /package/dist/web/assets/{cue-DoTRbBg-.js → cue-BkDnFqUD.js} +0 -0
- /package/dist/web/assets/{cypher-DVsNdTT3.js → cypher-1cMqojSQ.js} +0 -0
- /package/dist/web/assets/{d-EX7BBSrv.js → d-BnYjRXtH.js} +0 -0
- /package/dist/web/assets/{dark-plus-P_YTHwlh.js → dark-plus-XfbaH7kZ.js} +0 -0
- /package/dist/web/assets/{dart-Do-rgU4S.js → dart-Ca7s-8hK.js} +0 -0
- /package/dist/web/assets/{data-grid-types-CUx8cusN.js → data-grid-types-BXwlFMzs.js} +0 -0
- /package/dist/web/assets/{dax-DxOieacu.js → dax-CmuOw-mY.js} +0 -0
- /package/dist/web/assets/{desktop-CvdV7SBf.js → desktop-C2gJ6Ovb.js} +0 -0
- /package/dist/web/assets/{diff-CNOetGen.js → diff-BCEo1Ej7.js} +0 -0
- /package/dist/web/assets/{dist-CMfLpPW_.js → dist-CUPbqNnL.js} +0 -0
- /package/dist/web/assets/{dist-DduBIVkD.js → dist-DOlBnxFX.js} +0 -0
- /package/dist/web/assets/{docker-DAlG4_q-.js → docker-DN2jlyAh.js} +0 -0
- /package/dist/web/assets/{dotenv-Dh3qf6yx.js → dotenv-NExMY2kS.js} +0 -0
- /package/dist/web/assets/{dracula-baAGmJYG.js → dracula-B9lbE01y.js} +0 -0
- /package/dist/web/assets/{dracula-soft-l7ZIyums.js → dracula-soft-CMBBfKF_.js} +0 -0
- /package/dist/web/assets/{dream-maker-DeRjdBn8.js → dream-maker-DEyDwFsd.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-CB05-jQA.js → emacs-lisp-D_qP2zS6.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CT-llQBT.js → everforest-dark-DbHa2U-p.js} +0 -0
- /package/dist/web/assets/{everforest-light-ByPJkTT7.js → everforest-light-BISkJchP.js} +0 -0
- /package/dist/web/assets/{fennel-CeMarbvn.js → fennel-6B22SfJR.js} +0 -0
- /package/dist/web/assets/{file-exclamation-point-COAwgNGw.js → file-exclamation-point-a8f3_w7a.js} +0 -0
- /package/dist/web/assets/{fish-BYrt6Xtb.js → fish-CubIIvJD.js} +0 -0
- /package/dist/web/assets/{fluent-Cr5VcAwh.js → fluent-DF55zQ09.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-DQmKtrLa.js → fortran-free-form-DvOu8jAt.js} +0 -0
- /package/dist/web/assets/{gdscript-QTDKjzhY.js → gdscript-DCPxdLpq.js} +0 -0
- /package/dist/web/assets/{gdshader-Chr-5UYt.js → gdshader-CTZ381FO.js} +0 -0
- /package/dist/web/assets/{genie-kWHMWnt0.js → genie-C_QDtvgU.js} +0 -0
- /package/dist/web/assets/{gherkin-DtEBQFUo.js → gherkin-BcvK42tF.js} +0 -0
- /package/dist/web/assets/{github-dark-B5aiUta2.js → github-dark-C9afs_4w.js} +0 -0
- /package/dist/web/assets/{github-dark-default-BkxtsTqR.js → github-dark-default-CKGVrl3Z.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-B9QjR_0K.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-D6bO-5c0.js → github-dark-high-contrast-DW78QukY.js} +0 -0
- /package/dist/web/assets/{github-light-Cg2ZW_CJ.js → github-light-BnP2dznS.js} +0 -0
- /package/dist/web/assets/{github-light-default-eD7qUVlQ.js → github-light-default-C2fPZzPG.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-CFnlSVjd.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
- /package/dist/web/assets/{gleam-BpYoOqIT.js → gleam-BpeKwEIO.js} +0 -0
- /package/dist/web/assets/{gn-BxwtZPau.js → gn-nUb_Mit0.js} +0 -0
- /package/dist/web/assets/{gnuplot-CRyiZDfl.js → gnuplot-D7I0v20p.js} +0 -0
- /package/dist/web/assets/{go-Dw1Qx94W.js → go-D7Qqska7.js} +0 -0
- /package/dist/web/assets/{groovy-zq_SbWk-.js → groovy-DS-pne5-.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DbbNy8-Q.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-BQZ7Kj6C.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-vNavErXi.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-AOFbCd7i.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-DqtxND6H.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-Cg0basu6.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
- /package/dist/web/assets/{haskell-CcsmcOez.js → haskell-uQcywkDH.js} +0 -0
- /package/dist/web/assets/{haxe-BimYY9X4.js → haxe-c6bI4_GT.js} +0 -0
- /package/dist/web/assets/{hcl-CTrXCK40.js → hcl-CQXTiEXq.js} +0 -0
- /package/dist/web/assets/{hjson-CmqqU6Qg.js → hjson-ChETf2bz.js} +0 -0
- /package/dist/web/assets/{hlsl-CuJtX_uf.js → hlsl-BMC4JGoE.js} +0 -0
- /package/dist/web/assets/{horizon-BGeSjDYJ.js → horizon-BusFlhLW.js} +0 -0
- /package/dist/web/assets/{horizon-bright-GYMgd3R0.js → horizon-bright-2oD0Mz7W.js} +0 -0
- /package/dist/web/assets/{houston--JD_5r2q.js → houston-DdbbjqFg.js} +0 -0
- /package/dist/web/assets/{hy-C9ILOiqk.js → hy-D_SOuqw9.js} +0 -0
- /package/dist/web/assets/{image-gallery-BWFpZAAP.js → image-gallery-CrM5fHKy.js} +0 -0
- /package/dist/web/assets/{imba-B6F3yus5.js → imba-BxLOHO5I.js} +0 -0
- /package/dist/web/assets/{ini-BfkQswMZ.js → ini-ox-CFcg0.js} +0 -0
- /package/dist/web/assets/{java-DEH5avQ1.js → java-Bzo8yIal.js} +0 -0
- /package/dist/web/assets/{javascript-Dr4yHuV2.js → javascript-DHuz9VXn.js} +0 -0
- /package/dist/web/assets/{json-BuR35ewf.js → json-0_y80FD5.js} +0 -0
- /package/dist/web/assets/{json5-Ny9ZiJAC.js → json5-CEQ50pGM.js} +0 -0
- /package/dist/web/assets/{jsonc-zYVuoZkR.js → jsonc-CfrLEAKA.js} +0 -0
- /package/dist/web/assets/{jsonl-B9xthb_H.js → jsonl-fKbrANC2.js} +0 -0
- /package/dist/web/assets/{jsonnet-CCoLJbMP.js → jsonnet-DKbkPaQ2.js} +0 -0
- /package/dist/web/assets/{jssm-W0GDlQXi.js → jssm-Dxxx1Hqu.js} +0 -0
- /package/dist/web/assets/{jsx-BrLJpWC-.js → jsx-DlJL-Oj3.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-CuVwt_MO.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-B3W_617P.js → kanagawa-lotus-BHltayiE.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-CEx4MP5W.js → kanagawa-wave-D-KXPjw6.js} +0 -0
- /package/dist/web/assets/{kdl-DilRejnB.js → kdl-DvtoJdFo.js} +0 -0
- /package/dist/web/assets/{kotlin-Bq5JtKaa.js → kotlin-CTS4MLcx.js} +0 -0
- /package/dist/web/assets/{kusto-Bhp2KYog.js → kusto-HtSE_sKm.js} +0 -0
- /package/dist/web/assets/{laserwave-WA_7c5hu.js → laserwave-DaXZG9ge.js} +0 -0
- /package/dist/web/assets/{lean-AEGcXwS-.js → lean-BYtGUfWx.js} +0 -0
- /package/dist/web/assets/{less-NPqH-26u.js → less-BoxmU_Mk.js} +0 -0
- /package/dist/web/assets/{lib-BFetOULu.js → lib-BzxL1ARZ.js} +0 -0
- /package/dist/web/assets/{libesm-tR4XlhMj.js → libesm-ah-mr-sh.js} +0 -0
- /package/dist/web/assets/{light-plus-B4xW7cgY.js → light-plus-Bt5CyUuo.js} +0 -0
- /package/dist/web/assets/{llvm-CPyWNcDE.js → llvm-CSdk9q9V.js} +0 -0
- /package/dist/web/assets/{log-CeQmiQSz.js → log-B-kp_3CL.js} +0 -0
- /package/dist/web/assets/{logo-BO0VrjBm.js → logo-DdIsXFDO.js} +0 -0
- /package/dist/web/assets/{luau-B6GT9kA1.js → luau-DUBpLlzQ.js} +0 -0
- /package/dist/web/assets/{make-CwfmN7e_.js → make-B3JVjoW_.js} +0 -0
- /package/dist/web/assets/{markdown-Bbe4FlLp.js → markdown-Seon0vHs.js} +0 -0
- /package/dist/web/assets/{material-theme-iD9XbfsB.js → material-theme-Nq-ChX1P.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-u7M3XGe7.js → material-theme-darker-CPV4ayXJ.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-BGdI48zn.js → material-theme-lighter-0kyjYqEd.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-_mk62F5q.js → material-theme-ocean-DGpAp5co.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-CxFbDyAu.js → material-theme-palenight-B0AUn0NG.js} +0 -0
- /package/dist/web/assets/{matlab-Z0RUE3V_.js → matlab-C2waytxs.js} +0 -0
- /package/dist/web/assets/{mdx-BZ5wPYzV.js → mdx-DAis6R6I.js} +0 -0
- /package/dist/web/assets/{mermaid-CY1_aP-h.js → mermaid-_rnT2YGD.js} +0 -0
- /package/dist/web/assets/{min-dark-DKWtxOre.js → min-dark-BSSjchUB.js} +0 -0
- /package/dist/web/assets/{min-light-e1iwHDfP.js → min-light-DomAFQsa.js} +0 -0
- /package/dist/web/assets/{mipsasm-BiDfQ4ir.js → mipsasm-bBgtvPyG.js} +0 -0
- /package/dist/web/assets/{mojo-DteFEHsj.js → mojo-Cp9BayW0.js} +0 -0
- /package/dist/web/assets/{monokai-BgPbE9xZ.js → monokai-CL_9gbmn.js} +0 -0
- /package/dist/web/assets/{moonbit-mEbRp-mA.js → moonbit-BS_fcr-P.js} +0 -0
- /package/dist/web/assets/{move-CogqZw7A.js → move-C79AH45U.js} +0 -0
- /package/dist/web/assets/{narrat-BD2qjmy1.js → narrat-CJ5s3BDf.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-BNX8GOD_.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
- /package/dist/web/assets/{night-owl-ed3GbziD.js → night-owl-Jkf9zFQK.js} +0 -0
- /package/dist/web/assets/{night-owl-light-B0j7O0Wn.js → night-owl-light-DRPeaG5J.js} +0 -0
- /package/dist/web/assets/{nix-DFDbcQQm.js → nix-D2Jet9DN.js} +0 -0
- /package/dist/web/assets/{nord-D93TxEQi.js → nord-CREVs22j.js} +0 -0
- /package/dist/web/assets/{nushell-3_H_XKja.js → nushell-CAiDi_mR.js} +0 -0
- /package/dist/web/assets/{objective-c-BM3u6nlm.js → objective-c-CVkmJkC1.js} +0 -0
- /package/dist/web/assets/{objective-cpp-DZ73h42g.js → objective-cpp-DIKDO1Ly.js} +0 -0
- /package/dist/web/assets/{ocaml-B1PU5gmh.js → ocaml-R9qtGQhs.js} +0 -0
- /package/dist/web/assets/{odin-D14L6aRL.js → odin-DTYNQqVr.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-Bj-94Z8v.js → one-dark-pro-BSLWUJn5.js} +0 -0
- /package/dist/web/assets/{one-light-DTdGWyOZ.js → one-light-DbkRXG3t.js} +0 -0
- /package/dist/web/assets/{openscad-Ciq554Sj.js → openscad-BlPcSnmT.js} +0 -0
- /package/dist/web/assets/{pascal-Bfd4qdGi.js → pascal-B1Y46ZZB.js} +0 -0
- /package/dist/web/assets/{pkl-CTurDUfj.js → pkl-BhJSJbFv.js} +0 -0
- /package/dist/web/assets/{plastic-BLQfZXQp.js → plastic-BzxsCTpi.js} +0 -0
- /package/dist/web/assets/{plsql-DyYfP9kQ.js → plsql-DBba7dyP.js} +0 -0
- /package/dist/web/assets/{po-CgXdT8vQ.js → po-BvgLlnNo.js} +0 -0
- /package/dist/web/assets/{poimandres-DOg5fK6s.js → poimandres-DUaI_1JP.js} +0 -0
- /package/dist/web/assets/{polar-Dv4aoL6X.js → polar-C7uILwJn.js} +0 -0
- /package/dist/web/assets/{postcss-DxugHnJD.js → postcss-BgJlpFdw.js} +0 -0
- /package/dist/web/assets/{powerquery-CR3kzRyX.js → powerquery-Dn1MIpZu.js} +0 -0
- /package/dist/web/assets/{powershell-BE91xJQB.js → powershell-Bo-DYzOS.js} +0 -0
- /package/dist/web/assets/{prisma-B_3yUlRQ.js → prisma-CTLs5IdT.js} +0 -0
- /package/dist/web/assets/{prolog-DcT781Ht.js → prolog-QRGun3eG.js} +0 -0
- /package/dist/web/assets/{proto-CSf-w5k2.js → proto-B7PI7apn.js} +0 -0
- /package/dist/web/assets/{puppet-CLGO6QWl.js → puppet-CBq0tLrP.js} +0 -0
- /package/dist/web/assets/{purescript-Bah2McKd.js → purescript-DFewOQb2.js} +0 -0
- /package/dist/web/assets/{python-DUH8qy7N.js → python-iWuxKXvT.js} +0 -0
- /package/dist/web/assets/{qmldir-YIsHnjKq.js → qmldir-cXiW3AsF.js} +0 -0
- /package/dist/web/assets/{qss-CLmoVNA4.js → qss-DASwAbBL.js} +0 -0
- /package/dist/web/assets/{r-Pon-b-vz.js → r-TU2eaIPw.js} +0 -0
- /package/dist/web/assets/{racket-BA09P5Kg.js → racket-SkRKqrLY.js} +0 -0
- /package/dist/web/assets/{raku-DlpzU7Fe.js → raku-D31lthkZ.js} +0 -0
- /package/dist/web/assets/{react-w6wINGnd.js → react-I_EwPIZi.js} +0 -0
- /package/dist/web/assets/{red-l7VuCR6d.js → red-CTcQZevb.js} +0 -0
- /package/dist/web/assets/{reg-g_k7gQEr.js → reg-D8YAsTGf.js} +0 -0
- /package/dist/web/assets/{regexp-CT17B7I7.js → regexp-CD2uvAWx.js} +0 -0
- /package/dist/web/assets/{rel-DkPCS7Bp.js → rel-CAaI-1fg.js} +0 -0
- /package/dist/web/assets/{resolve-theme-DdB7Yo5Z.js → resolve-theme-CJvkL8hU.js} +0 -0
- /package/dist/web/assets/{riscv-CYuFTw30.js → riscv-DR-hJVIj.js} +0 -0
- /package/dist/web/assets/{ron-cY7u_yEu.js → ron-CuUCJ0n4.js} +0 -0
- /package/dist/web/assets/{rose-pine-Cr_h_Ubo.js → rose-pine-aFHxW48i.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-rRewaSZg.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-3VX5Xwca.js → rose-pine-moon-wqyh0an4.js} +0 -0
- /package/dist/web/assets/{rosmsg-Du4ie5PU.js → rosmsg-CI1ywimO.js} +0 -0
- /package/dist/web/assets/{rust-B7xWL-Eh.js → rust-p7Imk4G_.js} +0 -0
- /package/dist/web/assets/{sass-oXbOG8de.js → sass-C8JfaqCV.js} +0 -0
- /package/dist/web/assets/{save-C4z1p51e.js → save-DRPrDfTt.js} +0 -0
- /package/dist/web/assets/{scala-nlZJhKHf.js → scala-DNxCW1By.js} +0 -0
- /package/dist/web/assets/{scheme-FZ9_Zy8D.js → scheme-BYuUPJoL.js} +0 -0
- /package/dist/web/assets/{sdbl-D_lyvXiu.js → sdbl-DbPeJPUr.js} +0 -0
- /package/dist/web/assets/{shellscript-DRWRboAd.js → shellscript-DMiHlR3q.js} +0 -0
- /package/dist/web/assets/{shield-check-C5laObJv.js → shield-check-8GObuUAn.js} +0 -0
- /package/dist/web/assets/{shield-off-DULFtyLS.js → shield-off-egKAZEwR.js} +0 -0
- /package/dist/web/assets/{slack-dark-BGk2uVHu.js → slack-dark-C4xGcQXI.js} +0 -0
- /package/dist/web/assets/{slack-ochin-PAoSX041.js → slack-ochin-BzgRt_wm.js} +0 -0
- /package/dist/web/assets/{smalltalk-qP0zvICo.js → smalltalk-LDeL9PYH.js} +0 -0
- /package/dist/web/assets/{snazzy-light-B7uccpH6.js → snazzy-light-CXE7a5j_.js} +0 -0
- /package/dist/web/assets/{solarized-dark-CbICLZs3.js → solarized-dark-DgUhY74x.js} +0 -0
- /package/dist/web/assets/{solarized-light-CprWhyjj.js → solarized-light-DD9C-H-9.js} +0 -0
- /package/dist/web/assets/{solidity-B7pcm2K6.js → solidity-CAbDB4D4.js} +0 -0
- /package/dist/web/assets/{splunk-Co2LL4J_.js → splunk-kQREPfIc.js} +0 -0
- /package/dist/web/assets/{sql-cETIdGnT.js → sql-CBzeNAD8.js} +0 -0
- /package/dist/web/assets/{ssh-config-DETG6JCD.js → ssh-config-L1euxrsM.js} +0 -0
- /package/dist/web/assets/{stylus-tmTre2-O.js → stylus-DvBnYniP.js} +0 -0
- /package/dist/web/assets/{swift-BeKQbULV.js → swift-DF5NDQp6.js} +0 -0
- /package/dist/web/assets/{synthwave-84-eCL4-Xdn.js → synthwave-84-DbBhB_Hq.js} +0 -0
- /package/dist/web/assets/{system-verilog-CviwWvEx.js → system-verilog-D8s2ZW_k.js} +0 -0
- /package/dist/web/assets/{systemd-WuoFIbBS.js → systemd-a5-XZJsG.js} +0 -0
- /package/dist/web/assets/{table-CHmxuh6M.js → table-BX2ZrJfM.js} +0 -0
- /package/dist/web/assets/{talonscript-B9VBu7VI.js → talonscript-BLa0URTW.js} +0 -0
- /package/dist/web/assets/{tasl-CN7r11ky.js → tasl-DIoV4XcC.js} +0 -0
- /package/dist/web/assets/{tcl-e1cdhe3h.js → tcl-ztYAdXfQ.js} +0 -0
- /package/dist/web/assets/{terraform-BEP0mfa_.js → terraform-DvpCCiOZ.js} +0 -0
- /package/dist/web/assets/{tokyo-night-BfA_dA37.js → tokyo-night-BkHlYUv0.js} +0 -0
- /package/dist/web/assets/{toml-BrDIfNnt.js → toml-acIrxos6.js} +0 -0
- /package/dist/web/assets/{tsv-CyfGyE4i.js → tsv-BJB5EVCT.js} +0 -0
- /package/dist/web/assets/{tsx-BrFA8Iwf.js → tsx-B1MmXGT3.js} +0 -0
- /package/dist/web/assets/{turtle-9GBtR0mP.js → turtle-DFceTiqd.js} +0 -0
- /package/dist/web/assets/{typescript-BuTRZ65V.js → typescript-Dz9wU5Y1.js} +0 -0
- /package/dist/web/assets/{typespec-CRiNNRXF.js → typespec-CPuYZijj.js} +0 -0
- /package/dist/web/assets/{typst-CmpTnnkl.js → typst-CfsCIYv8.js} +0 -0
- /package/dist/web/assets/{utils-B9H0d3Fh.js → utils-DoY4yTP-.js} +0 -0
- /package/dist/web/assets/{v-DnIi2aab.js → v-CGo0jcXA.js} +0 -0
- /package/dist/web/assets/{vala-B7VxCNZP.js → vala-AsSR0hrb.js} +0 -0
- /package/dist/web/assets/{vb-DGoQ3pBT.js → vb-C7tvbXv1.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-ClRVIJ-k.js → vendor-xterm-BeaIjRrv.js} +0 -0
- /package/dist/web/assets/{verilog-zGOC4qwV.js → verilog--a33ND-p.js} +0 -0
- /package/dist/web/assets/{vesper-DXR9-06q.js → vesper-B_Z0h4pn.js} +0 -0
- /package/dist/web/assets/{vhdl-CCO0oz0l.js → vhdl-CWL5dZPN.js} +0 -0
- /package/dist/web/assets/{viml-Dxwr-QNN.js → viml-BglmT14Y.js} +0 -0
- /package/dist/web/assets/{vitesse-black-Bhah7RxE.js → vitesse-black-RKFsSqQI.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-CGK6XVwc.js → vitesse-dark-D7zDWNmc.js} +0 -0
- /package/dist/web/assets/{vitesse-light-ne5kfw8h.js → vitesse-light-MAp87W5b.js} +0 -0
- /package/dist/web/assets/{vyper-ErDFqVoP.js → vyper-CgCBYg1G.js} +0 -0
- /package/dist/web/assets/{wasm-DtWvUWGr.js → wasm-BOG7ZeTT.js} +0 -0
- /package/dist/web/assets/{wasm-Bd258nS1.js → wasm-Bwl4txwp.js} +0 -0
- /package/dist/web/assets/{wenyan-EZe1IEYT.js → wenyan-DpShPKXZ.js} +0 -0
- /package/dist/web/assets/{wgsl-B1o5_I_9.js → wgsl-BakcCblK.js} +0 -0
- /package/dist/web/assets/{wikitext-C_rwp8An.js → wikitext-NroRWVAO.js} +0 -0
- /package/dist/web/assets/{wit-gbYtF3rY.js → wit-B5CM2pg6.js} +0 -0
- /package/dist/web/assets/{wolfram-DrcQFe2n.js → wolfram-7MTFBJpR.js} +0 -0
- /package/dist/web/assets/{x-B1qHmLdR.js → x-BcqfTY_7.js} +0 -0
- /package/dist/web/assets/{yaml-CzDAObLX.js → yaml-Bwr01G2K.js} +0 -0
- /package/dist/web/assets/{zenscript-B-J1phtg.js → zenscript-Dj8GATvi.js} +0 -0
- /package/dist/web/assets/{zig-BlzNia0O.js → zig-LAE-fbS-.js} +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Maximize, Minimize, Pause, Play, Volume1, Volume2, VolumeX } from "lucide-react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import { VideoSettingsMenu } from "./video-settings-menu";
|
|
4
|
+
import type { VideoTransform } from "./video-transform";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
playing: boolean;
|
|
8
|
+
position: number;
|
|
9
|
+
duration: number | null;
|
|
10
|
+
volume: number;
|
|
11
|
+
muted: boolean;
|
|
12
|
+
speed: number;
|
|
13
|
+
transform: VideoTransform;
|
|
14
|
+
fullscreen: boolean;
|
|
15
|
+
onTogglePlay: () => void;
|
|
16
|
+
/** Live value while the thumb is dragged (updates the clock only). */
|
|
17
|
+
onScrub: (t: number) => void;
|
|
18
|
+
/** Final value when the drag ends. */
|
|
19
|
+
onSeek: (t: number) => void;
|
|
20
|
+
onVolume: (v: number) => void;
|
|
21
|
+
onToggleMute: () => void;
|
|
22
|
+
onSpeed: (rate: number) => void;
|
|
23
|
+
onRotate: (direction: 1 | -1) => void;
|
|
24
|
+
onFlipH: () => void;
|
|
25
|
+
onFlipV: () => void;
|
|
26
|
+
onResetTransform: () => void;
|
|
27
|
+
onToggleFullscreen: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Bottom bar: 44px targets, primary actions in thumb reach on phones. */
|
|
31
|
+
export function VideoPlayerControls(p: Props) {
|
|
32
|
+
const VolumeIcon = p.muted || p.volume === 0 ? VolumeX : p.volume < 0.5 ? Volume1 : Volume2;
|
|
33
|
+
const commit = (e: React.SyntheticEvent<HTMLInputElement>) => p.onSeek(Number((e.target as HTMLInputElement).value));
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex items-center gap-1 px-2 py-1 bg-surface border-t border-border">
|
|
37
|
+
<Button variant="ghost" size="icon" className="size-11 shrink-0" onClick={p.onTogglePlay} aria-label={p.playing ? "Pause" : "Play"}>
|
|
38
|
+
{p.playing ? <Pause className="size-5" /> : <Play className="size-5" />}
|
|
39
|
+
</Button>
|
|
40
|
+
|
|
41
|
+
<span className="text-xs tabular-nums text-text-secondary shrink-0 min-w-[4.5rem] text-center">
|
|
42
|
+
{formatClock(p.position)}{p.duration ? ` / ${formatClock(p.duration)}` : ""}
|
|
43
|
+
</span>
|
|
44
|
+
|
|
45
|
+
{p.duration ? (
|
|
46
|
+
<input
|
|
47
|
+
type="range"
|
|
48
|
+
min={0}
|
|
49
|
+
max={p.duration}
|
|
50
|
+
step={0.1}
|
|
51
|
+
value={Math.min(p.position, p.duration)}
|
|
52
|
+
aria-label="Seek"
|
|
53
|
+
className="flex-1 h-11 accent-primary cursor-pointer min-w-0"
|
|
54
|
+
onChange={(e) => p.onScrub(Number(e.target.value))}
|
|
55
|
+
onPointerUp={commit}
|
|
56
|
+
onKeyUp={commit}
|
|
57
|
+
onTouchEnd={commit}
|
|
58
|
+
/>
|
|
59
|
+
) : <div className="flex-1" />}
|
|
60
|
+
|
|
61
|
+
{/* Volume: icon toggles mute, slider sets level. Slider hidden on phones (hardware keys). */}
|
|
62
|
+
<div className="flex items-center shrink-0">
|
|
63
|
+
<Button variant="ghost" size="icon" className="size-11" onClick={p.onToggleMute} aria-label={p.muted ? "Unmute" : "Mute"}>
|
|
64
|
+
<VolumeIcon className="size-5" />
|
|
65
|
+
</Button>
|
|
66
|
+
<input
|
|
67
|
+
type="range"
|
|
68
|
+
min={0}
|
|
69
|
+
max={1}
|
|
70
|
+
step={0.05}
|
|
71
|
+
value={p.muted ? 0 : p.volume}
|
|
72
|
+
aria-label="Volume"
|
|
73
|
+
className="hidden sm:block w-20 h-11 accent-primary cursor-pointer"
|
|
74
|
+
onChange={(e) => p.onVolume(Number(e.target.value))}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<VideoSettingsMenu
|
|
79
|
+
speed={p.speed}
|
|
80
|
+
onSpeed={p.onSpeed}
|
|
81
|
+
transform={p.transform}
|
|
82
|
+
onRotate={p.onRotate}
|
|
83
|
+
onFlipH={p.onFlipH}
|
|
84
|
+
onFlipV={p.onFlipV}
|
|
85
|
+
onResetTransform={p.onResetTransform}
|
|
86
|
+
/>
|
|
87
|
+
|
|
88
|
+
<Button variant="ghost" size="icon" className="size-11 shrink-0" onClick={p.onToggleFullscreen} aria-label={p.fullscreen ? "Exit fullscreen" : "Fullscreen"}>
|
|
89
|
+
{p.fullscreen ? <Minimize className="size-5" /> : <Maximize className="size-5" />}
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** 65 → "1:05", 3725 → "1:02:05". */
|
|
96
|
+
export function formatClock(sec: number): string {
|
|
97
|
+
const s = Math.max(0, Math.floor(Number.isFinite(sec) ? sec : 0));
|
|
98
|
+
const h = Math.floor(s / 3600);
|
|
99
|
+
const m = Math.floor((s % 3600) / 60);
|
|
100
|
+
const r = s % 60;
|
|
101
|
+
const mm = h ? String(m).padStart(2, "0") : String(m);
|
|
102
|
+
return `${h ? `${h}:` : ""}${mm}:${String(r).padStart(2, "0")}`;
|
|
103
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Loader2 } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { rawMediaUrl, transcodeMediaUrl } from "@/lib/media-url";
|
|
5
|
+
import { VideoPlayerControls } from "./video-player-controls";
|
|
6
|
+
import { SPEED_STEPS } from "./video-settings-menu";
|
|
7
|
+
import { useVideoKeyboardShortcuts, type VideoPlayerActions } from "./use-video-keyboard-shortcuts";
|
|
8
|
+
import {
|
|
9
|
+
IDENTITY_TRANSFORM, fittedMaxSize, rotateClockwise, rotateCounterClockwise, toCssTransform, type VideoTransform,
|
|
10
|
+
} from "./video-transform";
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
filePath: string;
|
|
14
|
+
projectName: string;
|
|
15
|
+
/**
|
|
16
|
+
* `native`: the browser decodes the file itself, streamed by Range requests — seeking
|
|
17
|
+
* is a `currentTime` assignment. `transcode`: ffmpeg pipes a live fragmented MP4 with
|
|
18
|
+
* no duration, so the bar uses ffprobe's duration and a seek restarts the stream at
|
|
19
|
+
* `?start=`; position on screen = start + currentTime.
|
|
20
|
+
*/
|
|
21
|
+
mode: "native" | "transcode";
|
|
22
|
+
/** Seconds from ffprobe (transcode mode); native mode reads it from the element. */
|
|
23
|
+
probeDuration?: number | null;
|
|
24
|
+
/** Native decode failed (e.g. HEVC in .mov) — caller may retry through ffmpeg. */
|
|
25
|
+
onNativeError?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Custom player shared by both playback paths so rotation, speed and shortcuts behave the same. */
|
|
29
|
+
export function VideoPlayer({ filePath, projectName, mode, probeDuration = null, onNativeError }: Props) {
|
|
30
|
+
const rootRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
const stageRef = useRef<HTMLDivElement>(null);
|
|
32
|
+
const videoRef = useRef<HTMLVideoElement>(null);
|
|
33
|
+
const resumeRef = useRef(true);
|
|
34
|
+
|
|
35
|
+
const [start, setStart] = useState(0);
|
|
36
|
+
const [elapsed, setElapsed] = useState(0);
|
|
37
|
+
const [nativeDuration, setNativeDuration] = useState<number | null>(null);
|
|
38
|
+
const [playing, setPlaying] = useState(false);
|
|
39
|
+
const [waiting, setWaiting] = useState(true);
|
|
40
|
+
const [scrub, setScrub] = useState<number | null>(null);
|
|
41
|
+
const [volume, setVolume] = useState(1);
|
|
42
|
+
const [muted, setMuted] = useState(false);
|
|
43
|
+
const [speed, setSpeed] = useState(1);
|
|
44
|
+
const [transform, setTransform] = useState<VideoTransform>(IDENTITY_TRANSFORM);
|
|
45
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
46
|
+
const [box, setBox] = useState({ width: 0, height: 0 });
|
|
47
|
+
const [error, setError] = useState<string | null>(null);
|
|
48
|
+
|
|
49
|
+
const isTranscode = mode === "transcode";
|
|
50
|
+
const src = isTranscode ? transcodeMediaUrl(filePath, projectName, start) : rawMediaUrl(filePath, projectName);
|
|
51
|
+
const duration = isTranscode ? probeDuration : nativeDuration;
|
|
52
|
+
const position = scrub ?? (isTranscode ? start + elapsed : elapsed);
|
|
53
|
+
|
|
54
|
+
// New source (mount, other file, or transcode restart) → back to loading state.
|
|
55
|
+
useEffect(() => { setElapsed(0); setWaiting(true); setError(null); }, [src]);
|
|
56
|
+
useEffect(() => { setStart(0); setNativeDuration(null); setTransform(IDENTITY_TRANSFORM); }, [filePath, mode]);
|
|
57
|
+
|
|
58
|
+
// Element properties survive src changes but not a remount; keep them in sync with state.
|
|
59
|
+
useEffect(() => { const v = videoRef.current; if (v) { v.volume = volume; v.muted = muted; } }, [volume, muted]);
|
|
60
|
+
useEffect(() => { const v = videoRef.current; if (v) v.playbackRate = speed; }, [speed, src]);
|
|
61
|
+
|
|
62
|
+
// A detached <video> keeps its network fetch alive until garbage-collected, which
|
|
63
|
+
// would leave ffmpeg encoding for nobody. Emptying the source aborts the load now.
|
|
64
|
+
// `isConnected` guards StrictMode's simulated unmount, where the element stays in the
|
|
65
|
+
// DOM and React would not re-apply the src attribute afterwards.
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
const v = videoRef.current;
|
|
68
|
+
return () => { if (v && !v.isConnected) { v.pause(); v.removeAttribute("src"); v.load(); } };
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
const onFs = () => setFullscreen(document.fullscreenElement === rootRef.current);
|
|
73
|
+
document.addEventListener("fullscreenchange", onFs);
|
|
74
|
+
return () => document.removeEventListener("fullscreenchange", onFs);
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
// Rotated 90°/270° video must be sized against the swapped stage axes.
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const el = stageRef.current;
|
|
80
|
+
if (!el) return;
|
|
81
|
+
const ro = new ResizeObserver(() => setBox({ width: el.clientWidth, height: el.clientHeight }));
|
|
82
|
+
ro.observe(el);
|
|
83
|
+
return () => ro.disconnect();
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
const seekTo = useCallback((t: number) => {
|
|
87
|
+
const v = videoRef.current;
|
|
88
|
+
const clamped = Math.max(0, duration ? Math.min(t, duration - 0.5) : t);
|
|
89
|
+
setScrub(null);
|
|
90
|
+
if (!isTranscode) { if (v) v.currentTime = clamped; return; }
|
|
91
|
+
// Restarting ffmpeg for a sub-second nudge is wasteful.
|
|
92
|
+
if (Math.abs(clamped - (start + (v?.currentTime ?? 0))) < 0.5) return;
|
|
93
|
+
resumeRef.current = !(v?.paused ?? false);
|
|
94
|
+
setStart(clamped);
|
|
95
|
+
}, [duration, isTranscode, start]);
|
|
96
|
+
|
|
97
|
+
const actions = useMemo<VideoPlayerActions>(() => ({
|
|
98
|
+
togglePlay: () => { const v = videoRef.current; if (!v) return; if (v.paused) void v.play().catch(() => {}); else v.pause(); },
|
|
99
|
+
seekBy: (d) => seekTo((isTranscode ? start : 0) + (videoRef.current?.currentTime ?? 0) + d),
|
|
100
|
+
toggleFullscreen: () => {
|
|
101
|
+
if (document.fullscreenElement) void document.exitFullscreen();
|
|
102
|
+
else void rootRef.current?.requestFullscreen?.();
|
|
103
|
+
},
|
|
104
|
+
toggleMute: () => setMuted((m) => !m),
|
|
105
|
+
volumeBy: (d) => { setMuted(false); setVolume((v) => Math.min(1, Math.max(0, Math.round((v + d) * 100) / 100))); },
|
|
106
|
+
speedStep: (dir) => setSpeed((s) => {
|
|
107
|
+
const i = SPEED_STEPS.indexOf(s as typeof SPEED_STEPS[number]);
|
|
108
|
+
return SPEED_STEPS[Math.min(SPEED_STEPS.length - 1, Math.max(0, (i === -1 ? 3 : i) + dir))] ?? 1;
|
|
109
|
+
}),
|
|
110
|
+
rotate: (dir) => setTransform((t) => (dir === 1 ? rotateClockwise(t) : rotateCounterClockwise(t))),
|
|
111
|
+
}), [seekTo, isTranscode, start]);
|
|
112
|
+
|
|
113
|
+
const onKeyDown = useVideoKeyboardShortcuts(actions);
|
|
114
|
+
const fitted = fittedMaxSize(transform, box);
|
|
115
|
+
|
|
116
|
+
if (error) {
|
|
117
|
+
return (
|
|
118
|
+
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary p-4 text-center">
|
|
119
|
+
<p className="text-sm">{error}</p>
|
|
120
|
+
<Button variant="outline" size="sm" onClick={() => { setError(null); setStart((s) => s + 0.001); }}>Retry</Button>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div
|
|
127
|
+
ref={rootRef}
|
|
128
|
+
data-testid="video-player"
|
|
129
|
+
tabIndex={0}
|
|
130
|
+
onKeyDown={onKeyDown}
|
|
131
|
+
className="flex flex-col h-full bg-black outline-none focus-visible:ring-1 focus-visible:ring-primary/60"
|
|
132
|
+
>
|
|
133
|
+
<div
|
|
134
|
+
ref={stageRef}
|
|
135
|
+
className="relative flex-1 min-h-0 flex items-center justify-center overflow-hidden"
|
|
136
|
+
onClick={(e) => { rootRef.current?.focus(); if (e.detail === 1) actions.togglePlay(); }}
|
|
137
|
+
onDoubleClick={actions.toggleFullscreen}
|
|
138
|
+
>
|
|
139
|
+
<video
|
|
140
|
+
ref={videoRef}
|
|
141
|
+
src={src}
|
|
142
|
+
autoPlay
|
|
143
|
+
playsInline
|
|
144
|
+
preload={isTranscode ? "auto" : "metadata"}
|
|
145
|
+
style={{ transform: toCssTransform(transform), maxWidth: fitted.maxWidth || undefined, maxHeight: fitted.maxHeight || undefined }}
|
|
146
|
+
onLoadedMetadata={(e) => {
|
|
147
|
+
const v = e.currentTarget;
|
|
148
|
+
v.playbackRate = speed;
|
|
149
|
+
if (!isTranscode && Number.isFinite(v.duration)) setNativeDuration(v.duration);
|
|
150
|
+
if (isTranscode && !resumeRef.current) v.pause();
|
|
151
|
+
}}
|
|
152
|
+
onPlay={() => setPlaying(true)}
|
|
153
|
+
onPause={() => setPlaying(false)}
|
|
154
|
+
onWaiting={() => setWaiting(true)}
|
|
155
|
+
onPlaying={() => setWaiting(false)}
|
|
156
|
+
onCanPlay={() => setWaiting(false)}
|
|
157
|
+
onTimeUpdate={(e) => setElapsed(e.currentTarget.currentTime)}
|
|
158
|
+
onEnded={() => setPlaying(false)}
|
|
159
|
+
onError={() => {
|
|
160
|
+
if (!isTranscode) onNativeError?.();
|
|
161
|
+
else setError("Transcoding failed. ffmpeg may have stopped or the file is corrupt.");
|
|
162
|
+
}}
|
|
163
|
+
/>
|
|
164
|
+
{waiting && <Loader2 className="absolute size-8 animate-spin text-white/80 pointer-events-none" />}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<VideoPlayerControls
|
|
168
|
+
playing={playing}
|
|
169
|
+
position={position}
|
|
170
|
+
duration={duration}
|
|
171
|
+
volume={volume}
|
|
172
|
+
muted={muted}
|
|
173
|
+
speed={speed}
|
|
174
|
+
transform={transform}
|
|
175
|
+
fullscreen={fullscreen}
|
|
176
|
+
onTogglePlay={actions.togglePlay}
|
|
177
|
+
onScrub={setScrub}
|
|
178
|
+
onSeek={seekTo}
|
|
179
|
+
onVolume={(v) => { setVolume(v); setMuted(v === 0); }}
|
|
180
|
+
onToggleMute={actions.toggleMute}
|
|
181
|
+
onSpeed={setSpeed}
|
|
182
|
+
onRotate={actions.rotate}
|
|
183
|
+
onFlipH={() => setTransform((t) => ({ ...t, flipH: !t.flipH }))}
|
|
184
|
+
onFlipV={() => setTransform((t) => ({ ...t, flipV: !t.flipV }))}
|
|
185
|
+
onResetTransform={() => setTransform(IDENTITY_TRANSFORM)}
|
|
186
|
+
onToggleFullscreen={actions.toggleFullscreen}
|
|
187
|
+
/>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { FlipHorizontal2, FlipVertical2, RotateCcw, RotateCw, Settings, Undo2 } from "lucide-react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import {
|
|
4
|
+
DropdownMenu,
|
|
5
|
+
DropdownMenuContent,
|
|
6
|
+
DropdownMenuItem,
|
|
7
|
+
DropdownMenuLabel,
|
|
8
|
+
DropdownMenuRadioGroup,
|
|
9
|
+
DropdownMenuRadioItem,
|
|
10
|
+
DropdownMenuSeparator,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
} from "@/components/ui/dropdown-menu";
|
|
13
|
+
import { isIdentity, type VideoTransform } from "./video-transform";
|
|
14
|
+
import { VIDEO_SHORTCUTS } from "./use-video-keyboard-shortcuts";
|
|
15
|
+
|
|
16
|
+
export const SPEED_STEPS = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 3] as const;
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
speed: number;
|
|
20
|
+
onSpeed: (rate: number) => void;
|
|
21
|
+
transform: VideoTransform;
|
|
22
|
+
onRotate: (direction: 1 | -1) => void;
|
|
23
|
+
onFlipH: () => void;
|
|
24
|
+
onFlipV: () => void;
|
|
25
|
+
onResetTransform: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Gear menu: playback speed, orientation fixes, and the shortcut cheat-sheet. */
|
|
29
|
+
export function VideoSettingsMenu({ speed, onSpeed, transform, onRotate, onFlipH, onFlipV, onResetTransform }: Props) {
|
|
30
|
+
return (
|
|
31
|
+
<DropdownMenu>
|
|
32
|
+
<DropdownMenuTrigger asChild>
|
|
33
|
+
<Button variant="ghost" size="icon" className="size-11 shrink-0 relative" aria-label="Playback settings">
|
|
34
|
+
<Settings className="size-5" />
|
|
35
|
+
{speed !== 1 && (
|
|
36
|
+
<span className="absolute -top-0.5 -right-0.5 text-[10px] leading-none px-1 rounded bg-primary text-primary-foreground tabular-nums">
|
|
37
|
+
{speed}x
|
|
38
|
+
</span>
|
|
39
|
+
)}
|
|
40
|
+
</Button>
|
|
41
|
+
</DropdownMenuTrigger>
|
|
42
|
+
{/* Flat list on purpose: sub-menus are awkward on touch screens. */}
|
|
43
|
+
<DropdownMenuContent align="end" className="w-60 max-h-[70vh] overflow-y-auto">
|
|
44
|
+
<DropdownMenuLabel>Speed</DropdownMenuLabel>
|
|
45
|
+
<DropdownMenuRadioGroup value={String(speed)} onValueChange={(v) => onSpeed(Number(v))}>
|
|
46
|
+
<div className="grid grid-cols-4 gap-1 px-1 pb-1">
|
|
47
|
+
{SPEED_STEPS.map((s) => (
|
|
48
|
+
<DropdownMenuRadioItem
|
|
49
|
+
key={s}
|
|
50
|
+
value={String(s)}
|
|
51
|
+
className="justify-center pl-2 min-h-11 text-xs tabular-nums data-[state=checked]:bg-accent [&>span:first-child]:hidden"
|
|
52
|
+
>
|
|
53
|
+
{s}x
|
|
54
|
+
</DropdownMenuRadioItem>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
</DropdownMenuRadioGroup>
|
|
58
|
+
|
|
59
|
+
<DropdownMenuSeparator />
|
|
60
|
+
<DropdownMenuLabel>Orientation</DropdownMenuLabel>
|
|
61
|
+
<DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onRotate(1); }}>
|
|
62
|
+
<RotateCw className="size-4" /> Rotate right <span className="ml-auto text-xs text-text-subtle">R</span>
|
|
63
|
+
</DropdownMenuItem>
|
|
64
|
+
<DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onRotate(-1); }}>
|
|
65
|
+
<RotateCcw className="size-4" /> Rotate left <span className="ml-auto text-xs text-text-subtle">⇧R</span>
|
|
66
|
+
</DropdownMenuItem>
|
|
67
|
+
<DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onFlipH(); }}>
|
|
68
|
+
<FlipHorizontal2 className="size-4" /> Flip horizontal {transform.flipH && <span className="ml-auto text-xs">✓</span>}
|
|
69
|
+
</DropdownMenuItem>
|
|
70
|
+
<DropdownMenuItem className="min-h-11" onSelect={(e) => { e.preventDefault(); onFlipV(); }}>
|
|
71
|
+
<FlipVertical2 className="size-4" /> Flip vertical {transform.flipV && <span className="ml-auto text-xs">✓</span>}
|
|
72
|
+
</DropdownMenuItem>
|
|
73
|
+
{!isIdentity(transform) && (
|
|
74
|
+
<DropdownMenuItem className="min-h-11" onSelect={onResetTransform}>
|
|
75
|
+
<Undo2 className="size-4" /> Reset orientation
|
|
76
|
+
</DropdownMenuItem>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
<DropdownMenuSeparator />
|
|
80
|
+
<DropdownMenuLabel>Keyboard</DropdownMenuLabel>
|
|
81
|
+
<div className="px-2 pb-2 grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 text-xs text-text-secondary">
|
|
82
|
+
{VIDEO_SHORTCUTS.map(([keys, action]) => (
|
|
83
|
+
<div key={keys} className="contents">
|
|
84
|
+
<kbd className="font-mono text-text-subtle whitespace-nowrap">{keys}</kbd>
|
|
85
|
+
<span>{action}</span>
|
|
86
|
+
</div>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
</DropdownMenuContent>
|
|
90
|
+
</DropdownMenu>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rotation/flip state for the video player, applied as a CSS transform.
|
|
3
|
+
*
|
|
4
|
+
* Camera files are often recorded sideways (dashcams, phones) and carry no rotation
|
|
5
|
+
* metadata, so the viewer needs a manual fix. Kept as pure functions so the layout
|
|
6
|
+
* math is testable without a DOM.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface VideoTransform {
|
|
10
|
+
/** Clockwise degrees, one of 0 / 90 / 180 / 270. */
|
|
11
|
+
rotation: 0 | 90 | 180 | 270;
|
|
12
|
+
flipH: boolean;
|
|
13
|
+
flipV: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const IDENTITY_TRANSFORM: VideoTransform = { rotation: 0, flipH: false, flipV: false };
|
|
17
|
+
|
|
18
|
+
export function rotateClockwise(t: VideoTransform): VideoTransform {
|
|
19
|
+
return { ...t, rotation: ((t.rotation + 90) % 360) as VideoTransform["rotation"] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function rotateCounterClockwise(t: VideoTransform): VideoTransform {
|
|
23
|
+
return { ...t, rotation: ((t.rotation + 270) % 360) as VideoTransform["rotation"] };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function isIdentity(t: VideoTransform): boolean {
|
|
27
|
+
return t.rotation === 0 && !t.flipH && !t.flipV;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** CSS `transform` value. Flip is applied in the video's own axes, before rotation. */
|
|
31
|
+
export function toCssTransform(t: VideoTransform): string {
|
|
32
|
+
const parts: string[] = [];
|
|
33
|
+
if (t.rotation) parts.push(`rotate(${t.rotation}deg)`);
|
|
34
|
+
if (t.flipH) parts.push("scaleX(-1)");
|
|
35
|
+
if (t.flipV) parts.push("scaleY(-1)");
|
|
36
|
+
return parts.length ? parts.join(" ") : "none";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Size the element must be laid out at so that, once rotated, it fits `box`.
|
|
41
|
+
* A 90°/270° rotation swaps the visual axes, so the element's width is limited by
|
|
42
|
+
* the box's height and vice versa. Returns max-width/max-height in CSS pixels.
|
|
43
|
+
*/
|
|
44
|
+
export function fittedMaxSize(t: VideoTransform, box: { width: number; height: number }): { maxWidth: number; maxHeight: number } {
|
|
45
|
+
const swapped = t.rotation === 90 || t.rotation === 270;
|
|
46
|
+
return swapped
|
|
47
|
+
? { maxWidth: box.height, maxHeight: box.width }
|
|
48
|
+
: { maxWidth: box.width, maxHeight: box.height };
|
|
49
|
+
}
|
|
@@ -1,23 +1,84 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Download, FileWarning, Loader2 } from "lucide-react";
|
|
3
|
+
import { Button } from "@/components/ui/button";
|
|
4
|
+
import { extensionOf } from "@/components/os-explorer/can-open-in-ppm";
|
|
5
|
+
import { probeMedia, rawMediaUrl, type MediaProbeInfo } from "@/lib/media-url";
|
|
6
|
+
import { VideoPlayer } from "./video-player/video-player";
|
|
3
7
|
|
|
8
|
+
/** Containers every browser demuxes natively; anything else goes through ffmpeg. */
|
|
9
|
+
const NATIVE_CONTAINERS = new Set(["mp4", "m4v", "webm", "ogg", "ogv", "mov"]);
|
|
10
|
+
|
|
11
|
+
type Mode =
|
|
12
|
+
| { kind: "native" }
|
|
13
|
+
| { kind: "probing" }
|
|
14
|
+
| { kind: "transcode"; probe: MediaProbeInfo }
|
|
15
|
+
| { kind: "unsupported"; probe: MediaProbeInfo | null };
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Streams the file straight from its URL so the browser fetches it in byte ranges
|
|
19
|
+
* (instant start, cheap seeking, no whole-file blob in memory). Files the browser
|
|
20
|
+
* cannot decode — AVI, MKV, or an mp4 with HEVC inside — fall back to server-side
|
|
21
|
+
* transcoding when ffmpeg is installed.
|
|
22
|
+
*/
|
|
4
23
|
export function VideoPreview({ filePath, projectName }: { filePath: string; projectName: string }) {
|
|
5
|
-
const
|
|
24
|
+
const ext = extensionOf(filePath);
|
|
25
|
+
const [mode, setMode] = useState<Mode>(() => (NATIVE_CONTAINERS.has(ext) ? { kind: "native" } : { kind: "probing" }));
|
|
6
26
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
// Reset when a different file is shown in the same tab.
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
setMode(NATIVE_CONTAINERS.has(extensionOf(filePath)) ? { kind: "native" } : { kind: "probing" });
|
|
30
|
+
}, [filePath]);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (mode.kind !== "probing") return;
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
probeMedia(filePath, projectName)
|
|
36
|
+
.then((probe) => {
|
|
37
|
+
if (cancelled) return;
|
|
38
|
+
setMode(probe.transcodable ? { kind: "transcode", probe } : { kind: "unsupported", probe });
|
|
39
|
+
})
|
|
40
|
+
.catch(() => { if (!cancelled) setMode({ kind: "unsupported", probe: null }); });
|
|
41
|
+
return () => { cancelled = true; };
|
|
42
|
+
}, [mode.kind, filePath, projectName]);
|
|
43
|
+
|
|
44
|
+
if (mode.kind === "probing") {
|
|
16
45
|
return <div className="flex items-center justify-center h-full"><Loader2 className="size-5 animate-spin text-text-subtle" /></div>;
|
|
17
46
|
}
|
|
47
|
+
if (mode.kind === "transcode") {
|
|
48
|
+
return <VideoPlayer filePath={filePath} projectName={projectName} mode="transcode" probeDuration={mode.probe.duration} />;
|
|
49
|
+
}
|
|
50
|
+
if (mode.kind === "unsupported") {
|
|
51
|
+
return <UnsupportedVideo filePath={filePath} projectName={projectName} probe={mode.probe} />;
|
|
52
|
+
}
|
|
53
|
+
return (
|
|
54
|
+
<VideoPlayer
|
|
55
|
+
filePath={filePath}
|
|
56
|
+
projectName={projectName}
|
|
57
|
+
mode="native"
|
|
58
|
+
// A native container can still hide a codec the browser lacks (HEVC in .mov);
|
|
59
|
+
// let ffmpeg have a go before giving up.
|
|
60
|
+
onNativeError={() => setMode({ kind: "probing" })}
|
|
61
|
+
/>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function UnsupportedVideo({ filePath, projectName, probe }: { filePath: string; projectName: string; probe: MediaProbeInfo | null }) {
|
|
66
|
+
const codecs = [probe?.video?.codec, probe?.audio?.codec].filter(Boolean).join(" + ");
|
|
18
67
|
return (
|
|
19
|
-
<div className="flex items-center justify-center h-full
|
|
20
|
-
<
|
|
68
|
+
<div className="flex flex-col items-center justify-center h-full gap-3 text-text-secondary p-6 text-center">
|
|
69
|
+
<FileWarning className="size-10 text-text-subtle" />
|
|
70
|
+
<p className="text-sm">
|
|
71
|
+
This video cannot be played in the browser{codecs ? ` (${codecs})` : ""}.
|
|
72
|
+
</p>
|
|
73
|
+
<p className="text-xs text-text-subtle max-w-sm">
|
|
74
|
+
Install <code>ffmpeg</code> on the PPM server (e.g. <code>winget install ffmpeg</code>, <code>brew install ffmpeg</code>,{" "}
|
|
75
|
+
<code>apt install ffmpeg</code>) and restart PPM to transcode it on the fly.
|
|
76
|
+
</p>
|
|
77
|
+
<Button asChild variant="outline" size="sm" className="h-11 px-4">
|
|
78
|
+
<a href={`${rawMediaUrl(filePath, projectName)}&download=true`} download>
|
|
79
|
+
<Download className="size-4 mr-2" /> Download file
|
|
80
|
+
</a>
|
|
81
|
+
</Button>
|
|
21
82
|
</div>
|
|
22
83
|
);
|
|
23
84
|
}
|
|
@@ -41,6 +41,7 @@ import { flattenVisibleTree, type InputRow } from "./flatten-visible-tree";
|
|
|
41
41
|
import { downloadFile, downloadFolder } from "@/lib/file-download";
|
|
42
42
|
import { api, projectUrl } from "@/lib/api-client";
|
|
43
43
|
import { openExplorer } from "@/components/os-explorer/open-explorer";
|
|
44
|
+
import { openTerminalAt } from "@/lib/open-terminal-at";
|
|
44
45
|
import { fsChanged, onFsChanged } from "@/components/os-explorer/explorer-store";
|
|
45
46
|
import { dirnameOf } from "@/components/os-explorer/format-file-meta";
|
|
46
47
|
import { useDragAutoScroll } from "@/components/os-explorer/dnd/use-drag-auto-scroll";
|
|
@@ -378,6 +379,11 @@ export function FileTree({ onFileOpen }: FileTreeProps = {}) {
|
|
|
378
379
|
void openExplorer(absoluteProjectPath(activeProject!.path, target));
|
|
379
380
|
return;
|
|
380
381
|
}
|
|
382
|
+
if (action === "open-in-terminal") {
|
|
383
|
+
const target = node.type === "directory" ? node.path : parentDirOfRelative(node.path);
|
|
384
|
+
openTerminalAt(absoluteProjectPath(activeProject!.path, target), activeProject!.name);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
381
387
|
if (action === "paste" && node.type === "directory") {
|
|
382
388
|
pasteFiles(node.path);
|
|
383
389
|
return;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Context menu items for a tree node (file or folder).
|
|
3
3
|
*/
|
|
4
|
-
import { Download, FolderOpen } from "lucide-react";
|
|
4
|
+
import { Download, FolderOpen, TerminalSquare } from "lucide-react";
|
|
5
5
|
import type { FileNode, ClipboardState } from "@/stores/file-store";
|
|
6
6
|
import {
|
|
7
7
|
ContextMenuContent,
|
|
@@ -43,6 +43,10 @@ export function TreeNodeContextMenu({
|
|
|
43
43
|
<FolderOpen className="size-3.5 mr-2" />
|
|
44
44
|
Open in File Explorer
|
|
45
45
|
</ContextMenuItem>
|
|
46
|
+
<ContextMenuItem onClick={() => onAction("open-in-terminal", node)}>
|
|
47
|
+
<TerminalSquare className="size-3.5 mr-2" />
|
|
48
|
+
Open in Terminal
|
|
49
|
+
</ContextMenuItem>
|
|
46
50
|
<ContextMenuSeparator />
|
|
47
51
|
</>
|
|
48
52
|
)}
|
|
@@ -21,6 +21,7 @@ export interface WindowContentProps {
|
|
|
21
21
|
|
|
22
22
|
export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentType<WindowContentProps>>> = {
|
|
23
23
|
explorer: lazy(() => import("@/components/os-explorer/explorer-window-content")),
|
|
24
|
+
"team-member": lazy(() => import("@/components/chat/team-member-window-content")),
|
|
24
25
|
};
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -41,6 +42,10 @@ export function chromeFor(kind: WindowKind): WindowChrome | undefined {
|
|
|
41
42
|
export function windowTitle(kind: WindowKind, payload?: Record<string, unknown>): string {
|
|
42
43
|
const explicit = payload?.title;
|
|
43
44
|
if (typeof explicit === "string" && explicit.trim()) return explicit;
|
|
45
|
+
if (kind === "team-member") {
|
|
46
|
+
const member = payload?.memberName;
|
|
47
|
+
return typeof member === "string" && member ? `Session — ${member}` : "Team member";
|
|
48
|
+
}
|
|
44
49
|
const path = payload?.path;
|
|
45
50
|
if (kind === "explorer" && typeof path === "string" && path) {
|
|
46
51
|
const segments = path.split(/[\\/]/).filter(Boolean);
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import type { Rect } from "./window-geometry";
|
|
4
4
|
|
|
5
5
|
/** Content families the layer can host. The registry maps each kind to its body component. */
|
|
6
|
-
export type WindowKind = "explorer";
|
|
6
|
+
export type WindowKind = "explorer" | "team-member";
|
|
7
7
|
|
|
8
8
|
export type WindowVisualState = "normal" | "maximized" | "minimized";
|
|
9
9
|
|