@ogabrielluiz/patchflow 0.1.3 → 0.1.5
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-4VUBNFI4.js → chunk-YONEHQMI.js} +1 -1
- package/dist/chunk-YONEHQMI.js.map +1 -0
- package/dist/index.cjs +45 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +46 -17
- package/dist/index.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +6 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-4VUBNFI4.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts"],"sourcesContent":["// ── Signal Types ──\n\nexport type SignalType = 'audio' | 'cv' | 'pitch' | 'gate' | 'trigger' | 'clock';\n\nexport const SIGNAL_OPERATORS: Record<string, SignalType> = {\n '->': 'audio',\n '>>': 'cv',\n 'p>': 'pitch',\n 'g>': 'gate',\n 't>': 'trigger',\n 'c>': 'clock',\n};\n\n// ── Graph Primitives ──\n\nexport interface Port {\n id: string; // normalized key (lowercase, trimmed)\n display: string; // original form for rendering\n direction: 'in' | 'out';\n}\n\nexport interface Param {\n key: string;\n value: string;\n}\n\nexport interface Block {\n id: string;\n label: string;\n subLabel: string | null;\n params: Param[];\n ports: Port[];\n parentModule: string | null; // null for top-level modules, module name for sections\n voice: string | null;\n}\n\nexport interface ConnectionEndpoint {\n blockId: string;\n portId: string; // normalized key\n portDisplay: string; // original form\n}\n\nexport interface Connection {\n id: string;\n source: ConnectionEndpoint;\n target: ConnectionEndpoint;\n signalType: SignalType;\n annotation: string | null;\n graphvizExtras: Record<string, string> | null;\n}\n\n// ── Patch Graph (Parser Output) ──\n\nexport interface PatchGraph {\n declaredBlocks: Block[];\n stubBlocks: Block[];\n connections: Connection[];\n feedbackEdges: Connection[];\n signalTypeStats: Partial<Record<SignalType, number>>;\n voices: string[];\n}\n\n// ── Layout Types ──\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface LayoutPort extends Port {\n position: Point;\n signalType: SignalType | null;\n}\n\nexport interface LayoutBlock {\n id: string;\n label: string;\n subLabel: string | null;\n params: Param[];\n ports: LayoutPort[];\n parentModule: string | null;\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface LayoutConnection {\n id: string;\n source: ConnectionEndpoint;\n target: ConnectionEndpoint;\n signalType: SignalType;\n annotation: string | null;\n path: string; // SVG path d attribute\n isFeedback: boolean;\n sourcePoint: Point;\n targetPoint: Point;\n}\n\nexport interface LayoutResult {\n blocks: LayoutBlock[];\n connections: LayoutConnection[];\n width: number;\n height: number;\n signalTypeStats: Partial<Record<SignalType, number>>;\n /**\n * Non-fatal diagnostics produced during layout. Currently used to surface\n * internal consistency issues (e.g. computed height doesn't cover the\n * actual block content) so future regressions are visible.\n */\n warnings?: string[];\n}\n\n// ── Theme Types ──\n\nexport interface CableColor {\n stroke: string;\n plugTip: string;\n}\n\nexport interface SocketColors {\n bezel: string;\n bezelStroke: string;\n ring: string;\n hole: string;\n pin: string;\n}\n\nexport interface Theme {\n background: string;\n panel: {\n fill: string;\n stroke: string;\n highlight: string;\n shadow: string;\n cornerRadius: number;\n shadowBlur: number;\n shadowOpacity: number;\n bevelWidth: number;\n };\n label: {\n fontFamily: string;\n color: string;\n subColor: string;\n plateFill: string;\n plateStroke: string;\n };\n param: {\n plateFill: string;\n plateStroke: string;\n textColor: string;\n };\n port: {\n fontFamily: string;\n fontSize: number;\n colors: SocketColors;\n hideSocket: boolean;\n labelColor: string;\n pill: {\n show: boolean;\n fontSize: number;\n textColor: string;\n cornerRadius: number;\n };\n };\n cable: {\n width: number;\n colors: Record<SignalType, CableColor>;\n plugTipRadius: number;\n };\n annotation: {\n fontFamily: string;\n fontSize: number;\n color: string;\n haloColor: string;\n };\n grid: {\n dotColor: string;\n dotRadius: number;\n spacing: number;\n opacity: number;\n } | null;\n}\n\n// ── Options ──\n\nexport interface RenderOptions {\n theme?: DeepPartial<Theme>;\n maxWidth?: number;\n padding?: number;\n legend?: boolean | 'auto';\n}\n\nexport interface LayoutOptions {\n direction?: 'LR' | 'TB';\n nodeSep?: number;\n rankSep?: number;\n feedbackSide?: 'top' | 'bottom';\n}\n\n// ── Parse Result ──\n\nexport type ErrorCode =\n | 'SYNTAX_ERROR'\n | 'UNKNOWN_OPERATOR'\n | 'MISSING_PORT'\n | 'UNCLOSED_PAREN'\n | 'DUPLICATE_MODULE'\n | 'UNKNOWN_MODULE'\n | 'INVALID_PORT'\n | 'AMBIGUOUS_PORT_DIRECTION';\n\nexport type ErrorSeverity = 'error' | 'warning';\n\nexport interface ParseDiagnostic {\n code: ErrorCode;\n message: string;\n line: number;\n column: number;\n length: number;\n severity: ErrorSeverity;\n}\n\nexport interface ParseResult {\n graph: PatchGraph;\n errors: ParseDiagnostic[];\n warnings: ParseDiagnostic[];\n}\n\n// ── Utility Types ──\n\nexport type DeepPartial<T> = {\n [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAIO,IAAM,mBAA+C;AAAA,EAC1D,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AACR;","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -4091,7 +4091,7 @@ function selfLoopArcPath(source, target, blockBottom, arcOffset) {
|
|
|
4091
4091
|
var MIN_WIDTH = 140;
|
|
4092
4092
|
var MIN_HEIGHT = 90;
|
|
4093
4093
|
function getBlockDimensions(block, portCount) {
|
|
4094
|
-
const labelWidth = block.label.length *
|
|
4094
|
+
const labelWidth = block.label.length * 11;
|
|
4095
4095
|
const subLabelWidth = block.subLabel ? block.subLabel.length * 7 : 0;
|
|
4096
4096
|
const paramWidths = block.params.map((p) => `${p.key}: ${p.value}`.length * 7);
|
|
4097
4097
|
const longestParam = paramWidths.length > 0 ? Math.max(...paramWidths) : 0;
|
|
@@ -4333,19 +4333,46 @@ function layout(graph, options = {}) {
|
|
|
4333
4333
|
const feedbackSpace = hasFeedback ? diagramBottom - maxY + feedbackArcOffset + 30 + 16 : 0;
|
|
4334
4334
|
const width = maxX + margin;
|
|
4335
4335
|
const height = maxY + margin + feedbackSpace;
|
|
4336
|
+
const warnings = checkHeightInvariant({
|
|
4337
|
+
blocks: layoutBlocks,
|
|
4338
|
+
height,
|
|
4339
|
+
hasFeedback,
|
|
4340
|
+
feedbackBottom: diagramBottom + feedbackArcOffset
|
|
4341
|
+
});
|
|
4336
4342
|
return {
|
|
4337
4343
|
blocks: layoutBlocks,
|
|
4338
4344
|
connections: layoutConnections,
|
|
4339
4345
|
width,
|
|
4340
4346
|
height,
|
|
4341
|
-
signalTypeStats: graph.signalTypeStats
|
|
4347
|
+
signalTypeStats: graph.signalTypeStats,
|
|
4348
|
+
warnings
|
|
4342
4349
|
};
|
|
4343
4350
|
}
|
|
4351
|
+
function checkHeightInvariant(args) {
|
|
4352
|
+
const { blocks, height, hasFeedback, feedbackBottom } = args;
|
|
4353
|
+
const warnings = [];
|
|
4354
|
+
const blockBottomMax = blocks.length > 0 ? Math.max(...blocks.map((b) => b.y + b.height)) : 0;
|
|
4355
|
+
const contentBottom = hasFeedback ? Math.max(blockBottomMax, feedbackBottom) : blockBottomMax;
|
|
4356
|
+
if (height < contentBottom) {
|
|
4357
|
+
warnings.push(
|
|
4358
|
+
`layout: computed height (${height.toFixed(1)}) is below content bottom (${contentBottom.toFixed(1)}); legend/notes may overlap block content`
|
|
4359
|
+
);
|
|
4360
|
+
}
|
|
4361
|
+
return warnings;
|
|
4362
|
+
}
|
|
4344
4363
|
|
|
4345
4364
|
// src/renderer.ts
|
|
4346
4365
|
function genId() {
|
|
4347
4366
|
return "pf-" + Math.random().toString(16).slice(2, 8);
|
|
4348
4367
|
}
|
|
4368
|
+
function fitLabel(label, maxWidth, charWidth) {
|
|
4369
|
+
if (maxWidth <= 0 || charWidth <= 0) return label;
|
|
4370
|
+
const maxChars = Math.floor(maxWidth / charWidth);
|
|
4371
|
+
if (maxChars <= 0) return label;
|
|
4372
|
+
if (label.length <= maxChars) return label;
|
|
4373
|
+
if (maxChars === 1) return "\u2026";
|
|
4374
|
+
return label.slice(0, maxChars - 1) + "\u2026";
|
|
4375
|
+
}
|
|
4349
4376
|
function buildDesc(layoutResult) {
|
|
4350
4377
|
const blockCount = layoutResult.blocks.length;
|
|
4351
4378
|
const connCount = layoutResult.connections.length;
|
|
@@ -4391,11 +4418,11 @@ function buildPanels(theme, idPrefix, blocks) {
|
|
|
4391
4418
|
const parts = [];
|
|
4392
4419
|
for (const block of blocks) {
|
|
4393
4420
|
const moduleName = sanitizeForSvg(block.parentModule || block.label);
|
|
4394
|
-
const label = sanitizeForSvg(block.label);
|
|
4395
4421
|
const fontFamily = sanitizeForSvg(theme.label.fontFamily);
|
|
4396
4422
|
const insetX = block.x + 12;
|
|
4397
4423
|
const insetY = block.y + 8;
|
|
4398
4424
|
const insetW = block.width - 24;
|
|
4425
|
+
const label = sanitizeForSvg(fitLabel(block.label, insetW, 11));
|
|
4399
4426
|
let group = `<g data-module="${moduleName}" filter="url(#${idPrefix}-panel-shadow)">`;
|
|
4400
4427
|
group += `<rect x="${block.x}" y="${block.y}" width="${block.width}" height="${block.height}" fill="${theme.panel.fill}" stroke="${theme.panel.stroke}" stroke-width="0.75" rx="${theme.panel.cornerRadius}"/>`;
|
|
4401
4428
|
group += `<line x1="${block.x}" y1="${block.y + 0.5}" x2="${block.x + block.width}" y2="${block.y + 0.5}" stroke="${theme.panel.highlight}" stroke-width="${theme.panel.bevelWidth}"/>`;
|
|
@@ -4403,7 +4430,7 @@ function buildPanels(theme, idPrefix, blocks) {
|
|
|
4403
4430
|
group += `<rect x="${insetX}" y="${insetY}" width="${insetW}" height="28" fill="${theme.label.plateFill}" stroke="${theme.label.plateStroke}" stroke-width="0.5"/>`;
|
|
4404
4431
|
group += `<text x="${block.x + block.width / 2}" y="${block.y + 22}" text-anchor="middle" font-family="${fontFamily}" font-size="14" font-weight="700" fill="${theme.label.color}" letter-spacing="3">${label}</text>`;
|
|
4405
4432
|
if (block.subLabel) {
|
|
4406
|
-
const subLabel = sanitizeForSvg(block.subLabel);
|
|
4433
|
+
const subLabel = sanitizeForSvg(fitLabel(block.subLabel, insetW - 8, 7));
|
|
4407
4434
|
const barX = insetX;
|
|
4408
4435
|
const barY = insetY + 28;
|
|
4409
4436
|
const barW = insetW;
|
|
@@ -4429,7 +4456,8 @@ function buildParams(blocks, theme) {
|
|
|
4429
4456
|
`<rect x="${px}" y="${py}" width="${pw}" height="20" fill="${theme.param.plateFill}" stroke="${theme.param.plateStroke}" stroke-width="0.5"/>`
|
|
4430
4457
|
);
|
|
4431
4458
|
const keyNorm = param.key.trim().toLowerCase();
|
|
4432
|
-
const
|
|
4459
|
+
const rawText = keyNorm === blockLabelNorm ? param.value : `${param.key}: ${param.value}`;
|
|
4460
|
+
const text = sanitizeForSvg(fitLabel(rawText, pw - 8, 7));
|
|
4433
4461
|
parts.push(
|
|
4434
4462
|
`<text x="${px + pw / 2}" y="${py + 14}" text-anchor="middle" font-family="${monoFont}" font-size="10" fill="${theme.param.textColor}">${text}</text>`
|
|
4435
4463
|
);
|
|
@@ -4580,7 +4608,7 @@ function buildAnnotations(theme, connections, layoutHeight) {
|
|
|
4580
4608
|
});
|
|
4581
4609
|
return parts.join("");
|
|
4582
4610
|
}
|
|
4583
|
-
function buildLegend(theme, layoutResult) {
|
|
4611
|
+
function buildLegend(theme, layoutResult, diagramBottom) {
|
|
4584
4612
|
const order = ["audio", "cv", "pitch", "gate", "trigger", "clock"];
|
|
4585
4613
|
const used = order.filter((t) => (layoutResult.signalTypeStats[t] ?? 0) > 0);
|
|
4586
4614
|
if (used.length === 0) return "";
|
|
@@ -4589,7 +4617,7 @@ function buildLegend(theme, layoutResult) {
|
|
|
4589
4617
|
const itemWidth = 70;
|
|
4590
4618
|
const totalWidth = used.length * itemWidth;
|
|
4591
4619
|
const legendStartX = layoutResult.width - totalWidth;
|
|
4592
|
-
const y =
|
|
4620
|
+
const y = diagramBottom - 20;
|
|
4593
4621
|
for (let i = 0; i < used.length; i++) {
|
|
4594
4622
|
const sig = used[i];
|
|
4595
4623
|
const color = theme.cable.colors[sig].stroke;
|
|
@@ -4621,6 +4649,14 @@ function renderSvg(layoutResult, theme) {
|
|
|
4621
4649
|
`<pattern id="${idPrefix}-dots" width="${spacing}" height="${spacing}" patternUnits="userSpaceOnUse"><circle cx="${spacing / 2}" cy="${spacing / 2}" r="${theme.grid.dotRadius}" fill="${theme.grid.dotColor}" opacity="${theme.grid.opacity}"/></pattern>`
|
|
4622
4650
|
);
|
|
4623
4651
|
}
|
|
4652
|
+
const labelPadX = 130;
|
|
4653
|
+
const vbWidth = width + labelPadX * 2;
|
|
4654
|
+
const topPad = 40;
|
|
4655
|
+
const noteCount = layoutResult.connections.filter((c) => c.annotation).length;
|
|
4656
|
+
const notesHeight = noteCount > 0 ? noteCount * 16 + 10 : 0;
|
|
4657
|
+
const bottomPad = Math.max(40, notesHeight + 10);
|
|
4658
|
+
const vbHeight = height + topPad + bottomPad;
|
|
4659
|
+
const diagramBottom = height + bottomPad;
|
|
4624
4660
|
const layers = [
|
|
4625
4661
|
`<g class="pf-layer-bg">${buildBackground(theme, idPrefix, width, height)}</g>`,
|
|
4626
4662
|
`<g class="pf-layer-cables">${buildCables(theme, layoutResult.connections)}</g>`,
|
|
@@ -4628,17 +4664,10 @@ function renderSvg(layoutResult, theme) {
|
|
|
4628
4664
|
`<g class="pf-layer-params">${buildParams(layoutResult.blocks, theme)}</g>`,
|
|
4629
4665
|
`<g class="pf-layer-jacks">${buildJacks(theme, idPrefix, layoutResult.blocks)}</g>`,
|
|
4630
4666
|
`<g class="pf-layer-labels">${buildLabels(theme, layoutResult.blocks, layoutResult.connections)}</g>`,
|
|
4631
|
-
`<g class="pf-layer-annotations">${buildAnnotations(theme, layoutResult.connections,
|
|
4632
|
-
`<g class="pf-layer-legend">${buildLegend(theme, layoutResult)}</g>`
|
|
4667
|
+
`<g class="pf-layer-annotations">${buildAnnotations(theme, layoutResult.connections, diagramBottom)}</g>`,
|
|
4668
|
+
`<g class="pf-layer-legend">${buildLegend(theme, layoutResult, diagramBottom)}</g>`
|
|
4633
4669
|
].join("");
|
|
4634
4670
|
const style = `<style>@media print { .pf-panel, .pf-jack { filter: none; } }</style>`;
|
|
4635
|
-
const labelPadX = 130;
|
|
4636
|
-
const vbWidth = width + labelPadX * 2;
|
|
4637
|
-
const topPad = 40;
|
|
4638
|
-
const noteCount = layoutResult.connections.filter((c) => c.annotation).length;
|
|
4639
|
-
const notesHeight = noteCount > 0 ? noteCount * 16 + 10 : 0;
|
|
4640
|
-
const bottomPad = Math.max(40, notesHeight + 10);
|
|
4641
|
-
const vbHeight = height + topPad + bottomPad;
|
|
4642
4671
|
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>`;
|
|
4643
4672
|
return svg;
|
|
4644
4673
|
}
|