/** * The floating power trigger: a Windows-style shutdown icon button at the * bottom-right of the page. Clicking it opens a confirm dialog; the confirmed * request POSTs to the loopback-only /api/dsh-desktop-launcher/shutdown route, * and the host process exits gracefully (ctx.appExit) a beat after the * response. */ import { useCallback, useEffect, useState } from 'react' import { createPortal } from 'react-dom' import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots' import { closeCurrentPage, requestShutdown } from './shutdown-api.ts' import css from './shutdown.module.css' /** The inject face the footer registration provides. */ export interface ShutdownEntryFace { /** Whether the confirm dialog is required before exiting (settings-backed). */ confirmShutdown: () => boolean } /** Entry props: the column state + the locale seat + the face. */ export type ShutdownEntryProps = PropsLocale<'desktop-launcher'> & { wide: boolean; floating?: boolean } & InjectFace /** Dialog view state. */ type View = 'closed' | 'confirm' | 'shutting-down' | 'error' /** * The Windows-style power icon (a circle with a vertical line at the top). * @param size - rendered side length. * @returns the icon element. */ function PowerIcon({ size }: { size: number }) { return ( ) } /** * Render the shutdown trigger and the confirm dialog. * @param props - column state, locale copy, and the confirm gate. * @returns the entry element tree. */ export function ShutdownEntry(props: ShutdownEntryProps) { const { t, wide, floating = false } = props const [view, setView] = useState('closed') const [error, setError] = useState(undefined) const close = useCallback(() => { setView(current => current === 'shutting-down' ? current : 'closed') }, []) const performShutdown = useCallback(async () => { setError(undefined) setView('shutting-down') try { await requestShutdown() // The host acknowledges first and exits a beat later; close (or blank) // the page before the process goes away. closeCurrentPage() } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)) setView('error') } }, []) const handleTrigger = useCallback(() => { if (props.confirmShutdown()) setView('confirm') else void performShutdown() }, [performShutdown, props.confirmShutdown]) // Esc closes the dialog (never while an exit is in flight). useEffect(() => { if (view === 'closed') return const onKeyDown = (event: KeyboardEvent): void => { if (event.key === 'Escape') close() } window.addEventListener('keydown', onKeyDown) return () => { window.removeEventListener('keydown', onKeyDown) } }, [view, close]) return ( <> {view !== 'closed' && createPortal((
), document.body)} ) }