Compare commits

...

2 Commits

Author SHA1 Message Date
Tony Garnock-Jones f5a04ff608 Draw substantially more of the jolly owl 2022-01-26 17:16:32 +01:00
Tony Garnock-Jones 6165c10a6c Bump deps 2022-01-26 17:15:51 +01:00
6 changed files with 561 additions and 154 deletions

View File

@ -15,6 +15,30 @@
<p>Interaction Diagrams for Syndicated Actors</p>
<div id="container">
<svg id="playground" viewBox="0 0 100 100" width="100" height="100">
<defs>
<marker id="arrowhead" markerWidth="15" markerHeight="15" refX="3.75" refY="7.5"
orient="auto-start-reverse">
<polygon points="0 0, 15 7.5, 0 15, 3.75 7.5, 0 0" />
</marker>
<linearGradient id="swimlane-fadeout" x1="0" x2="0" y1="0" y2="1">
<stop stop-color="var(--lifeline-color)" stop-opacity="1" offset="0%"/>
<stop stop-color="var(--lifeline-color)" stop-opacity="0" offset="100%"/>
</linearGradient>
</defs>
<!-- <path d="M 0 30 c 17.7 17.7 232.3 -17.7 250 0" -->
<!-- stroke-width="2" -->
<!-- fill="none" -->
<!-- stroke="#000" -->
<!-- marker-end="url(#arrowhead)"/> -->
<!-- <line x1="0" y1="50" x2="250" y2="50" stroke="#000" stroke-width="2" marker-end="url(#arrowhead)" /> -->
<!-- <path d="M16.7,178 c87.6-46.9,162.9-185.4,227-136.4C307.2,90.1,195,158.5,111,108.9C71,85.2,92.2,30.7,126,7" -->
<!-- stroke-width="2" -->
<!-- fill="none" -->
<!-- stroke="#000" -->
<!-- marker-end="url(#arrowhead)"/> -->
<g id="underlay"></g>
<g id="overlay"></g>

View File

@ -19,8 +19,8 @@
"@syndicate-lang/ws-relay": "^0.11"
},
"devDependencies": {
"@preserves/core": ">=0.19",
"@preserves/schema": ">=0.20",
"@preserves/core": ">=0.20.2",
"@preserves/schema": ">=0.21.2",
"@syndicate-lang/ts-plugin": "^0.11",
"@syndicate-lang/tsc": "^0.11",
"rollup": "^2.60",

View File

@ -1,18 +1,23 @@
/// SPDX-License-Identifier: GPL-3.0-or-later
/// SPDX-FileCopyrightText: Copyright © 2022 Tony Garnock-Jones <tonyg@leastfixedpoint.com>
import { Dataspace, Cap, Value, IdentitySet, Ref, Bytes, Observe, Turn, Reader, genericEmbeddedTypeDecode, Schemas, GenericEmbedded, Dictionary, fromJS } from "@syndicate-lang/core";
import { Dataspace, IdentitySet, Ref, Bytes, Observe, Turn, Reader, genericEmbeddedTypeDecode, Schemas, GenericEmbedded, Dictionary, fromJS } from "@syndicate-lang/core";
import { QuasiValue as Q } from "@syndicate-lang/core";
const Trace = Schemas.trace;
import * as P from './pict';
import { boot as bootHtml, Anchor, makeTemplate, UIEvent, UIFragment } from "@syndicate-lang/html";
// const svg = makeTemplate('svg');
import { boot as bootHtml, UIFragment } from "@syndicate-lang/html";
assertion type URLContent(url: string, content: Bytes | false);
const SWIMLANE_WIDTH = 100;
const LIFELINE_WIDTH = 10;
const LIFELINE_WIDTH = 5;
const ACTIVATION_WIDTH = LIFELINE_WIDTH * 2;
const VERTICAL_GAP = 20;
const POST_TURN_GAP = VERTICAL_GAP * 3;
const PLAYGROUND_PADDING = 10;
const TAIL_LENGTH = 60;
const TERMINATOR_SIZE = new P.Point(LIFELINE_WIDTH * 3, LIFELINE_WIDTH);
export function main() {
Dataspace.boot(ds => {
@ -57,14 +62,6 @@ function bootRenderer(ds: Ref) {
spawn named 'renderer' {
const r = new Renderer(ds);
at ds {
// const ui = new Anchor();
// field y: number = 50;
// assert ui.html('#playground', svg`<text y="${y.value}">foo</text>`);
// on message UIEvent(ui.fragmentId, '.', 'click', $_e) => {
// y.value += 20;
// }
// // Turn.active.every(1000 / 30.0, () => y.value += 5);
const traceUrl = 'd2';
during URLContent(traceUrl, false) => {
alert("Couldn't fetch trace file: " + JSON.stringify(traceUrl));
@ -89,10 +86,10 @@ class Swimlane {
readonly renderer: Renderer;
readonly laneNumber: number;
readonly startY: number;
readonly entries: Array<Schemas.trace.TraceEntry<GenericEmbedded>> = [];
readonly entries: Array<P.BoundedPict<any>> = [];
readonly lifeline = P.rect([LIFELINE_WIDTH, 0], {"class": "lifeline"});
// readonly lifeline = svg.tag(SVGRectElement)`<rect class="lifeline"/>`;
tail: P.BoundedPict<SVGRectElement> | null =
P.rect([LIFELINE_WIDTH, TAIL_LENGTH], {"class": "lifeline-tail"});
constructor (actorId: Schemas.trace.ActorId<GenericEmbedded>, laneNumber: number, renderer: Renderer) {
this.actorId = actorId;
@ -100,37 +97,47 @@ class Swimlane {
this.laneNumber = laneNumber;
this.startY = renderer.ypos;
new P.Point(this.midX - LIFELINE_WIDTH / 2, this.startY).apply(this.lifeline);
this.renderer.ypos += 10;
this.lifeline.setAttribute('height', '' + (this.renderer.ypos - this.startY));
this.renderer.underlay.appendChild(this.lifeline);
this.lifeline.updateBounds(b => b.withTopMid([this.midX, this.startY]));
this.updateLifelineHeight();
this.renderer.underlay.appendChild(this.lifeline.node);
this.renderer.underlay.appendChild(this.tail!.node);
}
updateLifelineHeight() {
this.lifeline.updateBounds(b => b.withHeight(this.renderer.ypos - b.top));
this.tail?.updateBounds(b => b.withTopMid([this.midX, this.renderer.ypos]));
}
get midX(): number {
return this.laneNumber * SWIMLANE_WIDTH;
}
addTraceEntry(entry: Schemas.trace.TraceEntry<GenericEmbedded>) {
addEntry(klass: string | null, entry: P.BoundedPict<any>): P.BoundedPict<any> {
this.entries.push(entry);
// const n = this.renderer.placard(
// 'test1', this.renderer.textNode('label', fromJS(entry.item).asPreservesText()));
// const b = this.renderer.measureBBox(n);
// n.setAttribute('x', '' + (this.laneNumber * SWIMLANE_WIDTH - b.width / 2));
// n.setAttribute('y', '' + this.renderer.ypos);
const n = P.placard(P.text(fromJS(entry.item).asPreservesText(), {"class": "label"}),
{"class": "test1"});
const b = P.bbox(n);
b.withTopMid([this.midX, this.renderer.ypos]).apply(n);
this.renderer.ypos += b.height + 10;
this.renderer.overlay.appendChild(n);
this.lifeline.setAttribute('height', '' + (this.renderer.ypos - this.startY));
if (entry.item._variant === "stop") {
this.renderer.releaseSwimlane(this.laneNumber);
if (klass !== null) {
const pin = P.circle(2, {"class": "pin"});
pin.updateBounds(b => b.withTopMid([this.midX, this.renderer.ypos]));
this.renderer.advance(pin.bounds.height);
this.renderer.overlay.appendChild(pin.node);
}
const n = klass === null ? entry : P.placard(entry, {"class": klass});
n.updateBounds(b => b.withTopMid([this.midX, this.renderer.ypos]));
this.renderer.overlay.appendChild(n.node);
this.renderer.advance(n.bounds.height);
this.updateLifelineHeight();
this.renderer.adjustViewport();
return n;
}
finish() {
this.renderer.underlay.removeChild(this.tail!.node);
this.tail = null;
this.renderer.releaseSwimlane(this.laneNumber);
}
}
@ -140,8 +147,9 @@ class Renderer {
readonly playground: SVGSVGElement = document.getElementById('playground')! as any;
readonly underlay: SVGGElement = this.playground.getElementById('underlay')! as any;
readonly overlay: SVGGElement = this.playground.getElementById('overlay')! as any;
readonly swimlanes: Dictionary<GenericEmbedded, Swimlane> = new Dictionary();
readonly swimlanes = new Dictionary<GenericEmbedded, Swimlane>();
readonly busyLanes = new IdentitySet<number>();
readonly turnMap = new Dictionary<GenericEmbedded, P.Point>();
ypos = 0;
constructor (ds: Ref) {
@ -155,44 +163,14 @@ class Renderer {
}
}
// temporarilyInDom<R>(e: SVGElement, f: () => R): R {
// this.playground.appendChild(e);
// const result = f();
// this.playground.removeChild(e);
// return result;
// }
// measureClientRect(e: SVGElement) {
// return this.temporarilyInDom(e, () => e.getBoundingClientRect());
// }
// measureBBox(e: SVGGraphicsElement) {
// return this.temporarilyInDom(e, () => e.getBBox());
// }
// measureTextLength(e: SVGTextContentElement) {
// return this.temporarilyInDom(e, () => e.getComputedTextLength());
// }
// textNode(klass: string, text: string): SVGTextElement {
// return svg.tag(SVGTextElement)`<text class="${klass}">${text}</text>`;
// }
// placard(klass: string, n: SVGGraphicsElement): SVGSVGElement {
// const b = this.measureBBox(n);
// const [px, py] = [12, 6];
// const base = svg.tag()`<rect class="${klass}" width="${b.width + px * 2}" height="${b.height + py * 2}"/>`;
// return svg.tag(SVGSVGElement)`<svg class="placard">${base}<g transform="translate(${px} ${py})">${n}</g></svg>`;
// }
adjustViewport() {
const bbox = this.playground.getBBox();
this.playground.viewBox.baseVal.x = bbox.x - 10;
this.playground.viewBox.baseVal.y = bbox.y - 10;
this.playground.viewBox.baseVal.width = bbox.width + 20;
this.playground.viewBox.baseVal.height = bbox.height + 20;
this.playground.width.baseVal.value = bbox.width + 20;
this.playground.height.baseVal.value = bbox.height + 20;
this.playground.viewBox.baseVal.x = bbox.x - PLAYGROUND_PADDING;
this.playground.viewBox.baseVal.y = bbox.y - PLAYGROUND_PADDING;
this.playground.viewBox.baseVal.width = bbox.width + PLAYGROUND_PADDING * 2;
this.playground.viewBox.baseVal.height = bbox.height + PLAYGROUND_PADDING * 2;
this.playground.width.baseVal.value = bbox.width + PLAYGROUND_PADDING * 2;
this.playground.height.baseVal.value = bbox.height + PLAYGROUND_PADDING * 2;
}
allocateSwimlane(): number {
@ -215,7 +193,163 @@ class Renderer {
return s;
}
advance(delta: number) {
this.ypos += delta;
}
connect(ps: P.Point, pe: P.Point) {
if (pe.x == ps.x) {
const pullMagnitude = pe.sub(ps).scale(0.1).length;
const pullAngle = Math.PI;
const pull = P.Point.rTheta(pullMagnitude + 30, pullAngle);
// TODO: compute marker size from the actual definition
const arrowheadSpace = P.Point.rTheta(15 - 3.75, pullAngle);
this.overlay.appendChild(
P.cubicBezier(ps,
ps.add(pull),
pe.add(pull),
pe.add(arrowheadSpace),
{"class": "arrow"}).node);
} else {
const pullMagnitude = pe.sub(ps).scale(0.1).length;
const pullAngle = pe.x > ps.x ? Math.PI/4 : 3*Math.PI/4;
const pull = P.Point.rTheta(pullMagnitude, pullAngle);
// TODO: compute marker size from the actual definition
const arrowheadSpace = P.Point.rTheta(15 - 3.75, pullAngle);
const pe1 = pe.sub(arrowheadSpace);
this.overlay.appendChild(
P.cubicBezier(ps,
ps.add(pull),
pe1.sub(pull),
pe1,
{"class": "arrow"}).node);
}
}
rewindTo<R>(pos: number, f: () => R): R {
const saved = this.ypos;
this.ypos = pos;
try {
return f();
} finally {
this.ypos = saved;
}
}
addTraceEntry(entry: Schemas.trace.TraceEntry<GenericEmbedded>) {
this.swimlaneFor(entry.actor).addTraceEntry(entry);
switch (entry.item._variant) {
case "start": {
this.advance(LIFELINE_WIDTH);
const swimlane = this.swimlaneFor(entry.actor);
swimlane.addEntry(null, P.rect(TERMINATOR_SIZE, {"class": "terminator"}));
this.advance(LIFELINE_WIDTH);
swimlane.addEntry("start", P.text(fromJS(entry.item).asPreservesText()));
break;
}
case "turn": {
const swimlane = this.swimlaneFor(entry.actor);
const activation = P.rect([ACTIVATION_WIDTH, 0], {"class": "activation"});
this.advance(LIFELINE_WIDTH);
activation.updateBounds(b => b.withTopMid([swimlane.midX, this.ypos]));
let causingTurnId: Schemas.trace.TurnId<GenericEmbedded> | null = null;
switch (entry.item.value.cause._variant) {
case "turn":
causingTurnId = entry.item.value.cause.id;
break;
case "delay":
causingTurnId = entry.item.value.cause.causingTurn;
break;
default:
break;
}
if (causingTurnId !== null) {
this.connect(this.turnMap.get(fromJS(causingTurnId))!,
new P.Point(swimlane.midX, this.ypos));
}
this.advance(LIFELINE_WIDTH);
swimlane.addEntry("turn-start", P.text(
entry.item.value.id.asPreservesText() + ': ' + fromJS(entry.item.value.cause).asPreservesText()));
entry.item.value.actions.forEach(a => {
let entryClass: string;
switch (a._variant) {
case "dequeue":
case "dequeueInternal":
entryClass = "event"; break;
case "enqueue":
case "enqueueInternal":
case "link":
case "spawn":
entryClass = "action"; break;
case "facetStart":
case "facetStop":
entryClass = "internal"; break;
case "linkedTaskStart":
entryClass = "start"; break;
}
this.advance(LIFELINE_WIDTH);
const fPos = this.ypos;
const e = swimlane.addEntry(entryClass, P.text(fromJS(a).asPreservesText()));
switch (a._variant) {
case "link": {
const other = this.swimlaneFor(a.childActor);
const f = this.rewindTo(fPos, () =>
other.addEntry(entryClass, P.text(
`linked to ${fromJS(a.parentActor).asPreservesText()}`)));
// TODO: compute marker size from the actual definition
const arrowheadSpace = new P.Point(
(15 - 3.75) * (other.midX > swimlane.midX ? -1 : 1),
0);
const ps = other.midX > swimlane.midX
? e.bounds.midRight.sub(arrowheadSpace)
: e.bounds.midLeft.sub(arrowheadSpace);
const pe = other.midX > swimlane.midX
? f.bounds.midLeft.add(arrowheadSpace)
: f.bounds.midRight.sub(arrowheadSpace);
this.overlay.appendChild(P.line(ps, pe, {"class": "doublearrow"}).node);
break;
}
case "spawn": {
if (!a.link) {
const other = this.swimlaneFor(a.id);
this.connect(new P.Point(swimlane.midX, this.ypos),
new P.Point(other.midX, this.ypos));
}
break;
}
default:
break;
}
});
this.advance(LIFELINE_WIDTH);
this.turnMap.set(fromJS(entry.item.value.id), new P.Point(swimlane.midX, this.ypos));
activation.updateBounds(b => b.withHeight(this.ypos - b.top));
this.underlay.appendChild(activation.node);
this.advance(POST_TURN_GAP - VERTICAL_GAP);
swimlane.updateLifelineHeight();
break;
}
case "stop": {
this.advance(VERTICAL_GAP);
const swimlane = this.swimlaneFor(entry.actor);
switch (entry.item.status._variant) {
case "ok":
swimlane.addEntry("stop-ok", P.text("✓"));
break;
case "Error":
swimlane.addEntry("stop-error", P.text(
"ERROR: " + fromJS(entry.item.status.value).asPreservesText(),
{"class": "stop-error"}));
break;
}
this.advance(LIFELINE_WIDTH);
swimlane.addEntry(null, P.rect(TERMINATOR_SIZE, {"class": "terminator"}));
this.advance(LIFELINE_WIDTH);
swimlane.finish();
break;
}
}
}
}

View File

@ -26,10 +26,18 @@ export class Point {
}
}
static rTheta(r: number, theta /* radians */: number): Point {
return new Point(r * Math.cos(theta), r * Math.sin(theta));
}
toDOM(): DOMPoint {
return new DOMPoint(this.x, this.y);
}
get length(): number {
return Math.sqrt(this.x * this.x + this.y * this.y);
}
add(p0: Pointlike): Point {
const p = Point.from(p0);
return new Point(this.x + p.x, this.y + p.y);
@ -51,10 +59,6 @@ export class Point {
return new Point(Math.min(this.x, p.x), Math.min(this.y, p.y));
}
apply<N extends SVGElement>(n: N): N {
return applyAttributes(n, { "x": this.x, "y": this.y });
}
withX(x: number): Point { return new Point(x, this.y); }
withY(y: number): Point { return new Point(this.x, y); }
@ -170,13 +174,158 @@ export class Rect {
return new Rect(p.x - this.width, p.y - this.height / 2, this.width, this.height);
}
apply<N extends SVGElement>(n: N): N {
return applyAttributes(n, {
"x": this.x,
"y": this.y,
"width": this.width,
"height": this.height,
union(r: Rect): Rect {
const x = Math.min(this.x, r.x);
const y = Math.min(this.y, r.y);
const w = Math.max(this.right, r.right) - x;
const h = Math.max(this.bottom, r.bottom) - y;
return new Rect(x, y, w, h);
}
}
export abstract class Pict<E extends SVGGraphicsElement> {
readonly node: E;
constructor (node: E) {
this.node = node;
}
abstract get bounds(): Rect;
}
export class BoundedPict<E extends SVGGraphicsElement> extends Pict<E> {
_bounds: Rect;
constructor (node: E, bounds?: Rectlike) {
super(node);
this._bounds = Rect.from(bounds ?? bbox(node));
}
get bounds(): Rect { return this._bounds; }
set bounds(r: Rect) {
const oldBounds = this._bounds;
this._bounds = r;
this.applyBounds(oldBounds);
}
updateBounds(f: (b: Rect) => Rect): this {
this.bounds = f(this.bounds);
return this;
}
applyBounds(_oldBounds: Rect) {
const b = this.bounds;
applyAttributes(this.node, { "x": b.x, "y": b.y, "width": b.width, "height": b.height });
}
}
export class LinelikePict<E extends SVGGeometryElement> extends Pict<E> {
_start: Point;
_end: Point;
constructor (node: E, start?: Pointlike, end?: Pointlike) {
super(node);
this._start = Point.from(start ?? Point.ZERO);
this._end = Point.from(end ?? Point.ZERO);
}
get bounds(): Rect { return bbox(this.node); }
get start(): Point { return this._start; }
set start(p: Pointlike) {
this._start = Point.from(p);
this.applyPoints();
}
get end(): Point { return this._end; }
set end(p: Pointlike) {
this._end = Point.from(p);
this.applyPoints();
}
applyPoints(): this {
applyAttributes(this.node, {
"x1": this._start.x,
"y1": this._start.y,
"x2": this._end.x,
"y2": this._end.y,
});
return this;
}
}
export class CubicBezierPict extends LinelikePict<SVGPathElement> {
_c1: Point;
_c2: Point;
constructor (
n: SVGPathElement,
start: Pointlike,
c1: Pointlike,
c2: Pointlike,
end: Pointlike,
) {
super(n, start, end);
this._c1 = Point.from(c1);
this._c2 = Point.from(c2);
}
get c1(): Point { return this._c1; }
set c1(p: Pointlike) {
this._c1 = Point.from(p);
this.applyPoints();
}
get c2(): Point { return this._c2; }
set c2(p: Pointlike) {
this._c2 = Point.from(p);
this.applyPoints();
}
applyPoints(): this {
applyAttributes(this.node, {
"d": `M ${this._start.x} ${this._start.y}
C ${this._c1.x} ${this._c1.y}
${this._c2.x} ${this._c2.y}
${this._end.x} ${this._end.y}`,
});
return this;
}
}
export class EllipsePict extends BoundedPict<SVGEllipseElement> {
applyBounds(_oldBounds: Rect) {
const c = this.bounds.center;
const r = c.sub(this.bounds.topLeft);
applyAttributes(this.node, { "cx": c.x, "cy": c.y, "rx": r.x, "ry": r.y });
}
}
export class PictGroup extends BoundedPict<SVGGElement> {
readonly children: BoundedPict<any>[] = [];
constructor() {
super(new SVGGElement());
}
add(p: BoundedPict<any>) {
this.children.push(p);
this.node.appendChild(p.node);
p.updateBounds(this.place);
if (this.children.length === 1) {
this._bounds = p.bounds;
} else {
this._bounds = this._bounds.union(p.bounds);
}
}
applyBounds(_oldBounds: Rect) {
const p = this.bounds.topLeft;
applyAttributes(this.node, { "transform": `translate(${p.x} ${p.y})` });
}
place(r: Rect): Rect {
return r;
}
}
@ -243,31 +392,55 @@ export function applyAttributes<N extends Element>(n: N, attributes?: Attributes
return n;
}
export function text(text: string, attributes?: Attributes): SVGTextElement {
return applyAttributes(svg.tag(SVGTextElement)`<text>${text}</text>`, attributes);
export function text(text: string, attributes?: Attributes): BoundedPict<SVGTextElement> {
return new BoundedPict(applyAttributes(svg.tag(SVGTextElement)`<text>${text}</text>`, attributes));
}
export function rect(extent0: Pointlike = Point.ZERO, attributes?: Attributes): SVGRectElement {
export function rect(extent0: Pointlike = Point.ZERO, attributes?: Attributes): BoundedPict<SVGRectElement> {
const extent = Point.from(extent0);
return applyAttributes(
svg.tag(SVGRectElement)`<rect width="${extent.x}" height="${extent.y}"/>`,
attributes);
return new BoundedPict(
applyAttributes(svg.tag(SVGRectElement)`<rect width="${extent.x}" height="${extent.y}"/>`,
attributes),
Rect.originExtent(Point.ZERO, extent));
}
export function circle(radius: number, attributes?: Attributes): SVGCircleElement {
return applyAttributes(svg.tag(SVGCircleElement)`<circle r="${radius}"/>`, attributes);
export function circle(radius: number, attributes?: Attributes): EllipsePict {
return new EllipsePict(applyAttributes(
svg.tag(SVGEllipseElement)`<ellipse rx="${radius}" ry="${radius}"/>`,
attributes));
}
export function alignTo(a: SVGGraphicsElement, n: Anchor, b: SVGGraphicsElement) {
n(bbox(a)).set(n(bbox(b)).get()).apply(a);
export function alignTo(a1: Anchor, p1: BoundedPict<any>, a2: Anchor, p2: BoundedPict<any>) {
p1.bounds = a1(p1.bounds).set(a2(p2.bounds).get());
}
export function placard(n: SVGGraphicsElement, attributes?: Attributes): SVGSVGElement {
export function placard(p: BoundedPict<any>, attributes?: Attributes): BoundedPict<SVGSVGElement> {
const padding = new Point(12, 6);
const base = rect(bbox(n).extent.add(padding.scale(2)), attributes);
return svg.tag(SVGSVGElement)`
const base = rect(p.bounds.extent.add(padding.scale(2)), attributes);
p.bounds = p.bounds.withTopLeft(padding);
return new BoundedPict(svg.tag(SVGSVGElement)`
<svg class="placard">
${base}
<g transform="translate(${padding.x} ${padding.y})">${n}</g>
</svg>`;
${base.node}
${p.node}
</svg>`, base.bounds);
}
export function line(
start?: Pointlike,
end?: Pointlike,
attributes?: Attributes,
): LinelikePict<SVGLineElement> {
const n = applyAttributes(svg.tag(SVGLineElement)`<line/>`, attributes)
return new LinelikePict(n, start, end).applyPoints();
}
export function cubicBezier(
s: Pointlike,
c1: Pointlike,
c2: Pointlike,
e: Pointlike,
attributes?: Attributes,
): CubicBezierPict {
const n = applyAttributes(svg.tag(SVGPathElement)`<path/>`, attributes);
return new CubicBezierPict(n, s, c1, c2, e).applyPoints();
}

View File

@ -1,3 +1,17 @@
:root {
--lifeline-color: #bebebe;
--lifeline-start-color: #98fb98;
--lifeline-stop-ok-color: #98fb98;
--lifeline-stop-error-color: #ff4400;
--lifeline-stop-error-text-color: #ffffff;
--event-color: #ffa500;
--action-color: #ffffff;
--internal-color: #e0e0e0;
--activation-color: #ffffff;
--turn-start-color: var(--event-color);
--arrow-color: #000000;
}
html {
box-sizing: border-box;
}
@ -21,8 +35,9 @@ body {
}
body > p {
padding: 0 0.33em;
margin: 0.33em 0;
padding: 0.33em 0.33em;
margin: 0;
border-bottom: solid black 1px;
}
body > div {
@ -34,25 +49,86 @@ body > div {
svg {
background: white;
font-family: monospace;
border-top: solid black 1px;
min-width: 100%;
min-height: 100%;
}
svg rect.lifeline {
fill: #ddd;
fill: var(--lifeline-color);
}
svg text.label {
svg rect.terminator {
fill: var(--lifeline-color);
}
svg rect.lifeline-tail {
fill: url(#swimlane-fadeout);
}
svg rect.activation {
fill: var(--activation-color);
stroke: #000;
}
svg text {
white-space: pre;
dominant-baseline: text-before-edge;
}
rect.test1 {
fill: #fff;
svg rect.start {
fill: var(--lifeline-start-color);
stroke: #000;
}
svg rect.stop-ok {
fill: var(--lifeline-stop-ok-color);
stroke: #000;
}
svg rect.stop-error {
fill: var(--lifeline-stop-error-color);
stroke: #000;
}
svg text.stop-error {
fill: var(--lifeline-stop-error-text-color);
stroke: none;
}
svg rect.event {
fill: var(--event-color);
stroke: #000;
}
svg rect.turn-start {
fill: var(--turn-start-color);
stroke: #000;
}
svg rect.action {
fill: var(--action-color);
stroke: #000;
}
svg rect.internal {
fill: var(--internal-color);
stroke: #000;
}
svg.placard {
overflow: visible;
}
svg .arrow {
fill: none;
stroke: var(--arrow-color);
stroke-width: 1;
marker-end: url(#arrowhead);
}
svg .doublearrow {
fill: none;
stroke: var(--arrow-color);
stroke-width: 1;
marker-start: url(#arrowhead);
marker-end: url(#arrowhead);
}

View File

@ -23,17 +23,17 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@preserves/core@>=0.19", "@preserves/core@^0.19.0":
version "0.19.0"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.19.0.tgz#7105a50beeb5d9531515547ca197a2c122c84935"
integrity sha512-Evsv3PGI51cJlTOr/6v52fTV6cjU11Pq6ogXBIPzfwmlMZ6kB5mx7EweFVwAf0dy5yCgBkq7ILSTCk4ZoV+x1g==
"@preserves/core@>=0.20.2", "@preserves/core@^0.20.2":
version "0.20.2"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.20.2.tgz#37afa1f1770d0e667b4d384f717e648af745a7fa"
integrity sha512-6mZbhYuzAvgA4u2UpuyW2JI5in4h693AdrDTLXfrpv542tFFcI9XxGyL7AJ/CnwDxaX/0d9lRt7uTOrfJJ0uMw==
"@preserves/schema@>=0.20":
version "0.20.0"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.20.0.tgz#e1dcb498655114fef06678ff6298b953d127f919"
integrity sha512-TmNCKJwYSN7qksa0wRWfSkchuNIfGUBJLkZp5hBX/6uqtY1RWGa9Q4nICaNuljw+pKaCFlWqpG0ebP5cmJMCGw==
"@preserves/schema@>=0.21.2":
version "0.21.2"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.21.2.tgz#b8aff321e4971a7ff9275f33c499fab068da06bb"
integrity sha512-RZIIc2ETvRLC3sjjfFlyjJK3+6dlOK0N94E7lWPxK9c226vF4MU0CePi9ryRPBihgn5VdsjhCCfWXxt+te3IIw==
dependencies:
"@preserves/core" "^0.19.0"
"@preserves/core" "^0.20.2"
"@types/glob" "^7.1"
"@types/minimatch" "^3.0"
chalk "^4.1"
@ -51,52 +51,52 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@syndicate-lang/compiler@^0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@syndicate-lang/compiler/-/compiler-0.11.1.tgz#da7c5c9ab23d37cda6d09692080cc4a9e078ffa6"
integrity sha512-nFyCmEZW6p8Oq/SWb/kXonfWH2nD4l2m9xIMj31tjC6waAE2n0+NQjYY4ieS7k9nWZ2cFYNhgSgVnIJ89P+bDw==
"@syndicate-lang/compiler@^0.11.3":
version "0.11.3"
resolved "https://registry.yarnpkg.com/@syndicate-lang/compiler/-/compiler-0.11.3.tgz#77b491c896bf0a83776cb99224968fd979a5e086"
integrity sha512-2ru1hCsaBFQCrPMocUDmyT6GE3IYuqUMYtHwx2pwegH8xPdzz33aShrAmfSRcv56xC+kkGXpj6hS3e+pM2ks6A==
"@syndicate-lang/core@^0.11", "@syndicate-lang/core@^0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@syndicate-lang/core/-/core-0.11.1.tgz#32d0191822cb4c126549a3f0067bb1d166f39eb5"
integrity sha512-3ofAypJ18ddSOtdGyXycDy4BAW/zMaOUEwndp/ADjmxIRDW6ja4GA5TZ0vIQjPfH7brSQhhECivt7aLf5HLzfw==
"@syndicate-lang/core@^0.11", "@syndicate-lang/core@^0.11.4":
version "0.11.4"
resolved "https://registry.yarnpkg.com/@syndicate-lang/core/-/core-0.11.4.tgz#995e2ad3d2594929b6b01f0a3c9c045f7fbcbe5b"
integrity sha512-6abI79+rbR0wC2kJeM/MUJNy8WQYTrtQwEcQOEXCCsfoj7KZ75wkzmC05B09Fu2URJpFzFRRaVKRyImx3oDWXA==
dependencies:
"@preserves/core" ">=0.19"
"@preserves/schema" ">=0.20"
"@preserves/core" ">=0.20.2"
"@preserves/schema" ">=0.21.2"
"@syndicate-lang/html@^0.11":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@syndicate-lang/html/-/html-0.11.2.tgz#80d9b754fa561711de6eb78ab30f0968c168d570"
integrity sha512-QVDoTM1E9su/pLgSF28Ei3gycExndxdgLk8GmpOatvYTuO+651Y/8ZU7kKHqKzdLSHew5DNTIVze0Sh3iAcR3Q==
version "0.11.5"
resolved "https://registry.yarnpkg.com/@syndicate-lang/html/-/html-0.11.5.tgz#f1ed2aab0b37e848ffe88fbbd450d4140c670602"
integrity sha512-Y440cq+niz9SaoU5z79SCq44Q4SU59ILRW/gmwByYWTt3zt35zV8nYwn/QhylNU4OvrCkLNImIYtV4f8TSrNAg==
dependencies:
"@syndicate-lang/core" "^0.11.1"
"@syndicate-lang/core" "^0.11.4"
"@syndicate-lang/ts-plugin@^0.11":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@syndicate-lang/ts-plugin/-/ts-plugin-0.11.2.tgz#89a6e5d61ad82d57a209ed239cbf8ed949dd6005"
integrity sha512-XvgW3Hnwd+ANtR3nJg8YwXMQw9IVSUn0ucNz7+wGoR0kOLkg5U6S8vwQTpYjVv4F942MZH2m5wwhGsaIA0KPqQ==
version "0.11.5"
resolved "https://registry.yarnpkg.com/@syndicate-lang/ts-plugin/-/ts-plugin-0.11.5.tgz#afc62b4c73af28fab9c33b30cd78227983ab9f5f"
integrity sha512-LMZO1W9Qa4OvMMMInFXre9Lb9xcFVMG7EqrY3VpmH5OVV+/0CznaNKCmy7EwJJ/Oumuxniu9vSYZ6Mfs9W3ZQg==
dependencies:
"@syndicate-lang/compiler" "^0.11.1"
"@syndicate-lang/core" "^0.11.1"
"@syndicate-lang/compiler" "^0.11.3"
"@syndicate-lang/core" "^0.11.4"
"@syndicate-lang/tsc@^0.11":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@syndicate-lang/tsc/-/tsc-0.11.2.tgz#ebae716b7c093aaa3d4a919b0888acecf4ecd5f6"
integrity sha512-HpexzKZXPaXE0b3Iv8pHSDQ0jH64xDRgwYntB4yyp0Jt3MkvbagIIvtEbyG3gXu0moiFGUTvAp2inBRt2cQ2jA==
version "0.11.5"
resolved "https://registry.yarnpkg.com/@syndicate-lang/tsc/-/tsc-0.11.5.tgz#67155080f357628b12014b842e00ceae8579e9d8"
integrity sha512-hOwQp7rFiwNaLj6r0LtdUEKe5eql8elWQuQDUbSz5DJ0mHuURDu4BdEH2yLdQEn1lyHz50R4Ze8UcLli4sIabA==
dependencies:
"@syndicate-lang/compiler" "^0.11.1"
"@syndicate-lang/core" "^0.11.1"
"@syndicate-lang/compiler" "^0.11.3"
"@syndicate-lang/core" "^0.11.4"
glob "^7.1.6"
yargs "^16.2.0"
"@syndicate-lang/ws-relay@^0.11":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@syndicate-lang/ws-relay/-/ws-relay-0.11.2.tgz#1174eb0f652a3286e858e612ea97d7f80a39ef11"
integrity sha512-S6kJ/yv1hYgs5vRUdqFhwAZPaIwdQKw9UDWZHgRc+SW9j8k0EWYh9D/ocsv3ZHAO2a8zhku9/LTz5QHhdk1HVg==
version "0.11.5"
resolved "https://registry.yarnpkg.com/@syndicate-lang/ws-relay/-/ws-relay-0.11.5.tgz#b5a72e884679c8166c85b4e694bbfeb00977b4e5"
integrity sha512-XbdYO6h1RXIS3hor4vUtO1y4HjGt1NF//JImdVrWmjCu2rWAMspa99LOZe/XhUK2wk6BLfxk2fZEA3fCz6Yiiw==
dependencies:
"@preserves/core" ">=0.19"
"@preserves/schema" ">=0.20"
"@syndicate-lang/core" "^0.11.1"
"@preserves/core" ">=0.20.2"
"@preserves/schema" ">=0.21.2"
"@syndicate-lang/core" "^0.11.4"
"@types/estree@0.0.39":
version "0.0.39"
@ -117,9 +117,9 @@
integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==
"@types/node@*":
version "17.0.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.10.tgz#616f16e9d3a2a3d618136b1be244315d95bd7cab"
integrity sha512-S/3xB4KzyFxYGCppyDt68yzBU9ysL88lSdIah4D6cptdcltc4NCPCAMc0+PCpg/lLIyC7IPvj2Z52OJWeIUkog==
version "17.0.12"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.12.tgz#f7aa331b27f08244888c47b7df126184bc2339c5"
integrity sha512-4YpbAsnJXWYK/fpTVFlMIcUIho2AYCi4wg5aNPrG1ng7fn/1/RZfCIpRCiBX+12RVa34RluilnvCqD+g3KiSiA==
aggregate-error@^3.0.0:
version "3.1.0"
@ -615,9 +615,9 @@ rollup-plugin-sourcemaps@^0.6:
source-map-resolve "^0.6.0"
rollup@^2.60:
version "2.66.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.66.0.tgz#ee529ea15a20485d579039637fec3050bad03bbb"
integrity sha512-L6mKOkdyP8HK5kKJXaiWG7KZDumPJjuo1P+cfyHOJPNNTK3Moe7zCH5+fy7v8pVmHXtlxorzaBjvkBMB23s98g==
version "2.66.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.66.1.tgz#366b0404de353c4331d538c3ad2963934fcb4937"
integrity sha512-crSgLhSkLMnKr4s9iZ/1qJCplgAgrRY+igWv8KhG/AjKOJ0YX/WpmANyn8oxrw+zenF3BXWDLa7Xl/QZISH+7w==
optionalDependencies:
fsevents "~2.3.2"