@meltemi/diagrams-editor 1.9.0 → 1.10.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/dist/add-icons.d.ts +44 -0
- package/dist/add-icons.d.ts.map +1 -0
- package/dist/add-icons.js +178 -0
- package/dist/add-icons.js.map +1 -0
- package/dist/arrange.d.ts.map +1 -1
- package/dist/arrange.js +0 -1
- package/dist/arrange.js.map +1 -1
- package/dist/asset-files.d.ts +46 -0
- package/dist/asset-files.d.ts.map +1 -0
- package/dist/asset-files.js +79 -0
- package/dist/asset-files.js.map +1 -0
- package/dist/c4.d.ts +91 -1
- package/dist/c4.d.ts.map +1 -1
- package/dist/c4.js +358 -9
- package/dist/c4.js.map +1 -1
- package/dist/canvas/keyboard.d.ts +3 -1
- package/dist/canvas/keyboard.d.ts.map +1 -1
- package/dist/canvas/keyboard.js +4 -0
- package/dist/canvas/keyboard.js.map +1 -1
- package/dist/canvas/palette.d.ts +7 -5
- package/dist/canvas/palette.d.ts.map +1 -1
- package/dist/canvas/palette.js +13 -9
- package/dist/canvas/palette.js.map +1 -1
- package/dist/canvas/protocol.d.ts +223 -7
- package/dist/canvas/protocol.d.ts.map +1 -1
- package/dist/canvas/protocol.js +58 -1
- package/dist/canvas/protocol.js.map +1 -1
- package/dist/canvas/routes.d.ts +17 -0
- package/dist/canvas/routes.d.ts.map +1 -1
- package/dist/canvas/routes.js +37 -0
- package/dist/canvas/routes.js.map +1 -1
- package/dist/canvas/turning.d.ts +7 -0
- package/dist/canvas/turning.d.ts.map +1 -1
- package/dist/canvas/turning.js +17 -3
- package/dist/canvas/turning.js.map +1 -1
- package/dist/clipboard.d.ts.map +1 -1
- package/dist/clipboard.js +61 -9
- package/dist/clipboard.js.map +1 -1
- package/dist/effects.d.ts +5 -0
- package/dist/effects.d.ts.map +1 -1
- package/dist/effects.js +28 -22
- package/dist/effects.js.map +1 -1
- package/dist/exports.d.ts +19 -11
- package/dist/exports.d.ts.map +1 -1
- package/dist/exports.js +54 -19
- package/dist/exports.js.map +1 -1
- package/dist/icons.d.ts +2 -0
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +39 -6
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/inspector.d.ts.map +1 -1
- package/dist/inspector.js +229 -97
- package/dist/inspector.js.map +1 -1
- package/dist/messages/en.d.ts +55 -20
- package/dist/messages/en.d.ts.map +1 -1
- package/dist/messages/en.js +59 -26
- package/dist/messages/en.js.map +1 -1
- package/dist/messages/es.d.ts.map +1 -1
- package/dist/messages/es.js +47 -43
- package/dist/messages/es.js.map +1 -1
- package/dist/messages/index.d.ts +43 -17
- package/dist/messages/index.d.ts.map +1 -1
- package/dist/messages/keys.d.ts.map +1 -1
- package/dist/messages/keys.js +2 -0
- package/dist/messages/keys.js.map +1 -1
- package/dist/messages/services.d.ts +6 -0
- package/dist/messages/services.d.ts.map +1 -1
- package/dist/messages/services.js +8 -2
- package/dist/messages/services.js.map +1 -1
- package/dist/move-file.d.ts +32 -0
- package/dist/move-file.d.ts.map +1 -0
- package/dist/move-file.js +58 -0
- package/dist/move-file.js.map +1 -0
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js.map +1 -1
- package/dist/projects.d.ts +65 -1
- package/dist/projects.d.ts.map +1 -1
- package/dist/projects.js +349 -1
- package/dist/projects.js.map +1 -1
- package/dist/services/actions.d.ts.map +1 -1
- package/dist/services/actions.js +11 -1
- package/dist/services/actions.js.map +1 -1
- package/dist/services/completion.d.ts.map +1 -1
- package/dist/services/completion.js +46 -6
- package/dist/services/completion.js.map +1 -1
- package/dist/services/hover.d.ts.map +1 -1
- package/dist/services/hover.js +3 -2
- package/dist/services/hover.js.map +1 -1
- package/dist/services/navigation.d.ts +1 -1
- package/dist/services/navigation.d.ts.map +1 -1
- package/dist/services/navigation.js +2 -1
- package/dist/services/navigation.js.map +1 -1
- package/dist/session.d.ts +56 -10
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +650 -38
- package/dist/session.js.map +1 -1
- package/dist/targets.d.ts +25 -4
- package/dist/targets.d.ts.map +1 -1
- package/dist/targets.js +103 -7
- package/dist/targets.js.map +1 -1
- package/dist/text-places.d.ts.map +1 -1
- package/dist/text-places.js +55 -1
- package/dist/text-places.js.map +1 -1
- package/dist/themes.d.ts.map +1 -1
- package/dist/themes.js +11 -1
- package/dist/themes.js.map +1 -1
- package/package.json +16 -16
package/dist/inspector.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ARROWHEADS, BOX_SHAPES, CORNERS, BUILT_IN_THEMES, DEFAULT_THEME, FONT_WEIGHTS, TEXT_ALIGNS, DIRECTIONS, ELEMENT_ONLY_PROPERTIES, ICON_POSITIONS, LABEL_POSITIONS, LAYOUT_MODES, LINE_STYLES, PATTERNS, PORTS, PRESETS, PROFILES, RELATION_ONLY_PROPERTIES, ROUTINGS, STYLE_PROPERTIES, THEME_COLOR_TOKENS, takesRotation, takesShape, } from "@meltemi/diagrams-model";
|
|
1
|
+
import { ARROWHEADS, BOX_SHAPES, CORNERS, BUILT_IN_THEMES, DEFAULT_THEME, FONT_WEIGHTS, TEXT_ALIGNS, VERTICAL_ALIGNS, DIRECTIONS, ELEMENT_ONLY_PROPERTIES, ICON_POSITIONS, LABEL_POSITIONS, LAYOUT_MODES, LINE_STYLES, PATTERNS, PORTS, PRESETS, PROFILES, RELATION_ONLY_PROPERTIES, ROUTINGS, STYLE_PROPERTIES, THEME_COLOR_TOKENS, takesRotation, takesShape, } from "@meltemi/diagrams-model";
|
|
2
2
|
import { CORNERED_SHAPES, StyleResolver } from "@meltemi/diagrams-layout";
|
|
3
3
|
import { DEFAULT_DURATIONS, themeDurations } from "@meltemi/diagrams-motion";
|
|
4
4
|
import { diagramView } from "./analysis.js";
|
|
5
5
|
import { interiorOf, steadyGroups } from "./arrange.js";
|
|
6
6
|
import { c4DiagramFields, c4ElementFields, c4FieldOperations, c4RelationFields } from "./c4.js";
|
|
7
|
-
import { effectFields, effectLabel, effectOperations, effectsFor } from "./effects.js";
|
|
7
|
+
import { effectFields, effectLabel, effectOperations, effectsFor, selectionAfter, } from "./effects.js";
|
|
8
8
|
import { drawnIconSize, iconChoice, iconKind } from "./icons.js";
|
|
9
9
|
import { messages } from "./messages/index.js";
|
|
10
10
|
import { freshRelationId, namedNotice, namedRelation, styleTargets } from "./targets.js";
|
|
@@ -97,12 +97,27 @@ const STYLE_FIELDS = [
|
|
|
97
97
|
glyphs: "shape",
|
|
98
98
|
elements: (kind) => takesShape(kind),
|
|
99
99
|
},
|
|
100
|
+
// Free boxes and texts: where the text sits across; boxes also where it sits down the shape.
|
|
101
|
+
// The canvas draws the two as one grid of cells.
|
|
102
|
+
{
|
|
103
|
+
name: "textAlign",
|
|
104
|
+
type: "enum",
|
|
105
|
+
options: TEXT_ALIGNS,
|
|
106
|
+
glyphs: "textAlign",
|
|
107
|
+
elements: (kind, diagram) => diagram.type === "free" && (kind === "box" || kind === "text"),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "verticalAlign",
|
|
111
|
+
type: "enum",
|
|
112
|
+
options: VERTICAL_ALIGNS,
|
|
113
|
+
glyphs: "verticalAlign",
|
|
114
|
+
elements: (kind, diagram) => diagram.type === "free" && kind === "box",
|
|
115
|
+
},
|
|
100
116
|
];
|
|
101
117
|
/** Style fields of free diagrams: text. */
|
|
102
118
|
const FREE_STYLE_FIELDS = [
|
|
103
119
|
{ name: "fontSize", type: "number" },
|
|
104
120
|
{ name: "fontWeight", type: "enum", options: FONT_WEIGHTS.map(String) },
|
|
105
|
-
{ name: "textAlign", type: "enum", options: TEXT_ALIGNS },
|
|
106
121
|
];
|
|
107
122
|
/** The word for a kind in titles: the interface's, or the kind itself for declared types. */
|
|
108
123
|
function kindName(kind) {
|
|
@@ -127,7 +142,10 @@ const shown = (value) => value === undefined
|
|
|
127
142
|
? value.slice(1)
|
|
128
143
|
: value;
|
|
129
144
|
function styleFields(diagram, targets) {
|
|
130
|
-
|
|
145
|
+
// Free lines take a relation's properties and none of a box's.
|
|
146
|
+
const isLine = (t) => t.kind === "element" && diagram.elementsById.get(t.id)?.kind === "line";
|
|
147
|
+
const lines = targets.length > 0 && targets.every(isLine);
|
|
148
|
+
const kinds = new Set(targets.map((t) => (isLine(t) ? "relation" : t.kind)));
|
|
131
149
|
const styles = targets.map((t) => writtenStyle(diagram, t.id, t.kind));
|
|
132
150
|
const fields = diagram.type === "free" ? [...STYLE_FIELDS, ...FREE_STYLE_FIELDS] : STYLE_FIELDS;
|
|
133
151
|
const labels = messages().style;
|
|
@@ -137,10 +155,11 @@ function styleFields(diagram, targets) {
|
|
|
137
155
|
!(kinds.has("relation") && ELEMENT_ONLY_PROPERTIES.includes(f.name)) &&
|
|
138
156
|
!(diagram.type === "sequence" && NOT_IN_SEQUENCES.has(f.name)) &&
|
|
139
157
|
(f.elements === undefined ||
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
(!lines &&
|
|
159
|
+
targets.every((t) => {
|
|
160
|
+
const element = t.kind === "element" ? diagram.elementsById.get(t.id) : undefined;
|
|
161
|
+
return element !== undefined && f.elements(element.kind, diagram);
|
|
162
|
+
}))))
|
|
144
163
|
.map((f) => {
|
|
145
164
|
const values = styles.map((s) => shown(s[f.name]));
|
|
146
165
|
const same = values.every((v) => v === values[0]);
|
|
@@ -170,6 +189,11 @@ function withElementDefaults(fields, view, theme, ids) {
|
|
|
170
189
|
const elements = ids.flatMap((id) => view.diagram.elementsById.get(id) ?? []);
|
|
171
190
|
const shared = (values) => values.length > 0 && values.every((v) => v === values[0]) ? values[0] : undefined;
|
|
172
191
|
const styles = new StyleResolver(theme, view.diagram);
|
|
192
|
+
// The styles the targets would take without their own `style` statements.
|
|
193
|
+
const inherited = new StyleResolver(theme, {
|
|
194
|
+
...view.diagram,
|
|
195
|
+
styles: view.diagram.styles.filter((entry) => !ids.includes(entry.target)),
|
|
196
|
+
});
|
|
173
197
|
const shapeOf = (e) => view.layout?.shapes?.get(e.id) ?? styles.element(e).shape ?? e.shape ?? "rounded";
|
|
174
198
|
// The radius drawn without one written: the theme's on rounded boxes, notes and frames, the
|
|
175
199
|
// group radius on groups, a person's or an agent's body radius, and none on the other corners.
|
|
@@ -200,8 +224,23 @@ function withElementDefaults(fields, view, theme, ids) {
|
|
|
200
224
|
const centrePiece = (e) => iconKind(e.kind, view.diagram) && (iconShaped(e) || position(e) === "center");
|
|
201
225
|
const some = elements.length > 0;
|
|
202
226
|
const icon = shared(elements.map((e) => styles.element(e).icon ?? ""));
|
|
227
|
+
// Free lines: no head unless written, straight segments, the theme's corners.
|
|
228
|
+
const lines = some && elements.every((e) => e.kind === "line");
|
|
203
229
|
const defaults = {
|
|
230
|
+
...(lines
|
|
231
|
+
? {
|
|
232
|
+
"style.arrowhead": "none",
|
|
233
|
+
"style.arrowtail": "none",
|
|
234
|
+
"style.routing": "polyline",
|
|
235
|
+
"style.corners": theme.corners,
|
|
236
|
+
}
|
|
237
|
+
: {}),
|
|
204
238
|
"style.shape": shared(elements.map((e) => view.layout?.shapes?.get(e.id) ?? "rounded")),
|
|
239
|
+
// What applies without the targets' own statements (classes, rules), else a box's text
|
|
240
|
+
// centred in the middle of its shape and a free text's starting on the left: the alignment
|
|
241
|
+
// picker writes a cell's value only where it differs from this.
|
|
242
|
+
"style.textAlign": shared(elements.map((e) => inherited.element(e).textAlign ?? (e.kind === "text" ? "left" : "center"))),
|
|
243
|
+
"style.verticalAlign": shared(elements.map((e) => inherited.element(e).verticalAlign ?? "middle")),
|
|
205
244
|
"style.radius": shared(elements.map((e) => String(radius(e)))),
|
|
206
245
|
// A rule's or a class's angle, else upright.
|
|
207
246
|
"style.rotation": shared(elements.map((e) => String(styles.element(e).rotation ?? 0))),
|
|
@@ -255,20 +294,23 @@ function withIcons(model, view, icons) {
|
|
|
255
294
|
}
|
|
256
295
|
/**
|
|
257
296
|
* The relation's style fields with what applies without a written value: the theme's head at the
|
|
258
|
-
* end (calls and replies in sequence diagrams), the end's head at the start of a
|
|
259
|
-
* the diagram's route kind and the theme's corners.
|
|
297
|
+
* end (calls and replies in sequence diagrams; none for a line), the end's head at the start of a
|
|
298
|
+
* two-way arrow, the diagram's route kind and the theme's corners.
|
|
260
299
|
*/
|
|
261
300
|
function withRelationDefaults(fields, diagram, relation, theme) {
|
|
262
301
|
const written = writtenStyle(diagram, relation.id ?? relation.key, "relation");
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
302
|
+
// A line (`--`, `---`) has no head at either end unless one is written.
|
|
303
|
+
const endDefault = relation.heads === "none"
|
|
304
|
+
? "none"
|
|
305
|
+
: diagram.type === "sequence"
|
|
306
|
+
? relation.line === "dashed" || relation.async
|
|
307
|
+
? "open"
|
|
308
|
+
: "triangle"
|
|
309
|
+
: theme.arrowhead;
|
|
268
310
|
const end = typeof written.arrowhead === "string" ? written.arrowhead : endDefault;
|
|
269
311
|
const defaults = {
|
|
270
312
|
"style.arrowhead": endDefault,
|
|
271
|
-
"style.arrowtail": relation.
|
|
313
|
+
"style.arrowtail": relation.heads === "both" ? end : "none",
|
|
272
314
|
"style.routing": diagram.properties.routing,
|
|
273
315
|
"style.corners": theme.corners,
|
|
274
316
|
};
|
|
@@ -276,11 +318,34 @@ function withRelationDefaults(fields, diagram, relation, theme) {
|
|
|
276
318
|
}
|
|
277
319
|
/** Kinds an element of a profile can become; icons, images and groups keep theirs. */
|
|
278
320
|
function kindOptions(diagram, element) {
|
|
279
|
-
if (element.kind === "group" ||
|
|
321
|
+
if (element.kind === "group" ||
|
|
322
|
+
element.kind === "icon" ||
|
|
323
|
+
element.kind === "image" ||
|
|
324
|
+
element.kind === "line") {
|
|
280
325
|
return [element.kind];
|
|
281
326
|
}
|
|
327
|
+
if (element.kind === "note" && diagram.type !== "flow")
|
|
328
|
+
return [element.kind];
|
|
282
329
|
return diagram.type === "free" ? ["box", "text"] : ["node", "decision", "data", "note", "tag"];
|
|
283
330
|
}
|
|
331
|
+
/**
|
|
332
|
+
* The element a note is attached to, as a picker of the diagram's other elements (the drawn
|
|
333
|
+
* entity paths in a C4 view), empty for none.
|
|
334
|
+
*/
|
|
335
|
+
function attachmentField(diagram, note) {
|
|
336
|
+
const targets = diagram.elements.filter((e) => e.id !== note.id &&
|
|
337
|
+
e.kind !== "line" &&
|
|
338
|
+
// A view attaches to the entities it draws.
|
|
339
|
+
(diagram.type !== "c4" || e.entity !== null));
|
|
340
|
+
return {
|
|
341
|
+
name: "attachedTo",
|
|
342
|
+
label: messages().fields.attachedTo,
|
|
343
|
+
type: "enum",
|
|
344
|
+
value: note.attachedTo,
|
|
345
|
+
placeholder: messages().placeholders.none,
|
|
346
|
+
options: targets.map((e) => e.id),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
284
349
|
/**
|
|
285
350
|
* The `layer` and `link` fields of free objects and of every element, and the group an element of
|
|
286
351
|
* a flow or free diagram is declared in.
|
|
@@ -298,7 +363,7 @@ function identityFields(diagram, element) {
|
|
|
298
363
|
placeholder: "main",
|
|
299
364
|
options: diagram.properties.layers,
|
|
300
365
|
});
|
|
301
|
-
if (element) {
|
|
366
|
+
if (element && element.kind !== "line") {
|
|
302
367
|
// A group cannot go into itself or into a group it holds.
|
|
303
368
|
const groups = diagram.elements.filter((e) => e.kind === "group" && e.id !== element.id && !e.groupPath.includes(element.id));
|
|
304
369
|
// A flow node or a free box can become the container of what is drawn on it.
|
|
@@ -338,7 +403,8 @@ function identityFields(diagram, element) {
|
|
|
338
403
|
}
|
|
339
404
|
return fields;
|
|
340
405
|
}
|
|
341
|
-
|
|
406
|
+
/** The place and the size of an element, written as `place` and `size` statements. */
|
|
407
|
+
function geometryFields(view, element) {
|
|
342
408
|
const diagram = view.diagram;
|
|
343
409
|
const place = diagram.placement.find((p) => p.statement === "place" && p.target === element.id);
|
|
344
410
|
const size = diagram.placement.find((p) => p.statement === "size" && p.target === element.id);
|
|
@@ -351,6 +417,71 @@ function elementFields(view, element) {
|
|
|
351
417
|
y: round(box.y + box.height / 2 - (origin?.y ?? 0)),
|
|
352
418
|
}
|
|
353
419
|
: null;
|
|
420
|
+
const words = messages().fields;
|
|
421
|
+
return [
|
|
422
|
+
{
|
|
423
|
+
name: "x",
|
|
424
|
+
label: words.x,
|
|
425
|
+
type: "number",
|
|
426
|
+
value: place ? place.x : null,
|
|
427
|
+
...(center ? { placeholder: String(center.x) } : {}),
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: "y",
|
|
431
|
+
label: words.y,
|
|
432
|
+
type: "number",
|
|
433
|
+
value: place ? place.y : null,
|
|
434
|
+
...(center ? { placeholder: String(center.y) } : {}),
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: "w",
|
|
438
|
+
label: words.width,
|
|
439
|
+
type: "number",
|
|
440
|
+
value: size ? size.width : null,
|
|
441
|
+
...(box ? { placeholder: String(round(box.width)) } : {}),
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
name: "h",
|
|
445
|
+
label: words.height,
|
|
446
|
+
type: "number",
|
|
447
|
+
value: size ? size.height : null,
|
|
448
|
+
...(box ? { placeholder: String(round(box.height)) } : {}),
|
|
449
|
+
},
|
|
450
|
+
];
|
|
451
|
+
}
|
|
452
|
+
/** The fields of a note of a C4 view: its own, and none of an entity's. */
|
|
453
|
+
function viewNoteFields(view, element) {
|
|
454
|
+
const words = messages().fields;
|
|
455
|
+
return [
|
|
456
|
+
{ name: "id", label: words.id, type: "id", value: element.id },
|
|
457
|
+
{
|
|
458
|
+
name: "kind",
|
|
459
|
+
label: words.kind,
|
|
460
|
+
type: "enum",
|
|
461
|
+
value: element.kind,
|
|
462
|
+
options: [element.kind],
|
|
463
|
+
readonly: true,
|
|
464
|
+
},
|
|
465
|
+
{ name: "label", label: words.label, type: "text", value: element.label },
|
|
466
|
+
attachmentField(view.diagram, element),
|
|
467
|
+
{
|
|
468
|
+
name: "classes",
|
|
469
|
+
label: words.classes,
|
|
470
|
+
type: "classes",
|
|
471
|
+
value: element.classes.map((c) => c.name).join(", ") || null,
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "url",
|
|
475
|
+
label: words.url,
|
|
476
|
+
type: "text",
|
|
477
|
+
value: element.url,
|
|
478
|
+
placeholder: "https://…",
|
|
479
|
+
},
|
|
480
|
+
...geometryFields(view, element),
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
function elementFields(view, element) {
|
|
484
|
+
const diagram = view.diagram;
|
|
354
485
|
const ports = element.ports
|
|
355
486
|
.map((p) => `${p.name} ${p.side}${Math.round(p.position * 100) === 50 ? "" : ` ${Math.round(p.position * 100)}%`}`)
|
|
356
487
|
.join(", ");
|
|
@@ -370,6 +501,7 @@ function elementFields(view, element) {
|
|
|
370
501
|
: element.kind === "image"
|
|
371
502
|
? { name: "source", label: words.imageFile, type: "text", value: element.source }
|
|
372
503
|
: { name: "label", label: words.label, type: "text", value: element.label },
|
|
504
|
+
...(element.kind === "note" ? [attachmentField(diagram, element)] : []),
|
|
373
505
|
{
|
|
374
506
|
name: "description",
|
|
375
507
|
label: words.description,
|
|
@@ -384,6 +516,10 @@ function elementFields(view, element) {
|
|
|
384
516
|
},
|
|
385
517
|
];
|
|
386
518
|
fields.push(...identityFields(diagram, element));
|
|
519
|
+
// A free line is its points: no ports, no place, no size.
|
|
520
|
+
if (element.kind === "line") {
|
|
521
|
+
return [...fields, ...styleFields(diagram, [{ id: element.id, kind: "element" }])];
|
|
522
|
+
}
|
|
387
523
|
if (element.kind !== "group")
|
|
388
524
|
fields.push({
|
|
389
525
|
name: "ports",
|
|
@@ -392,31 +528,7 @@ function elementFields(view, element) {
|
|
|
392
528
|
value: ports || null,
|
|
393
529
|
placeholder: "in west 30%, out east",
|
|
394
530
|
});
|
|
395
|
-
fields.push(
|
|
396
|
-
name: "x",
|
|
397
|
-
label: words.x,
|
|
398
|
-
type: "number",
|
|
399
|
-
value: place ? place.x : null,
|
|
400
|
-
...(center ? { placeholder: String(center.x) } : {}),
|
|
401
|
-
}, {
|
|
402
|
-
name: "y",
|
|
403
|
-
label: words.y,
|
|
404
|
-
type: "number",
|
|
405
|
-
value: place ? place.y : null,
|
|
406
|
-
...(center ? { placeholder: String(center.y) } : {}),
|
|
407
|
-
}, {
|
|
408
|
-
name: "w",
|
|
409
|
-
label: words.width,
|
|
410
|
-
type: "number",
|
|
411
|
-
value: size ? size.width : null,
|
|
412
|
-
...(box ? { placeholder: String(round(box.width)) } : {}),
|
|
413
|
-
}, {
|
|
414
|
-
name: "h",
|
|
415
|
-
label: words.height,
|
|
416
|
-
type: "number",
|
|
417
|
-
value: size ? size.height : null,
|
|
418
|
-
...(box ? { placeholder: String(round(box.height)) } : {}),
|
|
419
|
-
});
|
|
531
|
+
fields.push(...geometryFields(view, element));
|
|
420
532
|
return [...fields, ...styleFields(diagram, [{ id: element.id, kind: "element" }])];
|
|
421
533
|
}
|
|
422
534
|
function relationFields(view, relation) {
|
|
@@ -683,12 +795,26 @@ export function themeSwatches(theme) {
|
|
|
683
795
|
}
|
|
684
796
|
/** The built-in theme of the same family in the other scheme: `technical.dark` for `technical.light`. */
|
|
685
797
|
export function counterpartTheme(name) {
|
|
686
|
-
const match = /^(technical|editorial|stage)\.(light|dark)$/.exec(name);
|
|
798
|
+
const match = /^(technical|editorial|stage|sketch)\.(light|dark)$/.exec(name);
|
|
687
799
|
if (!match)
|
|
688
800
|
return undefined;
|
|
689
801
|
return `${match[1]}.${match[2] === "light" ? "dark" : "light"}`;
|
|
690
802
|
}
|
|
691
|
-
/** The
|
|
803
|
+
/** The effects in the order of the strip's toggles. */
|
|
804
|
+
const EFFECT_ORDER = ["flow", "dash", "pulse", "trace"];
|
|
805
|
+
/** The fields of an effect that is on, in the order of its card. */
|
|
806
|
+
const EFFECT_PROPERTIES = [
|
|
807
|
+
"duration",
|
|
808
|
+
"repeat",
|
|
809
|
+
"delay",
|
|
810
|
+
"direction",
|
|
811
|
+
"particles",
|
|
812
|
+
"color",
|
|
813
|
+
];
|
|
814
|
+
/**
|
|
815
|
+
* The strip's fields, in the order it shows them; the first `line` is the relation's arrow. The
|
|
816
|
+
* style fields come first, then each effect's toggle with the fields of its card.
|
|
817
|
+
*/
|
|
692
818
|
const STRIP = [
|
|
693
819
|
"theme",
|
|
694
820
|
"style.fill",
|
|
@@ -704,22 +830,22 @@ const STRIP = [
|
|
|
704
830
|
"style.radius",
|
|
705
831
|
"style.rotation",
|
|
706
832
|
"style.shape",
|
|
833
|
+
// Free boxes and texts: one grid of cells for the two.
|
|
834
|
+
"style.textAlign",
|
|
835
|
+
"style.verticalAlign",
|
|
707
836
|
// For elements with an icon only.
|
|
708
837
|
"style.iconPosition",
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
"effect.pulse",
|
|
714
|
-
"effect.pulse.duration",
|
|
715
|
-
"effect.trace",
|
|
716
|
-
"effect.trace.duration",
|
|
838
|
+
...EFFECT_ORDER.flatMap((effect) => [
|
|
839
|
+
`effect.${effect}`,
|
|
840
|
+
...EFFECT_PROPERTIES.map((property) => `effect.${effect}.${property}`),
|
|
841
|
+
]),
|
|
717
842
|
];
|
|
718
843
|
/**
|
|
719
844
|
* Marks the fields of the style strip and puts them first: colours with the theme's swatches, the
|
|
720
845
|
* line, width and arrowhead, the shape and, for elements with an icon (`iconed`), the icon's
|
|
721
|
-
* position, the effects with
|
|
722
|
-
*
|
|
846
|
+
* position, the effects as toggles with every field of an effect that is on grouped under its
|
|
847
|
+
* toggle (`group`; the speed with the theme's default as placeholder, the colour with the
|
|
848
|
+
* swatches), and, for the diagram, the theme with its light or dark counterpart.
|
|
723
849
|
*/
|
|
724
850
|
function withStrip(model, theme, themes, iconed) {
|
|
725
851
|
const swatches = themeSwatches(theme);
|
|
@@ -733,14 +859,15 @@ function withStrip(model, theme, themes, iconed) {
|
|
|
733
859
|
const fields = model.fields.map((field) => {
|
|
734
860
|
if (!inStrip(field.name))
|
|
735
861
|
return field;
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
placeholder: String(durations[effect] ?? DEFAULT_DURATIONS[effect])
|
|
743
|
-
}
|
|
862
|
+
// A field of an effect that is on belongs to the card of its toggle.
|
|
863
|
+
const grouped = /^(effect\.(flow|dash|pulse|trace))\.(\w+)$/.exec(field.name);
|
|
864
|
+
if (grouped) {
|
|
865
|
+
const effect = grouped[2];
|
|
866
|
+
const placed = { ...field, section: "strip", group: grouped[1] };
|
|
867
|
+
if (grouped[3] === "duration") {
|
|
868
|
+
return { ...placed, placeholder: String(durations[effect] ?? DEFAULT_DURATIONS[effect]) };
|
|
869
|
+
}
|
|
870
|
+
return field.type === "color" ? { ...placed, swatches } : placed;
|
|
744
871
|
}
|
|
745
872
|
if (field.type === "color")
|
|
746
873
|
return { ...field, section: "strip", swatches };
|
|
@@ -823,7 +950,9 @@ function inspectFields(analysis, view, selection) {
|
|
|
823
950
|
title: titles.target(kindName(element.kind === "group" ? "boundary" : element.kind), id),
|
|
824
951
|
target: { kind: "element", ids: [id] },
|
|
825
952
|
fields: [
|
|
826
|
-
...
|
|
953
|
+
...(element.kind === "note" && element.entity === null
|
|
954
|
+
? viewNoteFields(view, element)
|
|
955
|
+
: c4ElementFields(diagram, element)),
|
|
827
956
|
...withElementDefaults(styleFields(diagram, [{ id, kind: "element" }]), view, effectiveTheme(analysis, view), [id]),
|
|
828
957
|
...effectFields(diagram, id, "element"),
|
|
829
958
|
],
|
|
@@ -833,12 +962,11 @@ function inspectFields(analysis, view, selection) {
|
|
|
833
962
|
return {
|
|
834
963
|
title: titles.relation(relation.id ?? `${relation.from} → ${relation.to}`),
|
|
835
964
|
target: { kind: "relation", ids: [id] },
|
|
965
|
+
// Anonymous relations take styles and effects too: setting one names them in the model.
|
|
836
966
|
fields: [
|
|
837
967
|
...c4RelationFields(diagram, relation),
|
|
838
|
-
...(relation.id
|
|
839
|
-
|
|
840
|
-
: []),
|
|
841
|
-
...(relation.id ? effectFields(diagram, relation.id, "relation") : []),
|
|
968
|
+
...withRelationDefaults(styleFields(diagram, [{ id: relation.id ?? relation.key, kind: "relation" }]), diagram, relation, effectiveTheme(analysis, view)),
|
|
969
|
+
...effectFields(diagram, relation.id ?? relation.key, "relation"),
|
|
842
970
|
],
|
|
843
971
|
};
|
|
844
972
|
}
|
|
@@ -862,16 +990,16 @@ function inspectFields(analysis, view, selection) {
|
|
|
862
990
|
}
|
|
863
991
|
if (kind === "event") {
|
|
864
992
|
const event = diagram.eventsByKey.get(id);
|
|
865
|
-
// Notes and fragments
|
|
866
|
-
const styled = event.
|
|
993
|
+
// Notes and fragments take styles and effects; setting one names an anonymous one.
|
|
994
|
+
const styled = event.kind === "note" || event.kind === "fragment";
|
|
867
995
|
return {
|
|
868
996
|
// Fragment operators are notation and stay as written.
|
|
869
997
|
title: titles.target(event.kind === "fragment" ? event.operator : kindName(event.kind), event.id ?? ""),
|
|
870
998
|
target: { kind: "event", ids: [id] },
|
|
871
999
|
fields: [
|
|
872
1000
|
...eventFields(diagram, event),
|
|
873
|
-
...(styled ? styleFields(diagram, [{ id: event.id, kind: "event" }]) : []),
|
|
874
|
-
...(styled ? effectFields(diagram, event.id, "event") : []),
|
|
1001
|
+
...(styled ? styleFields(diagram, [{ id: event.id ?? id, kind: "event" }]) : []),
|
|
1002
|
+
...(styled ? effectFields(diagram, event.id ?? id, "event") : []),
|
|
875
1003
|
],
|
|
876
1004
|
};
|
|
877
1005
|
}
|
|
@@ -887,10 +1015,8 @@ function inspectFields(analysis, view, selection) {
|
|
|
887
1015
|
...styleFields(diagram, [{ id: relation.id ?? relation.key, kind: "relation" }]),
|
|
888
1016
|
]
|
|
889
1017
|
: relationFields(view, relation), diagram, relation, effectiveTheme(analysis, view)),
|
|
890
|
-
// Anonymous relations take effects too: setting one names
|
|
891
|
-
...(relation.id
|
|
892
|
-
? effectFields(diagram, relation.id ?? relation.key, "relation")
|
|
893
|
-
: []),
|
|
1018
|
+
// Anonymous relations and messages take effects too: setting one names them.
|
|
1019
|
+
...effectFields(diagram, relation.id ?? relation.key, "relation"),
|
|
894
1020
|
],
|
|
895
1021
|
};
|
|
896
1022
|
}
|
|
@@ -901,8 +1027,8 @@ function inspectFields(analysis, view, selection) {
|
|
|
901
1027
|
const event = diagram.eventsByKey.get(t.id);
|
|
902
1028
|
if (event.kind === "message")
|
|
903
1029
|
return [{ id: diagram.relationsByKey.get(t.id)?.id ?? t.id, kind: "relation" }];
|
|
904
|
-
return event.
|
|
905
|
-
? [{ id: event.id, kind: "event" }]
|
|
1030
|
+
return event.kind === "note" || event.kind === "fragment"
|
|
1031
|
+
? [{ id: event.id ?? t.id, kind: "event" }]
|
|
906
1032
|
: [];
|
|
907
1033
|
});
|
|
908
1034
|
return {
|
|
@@ -957,12 +1083,6 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
957
1083
|
return effectOperations(diagram, ids, field, value);
|
|
958
1084
|
if (field.startsWith("style.")) {
|
|
959
1085
|
const property = field.slice("style.".length);
|
|
960
|
-
for (const id of ids) {
|
|
961
|
-
const event = diagram.relationsByKey.has(id) ? undefined : diagram.eventsByKey.get(id);
|
|
962
|
-
if (event && event.id === null) {
|
|
963
|
-
return { ok: false, reason: reasons.nameToStyle(event.kind) };
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
1086
|
let written = text === "" ? null : text;
|
|
967
1087
|
// Numbers within the property's bounds, such as a corner radius from 0 to 64.
|
|
968
1088
|
const spec = STYLE_PROPERTIES[property];
|
|
@@ -1007,7 +1127,11 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1007
1127
|
});
|
|
1008
1128
|
}
|
|
1009
1129
|
const notice = namedNotice(resolved.named);
|
|
1010
|
-
return {
|
|
1130
|
+
return {
|
|
1131
|
+
ok: true,
|
|
1132
|
+
operations: ops,
|
|
1133
|
+
...(notice ? { notice, select: selectionAfter(ids, resolved.targets) } : {}),
|
|
1134
|
+
};
|
|
1011
1135
|
}
|
|
1012
1136
|
const id = ids[0];
|
|
1013
1137
|
if (diagram.type === "sequence") {
|
|
@@ -1062,6 +1186,9 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1062
1186
|
ops.push(...steadyGroups(view, [id], parent));
|
|
1063
1187
|
break;
|
|
1064
1188
|
}
|
|
1189
|
+
case "attachedTo":
|
|
1190
|
+
ops.push({ kind: "attachNote", diagram: d, note: id, element: text || null });
|
|
1191
|
+
break;
|
|
1065
1192
|
case "kind":
|
|
1066
1193
|
if (text && text !== "group")
|
|
1067
1194
|
ops.push({
|
|
@@ -1171,6 +1298,11 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1171
1298
|
}
|
|
1172
1299
|
if (relation) {
|
|
1173
1300
|
const named = relation.id;
|
|
1301
|
+
// What an edit that named the relation first says, and the selection that follows the id.
|
|
1302
|
+
const naming = (target, operations) => {
|
|
1303
|
+
const notice = namedNotice(operations.length > 0 ? [target] : []);
|
|
1304
|
+
return notice ? { notice, select: [target] } : {};
|
|
1305
|
+
};
|
|
1174
1306
|
switch (field) {
|
|
1175
1307
|
case "id":
|
|
1176
1308
|
if (!text)
|
|
@@ -1187,7 +1319,7 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1187
1319
|
relation: target,
|
|
1188
1320
|
label: text || null,
|
|
1189
1321
|
});
|
|
1190
|
-
|
|
1322
|
+
return { ok: true, operations: ops, ...naming(target, operations) };
|
|
1191
1323
|
}
|
|
1192
1324
|
case "description":
|
|
1193
1325
|
ops.push({
|
|
@@ -1208,8 +1340,7 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1208
1340
|
property: "layer",
|
|
1209
1341
|
value: text || null,
|
|
1210
1342
|
});
|
|
1211
|
-
|
|
1212
|
-
return { ok: true, operations: ops, ...(notice ? { notice } : {}) };
|
|
1343
|
+
return { ok: true, operations: ops, ...naming(target, operations) };
|
|
1213
1344
|
}
|
|
1214
1345
|
case "classes":
|
|
1215
1346
|
ops.push({
|
|
@@ -1235,8 +1366,7 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1235
1366
|
property: "line",
|
|
1236
1367
|
value: "dotted",
|
|
1237
1368
|
});
|
|
1238
|
-
|
|
1239
|
-
return { ok: true, operations: ops, ...(notice ? { notice } : {}) };
|
|
1369
|
+
return { ok: true, operations: ops, ...naming(target, operations) };
|
|
1240
1370
|
}
|
|
1241
1371
|
ops.push({
|
|
1242
1372
|
kind: "setRelationLine",
|
|
@@ -1283,17 +1413,19 @@ export function fieldOperations(analysis, diagramId, ids, field, value) {
|
|
|
1283
1413
|
case "labelAt": {
|
|
1284
1414
|
if (number === null || !Number.isFinite(number))
|
|
1285
1415
|
return { ok: false, reason: reasons.percentage };
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const entry =
|
|
1289
|
-
|
|
1416
|
+
// An anonymous relation is named first: its label is placed by the relation's id.
|
|
1417
|
+
const { id: target, operations } = namedRelation(diagram, relation);
|
|
1418
|
+
const entry = named
|
|
1419
|
+
? diagram.placement.find((p) => p.statement === "label" && p.target === named)
|
|
1420
|
+
: undefined;
|
|
1421
|
+
ops.push(...operations, {
|
|
1290
1422
|
kind: "setLabelAnchor",
|
|
1291
1423
|
diagram: d,
|
|
1292
|
-
relation:
|
|
1424
|
+
relation: target,
|
|
1293
1425
|
at: number,
|
|
1294
1426
|
offset: entry?.offset ?? { x: 0, y: 0 },
|
|
1295
1427
|
});
|
|
1296
|
-
|
|
1428
|
+
return { ok: true, operations: ops, ...naming(target, operations) };
|
|
1297
1429
|
}
|
|
1298
1430
|
default:
|
|
1299
1431
|
return { ok: false, reason: `Unknown field \`${field}\`.` };
|