// A computer's desktop in the simulation, after Java's GUIDesktopWindow and // GUIDesktopPanel: a window titled with the computer's label and address // (the label alone where it is the address), Java's background, the // software installation's icon first and then the installed programs in the // order of Java's list, a grey taskbar with "home screen" in the middle and, // on the right, the network icon — lit while the computer's cable carries a // frame — that shows its network as it runs (NetworkInfoPanel). Java's panel // has a "change" button under read-only fields, which writes back what they // show; it is left out. // // On a wide screen the window lies over the surface and moves by its title // bar, as Java's windows do; on a phone it fills the surface. A program's // icon opens its window over the desktop (ProgramFrame), the home button // leaves it; so far the command line has one (F4.6b), the others follow. import { useCallback, useEffect, useState, type PointerEvent as ReactPointerEvent } from 'react'; import type * as Y from 'yjs'; import { desktopView } from '@mdg-filius/doc'; import background from '../assets/desktop/hintergrundbild.png'; import networkOff from '../assets/desktop/netzwek_aus.png'; import networkOn from '../assets/desktop/netzwek_c.png'; import { ICON_URLS } from '../canvas/icons.js'; import { useLanguage, useTranslate } from '../i18n/i18n.js'; import type { Simulation } from '../simulation/useSimulation.js'; import { ProgramFrame } from './ProgramFrame.js'; import { desktopPrograms, INSTALLATION_ICON, JAVA_PROGRAMS, type DesktopProgram } from './programs.js'; import { InstallationPanel } from './InstallationPanel.js'; import { TerminalPanel } from './TerminalPanel.js'; import './desktop.css'; export interface DesktopWindowProps { readonly doc: Y.Doc; readonly id: string; /** The room: the computer's network as it runs, its programs' windows. */ readonly simulation: Simulation; /** Whether its cable carried a frame at the last tick. */ readonly active: boolean; /** The window's top left corner on the surface. */ readonly x: number; readonly y: number; /** Its place in the stack of desktops. */ readonly z: number; /** The window open on it — a program's or the installation's —, kept by the editor over a return to the design (Java's desktop keeps it). */ readonly program: DesktopWindowName | null; readonly onProgram: (program: DesktopWindowName | null) => void; /** The person's origin, for what the installation changes in the document. */ readonly origin: object; readonly onMove: (x: number, y: number) => void; readonly onFront: () => void; readonly onClose: () => void; } /** What can be open on a desktop: a program's window or the software installation. */ export type DesktopWindowName = DesktopProgram | 'installation'; /** The programs whose windows are built so far. */ const WITH_WINDOW: ReadonlySet = new Set(['commandPrompt']); /** The document's desktop of a computer, read anew after every change (a program installed, the label changed). */ function useDesktopView(doc: Y.Doc, id: string) { const [view, setView] = useState(() => desktopView(doc, id)); useEffect(() => { setView(desktopView(doc, id)); const update = () => setView(desktopView(doc, id)); doc.on('update', update); return () => doc.off('update', update); }, [doc, id]); return view; } export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: open, onProgram: setOpen, origin, onMove, onFront, onClose }: DesktopWindowProps) { const t = useTranslate(); const language = useLanguage(); const view = useDesktopView(doc, id); const { watch } = simulation; useEffect(() => watch([`node:${id}`]), [watch, id]); const settings = simulation.nodes.get(id); const [info, setInfo] = useState(false); const home = useCallback(() => setOpen(null), [setOpen]); const [drag, setDrag] = useState<{ readonly pointer: number; readonly dx: number; readonly dy: number; moved: boolean } | null>(null); if (view === undefined) return null; const programs = desktopPrograms(view.programs, language); // A program removed while its window was open: the desktop shows. const shown = open === 'installation' || (open !== null && programs.includes(open)) ? open : null; const address = settings?.ipAddress ?? ''; const title = view.addressAsName ? view.label : `${view.label} - ${address}`; // The title bar moves the window; the pointer is captured once it moves (pointer-capture trap). const onPointerDown = (event: ReactPointerEvent) => { if ((event.target as Element).closest('button') !== null) return; setDrag({ pointer: event.pointerId, dx: event.clientX - x, dy: event.clientY - y, moved: false }); }; const onPointerMove = (event: ReactPointerEvent) => { if (drag === null || drag.pointer !== event.pointerId) return; if (!drag.moved) { drag.moved = true; event.currentTarget.setPointerCapture(event.pointerId); } onMove(Math.max(0, event.clientX - drag.dx), Math.max(0, event.clientY - drag.dy)); }; const onPointerUp = () => setDrag(null); const rows: readonly [string, string][] = [ [t('network.ipAddress'), address], [t('network.subnetMask'), settings?.subnetMask ?? ''], [t('network.dnsServer'), settings?.dnsServer ?? ''], [t('network.gateway'), settings?.defaultGateway ?? ''], [t('network.macAddress'), settings?.macAddress ?? ''], ]; return (

{title}

  • {programs.map((program) => (
  • ))}
{shown === 'installation' ? ( ) : shown === null ? null : ( {shown === 'commandPrompt' ? : null} )} {info ? ( // A press beside the panel closes it, as in Java.
event.target === event.currentTarget && setInfo(false)}>
{rows.map(([label, value]) => (
{label}
{value}
))}
) : null}
); }