Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Merge branch 'main' into rm-agents | 1 | import { useEffect, useState } from "react"; |
| 2 | ||
| 3 | import { useDesktopRunner } from "../lib/desktop-runner"; | |
| 4 | import { type SectionId, desktopSettings, sectionOf } from "../lib/desktop-settings"; | |
| 5 | import { DesktopRunnerMenu } from "./desktop-runner-menu"; | |
| 6 | import { DesktopSettingsDialog } from "./desktop-settings"; | |
| 7 | ||
| 8 | /** Where the dot sits when the app doesn't say: near the right of the title bar, before the window's buttons. */ | |
| 9 | const DOT_AT = 0.86; | |
| 10 | ||
| 11 | /** | |
| 12 | * Inside the desktop app, on every page: the app's settings dialog and the | |
| 13 | * runner's menu, which this page draws over itself since it fills the | |
| 14 | * window under the title bar (lib/desktop-settings.ts). It tells the app it | |
| 15 | * can as soon as it mounts, and no longer when it goes, so the app opens | |
| 16 | * them here rather than in windows of their own. It draws nothing until | |
| 17 | * the app asks. Outside the app, and in an app too old to ask, it is | |
| 18 | * nothing at all. | |
| 19 | */ | |
| 20 | export function DesktopApp() { | |
| 21 | // Found once hydrated: the server never has it, and the first render matches the server's. | |
| 22 | const [bridge, setBridge] = useState<ReturnType<typeof desktopSettings>>(null); | |
| 23 | useEffect(() => setBridge(desktopSettings()), []); | |
| 24 | const runner = useDesktopRunner(); | |
| 25 | const [settings, setSettings] = useState<{ open: boolean; section: SectionId; check: number }>({ open: false, section: "general", check: 0 }); | |
| 26 | const [menu, setMenu] = useState<{ open: boolean; at: number }>({ open: false, at: DOT_AT }); | |
| 27 | ||
| 28 | useEffect(() => { | |
| 29 | if (!bridge) return; | |
| 30 | const stopOpen = bridge.onOpen((opening) => { | |
| 31 | setMenu((now) => ({ ...now, open: false })); | |
| 32 | setSettings((now) => ({ | |
| 33 | open: true, | |
| 34 | // Opened again with no section named, it opens where it was. | |
| 35 | section: opening?.section ? sectionOf(opening.section) : now.open ? now.section : "general", | |
| 36 | check: opening?.check ? now.check + 1 : now.check, | |
| 37 | })); | |
| 38 | }); | |
| 39 | const stopMenu = bridge.onRunnerMenu((asked) => { | |
| 40 | const at = typeof asked?.at === "number" && Number.isFinite(asked.at) ? asked.at : null; | |
| 41 | setMenu((now) => ({ open: asked?.action === "toggle" ? !now.open : true, at: at ?? now.at })); | |
| 42 | }); | |
| 43 | void bridge.ready(true).catch(() => {}); | |
| 44 | return () => { | |
| 45 | stopOpen(); | |
| 46 | stopMenu(); | |
| 47 | void bridge.ready(false).catch(() => {}); | |
| 48 | }; | |
| 49 | }, [bridge]); | |
| 50 | ||
| 51 | // The title bar's dot shows whether its menu is open. | |
| 52 | useEffect(() => { | |
| 53 | bridge?.setRunnerMenuOpen(menu.open); | |
| 54 | }, [bridge, menu.open]); | |
| 55 | ||
| 56 | if (!bridge) return null; | |
| 57 | return ( | |
| 58 | <> | |
| 59 | <DesktopRunnerMenu | |
| 60 | open={menu.open} | |
| 61 | at={menu.at} | |
| 62 | runner={runner} | |
| 63 | onOpenChange={(open) => setMenu((now) => ({ ...now, open }))} | |
| 64 | onSettings={() => { | |
| 65 | setMenu((now) => ({ ...now, open: false })); | |
| 66 | setSettings((now) => ({ ...now, open: true, section: "runner" })); | |
| 67 | }} | |
| 68 | /> | |
| 69 | <DesktopSettingsDialog | |
| 70 | open={settings.open} | |
| 71 | section={settings.section} | |
| 72 | check={settings.check} | |
| 73 | runner={runner} | |
| 74 | onOpenChange={(open) => setSettings((now) => ({ ...now, open }))} | |
| 75 | onSection={(section) => setSettings((now) => ({ ...now, section }))} | |
| 76 | /> | |
| 77 | </> | |
| 78 | ); | |
| 79 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.