feat(editor): F4.6c the software installation on a computer's desktop
A new oracle, InstallRun, installs each of Java's programs on new computers, all of them on one, each installed and removed again, and a DNS and an e-mail server installed again after their removal; Java saves the result as a project. A new program is switched off (a computer's firewall is on), makes the folders and files it lacks (the web server's start page in FILIUS' language and its picture, /peer2peer, /mailserver/konten.txt, /dns/hosts), and removing it takes its settings and leaves its files; a server installed again reads its records or accounts from them. Java installs in the order of its list and starts the program at once. The document's setSoftware does the same, checked against Java's saved installation. The core knows what a program makes in the files and installs or removes a program while the simulation runs; the room watches the document's programs and hands changes to the engine, so a new command line works at once. The desktop offers Java's dialog with two lists, arrows, double click and apply changes. Also: Java's file may name a number it rolled by the result of any getter (an idref, here a thread's priority); GetterDefaults now measures the priority of the saved thread classes, which the import read as 0 before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -24,6 +24,7 @@ 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';
|
||||
|
||||
@@ -39,14 +40,19 @@ export interface DesktopWindowProps {
|
||||
readonly y: number;
|
||||
/** Its place in the stack of desktops. */
|
||||
readonly z: number;
|
||||
/** The program whose window is open on it, kept by the editor over a return to the design (Java's desktop keeps it). */
|
||||
readonly program: DesktopProgram | null;
|
||||
readonly onProgram: (program: DesktopProgram | null) => void;
|
||||
/** 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<DesktopProgram> = new Set(['commandPrompt']);
|
||||
|
||||
@@ -62,7 +68,7 @@ function useDesktopView(doc: Y.Doc, id: string) {
|
||||
return view;
|
||||
}
|
||||
|
||||
export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: open, onProgram: setOpen, onMove, onFront, onClose }: DesktopWindowProps) {
|
||||
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);
|
||||
@@ -75,7 +81,7 @@ export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: o
|
||||
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 !== null && programs.includes(open) ? open : null;
|
||||
const shown = open === 'installation' || (open !== null && programs.includes(open)) ? open : null;
|
||||
const address = settings?.ipAddress ?? '';
|
||||
const title = view.addressAsName ? view.label : `${view.label} - ${address}`;
|
||||
|
||||
@@ -114,8 +120,7 @@ export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: o
|
||||
<div className="desktop-screen" style={{ backgroundImage: `url(${background})` }}>
|
||||
<ul className="desktop-icons">
|
||||
<li>
|
||||
{/* The installation's window comes with F4.6c. */}
|
||||
<button type="button" className="desktop-icon" aria-disabled="true" title={t('desktop.installation')}>
|
||||
<button type="button" className="desktop-icon" data-program="installation" title={t('desktop.installation')} onClick={() => setOpen('installation')}>
|
||||
<img src={INSTALLATION_ICON} alt="" />
|
||||
<span>{t('desktop.installation')}</span>
|
||||
</button>
|
||||
@@ -136,7 +141,11 @@ export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: o
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{shown === null ? null : (
|
||||
{shown === 'installation' ? (
|
||||
<ProgramFrame icon={INSTALLATION_ICON} title={t('desktop.installation')} program="installation">
|
||||
<InstallationPanel doc={doc} node={id} installed={view.programs} origin={origin} onDone={home} />
|
||||
</ProgramFrame>
|
||||
) : shown === null ? null : (
|
||||
<ProgramFrame icon={JAVA_PROGRAMS[shown].icon} title={t(`program.${shown}`)} program={shown}>
|
||||
{shown === 'commandPrompt' ? <TerminalPanel node={id} simulation={simulation} onExit={home} /> : null}
|
||||
</ProgramFrame>
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
// A computer's software installation, after Java's GUIInstallationsDialog:
|
||||
// the programs installed on the left, those to be had on the right, each in
|
||||
// the order of Java's list; the arrows between them (or a double click) move
|
||||
// the chosen ones over, "apply changes" installs and removes them in one
|
||||
// step (setSoftware) and goes back to the desktop. Java's list of installed
|
||||
// programs comes in the order of a hash map; here in the order of its list.
|
||||
// A tap chooses a program, another tap lets it go, as Java's lists choose
|
||||
// several.
|
||||
|
||||
import { useState } from 'react';
|
||||
import type * as Y from 'yjs';
|
||||
import { setSoftware, SOFTWARE_ORDER, type Software } from '@mdg-filius/doc';
|
||||
import type { InstallableProgram } from '@mdg-filius/core';
|
||||
import arrowLeft from '../assets/desktop/pfeil_links.png';
|
||||
import arrowRight from '../assets/desktop/pfeil_rechts.png';
|
||||
import { useLanguage, useTranslate } from '../i18n/i18n.js';
|
||||
|
||||
function ProgramList({
|
||||
name,
|
||||
label,
|
||||
programs,
|
||||
chosen,
|
||||
onChoose,
|
||||
onMove,
|
||||
}: {
|
||||
readonly name: string;
|
||||
readonly label: string;
|
||||
readonly programs: readonly Software[];
|
||||
readonly chosen: ReadonlySet<Software>;
|
||||
readonly onChoose: (program: Software) => void;
|
||||
readonly onMove: (program: Software) => void;
|
||||
}) {
|
||||
const t = useTranslate();
|
||||
return (
|
||||
<div className="installation-column">
|
||||
<span id={`${name}-label`}>{label}</span>
|
||||
<ul className="installation-list" role="listbox" aria-multiselectable="true" aria-labelledby={`${name}-label`} data-list={name}>
|
||||
{programs.map((program) => (
|
||||
<li key={program} role="option" aria-selected={chosen.has(program)} tabIndex={0} onClick={() => onChoose(program)} onDoubleClick={() => onMove(program)} onKeyDown={(event) => (event.key === ' ' || event.key === 'Enter') && (event.preventDefault(), onChoose(program))}>
|
||||
{t(`program.${program}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function InstallationPanel({ doc, node, installed, origin, onDone }: { readonly doc: Y.Doc; readonly node: string; readonly installed: readonly InstallableProgram[]; readonly origin: object; readonly onDone: () => void }) {
|
||||
const t = useTranslate();
|
||||
const language = useLanguage();
|
||||
const order = SOFTWARE_ORDER[language];
|
||||
const [kept, setKept] = useState<ReadonlySet<Software>>(() => new Set(order.filter((program) => installed.includes(program))));
|
||||
const [chosen, setChosen] = useState<ReadonlySet<Software>>(new Set());
|
||||
const left = order.filter((program) => kept.has(program));
|
||||
const right = order.filter((program) => !kept.has(program));
|
||||
const choose = (program: Software) =>
|
||||
setChosen((before) => {
|
||||
const next = new Set(before);
|
||||
if (!next.delete(program)) next.add(program);
|
||||
return next;
|
||||
});
|
||||
/** Moves the chosen programs of one side to the other. */
|
||||
const move = (toInstalled: boolean, programs: readonly Software[]) => {
|
||||
setKept((before) => {
|
||||
const next = new Set(before);
|
||||
for (const program of programs) if (toInstalled) next.add(program);
|
||||
else next.delete(program);
|
||||
return next;
|
||||
});
|
||||
setChosen((before) => new Set([...before].filter((program) => !programs.includes(program))));
|
||||
};
|
||||
const apply = () => {
|
||||
const install = order.filter((program) => kept.has(program) && !installed.includes(program));
|
||||
const remove = order.filter((program) => !kept.has(program) && installed.includes(program));
|
||||
setSoftware(doc, node, { install, remove }, language, origin);
|
||||
onDone();
|
||||
};
|
||||
return (
|
||||
<div className="installation">
|
||||
<div className="installation-lists">
|
||||
<ProgramList name="installed" label={t('installation.installed')} programs={left} chosen={chosen} onChoose={choose} onMove={(program) => move(false, [program])} />
|
||||
<div className="installation-arrows">
|
||||
<button type="button" aria-label={t('installation.add')} title={t('installation.add')} onClick={() => move(true, right.filter((program) => chosen.has(program)))}>
|
||||
<img src={arrowLeft} alt="" />
|
||||
</button>
|
||||
<button type="button" aria-label={t('installation.remove')} title={t('installation.remove')} onClick={() => move(false, left.filter((program) => chosen.has(program)))}>
|
||||
<img src={arrowRight} alt="" />
|
||||
</button>
|
||||
</div>
|
||||
<ProgramList name="available" label={t('installation.available')} programs={right} chosen={chosen} onChoose={choose} onMove={(program) => move(true, [program])} />
|
||||
</div>
|
||||
<button type="button" className="installation-apply" onClick={apply}>
|
||||
{t('installation.apply')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -295,3 +295,87 @@
|
||||
font: 14px ui-monospace, Menlo, Consolas, monospace;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* The software installation (GUIInstallationsDialog): two lists of 170
|
||||
* pixels in Java, the arrows between them, "apply changes" below. */
|
||||
.installation {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background: #eeeeee;
|
||||
color: #000000;
|
||||
font: 13px Dialog, sans-serif;
|
||||
}
|
||||
|
||||
.installation-lists {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 52px minmax(0, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.installation-column {
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.installation-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
background: #ffffff;
|
||||
border: 1px solid #a0a0a0;
|
||||
}
|
||||
|
||||
.installation-list li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 44px;
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.installation-list li[aria-selected='true'] {
|
||||
background: rgb(184 207 229);
|
||||
}
|
||||
|
||||
.installation-list li:focus-visible {
|
||||
outline: 2px solid rgb(99 130 191);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.installation-arrows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.installation-arrows button,
|
||||
.installation-apply {
|
||||
min-width: 44px;
|
||||
min-height: 44px;
|
||||
border: 1px solid #a0a0a0;
|
||||
border-radius: 6px;
|
||||
background: #f8f8f8;
|
||||
color: #000000;
|
||||
font: 13px Dialog, sans-serif;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.installation-apply {
|
||||
padding: 0 16px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// set up in the device sheet, as in Java.
|
||||
|
||||
import type { InstallableProgram } from '@mdg-filius/core';
|
||||
import { SOFTWARE_ORDER } from '@mdg-filius/doc';
|
||||
import type { Language } from '../i18n/i18n.js';
|
||||
import browser from '../assets/desktop/icon_browser.png';
|
||||
import simpleClient from '../assets/desktop/icon_clientbaustein.png';
|
||||
@@ -43,11 +44,8 @@ export const JAVA_PROGRAMS: Readonly<Record<DesktopProgram, { readonly javaClass
|
||||
/** The software installation's icon, always first on the desktop (GUIDesktopPanel.initInstaller). */
|
||||
export const INSTALLATION_ICON = installation;
|
||||
|
||||
/** The order of Java's list in each language. */
|
||||
export const DESKTOP_ORDER: Readonly<Record<Language, readonly DesktopProgram[]>> = {
|
||||
de: ['commandPrompt', 'imageViewer', 'fileExplorer', 'dnsServer', 'emailClient', 'emailServer', 'echoServer', 'simpleClient', 'firewall', 'gnutella', 'textEditor', 'webServer', 'webBrowser'],
|
||||
en: ['commandPrompt', 'dnsServer', 'echoServer', 'emailServer', 'emailClient', 'fileExplorer', 'firewall', 'simpleClient', 'gnutella', 'imageViewer', 'textEditor', 'webBrowser', 'webServer'],
|
||||
};
|
||||
/** The order of Java's list in each language (the installation's too). */
|
||||
export const DESKTOP_ORDER: Readonly<Record<Language, readonly DesktopProgram[]>> = SOFTWARE_ORDER;
|
||||
|
||||
/** A computer's programs as its desktop shows them, in the language's order. */
|
||||
export function desktopPrograms(installed: readonly InstallableProgram[], language: Language): DesktopProgram[] {
|
||||
|
||||
Reference in New Issue
Block a user