@transitrix/diagrams 1.10.0 → 1.12.0
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/activities/resolver.d.ts +3 -0
- package/dist/activities/resolver.js +39 -18
- package/dist/activities/validate.js +67 -4
- package/dist/canon-resolver-utils.d.ts +3 -0
- package/dist/canon-resolver-utils.js +16 -0
- package/dist/compliance/classify.js +1 -0
- package/dist/compliance/impact.d.ts +3 -1
- package/dist/compliance/impact.js +41 -13
- package/dist/compliance/types.d.ts +6 -0
- package/dist/compliance-verification-matrix/build.d.ts +17 -0
- package/dist/compliance-verification-matrix/build.js +65 -0
- package/dist/compliance-verification-matrix/csv.d.ts +6 -0
- package/dist/compliance-verification-matrix/csv.js +41 -0
- package/dist/compliance-verification-matrix/index.d.ts +3 -0
- package/dist/compliance-verification-matrix/index.js +2 -0
- package/dist/compliance-verification-matrix/types.d.ts +75 -0
- package/dist/compliance-verification-matrix/types.js +4 -0
- package/dist/fgca/action-change-ids.d.ts +7 -0
- package/dist/fgca/action-change-ids.js +11 -0
- package/dist/fgca/parse-canonical.d.ts +17 -3
- package/dist/fgca/parse-canonical.js +44 -16
- package/dist/fgca/resolver.d.ts +6 -2
- package/dist/fgca/resolver.js +28 -6
- package/dist/fgca/validate.d.ts +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/process-blueprint/index.d.ts +3 -1
- package/dist/process-blueprint/index.js +1 -0
- package/dist/process-blueprint/layout.js +33 -29
- package/dist/process-blueprint/resolve-columns.d.ts +52 -0
- package/dist/process-blueprint/resolve-columns.js +138 -0
- package/dist/process-blueprint/types.d.ts +23 -5
- package/dist/process-blueprint/validate.d.ts +19 -1
- package/dist/process-blueprint/validate.js +73 -21
- package/dist/repo-validate/check-process-parent.d.ts +8 -0
- package/dist/repo-validate/check-process-parent.js +172 -0
- package/dist/repo-validate/index.d.ts +1 -0
- package/dist/repo-validate/index.js +1 -0
- package/dist/repo-validate/validate-repo.js +3 -0
- package/dist/webview/entry.js +26 -4
- package/dist/webview/render-fgca.d.ts +2 -0
- package/dist/webview/render-fgca.js +9 -3
- package/dist/webview/render-process.d.ts +19 -1
- package/dist/webview/render-process.js +75 -42
- package/package.json +3 -3
- package/src/activities/resolver.ts +40 -20
- package/src/activities/validate.ts +66 -4
- package/src/canon-resolver-utils.ts +19 -0
- package/src/compliance/classify.ts +1 -0
- package/src/compliance/impact.ts +41 -12
- package/src/compliance/types.ts +6 -0
- package/src/compliance-verification-matrix/build.ts +84 -0
- package/src/compliance-verification-matrix/csv.ts +47 -0
- package/src/compliance-verification-matrix/index.ts +7 -0
- package/src/compliance-verification-matrix/types.ts +85 -0
- package/src/fgca/action-change-ids.ts +13 -0
- package/src/fgca/parse-canonical.ts +56 -15
- package/src/fgca/resolver.ts +39 -6
- package/src/fgca/validate.ts +2 -0
- package/src/index.ts +1 -0
- package/src/process-blueprint/index.ts +11 -0
- package/src/process-blueprint/layout.ts +48 -29
- package/src/process-blueprint/resolve-columns.ts +147 -0
- package/src/process-blueprint/types.ts +19 -5
- package/src/process-blueprint/validate.ts +107 -21
- package/src/repo-validate/check-process-parent.ts +184 -0
- package/src/repo-validate/index.ts +5 -0
- package/src/repo-validate/validate-repo.ts +3 -0
- package/src/webview/entry.ts +26 -4
- package/src/webview/render-fgca.ts +13 -2
- package/src/webview/render-process.ts +94 -49
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { validateRepoModel } from './validate-repo.js';
|
|
2
2
|
export { resolveRepoModel } from './resolve-model.js';
|
|
3
|
+
export {
|
|
4
|
+
checkProcessParentSemantics,
|
|
5
|
+
collectInEffectProcessParentEdges,
|
|
6
|
+
todayIsoDate,
|
|
7
|
+
} from './check-process-parent.js';
|
|
3
8
|
export type { RepoDoc, RepoFinding, RepoModelInput, ResolvedElementRecord, ResolvedRelationRecord, ResolvedRepoModel } from './types.js';
|
|
@@ -31,6 +31,7 @@ import { checkStrategyChainSemantics } from './check-strategy-chain.js';
|
|
|
31
31
|
import { checkElementHygiene } from './check-element-hygiene.js';
|
|
32
32
|
import { checkVersionedAttributes } from './check-versioned-attributes.js';
|
|
33
33
|
import { checkCandidateFields } from './check-candidate-fields.js';
|
|
34
|
+
import { checkProcessParentSemantics } from './check-process-parent.js';
|
|
34
35
|
import type { RepoDoc, RepoFinding, RepoModelInput } from './types.js';
|
|
35
36
|
|
|
36
37
|
const PScope: RepoFinding['scope'] = 'repo';
|
|
@@ -161,6 +162,7 @@ function checkReferentialIntegrity(input: RepoModelInput, findings: RepoFinding[
|
|
|
161
162
|
* realizes — BUSINESS_SERVICE → CAPABILITY (25-business-services.md)
|
|
162
163
|
* hosts — NODE → TECHNOLOGY_SERVICE (25-nodes.md / 26-technology-services.md)
|
|
163
164
|
* uses — APPLICATION → TECHNOLOGY_SERVICE (26-technology-services.md)
|
|
165
|
+
* process_parent — PROCESS → PROCESS (17-relations.md §3; REL-007/008)
|
|
164
166
|
*
|
|
165
167
|
* Implemented element checks:
|
|
166
168
|
* TSVC-003 — TECHNOLOGY_SERVICE.node must resolve to NODE notation
|
|
@@ -423,6 +425,7 @@ export function validateRepoModel(input: RepoModelInput): RepoFinding[] {
|
|
|
423
425
|
checkAtomicity(input, findings);
|
|
424
426
|
checkReferentialIntegrity(input, findings);
|
|
425
427
|
checkLayerSemantics(input, findings);
|
|
428
|
+
checkProcessParentSemantics(input, findings);
|
|
426
429
|
checkPolicy(input, findings);
|
|
427
430
|
// Phase 7 — strategy-chain semantic rules ported from DSM's Go Validate*
|
|
428
431
|
// functions (GOALS-010, ACT-006..009, FGCA-008..011).
|
package/src/webview/entry.ts
CHANGED
|
@@ -187,22 +187,44 @@ function dispatchValidate(kind: NotationKind, doc: unknown): RenderResult {
|
|
|
187
187
|
return r;
|
|
188
188
|
}
|
|
189
189
|
case 'dgca': {
|
|
190
|
-
const
|
|
190
|
+
const meta = (doc ?? {}) as Record<string, unknown>;
|
|
191
|
+
const outOfScopeGoalIds = meta['__outOfScopeGoalIds'] instanceof Set
|
|
192
|
+
? (meta['__outOfScopeGoalIds'] as Set<string>)
|
|
193
|
+
: Array.isArray(meta['__outOfScopeGoalIds'])
|
|
194
|
+
? new Set(meta['__outOfScopeGoalIds'] as string[])
|
|
195
|
+
: undefined;
|
|
196
|
+
const outOfScopeFactorIds = meta['__outOfScopeFactorIds'] instanceof Set
|
|
197
|
+
? (meta['__outOfScopeFactorIds'] as Set<string>)
|
|
198
|
+
: Array.isArray(meta['__outOfScopeFactorIds'])
|
|
199
|
+
? new Set(meta['__outOfScopeFactorIds'] as string[])
|
|
200
|
+
: undefined;
|
|
201
|
+
const v = parseCanonicalFGCA(doc, outOfScopeGoalIds, outOfScopeFactorIds);
|
|
191
202
|
const r = emptyResult(kind, v.valid ? 'ok' : 'error');
|
|
192
203
|
r.errors.push(...v.errors);
|
|
193
204
|
r.warnings.push(...v.warnings);
|
|
194
205
|
if (v.valid && v.parsed) {
|
|
195
|
-
r.svg = renderFgcaSvg(v.parsed, { variant: 'dgca' });
|
|
206
|
+
r.svg = renderFgcaSvg(v.parsed, { variant: 'dgca', scopeCaption: v.scopeCaption });
|
|
196
207
|
}
|
|
197
208
|
return r;
|
|
198
209
|
}
|
|
199
210
|
case 'dga': {
|
|
200
|
-
const
|
|
211
|
+
const meta = (doc ?? {}) as Record<string, unknown>;
|
|
212
|
+
const outOfScopeGoalIds = meta['__outOfScopeGoalIds'] instanceof Set
|
|
213
|
+
? (meta['__outOfScopeGoalIds'] as Set<string>)
|
|
214
|
+
: Array.isArray(meta['__outOfScopeGoalIds'])
|
|
215
|
+
? new Set(meta['__outOfScopeGoalIds'] as string[])
|
|
216
|
+
: undefined;
|
|
217
|
+
const outOfScopeFactorIds = meta['__outOfScopeFactorIds'] instanceof Set
|
|
218
|
+
? (meta['__outOfScopeFactorIds'] as Set<string>)
|
|
219
|
+
: Array.isArray(meta['__outOfScopeFactorIds'])
|
|
220
|
+
? new Set(meta['__outOfScopeFactorIds'] as string[])
|
|
221
|
+
: undefined;
|
|
222
|
+
const v = parseCanonicalFGA(doc, outOfScopeGoalIds, outOfScopeFactorIds);
|
|
201
223
|
const r = emptyResult(kind, v.valid ? 'ok' : 'error');
|
|
202
224
|
r.errors.push(...v.errors);
|
|
203
225
|
r.warnings.push(...v.warnings);
|
|
204
226
|
if (v.valid && v.parsed) {
|
|
205
|
-
r.svg = renderFgcaSvg(v.parsed, { variant: 'dga' });
|
|
227
|
+
r.svg = renderFgcaSvg(v.parsed, { variant: 'dga', scopeCaption: v.scopeCaption });
|
|
206
228
|
}
|
|
207
229
|
return r;
|
|
208
230
|
}
|
|
@@ -110,6 +110,8 @@ export interface RenderFgcaOptions {
|
|
|
110
110
|
entryCurvature?: number;
|
|
111
111
|
nodeSizePreset?: NodeSizePreset;
|
|
112
112
|
layoutOptions?: Parameters<typeof layoutFGCAPreview>[1];
|
|
113
|
+
/** Show scope caption when goal-scoped projection omits goals that reference out-of-scope goals. */
|
|
114
|
+
scopeCaption?: boolean;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
117
|
export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): string {
|
|
@@ -120,8 +122,9 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
120
122
|
entryCurvature,
|
|
121
123
|
nodeSizePreset = 'normal',
|
|
122
124
|
layoutOptions,
|
|
125
|
+
scopeCaption = false,
|
|
123
126
|
} = options;
|
|
124
|
-
const hideChanges = variant === 'dga';
|
|
127
|
+
const hideChanges = variant === 'dga' || doc.hideChanges === true;
|
|
125
128
|
const nodeSize = resolveDgcaNodeSize(parseNodeSizePreset(nodeSizePreset));
|
|
126
129
|
|
|
127
130
|
const { nodes, edges, columns, width, height } = layoutFGCAPreview(doc, {
|
|
@@ -144,9 +147,16 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
144
147
|
? `<text class="text-header" x="${PAD}" y="${PAD - 6}">${escXml(title)}</text>`
|
|
145
148
|
: '';
|
|
146
149
|
|
|
150
|
+
const captionSvg = scopeCaption
|
|
151
|
+
? `<text class="text-caption" x="${PAD}" y="${height + 16}">${escXml('This diagram is shown for the selected goal scope. Actions on it may also serve goals outside that scope.')}</text>`
|
|
152
|
+
: '';
|
|
153
|
+
|
|
147
154
|
const embedCss = generateSvgEmbedCss('transitrix');
|
|
148
155
|
|
|
149
|
-
|
|
156
|
+
// Calculate final height including caption if present
|
|
157
|
+
const finalHeight = scopeCaption ? height + 24 : height;
|
|
158
|
+
|
|
159
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${finalHeight}" viewBox="0 0 ${width} ${finalHeight}">
|
|
150
160
|
<style>${embedCss}</style>
|
|
151
161
|
<defs>
|
|
152
162
|
<marker id="arrow" markerWidth="8" markerHeight="8" refX="8" refY="3" orient="auto">
|
|
@@ -155,5 +165,6 @@ export function renderFgcaSvg(doc: FGCADoc, options: RenderFgcaOptions = {}): st
|
|
|
155
165
|
</defs>
|
|
156
166
|
${titleSvg}
|
|
157
167
|
${body}
|
|
168
|
+
${captionSvg}
|
|
158
169
|
</svg>`;
|
|
159
170
|
}
|
|
@@ -75,11 +75,48 @@ export interface ProcessDiagramLayout {
|
|
|
75
75
|
associations: ProcessAssociation[];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export interface BpmnTypography {
|
|
79
|
+
taskFontPx: number;
|
|
80
|
+
headerFontPx: number;
|
|
81
|
+
labelFontPx: number;
|
|
82
|
+
taskChars: number;
|
|
83
|
+
taskLineH: number;
|
|
84
|
+
labelChars: number;
|
|
85
|
+
labelLineH: number;
|
|
86
|
+
headerCharW: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Interactive-preview / default-export type sizes. */
|
|
90
|
+
export const DEFAULT_BPMN_TYPOGRAPHY: BpmnTypography = {
|
|
91
|
+
taskFontPx: 11,
|
|
92
|
+
headerFontPx: 11,
|
|
93
|
+
labelFontPx: 10,
|
|
94
|
+
taskChars: 14,
|
|
95
|
+
taskLineH: 14,
|
|
96
|
+
labelChars: 18,
|
|
97
|
+
labelLineH: 13,
|
|
98
|
+
headerCharW: 6.5,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
/** Presentation-export type sizes (20 px floor, wrap tuned to default node boxes). */
|
|
102
|
+
export const PRESENTATION_BPMN_TYPOGRAPHY: BpmnTypography = {
|
|
103
|
+
taskFontPx: 20,
|
|
104
|
+
headerFontPx: 20,
|
|
105
|
+
labelFontPx: 20,
|
|
106
|
+
taskChars: 9,
|
|
107
|
+
taskLineH: 22,
|
|
108
|
+
labelChars: 14,
|
|
109
|
+
labelLineH: 22,
|
|
110
|
+
headerCharW: 11.5,
|
|
111
|
+
};
|
|
112
|
+
|
|
78
113
|
export interface RenderProcessOptions {
|
|
79
114
|
/** Raw SVG injected after `<style>` — a title line or full title block. */
|
|
80
115
|
title?: string;
|
|
81
116
|
/** Extra vertical space (px) reserved above the pool for a title block. */
|
|
82
117
|
topInset?: number;
|
|
118
|
+
/** Label metrics. Omit for the interactive-preview defaults. */
|
|
119
|
+
typography?: BpmnTypography;
|
|
83
120
|
}
|
|
84
121
|
|
|
85
122
|
// ---------------------------------------------------------------------------
|
|
@@ -92,49 +129,52 @@ const LANE_LABEL_BAND = 44;
|
|
|
92
129
|
/** End margin (px) along the header height axis for rotated pool/lane captions. */
|
|
93
130
|
const HEADER_LABEL_AXIS_PAD = 32;
|
|
94
131
|
|
|
95
|
-
/**
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
/** Maximum characters per wrapped line inside a task box. */
|
|
99
|
-
const TASK_CHARS = 14;
|
|
132
|
+
/** Active wrap/font metrics for the current render call (sync, restored after). */
|
|
133
|
+
let typo: BpmnTypography = DEFAULT_BPMN_TYPOGRAPHY;
|
|
100
134
|
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
135
|
+
function runWithTypography<T>(t: BpmnTypography, fn: () => T): T {
|
|
136
|
+
const prev = typo;
|
|
137
|
+
typo = t;
|
|
138
|
+
try {
|
|
139
|
+
return fn();
|
|
140
|
+
} finally {
|
|
141
|
+
typo = prev;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
109
144
|
|
|
110
145
|
// ---------------------------------------------------------------------------
|
|
111
146
|
// BPMN-specific CSS — no font-style:italic per CLAUDE.md design rule
|
|
112
147
|
// ---------------------------------------------------------------------------
|
|
113
148
|
|
|
114
|
-
export
|
|
149
|
+
export function bpmnProcessCss(t: BpmnTypography = DEFAULT_BPMN_TYPOGRAPHY): string {
|
|
150
|
+
return `
|
|
115
151
|
.bpmn-pool { fill: var(--ts-bg-surface,#f8fafc); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
116
152
|
.bpmn-pool-name { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
117
153
|
.bpmn-lane { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
118
154
|
.bpmn-lane-header { fill: var(--ts-bg-elevated,#f1f5f9); stroke: var(--ts-node-stroke,#004d67); stroke-width: 0.75; }
|
|
119
|
-
.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
120
|
-
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
155
|
+
.bpmn-pool-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.headerFontPx}px; font-weight: 700; }
|
|
156
|
+
.bpmn-lane-label { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.headerFontPx}px; font-weight: 600; }
|
|
121
157
|
.bpmn-task { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
122
|
-
.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
158
|
+
.bpmn-task-name { fill: var(--ts-text-primary,#0d2b35); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.taskFontPx}px; text-anchor: middle; }
|
|
123
159
|
.bpmn-event { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); }
|
|
124
160
|
.bpmn-event-start { stroke-width: 1.5; }
|
|
125
161
|
.bpmn-event-end { stroke-width: 4; }
|
|
126
|
-
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
162
|
+
.bpmn-event-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.labelFontPx}px; text-anchor: middle; }
|
|
127
163
|
.bpmn-gateway { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; }
|
|
128
164
|
.bpmn-gateway-marker { stroke: var(--ts-node-stroke,#004d67); stroke-width: 2.5; stroke-linecap: round; fill: none; }
|
|
129
|
-
.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size:
|
|
165
|
+
.bpmn-gateway-label { fill: var(--ts-text-secondary,#516970); font-family: var(--vscode-font-family, system-ui, -apple-system, sans-serif); font-size: ${t.labelFontPx}px; text-anchor: middle; }
|
|
130
166
|
.bpmn-seq-flow { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; }
|
|
131
167
|
.bpmn-default-mark { stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; }
|
|
132
168
|
.bpmn-assoc { fill: none; stroke: var(--ts-edge-stroke,#004d67); stroke-width: 1; stroke-dasharray: 4 2; }
|
|
133
169
|
.bpmn-data-obj { fill: var(--ts-bg,#ffffff); stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
|
|
134
|
-
.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size:
|
|
170
|
+
.bpmn-data-obj-label { fill: var(--ts-text-secondary,#516970); font-size: ${t.labelFontPx}px; text-anchor: middle; }
|
|
135
171
|
.bpmn-event-intermediate { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1; }
|
|
136
172
|
.bpmn-event-icon { fill: none; stroke: var(--ts-node-stroke,#004d67); stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
|
|
137
173
|
`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Default-preview CSS (11 px / 10 px). Presentation export builds its own via `bpmnProcessCss`. */
|
|
177
|
+
export const BPMN_PROCESS_CSS = bpmnProcessCss(DEFAULT_BPMN_TYPOGRAPHY);
|
|
138
178
|
|
|
139
179
|
// ---------------------------------------------------------------------------
|
|
140
180
|
// Internal helpers
|
|
@@ -153,54 +193,56 @@ function wordTruncate(text: string, maxChars: number): string {
|
|
|
153
193
|
}
|
|
154
194
|
|
|
155
195
|
function fitRotatedHeaderText(name: string, spanPx: number): string {
|
|
156
|
-
const maxChars = Math.max(4, Math.floor(spanPx /
|
|
196
|
+
const maxChars = Math.max(4, Math.floor(spanPx / typo.headerCharW));
|
|
157
197
|
return wordTruncate(name, maxChars);
|
|
158
198
|
}
|
|
159
199
|
|
|
160
200
|
function wrapTaskName(name: string): string[] {
|
|
161
201
|
if (!name) return [];
|
|
202
|
+
const limit = typo.taskChars;
|
|
162
203
|
const words = name.split(/\s+/);
|
|
163
204
|
const lines: string[] = [];
|
|
164
205
|
let cur = '';
|
|
165
206
|
for (const w of words) {
|
|
166
|
-
const token = w.length >
|
|
207
|
+
const token = w.length > limit ? w.slice(0, limit - 1) + '…' : w;
|
|
167
208
|
if (!cur) {
|
|
168
209
|
cur = token;
|
|
169
|
-
} else if (cur.length + 1 + token.length <=
|
|
210
|
+
} else if (cur.length + 1 + token.length <= limit) {
|
|
170
211
|
cur += ' ' + token;
|
|
171
212
|
} else {
|
|
172
213
|
lines.push(cur);
|
|
173
214
|
if (lines.length === 3) {
|
|
174
|
-
if (lines[2].length >
|
|
215
|
+
if (lines[2].length > limit - 1) lines[2] = lines[2].slice(0, limit - 1) + '…';
|
|
175
216
|
return lines;
|
|
176
217
|
}
|
|
177
218
|
cur = token;
|
|
178
219
|
}
|
|
179
220
|
}
|
|
180
221
|
if (cur && lines.length < 3) lines.push(cur);
|
|
181
|
-
if (lines.length === 0) lines.push(name.slice(0,
|
|
222
|
+
if (lines.length === 0) lines.push(name.slice(0, limit));
|
|
182
223
|
return lines;
|
|
183
224
|
}
|
|
184
225
|
|
|
185
226
|
function taskNameSvg(name: string | undefined, cx: number, cy: number): string {
|
|
186
227
|
if (!name) return '';
|
|
187
228
|
const lines = wrapTaskName(name);
|
|
188
|
-
const totalH = lines.length *
|
|
189
|
-
const startY = cy - totalH / 2 +
|
|
229
|
+
const totalH = lines.length * typo.taskLineH;
|
|
230
|
+
const startY = cy - totalH / 2 + typo.taskLineH * 0.8;
|
|
190
231
|
const tspans = lines
|
|
191
|
-
.map((ln, i) => `<tspan x="${r(cx)}" y="${r(startY + i *
|
|
232
|
+
.map((ln, i) => `<tspan x="${r(cx)}" y="${r(startY + i * typo.taskLineH)}">${escXml(ln)}</tspan>`)
|
|
192
233
|
.join('');
|
|
193
234
|
return `<text class="bpmn-task-name">${tspans}</text>`;
|
|
194
235
|
}
|
|
195
236
|
|
|
196
237
|
function wrapBelowLabel(name: string): string[] {
|
|
238
|
+
const limit = typo.labelChars;
|
|
197
239
|
const words = name.split(/\s+/).filter(Boolean);
|
|
198
240
|
const allLines: string[] = [];
|
|
199
241
|
let cur = '';
|
|
200
242
|
for (const word of words) {
|
|
201
|
-
const tok = word.length >
|
|
243
|
+
const tok = word.length > limit ? word.slice(0, limit - 1) + '…' : word;
|
|
202
244
|
const test = cur ? `${cur} ${tok}` : tok;
|
|
203
|
-
if (test.length <=
|
|
245
|
+
if (test.length <= limit) {
|
|
204
246
|
cur = test;
|
|
205
247
|
} else {
|
|
206
248
|
if (cur) allLines.push(cur);
|
|
@@ -208,11 +250,11 @@ function wrapBelowLabel(name: string): string[] {
|
|
|
208
250
|
}
|
|
209
251
|
}
|
|
210
252
|
if (cur) allLines.push(cur);
|
|
211
|
-
if (allLines.length === 0) return [name.slice(0,
|
|
253
|
+
if (allLines.length === 0) return [name.slice(0, limit)];
|
|
212
254
|
if (allLines.length <= 3) return allLines;
|
|
213
255
|
const lines = allLines.slice(0, 3);
|
|
214
256
|
const last = lines[2];
|
|
215
|
-
lines[2] = last.length + 1 <=
|
|
257
|
+
lines[2] = last.length + 1 <= limit ? last + '…' : last.slice(0, limit - 1) + '…';
|
|
216
258
|
return lines;
|
|
217
259
|
}
|
|
218
260
|
|
|
@@ -226,7 +268,7 @@ function belowLabelSvg(name: string | undefined, cls: string, cx: number, belowY
|
|
|
226
268
|
return `<text class="${cls}" x="${r(textX)}" y="${startY}">${escXml(lines[0])}</text>`;
|
|
227
269
|
}
|
|
228
270
|
const tspans = lines
|
|
229
|
-
.map((ln, i) => `<tspan x="${r(textX)}" dy="${i === 0 ? 0 :
|
|
271
|
+
.map((ln, i) => `<tspan x="${r(textX)}" dy="${i === 0 ? 0 : typo.labelLineH}">${escXml(ln)}</tspan>`)
|
|
230
272
|
.join('');
|
|
231
273
|
return `<text class="${cls}" x="${r(textX)}" y="${startY}">${tspans}</text>`;
|
|
232
274
|
}
|
|
@@ -488,20 +530,23 @@ export function renderProcessLayoutSvg(
|
|
|
488
530
|
options: RenderProcessOptions = {},
|
|
489
531
|
): string {
|
|
490
532
|
const { title = '', topInset = title ? 32 : 0 } = options;
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
533
|
+
const t = options.typography ?? DEFAULT_BPMN_TYPOGRAPHY;
|
|
534
|
+
return runWithTypography(t, () => {
|
|
535
|
+
const pb = layout.poolBounds;
|
|
536
|
+
const PAD = 16;
|
|
537
|
+
|
|
538
|
+
const svgW = r(pb.x + pb.width + PAD);
|
|
539
|
+
const svgH = r(pb.y + pb.height + PAD + topInset);
|
|
540
|
+
|
|
541
|
+
const embedCss = generateSvgEmbedCss('transitrix') + bpmnProcessCss(t);
|
|
542
|
+
const body = renderProcessBody(layout, 0, topInset);
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${svgW}" height="${svgH}" viewBox="0 0 ${svgW} ${svgH}">\n` +
|
|
546
|
+
`<style>${embedCss}</style>\n` +
|
|
547
|
+
(title ? `${title}\n` : '') +
|
|
548
|
+
`${body}\n` +
|
|
549
|
+
`</svg>`
|
|
550
|
+
);
|
|
551
|
+
});
|
|
507
552
|
}
|