@ogabrielluiz/patchflow 0.1.0 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-B7DUSPQH.js → chunk-4VUBNFI4.js} +1 -1
- package/dist/{chunk-B7DUSPQH.js.map → chunk-4VUBNFI4.js.map} +1 -1
- package/dist/index.cjs +136 -54
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +137 -55
- package/dist/index.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/package.json +11 -3
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
SIGNAL_OPERATORS,
|
|
3
3
|
__commonJS,
|
|
4
4
|
__toESM
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-4VUBNFI4.js";
|
|
6
6
|
|
|
7
7
|
// node_modules/@dagrejs/graphlib/lib/graph.js
|
|
8
8
|
var require_graph = __commonJS({
|
|
@@ -3542,7 +3542,8 @@ var errorMessages = {
|
|
|
3542
3542
|
duplicateModule: (name) => `Module "${name}" declared more than once.`,
|
|
3543
3543
|
didYouMean: (got, suggestion) => `Module "${got}" is not declared. Did you mean "${suggestion}"?`,
|
|
3544
3544
|
syntaxError: (detail) => `Syntax error: ${detail}`,
|
|
3545
|
-
emptyDiagram: () => `Diagram is empty \u2014 no connections found
|
|
3545
|
+
emptyDiagram: () => `Diagram is empty \u2014 no connections found.`,
|
|
3546
|
+
ambiguousPortDirection: (port, module) => `Port "${port}" on module "${module}" is used as both input and output. Use distinct names like "In ${port}"/"Out ${port}" to disambiguate.`
|
|
3546
3547
|
};
|
|
3547
3548
|
function editDistance(a, b) {
|
|
3548
3549
|
const la = a.length;
|
|
@@ -3852,6 +3853,39 @@ function parse(input) {
|
|
|
3852
3853
|
}
|
|
3853
3854
|
}
|
|
3854
3855
|
}
|
|
3856
|
+
const portUsageMap = /* @__PURE__ */ new Map();
|
|
3857
|
+
const allConns = [...forward, ...feedback];
|
|
3858
|
+
for (const conn of allConns) {
|
|
3859
|
+
const srcBlock = conn.source.blockId;
|
|
3860
|
+
if (!portUsageMap.has(srcBlock)) portUsageMap.set(srcBlock, /* @__PURE__ */ new Map());
|
|
3861
|
+
const srcPorts = portUsageMap.get(srcBlock);
|
|
3862
|
+
if (!srcPorts.has(conn.source.portId)) srcPorts.set(conn.source.portId, { asSource: [], asTarget: [] });
|
|
3863
|
+
srcPorts.get(conn.source.portId).asSource.push(0);
|
|
3864
|
+
const tgtBlock = conn.target.blockId;
|
|
3865
|
+
if (!portUsageMap.has(tgtBlock)) portUsageMap.set(tgtBlock, /* @__PURE__ */ new Map());
|
|
3866
|
+
const tgtPorts = portUsageMap.get(tgtBlock);
|
|
3867
|
+
if (!tgtPorts.has(conn.target.portId)) tgtPorts.set(conn.target.portId, { asSource: [], asTarget: [] });
|
|
3868
|
+
tgtPorts.get(conn.target.portId).asTarget.push(0);
|
|
3869
|
+
}
|
|
3870
|
+
for (const [blockId, portMap] of portUsageMap) {
|
|
3871
|
+
for (const [portId, usage] of portMap) {
|
|
3872
|
+
if (usage.asSource.length > 0 && usage.asTarget.length > 0) {
|
|
3873
|
+
const block = allBlocks.get(blockId);
|
|
3874
|
+
const blockLabel = block ? block.label : blockId;
|
|
3875
|
+
const srcConn = allConns.find((c) => c.source.blockId === blockId && c.source.portId === portId);
|
|
3876
|
+
const tgtConn = allConns.find((c) => c.target.blockId === blockId && c.target.portId === portId);
|
|
3877
|
+
const portDisplay = srcConn ? srcConn.source.portDisplay : tgtConn ? tgtConn.target.portDisplay : portId;
|
|
3878
|
+
warnings.push({
|
|
3879
|
+
code: "AMBIGUOUS_PORT_DIRECTION",
|
|
3880
|
+
message: errorMessages.ambiguousPortDirection(portDisplay, blockLabel),
|
|
3881
|
+
line: 0,
|
|
3882
|
+
column: 1,
|
|
3883
|
+
length: 1,
|
|
3884
|
+
severity: "warning"
|
|
3885
|
+
});
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3855
3889
|
const graph = {
|
|
3856
3890
|
declaredBlocks: declaredBlocksList,
|
|
3857
3891
|
stubBlocks,
|
|
@@ -4068,23 +4102,33 @@ function collectPorts(blocks, connections) {
|
|
|
4068
4102
|
}
|
|
4069
4103
|
return result;
|
|
4070
4104
|
}
|
|
4105
|
+
function startYForFace(blockY, blockHeight, portCount) {
|
|
4106
|
+
const topMargin = 40;
|
|
4107
|
+
const bottomMargin = 30;
|
|
4108
|
+
const topY = blockY + topMargin;
|
|
4109
|
+
const bottomY = blockY + blockHeight - bottomMargin;
|
|
4110
|
+
const availableHeight = bottomY - topY;
|
|
4111
|
+
const clusterHeight = Math.max(0, (portCount - 1) * 24);
|
|
4112
|
+
return topY + (availableHeight - clusterHeight) / 2;
|
|
4113
|
+
}
|
|
4071
4114
|
function placePorts(block, ports) {
|
|
4072
4115
|
const inPorts = ports.filter((p) => p.direction === "in");
|
|
4073
4116
|
const outPorts = ports.filter((p) => p.direction === "out");
|
|
4074
4117
|
const layoutPorts = [];
|
|
4075
|
-
const startY = block.y + 40 + (block.subLabel ? 18 : 0);
|
|
4076
4118
|
const spacing = 24;
|
|
4119
|
+
const inStartY = startYForFace(block.y, block.height, inPorts.length);
|
|
4077
4120
|
inPorts.forEach((p, i) => {
|
|
4078
4121
|
layoutPorts.push({
|
|
4079
4122
|
...p,
|
|
4080
|
-
position: { x: block.x, y:
|
|
4123
|
+
position: { x: block.x, y: inStartY + i * spacing },
|
|
4081
4124
|
signalType: null
|
|
4082
4125
|
});
|
|
4083
4126
|
});
|
|
4127
|
+
const outStartY = startYForFace(block.y, block.height, outPorts.length);
|
|
4084
4128
|
outPorts.forEach((p, i) => {
|
|
4085
4129
|
layoutPorts.push({
|
|
4086
4130
|
...p,
|
|
4087
|
-
position: { x: block.x + block.width, y:
|
|
4131
|
+
position: { x: block.x + block.width, y: outStartY + i * spacing },
|
|
4088
4132
|
signalType: null
|
|
4089
4133
|
});
|
|
4090
4134
|
});
|
|
@@ -4127,7 +4171,7 @@ function findPortPosition(block, portId, direction) {
|
|
|
4127
4171
|
}
|
|
4128
4172
|
function layout(graph, options = {}) {
|
|
4129
4173
|
const direction = options.direction ?? "LR";
|
|
4130
|
-
const rankSep = options.rankSep ??
|
|
4174
|
+
const rankSep = options.rankSep ?? 120;
|
|
4131
4175
|
const nodeSep = options.nodeSep ?? 40;
|
|
4132
4176
|
const allBlocks = [...graph.declaredBlocks, ...graph.stubBlocks];
|
|
4133
4177
|
const allConnections = [...graph.connections, ...graph.feedbackEdges];
|
|
@@ -4372,7 +4416,28 @@ var SIGNAL_PILL_LABEL = {
|
|
|
4372
4416
|
trigger: "trig",
|
|
4373
4417
|
clock: "clk"
|
|
4374
4418
|
};
|
|
4375
|
-
|
|
4419
|
+
var UPWARD_CABLE_THRESHOLD = 20;
|
|
4420
|
+
function computeLabelBelowMap(connections) {
|
|
4421
|
+
const map = /* @__PURE__ */ new Map();
|
|
4422
|
+
for (const conn of connections) {
|
|
4423
|
+
const srcKey = `${conn.source.blockId}:${conn.source.portId}:out`;
|
|
4424
|
+
const srcDy = conn.targetPoint.y - conn.sourcePoint.y;
|
|
4425
|
+
if (srcDy < -UPWARD_CABLE_THRESHOLD) {
|
|
4426
|
+
map.set(srcKey, true);
|
|
4427
|
+
} else if (!map.has(srcKey)) {
|
|
4428
|
+
map.set(srcKey, false);
|
|
4429
|
+
}
|
|
4430
|
+
const tgtKey = `${conn.target.blockId}:${conn.target.portId}:in`;
|
|
4431
|
+
const tgtDy = conn.sourcePoint.y - conn.targetPoint.y;
|
|
4432
|
+
if (conn.isFeedback || tgtDy > UPWARD_CABLE_THRESHOLD) {
|
|
4433
|
+
map.set(tgtKey, true);
|
|
4434
|
+
} else if (!map.has(tgtKey)) {
|
|
4435
|
+
map.set(tgtKey, false);
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
return map;
|
|
4439
|
+
}
|
|
4440
|
+
function buildLabels(theme, blocks, connections) {
|
|
4376
4441
|
const parts = [];
|
|
4377
4442
|
const fontFamily = sanitizeForSvg(theme.port.fontFamily);
|
|
4378
4443
|
const pillShow = theme.port.pill.show;
|
|
@@ -4381,79 +4446,89 @@ function buildLabels(theme, blocks) {
|
|
|
4381
4446
|
const pillRadius = theme.port.pill.cornerRadius;
|
|
4382
4447
|
const pillPadX = 3;
|
|
4383
4448
|
const pillHeight = 11;
|
|
4384
|
-
const pillGap = 6;
|
|
4385
4449
|
const charWidth = 6.5;
|
|
4450
|
+
const pillOffsetAbove = 20;
|
|
4451
|
+
const nameOffsetAbove = 32;
|
|
4452
|
+
const pillOffsetBelow = 20;
|
|
4453
|
+
const nameOffsetBelow = 32;
|
|
4454
|
+
const belowMap = computeLabelBelowMap(connections);
|
|
4455
|
+
const labelOffsetX = 6;
|
|
4386
4456
|
for (const block of blocks) {
|
|
4387
4457
|
for (const port of block.ports) {
|
|
4388
4458
|
const { x, y } = port.position;
|
|
4389
|
-
const
|
|
4390
|
-
const
|
|
4391
|
-
const labelY = y + 3;
|
|
4392
|
-
const anchor = isOut ? "start" : "end";
|
|
4459
|
+
const key = `${block.id}:${port.id}:${port.direction}`;
|
|
4460
|
+
const below = belowMap.get(key) === true;
|
|
4393
4461
|
const display = sanitizeForSvg(port.display);
|
|
4394
|
-
const
|
|
4462
|
+
const isOutput = port.direction === "out";
|
|
4463
|
+
const textY = below ? y + nameOffsetBelow : y - nameOffsetAbove;
|
|
4464
|
+
const textX = isOutput ? x + labelOffsetX : x - labelOffsetX;
|
|
4465
|
+
const textAnchor = isOutput ? "start" : "end";
|
|
4395
4466
|
parts.push(
|
|
4396
|
-
`<text x="${
|
|
4467
|
+
`<text x="${textX}" y="${textY}" font-family="${fontFamily}" font-size="${theme.port.fontSize}" fill="${theme.port.labelColor}" font-weight="600" text-anchor="${textAnchor}" dominant-baseline="central">${display}</text>`
|
|
4397
4468
|
);
|
|
4398
4469
|
if (pillShow && port.signalType) {
|
|
4399
4470
|
const pillText = SIGNAL_PILL_LABEL[port.signalType];
|
|
4400
4471
|
const pillWidth = pillText.length * charWidth + pillPadX * 2;
|
|
4401
4472
|
const pillColor = theme.cable.colors[port.signalType].stroke;
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
pillX = labelX - labelWidth - pillGap - pillWidth;
|
|
4407
|
-
}
|
|
4408
|
-
const pillY = y - pillHeight / 2;
|
|
4409
|
-
const textX = pillX + pillWidth / 2;
|
|
4410
|
-
const textY = pillY + pillHeight / 2 + pillFontSize / 2 - 1;
|
|
4473
|
+
const pillCenterY = below ? y + pillOffsetBelow : y - pillOffsetAbove;
|
|
4474
|
+
const pillX = isOutput ? x + labelOffsetX : x - labelOffsetX - pillWidth;
|
|
4475
|
+
const pillY = pillCenterY - pillHeight / 2;
|
|
4476
|
+
const pillTextX = pillX + pillWidth / 2;
|
|
4411
4477
|
parts.push(
|
|
4412
4478
|
`<rect class="pf-port-pill" x="${pillX}" y="${pillY}" width="${pillWidth}" height="${pillHeight}" rx="${pillRadius}" fill="${pillColor}" data-signal="${port.signalType}"/>`
|
|
4413
4479
|
);
|
|
4414
4480
|
parts.push(
|
|
4415
|
-
`<text class="pf-port-pill-text" x="${
|
|
4481
|
+
`<text class="pf-port-pill-text" x="${pillTextX}" y="${pillCenterY}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamily}" font-size="${pillFontSize}" fill="${pillTextColor}" font-weight="600">${sanitizeForSvg(pillText)}</text>`
|
|
4416
4482
|
);
|
|
4417
4483
|
}
|
|
4418
4484
|
}
|
|
4419
4485
|
}
|
|
4420
4486
|
return parts.join("");
|
|
4421
4487
|
}
|
|
4422
|
-
function buildAnnotations(theme, connections) {
|
|
4488
|
+
function buildAnnotations(theme, connections, layoutHeight) {
|
|
4489
|
+
const annotated = connections.filter((c) => c.annotation);
|
|
4490
|
+
if (annotated.length === 0) return "";
|
|
4423
4491
|
const parts = [];
|
|
4424
4492
|
const fontFamily = sanitizeForSvg(theme.annotation.fontFamily);
|
|
4425
|
-
const
|
|
4426
|
-
const
|
|
4427
|
-
|
|
4428
|
-
|
|
4493
|
+
const noteFontSize = theme.annotation.fontSize + 2;
|
|
4494
|
+
const markerFontFamily = fontFamily;
|
|
4495
|
+
annotated.forEach((conn, i) => {
|
|
4496
|
+
const num = i + 1;
|
|
4429
4497
|
const sx = conn.sourcePoint.x;
|
|
4430
4498
|
const sy = conn.sourcePoint.y;
|
|
4431
4499
|
const tx = conn.targetPoint.x;
|
|
4432
4500
|
const ty = conn.targetPoint.y;
|
|
4433
|
-
let
|
|
4434
|
-
let
|
|
4501
|
+
let mx;
|
|
4502
|
+
let my;
|
|
4435
4503
|
if (conn.isFeedback) {
|
|
4436
|
-
|
|
4437
|
-
|
|
4504
|
+
mx = (sx + tx) / 2;
|
|
4505
|
+
const match = conn.path.match(/L\s+\S+\s+\S+\s+L\s+\S+\s+(\S+)/);
|
|
4506
|
+
my = match ? parseFloat(match[1]) : Math.max(sy, ty) + 30;
|
|
4438
4507
|
} else {
|
|
4439
|
-
|
|
4440
|
-
|
|
4508
|
+
mx = (sx + tx) / 2;
|
|
4509
|
+
my = (sy + ty) / 2;
|
|
4441
4510
|
}
|
|
4442
|
-
const
|
|
4443
|
-
let raw = conn.annotation;
|
|
4444
|
-
if (!conn.isFeedback) {
|
|
4445
|
-
const available = Math.abs(tx - sx);
|
|
4446
|
-
const prefixWidth = prefix.length * charWidth;
|
|
4447
|
-
const maxChars = Math.max(0, Math.floor((available - prefixWidth) / charWidth));
|
|
4448
|
-
if (maxChars > 0 && raw.length > maxChars) {
|
|
4449
|
-
raw = maxChars > 1 ? raw.slice(0, maxChars - 1) + "\u2026" : raw.slice(0, maxChars);
|
|
4450
|
-
}
|
|
4451
|
-
}
|
|
4452
|
-
const text = prefix + sanitizeForSvg(raw);
|
|
4511
|
+
const markerStroke = conn.isFeedback ? theme.cable.colors[conn.signalType].stroke : theme.annotation.color;
|
|
4453
4512
|
parts.push(
|
|
4454
|
-
`<
|
|
4513
|
+
`<circle cx="${mx}" cy="${my}" r="8" fill="${theme.panel.highlight}" stroke="${markerStroke}" stroke-width="0.5" data-annotation-marker="${num}"/>`
|
|
4455
4514
|
);
|
|
4456
|
-
|
|
4515
|
+
parts.push(
|
|
4516
|
+
`<text x="${mx}" y="${my + 3}" text-anchor="middle" font-family="${markerFontFamily}" font-size="9" fill="${theme.annotation.color}">${num}</text>`
|
|
4517
|
+
);
|
|
4518
|
+
});
|
|
4519
|
+
const panelX = -120;
|
|
4520
|
+
const lineGap = 16;
|
|
4521
|
+
const noteCount = annotated.length;
|
|
4522
|
+
const bottomY = layoutHeight - 10;
|
|
4523
|
+
const firstNoteY = bottomY - (noteCount - 1) * lineGap;
|
|
4524
|
+
annotated.forEach((conn, i) => {
|
|
4525
|
+
const num = i + 1;
|
|
4526
|
+
const noteText = `${num}. ${sanitizeForSvg(conn.annotation)}`;
|
|
4527
|
+
const noteY = firstNoteY + i * lineGap;
|
|
4528
|
+
parts.push(
|
|
4529
|
+
`<text x="${panelX}" y="${noteY}" font-family="${fontFamily}" font-size="${noteFontSize}" font-weight="600" fill="${theme.annotation.color}">${noteText}</text>`
|
|
4530
|
+
);
|
|
4531
|
+
});
|
|
4457
4532
|
return parts.join("");
|
|
4458
4533
|
}
|
|
4459
4534
|
function buildLegend(theme, layoutResult) {
|
|
@@ -4463,16 +4538,18 @@ function buildLegend(theme, layoutResult) {
|
|
|
4463
4538
|
const parts = [];
|
|
4464
4539
|
const fontFamily = sanitizeForSvg(theme.annotation.fontFamily);
|
|
4465
4540
|
const itemWidth = 70;
|
|
4541
|
+
const totalWidth = used.length * itemWidth;
|
|
4542
|
+
const legendStartX = layoutResult.width - totalWidth;
|
|
4466
4543
|
const y = layoutResult.height - 20;
|
|
4467
|
-
let
|
|
4468
|
-
|
|
4544
|
+
for (let i = 0; i < used.length; i++) {
|
|
4545
|
+
const sig = used[i];
|
|
4469
4546
|
const color = theme.cable.colors[sig].stroke;
|
|
4547
|
+
const x = legendStartX + i * itemWidth;
|
|
4470
4548
|
let g = `<g transform="translate(${x}, ${y})">`;
|
|
4471
4549
|
g += `<line x1="0" y1="0" x2="20" y2="0" stroke="${color}" stroke-width="3" stroke-linecap="round"/>`;
|
|
4472
4550
|
g += `<text x="26" y="3" font-family="${fontFamily}" font-size="9" fill="${theme.annotation.color}">${sig}</text>`;
|
|
4473
4551
|
g += `</g>`;
|
|
4474
4552
|
parts.push(g);
|
|
4475
|
-
x += itemWidth;
|
|
4476
4553
|
}
|
|
4477
4554
|
return parts.join("");
|
|
4478
4555
|
}
|
|
@@ -4501,14 +4578,19 @@ function renderSvg(layoutResult, theme) {
|
|
|
4501
4578
|
`<g class="pf-layer-panels" >${buildPanels(theme, idPrefix, layoutResult.blocks)}</g>`,
|
|
4502
4579
|
`<g class="pf-layer-params">${buildParams(layoutResult.blocks, theme)}</g>`,
|
|
4503
4580
|
`<g class="pf-layer-jacks">${buildJacks(theme, idPrefix, layoutResult.blocks)}</g>`,
|
|
4504
|
-
`<g class="pf-layer-labels">${buildLabels(theme, layoutResult.blocks)}</g>`,
|
|
4505
|
-
`<g class="pf-layer-annotations">${buildAnnotations(theme, layoutResult.connections)}</g>`,
|
|
4581
|
+
`<g class="pf-layer-labels">${buildLabels(theme, layoutResult.blocks, layoutResult.connections)}</g>`,
|
|
4582
|
+
`<g class="pf-layer-annotations">${buildAnnotations(theme, layoutResult.connections, height)}</g>`,
|
|
4506
4583
|
`<g class="pf-layer-legend">${buildLegend(theme, layoutResult)}</g>`
|
|
4507
4584
|
].join("");
|
|
4508
4585
|
const style = `<style>@media print { .pf-panel, .pf-jack { filter: none; } }</style>`;
|
|
4509
4586
|
const labelPadX = 130;
|
|
4510
4587
|
const vbWidth = width + labelPadX * 2;
|
|
4511
|
-
const
|
|
4588
|
+
const topPad = 40;
|
|
4589
|
+
const noteCount = layoutResult.connections.filter((c) => c.annotation).length;
|
|
4590
|
+
const notesHeight = noteCount > 0 ? noteCount * 16 + 10 : 0;
|
|
4591
|
+
const bottomPad = Math.max(40, notesHeight + 10);
|
|
4592
|
+
const vbHeight = height + topPad + bottomPad;
|
|
4593
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${-labelPadX} ${-topPad} ${vbWidth} ${vbHeight}" width="100%" data-pf-min-width="${minWidth + labelPadX * 2}" role="img" aria-labelledby="${idPrefix}-title ${idPrefix}-desc"><title id="${idPrefix}-title">Patch diagram</title><desc id="${idPrefix}-desc">${desc}</desc>` + style + `<defs>${defsParts.join("")}</defs>` + layers + `</svg>`;
|
|
4512
4594
|
return svg;
|
|
4513
4595
|
}
|
|
4514
4596
|
|
|
@@ -4571,7 +4653,7 @@ var defaultTheme = {
|
|
|
4571
4653
|
annotation: {
|
|
4572
4654
|
fontFamily: "'SF Mono', 'Fira Code', Consolas, 'Courier New', monospace",
|
|
4573
4655
|
fontSize: 9,
|
|
4574
|
-
color: "#
|
|
4656
|
+
color: "#4a4a4a",
|
|
4575
4657
|
haloColor: "#f7f5f0"
|
|
4576
4658
|
},
|
|
4577
4659
|
grid: {
|