reladraw 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -1
- package/SYNTAX.md +9 -1
- package/dist/element.d.ts +15 -0
- package/dist/element.js +87 -0
- package/dist/render.js +29 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -48,6 +48,22 @@ npm install && npm run build
|
|
|
48
48
|
node dist/cli.js examples/arch.reladraw -o out.svg
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
## In a web page
|
|
52
|
+
|
|
53
|
+
Load one script, then write diagrams straight into the page:
|
|
54
|
+
|
|
55
|
+
```html
|
|
56
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/reladraw/dist/element.js"></script>
|
|
57
|
+
|
|
58
|
+
<reladraw-diagram>
|
|
59
|
+
node a "Parser"
|
|
60
|
+
node b "Renderer" right of a
|
|
61
|
+
edge a -> b
|
|
62
|
+
</reladraw-diagram>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Each `<reladraw-diagram>` is replaced by its SVG. Indent the source however suits the page, since the indentation its lines share is taken off. Add `theme="light"` (or any other theme) to change the colors. A mistake is shown where the diagram would have been, with its line number counted from the first line of the diagram. The page's HTML is read first, so a `<` followed by a letter inside a text has to be written `<`.
|
|
66
|
+
|
|
51
67
|
## Using it with an agent
|
|
52
68
|
|
|
53
69
|
To install a skill to let your agent know how to use reladraw:
|
|
@@ -66,7 +82,7 @@ That gets you a copy of the skill at the time you run it, so you'll need to re-r
|
|
|
66
82
|
|
|
67
83
|
## Status
|
|
68
84
|
|
|
69
|
-
Version 0.
|
|
85
|
+
Version 0.12.0. Early stage, but works. The parser, layout engine, and SVG renderer are written in TypeScript, with zero runtime dependencies. There is a command-line tool that turns a .reladraw text file into an SVG, and an element that does the same inside a web page.
|
|
70
86
|
|
|
71
87
|
The language isn't stable yet, so expect the syntax to change.
|
|
72
88
|
|
package/SYNTAX.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Syntax reference — 0.
|
|
1
|
+
# Syntax reference — 0.12.0
|
|
2
2
|
|
|
3
3
|
What the language accepts. The parser, resolver and SVG renderer implement all of it; the sections at the end record what is defective, unchecked or undecided.
|
|
4
4
|
|
|
@@ -1068,6 +1068,14 @@ That one was found by testing the lexer, not by rendering — and it could not h
|
|
|
1068
1068
|
|
|
1069
1069
|
Pre-1.0, so the minor number is where a breaking change goes. Every removal below is refused by name with the replacement quoted, rather than dropped in silence — an older file stops with an error saying what to write instead.
|
|
1070
1070
|
|
|
1071
|
+
**0.12.0**
|
|
1072
|
+
|
|
1073
|
+
- Diagrams can be written straight into a web page. Load `dist/element.js` from the package, and each `<reladraw-diagram>` element is drawn in place from the source inside it, with `theme=` choosing the colors. See "In a web page" in the README.
|
|
1074
|
+
|
|
1075
|
+
**0.11.1**
|
|
1076
|
+
|
|
1077
|
+
- Several diagrams inlined into one web page no longer lose their arrowheads. Every drawing used to name its arrowheads the same way, so a page took them all from the first drawing, and drew none when that drawing was hidden. The gaps where lines cross were named the same way, so a second drawing's lines were cut where the first drawing's crossed. Each drawing's names now carry a prefix of its own, taken from the drawing, so the same source still gives the same SVG.
|
|
1078
|
+
|
|
1071
1079
|
**0.11.0**
|
|
1072
1080
|
|
|
1073
1081
|
- A line never passes through a box. Every edge takes the shortest way between its ends that goes through none, and goes round whatever is in its way, including edges that name no side. This replaces the separate rules for lines going over a row, turning back and going round a node behind, which kept missing cases; a fan of three from one side into a column was the latest. See "Going round boxes".
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare class ReladrawDiagram extends HTMLElement {
|
|
2
|
+
#private;
|
|
3
|
+
static observedAttributes: string[];
|
|
4
|
+
connectedCallback(): void;
|
|
5
|
+
attributeChangedCallback(): void;
|
|
6
|
+
/** The diagram's source, as read from the page or last set. */
|
|
7
|
+
get source(): string;
|
|
8
|
+
set source(text: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Source as written inside an indented page: the blank lines around it
|
|
12
|
+
* dropped, and the indentation every line shares taken off, so a continuation
|
|
13
|
+
* line is indented relative to its statement and nothing else.
|
|
14
|
+
*/
|
|
15
|
+
export declare function dedent(text: string): string;
|
package/dist/element.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// The only module that runs in a browser and nowhere else. It is compiled by
|
|
2
|
+
// tsconfig.element.json, the one build that sees the DOM's types.
|
|
3
|
+
/**
|
|
4
|
+
* `<reladraw-diagram>`: reladraw source written straight into a web page and
|
|
5
|
+
* drawn in its place as an SVG. Loading this module is the whole setup:
|
|
6
|
+
*
|
|
7
|
+
* <script type="module" src="https://cdn.jsdelivr.net/npm/reladraw/dist/element.js"></script>
|
|
8
|
+
* <reladraw-diagram>
|
|
9
|
+
* node a "Hello"
|
|
10
|
+
* node b "World" right of a
|
|
11
|
+
* edge a -> b
|
|
12
|
+
* </reladraw-diagram>
|
|
13
|
+
*
|
|
14
|
+
* `theme="dark"` renders in a named theme, as the command line's `--theme` does.
|
|
15
|
+
* Setting `.source` from a script redraws with new text.
|
|
16
|
+
*/
|
|
17
|
+
import { SourceError } from './errors.js';
|
|
18
|
+
import { compile } from './index.js';
|
|
19
|
+
import { THEME_NAMES, THEMES } from './themes.js';
|
|
20
|
+
export class ReladrawDiagram extends HTMLElement {
|
|
21
|
+
static observedAttributes = ['theme'];
|
|
22
|
+
#source;
|
|
23
|
+
connectedCallback() {
|
|
24
|
+
if (this.#source !== undefined) {
|
|
25
|
+
this.#draw();
|
|
26
|
+
}
|
|
27
|
+
else if (document.readyState === 'loading') {
|
|
28
|
+
// Only when the module was loaded `async`, or before the page's own
|
|
29
|
+
// markup: the element is connected when its opening tag is parsed, and
|
|
30
|
+
// the text inside it has not arrived yet.
|
|
31
|
+
document.addEventListener('DOMContentLoaded', () => this.connectedCallback(), { once: true });
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.#source = dedent(this.textContent ?? '');
|
|
35
|
+
this.#draw();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
attributeChangedCallback() {
|
|
39
|
+
if (this.#source !== undefined)
|
|
40
|
+
this.#draw();
|
|
41
|
+
}
|
|
42
|
+
/** The diagram's source, as read from the page or last set. */
|
|
43
|
+
get source() {
|
|
44
|
+
return this.#source ?? '';
|
|
45
|
+
}
|
|
46
|
+
set source(text) {
|
|
47
|
+
this.#source = text;
|
|
48
|
+
this.#draw();
|
|
49
|
+
}
|
|
50
|
+
#draw() {
|
|
51
|
+
const name = this.getAttribute('theme');
|
|
52
|
+
if (name !== null && THEMES[name] === undefined) {
|
|
53
|
+
this.#fail(`there is no theme called "${name}" — the themes are ${THEME_NAMES.join(', ')}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
this.innerHTML = compile(this.source, name === null ? {} : { theme: THEMES[name] });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (!(error instanceof SourceError))
|
|
61
|
+
throw error;
|
|
62
|
+
this.#fail(error.format());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Said in the page where the diagram would have been, and in the console. */
|
|
66
|
+
#fail(message) {
|
|
67
|
+
const pre = document.createElement('pre');
|
|
68
|
+
pre.textContent = `reladraw: ${message}`;
|
|
69
|
+
pre.style.color = 'crimson';
|
|
70
|
+
this.replaceChildren(pre);
|
|
71
|
+
console.error(`reladraw: ${message}`, this);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Source as written inside an indented page: the blank lines around it
|
|
76
|
+
* dropped, and the indentation every line shares taken off, so a continuation
|
|
77
|
+
* line is indented relative to its statement and nothing else.
|
|
78
|
+
*/
|
|
79
|
+
export function dedent(text) {
|
|
80
|
+
const lines = text.replace(/^\s*\n|\s+$/g, '').split('\n');
|
|
81
|
+
const indents = lines.filter((line) => line.trim() !== '').map((line) => /^[ \t]*/.exec(line)[0].length);
|
|
82
|
+
const shared = Math.min(...indents, Infinity);
|
|
83
|
+
return lines.map((line) => line.slice(Math.min(shared, /^[ \t]*/.exec(line)[0].length))).join('\n');
|
|
84
|
+
}
|
|
85
|
+
if (customElements.get('reladraw-diagram') === undefined) {
|
|
86
|
+
customElements.define('reladraw-diagram', ReladrawDiagram);
|
|
87
|
+
}
|
package/dist/render.js
CHANGED
|
@@ -78,7 +78,7 @@ export function render(layout, options = {}) {
|
|
|
78
78
|
height: Math.ceil(ink.maxY) - Math.floor(ink.minY),
|
|
79
79
|
};
|
|
80
80
|
const arrowColors = new Set(layout.edges.map((edge) => lineOf(edge.appearance, theme, theme.edge)));
|
|
81
|
-
|
|
81
|
+
const svg = [
|
|
82
82
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${canvas.width}" height="${canvas.height}" viewBox="${canvas.x} ${canvas.y} ${canvas.width} ${canvas.height}" font-family=${quote(measurer.fontFamily)} font-size="${fontSize}px">`,
|
|
83
83
|
' <defs>',
|
|
84
84
|
...[...arrowColors].map((color) => arrowMarker(color)),
|
|
@@ -97,6 +97,34 @@ export function render(layout, options = {}) {
|
|
|
97
97
|
'</svg>',
|
|
98
98
|
'',
|
|
99
99
|
].join('\n');
|
|
100
|
+
return ownIds(svg);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Gives every id in a drawing a prefix of its own, taken from a hash of the
|
|
104
|
+
* drawing.
|
|
105
|
+
*
|
|
106
|
+
* Ids are shared by every SVG inlined into one page, and a reference resolves to
|
|
107
|
+
* the first element in the page carrying the name. With plain names, a second
|
|
108
|
+
* drawing's arrowheads came from the first one — and when the first was hidden,
|
|
109
|
+
* in a closed dialog or a collapsed tab, they were not drawn at all. Its masks
|
|
110
|
+
* would have cut the second drawing's lines where the first one's crossed.
|
|
111
|
+
*
|
|
112
|
+
* A hash rather than a counter, so the same source gives the same bytes on every
|
|
113
|
+
* run. Two drawings that share a prefix are byte-for-byte the same, so whichever
|
|
114
|
+
* copy a reference lands on draws the same thing — unless that copy is hidden,
|
|
115
|
+
* which is the one case this does not cover.
|
|
116
|
+
*
|
|
117
|
+
* Only attribute positions are rewritten. Anything a diagram's author wrote is
|
|
118
|
+
* escaped before it reaches an attribute, so it cannot contain the `"` both
|
|
119
|
+
* patterns begin or end with.
|
|
120
|
+
*/
|
|
121
|
+
function ownIds(svg) {
|
|
122
|
+
let hash = 0x811c9dc5;
|
|
123
|
+
for (let i = 0; i < svg.length; i++) {
|
|
124
|
+
hash = Math.imul(hash ^ svg.charCodeAt(i), 0x01000193);
|
|
125
|
+
}
|
|
126
|
+
const prefix = `r${(hash >>> 0).toString(16).padStart(8, '0')}-`;
|
|
127
|
+
return svg.replaceAll(' id="', ` id="${prefix}`).replaceAll('="url(#', `="url(#${prefix}`);
|
|
100
128
|
}
|
|
101
129
|
// --- nodes -------------------------------------------------------------------
|
|
102
130
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "reladraw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "A diagram language where you say where things go.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"url": "https://github.com/reladraw/reladraw/issues"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"build": "tsc",
|
|
35
|
+
"build": "tsc && tsc -p tsconfig.element.json",
|
|
36
36
|
"prepublishOnly": "npm run build",
|
|
37
37
|
"preversion": "./dev.sh check-version \"$npm_new_version\"",
|
|
38
38
|
"dev": "tsc --watch",
|