feat(editor): F4.3 the editor's frame and surface; a switch's cloud read and written
packages/editor (React 18, Vite) draws a document as Java FILIUS does: Java's default hardware icons, the device button of JSidebarButton, cables as JCablePanel's quadratic curve between the buttons' middles, wireless cables dashed, documentation items below. The surface moves with mouse and finger (the pointer captured only once it moved) and zooms with Ctrl and the wheel, a trackpad pinch, two fingers and the zoom bar; the first view shows all. German and English catalogs with Java's device names, the dashboard's colour tokens, the bridge with its origin check. packages/doc gets the drawing's light view of a document. Java's Switch.cloud (the Internet in the examples) was lost on reading; it is now read, kept in the model and written, with FlsOracle and the corpus, written and exported checks renewed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// The editor's page. Embedded in the dashboard (F6) it gets its document and
|
||||
// language over the bridge; on its own — for development and the browser
|
||||
// tests — it opens one of FILIUS' examples or a file (`?example=<name>`
|
||||
// opens that example at once, `?lang=en` and `?theme=dark` set the rest).
|
||||
|
||||
import { StrictMode, useEffect, useState } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import * as Y from 'yjs';
|
||||
import { writeProject } from '@mdg-filius/doc';
|
||||
import { openProjectArchive } from '@mdg-filius/fls';
|
||||
import { startBridge } from './bridge/dashboardBridge.js';
|
||||
import { Editor } from './Editor.js';
|
||||
import { LanguageProvider, translate, type Language } from './i18n/i18n.js';
|
||||
import './theme/tokens.css';
|
||||
|
||||
const EXAMPLES = import.meta.glob('../../../corpus/java-examples/*.fls', { query: '?url', import: 'default', eager: true }) as Record<string, string>;
|
||||
const exampleUrl = (name: string) => Object.entries(EXAMPLES).find(([path]) => path.endsWith(`/${name}`))?.[1];
|
||||
|
||||
function docOf(bytes: Uint8Array): Y.Doc {
|
||||
const doc = new Y.Doc();
|
||||
writeProject(doc, openProjectArchive(bytes, () => crypto.randomUUID()));
|
||||
return doc;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const [language, setLanguage] = useState<Language>(params.get('lang') === 'en' ? 'en' : 'de');
|
||||
const [doc, setDoc] = useState<Y.Doc | null>(null);
|
||||
const [example, setExample] = useState(params.get('example') ?? '');
|
||||
|
||||
useEffect(() => {
|
||||
if (params.get('theme') === 'dark') document.documentElement.dataset.theme = 'dark';
|
||||
const parent = params.get('parent');
|
||||
if (parent === null) return undefined;
|
||||
return startBridge(window, parent, {
|
||||
onLocale: (locale) => setLanguage(locale.startsWith('en') ? 'en' : 'de'),
|
||||
onTheme: (theme, tokens) => {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
for (const [name, value] of Object.entries(tokens)) document.documentElement.style.setProperty(name, value);
|
||||
},
|
||||
});
|
||||
// The page's parameters are read once.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const url = exampleUrl(example);
|
||||
if (url === undefined) return;
|
||||
void fetch(url)
|
||||
.then((response) => response.arrayBuffer())
|
||||
.then((buffer) => setDoc(docOf(new Uint8Array(buffer))));
|
||||
}, [example]);
|
||||
|
||||
const t = (key: string) => translate(language, key);
|
||||
return (
|
||||
<LanguageProvider language={language}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<header style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '8px 12px', borderBottom: '1px solid var(--border-default)', background: 'var(--surface-card)' }}>
|
||||
<strong>{t('dev.title')}</strong>
|
||||
<label>
|
||||
{t('dev.example')}{' '}
|
||||
<select value={example} onChange={(event) => setExample(event.target.value)}>
|
||||
<option value="" />
|
||||
{Object.keys(EXAMPLES)
|
||||
.map((path) => path.slice(path.lastIndexOf('/') + 1))
|
||||
.sort()
|
||||
.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
{t('dev.openFile')}{' '}
|
||||
<input
|
||||
type="file"
|
||||
accept=".fls"
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file !== undefined) void file.arrayBuffer().then((buffer) => setDoc(docOf(new Uint8Array(buffer))));
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
</header>
|
||||
<main style={{ flex: 1, minHeight: 0 }}>
|
||||
{doc === null ? null : <Editor key={doc.guid} doc={doc} />}
|
||||
</main>
|
||||
</div>
|
||||
</LanguageProvider>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
Reference in New Issue
Block a user