From a66048f0ffc48555b0e7ec13c8bc52d7b1dfe20f Mon Sep 17 00:00:00 2001
From: ys_teng <58208381+YeeShin504@users.noreply.github.com>
Date: Sun, 22 Mar 2026 08:52:35 +0800
Subject: [PATCH 1/8] Add variable arrow thickness
---
docs/editor.js | 7 ++++---
src/layout.ts | 13 ++++++++++---
src/parser.ts | 12 +++++++-----
src/renderer.ts | 12 ++++++------
4 files changed, 27 insertions(+), 17 deletions(-)
diff --git a/docs/editor.js b/docs/editor.js
index 55674a0..e1e82a6 100644
--- a/docs/editor.js
+++ b/docs/editor.js
@@ -83,6 +83,7 @@ const REFERENCE_DATA = [
items: [
{ title: "Normal Arrow", syntax: 'a -> b : "label"', example: 'a -> b : "Request"' },
{ title: "Thick Arrow", syntax: 'a => b : "label"', example: 'a => b : "Big Data"' },
+ { title: "Variable Thickness", syntax: 'a =>[thickness] b : "label"', example: 'a =>[0.5] b : "Thin"' },
{ title: "Corrupt Arrow", syntax: 'a ~> b : "label"', example: 'a ~> b : "Fragmented"' },
{ title: "Dropped Message", syntax: 'a -x b : "label"', example: 'a -x b : "Timeout"' },
{ title: "Time Offsets", syntax: 'a @1.5 -> b @1', example: 'a @1.5 -> b @1 : "Relative Time"' }
@@ -92,7 +93,7 @@ const REFERENCE_DATA = [
group: "Arrow Styles",
items: [
{ title: "Arrow Head Size", syntax: 'def arrowHeadSize 10px', example: 'def arrowHeadSize 15px' },
- { title: "Thick Thickness", syntax: 'def thickArrowThickness 40px', example: 'def thickArrowThickness 20px' },
+ { title: "Thick Ratio", syntax: 'def thickArrowThickness 1.0', example: 'def thickArrowThickness 0.5' },
{ title: "Label Offset", syntax: 'def labelOffset 10px', example: 'def labelOffset 20px' },
{ title: "Message Font Size", syntax: 'def messageFontSize 15px', example: 'def messageFontSize 18px' }
]
@@ -221,8 +222,8 @@ b ~> a : "corrupted reply"
a -x b : "dropped"
b -x a : "dropped reply"
-a => b : "thick"
-b => a : "thick reply"
+a => b : "thick reply"
+b =>[1.5] a : "thicker reply"
a @2 < "left label @2"
b @5 > "right label @5"`;
diff --git a/src/layout.ts b/src/layout.ts
index 5d5aea9..ca49949 100644
--- a/src/layout.ts
+++ b/src/layout.ts
@@ -15,6 +15,7 @@ export interface ArrowPos {
x2: number;
y2: number;
arrowType: ArrowType;
+ thickness: number;
label?: string;
}
@@ -95,6 +96,12 @@ export function resolveLayout(entities: Entities): Diagram {
endY = action.end;
}
+ const currentThicknessRatio = (action.arrowType === "thick")
+ ? (action.thicknessRatio ?? settings.thickArrowThickness)
+ : 0;
+
+ const thickness = currentThicknessRatio * settings.timeTickInterval;
+
draws.push({
type: action.type,
x1: participantsX.get(action.from),
@@ -102,12 +109,12 @@ export function resolveLayout(entities: Entities): Diagram {
x2: participantsX.get(action.to),
y2: height + endY * settings.timeTickInterval,
arrowType: action.arrowType,
+ thickness,
label: action.label,
});
- const thicknessRatio = action.arrowType === "thick" ? settings.thickArrowThickness / settings.timeTickInterval : 0;
- counterMax = Math.max(counterMax, startY + thicknessRatio, endY + thicknessRatio);
- counter = endY + thicknessRatio;
+ counterMax = Math.max(counterMax, startY + currentThicknessRatio, endY + currentThicknessRatio);
+ counter++;
break;
case "annotation":
diff --git a/src/parser.ts b/src/parser.ts
index cb34f01..a686eca 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -17,6 +17,7 @@ export interface Arrow {
start?: number;
end?: number;
arrowType: ArrowType;
+ thicknessRatio?: number;
label?: string;
}
@@ -65,7 +66,7 @@ export interface Settings {
const DEFAULT_SETTINGS: Settings = {
arrowHeadSize: 10,
dropCrossSize: 12,
- thickArrowThickness: 40,
+ thickArrowThickness: 1.0,
labelOffset: 10,
corruptStartRatio: 0.85,
dropStartRatio: 0.85,
@@ -214,7 +215,7 @@ export function parse(src: string): Entities {
// arrows
const arrowMatch = line.match(
- /^(\w+)(?:\s*@([\d.%px]+))?\s*(->|=>|~>|-x)\s*(\w+)(?:\s*@([\d.%px]+))?(?:\s*:\s*"(.+)")?/
+ /^(\w+)(?:\s*@([\d.%px]+))?\s*(->|=>|~>|-x)(?:\[([\d.]+)\])?\s*(\w+)(?:\s*@([\d.%px]+))?(?:\s*:\s*"(.+)")?/
);
if (arrowMatch) {
@@ -231,9 +232,10 @@ export function parse(src: string): Entities {
from: arrowMatch[1],
start: arrowMatch[2] ? (parseNumber(arrowMatch[2]) ?? undefined) : undefined,
arrowType: typeMap[arrowMatch[3]],
- to: arrowMatch[4],
- end: arrowMatch[5] ? (parseNumber(arrowMatch[5]) ?? undefined) : undefined,
- label: arrowMatch[6]
+ thicknessRatio: arrowMatch[4] ? parseFloat(arrowMatch[4]) : undefined,
+ to: arrowMatch[5],
+ end: arrowMatch[6] ? (parseNumber(arrowMatch[6]) ?? undefined) : undefined,
+ label: arrowMatch[7]
});
continue;
diff --git a/src/renderer.ts b/src/renderer.ts
index f626ba6..c9c44ce 100644
--- a/src/renderer.ts
+++ b/src/renderer.ts
@@ -110,7 +110,7 @@ function drawAnnotation(settings: Settings, annotation: AnnotationPos): string {
}
function drawArrow(settings: Settings, arrow: ArrowPos): string {
- const { x1, y1, x2, y2, arrowType, label } = arrow;
+ const { x1, y1, x2, y2, arrowType, thickness, label } = arrow;
const dx = x2 - x1;
const dy = y2 - y1;
@@ -207,13 +207,13 @@ function drawArrow(settings: Settings, arrow: ArrowPos): string {
case "thick":
arrowsvg = `
-
+
-
-
-
+
+
+
`;
break;
@@ -240,7 +240,7 @@ function drawArrow(settings: Settings, arrow: ArrowPos): string {
const lx = mx - px * offset;
const ly = (arrowType === "thick")
- ? my + (direction * py * settings.thickArrowThickness / 2)
+ ? my + (direction * py * thickness / 2)
: my - py * offset;
From e9ba3820727d711073431da76d9561214fa9dc28 Mon Sep 17 00:00:00 2001
From: ys_teng <58208381+YeeShin504@users.noreply.github.com>
Date: Sun, 22 Mar 2026 09:47:28 +0800
Subject: [PATCH 2/8] Improve styling and layout of time axis
---
docs/editor.css | 86 ++++++++++++--------
docs/editor.js | 58 +++++++-------
docs/example.svg | 202 +++++++++++++++++++++++------------------------
src/layout.ts | 41 +++++++---
src/parser.ts | 8 +-
5 files changed, 217 insertions(+), 178 deletions(-)
diff --git a/docs/editor.css b/docs/editor.css
index d089dbd..5e90e9a 100644
--- a/docs/editor.css
+++ b/docs/editor.css
@@ -4,18 +4,31 @@
box-sizing: border-box;
}
+:root {
+ --bg-main: #1e1e1e;
+ --bg-sidebar: #252526;
+ --bg-header: #2d2d2d;
+ --border-color: #3e3e42;
+ --text-main: #d4d4d4;
+ --text-muted: #858585;
+ --text-header: #969696;
+ --accent-color: #0e639c;
+ --accent-hover: #1177bb;
+ --header-height: 42px;
+}
+
body {
font-family: system-ui, -apple-system, sans-serif;
- background: #1e1e1e;
- color: #d4d4d4;
+ background: var(--bg-main);
+ color: var(--text-main);
height: 100vh;
overflow: hidden;
}
header {
- background: #252526;
+ background: var(--bg-sidebar);
padding: 1rem 2rem;
- border-bottom: 1px solid #3e3e42;
+ border-bottom: 1px solid var(--border-color);
}
.header-content {
@@ -26,17 +39,17 @@ header {
.resizer {
width: 6px;
- background: #1e1e1e;
+ background: var(--bg-main);
cursor: col-resize;
position: relative;
z-index: 100;
- border-left: 1px solid #3e3e42;
- border-right: 1px solid #3e3e42;
+ border-left: 1px solid var(--border-color);
+ border-right: 1px solid var(--border-color);
transition: background 0.2s;
}
.resizer:hover, .resizer.dragging {
- background: #0e639c;
+ background: var(--accent-color);
}
.toggle-arrow {
@@ -44,9 +57,9 @@ header {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
- background: #2d2d2d;
- border: 1px solid #3e3e42;
- color: #858585;
+ background: var(--bg-header);
+ border: 1px solid var(--border-color);
+ color: var(--text-muted);
width: 20px;
height: 40px;
padding: 0;
@@ -74,7 +87,7 @@ header {
#panel-reference {
flex: 0 0 280px;
- background: #252526;
+ background: var(--bg-sidebar);
transition: flex-basis 0.3s ease-in-out;
}
@@ -109,21 +122,21 @@ main {
}
.reference-content {
- background: #252526;
+ background: var(--bg-sidebar);
padding: 1.25rem;
overflow-y: auto;
}
.search-container {
- padding: 0.75rem 1.25rem;
- background: #252526;
- border-bottom: 1px solid #3e3e42;
+ padding: 0.6rem 1rem;
+ background: var(--bg-header);
+ border-bottom: 1px solid var(--border-color);
}
#refSearch {
width: 100%;
background: #3c3c3c;
- border: 1px solid #3e3e42;
+ border: 1px solid var(--border-color);
color: #cccccc;
padding: 0.4rem 0.75rem;
border-radius: 3px;
@@ -133,7 +146,7 @@ main {
}
#refSearch:focus {
- border-color: #0e639c;
+ border-color: var(--accent-color);
}
.loading {
@@ -156,8 +169,8 @@ main {
}
.ref-item {
- background: #2d2d2d;
- border: 1px solid #3e3e42;
+ background: var(--bg-header);
+ border: 1px solid var(--border-color);
border-radius: 6px;
padding: 0.75rem;
margin-bottom: 0.75rem;
@@ -185,13 +198,13 @@ main {
color: #ce9178;
border-radius: 4px;
margin-bottom: 0.6rem;
- border: 1px solid #3e3e42;
+ border: 1px solid var(--border-color);
}
.try-btn-small {
background: transparent;
- border: 1px solid #3e3e42;
- color: #858585;
+ border: 1px solid var(--border-color);
+ color: var(--text-muted);
padding: 0.25rem 0.6rem;
border-radius: 3px;
font-size: 0.7rem;
@@ -211,22 +224,25 @@ main {
}
.panel-header {
- background: #2d2d2d;
- padding: 0.75rem 1rem;
- border-bottom: 1px solid #3e3e42;
+ background: var(--bg-header);
+ padding: 0.6rem 1rem;
+ border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
+ height: var(--header-height);
}
.panel-title {
- font-size: 0.875rem;
- font-weight: 500;
- color: #cccccc;
+ font-size: 0.75rem;
+ font-weight: 600;
+ color: var(--text-header);
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
}
.copy-btn {
- background: #0e639c;
+ background: var(--accent-color);
color: white;
border: none;
padding: 0.4rem 0.75rem;
@@ -237,7 +253,7 @@ main {
}
.copy-btn:hover {
- background: #1177bb;
+ background: var(--accent-hover);
}
.copy-btn:active {
@@ -306,8 +322,8 @@ svg {
}
.CodeMirror-gutters {
- background: #252526;
- border-right: 1px solid #3e3e42;
+ background: var(--bg-sidebar);
+ border-right: 1px solid var(--border-color);
}
.CodeMirror-linenumber {
@@ -395,13 +411,13 @@ svg {
list-style: none;
margin: 0;
padding: 2px;
- background: #252526;
+ background: var(--bg-sidebar);
border: 1px solid #454545;
border-radius: 4px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
font-family: 'JetBrains Mono', 'Consolas', 'Monaco', monospace;
font-size: 13px;
- color: #d4d4d4;
+ color: var(--text-main);
max-height: 200px;
overflow-y: auto;
}
diff --git a/docs/editor.js b/docs/editor.js
index e1e82a6..5df12b4 100644
--- a/docs/editor.js
+++ b/docs/editor.js
@@ -20,14 +20,14 @@ function initResizer(resizer, leftPanel, rightPanel, isFirst) {
resizer.addEventListener('mousedown', (e) => {
if (e.target.id === 'toggleReference') return;
-
+
startX = e.clientX;
startWidthLeft = leftPanel.offsetWidth;
startWidthRight = rightPanel.offsetWidth;
-
+
// Disable transitions during resizing to prevent lag
leftPanel.style.transition = 'none';
-
+
const onMouseMove = (e) => {
const deltaX = e.clientX - startX;
if (isFirst) {
@@ -46,10 +46,10 @@ function initResizer(resizer, leftPanel, rightPanel, isFirst) {
document.removeEventListener('mouseup', onMouseUp);
resizer.classList.remove('dragging');
document.body.style.cursor = 'default';
-
+
// Re-enable transition after resizing
leftPanel.style.transition = '';
-
+
if (editor) editor.refresh();
};
@@ -72,10 +72,10 @@ const REFERENCE_DATA = [
{
group: "Participants",
items: [
- { title: "Define Participant", syntax: 'participant Name alias', example: 'participant Client c\nparticipant Server s' },
- { title: "Participant Spacing", syntax: 'def participantSpacing 240px', example: 'def participantSpacing 300px' },
- { title: "Label Height", syntax: 'def participantLabelHeight 30px', example: 'def participantLabelHeight 50px' },
- { title: "Font Size", syntax: 'def participantFontSize 20px', example: 'def participantFontSize 24px' }
+ { title: "Define Participant", syntax: 'participant Name alias', example: 'participant Client c' },
+ { title: "Participant Spacing", syntax: 'def participantSpacing 240px', example: 'def participantSpacing 240px' },
+ { title: "Label Height", syntax: 'def participantLabelHeight 30px', example: 'def participantLabelHeight 30px' },
+ { title: "Font Size", syntax: 'def participantFontSize 20px', example: 'def participantFontSize 20px' }
]
},
{
@@ -92,20 +92,20 @@ const REFERENCE_DATA = [
{
group: "Arrow Styles",
items: [
- { title: "Arrow Head Size", syntax: 'def arrowHeadSize 10px', example: 'def arrowHeadSize 15px' },
- { title: "Thick Ratio", syntax: 'def thickArrowThickness 1.0', example: 'def thickArrowThickness 0.5' },
- { title: "Label Offset", syntax: 'def labelOffset 10px', example: 'def labelOffset 20px' },
- { title: "Message Font Size", syntax: 'def messageFontSize 15px', example: 'def messageFontSize 18px' }
+ { title: "Arrow Head Size", syntax: 'def arrowHeadSize 10px', example: 'def arrowHeadSize 10px' },
+ { title: "Thick Ratio", syntax: 'def thickArrowThickness 1.0', example: 'def thickArrowThickness 1.0' },
+ { title: "Label Offset", syntax: 'def labelOffset 10px', example: 'def labelOffset 10px' },
+ { title: "Message Font Size", syntax: 'def messageFontSize 15px', example: 'def messageFontSize 15px' }
]
},
{
group: "Corrupt & Dropped",
items: [
- { title: "Corrupt Start %", syntax: 'def corruptStartRatio 85%', example: 'def corruptStartRatio 50%' },
- { title: "Drop Start %", syntax: 'def dropStartRatio 85%', example: 'def dropStartRatio 50%' },
- { title: "Drop Cross Size", syntax: 'def dropCrossSize 12px', example: 'def dropCrossSize 20px' },
- { title: "Corrupt Squiggle Size", syntax: 'def squiggleSize 20px', example: 'def squiggleSize 10px' },
- { title: "Corrupt Squiggle Count", syntax: 'def squiggleCount 2', example: 'def squiggleCount 5' }
+ { title: "Corrupt Start %", syntax: 'def corruptStartRatio 85%', example: 'def corruptStartRatio 85%' },
+ { title: "Drop Start %", syntax: 'def dropStartRatio 85%', example: 'def dropStartRatio 85%' },
+ { title: "Drop Cross Size", syntax: 'def dropCrossSize 12px', example: 'def dropCrossSize 12px' },
+ { title: "Corrupt Squiggle Size", syntax: 'def squiggleSize 20px', example: 'def squiggleSize 20px' },
+ { title: "Corrupt Squiggle Count", syntax: 'def squiggleCount 2', example: 'def squiggleCount 2' }
]
},
{
@@ -113,7 +113,7 @@ const REFERENCE_DATA = [
items: [
{ title: "Left Note", syntax: 'a < "text"', example: 'a < "Local check"' },
{ title: "Right Note", syntax: 'a > "text"', example: 'a > "Processing"' },
- { title: "Annotation Width", syntax: 'def annotationWidth 80px', example: 'def annotationWidth 120px' }
+ { title: "Annotation Width", syntax: 'def annotationWidth 80px', example: 'def annotationWidth 80px' }
]
},
{
@@ -121,15 +121,15 @@ const REFERENCE_DATA = [
items: [
{ title: "Show Grid", syntax: 'def showGrid true', example: 'def showGrid true' },
{ title: "Show Time Ticks", syntax: 'def showTimeTicks true', example: 'def showTimeTicks true' },
- { title: "Time Interval", syntax: 'def timeTickInterval 40px', example: 'def timeTickInterval 60px' },
+ { title: "Time Interval", syntax: 'def timeTickInterval 40px', example: 'def timeTickInterval 40px' },
{ title: "Time Unit", syntax: 'def timeUnit /ms', example: 'def timeUnit /ms' }
]
},
{
group: "Layout",
items: [
- { title: "Horizontal Padding", syntax: 'def paddingX 30px', example: 'def paddingX 50px' },
- { title: "Vertical Padding", syntax: 'def paddingY 20px', example: 'def paddingY 50px' }
+ { title: "Horizontal Padding", syntax: 'def paddingX 30px', example: 'def paddingX 30px' },
+ { title: "Vertical Padding", syntax: 'def paddingY 20px', example: 'def paddingY 20px' }
]
}
];
@@ -142,8 +142,8 @@ function renderReference(filter = '') {
const query = filter.toLowerCase().trim();
REFERENCE_DATA.forEach(group => {
- const filteredItems = group.items.filter(item =>
- item.title.toLowerCase().includes(query) ||
+ const filteredItems = group.items.filter(item =>
+ item.title.toLowerCase().includes(query) ||
item.syntax.toLowerCase().includes(query) ||
group.group.toLowerCase().includes(query)
);
@@ -153,23 +153,23 @@ function renderReference(filter = '') {
const groupEl = document.createElement('div');
groupEl.className = 'ref-group';
groupEl.innerHTML = `
${group.group}
`;
-
+
const itemsContainer = document.createElement('div');
itemsContainer.className = 'group-items';
filteredItems.forEach(item => {
const itemEl = document.createElement('div');
itemEl.className = 'ref-item';
-
+
const titleEl = document.createElement('div');
titleEl.className = 'ref-title';
titleEl.textContent = item.title;
-
+
const codeEl = document.createElement('code');
codeEl.className = 'ref-syntax';
// Use the editor engine to highlight this static snippet!
CodeMirror.runMode(item.syntax, 'protocol-ml', codeEl);
-
+
const btn = document.createElement('button');
btn.className = 'try-btn-small';
btn.textContent = 'Try it';
@@ -228,7 +228,7 @@ b =>[1.5] a : "thicker reply"
a @2 < "left label @2"
b @5 > "right label @5"`;
-CodeMirror.commands.autocomplete = function(cm) {
+CodeMirror.commands.autocomplete = function (cm) {
cm.showHint({ hint: CodeMirror.hint["protocol-ml"] });
};
diff --git a/docs/example.svg b/docs/example.svg
index fa4c471..d54d429 100644
--- a/docs/example.svg
+++ b/docs/example.svg
@@ -1,19 +1,19 @@
-