/// SPDX-License-Identifier: GPL-3.0-or-later /// SPDX-FileCopyrightText: Copyright © 2022 Tony Garnock-Jones import { Dataspace, Cap, Value, 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'); assertion type URLContent(url: string, content: Bytes | false); const SWIMLANE_WIDTH = 100; const LIFELINE_WIDTH = 10; export function main() { Dataspace.boot(ds => { P.init(); bootHtml(ds); bootFetcher(ds); bootRenderer(ds); }); } function bootFetcher(ds: Ref) { spawn named 'fetcher' { at ds { during Observe({ "pattern": :pattern URLContent(\Q.lit($url: string), \_) }) => spawn named ['fetch', url] { const facet = Turn.activeFacet; facet.preventInertCheck(); fetch(url) .then(response => { if (!response.ok) { console.log('unsuccessful request', url, response); facet.turn(() => { assert URLContent(url, false); }); } else { response.arrayBuffer().then(bs => facet.turn(() => { assert URLContent(url, Bytes.from(bs)); })); } }) .catch(error => facet.turn(() => { console.error('failed request', url, error); assert URLContent(url, false); })); } } } } 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`foo`); // 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)); } during URLContent(traceUrl, $bs: Bytes) => { const vs = new Reader(bs.fromUtf8(), { embeddedDecode: genericEmbeddedTypeDecode, }).readToEnd(); for (const v of vs) { r.addTraceEntry(Trace.asTraceEntry(v)); } r.container.scrollLeft = (r.playground.width.baseVal.value - r.container.getBoundingClientRect().width) / 2; } } } } class Swimlane { readonly actorId: Schemas.trace.ActorId; readonly renderer: Renderer; readonly laneNumber: number; readonly startY: number; readonly entries: Array> = []; readonly lifeline = P.rect([LIFELINE_WIDTH, 0], {"class": "lifeline"}); // readonly lifeline = svg.tag(SVGRectElement)``; constructor (actorId: Schemas.trace.ActorId, laneNumber: number, renderer: Renderer) { this.actorId = actorId; this.renderer = renderer; 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); } get midX(): number { return this.laneNumber * SWIMLANE_WIDTH; } addTraceEntry(entry: Schemas.trace.TraceEntry) { 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); } this.renderer.adjustViewport(); } } class Renderer { ds: Ref; readonly container: HTMLDivElement = document.getElementById('container')! as any; 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 = new Dictionary(); readonly busyLanes = new IdentitySet(); ypos = 0; constructor (ds: Ref) { this.ds = ds; this.adjustViewport(); at this.ds { on asserted UIFragment($_fragmentId, $_newSelector, $_newHtml, $_newOrderBy) => { this.adjustViewport(); } } } // temporarilyInDom(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}`; // } // placard(klass: string, n: SVGGraphicsElement): SVGSVGElement { // const b = this.measureBBox(n); // const [px, py] = [12, 6]; // const base = svg.tag()``; // return svg.tag(SVGSVGElement)`${base}${n}`; // } 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; } allocateSwimlane(): number { let i = 0; while (this.busyLanes.has(i)) i++; this.busyLanes.add(i); return i; } releaseSwimlane(n: number) { this.busyLanes.delete(n); } swimlaneFor(actorId: Schemas.trace.ActorId): Swimlane { let k = fromJS(actorId); let s = this.swimlanes.get(k); if (s === void 0) { this.swimlanes.set(k, s = new Swimlane(actorId, this.allocateSwimlane(), this)); } return s; } addTraceEntry(entry: Schemas.trace.TraceEntry) { this.swimlaneFor(entry.actor).addTraceEntry(entry); } }