feat(editor): F4.6d the web browser and web server on a computer's desktop
The oracle BrowserWindowRun uses Java's browser and web server windows as a person does; the room keeps a browser window per computer and sends a program window's view, the start names the person's language. The editor shows the page in a sandboxed frame, links and forms go to the room, the web server has its button, virtual hosts and lines. Better than Java: relative images and links count from the page's folder, a mark stays in the page, the error page from Java's template, the virtual hosts' file with complete rows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,3 +2,4 @@
|
||||
export * from './host.js';
|
||||
export * from './sim.js';
|
||||
export * from './terminal.js';
|
||||
export * from './web.js';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// 6.1). The room runs on F (RemoteSimHost, F5) or in a web worker
|
||||
// (LocalSimHost); the messages are the same.
|
||||
|
||||
import type { CaptureRow } from '@mdg-filius/core';
|
||||
import type { CaptureRow, PageLanguage } from '@mdg-filius/core';
|
||||
|
||||
export const SIM_PROTOCOL_VERSION = 1;
|
||||
|
||||
@@ -26,8 +26,13 @@ export type ClientMessageKind = (typeof CLIENT_MESSAGE_KINDS)[number];
|
||||
export type ClientMessage =
|
||||
| { readonly v: 1; readonly kind: 'sub'; readonly topics: readonly Topic[] }
|
||||
| { readonly v: 1; readonly kind: 'unsub'; readonly topics: readonly Topic[] }
|
||||
/** Starts the simulation with the document as it is now. */
|
||||
| { readonly v: 1; readonly kind: 'start' }
|
||||
/**
|
||||
* Starts the simulation with the document as it is now; the pages the
|
||||
* devices write themselves (a router's) come in the language FILIUS runs in
|
||||
* for the person who starts it, as Java's come in its locale (German
|
||||
* without one).
|
||||
*/
|
||||
| { readonly v: 1; readonly kind: 'start'; readonly language?: PageLanguage }
|
||||
| { readonly v: 1; readonly kind: 'stop' }
|
||||
/** Java's speed slider, 1 to 100 per cent: how long a frame takes over a cable. */
|
||||
| { readonly v: 1; readonly kind: 'speed'; readonly value: number }
|
||||
@@ -42,7 +47,7 @@ export type ClientMessage =
|
||||
readonly reqId: string;
|
||||
};
|
||||
|
||||
export const HOST_MESSAGE_KINDS = ['state', 'activity', 'trace', 'app', 'node', 'ack', 'error'] as const;
|
||||
export const HOST_MESSAGE_KINDS = ['state', 'activity', 'trace', 'app', 'view', 'node', 'ack', 'error'] as const;
|
||||
export type HostMessageKind = (typeof HOST_MESSAGE_KINDS)[number];
|
||||
|
||||
export type SimMode = 'design' | 'simulation';
|
||||
@@ -67,6 +72,13 @@ export type HostMessage =
|
||||
| { readonly v: 1; readonly kind: 'trace'; readonly portId: string; readonly frames: readonly TraceFrame[]; readonly reset: boolean }
|
||||
/** A program's new window lines from line `from` on; on subscribing, all (from 0). */
|
||||
| { readonly v: 1; readonly kind: 'app'; readonly node: string; readonly app: AppKind; readonly from: number; readonly lines: readonly unknown[] }
|
||||
/**
|
||||
* What a program's window shows besides its lines (plan §7.3), whole: on
|
||||
* subscribing and when it changes; null while it has nothing (its program
|
||||
* does not run, a browser that never ran). The browser's (BrowserView) and
|
||||
* the web server's (WebServerView) so far.
|
||||
*/
|
||||
| { readonly v: 1; readonly kind: 'view'; readonly node: string; readonly app: AppKind; readonly view: unknown }
|
||||
/**
|
||||
* A computer's first card as the simulation runs it (DHCP may change it):
|
||||
* on subscribing, when it changes, and none while the simulation never ran.
|
||||
@@ -115,8 +127,11 @@ export function parseClientMessage(value: unknown): Parsed<ClientMessage> {
|
||||
return { message: { v: 1, kind: kind as 'sub' | 'unsub', topics: topics as Topic[] } };
|
||||
}
|
||||
case 'start':
|
||||
if (value.language === undefined) return { message: { v: 1, kind: 'start' } };
|
||||
if (value.language !== 'de' && value.language !== 'en') return bad('badMessage', `language ${String(value.language)}`);
|
||||
return { message: { v: 1, kind: 'start', language: value.language } };
|
||||
case 'stop':
|
||||
return { message: { v: 1, kind: kind as 'start' | 'stop' } };
|
||||
return { message: { v: 1, kind: 'stop' } };
|
||||
case 'speed':
|
||||
if (typeof value.value !== 'number' || !Number.isFinite(value.value)) return bad('badMessage', 'speed is no number');
|
||||
return { message: { v: 1, kind: 'speed', value: value.value } };
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// What the web browser's and the web server's windows show, as the room
|
||||
// keeps them and every client shows them (plan §7.3), after
|
||||
// GUIApplicationWebBrowserWindow and GUIApplicationWebServerWindow
|
||||
// (measured with tools/java-oracle's BrowserWindowRun).
|
||||
//
|
||||
// The browser: Java's address line shows "http://", then the address of each
|
||||
// load as "http://" + host + path without its query (also when nothing
|
||||
// comes); its title is the program's name, then that address, then the
|
||||
// title of the page once it came. The page itself goes to the client as it
|
||||
// came — the client shows it in a sandbox, its images from the data that
|
||||
// came for them.
|
||||
|
||||
/** The page the browser shows. */
|
||||
export type BrowserPage =
|
||||
/** Java's start page: the browser's logo. */
|
||||
| { readonly kind: 'start' }
|
||||
/** sw_webbrowser_msg1: the server could not be reached. */
|
||||
| { readonly kind: 'unreachable' }
|
||||
/** A status other than 200: the browser's error page for it (tmpl/http_fehler). */
|
||||
| { readonly kind: 'error'; readonly statusCode: number }
|
||||
/** An HTML page, with the images that came for it by their src as written (their content type and data in Base64). */
|
||||
| { readonly kind: 'html'; readonly html: string; readonly images: Readonly<Record<string, BrowserImage>> }
|
||||
/** Any other text (Java shows it as HTML). */
|
||||
| { readonly kind: 'text'; readonly text: string }
|
||||
/** An image on its own (Java shows its Base64 as HTML). */
|
||||
| { readonly kind: 'image'; readonly image: BrowserImage };
|
||||
|
||||
export interface BrowserImage {
|
||||
readonly contentType: string;
|
||||
readonly data: string;
|
||||
}
|
||||
|
||||
export interface BrowserView {
|
||||
/** The address line as the room last set it: "http://" at first and after an address that is none. */
|
||||
readonly address: string;
|
||||
/** The address of the last load, the title until its page came; null before the first load (the title is the program's name). */
|
||||
readonly loading: string | null;
|
||||
/** Whether the page shown came for the last load (its title is the window's). */
|
||||
readonly arrived: boolean;
|
||||
readonly page: BrowserPage;
|
||||
}
|
||||
|
||||
/** The browser before it loaded anything. */
|
||||
export const BROWSER_START: BrowserView = { address: 'http://', loading: null, arrived: false, page: { kind: 'start' } };
|
||||
|
||||
/** The web server's window besides its lines: the start button, the virtual hosts' check box and table (five rows of host name and folder). */
|
||||
export interface WebServerView {
|
||||
readonly active: boolean;
|
||||
readonly useVirtualHosts: boolean;
|
||||
readonly virtualHosts: readonly (readonly [string, string])[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The text of an HTML page's first title, as it is written (entities not
|
||||
* decoded); undefined without one or with an empty one (Java keeps the
|
||||
* title it had).
|
||||
*/
|
||||
export function htmlTitle(html: string): string | undefined {
|
||||
const match = /<title(?:\s[^>]*)?>([\s\S]*?)<\/title\s*>/i.exec(html);
|
||||
if (match === null) return undefined;
|
||||
const text = match[1]!.replace(/<[^>]*>/g, '');
|
||||
return text.trim() === '' ? undefined : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* The browser window's title, as Java's: the program's name before the
|
||||
* first load, then the address of the last load, then the title of its page
|
||||
* once it came, where the page has one (`pageTitle`: an HTML page's, an
|
||||
* error page's in the client's language).
|
||||
*/
|
||||
export function browserTitle(view: BrowserView, program: string, pageTitle: (page: BrowserPage) => string | undefined): string {
|
||||
if (view.loading === null) return program;
|
||||
return (view.arrived ? pageTitle(view.page) : undefined) ?? view.loading;
|
||||
}
|
||||
Reference in New Issue
Block a user