reladraw 0.4.0 → 0.6.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/NOTICE +37 -0
- package/README.md +2 -2
- package/SYNTAX.md +164 -7
- package/dist/ast.d.ts +40 -3
- package/dist/ast.js +24 -2
- package/dist/cli.js +18 -2
- package/dist/grammar.d.ts +1 -1
- package/dist/grammar.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/model.d.ts +14 -1
- package/dist/parser.js +93 -4
- package/dist/render.d.ts +1 -22
- package/dist/render.js +806 -34
- package/dist/resolve.js +123 -9
- package/dist/themes.d.ts +46 -0
- package/dist/themes.js +190 -0
- package/package.json +1 -1
package/dist/resolve.js
CHANGED
|
@@ -23,11 +23,12 @@ export function resolve(doc, options = {}) {
|
|
|
23
23
|
const margin = options.margin ?? DEFAULT_MARGIN;
|
|
24
24
|
const styles = collectStyles(doc.statements);
|
|
25
25
|
checkStyleKeys(doc.statements);
|
|
26
|
-
const
|
|
26
|
+
const defaults = collectDefaults(doc.statements, styles);
|
|
27
|
+
const { nodes, byName, roots } = buildTree(doc.statements, styles, defaults);
|
|
27
28
|
// Edges are resolved to nodes before anything is sized, because a labeled
|
|
28
29
|
// edge claims room in the gap it crosses and so has to be in hand while the
|
|
29
30
|
// gaps are being worked out. Nothing here reads geometry.
|
|
30
|
-
const edges = buildEdges(doc.statements, byName, styles);
|
|
31
|
+
const edges = buildEdges(doc.statements, byName, styles, defaults);
|
|
31
32
|
const local = new Map();
|
|
32
33
|
for (const root of roots)
|
|
33
34
|
sizeNode(root, edges, measurer, fontSize, local);
|
|
@@ -58,6 +59,40 @@ function collectStyles(statements) {
|
|
|
58
59
|
}
|
|
59
60
|
return styles;
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Each kind's default, with any style it names already folded in beneath its
|
|
64
|
+
* own words, so a default arrives at a node as one flat bundle. The same kind
|
|
65
|
+
* written twice is refused, as a second `diagram` is: nothing says which was
|
|
66
|
+
* meant.
|
|
67
|
+
*/
|
|
68
|
+
function collectDefaults(statements, styles) {
|
|
69
|
+
const defaults = new Map();
|
|
70
|
+
const seen = new Map();
|
|
71
|
+
for (const stmt of statements) {
|
|
72
|
+
if (stmt.kind !== 'default')
|
|
73
|
+
continue;
|
|
74
|
+
const earlier = seen.get(stmt.target);
|
|
75
|
+
if (earlier !== undefined) {
|
|
76
|
+
throw new SourceError(`default ${stmt.target} is written twice, here and on line ${earlier} — keep one`, stmt.line);
|
|
77
|
+
}
|
|
78
|
+
seen.set(stmt.target, stmt.line);
|
|
79
|
+
const { style: _named, ...flat } = appearanceOf(stmt.attrs, styles, stmt.line);
|
|
80
|
+
defaults.set(stmt.target, flat);
|
|
81
|
+
}
|
|
82
|
+
return defaults;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* One bundle laid over a weaker one, key by key — except the body, which is
|
|
86
|
+
* one thing said by either of two words. A default's `shape: cylinder` under
|
|
87
|
+
* a style's `icon: disk` is not a node with two bodies; the style said what
|
|
88
|
+
* this node is drawn as, and that settles it.
|
|
89
|
+
*/
|
|
90
|
+
function over(base, top) {
|
|
91
|
+
if (top['shape'] === undefined && top['icon'] === undefined)
|
|
92
|
+
return { ...base, ...top };
|
|
93
|
+
const { shape: _shape, icon: _icon, ...rest } = base;
|
|
94
|
+
return { ...rest, ...top };
|
|
95
|
+
}
|
|
61
96
|
/** A file holds one diagram, so a second `diagram` statement is a mistake. */
|
|
62
97
|
function collectDiagram(statements) {
|
|
63
98
|
let found;
|
|
@@ -70,12 +105,27 @@ function collectDiagram(statements) {
|
|
|
70
105
|
}
|
|
71
106
|
return found ?? {};
|
|
72
107
|
}
|
|
73
|
-
function buildTree(statements, styles) {
|
|
108
|
+
function buildTree(statements, styles, defaults) {
|
|
74
109
|
const nodes = [];
|
|
75
110
|
const byName = new Map();
|
|
76
111
|
const roots = [];
|
|
77
112
|
/** Each badge child's name, and the node whose `badge:` it was written out from. */
|
|
78
113
|
const badges = new Map();
|
|
114
|
+
// Which nodes will hold children has to be known before any node is built,
|
|
115
|
+
// because it decides which default a node wears, and its children are
|
|
116
|
+
// declared after it. A child is a dotted name, or a badge the node's own
|
|
117
|
+
// line or styles give it; a default cannot give a leaf a badge, so what
|
|
118
|
+
// the defaults say cannot change the answer.
|
|
119
|
+
const parents = new Set();
|
|
120
|
+
for (const stmt of statements) {
|
|
121
|
+
if (stmt.kind !== 'node')
|
|
122
|
+
continue;
|
|
123
|
+
const cut = stmt.name.lastIndexOf('.');
|
|
124
|
+
if (cut !== -1)
|
|
125
|
+
parents.add(stmt.name.slice(0, cut));
|
|
126
|
+
if (appearanceOf(stmt.attrs, styles, stmt.line)['badge'] !== undefined)
|
|
127
|
+
parents.add(stmt.name);
|
|
128
|
+
}
|
|
79
129
|
for (const stmt of statements) {
|
|
80
130
|
if (stmt.kind !== 'node')
|
|
81
131
|
continue;
|
|
@@ -87,7 +137,9 @@ function buildTree(statements, styles) {
|
|
|
87
137
|
}
|
|
88
138
|
throw new SourceError(`"${stmt.name}" is declared twice`, stmt.line);
|
|
89
139
|
}
|
|
90
|
-
|
|
140
|
+
// Weakest first: every node's default, then the leaf's or the container's,
|
|
141
|
+
// then the node's own styles and words over both.
|
|
142
|
+
const appearance = over(over(defaults.get('node') ?? {}, defaults.get(parents.has(stmt.name) ? 'container' : 'leaf') ?? {}), appearanceOf(stmt.attrs, styles, stmt.line));
|
|
91
143
|
const body = bodyFor(stmt.attrs, appearance, stmt.line);
|
|
92
144
|
const kind = KIND_OF_BODY[body.kind];
|
|
93
145
|
// A node with no text of its own is labelled with its name, because the
|
|
@@ -275,6 +327,11 @@ function checkAttrs(kind, name, attrs, line) {
|
|
|
275
327
|
const parts = COLOR_PARTS[kind].map((part) => `\`${part}\``).join(', ');
|
|
276
328
|
throw new SourceError(`"${name}" is ${article(KIND_WORD[kind])} and has ${wrote}. ${capital(article(KIND_WORD[kind]))} is ${KIND_PARTS[kind]}, so it has no ${key} — it takes ${parts}`, line);
|
|
277
329
|
}
|
|
330
|
+
// A word only the whole drawing takes, such as a theme.
|
|
331
|
+
if (belongTo(key).length === 0) {
|
|
332
|
+
throw new SourceError(`"${name}" is ${article(KIND_WORD[kind])} and has ${wrote}. \`${key}:\` is said about the ` +
|
|
333
|
+
`whole drawing — write \`diagram ${key}: ${shown}\``, line);
|
|
334
|
+
}
|
|
278
335
|
// Anything else names no part, so what the kind is made of explains
|
|
279
336
|
// nothing. What does explain it is where the word *does* belong, which is
|
|
280
337
|
// also the more useful thing to be told: the author has usually written a
|
|
@@ -373,7 +430,7 @@ function appearanceOf(attrs, styles, line) {
|
|
|
373
430
|
}
|
|
374
431
|
return { ...merged, ...attrs };
|
|
375
432
|
}
|
|
376
|
-
function buildEdges(statements, byName, styles) {
|
|
433
|
+
function buildEdges(statements, byName, styles, defaults) {
|
|
377
434
|
const edges = [];
|
|
378
435
|
for (const stmt of statements) {
|
|
379
436
|
if (stmt.kind !== 'edge')
|
|
@@ -400,7 +457,26 @@ function buildEdges(statements, byName, styles) {
|
|
|
400
457
|
}),
|
|
401
458
|
...(stmt.between.axis !== undefined ? { axis: stmt.between.axis } : {}),
|
|
402
459
|
};
|
|
403
|
-
const
|
|
460
|
+
const passes = stmt.passes?.map((placement) => {
|
|
461
|
+
const written = describePlacement(placement);
|
|
462
|
+
return {
|
|
463
|
+
direction: placement.direction,
|
|
464
|
+
written,
|
|
465
|
+
nodes: placement.targets.map(({ name, part }) => {
|
|
466
|
+
if (part !== undefined) {
|
|
467
|
+
// The line passes a box, and a side or a point has no side of its
|
|
468
|
+
// own to be passed on. Refused by name rather than dropped.
|
|
469
|
+
throw new SourceError(`edge passes "${written}", and a line passes a whole box — drop "${part}"`, stmt.line);
|
|
470
|
+
}
|
|
471
|
+
const node = byName.get(name);
|
|
472
|
+
if (!node) {
|
|
473
|
+
throw new SourceError(`edge passes "${written}", and "${name}" does not exist`, stmt.line);
|
|
474
|
+
}
|
|
475
|
+
return node;
|
|
476
|
+
}),
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
const appearance = { ...defaults.get('edge'), ...appearanceOf(stmt.attrs, styles, stmt.line) };
|
|
404
480
|
const what = `${stmt.from} -> ${stmt.to}`;
|
|
405
481
|
checkAttrs('edge', what, stmt.attrs, stmt.line);
|
|
406
482
|
checkStyleUse('edge', what, stmt.attrs, styles, stmt.line);
|
|
@@ -422,6 +498,7 @@ function buildEdges(statements, byName, styles) {
|
|
|
422
498
|
? { text: stmt.text, lines: linesFor(stmt.text, textAttrs, `edge ${what}`, stmt.line) }
|
|
423
499
|
: {}),
|
|
424
500
|
...(between ? { between } : {}),
|
|
501
|
+
...(passes ? { passes } : {}),
|
|
425
502
|
attrs: stmt.attrs,
|
|
426
503
|
appearance,
|
|
427
504
|
line: stmt.line,
|
|
@@ -1211,6 +1288,30 @@ const AXIS_WORD = { x: 'horizontally', y: 'vertically' };
|
|
|
1211
1288
|
function memberOn(target, axis) {
|
|
1212
1289
|
return target.byAxis ? target.byAxis[axis] : target.index;
|
|
1213
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* The ways named sides can face away from the other end. Opposite sides face
|
|
1293
|
+
* away together or not at all (`left right` with `from` first); sides at right
|
|
1294
|
+
* angles each may, and either is enough (`left top` with `from` first, where
|
|
1295
|
+
* the edge turns back over the top, or with `to` below, where it goes round).
|
|
1296
|
+
*/
|
|
1297
|
+
function facingAway(fromSide, toSide) {
|
|
1298
|
+
// The axis a side is on, and whether it faces away when its own end is first.
|
|
1299
|
+
const SIDES = {
|
|
1300
|
+
left: { axis: 'x', first: true },
|
|
1301
|
+
right: { axis: 'x', first: false },
|
|
1302
|
+
top: { axis: 'y', first: true },
|
|
1303
|
+
bottom: { axis: 'y', first: false },
|
|
1304
|
+
};
|
|
1305
|
+
const f = SIDES[String(fromSide)];
|
|
1306
|
+
const t = SIDES[String(toSide)];
|
|
1307
|
+
if (!f || !t)
|
|
1308
|
+
return [];
|
|
1309
|
+
const byFrom = { axis: f.axis, fromFirst: f.first };
|
|
1310
|
+
const byTo = { axis: t.axis, fromFirst: !t.first };
|
|
1311
|
+
if (f.axis !== t.axis)
|
|
1312
|
+
return [byFrom, byTo];
|
|
1313
|
+
return byFrom.fromFirst === byTo.fromFirst ? [byFrom] : [];
|
|
1314
|
+
}
|
|
1214
1315
|
/**
|
|
1215
1316
|
* Where a node sits within the group being solved: which member holds it, and
|
|
1216
1317
|
* where inside that member. An edge may name anything at any depth, so its ends
|
|
@@ -1246,7 +1347,9 @@ function corridorsIn(edges, locate, measurer, fontSize) {
|
|
|
1246
1347
|
// edge told to pass between two named things carries its text in *that*
|
|
1247
1348
|
// corridor rather than in the gap between its own ends, so widening this one
|
|
1248
1349
|
// would make room where the text never goes.
|
|
1249
|
-
|
|
1350
|
+
// An edge that says which side of something it passes is routed round it,
|
|
1351
|
+
// and its text rides on that route for the same reason.
|
|
1352
|
+
if (edge.text === undefined || edge.between || edge.passes)
|
|
1250
1353
|
continue;
|
|
1251
1354
|
const from = locate(edge.from);
|
|
1252
1355
|
const to = locate(edge.to);
|
|
@@ -1259,7 +1362,13 @@ function corridorsIn(edges, locate, measurer, fontSize) {
|
|
|
1259
1362
|
// one end only would move the midpoint rather than lengthen the run.
|
|
1260
1363
|
const extent = (axis) => textExtent(edge.lines, edge.textAttrs, axis, measurer, fontSize, edge.line) +
|
|
1261
1364
|
(TEXT_CLEARANCE + ARROW_LENGTH) * 2;
|
|
1262
|
-
corridors.push({
|
|
1365
|
+
corridors.push({
|
|
1366
|
+
edge,
|
|
1367
|
+
from,
|
|
1368
|
+
to,
|
|
1369
|
+
need: { x: extent('x'), y: extent('y') },
|
|
1370
|
+
away: facingAway(edge.attrs['from'], edge.attrs['to']),
|
|
1371
|
+
});
|
|
1263
1372
|
}
|
|
1264
1373
|
return corridors;
|
|
1265
1374
|
}
|
|
@@ -1601,7 +1710,7 @@ function alignedAt(side, span, own) {
|
|
|
1601
1710
|
*/
|
|
1602
1711
|
function room(corridors, constraints, solved, solveAll, made) {
|
|
1603
1712
|
let added = false;
|
|
1604
|
-
for (const [index, { from, to, need }] of corridors.entries()) {
|
|
1713
|
+
for (const [index, { from, to, need, away }] of corridors.entries()) {
|
|
1605
1714
|
const clear = (axis) => {
|
|
1606
1715
|
const at = (end) => solved[axis][end.index] + end.offset[axis];
|
|
1607
1716
|
const size = (end) => (axis === 'x' ? end.width : end.height);
|
|
@@ -1615,6 +1724,11 @@ function room(corridors, constraints, solved, solveAll, made) {
|
|
|
1615
1724
|
if (open.length !== 1)
|
|
1616
1725
|
continue;
|
|
1617
1726
|
const axis = open[0];
|
|
1727
|
+
// Ends facing away along the only open axis mean the boxes share a row, and
|
|
1728
|
+
// the renderer takes the line over the top with its text — see `planLoops`.
|
|
1729
|
+
// Widening the gap would make room where the text never goes.
|
|
1730
|
+
if (away.some((way) => way.axis === axis && (clear(axis).before === from) === way.fromFirst))
|
|
1731
|
+
continue;
|
|
1618
1732
|
// Given already, and a minimum stays met: asking again would widen
|
|
1619
1733
|
// nothing and only keep the caller looking.
|
|
1620
1734
|
if (made.has(`${index}:${axis}`))
|
package/dist/themes.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The named themes a file chooses with `diagram theme: <name>`, and the one it
|
|
3
|
+
* gets when it says nothing.
|
|
4
|
+
*
|
|
5
|
+
* A theme supplies every color the file does not state. It never supplies a
|
|
6
|
+
* size or a distance, so switching themes cannot move anything — geometry may
|
|
7
|
+
* not depend on appearance.
|
|
8
|
+
*
|
|
9
|
+
* Most of the palettes are borrowed from editor color schemes, all of them MIT
|
|
10
|
+
* licensed and credited in NOTICE. Those palettes were made for code, where a
|
|
11
|
+
* color marks a keyword or a string; a diagram needs a page, two box fills, a
|
|
12
|
+
* border, text and a line. So each is the scheme read as a diagram rather than
|
|
13
|
+
* a transfer of it: the page is the scheme's background, a leaf is its raised
|
|
14
|
+
* surface, a container sits between the two, and the lines take one of its
|
|
15
|
+
* accents.
|
|
16
|
+
*/
|
|
17
|
+
export interface Theme {
|
|
18
|
+
background: string;
|
|
19
|
+
boxFill: string;
|
|
20
|
+
boxStroke: string;
|
|
21
|
+
containerFill: string;
|
|
22
|
+
/** A container is a region rather than a thing, so its outline is quieter. */
|
|
23
|
+
containerStroke: string;
|
|
24
|
+
text: string;
|
|
25
|
+
mutedText: string;
|
|
26
|
+
edge: string;
|
|
27
|
+
/** An icon's drawn line. */
|
|
28
|
+
iconInk: string;
|
|
29
|
+
/** The body an icon's lines enclose. */
|
|
30
|
+
iconShade: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sampled out of `examples/reference/arch.png` rather than invented,
|
|
34
|
+
* so the benchmark render and the drawing it is measured against differ by
|
|
35
|
+
* geometry and typography alone. A container is a shade off the page and barely
|
|
36
|
+
* outlined; a leaf is the navy that carries the diagram's weight.
|
|
37
|
+
*/
|
|
38
|
+
export declare const DARK_THEME: Theme;
|
|
39
|
+
/**
|
|
40
|
+
* Every theme a file may name, in the order they are offered. Pairs sit
|
|
41
|
+
* together, dark first, and the two that have no light half follow them.
|
|
42
|
+
*/
|
|
43
|
+
export declare const THEMES: Readonly<Record<string, Theme>>;
|
|
44
|
+
/** The theme a file gets when it names none. */
|
|
45
|
+
export declare const DEFAULT_THEME = "dark";
|
|
46
|
+
export declare const THEME_NAMES: readonly string[];
|
package/dist/themes.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sampled out of `examples/reference/arch.png` rather than invented,
|
|
3
|
+
* so the benchmark render and the drawing it is measured against differ by
|
|
4
|
+
* geometry and typography alone. A container is a shade off the page and barely
|
|
5
|
+
* outlined; a leaf is the navy that carries the diagram's weight.
|
|
6
|
+
*/
|
|
7
|
+
export const DARK_THEME = {
|
|
8
|
+
background: '#111111',
|
|
9
|
+
boxFill: '#191728',
|
|
10
|
+
boxStroke: '#4f5367',
|
|
11
|
+
containerFill: '#191920',
|
|
12
|
+
containerStroke: '#25242f',
|
|
13
|
+
text: '#d9d9d9',
|
|
14
|
+
mutedText: '#8b8b8b',
|
|
15
|
+
edge: '#5c5c7c',
|
|
16
|
+
// Both sampled off the reference's machine glyphs. Note that the reference
|
|
17
|
+
// gives each icon its own hue — the drive is gray, the laptop periwinkle, the
|
|
18
|
+
// workstation violet — which is a drawing tool's per-shape default and not a
|
|
19
|
+
// system. One pair for the whole set is the deliberate difference: an icon
|
|
20
|
+
// should read as part of the diagram's palette, not as clip art dropped in.
|
|
21
|
+
iconInk: '#8d8d8e',
|
|
22
|
+
iconShade: '#3e3d58',
|
|
23
|
+
};
|
|
24
|
+
/** The dark theme's counterpart: the same roles, on a white page. */
|
|
25
|
+
const LIGHT_THEME = {
|
|
26
|
+
background: '#ffffff',
|
|
27
|
+
boxFill: '#eef0f7',
|
|
28
|
+
boxStroke: '#8a90a8',
|
|
29
|
+
containerFill: '#f6f7fa',
|
|
30
|
+
containerStroke: '#dcdfe7',
|
|
31
|
+
text: '#1f2328',
|
|
32
|
+
mutedText: '#6e7781',
|
|
33
|
+
edge: '#7c83a0',
|
|
34
|
+
iconInk: '#57606a',
|
|
35
|
+
iconShade: '#d6d9e6',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Every theme a file may name, in the order they are offered. Pairs sit
|
|
39
|
+
* together, dark first, and the two that have no light half follow them.
|
|
40
|
+
*/
|
|
41
|
+
export const THEMES = {
|
|
42
|
+
dark: DARK_THEME,
|
|
43
|
+
light: LIGHT_THEME,
|
|
44
|
+
// Solarized, Ethan Schoonover. base03 page, base02 leaves, blue lines.
|
|
45
|
+
'solarized-dark': {
|
|
46
|
+
background: '#002b36',
|
|
47
|
+
boxFill: '#073642',
|
|
48
|
+
boxStroke: '#586e75',
|
|
49
|
+
containerFill: '#03313c',
|
|
50
|
+
containerStroke: '#0b3f4c',
|
|
51
|
+
text: '#93a1a1',
|
|
52
|
+
mutedText: '#657b83',
|
|
53
|
+
edge: '#268bd2',
|
|
54
|
+
iconInk: '#839496',
|
|
55
|
+
iconShade: '#0f4a58',
|
|
56
|
+
},
|
|
57
|
+
// base3 page, base2 leaves, the same blue.
|
|
58
|
+
'solarized-light': {
|
|
59
|
+
background: '#fdf6e3',
|
|
60
|
+
boxFill: '#eee8d5',
|
|
61
|
+
boxStroke: '#93a1a1',
|
|
62
|
+
containerFill: '#f6efdc',
|
|
63
|
+
containerStroke: '#e3dcc7',
|
|
64
|
+
text: '#586e75',
|
|
65
|
+
mutedText: '#93a1a1',
|
|
66
|
+
edge: '#268bd2',
|
|
67
|
+
iconInk: '#657b83',
|
|
68
|
+
iconShade: '#e0d9c3',
|
|
69
|
+
},
|
|
70
|
+
// Gruvbox, Pavel Pertsev. bg0 page, bg1 leaves, the warm yellow for lines.
|
|
71
|
+
'gruvbox-dark': {
|
|
72
|
+
background: '#282828',
|
|
73
|
+
boxFill: '#3c3836',
|
|
74
|
+
boxStroke: '#665c54',
|
|
75
|
+
containerFill: '#32302f',
|
|
76
|
+
containerStroke: '#3c3836',
|
|
77
|
+
text: '#ebdbb2',
|
|
78
|
+
mutedText: '#a89984',
|
|
79
|
+
edge: '#d79921',
|
|
80
|
+
iconInk: '#a89984',
|
|
81
|
+
iconShade: '#504945',
|
|
82
|
+
},
|
|
83
|
+
'gruvbox-light': {
|
|
84
|
+
background: '#fbf1c7',
|
|
85
|
+
boxFill: '#ebdbb2',
|
|
86
|
+
boxStroke: '#bdae93',
|
|
87
|
+
containerFill: '#f2e5bc',
|
|
88
|
+
containerStroke: '#e5d4a7',
|
|
89
|
+
text: '#3c3836',
|
|
90
|
+
mutedText: '#7c6f64',
|
|
91
|
+
edge: '#b57614',
|
|
92
|
+
iconInk: '#7c6f64',
|
|
93
|
+
iconShade: '#d5c4a1',
|
|
94
|
+
},
|
|
95
|
+
// Catppuccin. Mocha's base page and surface leaves, blue lines.
|
|
96
|
+
'catppuccin-mocha': {
|
|
97
|
+
background: '#1e1e2e',
|
|
98
|
+
boxFill: '#313244',
|
|
99
|
+
boxStroke: '#6c7086',
|
|
100
|
+
containerFill: '#25253a',
|
|
101
|
+
containerStroke: '#313244',
|
|
102
|
+
text: '#cdd6f4',
|
|
103
|
+
mutedText: '#9399b2',
|
|
104
|
+
edge: '#89b4fa',
|
|
105
|
+
iconInk: '#a6adc8',
|
|
106
|
+
iconShade: '#45475a',
|
|
107
|
+
},
|
|
108
|
+
// Latte's base page and crust leaves, lavender lines.
|
|
109
|
+
'catppuccin-latte': {
|
|
110
|
+
background: '#eff1f5',
|
|
111
|
+
boxFill: '#dce0e8',
|
|
112
|
+
boxStroke: '#9ca0b0',
|
|
113
|
+
containerFill: '#e6e9ef',
|
|
114
|
+
containerStroke: '#ccd0da',
|
|
115
|
+
text: '#4c4f69',
|
|
116
|
+
mutedText: '#7c7f93',
|
|
117
|
+
edge: '#7287fd',
|
|
118
|
+
iconInk: '#6c6f85',
|
|
119
|
+
iconShade: '#ccd0da',
|
|
120
|
+
},
|
|
121
|
+
// Nord, Sven Greb. Polar Night page and leaves, Frost lines.
|
|
122
|
+
nord: {
|
|
123
|
+
background: '#2e3440',
|
|
124
|
+
boxFill: '#3b4252',
|
|
125
|
+
boxStroke: '#4c566a',
|
|
126
|
+
containerFill: '#333a47',
|
|
127
|
+
containerStroke: '#3b4252',
|
|
128
|
+
text: '#d8dee9',
|
|
129
|
+
mutedText: '#7b88a1',
|
|
130
|
+
edge: '#81a1c1',
|
|
131
|
+
iconInk: '#aeb7c6',
|
|
132
|
+
iconShade: '#434c5e',
|
|
133
|
+
},
|
|
134
|
+
// Dracula, the free palette. Current-line leaves, comment borders, purple lines.
|
|
135
|
+
dracula: {
|
|
136
|
+
background: '#282a36',
|
|
137
|
+
boxFill: '#44475a',
|
|
138
|
+
boxStroke: '#6272a4',
|
|
139
|
+
containerFill: '#21222c',
|
|
140
|
+
containerStroke: '#343746',
|
|
141
|
+
text: '#f8f8f2',
|
|
142
|
+
mutedText: '#6272a4',
|
|
143
|
+
edge: '#bd93f9',
|
|
144
|
+
iconInk: '#b6b9cc',
|
|
145
|
+
iconShade: '#565a70',
|
|
146
|
+
},
|
|
147
|
+
// For low vision and projectors: no fills to lean on, every line at full
|
|
148
|
+
// strength, and a container told apart by a gray outline alone.
|
|
149
|
+
'high-contrast-dark': {
|
|
150
|
+
background: '#000000',
|
|
151
|
+
boxFill: '#000000',
|
|
152
|
+
boxStroke: '#ffffff',
|
|
153
|
+
containerFill: '#000000',
|
|
154
|
+
containerStroke: '#9a9a9a',
|
|
155
|
+
text: '#ffffff',
|
|
156
|
+
mutedText: '#c8c8c8',
|
|
157
|
+
edge: '#ffffff',
|
|
158
|
+
iconInk: '#ffffff',
|
|
159
|
+
iconShade: '#3a3a3a',
|
|
160
|
+
},
|
|
161
|
+
'high-contrast-light': {
|
|
162
|
+
background: '#ffffff',
|
|
163
|
+
boxFill: '#ffffff',
|
|
164
|
+
boxStroke: '#000000',
|
|
165
|
+
containerFill: '#ffffff',
|
|
166
|
+
containerStroke: '#6a6a6a',
|
|
167
|
+
text: '#000000',
|
|
168
|
+
mutedText: '#3d3d3d',
|
|
169
|
+
edge: '#000000',
|
|
170
|
+
iconInk: '#000000',
|
|
171
|
+
iconShade: '#d0d0d0',
|
|
172
|
+
},
|
|
173
|
+
// For paper: no fill anywhere an ink cartridge would notice, black lines,
|
|
174
|
+
// gray only where the dark theme is quiet.
|
|
175
|
+
print: {
|
|
176
|
+
background: '#ffffff',
|
|
177
|
+
boxFill: '#ffffff',
|
|
178
|
+
boxStroke: '#000000',
|
|
179
|
+
containerFill: '#ffffff',
|
|
180
|
+
containerStroke: '#8c8c8c',
|
|
181
|
+
text: '#000000',
|
|
182
|
+
mutedText: '#666666',
|
|
183
|
+
edge: '#333333',
|
|
184
|
+
iconInk: '#000000',
|
|
185
|
+
iconShade: '#ffffff',
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
/** The theme a file gets when it names none. */
|
|
189
|
+
export const DEFAULT_THEME = 'dark';
|
|
190
|
+
export const THEME_NAMES = Object.keys(THEMES);
|