@twinfinity/printing 6.0.1-ci.28952-beta → 6.0.2-beta
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/README.md +3 -3
- package/dist/BuildInfo.js +3 -3
- package/dist/BuildInfo.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/layout/analyzeTemplate.js.map +1 -1
- package/dist/layout/clipUtils.js.map +1 -1
- package/dist/layout/colorConstants.js.map +1 -1
- package/dist/layout/elementKinds.js.map +1 -1
- package/dist/layout/exportDrawing.js.map +1 -1
- package/dist/layout/filters.js.map +1 -1
- package/dist/layout/geometryConstants.js.map +1 -1
- package/dist/layout/index.js.map +1 -1
- package/dist/layout/itemBuilders.js.map +1 -1
- package/dist/layout/labelsRender.js.map +1 -1
- package/dist/layout/legendConstants.js.map +1 -1
- package/dist/layout/legendRender.js.map +1 -1
- package/dist/layout/legendRows.js.map +1 -1
- package/dist/layout/legendUtils.js.map +1 -1
- package/dist/layout/metadata.js.map +1 -1
- package/dist/layout/model.js.map +1 -1
- package/dist/layout/options.js.map +1 -1
- package/dist/layout/presets.js.map +1 -1
- package/dist/layout/qrRender.js.map +1 -1
- package/dist/layout/renderDrawing.js.map +1 -1
- package/dist/layout/renderUtils.js.map +1 -1
- package/dist/layout/sectionValidation.js.map +1 -1
- package/dist/layout/templateVariables.js.map +1 -1
- package/dist/layout/typeGuards.js.map +1 -1
- package/dist/layout/types.js.map +1 -1
- package/dist/layout/utils.js.map +1 -1
- package/dist/layout/validation.js.map +1 -1
- package/dist/layout/validationHelpers.js.map +1 -1
- package/dist/layout/validationUtils.js.map +1 -1
- package/dist/layout/viewportPrep.js.map +1 -1
- package/dist/layout/viewportRender.js.map +1 -1
- package/dist/layout/viewportUtils.js.map +1 -1
- package/dist/layout/warnings.js.map +1 -1
- package/dist/printToPdf.js.map +1 -1
- package/dist/printToSvg.js.map +1 -1
- package/dist/sections.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/BuildInfo.ts +41 -41
- package/src/index.ts +10 -10
- package/src/layout/analyzeTemplate.ts +218 -218
- package/src/layout/clipUtils.ts +193 -193
- package/src/layout/colorConstants.ts +16 -16
- package/src/layout/elementKinds.ts +35 -35
- package/src/layout/exportDrawing.ts +206 -206
- package/src/layout/filters.ts +80 -80
- package/src/layout/geometryConstants.ts +11 -11
- package/src/layout/index.ts +671 -671
- package/src/layout/itemBuilders.ts +159 -159
- package/src/layout/labelsRender.ts +170 -170
- package/src/layout/legendConstants.ts +11 -11
- package/src/layout/legendRender.ts +543 -543
- package/src/layout/legendRows.ts +62 -62
- package/src/layout/legendUtils.ts +144 -144
- package/src/layout/metadata.ts +210 -210
- package/src/layout/model.ts +372 -372
- package/src/layout/options.ts +17 -17
- package/src/layout/presets.ts +51 -51
- package/src/layout/qrRender.ts +126 -126
- package/src/layout/qrcode.d.ts +2 -2
- package/src/layout/renderDrawing.ts +710 -710
- package/src/layout/renderUtils.ts +138 -138
- package/src/layout/sectionValidation.ts +2 -2
- package/src/layout/templateVariables.ts +317 -317
- package/src/layout/typeGuards.ts +9 -9
- package/src/layout/types.ts +835 -835
- package/src/layout/utils.ts +85 -85
- package/src/layout/validation.ts +392 -392
- package/src/layout/validationHelpers.ts +59 -59
- package/src/layout/validationUtils.ts +6 -6
- package/src/layout/viewportPrep.ts +351 -351
- package/src/layout/viewportRender.ts +442 -442
- package/src/layout/viewportUtils.ts +50 -50
- package/src/layout/warnings.ts +3 -3
- package/src/printToPdf.ts +108 -108
- package/src/printToSvg.ts +188 -188
- package/src/sections.ts +1019 -1019
- package/src/types.ts +49 -49
|
@@ -1,317 +1,317 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template variable extraction, validation, and resolution.
|
|
3
|
-
*
|
|
4
|
-
* This module provides utilities for working with parameterized templates:
|
|
5
|
-
* - Extract variable definitions for UI generation
|
|
6
|
-
* - Get default values (fail-safe fallback)
|
|
7
|
-
* - Resolve user-provided values with validation
|
|
8
|
-
* - Apply variables to template (substitute {{vars.*}} references)
|
|
9
|
-
*
|
|
10
|
-
* @module templateVariables
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { LayoutTemplate, TemplateVariableDef, TemplateVariableInfo } from './types';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Extract all variable definitions from a template.
|
|
17
|
-
*
|
|
18
|
-
* Returns an array of variable info objects suitable for UI generation.
|
|
19
|
-
* Returns empty array if template has no variables.
|
|
20
|
-
*
|
|
21
|
-
* @param template - Layout template (may or may not have variables)
|
|
22
|
-
* @returns Array of variable definitions with metadata
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```typescript
|
|
26
|
-
* const vars = getTemplateVariables(template);
|
|
27
|
-
* // [
|
|
28
|
-
* // { name: 'hyresobjektsnummer', type: 'string', default: '2022-F2008', label: 'Hyresobjekt', ... },
|
|
29
|
-
* // { name: 'showLegend', type: 'boolean', default: true, label: 'Show Legend', ... }
|
|
30
|
-
* // ]
|
|
31
|
-
*
|
|
32
|
-
* // Use for UI generation
|
|
33
|
-
* vars.forEach(v => {
|
|
34
|
-
* if (v.type === 'string') {
|
|
35
|
-
* renderTextField(v.label, v.default);
|
|
36
|
-
* } else if (v.type === 'boolean') {
|
|
37
|
-
* renderCheckbox(v.label, v.default);
|
|
38
|
-
* }
|
|
39
|
-
* });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
export function getTemplateVariables(template: LayoutTemplate): TemplateVariableInfo[] {
|
|
43
|
-
if (!template.variables) return [];
|
|
44
|
-
|
|
45
|
-
return Object.entries(template.variables).map(([name, def]) => ({
|
|
46
|
-
name,
|
|
47
|
-
type: def.type,
|
|
48
|
-
default: def.default,
|
|
49
|
-
label: def.label ?? name,
|
|
50
|
-
description: def.description,
|
|
51
|
-
format: def.format,
|
|
52
|
-
min: def.min,
|
|
53
|
-
max: def.max,
|
|
54
|
-
step: def.step,
|
|
55
|
-
minLength: def.minLength,
|
|
56
|
-
maxLength: def.maxLength,
|
|
57
|
-
pattern: def.pattern,
|
|
58
|
-
enum: def.enum,
|
|
59
|
-
items: def.items
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Extract all default values from a template.
|
|
65
|
-
*
|
|
66
|
-
* Returns an object with all variable names mapped to their default values.
|
|
67
|
-
* These defaults are guaranteed to work - template can always render with them.
|
|
68
|
-
*
|
|
69
|
-
* Returns empty object if template has no variables.
|
|
70
|
-
*
|
|
71
|
-
* @param template - Layout template
|
|
72
|
-
* @returns Object mapping variable names to default values
|
|
73
|
-
*
|
|
74
|
-
* @example
|
|
75
|
-
* ```typescript
|
|
76
|
-
* const defaults = getTemplateDefaults(template);
|
|
77
|
-
* // { hyresobjektsnummer: '2022-F2008', showLegend: true, scaleDenom: 100 }
|
|
78
|
-
*
|
|
79
|
-
* // Can always render with defaults (fail-safe)
|
|
80
|
-
* const { svg } = await renderDrawing({
|
|
81
|
-
* template,
|
|
82
|
-
* models,
|
|
83
|
-
* variables: defaults // Guaranteed to work
|
|
84
|
-
* });
|
|
85
|
-
* ```
|
|
86
|
-
*/
|
|
87
|
-
export function getTemplateDefaults(template: LayoutTemplate): Record<string, any> {
|
|
88
|
-
if (!template.variables) return {};
|
|
89
|
-
|
|
90
|
-
const defaults: Record<string, any> = {};
|
|
91
|
-
for (const [name, def] of Object.entries(template.variables)) {
|
|
92
|
-
defaults[name] = def.default;
|
|
93
|
-
}
|
|
94
|
-
return defaults;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Validate a value against a variable definition.
|
|
99
|
-
*
|
|
100
|
-
* Checks type and constraint validation (min/max, pattern, enum, etc.).
|
|
101
|
-
*
|
|
102
|
-
* @param value - Value to validate
|
|
103
|
-
* @param varDef - Variable definition with constraints
|
|
104
|
-
* @returns Validation result with ok flag and optional error message
|
|
105
|
-
*/
|
|
106
|
-
function validateVariable(value: any, varDef: TemplateVariableDef): { ok: boolean; error?: string } {
|
|
107
|
-
// Type check
|
|
108
|
-
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
109
|
-
if (varDef.type === 'number' && actualType !== 'number') {
|
|
110
|
-
return { ok: false, error: `Expected number, got ${actualType}` };
|
|
111
|
-
}
|
|
112
|
-
if (varDef.type === 'string' && actualType !== 'string') {
|
|
113
|
-
return { ok: false, error: `Expected string, got ${actualType}` };
|
|
114
|
-
}
|
|
115
|
-
if (varDef.type === 'boolean' && actualType !== 'boolean') {
|
|
116
|
-
return { ok: false, error: `Expected boolean, got ${actualType}` };
|
|
117
|
-
}
|
|
118
|
-
if (varDef.type === 'array' && actualType !== 'array') {
|
|
119
|
-
return { ok: false, error: `Expected array, got ${actualType}` };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// String validation
|
|
123
|
-
if (varDef.type === 'string' && typeof value === 'string') {
|
|
124
|
-
if (varDef.minLength !== undefined && value.length < varDef.minLength) {
|
|
125
|
-
return { ok: false, error: `String too short (min ${varDef.minLength})` };
|
|
126
|
-
}
|
|
127
|
-
if (varDef.maxLength !== undefined && value.length > varDef.maxLength) {
|
|
128
|
-
return { ok: false, error: `String too long (max ${varDef.maxLength})` };
|
|
129
|
-
}
|
|
130
|
-
if (varDef.pattern) {
|
|
131
|
-
try {
|
|
132
|
-
const regex = new RegExp(varDef.pattern);
|
|
133
|
-
if (!regex.test(value)) {
|
|
134
|
-
return { ok: false, error: `Does not match pattern ${varDef.pattern}` };
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
return { ok: false, error: `Invalid pattern: ${varDef.pattern}` };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (varDef.enum && !varDef.enum.includes(value)) {
|
|
141
|
-
return { ok: false, error: `Not in allowed values: ${varDef.enum.join(', ')}` };
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Number validation
|
|
146
|
-
if (varDef.type === 'number' && typeof value === 'number') {
|
|
147
|
-
if (!Number.isFinite(value)) {
|
|
148
|
-
return { ok: false, error: 'Must be a finite number' };
|
|
149
|
-
}
|
|
150
|
-
if (varDef.min !== undefined && value < varDef.min) {
|
|
151
|
-
return { ok: false, error: `Too small (min ${varDef.min})` };
|
|
152
|
-
}
|
|
153
|
-
if (varDef.max !== undefined && value > varDef.max) {
|
|
154
|
-
return { ok: false, error: `Too large (max ${varDef.max})` };
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return { ok: true };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Resolve template variables by merging user values with defaults.
|
|
163
|
-
*
|
|
164
|
-
* This is a **fail-safe** function:
|
|
165
|
-
* - Missing values → use default
|
|
166
|
-
* - Invalid values → use default + warn
|
|
167
|
-
* - Unknown variables → warn + ignore
|
|
168
|
-
*
|
|
169
|
-
* The result is ALWAYS valid and complete (safe to use for rendering).
|
|
170
|
-
*
|
|
171
|
-
* @param template - Layout template with variable definitions
|
|
172
|
-
* @param userValues - Optional user-provided values (can be partial or undefined)
|
|
173
|
-
* @returns Object with resolved values and any validation warnings
|
|
174
|
-
*
|
|
175
|
-
* @example
|
|
176
|
-
* ```typescript
|
|
177
|
-
* // User provides partial values
|
|
178
|
-
* const { values, warnings } = resolveTemplateValues(template, {
|
|
179
|
-
* hyresobjektsnummer: '2022-F2009' // Valid
|
|
180
|
-
* // Other vars use defaults
|
|
181
|
-
* });
|
|
182
|
-
* // values = { hyresobjektsnummer: '2022-F2009', showLegend: true, scaleDenom: 100 }
|
|
183
|
-
* // warnings = []
|
|
184
|
-
*
|
|
185
|
-
* // User provides invalid value
|
|
186
|
-
* const { values, warnings } = resolveTemplateValues(template, {
|
|
187
|
-
* scaleDenom: 9999 // Out of range (max: 500)
|
|
188
|
-
* });
|
|
189
|
-
* // values = { scaleDenom: 100, ... } ← Fell back to default
|
|
190
|
-
* // warnings = ["Invalid 'scaleDenom': Too large (max 500) - using default '100'"]
|
|
191
|
-
*
|
|
192
|
-
* // Always safe to render
|
|
193
|
-
* const resolvedTemplate = applyTemplateVariables(template, values);
|
|
194
|
-
* ```
|
|
195
|
-
*/
|
|
196
|
-
export function resolveTemplateValues(
|
|
197
|
-
template: LayoutTemplate,
|
|
198
|
-
userValues?: Record<string, any>
|
|
199
|
-
): { values: Record<string, any>; warnings: string[] } {
|
|
200
|
-
const defaults = getTemplateDefaults(template);
|
|
201
|
-
const warnings: string[] = [];
|
|
202
|
-
|
|
203
|
-
// Start with all defaults (fail-safe!)
|
|
204
|
-
const values = { ...defaults };
|
|
205
|
-
|
|
206
|
-
// No user values or no variables → return defaults
|
|
207
|
-
if (!userValues || !template.variables) {
|
|
208
|
-
return { values, warnings };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Merge and validate user values
|
|
212
|
-
for (const [name, value] of Object.entries(userValues)) {
|
|
213
|
-
const varDef = template.variables[name];
|
|
214
|
-
|
|
215
|
-
// Unknown variable
|
|
216
|
-
if (!varDef) {
|
|
217
|
-
warnings.push(`Unknown variable '${name}' - ignoring`);
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Validate value
|
|
222
|
-
const validation = validateVariable(value, varDef);
|
|
223
|
-
if (!validation.ok) {
|
|
224
|
-
warnings.push(`Invalid '${name}': ${validation.error} - using default '${defaults[name]}'`);
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Valid → use it
|
|
229
|
-
values[name] = value;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return { values, warnings };
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Apply variables to a template by substituting {{vars.*}} references.
|
|
237
|
-
*
|
|
238
|
-
* Recursively scans the template and replaces all {{vars.variableName}} references
|
|
239
|
-
* with the corresponding values from the provided values object.
|
|
240
|
-
*
|
|
241
|
-
* Returns a new template with all variables resolved (does not mutate input).
|
|
242
|
-
*
|
|
243
|
-
* @param template - Original template with {{vars.*}} references
|
|
244
|
-
* @param values - Resolved variable values (from resolveTemplateValues)
|
|
245
|
-
* @returns New template with all {{vars.*}} replaced by actual values
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* ```typescript
|
|
249
|
-
* const template = {
|
|
250
|
-
* elements: [
|
|
251
|
-
* { kind: 'text', text: 'Hyresobjekt: {{vars.hyresobjektsnummer}}' },
|
|
252
|
-
* { kind: 'viewport', clip: { where: { 'BIP.id': '{{vars.hyresobjektsnummer}}' } } }
|
|
253
|
-
* ]
|
|
254
|
-
* };
|
|
255
|
-
*
|
|
256
|
-
* const values = { hyresobjektsnummer: '2022-F2009' };
|
|
257
|
-
* const resolved = applyTemplateVariables(template, values);
|
|
258
|
-
* // {
|
|
259
|
-
* // elements: [
|
|
260
|
-
* // { kind: 'text', text: 'Hyresobjekt: 2022-F2009' },
|
|
261
|
-
* // { kind: 'viewport', clip: { where: { 'BIP.id': '2022-F2009' } } }
|
|
262
|
-
* // ]
|
|
263
|
-
* // }
|
|
264
|
-
* ```
|
|
265
|
-
*/
|
|
266
|
-
export function applyTemplateVariables(template: LayoutTemplate, values: Record<string, any>): LayoutTemplate {
|
|
267
|
-
// Deep clone to avoid mutation
|
|
268
|
-
const resolved: LayoutTemplate = JSON.parse(JSON.stringify(template));
|
|
269
|
-
|
|
270
|
-
// Recursively replace {{vars.*}} in all string values
|
|
271
|
-
function replaceInValue(obj: any): any {
|
|
272
|
-
if (typeof obj === 'string') {
|
|
273
|
-
return interpolateVariables(obj, values);
|
|
274
|
-
}
|
|
275
|
-
if (Array.isArray(obj)) {
|
|
276
|
-
return obj.map(replaceInValue);
|
|
277
|
-
}
|
|
278
|
-
if (obj && typeof obj === 'object') {
|
|
279
|
-
const result: any = {};
|
|
280
|
-
for (const [key, val] of Object.entries(obj)) {
|
|
281
|
-
result[key] = replaceInValue(val);
|
|
282
|
-
}
|
|
283
|
-
return result;
|
|
284
|
-
}
|
|
285
|
-
return obj;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Apply to all template properties that might contain variable references
|
|
289
|
-
resolved.elements = replaceInValue(resolved.elements);
|
|
290
|
-
if (resolved.assets) {
|
|
291
|
-
resolved.assets = replaceInValue(resolved.assets);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return resolved;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Interpolate {{vars.*}} references in a string.
|
|
299
|
-
*
|
|
300
|
-
* Replaces all occurrences of {{vars.variableName}} with the corresponding value.
|
|
301
|
-
* If variable is not found, keeps the placeholder and logs a warning.
|
|
302
|
-
*
|
|
303
|
-
* @param str - String potentially containing {{vars.*}} references
|
|
304
|
-
* @param values - Variable values
|
|
305
|
-
* @returns String with all {{vars.*}} replaced
|
|
306
|
-
*/
|
|
307
|
-
function interpolateVariables(str: string, values: Record<string, any>): string {
|
|
308
|
-
return str.replace(/\{\{vars\.(\w+)\}\}/g, (match, varName) => {
|
|
309
|
-
if (varName in values) {
|
|
310
|
-
const value = values[varName];
|
|
311
|
-
// Convert to string (handles numbers, booleans, etc.)
|
|
312
|
-
return String(value);
|
|
313
|
-
}
|
|
314
|
-
console.warn(`[templateVariables] Variable '${varName}' not found - keeping placeholder`);
|
|
315
|
-
return match; // Keep {{vars.varName}} as-is
|
|
316
|
-
});
|
|
317
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Template variable extraction, validation, and resolution.
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for working with parameterized templates:
|
|
5
|
+
* - Extract variable definitions for UI generation
|
|
6
|
+
* - Get default values (fail-safe fallback)
|
|
7
|
+
* - Resolve user-provided values with validation
|
|
8
|
+
* - Apply variables to template (substitute {{vars.*}} references)
|
|
9
|
+
*
|
|
10
|
+
* @module templateVariables
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { LayoutTemplate, TemplateVariableDef, TemplateVariableInfo } from './types';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract all variable definitions from a template.
|
|
17
|
+
*
|
|
18
|
+
* Returns an array of variable info objects suitable for UI generation.
|
|
19
|
+
* Returns empty array if template has no variables.
|
|
20
|
+
*
|
|
21
|
+
* @param template - Layout template (may or may not have variables)
|
|
22
|
+
* @returns Array of variable definitions with metadata
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const vars = getTemplateVariables(template);
|
|
27
|
+
* // [
|
|
28
|
+
* // { name: 'hyresobjektsnummer', type: 'string', default: '2022-F2008', label: 'Hyresobjekt', ... },
|
|
29
|
+
* // { name: 'showLegend', type: 'boolean', default: true, label: 'Show Legend', ... }
|
|
30
|
+
* // ]
|
|
31
|
+
*
|
|
32
|
+
* // Use for UI generation
|
|
33
|
+
* vars.forEach(v => {
|
|
34
|
+
* if (v.type === 'string') {
|
|
35
|
+
* renderTextField(v.label, v.default);
|
|
36
|
+
* } else if (v.type === 'boolean') {
|
|
37
|
+
* renderCheckbox(v.label, v.default);
|
|
38
|
+
* }
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function getTemplateVariables(template: LayoutTemplate): TemplateVariableInfo[] {
|
|
43
|
+
if (!template.variables) return [];
|
|
44
|
+
|
|
45
|
+
return Object.entries(template.variables).map(([name, def]) => ({
|
|
46
|
+
name,
|
|
47
|
+
type: def.type,
|
|
48
|
+
default: def.default,
|
|
49
|
+
label: def.label ?? name,
|
|
50
|
+
description: def.description,
|
|
51
|
+
format: def.format,
|
|
52
|
+
min: def.min,
|
|
53
|
+
max: def.max,
|
|
54
|
+
step: def.step,
|
|
55
|
+
minLength: def.minLength,
|
|
56
|
+
maxLength: def.maxLength,
|
|
57
|
+
pattern: def.pattern,
|
|
58
|
+
enum: def.enum,
|
|
59
|
+
items: def.items
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extract all default values from a template.
|
|
65
|
+
*
|
|
66
|
+
* Returns an object with all variable names mapped to their default values.
|
|
67
|
+
* These defaults are guaranteed to work - template can always render with them.
|
|
68
|
+
*
|
|
69
|
+
* Returns empty object if template has no variables.
|
|
70
|
+
*
|
|
71
|
+
* @param template - Layout template
|
|
72
|
+
* @returns Object mapping variable names to default values
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const defaults = getTemplateDefaults(template);
|
|
77
|
+
* // { hyresobjektsnummer: '2022-F2008', showLegend: true, scaleDenom: 100 }
|
|
78
|
+
*
|
|
79
|
+
* // Can always render with defaults (fail-safe)
|
|
80
|
+
* const { svg } = await renderDrawing({
|
|
81
|
+
* template,
|
|
82
|
+
* models,
|
|
83
|
+
* variables: defaults // Guaranteed to work
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
export function getTemplateDefaults(template: LayoutTemplate): Record<string, any> {
|
|
88
|
+
if (!template.variables) return {};
|
|
89
|
+
|
|
90
|
+
const defaults: Record<string, any> = {};
|
|
91
|
+
for (const [name, def] of Object.entries(template.variables)) {
|
|
92
|
+
defaults[name] = def.default;
|
|
93
|
+
}
|
|
94
|
+
return defaults;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate a value against a variable definition.
|
|
99
|
+
*
|
|
100
|
+
* Checks type and constraint validation (min/max, pattern, enum, etc.).
|
|
101
|
+
*
|
|
102
|
+
* @param value - Value to validate
|
|
103
|
+
* @param varDef - Variable definition with constraints
|
|
104
|
+
* @returns Validation result with ok flag and optional error message
|
|
105
|
+
*/
|
|
106
|
+
function validateVariable(value: any, varDef: TemplateVariableDef): { ok: boolean; error?: string } {
|
|
107
|
+
// Type check
|
|
108
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
109
|
+
if (varDef.type === 'number' && actualType !== 'number') {
|
|
110
|
+
return { ok: false, error: `Expected number, got ${actualType}` };
|
|
111
|
+
}
|
|
112
|
+
if (varDef.type === 'string' && actualType !== 'string') {
|
|
113
|
+
return { ok: false, error: `Expected string, got ${actualType}` };
|
|
114
|
+
}
|
|
115
|
+
if (varDef.type === 'boolean' && actualType !== 'boolean') {
|
|
116
|
+
return { ok: false, error: `Expected boolean, got ${actualType}` };
|
|
117
|
+
}
|
|
118
|
+
if (varDef.type === 'array' && actualType !== 'array') {
|
|
119
|
+
return { ok: false, error: `Expected array, got ${actualType}` };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// String validation
|
|
123
|
+
if (varDef.type === 'string' && typeof value === 'string') {
|
|
124
|
+
if (varDef.minLength !== undefined && value.length < varDef.minLength) {
|
|
125
|
+
return { ok: false, error: `String too short (min ${varDef.minLength})` };
|
|
126
|
+
}
|
|
127
|
+
if (varDef.maxLength !== undefined && value.length > varDef.maxLength) {
|
|
128
|
+
return { ok: false, error: `String too long (max ${varDef.maxLength})` };
|
|
129
|
+
}
|
|
130
|
+
if (varDef.pattern) {
|
|
131
|
+
try {
|
|
132
|
+
const regex = new RegExp(varDef.pattern);
|
|
133
|
+
if (!regex.test(value)) {
|
|
134
|
+
return { ok: false, error: `Does not match pattern ${varDef.pattern}` };
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
return { ok: false, error: `Invalid pattern: ${varDef.pattern}` };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (varDef.enum && !varDef.enum.includes(value)) {
|
|
141
|
+
return { ok: false, error: `Not in allowed values: ${varDef.enum.join(', ')}` };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Number validation
|
|
146
|
+
if (varDef.type === 'number' && typeof value === 'number') {
|
|
147
|
+
if (!Number.isFinite(value)) {
|
|
148
|
+
return { ok: false, error: 'Must be a finite number' };
|
|
149
|
+
}
|
|
150
|
+
if (varDef.min !== undefined && value < varDef.min) {
|
|
151
|
+
return { ok: false, error: `Too small (min ${varDef.min})` };
|
|
152
|
+
}
|
|
153
|
+
if (varDef.max !== undefined && value > varDef.max) {
|
|
154
|
+
return { ok: false, error: `Too large (max ${varDef.max})` };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { ok: true };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Resolve template variables by merging user values with defaults.
|
|
163
|
+
*
|
|
164
|
+
* This is a **fail-safe** function:
|
|
165
|
+
* - Missing values → use default
|
|
166
|
+
* - Invalid values → use default + warn
|
|
167
|
+
* - Unknown variables → warn + ignore
|
|
168
|
+
*
|
|
169
|
+
* The result is ALWAYS valid and complete (safe to use for rendering).
|
|
170
|
+
*
|
|
171
|
+
* @param template - Layout template with variable definitions
|
|
172
|
+
* @param userValues - Optional user-provided values (can be partial or undefined)
|
|
173
|
+
* @returns Object with resolved values and any validation warnings
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```typescript
|
|
177
|
+
* // User provides partial values
|
|
178
|
+
* const { values, warnings } = resolveTemplateValues(template, {
|
|
179
|
+
* hyresobjektsnummer: '2022-F2009' // Valid
|
|
180
|
+
* // Other vars use defaults
|
|
181
|
+
* });
|
|
182
|
+
* // values = { hyresobjektsnummer: '2022-F2009', showLegend: true, scaleDenom: 100 }
|
|
183
|
+
* // warnings = []
|
|
184
|
+
*
|
|
185
|
+
* // User provides invalid value
|
|
186
|
+
* const { values, warnings } = resolveTemplateValues(template, {
|
|
187
|
+
* scaleDenom: 9999 // Out of range (max: 500)
|
|
188
|
+
* });
|
|
189
|
+
* // values = { scaleDenom: 100, ... } ← Fell back to default
|
|
190
|
+
* // warnings = ["Invalid 'scaleDenom': Too large (max 500) - using default '100'"]
|
|
191
|
+
*
|
|
192
|
+
* // Always safe to render
|
|
193
|
+
* const resolvedTemplate = applyTemplateVariables(template, values);
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
export function resolveTemplateValues(
|
|
197
|
+
template: LayoutTemplate,
|
|
198
|
+
userValues?: Record<string, any>
|
|
199
|
+
): { values: Record<string, any>; warnings: string[] } {
|
|
200
|
+
const defaults = getTemplateDefaults(template);
|
|
201
|
+
const warnings: string[] = [];
|
|
202
|
+
|
|
203
|
+
// Start with all defaults (fail-safe!)
|
|
204
|
+
const values = { ...defaults };
|
|
205
|
+
|
|
206
|
+
// No user values or no variables → return defaults
|
|
207
|
+
if (!userValues || !template.variables) {
|
|
208
|
+
return { values, warnings };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Merge and validate user values
|
|
212
|
+
for (const [name, value] of Object.entries(userValues)) {
|
|
213
|
+
const varDef = template.variables[name];
|
|
214
|
+
|
|
215
|
+
// Unknown variable
|
|
216
|
+
if (!varDef) {
|
|
217
|
+
warnings.push(`Unknown variable '${name}' - ignoring`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Validate value
|
|
222
|
+
const validation = validateVariable(value, varDef);
|
|
223
|
+
if (!validation.ok) {
|
|
224
|
+
warnings.push(`Invalid '${name}': ${validation.error} - using default '${defaults[name]}'`);
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Valid → use it
|
|
229
|
+
values[name] = value;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { values, warnings };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Apply variables to a template by substituting {{vars.*}} references.
|
|
237
|
+
*
|
|
238
|
+
* Recursively scans the template and replaces all {{vars.variableName}} references
|
|
239
|
+
* with the corresponding values from the provided values object.
|
|
240
|
+
*
|
|
241
|
+
* Returns a new template with all variables resolved (does not mutate input).
|
|
242
|
+
*
|
|
243
|
+
* @param template - Original template with {{vars.*}} references
|
|
244
|
+
* @param values - Resolved variable values (from resolveTemplateValues)
|
|
245
|
+
* @returns New template with all {{vars.*}} replaced by actual values
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const template = {
|
|
250
|
+
* elements: [
|
|
251
|
+
* { kind: 'text', text: 'Hyresobjekt: {{vars.hyresobjektsnummer}}' },
|
|
252
|
+
* { kind: 'viewport', clip: { where: { 'BIP.id': '{{vars.hyresobjektsnummer}}' } } }
|
|
253
|
+
* ]
|
|
254
|
+
* };
|
|
255
|
+
*
|
|
256
|
+
* const values = { hyresobjektsnummer: '2022-F2009' };
|
|
257
|
+
* const resolved = applyTemplateVariables(template, values);
|
|
258
|
+
* // {
|
|
259
|
+
* // elements: [
|
|
260
|
+
* // { kind: 'text', text: 'Hyresobjekt: 2022-F2009' },
|
|
261
|
+
* // { kind: 'viewport', clip: { where: { 'BIP.id': '2022-F2009' } } }
|
|
262
|
+
* // ]
|
|
263
|
+
* // }
|
|
264
|
+
* ```
|
|
265
|
+
*/
|
|
266
|
+
export function applyTemplateVariables(template: LayoutTemplate, values: Record<string, any>): LayoutTemplate {
|
|
267
|
+
// Deep clone to avoid mutation
|
|
268
|
+
const resolved: LayoutTemplate = JSON.parse(JSON.stringify(template));
|
|
269
|
+
|
|
270
|
+
// Recursively replace {{vars.*}} in all string values
|
|
271
|
+
function replaceInValue(obj: any): any {
|
|
272
|
+
if (typeof obj === 'string') {
|
|
273
|
+
return interpolateVariables(obj, values);
|
|
274
|
+
}
|
|
275
|
+
if (Array.isArray(obj)) {
|
|
276
|
+
return obj.map(replaceInValue);
|
|
277
|
+
}
|
|
278
|
+
if (obj && typeof obj === 'object') {
|
|
279
|
+
const result: any = {};
|
|
280
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
281
|
+
result[key] = replaceInValue(val);
|
|
282
|
+
}
|
|
283
|
+
return result;
|
|
284
|
+
}
|
|
285
|
+
return obj;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Apply to all template properties that might contain variable references
|
|
289
|
+
resolved.elements = replaceInValue(resolved.elements);
|
|
290
|
+
if (resolved.assets) {
|
|
291
|
+
resolved.assets = replaceInValue(resolved.assets);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return resolved;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Interpolate {{vars.*}} references in a string.
|
|
299
|
+
*
|
|
300
|
+
* Replaces all occurrences of {{vars.variableName}} with the corresponding value.
|
|
301
|
+
* If variable is not found, keeps the placeholder and logs a warning.
|
|
302
|
+
*
|
|
303
|
+
* @param str - String potentially containing {{vars.*}} references
|
|
304
|
+
* @param values - Variable values
|
|
305
|
+
* @returns String with all {{vars.*}} replaced
|
|
306
|
+
*/
|
|
307
|
+
function interpolateVariables(str: string, values: Record<string, any>): string {
|
|
308
|
+
return str.replace(/\{\{vars\.(\w+)\}\}/g, (match, varName) => {
|
|
309
|
+
if (varName in values) {
|
|
310
|
+
const value = values[varName];
|
|
311
|
+
// Convert to string (handles numbers, booleans, etc.)
|
|
312
|
+
return String(value);
|
|
313
|
+
}
|
|
314
|
+
console.warn(`[templateVariables] Variable '${varName}' not found - keeping placeholder`);
|
|
315
|
+
return match; // Keep {{vars.varName}} as-is
|
|
316
|
+
});
|
|
317
|
+
}
|
package/src/layout/typeGuards.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
/** Type guard: true for plain objects, false for arrays, null, and primitives. */
|
|
2
|
-
export const isRecord = (value: unknown): value is Record<string, unknown> => {
|
|
3
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
-
};
|
|
5
|
-
|
|
6
|
-
/** Safely read a property from an unknown value. Returns undefined for non-objects. */
|
|
7
|
-
export const getProp = (obj: unknown, key: string): unknown => {
|
|
8
|
-
return isRecord(obj) ? obj[key] : undefined;
|
|
9
|
-
};
|
|
1
|
+
/** Type guard: true for plain objects, false for arrays, null, and primitives. */
|
|
2
|
+
export const isRecord = (value: unknown): value is Record<string, unknown> => {
|
|
3
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
/** Safely read a property from an unknown value. Returns undefined for non-objects. */
|
|
7
|
+
export const getProp = (obj: unknown, key: string): unknown => {
|
|
8
|
+
return isRecord(obj) ? obj[key] : undefined;
|
|
9
|
+
};
|