feat: F4.8a the documentation items in model and file, measured against Java's
New oracle DocuRun on Java's JDocuElement. Italic is kept (the model knew only bold), an item keeps its file's size and font size (Swift's 32 x 24 and 8-72 changed Java's files), an item without a colour is Java's light grey. Java's new items, colour lists, font sizes and styles, the drag's smallest sizes and Java's stacking; document commands for the editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
// The documentation items of a document, changed as Java's documentation mode
|
||||
// changes them (JDocuElement, measured with tools/java-oracle's DocuRun): a
|
||||
// new text or rectangle where it is dropped, moved and resized in whole
|
||||
// pixels, its colour, its font size and style, removed. A text's words are
|
||||
// its Y.Text, typed into by everyone at once.
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import {
|
||||
DOCUMENTATION_BLACK,
|
||||
DOCUMENTATION_GREY,
|
||||
DOCUMENTATION_NEW_SIZE,
|
||||
normalizedFrame,
|
||||
type DocumentationColor,
|
||||
type DocumentationFontStyle,
|
||||
type DocumentationItemKind,
|
||||
type Id,
|
||||
type Rect,
|
||||
} from '@mdg-filius/core';
|
||||
import { ROOTS } from './projectDoc.js';
|
||||
|
||||
const docs = (doc: Y.Doc) => doc.getMap<Y.Map<unknown>>(ROOTS.docs);
|
||||
|
||||
/**
|
||||
* A new item, its top left corner at (x, y): 100 × 50, a text empty and black
|
||||
* in Dialog 12, a rectangle grey; after all others in the list, so of its kind
|
||||
* the lowest, as Java adds it.
|
||||
*/
|
||||
export function addDocumentation(doc: Y.Doc, item: { readonly id: Id; readonly kind: DocumentationItemKind; readonly x: number; readonly y: number }, origin: unknown): void {
|
||||
doc.transact(() => {
|
||||
const order = Math.max(-1, ...[...docs(doc).values()].map((each) => each.get('order') as number)) + 1;
|
||||
const color = item.kind === 'text' ? DOCUMENTATION_BLACK : DOCUMENTATION_GREY;
|
||||
const map = new Y.Map<unknown>();
|
||||
map.set('kind', item.kind);
|
||||
map.set('x', Math.round(item.x));
|
||||
map.set('y', Math.round(item.y));
|
||||
map.set('width', DOCUMENTATION_NEW_SIZE.width);
|
||||
map.set('height', DOCUMENTATION_NEW_SIZE.height);
|
||||
map.set('text', new Y.Text(''));
|
||||
map.set('red', color.red);
|
||||
map.set('green', color.green);
|
||||
map.set('blue', color.blue);
|
||||
map.set('alpha', color.alpha);
|
||||
map.set('font', 'Dialog');
|
||||
map.set('size', 12);
|
||||
map.set('bold', false);
|
||||
map.set('italic', false);
|
||||
map.set('order', order);
|
||||
docs(doc).set(item.id, map);
|
||||
}, origin);
|
||||
}
|
||||
|
||||
/** Its place and size, in whole pixels (Java's bounds are ints), inside the editor's world. */
|
||||
export function setDocumentationFrame(doc: Y.Doc, id: Id, frame: Rect, origin: unknown): void {
|
||||
const map = docs(doc).get(id);
|
||||
if (map === undefined) return;
|
||||
const normalized = normalizedFrame({ x: Math.round(frame.x), y: Math.round(frame.y), width: Math.round(frame.width), height: Math.round(frame.height) });
|
||||
doc.transact(() => {
|
||||
for (const key of ['x', 'y', 'width', 'height'] as const) if (map.get(key) !== normalized[key]) map.set(key, normalized[key]);
|
||||
}, origin);
|
||||
}
|
||||
|
||||
export function setDocumentationColor(doc: Y.Doc, id: Id, color: DocumentationColor, origin: unknown): void {
|
||||
const map = docs(doc).get(id);
|
||||
if (map === undefined) return;
|
||||
doc.transact(() => {
|
||||
map.set('red', color.red);
|
||||
map.set('green', color.green);
|
||||
map.set('blue', color.blue);
|
||||
map.set('alpha', color.alpha);
|
||||
}, origin);
|
||||
}
|
||||
|
||||
/** A text's font size, or its style, which replaces the one it had (Java's deriveFont: italic, then bold, is bold). */
|
||||
export function setDocumentationFont(doc: Y.Doc, id: Id, font: { readonly size?: number; readonly style?: DocumentationFontStyle }, origin: unknown): void {
|
||||
const map = docs(doc).get(id);
|
||||
if (map === undefined || map.get('kind') !== 'text') return;
|
||||
doc.transact(() => {
|
||||
if (font.size !== undefined) map.set('size', font.size);
|
||||
if (font.style !== undefined) {
|
||||
map.set('bold', font.style === 'bold');
|
||||
map.set('italic', font.style === 'italic');
|
||||
}
|
||||
}, origin);
|
||||
}
|
||||
|
||||
export function removeDocumentation(doc: Y.Doc, id: Id, origin: unknown): void {
|
||||
doc.transact(() => docs(doc).delete(id), origin);
|
||||
}
|
||||
|
||||
/** A text's words, to type into; undefined for a rectangle or no item. */
|
||||
export function documentationText(doc: Y.Doc, id: Id): Y.Text | undefined {
|
||||
const map = docs(doc).get(id);
|
||||
return map?.get('kind') === 'text' ? (map.get('text') as Y.Text) : undefined;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// Public surface of the shared document.
|
||||
export * from './commands.js';
|
||||
export * from './documentation.js';
|
||||
export * from './fields.js';
|
||||
export * from './files.js';
|
||||
export * from './janitor.js';
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// own? }
|
||||
// links id → { order, sourceNode, sourcePort, targetNode, targetPort }
|
||||
// docs id → { kind, x, y, width, height, text, red, green, blue, alpha,
|
||||
// font, size, bold, order }
|
||||
// font, size, bold, italic, order }
|
||||
// archive name → bytes (the project's own Java applications)
|
||||
//
|
||||
// Departures from §5.2, to keep the model whole: a port's settings are the
|
||||
@@ -190,6 +190,7 @@ function documentationMap(item: DocumentationItem): Y.Map<unknown> {
|
||||
map.set('font', item.fontName);
|
||||
map.set('size', item.fontSize);
|
||||
map.set('bold', item.isBold);
|
||||
map.set('italic', item.isItalic);
|
||||
map.set('order', item.order);
|
||||
return map;
|
||||
}
|
||||
@@ -336,6 +337,8 @@ export function readProject(doc: Y.Doc): ImportedProject {
|
||||
fontName: map.get('font') as string,
|
||||
fontSize: map.get('size') as number,
|
||||
isBold: map.get('bold') as boolean,
|
||||
// A document made before F4.8a has no italic.
|
||||
isItalic: map.get('italic') === true,
|
||||
order: map.get('order') as number,
|
||||
})),
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// so that it can run on every change while someone drags a device.
|
||||
|
||||
import * as Y from 'yjs';
|
||||
import { compareIds, stableGeneratedMacAddress, type DocumentationItemKind, type HostLabelPolicy, type InstallableProgram, type NodeKind } from '@mdg-filius/core';
|
||||
import { inJavaDrawingOrder, stableGeneratedMacAddress, type DocumentationItemKind, type HostLabelPolicy, type InstallableProgram, type NodeKind } from '@mdg-filius/core';
|
||||
import { inOrder, ROOTS } from './projectDoc.js';
|
||||
|
||||
export interface NodeView {
|
||||
@@ -44,6 +44,7 @@ export interface DocumentationView {
|
||||
readonly font: string;
|
||||
readonly size: number;
|
||||
readonly bold: boolean;
|
||||
readonly italic: boolean;
|
||||
readonly order: number;
|
||||
}
|
||||
|
||||
@@ -153,12 +154,11 @@ export function topologyView(doc: Y.Doc): TopologyView {
|
||||
font: item.get('font') as string,
|
||||
size: item.get('size') as number,
|
||||
bold: item.get('bold') as boolean,
|
||||
italic: item.get('italic') === true,
|
||||
order: item.get('order') as number,
|
||||
}));
|
||||
// The model's render order (inDeterministicRenderOrder): rectangles below texts, then by order, then by id.
|
||||
const layer = (item: DocumentationView) => (item.kind === 'rectangle' ? 0 : 1);
|
||||
docs.sort((left, right) => layer(left) - layer(right) || left.order - right.order || compareIds(left.id, right.id));
|
||||
return { nodes, links, docs };
|
||||
// In Java's drawing order, the last on top: texts over rectangles, of one kind the earlier over the later.
|
||||
return { nodes, links, docs: inJavaDrawingOrder(docs) };
|
||||
}
|
||||
|
||||
/** What a computer's desktop needs of the document (Java's GUIDesktopWindow, GUIDesktopPanel). */
|
||||
|
||||
@@ -0,0 +1,894 @@
|
||||
{
|
||||
"new": {
|
||||
"text": {
|
||||
"bounds": [
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 0,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
},
|
||||
"rectangle": {
|
||||
"bounds": [
|
||||
300,
|
||||
100,
|
||||
100,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
204,
|
||||
204,
|
||||
204,
|
||||
255
|
||||
],
|
||||
"font": null,
|
||||
"text": null
|
||||
}
|
||||
},
|
||||
"dropped": {
|
||||
"text": {
|
||||
"bounds": [
|
||||
94,
|
||||
94,
|
||||
112,
|
||||
78
|
||||
],
|
||||
"color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 0,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
},
|
||||
"textUnfocused": {
|
||||
"bounds": [
|
||||
100,
|
||||
100,
|
||||
100,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 0,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"fonts": [
|
||||
{
|
||||
"name": "Dialog",
|
||||
"style": 0,
|
||||
"size": 18
|
||||
},
|
||||
{
|
||||
"name": "Dialog",
|
||||
"style": 2,
|
||||
"size": 18
|
||||
},
|
||||
{
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 18
|
||||
},
|
||||
{
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 12
|
||||
},
|
||||
{
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 24
|
||||
}
|
||||
],
|
||||
"textColor": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"rectangleColor": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
255
|
||||
],
|
||||
"menus": {
|
||||
"text": [
|
||||
{
|
||||
"menu": "Schriftgröße",
|
||||
"items": [
|
||||
"Normal *",
|
||||
"Groß",
|
||||
"Sehr Groß"
|
||||
]
|
||||
},
|
||||
{
|
||||
"menu": "Schriftstil",
|
||||
"items": [
|
||||
"Normal *",
|
||||
"Kursiv",
|
||||
"Fett"
|
||||
]
|
||||
},
|
||||
{
|
||||
"colors": "Farbe",
|
||||
"list": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
[
|
||||
102,
|
||||
102,
|
||||
102,
|
||||
255
|
||||
],
|
||||
[
|
||||
153,
|
||||
153,
|
||||
153,
|
||||
255
|
||||
],
|
||||
[
|
||||
179,
|
||||
179,
|
||||
179,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
[
|
||||
179,
|
||||
163,
|
||||
199,
|
||||
255
|
||||
],
|
||||
[
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
[
|
||||
156,
|
||||
186,
|
||||
89,
|
||||
255
|
||||
],
|
||||
[
|
||||
148,
|
||||
138,
|
||||
84,
|
||||
255
|
||||
],
|
||||
[
|
||||
148,
|
||||
56,
|
||||
54,
|
||||
255
|
||||
],
|
||||
[
|
||||
94,
|
||||
74,
|
||||
122,
|
||||
255
|
||||
],
|
||||
[
|
||||
31,
|
||||
74,
|
||||
125,
|
||||
255
|
||||
],
|
||||
[
|
||||
110,
|
||||
135,
|
||||
56,
|
||||
255
|
||||
],
|
||||
[
|
||||
89,
|
||||
84,
|
||||
48,
|
||||
255
|
||||
]
|
||||
],
|
||||
"chosen": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"rows": 3
|
||||
},
|
||||
{
|
||||
"separator": true
|
||||
},
|
||||
{
|
||||
"item": "Löschen"
|
||||
}
|
||||
],
|
||||
"textAfterLarge": [
|
||||
{
|
||||
"menu": "Schriftgröße",
|
||||
"items": [
|
||||
"Normal",
|
||||
"Groß *",
|
||||
"Sehr Groß"
|
||||
]
|
||||
},
|
||||
{
|
||||
"menu": "Schriftstil",
|
||||
"items": [
|
||||
"Normal *",
|
||||
"Kursiv",
|
||||
"Fett"
|
||||
]
|
||||
},
|
||||
{
|
||||
"colors": "Farbe",
|
||||
"list": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
[
|
||||
102,
|
||||
102,
|
||||
102,
|
||||
255
|
||||
],
|
||||
[
|
||||
153,
|
||||
153,
|
||||
153,
|
||||
255
|
||||
],
|
||||
[
|
||||
179,
|
||||
179,
|
||||
179,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
[
|
||||
179,
|
||||
163,
|
||||
199,
|
||||
255
|
||||
],
|
||||
[
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
[
|
||||
156,
|
||||
186,
|
||||
89,
|
||||
255
|
||||
],
|
||||
[
|
||||
148,
|
||||
138,
|
||||
84,
|
||||
255
|
||||
],
|
||||
[
|
||||
148,
|
||||
56,
|
||||
54,
|
||||
255
|
||||
],
|
||||
[
|
||||
94,
|
||||
74,
|
||||
122,
|
||||
255
|
||||
],
|
||||
[
|
||||
31,
|
||||
74,
|
||||
125,
|
||||
255
|
||||
],
|
||||
[
|
||||
110,
|
||||
135,
|
||||
56,
|
||||
255
|
||||
],
|
||||
[
|
||||
89,
|
||||
84,
|
||||
48,
|
||||
255
|
||||
]
|
||||
],
|
||||
"chosen": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"rows": 3
|
||||
},
|
||||
{
|
||||
"separator": true
|
||||
},
|
||||
{
|
||||
"item": "Löschen"
|
||||
}
|
||||
],
|
||||
"rectangle": [
|
||||
{
|
||||
"colors": "Farbe",
|
||||
"list": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
[
|
||||
204,
|
||||
204,
|
||||
204,
|
||||
255
|
||||
],
|
||||
[
|
||||
235,
|
||||
235,
|
||||
235,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
255
|
||||
],
|
||||
[
|
||||
252,
|
||||
201,
|
||||
163,
|
||||
255
|
||||
],
|
||||
[
|
||||
255,
|
||||
255,
|
||||
227,
|
||||
255
|
||||
],
|
||||
[
|
||||
212,
|
||||
207,
|
||||
181,
|
||||
255
|
||||
],
|
||||
[
|
||||
222,
|
||||
212,
|
||||
255,
|
||||
255
|
||||
],
|
||||
[
|
||||
209,
|
||||
232,
|
||||
255,
|
||||
255
|
||||
],
|
||||
[
|
||||
191,
|
||||
255,
|
||||
219,
|
||||
255
|
||||
],
|
||||
[
|
||||
204,
|
||||
222,
|
||||
173,
|
||||
255
|
||||
]
|
||||
],
|
||||
"chosen": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"rows": 3
|
||||
},
|
||||
{
|
||||
"separator": true
|
||||
},
|
||||
{
|
||||
"item": "Löschen"
|
||||
}
|
||||
]
|
||||
},
|
||||
"smallest": {
|
||||
"rectangle": {
|
||||
"bounds": [
|
||||
300,
|
||||
100,
|
||||
20,
|
||||
20
|
||||
],
|
||||
"color": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
255
|
||||
],
|
||||
"font": null,
|
||||
"text": null
|
||||
},
|
||||
"rectangleByItsTopLeft": {
|
||||
"bounds": [
|
||||
300,
|
||||
100,
|
||||
20,
|
||||
20
|
||||
],
|
||||
"color": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
255
|
||||
],
|
||||
"font": null,
|
||||
"text": null
|
||||
},
|
||||
"textFocused": {
|
||||
"bounds": [
|
||||
94,
|
||||
94,
|
||||
112,
|
||||
78
|
||||
],
|
||||
"color": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
},
|
||||
"textFocusedSmallest": {
|
||||
"bounds": [
|
||||
94,
|
||||
94,
|
||||
100,
|
||||
48
|
||||
],
|
||||
"color": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
},
|
||||
"textSmallest": {
|
||||
"bounds": [
|
||||
100,
|
||||
100,
|
||||
88,
|
||||
20
|
||||
],
|
||||
"color": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
},
|
||||
"textDraggedUnfocused": {
|
||||
"bounds": [
|
||||
100,
|
||||
100,
|
||||
88,
|
||||
20
|
||||
],
|
||||
"color": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 12
|
||||
},
|
||||
"text": ""
|
||||
}
|
||||
},
|
||||
"painted": {
|
||||
"rectangleEnabled": {
|
||||
"50,25": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
200
|
||||
],
|
||||
"1,25": [
|
||||
204,
|
||||
160,
|
||||
160,
|
||||
255
|
||||
],
|
||||
"2,25": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
200
|
||||
],
|
||||
"0,25": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"rectangleFocused": {
|
||||
"50,25": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
200
|
||||
],
|
||||
"3,25": [
|
||||
56,
|
||||
152,
|
||||
248,
|
||||
77
|
||||
],
|
||||
"1,25": [
|
||||
122,
|
||||
186,
|
||||
250,
|
||||
255
|
||||
]
|
||||
},
|
||||
"rectangleDisabled": {
|
||||
"50,25": [
|
||||
255,
|
||||
212,
|
||||
212,
|
||||
255
|
||||
],
|
||||
"1,25": [
|
||||
204,
|
||||
160,
|
||||
160,
|
||||
255
|
||||
],
|
||||
"2,25": [
|
||||
255,
|
||||
212,
|
||||
212,
|
||||
255
|
||||
],
|
||||
"0,25": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"textEnabled": {
|
||||
"0,25": [
|
||||
179,
|
||||
179,
|
||||
179,
|
||||
255
|
||||
],
|
||||
"1,25": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"50,0": [
|
||||
179,
|
||||
179,
|
||||
179,
|
||||
255
|
||||
],
|
||||
"99,25": [
|
||||
179,
|
||||
179,
|
||||
179,
|
||||
255
|
||||
]
|
||||
},
|
||||
"textDisabled": {
|
||||
"0,25": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"1,25": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"50,0": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"stacking": [
|
||||
"text 1",
|
||||
"text 2",
|
||||
"rectangle 1",
|
||||
"rectangle 2"
|
||||
],
|
||||
"topAt": "text 1",
|
||||
"saved": [
|
||||
{
|
||||
"type": 2,
|
||||
"text": "Kursiv und mittel\nzweite Zeile",
|
||||
"bounds": [
|
||||
40,
|
||||
40,
|
||||
160,
|
||||
60
|
||||
],
|
||||
"color": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 2,
|
||||
"size": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"text": "Fett",
|
||||
"bounds": [
|
||||
40,
|
||||
140,
|
||||
120,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 24
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"text": null,
|
||||
"bounds": [
|
||||
20,
|
||||
20,
|
||||
300,
|
||||
200
|
||||
],
|
||||
"color": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
255
|
||||
],
|
||||
"font": null
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"text": null,
|
||||
"bounds": [
|
||||
400,
|
||||
20,
|
||||
100,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
204,
|
||||
204,
|
||||
204,
|
||||
255
|
||||
],
|
||||
"font": null
|
||||
}
|
||||
],
|
||||
"loaded": [
|
||||
{
|
||||
"type": 2,
|
||||
"text": "Kursiv und mittel\nzweite Zeile",
|
||||
"bounds": [
|
||||
40,
|
||||
40,
|
||||
160,
|
||||
60
|
||||
],
|
||||
"color": [
|
||||
79,
|
||||
130,
|
||||
189,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 2,
|
||||
"size": 18
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 2,
|
||||
"text": "Fett",
|
||||
"bounds": [
|
||||
40,
|
||||
140,
|
||||
120,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255
|
||||
],
|
||||
"font": {
|
||||
"name": "Dialog",
|
||||
"style": 1,
|
||||
"size": 24
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"text": null,
|
||||
"bounds": [
|
||||
20,
|
||||
20,
|
||||
300,
|
||||
200
|
||||
],
|
||||
"color": [
|
||||
255,
|
||||
201,
|
||||
201,
|
||||
255
|
||||
],
|
||||
"font": null
|
||||
},
|
||||
{
|
||||
"type": 1,
|
||||
"text": null,
|
||||
"bounds": [
|
||||
400,
|
||||
20,
|
||||
100,
|
||||
50
|
||||
],
|
||||
"color": [
|
||||
204,
|
||||
204,
|
||||
204,
|
||||
255
|
||||
],
|
||||
"font": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// The documentation items against Java's documentation mode
|
||||
// (documentation.java.json; tools/java-oracle's DocuRun on JDocuElement):
|
||||
// a new text and rectangle, the colours and font sizes Java's menus offer,
|
||||
// the font and colour chosen there, the smallest sizes a drag leaves, the
|
||||
// stacking, and a project Java saved with documentation (fixtures/
|
||||
// documentation.fls) read, kept in the document and written again.
|
||||
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import {
|
||||
DOCUMENTATION_FONT_SIZES,
|
||||
DOCUMENTATION_RECTANGLE_COLORS,
|
||||
DOCUMENTATION_TEXT_COLORS,
|
||||
documentationFontStyle,
|
||||
documentationItem,
|
||||
inJavaDrawingOrder,
|
||||
resizedFrame,
|
||||
type DocumentationColor,
|
||||
type DocumentationItem,
|
||||
} from '@mdg-filius/core';
|
||||
import { exportProject, openProjectArchive } from '@mdg-filius/fls';
|
||||
import { addDocumentation, documentationText, readProject, removeDocumentation, setDocumentationColor, setDocumentationFont, setDocumentationFrame, topologyView, writeProject } from '../src/index.js';
|
||||
|
||||
type Rgba = [number, number, number, number];
|
||||
interface JavaFont {
|
||||
readonly name: string;
|
||||
readonly style: number;
|
||||
readonly size: number;
|
||||
}
|
||||
interface JavaElement {
|
||||
readonly bounds: [number, number, number, number];
|
||||
readonly color: Rgba;
|
||||
readonly font: JavaFont | null;
|
||||
readonly text: string | null;
|
||||
}
|
||||
interface JavaItem extends JavaElement {
|
||||
readonly type: number;
|
||||
}
|
||||
interface JavaMenuEntry {
|
||||
readonly menu?: string;
|
||||
readonly items?: string[];
|
||||
readonly colors?: string;
|
||||
readonly list?: Rgba[];
|
||||
readonly rows?: number;
|
||||
readonly item?: string;
|
||||
readonly separator?: boolean;
|
||||
}
|
||||
|
||||
const here = dirname(fileURLToPath(import.meta.url));
|
||||
const java = JSON.parse(readFileSync(join(here, 'documentation.java.json'), 'utf8')) as {
|
||||
readonly new: { readonly text: JavaElement; readonly rectangle: JavaElement };
|
||||
readonly fonts: JavaFont[];
|
||||
readonly textColor: Rgba;
|
||||
readonly rectangleColor: Rgba;
|
||||
readonly menus: { readonly text: JavaMenuEntry[]; readonly rectangle: JavaMenuEntry[] };
|
||||
readonly smallest: Record<string, JavaElement>;
|
||||
readonly stacking: string[];
|
||||
readonly topAt: string;
|
||||
readonly saved: JavaItem[];
|
||||
readonly loaded: JavaItem[];
|
||||
};
|
||||
|
||||
const channel = (value: number) => Math.round(value * 255);
|
||||
const rgba = (color: DocumentationColor): Rgba => [channel(color.red), channel(color.green), channel(color.blue), channel(color.alpha)];
|
||||
/** An item as Java's GUIDocuItem shows it: its bounds, colour and font (none for a rectangle). */
|
||||
const asJava = (item: DocumentationItem) => ({
|
||||
bounds: [item.frame.x, item.frame.y, item.frame.width, item.frame.height],
|
||||
color: rgba(item.color),
|
||||
font: item.kind === 'text' ? { name: item.fontName, style: documentationFontStyle(item), size: item.fontSize } : null,
|
||||
text: item.kind === 'text' ? item.text : null,
|
||||
});
|
||||
const itemOf = (doc: Y.Doc, id: string) => readProject(doc).documentationItems.find((item) => item.id === id)!;
|
||||
|
||||
describe("the documentation items, against Java's documentation mode", () => {
|
||||
it('makes a new text and rectangle as Java does: 100 × 50 where dropped, a text empty and black in Dialog 12, a rectangle grey', () => {
|
||||
const doc = new Y.Doc();
|
||||
addDocumentation(doc, { id: 'text', kind: 'text', x: 100, y: 100 }, 'me');
|
||||
addDocumentation(doc, { id: 'rectangle', kind: 'rectangle', x: 300.4, y: 99.6 }, 'me');
|
||||
expect(asJava(itemOf(doc, 'text'))).toEqual(java.new.text);
|
||||
expect(asJava(itemOf(doc, 'rectangle'))).toEqual(java.new.rectangle);
|
||||
// After the others: of its kind the lowest.
|
||||
expect([itemOf(doc, 'text').order, itemOf(doc, 'rectangle').order]).toEqual([0, 1]);
|
||||
// An item made without a font has Java's: Dialog 12, plain.
|
||||
expect(documentationItem({ id: 'x', kind: 'text', frame: { x: 0, y: 0, width: 5, height: 5 }, color: DOCUMENTATION_TEXT_COLORS[0]!, order: 0 })).toMatchObject({
|
||||
fontName: 'Dialog',
|
||||
fontSize: 12,
|
||||
isBold: false,
|
||||
isItalic: false,
|
||||
frame: { x: 0, y: 0, width: 5, height: 5 },
|
||||
});
|
||||
});
|
||||
|
||||
it("offers Java's colours and font sizes, and sets font and colour as Java's menu does", () => {
|
||||
const colors = (entries: JavaMenuEntry[]) => entries.find((entry) => entry.list !== undefined)!;
|
||||
expect(DOCUMENTATION_TEXT_COLORS.map(rgba)).toEqual(colors(java.menus.text).list);
|
||||
expect(DOCUMENTATION_RECTANGLE_COLORS.map(rgba)).toEqual(colors(java.menus.rectangle).list);
|
||||
expect([colors(java.menus.text).rows, colors(java.menus.rectangle).rows]).toEqual([3, 3]);
|
||||
expect(java.menus.text.map((entry) => entry.menu ?? entry.colors ?? entry.item ?? '-')).toEqual(['Schriftgröße', 'Schriftstil', 'Farbe', '-', 'Löschen']);
|
||||
expect(java.menus.rectangle.map((entry) => entry.menu ?? entry.colors ?? entry.item ?? '-')).toEqual(['Farbe', '-', 'Löschen']);
|
||||
|
||||
// „Groß“, „Kursiv“, „Fett“, „Normal“, „Sehr Groß“: a style replaces the one before.
|
||||
const doc = new Y.Doc();
|
||||
addDocumentation(doc, { id: 'text', kind: 'text', x: 100, y: 100 }, 'me');
|
||||
const font = () => asJava(itemOf(doc, 'text')).font;
|
||||
const fonts = [];
|
||||
for (const change of [{ size: DOCUMENTATION_FONT_SIZES[1] }, { style: 'italic' }, { style: 'bold' }, { size: DOCUMENTATION_FONT_SIZES[0] }, { size: DOCUMENTATION_FONT_SIZES[2] }] as const) {
|
||||
setDocumentationFont(doc, 'text', change, 'me');
|
||||
fonts.push(font());
|
||||
}
|
||||
expect(fonts).toEqual(java.fonts);
|
||||
// The drawing knows the style too.
|
||||
setDocumentationFont(doc, 'text', { style: 'italic' }, 'me');
|
||||
expect(topologyView(doc).docs.map((item) => [item.bold, item.italic])).toEqual([[false, true]]);
|
||||
setDocumentationFont(doc, 'text', { style: 'plain' }, 'me');
|
||||
expect(font()?.style).toBe(0);
|
||||
setDocumentationColor(doc, 'text', DOCUMENTATION_TEXT_COLORS[7]!, 'me');
|
||||
expect(rgba(itemOf(doc, 'text').color)).toEqual(java.textColor);
|
||||
|
||||
// A rectangle takes a colour, not a font.
|
||||
addDocumentation(doc, { id: 'rectangle', kind: 'rectangle', x: 300, y: 100 }, 'me');
|
||||
setDocumentationFont(doc, 'rectangle', { size: 24, style: 'bold' }, 'me');
|
||||
setDocumentationColor(doc, 'rectangle', DOCUMENTATION_RECTANGLE_COLORS[4]!, 'me');
|
||||
expect(asJava(itemOf(doc, 'rectangle'))).toMatchObject({ color: java.rectangleColor, font: null });
|
||||
expect([itemOf(doc, 'rectangle').fontSize, itemOf(doc, 'rectangle').isBold]).toEqual([12, false]);
|
||||
});
|
||||
|
||||
it('leaves a rectangle at least 20 × 20 and a text at least 88 × 20, the opposite edge where it was', () => {
|
||||
const rectangle = { x: 300, y: 100, width: 100, height: 50 };
|
||||
const [x, y, width, height] = java.smallest.rectangle!.bounds;
|
||||
expect(resizedFrame(rectangle, 'rectangle', { right: true, bottom: true }, { x: 303, y: 103 })).toEqual({ x, y, width, height });
|
||||
// By its top left corner, far past the other: the right and lower edges stay.
|
||||
const small = { x: 300, y: 100, width: 20, height: 20 };
|
||||
const [ax, ay, aw, ah] = java.smallest.rectangleByItsTopLeft!.bounds;
|
||||
expect(resizedFrame(small, 'rectangle', { left: true, top: true }, { x: 500, y: 300 })).toEqual({ x: ax, y: ay, width: aw, height: ah });
|
||||
const [tx, ty, tw, th] = java.smallest.textSmallest!.bounds;
|
||||
expect(resizedFrame({ x: 100, y: 100, width: 100, height: 50 }, 'text', { right: true, bottom: true }, { x: 0, y: 0 })).toEqual({ x: tx, y: ty, width: tw, height: th });
|
||||
// Edges follow the pointer; the others stay.
|
||||
expect(resizedFrame(rectangle, 'rectangle', { left: true }, { x: 250, y: 0 })).toEqual({ x: 250, y: 100, width: 150, height: 50 });
|
||||
expect(resizedFrame(rectangle, 'rectangle', { top: true }, { x: 0, y: 130 })).toEqual({ x: 300, y: 130, width: 100, height: 20 });
|
||||
expect(resizedFrame(rectangle, 'rectangle', { bottom: true }, { x: 999, y: 300 })).toEqual({ x: 300, y: 100, width: 100, height: 200 });
|
||||
expect(resizedFrame(rectangle, 'rectangle', {}, { x: 0, y: 0 })).toEqual(rectangle);
|
||||
|
||||
// The document keeps whole pixels.
|
||||
const doc = new Y.Doc();
|
||||
addDocumentation(doc, { id: 'r', kind: 'rectangle', x: 0, y: 0 }, 'me');
|
||||
setDocumentationFrame(doc, 'r', { x: 10.4, y: 20.6, width: 30.5, height: 40.2 }, 'me');
|
||||
expect(itemOf(doc, 'r').frame).toEqual({ x: 10, y: 21, width: 31, height: 40 });
|
||||
});
|
||||
|
||||
it('stacks as Java does: texts over rectangles, of one kind the earlier over the later', () => {
|
||||
const doc = new Y.Doc();
|
||||
const names = ['text 1', 'rectangle 1', 'text 2', 'rectangle 2'];
|
||||
names.forEach((name, index) => addDocumentation(doc, { id: name, kind: name.startsWith('text') ? 'text' : 'rectangle', x: 50 + 10 * index, y: 50 + 10 * index }, 'me'));
|
||||
// Drawn in this order, the last on top; Java names the top one first.
|
||||
const drawn = topologyView(doc).docs.map((item) => item.id);
|
||||
expect([...drawn].reverse()).toEqual(java.stacking);
|
||||
expect(drawn.at(-1)).toBe(java.topAt);
|
||||
expect(inJavaDrawingOrder(readProject(doc).documentationItems).map((item) => item.id)).toEqual(drawn);
|
||||
});
|
||||
|
||||
it('reads what Java saved, keeps it in the document, and writes it as Java does', () => {
|
||||
let counter = 0;
|
||||
const project = openProjectArchive(new Uint8Array(readFileSync(join(here, 'fixtures/documentation.fls'))), () => `id-${++counter}`);
|
||||
expect(java.loaded).toEqual(java.saved);
|
||||
const byFile = (items: readonly DocumentationItem[]) => [...items].sort((lhs, rhs) => lhs.order - rhs.order);
|
||||
const javaItems = java.loaded.map(({ type, ...rest }) => ({ ...rest, type }));
|
||||
expect(byFile(project.documentationItems).map((item) => ({ ...asJava(item), type: item.kind === 'rectangle' ? 1 : 2 }))).toEqual(javaItems);
|
||||
|
||||
// Through the document and back: italic and all.
|
||||
const doc = new Y.Doc();
|
||||
writeProject(doc, project);
|
||||
expect(readProject(doc).documentationItems).toEqual(project.documentationItems);
|
||||
expect(documentationText(doc, project.documentationItems.find((item) => item.kind === 'text')!.id)).toBeInstanceOf(Y.Text);
|
||||
expect(documentationText(doc, project.documentationItems.find((item) => item.kind === 'rectangle')!.id)).toBeUndefined();
|
||||
|
||||
// Written as Java writes it (rectangles first in the list, which leaves Java's stacking of each kind as it was).
|
||||
const written = exportProject(readProject(doc)).topology.documentation.map((item) => ({
|
||||
...item,
|
||||
color: item.color === null ? null : [...item.color],
|
||||
font: item.font === null ? null : { ...item.font },
|
||||
}));
|
||||
const expected = [...java.saved.filter((item) => item.type === 1), ...java.saved.filter((item) => item.type === 2)].map((item) => ({
|
||||
type: item.type,
|
||||
text: item.text ?? '',
|
||||
x: item.bounds[0],
|
||||
y: item.bounds[1],
|
||||
width: item.bounds[2],
|
||||
height: item.bounds[3],
|
||||
color: item.color,
|
||||
// A rectangle has no font in Java; the model gives every item one.
|
||||
font: item.font ?? { name: 'Dialog', style: 0, size: 12 },
|
||||
}));
|
||||
expect(written).toEqual(expected);
|
||||
|
||||
// A colour of Java's menu is opaque, also over a translucent one from a file.
|
||||
const translucent = project.documentationItems[0]!;
|
||||
setDocumentationColor(doc, translucent.id, { ...translucent.color, alpha: 0.5 }, 'me');
|
||||
expect(itemOf(doc, translucent.id).color.alpha).toBe(0.5);
|
||||
setDocumentationColor(doc, translucent.id, DOCUMENTATION_RECTANGLE_COLORS[4]!, 'me');
|
||||
expect(rgba(itemOf(doc, translucent.id).color)).toEqual([255, 201, 201, 255]);
|
||||
|
||||
removeDocumentation(doc, project.documentationItems[0]!.id, 'me');
|
||||
expect(readProject(doc).documentationItems).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
BIN
Binary file not shown.
@@ -8,7 +8,7 @@ import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import * as Y from 'yjs';
|
||||
import { stableGeneratedMacAddress } from '@mdg-filius/core';
|
||||
import { inJavaDrawingOrder, stableGeneratedMacAddress } from '@mdg-filius/core';
|
||||
import { exportProject, openProjectArchive, type ImportedProject } from '@mdg-filius/fls';
|
||||
import { addDevice, captureCards, desktopView, readProject, renameDevice, setHostAddress, setHostLabels, setInterfaceAddress, topologyView, writeProject } from '../src/index.js';
|
||||
|
||||
@@ -42,7 +42,21 @@ describe('the drawing of a document', () => {
|
||||
project.graph.links.map((link) => [link.id, link.sourceNodeId, link.sourcePortId, link.targetNodeId, link.targetPortId]),
|
||||
);
|
||||
expect(view.links.map((link) => link.wireless)).toEqual(exportProject(project).topology.links.map((link) => link.wireless));
|
||||
expect(view.docs.map(({ id, kind, x, y, width, height, text, color, font, size, bold, order }) => ({ id, kind, frame: { x, y, width, height }, text, color, fontName: font, fontSize: size, isBold: bold, order }))).toEqual(project.documentationItems);
|
||||
// In Java's drawing order, the last on top.
|
||||
expect(
|
||||
view.docs.map(({ id, kind, x, y, width, height, text, color, font, size, bold, italic, order }) => ({
|
||||
id,
|
||||
kind,
|
||||
frame: { x, y, width, height },
|
||||
text,
|
||||
color,
|
||||
fontName: font,
|
||||
fontSize: size,
|
||||
isBold: bold,
|
||||
isItalic: italic,
|
||||
order,
|
||||
})),
|
||||
).toEqual(inJavaDrawingOrder(project.documentationItems));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user