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":