yarramate 1.23.2 → 1.25.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/adapters/visual/protocol-contract.d.ts +19 -0
- package/dist/adapters/visual/session-server.js +16 -4
- package/dist/adapters/visual/wire.d.ts +9 -1
- package/dist/brief.js +19 -40
- package/dist/layout-mode.d.ts +56 -0
- package/dist/layout-mode.js +67 -0
- package/dist/notation/archimate.d.ts +6 -1
- package/dist/notation/archimate.js +9 -4
- package/dist/projection.d.ts +20 -1
- package/dist/projection.js +9 -0
- package/dist/relationship-reading.d.ts +28 -0
- package/dist/relationship-reading.js +52 -0
- package/dist/schema-validators.generated.js +36 -23
- package/dist/visual-app/assets/elk-worker.min-D8OVqK8T.js +22 -0
- package/dist/visual-app/assets/elk.bundled-CLux5E_P.js +24 -0
- package/dist/visual-app/assets/index-CJVWkpLt.css +1 -0
- package/dist/visual-app/assets/index-DGkU2CSB.js +369 -0
- package/dist/visual-app/index.html +2 -2
- package/dist/visual-app-lib/editor.js +39420 -38659
- package/dist/visual-app-lib/styles.css +1 -1
- package/dist/visual-app-lib/types/adapters/visual/protocol-contract.d.ts +19 -0
- package/dist/visual-app-lib/types/adapters/visual/wire.d.ts +9 -1
- package/dist/visual-app-lib/types/layout-mode.d.ts +56 -0
- package/dist/visual-app-lib/types/notation/archimate.d.ts +6 -1
- package/dist/visual-app-lib/types/projection.d.ts +20 -1
- package/dist/visual-app-lib/types/relationship-reading.d.ts +28 -0
- package/dist/visual-app-lib/types/visual-app/edge-routes.d.ts +63 -0
- package/dist/visual-app-lib/types/visual-app/elk-layout.d.ts +150 -0
- package/dist/visual-app-lib/types/visual-app/graph-canvas.d.ts +44 -6
- package/dist/visual-app-lib/types/visual-app/kind-icons.d.ts +1 -1
- package/dist/visual-app-lib/types/visual-app/layout-controls.d.ts +29 -0
- package/dist/visual-app-lib/types/visual-app/mount.d.ts +12 -0
- package/dist/visual-app-lib/types/visual-app/query-fields.d.ts +10 -8
- package/dist/visual-app-lib/types/visual-app/query-panel.d.ts +5 -3
- package/dist/visual-app-lib/types/visual-app/save-view.d.ts +30 -17
- package/dist/visual-app-lib/types/visual-app/style-presets.d.ts +39 -0
- package/dist/visual-app-lib/types/visual-app/workspace-state.d.ts +22 -4
- package/docs/CONSUMING-YARRAMATE.md +9 -0
- package/package.json +1 -2
- package/schema/yarramate-projection-result.schema.json +4 -1
- package/schema/yarramate-projection.schema.json +6 -1
- package/schema/yarramate-visual-event.schema.json +10 -0
- package/schema/yarramate-visual-layout.schema.json +31 -0
- package/dist/visual-app/assets/index-CnldeUHL.css +0 -1
- package/dist/visual-app/assets/index-CyKofiEE.js +0 -394
|
@@ -267,6 +267,23 @@ export interface VisualLayoutPositions {
|
|
|
267
267
|
readonly y: number;
|
|
268
268
|
};
|
|
269
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* The routes the canvas was drawing when a layout was saved (ADR 0147), keyed
|
|
272
|
+
* by relationship id: absolute canvas coordinates from the source end, both
|
|
273
|
+
* endpoints included, and where along the route the label sits. Saved WITH
|
|
274
|
+
* the positions, because a route is only right for the positions it was
|
|
275
|
+
* computed for: a reader who moved one subject keeps every other edge's
|
|
276
|
+
* route, and only the moved subject's edges fall back to a straight line.
|
|
277
|
+
*/
|
|
278
|
+
export interface VisualLayoutRoutes {
|
|
279
|
+
readonly [relationshipId: string]: {
|
|
280
|
+
readonly points: readonly {
|
|
281
|
+
readonly x: number;
|
|
282
|
+
readonly y: number;
|
|
283
|
+
}[];
|
|
284
|
+
readonly labelAt: number | null;
|
|
285
|
+
};
|
|
286
|
+
}
|
|
270
287
|
/**
|
|
271
288
|
* A change to a projection document, staged rather than written (ADR 0103).
|
|
272
289
|
*
|
|
@@ -332,6 +349,8 @@ export interface VisualLayoutSavePayload {
|
|
|
332
349
|
*/
|
|
333
350
|
readonly folded?: readonly string[];
|
|
334
351
|
readonly unfolded?: readonly string[];
|
|
352
|
+
/** The routes in force at save time (ADR 0147); absent when nothing was routed. */
|
|
353
|
+
readonly routes?: VisualLayoutRoutes;
|
|
335
354
|
}
|
|
336
355
|
/**
|
|
337
356
|
* Terminal event payload. Every reason is the runtime's to choose: only it
|
|
@@ -456,15 +456,16 @@ export const startVisualServer = async (options) => {
|
|
|
456
456
|
// like a broken saved view above — presentation state must never fail a
|
|
457
457
|
// session.
|
|
458
458
|
const layoutDir = resolve(options.cwd, ".yarramate/visual-layout");
|
|
459
|
-
const { layouts, folds } = (() => {
|
|
459
|
+
const { layouts, folds, routes } = (() => {
|
|
460
460
|
const layouts = {};
|
|
461
461
|
const folds = {};
|
|
462
|
+
const routes = {};
|
|
462
463
|
let entries;
|
|
463
464
|
try {
|
|
464
465
|
entries = readdirSync(layoutDir);
|
|
465
466
|
}
|
|
466
467
|
catch {
|
|
467
|
-
return { layouts, folds };
|
|
468
|
+
return { layouts, folds, routes };
|
|
468
469
|
}
|
|
469
470
|
for (const entry of entries) {
|
|
470
471
|
if (extname(entry) !== ".yaml" && extname(entry) !== ".yml")
|
|
@@ -476,6 +477,10 @@ export const startVisualServer = async (options) => {
|
|
|
476
477
|
continue;
|
|
477
478
|
const sidecar = parsed;
|
|
478
479
|
layouts[sidecar.projectionId] = sidecar.positions;
|
|
480
|
+
// The routes the canvas was drawing when it saved (ADR 0147). A
|
|
481
|
+
// sidecar written before them says nothing, and the layout recomputes.
|
|
482
|
+
if (sidecar.routes !== undefined)
|
|
483
|
+
routes[sidecar.projectionId] = sidecar.routes;
|
|
479
484
|
// A sidecar written before #473 has neither list, and says nothing
|
|
480
485
|
// about folding rather than saying "fold nothing" - the view's own
|
|
481
486
|
// default decides for it. Only a sidecar that STATES a fold overrides.
|
|
@@ -490,7 +495,7 @@ export const startVisualServer = async (options) => {
|
|
|
490
495
|
// Skipped sidecar: presentation state must never fail a session.
|
|
491
496
|
}
|
|
492
497
|
}
|
|
493
|
-
return { layouts, folds };
|
|
498
|
+
return { layouts, folds, routes };
|
|
494
499
|
})();
|
|
495
500
|
// `request.initialModel.graph` is the caller's compile (`buildVisualModelGraph`,
|
|
496
501
|
// before invoking `yarramate-visual start`) and is only the fallback below:
|
|
@@ -505,6 +510,7 @@ export const startVisualServer = async (options) => {
|
|
|
505
510
|
vocabulary: { conceptKinds: [], relationshipKinds: [] },
|
|
506
511
|
layouts,
|
|
507
512
|
...(Object.keys(folds).length === 0 ? {} : { folds }),
|
|
513
|
+
...(Object.keys(routes).length === 0 ? {} : { routes }),
|
|
508
514
|
sourceDigests: request.initialModel.sourceDigests,
|
|
509
515
|
// The request's model has no projections in it - `visual-model/v1` carries
|
|
510
516
|
// a graph, not a workspace - so the fallback states nothing rather than
|
|
@@ -1424,7 +1430,7 @@ export const startVisualServer = async (options) => {
|
|
|
1424
1430
|
// never `git commit`ed. It never asks the agent anything, so it is
|
|
1425
1431
|
// answered here directly rather than through the pending queue a
|
|
1426
1432
|
// poll would drain.
|
|
1427
|
-
const { projectionId, positions, folded, unfolded } = event.payload;
|
|
1433
|
+
const { projectionId, positions, folded, unfolded, routes: savedRoutes } = event.payload;
|
|
1428
1434
|
if (!views.some((view) => view.id === projectionId)) {
|
|
1429
1435
|
sendFrame(socket, {
|
|
1430
1436
|
kind: "layout-save-result",
|
|
@@ -1446,10 +1452,16 @@ export const startVisualServer = async (options) => {
|
|
|
1446
1452
|
// sidecar written by one host is read by the other.
|
|
1447
1453
|
...(folded === undefined ? {} : { folded }),
|
|
1448
1454
|
...(unfolded === undefined ? {} : { unfolded }),
|
|
1455
|
+
// The routes in force, beside the positions they were computed
|
|
1456
|
+
// for (ADR 0147); a save with nothing routed writes none.
|
|
1457
|
+
...(savedRoutes === undefined ? {} : { routes: savedRoutes }),
|
|
1449
1458
|
}), "utf8");
|
|
1450
1459
|
rendered = {
|
|
1451
1460
|
...rendered,
|
|
1452
1461
|
layouts: { ...rendered.layouts, [projectionId]: positions },
|
|
1462
|
+
...(savedRoutes === undefined
|
|
1463
|
+
? {}
|
|
1464
|
+
: { routes: { ...rendered.routes, [projectionId]: savedRoutes } }),
|
|
1453
1465
|
...(folded === undefined && unfolded === undefined
|
|
1454
1466
|
? {}
|
|
1455
1467
|
: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CanvasGraph } from '../../graph-projection.js';
|
|
2
2
|
import type { PatternMembership, PatternVacancy } from '../../compiler.js';
|
|
3
|
-
import type { VISUAL_PROTOCOL_VERSION, VisualApplyResultPayload, VisualAuthority, VisualBrowserInput, VisualCapabilities, VisualChoicePresentPayload, VisualDiagnostic, VisualFilterResultPayload, VisualFreezeReason, VisualKindOption, VisualPatternOption, VisualLayoutPositions, VisualLayoutSaveResultPayload, VisualResponse, VisualTerminationReason, VisualViewSummary } from './protocol-contract.js';
|
|
3
|
+
import type { VISUAL_PROTOCOL_VERSION, VisualApplyResultPayload, VisualAuthority, VisualBrowserInput, VisualCapabilities, VisualChoicePresentPayload, VisualDiagnostic, VisualFilterResultPayload, VisualFreezeReason, VisualKindOption, VisualPatternOption, VisualLayoutPositions, VisualLayoutRoutes, VisualLayoutSaveResultPayload, VisualResponse, VisualTerminationReason, VisualViewSummary } from './protocol-contract.js';
|
|
4
4
|
/**
|
|
5
5
|
* Transport shapes the session server and the browser application both speak.
|
|
6
6
|
*
|
|
@@ -77,6 +77,14 @@ export interface VisualRenderedModel {
|
|
|
77
77
|
readonly unfolded: readonly string[];
|
|
78
78
|
};
|
|
79
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* The routes each saved layout was drawing (ADR 0147), keyed by projection
|
|
82
|
+
* id. A sibling of `layouts` for the reason `folds` is: a layout entry is
|
|
83
|
+
* positions, and widening it would reach every reader of it.
|
|
84
|
+
*/
|
|
85
|
+
readonly routes?: {
|
|
86
|
+
readonly [projectionId: string]: VisualLayoutRoutes;
|
|
87
|
+
};
|
|
80
88
|
/**
|
|
81
89
|
* Which subject fills which slot of which instance (ADR 0131), and which
|
|
82
90
|
* slots nothing fills (#447), forwarded so the browser can draw containment
|
package/dist/brief.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { conceptKinds } from './profile.js';
|
|
2
|
+
import { RELATIONSHIP_READING, humanizeKind } from './relationship-reading.js';
|
|
2
3
|
const coreKindNames = new Map(conceptKinds.map(({ id, name }) => [id, name]));
|
|
3
4
|
const motivationKindIds = new Set(conceptKinds
|
|
4
5
|
.filter(({ layer }) => layer === 'motivation')
|
|
@@ -41,54 +42,32 @@ export const isDeclaredNonGoal = (kind, status, lineages) => {
|
|
|
41
42
|
const core = coreLocalKind(kind, lineages);
|
|
42
43
|
return core !== undefined && nonGoalKindIds.has(core);
|
|
43
44
|
};
|
|
44
|
-
const humanizeKind = (kind) => {
|
|
45
|
-
const local = kind.slice(kind.indexOf('#') + 1);
|
|
46
|
-
return local
|
|
47
|
-
.replaceAll(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
48
|
-
.replaceAll('-', ' ')
|
|
49
|
-
.toLowerCase();
|
|
50
|
-
};
|
|
51
45
|
const article = (reading) => /^[aeiou]/.test(reading) ? 'an' : 'a';
|
|
52
46
|
const sentenceEnd = (text) => /[.!?]["')\]]*$/.test(text.trimEnd()) ? text.trimEnd() : `${text.trimEnd()}.`;
|
|
53
47
|
const listPhrase = (items) => items.length <= 1
|
|
54
48
|
? (items[0] ?? '')
|
|
55
49
|
: `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
|
|
56
50
|
// Phrase forms are the prose readings of each core relationship kind's
|
|
57
|
-
// declared intent
|
|
58
|
-
//
|
|
51
|
+
// declared intent - the same table `next` reads for ordering, spoken from
|
|
52
|
+
// the source's perspective. The plain readings live in
|
|
53
|
+
// `./relationship-reading.ts`, shared with the canvas's edge labels (ADR
|
|
54
|
+
// 0147); only the two kinds whose reading depends on the relationship's own
|
|
55
|
+
// fields are phrased here.
|
|
59
56
|
const relationshipPhrase = (coreKind, fallbackKind, mode, content) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
case 'realization':
|
|
72
|
-
return 'realizes';
|
|
73
|
-
case 'composition':
|
|
74
|
-
return 'comprises';
|
|
75
|
-
case 'aggregation':
|
|
76
|
-
return 'aggregates';
|
|
77
|
-
case 'assignment':
|
|
78
|
-
return 'is assigned to';
|
|
79
|
-
case 'triggering':
|
|
80
|
-
return 'triggers';
|
|
81
|
-
case 'flow':
|
|
82
|
-
return content === undefined ? 'flows to' : `sends ${content} to`;
|
|
83
|
-
case 'specialization':
|
|
84
|
-
return 'specializes';
|
|
85
|
-
case 'influence':
|
|
86
|
-
return 'influences';
|
|
87
|
-
case 'association':
|
|
88
|
-
return 'is associated with';
|
|
89
|
-
default:
|
|
90
|
-
return humanizeKind(fallbackKind);
|
|
57
|
+
if (coreKind === 'access') {
|
|
58
|
+
return mode === 'read'
|
|
59
|
+
? 'reads'
|
|
60
|
+
: mode === 'write'
|
|
61
|
+
? 'writes'
|
|
62
|
+
: mode === 'read-write'
|
|
63
|
+
? 'reads and writes'
|
|
64
|
+
: 'accesses';
|
|
65
|
+
}
|
|
66
|
+
if (coreKind === 'flow') {
|
|
67
|
+
return content === undefined ? 'flows to' : `sends ${content} to`;
|
|
91
68
|
}
|
|
69
|
+
const reading = coreKind === undefined ? undefined : RELATIONSHIP_READING[coreKind];
|
|
70
|
+
return reading ?? humanizeKind(fallbackKind);
|
|
92
71
|
};
|
|
93
72
|
const estimateTokens = (text) => Math.ceil(text.length / 4);
|
|
94
73
|
export function renderBrief(result, profileContext, budgetTokens,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a view arranges itself, kept in a module that imports nothing.
|
|
3
|
+
*
|
|
4
|
+
* Apart from `projection.ts` for the reason `./layout-direction.ts` gives: the
|
|
5
|
+
* browser needs the value and not only the type, and `projection.ts` drags Ajv
|
|
6
|
+
* and the projection schema in for one constant. `projection.ts` re-exports
|
|
7
|
+
* everything here.
|
|
8
|
+
*
|
|
9
|
+
* The four modes are a ladder; each keeps everything below it (ADR 0147).
|
|
10
|
+
*
|
|
11
|
+
* - `layered`: ELK places the nodes and cytoscape draws its own orthogonal
|
|
12
|
+
* lines between them, through whatever happens to sit in the way. This is
|
|
13
|
+
* what shipped before 1.24; measured on the ApertureX reference model, 127
|
|
14
|
+
* of the Landscape's 206 drawn edges cut through a box that was not one of
|
|
15
|
+
* their endpoints.
|
|
16
|
+
* - `routed`: ELK also routes every edge around the nodes and reserves room
|
|
17
|
+
* for each label. Zero edges through boxes on every view measured.
|
|
18
|
+
* - `served-by`: routed, and serving, realization and specialization are
|
|
19
|
+
* layered UPWARD, so the served, realized or general element sits above
|
|
20
|
+
* what serves, realizes or specializes it, and the label reads down the
|
|
21
|
+
* page as "served by". Only the layering turns; the arrowhead, which says
|
|
22
|
+
* which end is which, keeps its ArchiMate form.
|
|
23
|
+
* - `bands`: served-by, and every element is pinned to its ArchiMate layer's
|
|
24
|
+
* band, motivation at the top and physical at the bottom.
|
|
25
|
+
*/
|
|
26
|
+
export declare const LAYOUT_MODES: readonly ['layered', 'routed', 'served-by', 'bands'];
|
|
27
|
+
export type LayoutMode = (typeof LAYOUT_MODES)[number];
|
|
28
|
+
/**
|
|
29
|
+
* How a view lays out when it does not say. Served-by, because it is the
|
|
30
|
+
* mode that read correctly the first time anyone looked at real tiers: the
|
|
31
|
+
* plain top-down run put the system API above the experience API (ADR 0147).
|
|
32
|
+
* A view that wants the pre-1.24 picture declares `layout: layered`.
|
|
33
|
+
*/
|
|
34
|
+
export declare const DEFAULT_LAYOUT: LayoutMode;
|
|
35
|
+
export declare const isLayoutMode: (value: unknown) => value is LayoutMode;
|
|
36
|
+
/** Whether ELK's own routes are drawn, rather than cytoscape's straight lines. */
|
|
37
|
+
export declare const routesEdges: (mode: LayoutMode) => boolean;
|
|
38
|
+
/** Whether the upward kinds are layered target-above-source. */
|
|
39
|
+
export declare const reversesForLayering: (mode: LayoutMode) => boolean;
|
|
40
|
+
/** Whether every node is pinned to its ArchiMate layer's band. */
|
|
41
|
+
export declare const partitionsByLayer: (mode: LayoutMode) => boolean;
|
|
42
|
+
/**
|
|
43
|
+
* The kinds ArchiMate draws with the TARGET above the source: the served
|
|
44
|
+
* element above its server, the realized above its realizer, the general
|
|
45
|
+
* above its specialization. Reversed for layering only; the notation module
|
|
46
|
+
* still draws the arrowhead at the target.
|
|
47
|
+
*/
|
|
48
|
+
export declare const LAYERING_REVERSED_KINDS: ReadonlySet<string>;
|
|
49
|
+
/**
|
|
50
|
+
* Each ArchiMate layer's band under `bands`, top to bottom. `composite` has no
|
|
51
|
+
* band of its own - a grouping holds members from any layer - and a subject
|
|
52
|
+
* with no layer floats free, so neither is listed: ELK partitions only what
|
|
53
|
+
* names a partition, and leaves the rest to the layering.
|
|
54
|
+
*/
|
|
55
|
+
export declare const LAYER_BAND: Readonly<Record<string, number>>;
|
|
56
|
+
export declare const layerBandOf: (layer: string | null | undefined) => number | undefined;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a view arranges itself, kept in a module that imports nothing.
|
|
3
|
+
*
|
|
4
|
+
* Apart from `projection.ts` for the reason `./layout-direction.ts` gives: the
|
|
5
|
+
* browser needs the value and not only the type, and `projection.ts` drags Ajv
|
|
6
|
+
* and the projection schema in for one constant. `projection.ts` re-exports
|
|
7
|
+
* everything here.
|
|
8
|
+
*
|
|
9
|
+
* The four modes are a ladder; each keeps everything below it (ADR 0147).
|
|
10
|
+
*
|
|
11
|
+
* - `layered`: ELK places the nodes and cytoscape draws its own orthogonal
|
|
12
|
+
* lines between them, through whatever happens to sit in the way. This is
|
|
13
|
+
* what shipped before 1.24; measured on the ApertureX reference model, 127
|
|
14
|
+
* of the Landscape's 206 drawn edges cut through a box that was not one of
|
|
15
|
+
* their endpoints.
|
|
16
|
+
* - `routed`: ELK also routes every edge around the nodes and reserves room
|
|
17
|
+
* for each label. Zero edges through boxes on every view measured.
|
|
18
|
+
* - `served-by`: routed, and serving, realization and specialization are
|
|
19
|
+
* layered UPWARD, so the served, realized or general element sits above
|
|
20
|
+
* what serves, realizes or specializes it, and the label reads down the
|
|
21
|
+
* page as "served by". Only the layering turns; the arrowhead, which says
|
|
22
|
+
* which end is which, keeps its ArchiMate form.
|
|
23
|
+
* - `bands`: served-by, and every element is pinned to its ArchiMate layer's
|
|
24
|
+
* band, motivation at the top and physical at the bottom.
|
|
25
|
+
*/
|
|
26
|
+
export const LAYOUT_MODES = ['layered', 'routed', 'served-by', 'bands'];
|
|
27
|
+
/**
|
|
28
|
+
* How a view lays out when it does not say. Served-by, because it is the
|
|
29
|
+
* mode that read correctly the first time anyone looked at real tiers: the
|
|
30
|
+
* plain top-down run put the system API above the experience API (ADR 0147).
|
|
31
|
+
* A view that wants the pre-1.24 picture declares `layout: layered`.
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_LAYOUT = 'served-by';
|
|
34
|
+
export const isLayoutMode = (value) => typeof value === 'string' && LAYOUT_MODES.includes(value);
|
|
35
|
+
/** Whether ELK's own routes are drawn, rather than cytoscape's straight lines. */
|
|
36
|
+
export const routesEdges = (mode) => mode !== 'layered';
|
|
37
|
+
/** Whether the upward kinds are layered target-above-source. */
|
|
38
|
+
export const reversesForLayering = (mode) => mode === 'served-by' || mode === 'bands';
|
|
39
|
+
/** Whether every node is pinned to its ArchiMate layer's band. */
|
|
40
|
+
export const partitionsByLayer = (mode) => mode === 'bands';
|
|
41
|
+
/**
|
|
42
|
+
* The kinds ArchiMate draws with the TARGET above the source: the served
|
|
43
|
+
* element above its server, the realized above its realizer, the general
|
|
44
|
+
* above its specialization. Reversed for layering only; the notation module
|
|
45
|
+
* still draws the arrowhead at the target.
|
|
46
|
+
*/
|
|
47
|
+
export const LAYERING_REVERSED_KINDS = new Set([
|
|
48
|
+
'serving',
|
|
49
|
+
'realization',
|
|
50
|
+
'specialization',
|
|
51
|
+
]);
|
|
52
|
+
/**
|
|
53
|
+
* Each ArchiMate layer's band under `bands`, top to bottom. `composite` has no
|
|
54
|
+
* band of its own - a grouping holds members from any layer - and a subject
|
|
55
|
+
* with no layer floats free, so neither is listed: ELK partitions only what
|
|
56
|
+
* names a partition, and leaves the rest to the layering.
|
|
57
|
+
*/
|
|
58
|
+
export const LAYER_BAND = {
|
|
59
|
+
motivation: 0,
|
|
60
|
+
strategy: 1,
|
|
61
|
+
business: 2,
|
|
62
|
+
application: 3,
|
|
63
|
+
technology: 4,
|
|
64
|
+
physical: 5,
|
|
65
|
+
implementation: 6,
|
|
66
|
+
};
|
|
67
|
+
export const layerBandOf = (layer) => layer === null || layer === undefined ? undefined : LAYER_BAND[layer];
|
|
@@ -55,7 +55,12 @@ export interface ConceptNotation extends ShapeMeta {
|
|
|
55
55
|
}
|
|
56
56
|
export declare const CONCEPT_NOTATION: readonly ConceptNotation[];
|
|
57
57
|
export declare function conceptNotationOf(kindLabel: string): ConceptNotation | null;
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The glyph as a data URI, drawn in `ink` - the notation's own by default, or
|
|
60
|
+
* the light stroke a dark ground needs (ADR 0148). The stroke is the only
|
|
61
|
+
* thing that varies; the strokes themselves are the notation's.
|
|
62
|
+
*/
|
|
63
|
+
export declare function kindGlyphDataUriOf(kindLabel: string, ink?: string): string | null;
|
|
59
64
|
export interface ArrowNotation {
|
|
60
65
|
readonly shape: 'none' | 'diamond' | 'triangle' | 'circle' | 'vee';
|
|
61
66
|
readonly fill?: 'filled' | 'hollow';
|
|
@@ -38,10 +38,10 @@ export const ASPECT_SHAPES = {
|
|
|
38
38
|
const KIND_SHAPE_OVERRIDES = {
|
|
39
39
|
grouping: { borderStyle: 'dashed' },
|
|
40
40
|
};
|
|
41
|
-
function svg(body) {
|
|
41
|
+
function svg(body, ink = INK) {
|
|
42
42
|
return (`<svg xmlns="http://www.w3.org/2000/svg" width="${ICON_SIZE}" height="${ICON_SIZE}" ` +
|
|
43
43
|
`viewBox="0 0 ${ICON_SIZE} ${ICON_SIZE}">` +
|
|
44
|
-
`<g fill="none" stroke="${
|
|
44
|
+
`<g fill="none" stroke="${ink}" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">${body}</g>` +
|
|
45
45
|
`</svg>`);
|
|
46
46
|
}
|
|
47
47
|
function toDataUri(svgMarkup) {
|
|
@@ -205,9 +205,14 @@ const CONCEPT_NOTATION_BY_ID = Object.fromEntries(CONCEPT_NOTATION.map((row) =>
|
|
|
205
205
|
export function conceptNotationOf(kindLabel) {
|
|
206
206
|
return CONCEPT_NOTATION_BY_ID[kindLabel] ?? null;
|
|
207
207
|
}
|
|
208
|
-
|
|
208
|
+
/**
|
|
209
|
+
* The glyph as a data URI, drawn in `ink` - the notation's own by default, or
|
|
210
|
+
* the light stroke a dark ground needs (ADR 0148). The stroke is the only
|
|
211
|
+
* thing that varies; the strokes themselves are the notation's.
|
|
212
|
+
*/
|
|
213
|
+
export function kindGlyphDataUriOf(kindLabel, ink = INK) {
|
|
209
214
|
const glyph = CONCEPT_NOTATION_BY_ID[kindLabel]?.glyph;
|
|
210
|
-
return glyph == null ? null : toDataUri(svg(glyph));
|
|
215
|
+
return glyph == null ? null : toDataUri(svg(glyph, ink));
|
|
211
216
|
}
|
|
212
217
|
// The 11 rows from `graph-canvas.tsx`'s ArchiMate edge selectors (Task 11).
|
|
213
218
|
const RELATIONSHIP_STYLE = {
|
package/dist/projection.d.ts
CHANGED
|
@@ -51,7 +51,13 @@ export interface ProjectionDefinition {
|
|
|
51
51
|
readonly presentation?: {
|
|
52
52
|
readonly title?: string;
|
|
53
53
|
readonly description?: string;
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* How this view arranges itself: `layered`, `routed`, `served-by` or
|
|
56
|
+
* `bands`, a ladder where each keeps everything below it (ADR 0147). A
|
|
57
|
+
* view that says nothing lays out `served-by`. The canvas offers the
|
|
58
|
+
* choice on screen and a save writes what is in force.
|
|
59
|
+
*/
|
|
60
|
+
readonly layout?: LayoutMode;
|
|
55
61
|
/**
|
|
56
62
|
* Which way this view runs its layers. Read by the LikeC4 export for its
|
|
57
63
|
* `autoLayout` and by the canvas for ELK's `elk.direction` (ADR 0121); a
|
|
@@ -78,6 +84,13 @@ export interface ProjectionDefinition {
|
|
|
78
84
|
readonly showLifecycle?: boolean;
|
|
79
85
|
readonly showEvidence?: boolean;
|
|
80
86
|
readonly showOwnership?: boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Whether an unnamed relationship is labelled with its reading - "serves",
|
|
89
|
+
* "served by", "realizes" - or left to its line style and arrowhead
|
|
90
|
+
* (ADR 0147). A named relationship keeps its name either way. On when
|
|
91
|
+
* absent, which is what shipped.
|
|
92
|
+
*/
|
|
93
|
+
readonly showKindLabels?: boolean;
|
|
81
94
|
/**
|
|
82
95
|
* The folder this view files itself under in an editor's rail: a label the
|
|
83
96
|
* author declares, nested with `/`, never the directory the projection
|
|
@@ -116,6 +129,12 @@ import { type FoldMembership } from './fold-tree.js';
|
|
|
116
129
|
*/
|
|
117
130
|
export { DEFAULT_DIRECTION, type LayoutDirection } from './layout-direction.js';
|
|
118
131
|
import type { LayoutDirection } from './layout-direction.js';
|
|
132
|
+
/**
|
|
133
|
+
* How a view arranges itself, and the default. Same split, same terms
|
|
134
|
+
* (ADR 0147).
|
|
135
|
+
*/
|
|
136
|
+
export { DEFAULT_LAYOUT, LAYOUT_MODES, type LayoutMode } from './layout-mode.js';
|
|
137
|
+
import type { LayoutMode } from './layout-mode.js';
|
|
119
138
|
export type ProjectionQuery = ProjectionDefinition['query'];
|
|
120
139
|
export interface ProjectionResult {
|
|
121
140
|
readonly format: 'yarramate/projection-result/v1';
|
package/dist/projection.js
CHANGED
|
@@ -23,6 +23,11 @@ import { kindLabelOf } from './kind-label.js';
|
|
|
23
23
|
* nesting vocabulary above, and re-exported here on the same terms (ADR 0121).
|
|
24
24
|
*/
|
|
25
25
|
export { DEFAULT_DIRECTION } from './layout-direction.js';
|
|
26
|
+
/**
|
|
27
|
+
* How a view arranges itself, and the default. Same split, same terms
|
|
28
|
+
* (ADR 0147).
|
|
29
|
+
*/
|
|
30
|
+
export { DEFAULT_LAYOUT, LAYOUT_MODES } from './layout-mode.js';
|
|
26
31
|
import { validateProjection } from './schema-validation.js';
|
|
27
32
|
export function loadProjection(source) {
|
|
28
33
|
const loaded = loadSourceDocument(source, validateProjection, 'Projection');
|
|
@@ -66,6 +71,7 @@ export function canonicalProjection(projection) {
|
|
|
66
71
|
...(presentation.showLifecycle === undefined ? {} : { showLifecycle: presentation.showLifecycle }),
|
|
67
72
|
...(presentation.showEvidence === undefined ? {} : { showEvidence: presentation.showEvidence }),
|
|
68
73
|
...(presentation.showOwnership === undefined ? {} : { showOwnership: presentation.showOwnership }),
|
|
74
|
+
...(presentation.showKindLabels === undefined ? {} : { showKindLabels: presentation.showKindLabels }),
|
|
69
75
|
...(presentation.notation === undefined ? {} : { notation: presentation.notation }),
|
|
70
76
|
},
|
|
71
77
|
}),
|
|
@@ -631,6 +637,9 @@ export function evaluateProjection(graph, projection, profileContext, membership
|
|
|
631
637
|
...(projection.presentation.showOwnership === undefined
|
|
632
638
|
? {}
|
|
633
639
|
: { showOwnership: projection.presentation.showOwnership }),
|
|
640
|
+
...(projection.presentation.showKindLabels === undefined
|
|
641
|
+
? {}
|
|
642
|
+
: { showKindLabels: projection.presentation.showKindLabels }),
|
|
634
643
|
...(projection.presentation.notation === undefined
|
|
635
644
|
? {}
|
|
636
645
|
: { notation: projection.presentation.notation }),
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a relationship reads as prose, kept in a module that imports nothing.
|
|
3
|
+
*
|
|
4
|
+
* One table for the brief and the canvas (ADR 0147). The brief has said
|
|
5
|
+
* "System API serves Process API" since it existed, and an edge label reading
|
|
6
|
+
* `serving` beside it was the same fact in a different voice. Every phrase
|
|
7
|
+
* here is spoken from the SOURCE: "a serves b".
|
|
8
|
+
*
|
|
9
|
+
* A canvas that layers the served element above what serves it reads the same
|
|
10
|
+
* edge from the other end, "b served by a", so the three kinds that turn for
|
|
11
|
+
* layering (`LAYERING_REVERSED_KINDS` in `./layout-mode.ts`) also carry a
|
|
12
|
+
* passive reading. Nothing else does: "b accessed by a" is not how anyone
|
|
13
|
+
* reads an access.
|
|
14
|
+
*/
|
|
15
|
+
export declare const RELATIONSHIP_READING: Readonly<Record<string, string>>;
|
|
16
|
+
export declare const REVERSED_READING: Readonly<Record<string, string>>;
|
|
17
|
+
/**
|
|
18
|
+
* `acme/p@1#applicationComponent` reads "application component" and
|
|
19
|
+
* `data-object` reads "data object": the last resort for a kind the table does
|
|
20
|
+
* not know, which is every extension kind.
|
|
21
|
+
*/
|
|
22
|
+
export declare const humanizeKind: (kind: string) => string;
|
|
23
|
+
/**
|
|
24
|
+
* The reading of one relationship kind. `reversed` asks for the passive form
|
|
25
|
+
* and gets it only where one exists; a kind with no passive reading keeps its
|
|
26
|
+
* active one, so a caller can pass the layering's answer straight through.
|
|
27
|
+
*/
|
|
28
|
+
export declare const relationshipReading: (coreKind: string, reversed?: boolean) => string;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a relationship reads as prose, kept in a module that imports nothing.
|
|
3
|
+
*
|
|
4
|
+
* One table for the brief and the canvas (ADR 0147). The brief has said
|
|
5
|
+
* "System API serves Process API" since it existed, and an edge label reading
|
|
6
|
+
* `serving` beside it was the same fact in a different voice. Every phrase
|
|
7
|
+
* here is spoken from the SOURCE: "a serves b".
|
|
8
|
+
*
|
|
9
|
+
* A canvas that layers the served element above what serves it reads the same
|
|
10
|
+
* edge from the other end, "b served by a", so the three kinds that turn for
|
|
11
|
+
* layering (`LAYERING_REVERSED_KINDS` in `./layout-mode.ts`) also carry a
|
|
12
|
+
* passive reading. Nothing else does: "b accessed by a" is not how anyone
|
|
13
|
+
* reads an access.
|
|
14
|
+
*/
|
|
15
|
+
export const RELATIONSHIP_READING = {
|
|
16
|
+
serving: 'serves',
|
|
17
|
+
access: 'accesses',
|
|
18
|
+
realization: 'realizes',
|
|
19
|
+
composition: 'comprises',
|
|
20
|
+
aggregation: 'aggregates',
|
|
21
|
+
assignment: 'is assigned to',
|
|
22
|
+
triggering: 'triggers',
|
|
23
|
+
flow: 'flows to',
|
|
24
|
+
specialization: 'specializes',
|
|
25
|
+
influence: 'influences',
|
|
26
|
+
association: 'is associated with',
|
|
27
|
+
};
|
|
28
|
+
export const REVERSED_READING = {
|
|
29
|
+
serving: 'served by',
|
|
30
|
+
realization: 'realized by',
|
|
31
|
+
specialization: 'specialized by',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* `acme/p@1#applicationComponent` reads "application component" and
|
|
35
|
+
* `data-object` reads "data object": the last resort for a kind the table does
|
|
36
|
+
* not know, which is every extension kind.
|
|
37
|
+
*/
|
|
38
|
+
export const humanizeKind = (kind) => {
|
|
39
|
+
const local = kind.slice(kind.indexOf('#') + 1);
|
|
40
|
+
return local
|
|
41
|
+
.replaceAll(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
42
|
+
.replaceAll('-', ' ')
|
|
43
|
+
.toLowerCase();
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The reading of one relationship kind. `reversed` asks for the passive form
|
|
47
|
+
* and gets it only where one exists; a kind with no passive reading keeps its
|
|
48
|
+
* active one, so a caller can pass the layering's answer straight through.
|
|
49
|
+
*/
|
|
50
|
+
export const relationshipReading = (coreKind, reversed = false) => (reversed ? REVERSED_READING[coreKind] : undefined) ??
|
|
51
|
+
RELATIONSHIP_READING[coreKind] ??
|
|
52
|
+
humanizeKind(coreKind);
|