From 85d806a571188b58d51ef64d1bec3c1d6eb6d78f Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Thu, 12 Mar 2026 15:13:33 +0800 Subject: [PATCH 1/3] Update README and build scripts --- .github/workflows/deploy-editor.yml | 12 +----------- .gitignore | 1 - README.md | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/.github/workflows/deploy-editor.yml b/.github/workflows/deploy-editor.yml index 6d829c2..4fcdea5 100644 --- a/.github/workflows/deploy-editor.yml +++ b/.github/workflows/deploy-editor.yml @@ -24,8 +24,7 @@ jobs: - name: Build interactive editor run: npm run build:docs - - name: Deploy to GitHub Pages (master) - if: github.ref == 'refs/heads/master' + - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -33,12 +32,3 @@ jobs: publish_branch: gh-pages keep_files: true - - name: Deploy to GitHub Pages (dev) - if: github.ref == 'refs/heads/dev' - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: dist - destination_dir: dev - publish_branch: gh-pages - keep_files: true diff --git a/.gitignore b/.gitignore index 01ee1d7..2aa8c99 100644 --- a/.gitignore +++ b/.gitignore @@ -81,7 +81,6 @@ out # Nuxt.js build / generate output .nuxt dist -docs/dist .output # Gatsby files diff --git a/README.md b/README.md index 9a93fef..b5215fd 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Markup language for rendering network protocol diagrams. ## Interactive Demo -Try the live editor: [Demo](https://yeeshin504.github.io/protocol-ml/index.html) (after building with `npm run build:docs`) +Try the live editor [here](https://alieron.github.io/protocol-ml/) ### Development From 3937672b5dfeff6fa3ef2c749aa01b35fd8e58a7 Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:58:47 +0800 Subject: [PATCH 2/3] Sync with upstream --- src/layout.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/layout.ts b/src/layout.ts index fefc0bc..4ad3863 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -74,6 +74,8 @@ export function resolveLayout(entities: Entities): Diagram { if (action.start) { counter = action.start; + startY = counter; + endY = counter + 1; } if (action.end) { From 8b42be3ff6d5fcae32cb317c2285f74a0ffab731 Mon Sep 17 00:00:00 2001 From: ys_teng <58208381+YeeShin504@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:11:22 +0800 Subject: [PATCH 3/3] Add time ticks and grid --- .github/workflows/deploy-editor.yml | 12 ++++++- docs/editor.js | 2 ++ src/layout.ts | 47 ++++++++++++++++++++++---- src/parser.ts | 17 ++++++++-- src/renderer.ts | 51 ++++++++++++++++++++++++++++- 5 files changed, 118 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy-editor.yml b/.github/workflows/deploy-editor.yml index 4fcdea5..6d829c2 100644 --- a/.github/workflows/deploy-editor.yml +++ b/.github/workflows/deploy-editor.yml @@ -24,7 +24,8 @@ jobs: - name: Build interactive editor run: npm run build:docs - - name: Deploy to GitHub Pages + - name: Deploy to GitHub Pages (master) + if: github.ref == 'refs/heads/master' uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -32,3 +33,12 @@ jobs: publish_branch: gh-pages keep_files: true + - name: Deploy to GitHub Pages (dev) + if: github.ref == 'refs/heads/dev' + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: dist + destination_dir: dev + publish_branch: gh-pages + keep_files: true diff --git a/docs/editor.js b/docs/editor.js index a4b9e08..68e6fe7 100644 --- a/docs/editor.js +++ b/docs/editor.js @@ -8,6 +8,8 @@ const copySVGBtn = document.getElementById('copySVG'); const INITIAL_CODE = `def messageSpacing 20px def participantSpacing 160px +def showTimeTicks true +def showGrid true participant Alice a participant Bob b diff --git a/src/layout.ts b/src/layout.ts index 4ad3863..5a1fc55 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -26,20 +26,33 @@ export interface AnnotationPos { text: string; } +export interface TickPos { + type: "tick"; + y: number; + label: string; +} + +export interface TimeAxisPos { + type: "timeAxis"; + x: number; + y1: number; + y2: number; +} + export interface Diagram { settings: Settings; width: number; height: number; - draws: (ParticipantPos | ArrowPos | AnnotationPos)[]; + draws: (ParticipantPos | ArrowPos | AnnotationPos | TickPos | TimeAxisPos)[]; } export function resolveLayout(entities: Entities): Diagram { const { settings, participants, actions, numAnnotations } = entities; // 1. check if need extra spacing for annotations - + let timeTickMargin = settings.showTimeTicks ? 60 : 0; let width = settings.participantSpacingX * (participants.length - 1) - + 2 * settings.paddingX; + + 2 * settings.paddingX + timeTickMargin; if (numAnnotations > 0) width += 2 * settings.annotationSpacingX; @@ -51,9 +64,12 @@ export function resolveLayout(entities: Entities): Diagram { // 3. resolve participant x position const participantsX = new Map(); // map alias -> x coord + let timeAxisX = settings.paddingX + (numAnnotations > 0 ? settings.annotationSpacingX : 0) + (timeTickMargin > 0 ? 20 : 0); { - // recomputing width, but whatever - let x = settings.paddingX + (numAnnotations > 0 ? settings.annotationSpacingX : 0); + let x = timeAxisX + timeTickMargin; + if (timeTickMargin === 0) { + x = settings.paddingX + (numAnnotations > 0 ? settings.annotationSpacingX : 0); + } for (const participant of participants) { participantsX.set(participant.alias, x); x += settings.participantSpacingX; @@ -63,7 +79,7 @@ export function resolveLayout(entities: Entities): Diagram { // 4. resolve arrows and annotations let counter = 0, counterMax = counter; - const draws: (ParticipantPos | ArrowPos | AnnotationPos)[] = []; + const draws: (ParticipantPos | ArrowPos | AnnotationPos | TickPos | TimeAxisPos)[] = []; for (const action of actions) { // actions should be in order :D switch (action.type) { @@ -136,6 +152,25 @@ export function resolveLayout(entities: Entities): Diagram { }; }); + if (settings.showTimeTicks || settings.showGrid) { + for (let i = 0; i <= counterMax; i++) { + draws.push({ + type: "tick", + y: oldHeight + settings.messageSpacingY + i * settings.messageSpacingY, + label: `${i}`, + }); + } + } + + if (settings.showTimeTicks) { + draws.push({ + type: "timeAxis", + x: timeAxisX, + y1: oldHeight, + y2: height, + }); + } + height += settings.paddingY; return { diff --git a/src/parser.ts b/src/parser.ts index 4021685..7ea02f0 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -56,12 +56,17 @@ const DEFAULT_SETTINGS = { paddingX: 30, paddingY: 20, + + showGrid: false, + showTimeTicks: false, }; -export type Settings = typeof DEFAULT_SETTINGS; +export type Settings = { + [key: string]: number | boolean | undefined; +} & typeof DEFAULT_SETTINGS; export function parse(src: string): Entities { - const settings = DEFAULT_SETTINGS; + const settings: Settings = { ...DEFAULT_SETTINGS }; const participants: Participant[] = []; const actions: (Arrow | Annotation)[] = []; @@ -80,7 +85,13 @@ export function parse(src: string): Entities { const [, name, value] = line.split(/\s+/); - settings[name] = parseFloat(value); + if (/^true$/i.test(value)) { + settings[name] = true; + } else if (/^false$/i.test(value)) { + settings[name] = false; + } else { + settings[name] = parseFloat(value); + } continue; } diff --git a/src/renderer.ts b/src/renderer.ts index 2c8ea11..53aae31 100644 --- a/src/renderer.ts +++ b/src/renderer.ts @@ -1,5 +1,43 @@ import { Entities, Settings } from "./parser"; -import { AnnotationPos, ArrowPos, ParticipantPos, resolveLayout } from "./layout"; +import { AnnotationPos, ArrowPos, ParticipantPos, TickPos, TimeAxisPos, resolveLayout } from "./layout"; + +function drawGrid(settings: Settings, ticks: TickPos[], width: number): string { + return ticks.map(tick => + `` + ).join("\n"); +} + +function drawTimeAxis(settings: Settings, axis: TimeAxisPos, ticks: TickPos[]): string { + const header = ` + + Time + +`; + + const tickElements = ticks.map(tick => ` + + + ${tick.label} +`).join("\n"); + + return `${header}${tickElements}`; +} function drawParticipant(settings: Settings, participant: ParticipantPos): string { const { x, y1, y2, name } = participant; @@ -202,6 +240,17 @@ export function renderSVG(entities: Entities): string { `` ); + const ticks = draws.filter((d): d is TickPos => d.type === "tick"); + const timeAxis = draws.find((d): d is TimeAxisPos => d.type === "timeAxis"); + + if (settings.showGrid) { + svg.push(drawGrid(settings, ticks, width)); + } + + if (settings.showTimeTicks && timeAxis) { + svg.push(drawTimeAxis(settings, timeAxis, ticks)); + } + for (const draw of draws) { switch (draw.type) { case "participant":