@twinfinity/printing 6.0.1-ci.28782-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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templateVariables.js","sourceRoot":"/","sources":["layout/templateVariables.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Template variable extraction, validation, and resolution.\r\n *\r\n * This module provides utilities for working with parameterized templates:\r\n * - Extract variable definitions for UI generation\r\n * - Get default values (fail-safe fallback)\r\n * - Resolve user-provided values with validation\r\n * - Apply variables to template (substitute {{vars.*}} references)\r\n *\r\n * @module templateVariables\r\n */\r\n\r\nimport type { LayoutTemplate, TemplateVariableDef, TemplateVariableInfo } from './types';\r\n\r\n/**\r\n * Extract all variable definitions from a template.\r\n *\r\n * Returns an array of variable info objects suitable for UI generation.\r\n * Returns empty array if template has no variables.\r\n *\r\n * @param template - Layout template (may or may not have variables)\r\n * @returns Array of variable definitions with metadata\r\n *\r\n * @example\r\n * ```typescript\r\n * const vars = getTemplateVariables(template);\r\n * // [\r\n * // { name: 'hyresobjektsnummer', type: 'string', default: '2022-F2008', label: 'Hyresobjekt', ... },\r\n * // { name: 'showLegend', type: 'boolean', default: true, label: 'Show Legend', ... }\r\n * // ]\r\n *\r\n * // Use for UI generation\r\n * vars.forEach(v => {\r\n * if (v.type === 'string') {\r\n * renderTextField(v.label, v.default);\r\n * } else if (v.type === 'boolean') {\r\n * renderCheckbox(v.label, v.default);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function getTemplateVariables(template: LayoutTemplate): TemplateVariableInfo[] {\r\n if (!template.variables) return [];\r\n\r\n return Object.entries(template.variables).map(([name, def]) => ({\r\n name,\r\n type: def.type,\r\n default: def.default,\r\n label: def.label ?? name,\r\n description: def.description,\r\n format: def.format,\r\n min: def.min,\r\n max: def.max,\r\n step: def.step,\r\n minLength: def.minLength,\r\n maxLength: def.maxLength,\r\n pattern: def.pattern,\r\n enum: def.enum,\r\n items: def.items\r\n }));\r\n}\r\n\r\n/**\r\n * Extract all default values from a template.\r\n *\r\n * Returns an object with all variable names mapped to their default values.\r\n * These defaults are guaranteed to work - template can always render with them.\r\n *\r\n * Returns empty object if template has no variables.\r\n *\r\n * @param template - Layout template\r\n * @returns Object mapping variable names to default values\r\n *\r\n * @example\r\n * ```typescript\r\n * const defaults = getTemplateDefaults(template);\r\n * // { hyresobjektsnummer: '2022-F2008', showLegend: true, scaleDenom: 100 }\r\n *\r\n * // Can always render with defaults (fail-safe)\r\n * const { svg } = await renderDrawing({\r\n * template,\r\n * models,\r\n * variables: defaults // Guaranteed to work\r\n * });\r\n * ```\r\n */\r\nexport function getTemplateDefaults(template: LayoutTemplate): Record<string, any> {\r\n if (!template.variables) return {};\r\n\r\n const defaults: Record<string, any> = {};\r\n for (const [name, def] of Object.entries(template.variables)) {\r\n defaults[name] = def.default;\r\n }\r\n return defaults;\r\n}\r\n\r\n/**\r\n * Validate a value against a variable definition.\r\n *\r\n * Checks type and constraint validation (min/max, pattern, enum, etc.).\r\n *\r\n * @param value - Value to validate\r\n * @param varDef - Variable definition with constraints\r\n * @returns Validation result with ok flag and optional error message\r\n */\r\nfunction validateVariable(value: any, varDef: TemplateVariableDef): { ok: boolean; error?: string } {\r\n // Type check\r\n const actualType = Array.isArray(value) ? 'array' : typeof value;\r\n if (varDef.type === 'number' && actualType !== 'number') {\r\n return { ok: false, error: `Expected number, got ${actualType}` };\r\n }\r\n if (varDef.type === 'string' && actualType !== 'string') {\r\n return { ok: false, error: `Expected string, got ${actualType}` };\r\n }\r\n if (varDef.type === 'boolean' && actualType !== 'boolean') {\r\n return { ok: false, error: `Expected boolean, got ${actualType}` };\r\n }\r\n if (varDef.type === 'array' && actualType !== 'array') {\r\n return { ok: false, error: `Expected array, got ${actualType}` };\r\n }\r\n\r\n // String validation\r\n if (varDef.type === 'string' && typeof value === 'string') {\r\n if (varDef.minLength !== undefined && value.length < varDef.minLength) {\r\n return { ok: false, error: `String too short (min ${varDef.minLength})` };\r\n }\r\n if (varDef.maxLength !== undefined && value.length > varDef.maxLength) {\r\n return { ok: false, error: `String too long (max ${varDef.maxLength})` };\r\n }\r\n if (varDef.pattern) {\r\n try {\r\n const regex = new RegExp(varDef.pattern);\r\n if (!regex.test(value)) {\r\n return { ok: false, error: `Does not match pattern ${varDef.pattern}` };\r\n }\r\n } catch {\r\n return { ok: false, error: `Invalid pattern: ${varDef.pattern}` };\r\n }\r\n }\r\n if (varDef.enum && !varDef.enum.includes(value)) {\r\n return { ok: false, error: `Not in allowed values: ${varDef.enum.join(', ')}` };\r\n }\r\n }\r\n\r\n // Number validation\r\n if (varDef.type === 'number' && typeof value === 'number') {\r\n if (!Number.isFinite(value)) {\r\n return { ok: false, error: 'Must be a finite number' };\r\n }\r\n if (varDef.min !== undefined && value < varDef.min) {\r\n return { ok: false, error: `Too small (min ${varDef.min})` };\r\n }\r\n if (varDef.max !== undefined && value > varDef.max) {\r\n return { ok: false, error: `Too large (max ${varDef.max})` };\r\n }\r\n }\r\n\r\n return { ok: true };\r\n}\r\n\r\n/**\r\n * Resolve template variables by merging user values with defaults.\r\n *\r\n * This is a **fail-safe** function:\r\n * - Missing values → use default\r\n * - Invalid values → use default + warn\r\n * - Unknown variables → warn + ignore\r\n *\r\n * The result is ALWAYS valid and complete (safe to use for rendering).\r\n *\r\n * @param template - Layout template with variable definitions\r\n * @param userValues - Optional user-provided values (can be partial or undefined)\r\n * @returns Object with resolved values and any validation warnings\r\n *\r\n * @example\r\n * ```typescript\r\n * // User provides partial values\r\n * const { values, warnings } = resolveTemplateValues(template, {\r\n * hyresobjektsnummer: '2022-F2009' // Valid\r\n * // Other vars use defaults\r\n * });\r\n * // values = { hyresobjektsnummer: '2022-F2009', showLegend: true, scaleDenom: 100 }\r\n * // warnings = []\r\n *\r\n * // User provides invalid value\r\n * const { values, warnings } = resolveTemplateValues(template, {\r\n * scaleDenom: 9999 // Out of range (max: 500)\r\n * });\r\n * // values = { scaleDenom: 100, ... } ← Fell back to default\r\n * // warnings = [\"Invalid 'scaleDenom': Too large (max 500) - using default '100'\"]\r\n *\r\n * // Always safe to render\r\n * const resolvedTemplate = applyTemplateVariables(template, values);\r\n * ```\r\n */\r\nexport function resolveTemplateValues(\r\n template: LayoutTemplate,\r\n userValues?: Record<string, any>\r\n): { values: Record<string, any>; warnings: string[] } {\r\n const defaults = getTemplateDefaults(template);\r\n const warnings: string[] = [];\r\n\r\n // Start with all defaults (fail-safe!)\r\n const values = { ...defaults };\r\n\r\n // No user values or no variables → return defaults\r\n if (!userValues || !template.variables) {\r\n return { values, warnings };\r\n }\r\n\r\n // Merge and validate user values\r\n for (const [name, value] of Object.entries(userValues)) {\r\n const varDef = template.variables[name];\r\n\r\n // Unknown variable\r\n if (!varDef) {\r\n warnings.push(`Unknown variable '${name}' - ignoring`);\r\n continue;\r\n }\r\n\r\n // Validate value\r\n const validation = validateVariable(value, varDef);\r\n if (!validation.ok) {\r\n warnings.push(`Invalid '${name}': ${validation.error} - using default '${defaults[name]}'`);\r\n continue;\r\n }\r\n\r\n // Valid → use it\r\n values[name] = value;\r\n }\r\n\r\n return { values, warnings };\r\n}\r\n\r\n/**\r\n * Apply variables to a template by substituting {{vars.*}} references.\r\n *\r\n * Recursively scans the template and replaces all {{vars.variableName}} references\r\n * with the corresponding values from the provided values object.\r\n *\r\n * Returns a new template with all variables resolved (does not mutate input).\r\n *\r\n * @param template - Original template with {{vars.*}} references\r\n * @param values - Resolved variable values (from resolveTemplateValues)\r\n * @returns New template with all {{vars.*}} replaced by actual values\r\n *\r\n * @example\r\n * ```typescript\r\n * const template = {\r\n * elements: [\r\n * { kind: 'text', text: 'Hyresobjekt: {{vars.hyresobjektsnummer}}' },\r\n * { kind: 'viewport', clip: { where: { 'BIP.id': '{{vars.hyresobjektsnummer}}' } } }\r\n * ]\r\n * };\r\n *\r\n * const values = { hyresobjektsnummer: '2022-F2009' };\r\n * const resolved = applyTemplateVariables(template, values);\r\n * // {\r\n * // elements: [\r\n * // { kind: 'text', text: 'Hyresobjekt: 2022-F2009' },\r\n * // { kind: 'viewport', clip: { where: { 'BIP.id': '2022-F2009' } } }\r\n * // ]\r\n * // }\r\n * ```\r\n */\r\nexport function applyTemplateVariables(template: LayoutTemplate, values: Record<string, any>): LayoutTemplate {\r\n // Deep clone to avoid mutation\r\n const resolved: LayoutTemplate = JSON.parse(JSON.stringify(template));\r\n\r\n // Recursively replace {{vars.*}} in all string values\r\n function replaceInValue(obj: any): any {\r\n if (typeof obj === 'string') {\r\n return interpolateVariables(obj, values);\r\n }\r\n if (Array.isArray(obj)) {\r\n return obj.map(replaceInValue);\r\n }\r\n if (obj && typeof obj === 'object') {\r\n const result: any = {};\r\n for (const [key, val] of Object.entries(obj)) {\r\n result[key] = replaceInValue(val);\r\n }\r\n return result;\r\n }\r\n return obj;\r\n }\r\n\r\n // Apply to all template properties that might contain variable references\r\n resolved.elements = replaceInValue(resolved.elements);\r\n if (resolved.assets) {\r\n resolved.assets = replaceInValue(resolved.assets);\r\n }\r\n\r\n return resolved;\r\n}\r\n\r\n/**\r\n * Interpolate {{vars.*}} references in a string.\r\n *\r\n * Replaces all occurrences of {{vars.variableName}} with the corresponding value.\r\n * If variable is not found, keeps the placeholder and logs a warning.\r\n *\r\n * @param str - String potentially containing {{vars.*}} references\r\n * @param values - Variable values\r\n * @returns String with all {{vars.*}} replaced\r\n */\r\nfunction interpolateVariables(str: string, values: Record<string, any>): string {\r\n return str.replace(/\\{\\{vars\\.(\\w+)\\}\\}/g, (match, varName) => {\r\n if (varName in values) {\r\n const value = values[varName];\r\n // Convert to string (handles numbers, booleans, etc.)\r\n return String(value);\r\n }\r\n console.warn(`[templateVariables] Variable '${varName}' not found - keeping placeholder`);\r\n return match; // Keep {{vars.varName}} as-is\r\n });\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"templateVariables.js","sourceRoot":"/","sources":["layout/templateVariables.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Template variable extraction, validation, and resolution.\n *\n * This module provides utilities for working with parameterized templates:\n * - Extract variable definitions for UI generation\n * - Get default values (fail-safe fallback)\n * - Resolve user-provided values with validation\n * - Apply variables to template (substitute {{vars.*}} references)\n *\n * @module templateVariables\n */\n\nimport type { LayoutTemplate, TemplateVariableDef, TemplateVariableInfo } from './types';\n\n/**\n * Extract all variable definitions from a template.\n *\n * Returns an array of variable info objects suitable for UI generation.\n * Returns empty array if template has no variables.\n *\n * @param template - Layout template (may or may not have variables)\n * @returns Array of variable definitions with metadata\n *\n * @example\n * ```typescript\n * const vars = getTemplateVariables(template);\n * // [\n * // { name: 'hyresobjektsnummer', type: 'string', default: '2022-F2008', label: 'Hyresobjekt', ... },\n * // { name: 'showLegend', type: 'boolean', default: true, label: 'Show Legend', ... }\n * // ]\n *\n * // Use for UI generation\n * vars.forEach(v => {\n * if (v.type === 'string') {\n * renderTextField(v.label, v.default);\n * } else if (v.type === 'boolean') {\n * renderCheckbox(v.label, v.default);\n * }\n * });\n * ```\n */\nexport function getTemplateVariables(template: LayoutTemplate): TemplateVariableInfo[] {\n if (!template.variables) return [];\n\n return Object.entries(template.variables).map(([name, def]) => ({\n name,\n type: def.type,\n default: def.default,\n label: def.label ?? name,\n description: def.description,\n format: def.format,\n min: def.min,\n max: def.max,\n step: def.step,\n minLength: def.minLength,\n maxLength: def.maxLength,\n pattern: def.pattern,\n enum: def.enum,\n items: def.items\n }));\n}\n\n/**\n * Extract all default values from a template.\n *\n * Returns an object with all variable names mapped to their default values.\n * These defaults are guaranteed to work - template can always render with them.\n *\n * Returns empty object if template has no variables.\n *\n * @param template - Layout template\n * @returns Object mapping variable names to default values\n *\n * @example\n * ```typescript\n * const defaults = getTemplateDefaults(template);\n * // { hyresobjektsnummer: '2022-F2008', showLegend: true, scaleDenom: 100 }\n *\n * // Can always render with defaults (fail-safe)\n * const { svg } = await renderDrawing({\n * template,\n * models,\n * variables: defaults // Guaranteed to work\n * });\n * ```\n */\nexport function getTemplateDefaults(template: LayoutTemplate): Record<string, any> {\n if (!template.variables) return {};\n\n const defaults: Record<string, any> = {};\n for (const [name, def] of Object.entries(template.variables)) {\n defaults[name] = def.default;\n }\n return defaults;\n}\n\n/**\n * Validate a value against a variable definition.\n *\n * Checks type and constraint validation (min/max, pattern, enum, etc.).\n *\n * @param value - Value to validate\n * @param varDef - Variable definition with constraints\n * @returns Validation result with ok flag and optional error message\n */\nfunction validateVariable(value: any, varDef: TemplateVariableDef): { ok: boolean; error?: string } {\n // Type check\n const actualType = Array.isArray(value) ? 'array' : typeof value;\n if (varDef.type === 'number' && actualType !== 'number') {\n return { ok: false, error: `Expected number, got ${actualType}` };\n }\n if (varDef.type === 'string' && actualType !== 'string') {\n return { ok: false, error: `Expected string, got ${actualType}` };\n }\n if (varDef.type === 'boolean' && actualType !== 'boolean') {\n return { ok: false, error: `Expected boolean, got ${actualType}` };\n }\n if (varDef.type === 'array' && actualType !== 'array') {\n return { ok: false, error: `Expected array, got ${actualType}` };\n }\n\n // String validation\n if (varDef.type === 'string' && typeof value === 'string') {\n if (varDef.minLength !== undefined && value.length < varDef.minLength) {\n return { ok: false, error: `String too short (min ${varDef.minLength})` };\n }\n if (varDef.maxLength !== undefined && value.length > varDef.maxLength) {\n return { ok: false, error: `String too long (max ${varDef.maxLength})` };\n }\n if (varDef.pattern) {\n try {\n const regex = new RegExp(varDef.pattern);\n if (!regex.test(value)) {\n return { ok: false, error: `Does not match pattern ${varDef.pattern}` };\n }\n } catch {\n return { ok: false, error: `Invalid pattern: ${varDef.pattern}` };\n }\n }\n if (varDef.enum && !varDef.enum.includes(value)) {\n return { ok: false, error: `Not in allowed values: ${varDef.enum.join(', ')}` };\n }\n }\n\n // Number validation\n if (varDef.type === 'number' && typeof value === 'number') {\n if (!Number.isFinite(value)) {\n return { ok: false, error: 'Must be a finite number' };\n }\n if (varDef.min !== undefined && value < varDef.min) {\n return { ok: false, error: `Too small (min ${varDef.min})` };\n }\n if (varDef.max !== undefined && value > varDef.max) {\n return { ok: false, error: `Too large (max ${varDef.max})` };\n }\n }\n\n return { ok: true };\n}\n\n/**\n * Resolve template variables by merging user values with defaults.\n *\n * This is a **fail-safe** function:\n * - Missing values → use default\n * - Invalid values → use default + warn\n * - Unknown variables → warn + ignore\n *\n * The result is ALWAYS valid and complete (safe to use for rendering).\n *\n * @param template - Layout template with variable definitions\n * @param userValues - Optional user-provided values (can be partial or undefined)\n * @returns Object with resolved values and any validation warnings\n *\n * @example\n * ```typescript\n * // User provides partial values\n * const { values, warnings } = resolveTemplateValues(template, {\n * hyresobjektsnummer: '2022-F2009' // Valid\n * // Other vars use defaults\n * });\n * // values = { hyresobjektsnummer: '2022-F2009', showLegend: true, scaleDenom: 100 }\n * // warnings = []\n *\n * // User provides invalid value\n * const { values, warnings } = resolveTemplateValues(template, {\n * scaleDenom: 9999 // Out of range (max: 500)\n * });\n * // values = { scaleDenom: 100, ... } ← Fell back to default\n * // warnings = [\"Invalid 'scaleDenom': Too large (max 500) - using default '100'\"]\n *\n * // Always safe to render\n * const resolvedTemplate = applyTemplateVariables(template, values);\n * ```\n */\nexport function resolveTemplateValues(\n template: LayoutTemplate,\n userValues?: Record<string, any>\n): { values: Record<string, any>; warnings: string[] } {\n const defaults = getTemplateDefaults(template);\n const warnings: string[] = [];\n\n // Start with all defaults (fail-safe!)\n const values = { ...defaults };\n\n // No user values or no variables → return defaults\n if (!userValues || !template.variables) {\n return { values, warnings };\n }\n\n // Merge and validate user values\n for (const [name, value] of Object.entries(userValues)) {\n const varDef = template.variables[name];\n\n // Unknown variable\n if (!varDef) {\n warnings.push(`Unknown variable '${name}' - ignoring`);\n continue;\n }\n\n // Validate value\n const validation = validateVariable(value, varDef);\n if (!validation.ok) {\n warnings.push(`Invalid '${name}': ${validation.error} - using default '${defaults[name]}'`);\n continue;\n }\n\n // Valid → use it\n values[name] = value;\n }\n\n return { values, warnings };\n}\n\n/**\n * Apply variables to a template by substituting {{vars.*}} references.\n *\n * Recursively scans the template and replaces all {{vars.variableName}} references\n * with the corresponding values from the provided values object.\n *\n * Returns a new template with all variables resolved (does not mutate input).\n *\n * @param template - Original template with {{vars.*}} references\n * @param values - Resolved variable values (from resolveTemplateValues)\n * @returns New template with all {{vars.*}} replaced by actual values\n *\n * @example\n * ```typescript\n * const template = {\n * elements: [\n * { kind: 'text', text: 'Hyresobjekt: {{vars.hyresobjektsnummer}}' },\n * { kind: 'viewport', clip: { where: { 'BIP.id': '{{vars.hyresobjektsnummer}}' } } }\n * ]\n * };\n *\n * const values = { hyresobjektsnummer: '2022-F2009' };\n * const resolved = applyTemplateVariables(template, values);\n * // {\n * // elements: [\n * // { kind: 'text', text: 'Hyresobjekt: 2022-F2009' },\n * // { kind: 'viewport', clip: { where: { 'BIP.id': '2022-F2009' } } }\n * // ]\n * // }\n * ```\n */\nexport function applyTemplateVariables(template: LayoutTemplate, values: Record<string, any>): LayoutTemplate {\n // Deep clone to avoid mutation\n const resolved: LayoutTemplate = JSON.parse(JSON.stringify(template));\n\n // Recursively replace {{vars.*}} in all string values\n function replaceInValue(obj: any): any {\n if (typeof obj === 'string') {\n return interpolateVariables(obj, values);\n }\n if (Array.isArray(obj)) {\n return obj.map(replaceInValue);\n }\n if (obj && typeof obj === 'object') {\n const result: any = {};\n for (const [key, val] of Object.entries(obj)) {\n result[key] = replaceInValue(val);\n }\n return result;\n }\n return obj;\n }\n\n // Apply to all template properties that might contain variable references\n resolved.elements = replaceInValue(resolved.elements);\n if (resolved.assets) {\n resolved.assets = replaceInValue(resolved.assets);\n }\n\n return resolved;\n}\n\n/**\n * Interpolate {{vars.*}} references in a string.\n *\n * Replaces all occurrences of {{vars.variableName}} with the corresponding value.\n * If variable is not found, keeps the placeholder and logs a warning.\n *\n * @param str - String potentially containing {{vars.*}} references\n * @param values - Variable values\n * @returns String with all {{vars.*}} replaced\n */\nfunction interpolateVariables(str: string, values: Record<string, any>): string {\n return str.replace(/\\{\\{vars\\.(\\w+)\\}\\}/g, (match, varName) => {\n if (varName in values) {\n const value = values[varName];\n // Convert to string (handles numbers, booleans, etc.)\n return String(value);\n }\n console.warn(`[templateVariables] Variable '${varName}' not found - keeping placeholder`);\n return match; // Keep {{vars.varName}} as-is\n });\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typeGuards.js","sourceRoot":"/","sources":["layout/typeGuards.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE;IACzE,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,GAAW,EAAW,EAAE;IAC1D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC,CAAC","sourcesContent":["/** Type guard: true for plain objects, false for arrays, null, and primitives. */\
|
|
1
|
+
{"version":3,"file":"typeGuards.js","sourceRoot":"/","sources":["layout/typeGuards.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE;IACzE,OAAO,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAChF,CAAC,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,GAAW,EAAW,EAAE;IAC1D,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC,CAAC","sourcesContent":["/** Type guard: true for plain objects, false for arrays, null, and primitives. */\nexport const isRecord = (value: unknown): value is Record<string, unknown> => {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n};\n\n/** Safely read a property from an unknown value. Returns undefined for non-objects. */\nexport const getProp = (obj: unknown, key: string): unknown => {\n return isRecord(obj) ? obj[key] : undefined;\n};\n"]}
|
package/dist/layout/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"/","sources":["layout/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SectionPath, SectionLabel } from '@twinfinity/geometry';\r\n\r\nexport type PagePresetKey = 'a1' | 'a3' | 'a4';\r\nexport type PageOrientation = 'portrait' | 'landscape';\r\n\r\nexport interface PageSpec {\r\n widthMm: number;\r\n heightMm: number;\r\n marginMm?: number;\r\n backgroundColor?: string;\r\n}\r\n\r\nexport interface TextStyle {\r\n fontFamily: string;\r\n fontSizeMm: number;\r\n fontWeight?: 'normal' | 'bold' | 'lighter' | 'bolder' | string;\r\n color?: string;\r\n letterSpacingMm?: number;\r\n lineHeightMm?: number;\r\n}\r\n\r\nexport interface StyleMap {\r\n [styleName: string]: TextStyle;\r\n}\r\n\r\nexport interface AssetSpec {\r\n id: string;\r\n src?: string;\r\n content?: string;\r\n}\r\n\r\nexport type WhereCondition = Record<string, string | number | boolean | Array<string | number>>;\r\n\r\nexport interface ClipConfig {\r\n class?: string | string[];\r\n where: WhereCondition;\r\n expandByM?: number;\r\n clipLabels?: boolean;\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n strokeOpacity?: number;\r\n fill?: string;\r\n fillOpacity?: number;\r\n}\r\n\r\n/**\r\n * Template-driven fill rule for a viewport.\r\n *\r\n * Fill rules control which products get filled polygons in the drawing.\r\n * When no `fills` array is present on a viewport, no fills are rendered.\r\n *\r\n * Example:\r\n * ```json\r\n * { \"class\": \"space\", \"fill\": \"sample\", \"fillOpacity\": 0.35 }\r\n * { \"class\": \"walls\", \"fill\": \"#cccccc\", \"fillOpacity\": 0.5 }\r\n * ```\r\n */\r\nexport interface FillRule {\r\n /** IFC class filter (e.g. \"space\", \"walls\"). Matches ifcClass or ifcClassId. */\r\n class?: string | string[];\r\n /** Property-based filter (same as highlight/clip `where`). */\r\n where?: WhereCondition;\r\n /** Fill color: CSS hex (e.g. \"#80c8b4\"), \"sample\" to read from 3D model material, or \"legend\" to use colors assigned by legend. */\r\n fill: string;\r\n /** Fill opacity (0–1). Default: 0.35. */\r\n fillOpacity?: number;\r\n /** When true, only apply fill to items whose centroid is spatially inside the clip polygon.\r\n * Useful when expandByM reveals adjacent spaces that shouldn't be filled.\r\n * @default false */\r\n matchClipFilter?: boolean;\r\n}\r\n\r\nexport interface HighlightConfig {\r\n class?: string | string[];\r\n where: WhereCondition;\r\n /** Only highlight items that also appear in the referenced viewport (i.e., intersect both planes). */\r\n intersectsViewport?: string;\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n strokeOpacity?: number;\r\n fill?: string;\r\n fillOpacity?: number;\r\n}\r\n\r\nexport type LayoutElement =\r\n | ViewportElement\r\n | TextElement\r\n | ImageElement\r\n | LineElement\r\n | RectElement\r\n | CircleElement\r\n | PolylineElement\r\n | PolygonElement\r\n | ScaleBarElement\r\n | QrElement\r\n | LabelsElement\r\n | LegendElement\r\n | GroupElement;\r\n\r\nexport interface BaseElement {\r\n kind: LayoutElement['kind'];\r\n id: string;\r\n zIndex?: number;\r\n}\r\n\r\n/** Style for a single line category in a viewport. */\r\nexport interface ViewportLineStyle {\r\n /** Line width in mm. */\r\n widthMm?: number;\r\n /** Line color (CSS color string). */\r\n color?: string;\r\n /** SVG dash array (e.g. \"4 2\"). */\r\n dash?: string;\r\n /** Whether this category is rendered. Default: true. */\r\n enabled?: boolean;\r\n}\r\n\r\n/**\r\n * Per-category line styles for a viewport.\r\n *\r\n * Resolution order: category-specific → `default` → built-in defaults.\r\n *\r\n * Example:\r\n * ```json\r\n * {\r\n * \"default\": { \"widthMm\": 0.01, \"color\": \"#1f2937\" },\r\n * \"cutLines\": { \"widthMm\": 0.02, \"color\": \"#000000\" },\r\n * \"hiddenLines\": { \"widthMm\": 0.005, \"color\": \"#9ca3af\", \"dash\": \"4 2\" }\r\n * }\r\n * ```\r\n */\r\nexport interface ViewportLineStyles {\r\n /** Fallback style for any category not explicitly listed. */\r\n default?: ViewportLineStyle;\r\n cutLines?: ViewportLineStyle;\r\n visibleLines?: ViewportLineStyle;\r\n hiddenLines?: ViewportLineStyle;\r\n silhouettes?: ViewportLineStyle;\r\n featureEdges?: ViewportLineStyle;\r\n stairArrows?: ViewportLineStyle;\r\n outlineVisible?: ViewportLineStyle;\r\n outlineHidden?: ViewportLineStyle;\r\n}\r\n\r\n/**\r\n * Include filter for viewport geometry.\r\n *\r\n * Can be:\r\n * - String or string array: class names only\r\n * - Object with class + where: property-based filtering\r\n * - Preset reference: { preset: \"presetName\" }\r\n *\r\n * Examples:\r\n * ```json\r\n * \"includeClasses\": [\"space\", \"walls\"]\r\n * \"includeClasses\": [{ \"class\": \"space\", \"where\": { \"BIP.spacetype\": \"ROOM\" } }]\r\n * \"includeClasses\": { \"preset\": \"defaultBuilding\" }\r\n * ```\r\n */\r\nexport type IncludeClassesFilter =\r\n | string\r\n | string[]\r\n | { class?: string | string[]; where?: WhereCondition }\r\n | { preset: string };\r\n\r\n/**\r\n * Resolved include filter (after preset expansion).\r\n * Either a simple class name or a class+where filter.\r\n */\r\nexport type ResolvedIncludeFilter = string | { class?: string | string[]; where: WhereCondition };\r\n\r\nexport interface ViewportElement extends BaseElement {\r\n kind: 'viewport';\r\n xMm: number;\r\n yMm: number;\r\n widthMm: number;\r\n heightMm: number;\r\n scaleMode?: 'fit' | 'fitAndRound' | 'fixed';\r\n fixedScaleDenom?: number;\r\n preferredScales?: number[];\r\n scalePreset?: string;\r\n align?: 'center' | 'topLeft';\r\n rotationDeg?: number;\r\n includeClasses?: IncludeClassesFilter | IncludeClassesFilter[];\r\n /**\r\n * Per-category line styles. When set, overrides `lineWidthMm`/`lineColor`.\r\n * Use `lineStyles.default` for a single style across all categories,\r\n * or specify individual categories (cutLines, hiddenLines, etc.).\r\n */\r\n lineStyles?: ViewportLineStyles;\r\n /** Simple shorthand: default line width for all categories. Overridden by `lineStyles`. */\r\n lineWidthMm?: number;\r\n /** Simple shorthand: default line color for all categories. Overridden by `lineStyles`. */\r\n lineColor?: string;\r\n /**\r\n * Fill rules controlling which products get filled polygons.\r\n * When absent, no fills are rendered (default: no fill).\r\n */\r\n fills?: FillRule[];\r\n northArrow?: { assetId: string; sizeMm?: number; offsetXMm?: number; offsetYMm?: number; rotationDeg?: number };\r\n border?: { stroke?: string; strokeWidthMm?: number; dashed?: boolean };\r\n clip?: ClipConfig;\r\n highlight?: HighlightConfig;\r\n\r\n // Section configuration (overrides app defaults if specified)\r\n /**\r\n * Override cutting plane height for horizontal plans (Y coordinate).\r\n * If omitted, uses app default. Shorthand for planeNormal=[0,1,0], planePosition=cutHeight.\r\n */\r\n cutHeight?: number;\r\n /**\r\n * Override depth above/below cutting plane. If omitted, uses app default.\r\n * Only applicable for SLAB mode (PLANE mode doesn't use depth).\r\n */\r\n depth?: { above?: number; below?: number };\r\n /**\r\n * Plane normal vector [x, y, z]. Automatically normalized.\r\n * Takes precedence over cutHeight if both are specified.\r\n * Examples:\r\n * - [1, 0, 0] = vertical section perpendicular to X axis\r\n * - [0, 1, 0] = horizontal plan (same as using cutHeight)\r\n * - [0, 0, 1] = vertical section perpendicular to Z axis\r\n * - [1, 0, 1] = diagonal section (will be normalized)\r\n */\r\n planeNormal?: [number, number, number];\r\n /**\r\n * Distance from origin along the (normalized) plane normal.\r\n * Used with planeNormal to define the cutting plane.\r\n * The plane passes through the point: normalize(planeNormal) * planePosition.\r\n */\r\n planePosition?: number;\r\n /**\r\n * Section computation mode: 'slab' or 'plane'.\r\n * - 'slab': Optimized for horizontal floor plans. Uses depth above/below to include geometry.\r\n * - 'plane': Arbitrary plane cuts (vertical sections, diagonal sections). Computes exact intersection.\r\n *\r\n * If omitted, mode is auto-detected from plane orientation:\r\n * - Horizontal planes (planeNormal ≈ Y-axis) → 'slab'\r\n * - Vertical or diagonal planes → 'plane'\r\n */\r\n mode?: 'slab' | 'plane';\r\n}\r\n\r\nexport interface TextElement extends BaseElement {\r\n kind: 'text';\r\n xMm: number;\r\n yMm: number;\r\n textStyle?: string;\r\n text: string;\r\n maxWidthMm?: number;\r\n align?: 'left' | 'center' | 'right';\r\n}\r\n\r\nexport interface ImageElement extends BaseElement {\r\n kind: 'image';\r\n xMm: number;\r\n yMm: number;\r\n widthMm: number;\r\n heightMm: number;\r\n assetId: string;\r\n fit?: 'contain' | 'cover' | 'stretch';\r\n}\r\n\r\nexport interface LineElement extends BaseElement {\r\n kind: 'line';\r\n x1Mm: number;\r\n y1Mm: number;\r\n x2Mm: number;\r\n y2Mm: number;\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n dashed?: boolean;\r\n}\r\n\r\nexport interface RectElement extends BaseElement {\r\n kind: 'rect';\r\n xMm: number;\r\n yMm: number;\r\n widthMm: number;\r\n heightMm: number;\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n fill?: string;\r\n dashed?: boolean;\r\n}\r\n\r\nexport interface CircleElement extends BaseElement {\r\n kind: 'circle';\r\n cxMm: number;\r\n cyMm: number;\r\n rMm: number;\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n fill?: string;\r\n dashed?: boolean;\r\n}\r\n\r\nexport interface PointMm {\r\n xMm: number;\r\n yMm: number;\r\n}\r\n\r\nexport interface PolylineElement extends BaseElement {\r\n kind: 'polyline';\r\n points: PointMm[];\r\n stroke: string;\r\n strokeWidthMm: number;\r\n dashed?: boolean;\r\n}\r\n\r\nexport interface PolygonElement extends BaseElement {\r\n kind: 'polygon';\r\n points: PointMm[];\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n fill?: string;\r\n dashed?: boolean;\r\n}\r\n\r\nexport interface ScaleBarElement extends BaseElement {\r\n kind: 'scaleBar';\r\n xMm: number;\r\n yMm: number;\r\n maxLengthMm: number;\r\n unit?: string;\r\n segments?: number;\r\n labelEvery?: number;\r\n stroke?: string;\r\n strokeWidthMm?: number;\r\n textStyle?: string;\r\n viewport?: string;\r\n}\r\n\r\nexport interface QrElement extends BaseElement {\r\n kind: 'qr';\r\n xMm: number;\r\n yMm: number;\r\n sizeMm: number;\r\n role?: 'main' | 'anchor' | 'custom';\r\n anchorCorner?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';\r\n viewport?: string;\r\n dataTemplate?: string;\r\n}\r\n\r\nexport interface LabelsElement extends BaseElement {\r\n kind: 'labels';\r\n viewport: string;\r\n class?: string | string[];\r\n where?: WhereCondition;\r\n text: string;\r\n textStyle?: string;\r\n align?: 'left' | 'center' | 'right';\r\n maxWidthMm?: number;\r\n /** When true, only show labels for items whose position is spatially inside the clip polygon.\r\n * @default false */\r\n matchClipFilter?: boolean;\r\n}\r\n\r\nexport type LegendAnchor = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';\r\n\r\nexport interface LegendColumn {\r\n id: string;\r\n label: string;\r\n property: string;\r\n format?: { digits?: number; prefix?: string; suffix?: string };\r\n align?: 'left' | 'center' | 'right';\r\n minWidthMm?: number;\r\n maxWidthMm?: number;\r\n /**\r\n * Enable summary (total) calculation for this column.\r\n * When true, the summary row will show the sum of all values in this column.\r\n * Only works for numerical columns.\r\n * @default false\r\n */\r\n summary?: boolean;\r\n}\r\n\r\nexport interface LegendElement extends BaseElement {\r\n kind: 'legend';\r\n viewport: string;\r\n class?: string | string[];\r\n where?: WhereCondition;\r\n clipToViewport?: boolean;\r\n anchor?: LegendAnchor;\r\n anchorPositionMm?: { xMm: number; yMm: number };\r\n marginMm?: number;\r\n textStyle?: string;\r\n showColorColumn?: boolean;\r\n colorColumnWidthMm?: number;\r\n cellPaddingMm?: number;\r\n borderStrokeWidthMm?: number;\r\n borderStrokeColor?: string;\r\n gridStrokeWidthMm?: number;\r\n gridStrokeColor?: string;\r\n headerStrokeWidthMm?: number;\r\n headerStrokeColor?: string;\r\n columns: LegendColumn[];\r\n /**\r\n * Summary row configuration.\r\n * Displays a totals row at the bottom of the legend.\r\n * Columns with `summary: true` will show their sum in the summary row.\r\n *\r\n * @example\r\n * ```json\r\n * {\r\n * \"summary\": {\r\n * \"enabled\": true,\r\n * \"label\": \"Total\"\r\n * },\r\n * \"columns\": [\r\n * { \"id\": \"name\", \"label\": \"Room\", \"property\": \"BIP.spacename\" },\r\n * { \"id\": \"area\", \"label\": \"Area\", \"property\": \"BIP.area\", \"summary\": true }\r\n * ]\r\n * }\r\n * ```\r\n */\r\n summary?: {\r\n /** Enable summary row. */\r\n enabled: boolean;\r\n /** Label for first column in summary row. Defaults to \"Total\". */\r\n label?: string;\r\n };\r\n /**\r\n * Group legend rows by a specific column property.\r\n * When enabled, rows with identical values in the groupBy column are consolidated into a single row.\r\n * Numerical columns are summed across grouped rows; non-numerical columns take the first value.\r\n *\r\n * @example\r\n * ```json\r\n * {\r\n * \"groupBy\": \"BIP.spacename\",\r\n * \"columns\": [\r\n * { \"id\": \"name\", \"label\": \"Room\", \"property\": \"BIP.spacename\" },\r\n * { \"id\": \"area\", \"label\": \"Area\", \"property\": \"BIP.area\", \"format\": { \"digits\": 2 } }\r\n * ]\r\n * }\r\n * ```\r\n * This groups all rooms with the same name and sums their areas.\r\n */\r\n groupBy?: string;\r\n /**\r\n * Assign colors to legend rows and apply them to the referenced viewport.\r\n * When enabled, generates colors for each legend row (or group if groupBy is used).\r\n * The viewport can use these colors by setting fill: \"legend\" in its fill rules.\r\n *\r\n * @example\r\n * ```json\r\n * {\r\n * \"assignColors\": {\r\n * \"enabled\": true,\r\n * \"palette\": \"auto\"\r\n * },\r\n * \"groupBy\": \"BIP.spacename\"\r\n * }\r\n * ```\r\n * Generates distinct colors for each room type, applied to matching spaces in viewport.\r\n */\r\n assignColors?: {\r\n /** Enable color assignment to viewport items. */\r\n enabled: boolean;\r\n /** Color palette: \"auto\" for auto-generated colors, or array of hex colors. */\r\n palette?: 'auto' | string[];\r\n };\r\n}\r\n\r\nexport interface GroupTransform {\r\n translateXMm?: number;\r\n translateYMm?: number;\r\n rotateDeg?: number;\r\n scaleX?: number;\r\n scaleY?: number;\r\n}\r\n\r\nexport interface GroupClipRect {\r\n type: 'rect';\r\n xMm: number;\r\n yMm: number;\r\n widthMm: number;\r\n heightMm: number;\r\n}\r\n\r\nexport interface GroupElement extends BaseElement {\r\n kind: 'group';\r\n opacity?: number;\r\n transform?: GroupTransform;\r\n clip?: GroupClipRect;\r\n children: LayoutElement[];\r\n}\r\n\r\n/**\r\n * Template variable definition.\r\n *\r\n * Variables allow templates to be parameterized and reused with different values.\r\n * All variables MUST have a default value to ensure templates work without user input.\r\n *\r\n * Variables are referenced in templates using {{vars.variableName}} syntax.\r\n *\r\n * Example:\r\n * ```json\r\n * {\r\n * \"variables\": {\r\n * \"hyresobjektsnummer\": {\r\n * \"type\": \"string\",\r\n * \"default\": \"2022-F2008\",\r\n * \"label\": \"Hyresobjekt Number\",\r\n * \"description\": \"The rental object identifier\"\r\n * }\r\n * },\r\n * \"elements\": [\r\n * {\r\n * \"kind\": \"text\",\r\n * \"text\": \"Hyresobjekt: {{vars.hyresobjektsnummer}}\"\r\n * }\r\n * ]\r\n * }\r\n * ```\r\n */\r\nexport interface TemplateVariableDef {\r\n /** Variable type. Determines validation and UI control type. */\r\n type: 'string' | 'number' | 'boolean' | 'array';\r\n\r\n /**\r\n * Default value for this variable.\r\n * REQUIRED - ensures template always works even without user input.\r\n * Must match the specified type.\r\n */\r\n default: any;\r\n\r\n /** Human-readable label for UI generation. Defaults to variable name. */\r\n label?: string;\r\n\r\n /** Description/help text for UI generation. */\r\n description?: string;\r\n\r\n /**\r\n * Semantic format hint for UI generation.\r\n * Provides additional context for rendering appropriate controls.\r\n *\r\n * - `color`: Renders color picker (for type: 'string')\r\n * - `email`: Renders email input with validation (for type: 'string')\r\n * - `url`: Renders URL input with validation (for type: 'string')\r\n * - `date`: Renders date picker (for type: 'string')\r\n *\r\n * @example\r\n * ```json\r\n * {\r\n * \"lineColor\": {\r\n * \"type\": \"string\",\r\n * \"format\": \"color\",\r\n * \"default\": \"#1f2937\"\r\n * }\r\n * }\r\n * ```\r\n */\r\n format?: 'color' | 'email' | 'url' | 'date';\r\n\r\n // String validation\r\n /** Minimum string length (for type: 'string'). */\r\n minLength?: number;\r\n /** Maximum string length (for type: 'string'). */\r\n maxLength?: number;\r\n /** Regex pattern for validation (for type: 'string'). */\r\n pattern?: string;\r\n /** Allowed values (dropdown options). */\r\n enum?: string[];\r\n\r\n // Number validation\r\n /** Minimum value (for type: 'number'). */\r\n min?: number;\r\n /** Maximum value (for type: 'number'). */\r\n max?: number;\r\n /** Step size for UI controls (for type: 'number'). */\r\n step?: number;\r\n\r\n // Array validation\r\n /** Array item specification (for type: 'array'). */\r\n items?: {\r\n type: 'string' | 'number';\r\n enum?: any[];\r\n };\r\n}\r\n\r\n/**\r\n * Variable information extracted from template for UI generation.\r\n */\r\nexport interface TemplateVariableInfo {\r\n name: string;\r\n type: 'string' | 'number' | 'boolean' | 'array';\r\n default: any;\r\n label: string;\r\n description?: string;\r\n format?: 'color' | 'email' | 'url' | 'date';\r\n min?: number;\r\n max?: number;\r\n step?: number;\r\n minLength?: number;\r\n maxLength?: number;\r\n pattern?: string;\r\n enum?: any[];\r\n items?: {\r\n type: 'string' | 'number';\r\n enum?: any[];\r\n };\r\n}\r\n\r\nexport interface LayoutTemplate {\r\n /** JSON Schema reference for editor autocompletion. Ignored at runtime. */\r\n $schema?: string;\r\n id?: string;\r\n\r\n /**\r\n * Template variables for parameterization.\r\n *\r\n * Variables allow templates to be reused with different values.\r\n * All variables MUST have defaults - templates work without user input.\r\n * Reference variables in template using {{vars.variableName}} syntax.\r\n *\r\n * Optional - templates without variables work as before (backward compatible).\r\n */\r\n variables?: Record<string, TemplateVariableDef>;\r\n\r\n page: PageSpec;\r\n styles?: StyleMap;\r\n assets?: AssetSpec[];\r\n elements: LayoutElement[];\r\n presets?: {\r\n includeClasses?: Record<string, string[]>;\r\n scales?: Record<string, { preferredScales?: number[] }>;\r\n };\r\n /** Allow fetching remote assets (images, fonts). Default: false. */\r\n allowRemoteAssets?: boolean;\r\n}\r\n\r\nexport interface ModelMetadataEntry {\r\n productId?: string;\r\n ifcClass?: string;\r\n properties?: any;\r\n [key: string]: any;\r\n}\r\n\r\nexport type ModelMetadata = Record<string, ModelMetadataEntry>;\r\n\r\nexport interface ProductFieldPaths {\r\n idPath?: string;\r\n namePath?: string;\r\n areaPath?: string;\r\n classPath?: string;\r\n}\r\n\r\nexport interface NormalizedProductItem {\r\n gid?: string;\r\n /** IfcClassType category (e.g. \"space\", \"walls\"). Used for template class filters. */\r\n ifcClass?: string | null;\r\n /** PredefinedIfcClassId (e.g. \"IfcSpace\", \"IfcWall\"). Used as fallback for class matching. */\r\n ifcClassId?: string | null;\r\n propertySets: Record<string, Record<string, { value: any; unit?: string; type?: string }>>;\r\n productId: string;\r\n productName: string;\r\n area: number | null;\r\n x?: number | null;\r\n z?: number | null;\r\n polylines?: Array<{ points?: Array<{ x: number; z: number }> }>;\r\n [key: string]: any;\r\n}\r\n\r\nexport type SectionStateValidationResult =\r\n | { ok: true }\r\n | {\r\n ok: false;\r\n errors: string[];\r\n };\r\n\r\nexport interface LabelStyle {\r\n /** Text fill color. Default: \"#374151\" */\r\n fill?: string;\r\n /** Font family. Default: '\"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif' */\r\n fontFamily?: string;\r\n /** Font weight. Default: \"300\" */\r\n fontWeight?: string;\r\n}\r\n\r\nexport interface SvgModelView {\r\n groupSvg: string;\r\n widthModel: number;\r\n heightModel: number;\r\n geometryTransform?: string;\r\n transform?: {\r\n viewMinX: number;\r\n viewMaxX: number;\r\n viewMinZ: number;\r\n viewMaxZ: number;\r\n viewWidth: number;\r\n viewHeight: number;\r\n };\r\n rawPaths?: SectionPath[];\r\n rawLabels?: SectionLabel[];\r\n labelStyle?: LabelStyle;\r\n}\r\n\r\nexport type ModelContext = Record<string, SvgModelView>;\r\nexport type LayoutData = Record<string, any>;\r\nexport type ViewportItemsMap = Record<string, { items: NormalizedProductItem[] }>;\r\nexport type LayoutDataViewports = { viewports: ViewportItemsMap } & Record<string, any>;\r\n\r\nexport interface ValidationOptions {\r\n allowRemoteAssets?: boolean;\r\n allowDataUrls?: boolean;\r\n maxViewports?: number;\r\n}\r\n\r\nexport interface ValidationIssue {\r\n path: string;\r\n message: string;\r\n}\r\n\r\nexport type ValidationResult = { ok: true; template: any } | { ok: false; errors: ValidationIssue[] };\r\n\r\nexport interface ComputedViewportScale {\r\n denom: number;\r\n mmPerModelUnit: number;\r\n fits: boolean;\r\n}\r\n\r\nexport interface SectionResult {\r\n perProduct: Array<{\r\n product: any;\r\n segments: Array<[any, any]>;\r\n polylines: Array<{ points: any[]; closed: boolean }>;\r\n }>;\r\n}\r\n\r\nexport interface RenderDrawingInput {\r\n template: LayoutTemplate;\r\n /**\r\n * Section geometry to render.\r\n *\r\n * - Pass a single `SectionResult` to use the same geometry for every viewport.\r\n * - Pass a `Record<string, SectionResult>` keyed by viewport ID for per-viewport control.\r\n *\r\n * Either `sections` or `models` must be provided. When `models` is provided,\r\n * `sections` is ignored and the pre-built SvgModelViews are used directly.\r\n */\r\n sections?: SectionResult | Record<string, SectionResult>;\r\n /**\r\n * Pre-built model views keyed by viewport ID.\r\n *\r\n * Use this when geometry comes from a source other than `computePlaneSection`\r\n * (e.g. SlabSection). The caller is responsible for producing `SvgModelView`\r\n * objects; `renderDrawing` will pass them straight to `renderLayout`.\r\n */\r\n models?: ModelContext;\r\n options?: {\r\n allowRemoteAssets?: boolean;\r\n qrContext?: {\r\n host?: string | null;\r\n containerName?: string | null;\r\n floorName?: string | null;\r\n planeHeight?: number | null;\r\n };\r\n /**\r\n * Embed room labels directly in the section SVG.\r\n *\r\n * Only use this when the template does NOT have its own `labels` elements,\r\n * otherwise you will get duplicate labels. When omitted, renderDrawing\r\n * auto-detects template `labels` elements and skips embedding.\r\n */\r\n roomLabels?: {\r\n /** Dot-separated path to the label text under product.properties.data. */\r\n labelPath: string;\r\n /** IFC class name filter (e.g. \"space\"). If omitted, all entries are included. */\r\n classFilter?: string;\r\n };\r\n /**\r\n * Raw per-product entries for automatic item building when using `models`.\r\n *\r\n * Each entry needs a `product` (BIM object with gid, name, class, properties)\r\n * and `polylines` (for centroid computation and fill polygon rendering).\r\n *\r\n * When provided, renderDrawing builds NormalizedProductItems per viewport,\r\n * resolving fill colors from each viewport's fill rules. This replaces\r\n * manual item construction in consumer code.\r\n *\r\n * Ignored when `viewportItems` is also provided (explicit items take priority).\r\n */\r\n productEntries?: Array<{\r\n product: unknown;\r\n polylines: Array<{ points: Array<{ x: number; z: number }> }>;\r\n }>;\r\n /**\r\n * Pre-built product items when `models` is provided directly.\r\n * Keyed by viewport ID, each containing an array of NormalizedProductItems.\r\n * Used for legends, labels, and other template elements that need item data.\r\n *\r\n * Takes priority over `productEntries` when both are provided.\r\n */\r\n viewportItems?: Record<string, { items: NormalizedProductItem[] }>;\r\n /**\r\n * App default section settings. Used as fallback when viewports don't specify\r\n * their own cutHeight/depth configuration.\r\n */\r\n sectionDefaults?: {\r\n cutHeight?: number;\r\n depth?: { above?: number; below?: number };\r\n };\r\n /**\r\n * Template variable values.\r\n *\r\n * When template defines `variables`, provide values here to customize the output.\r\n * Missing values automatically use defaults from template (fail-safe).\r\n * Invalid values fall back to defaults with warnings.\r\n *\r\n * Optional - if not provided, all template defaults are used.\r\n *\r\n * @example\r\n * ```typescript\r\n * await renderDrawing({\r\n * template,\r\n * models,\r\n * options: {\r\n * variables: {\r\n * hyresobjektsnummer: '2022-F2009',\r\n * showLegend: true\r\n * }\r\n * }\r\n * });\r\n * ```\r\n */\r\n variables?: Record<string, any>;\r\n };\r\n}\r\n\r\nexport interface RenderDrawingResult {\r\n svg: string;\r\n warnings: string[];\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"/","sources":["layout/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { SectionPath, SectionLabel } from '@twinfinity/geometry';\n\nexport type PagePresetKey = 'a1' | 'a3' | 'a4';\nexport type PageOrientation = 'portrait' | 'landscape';\n\nexport interface PageSpec {\n widthMm: number;\n heightMm: number;\n marginMm?: number;\n backgroundColor?: string;\n}\n\nexport interface TextStyle {\n fontFamily: string;\n fontSizeMm: number;\n fontWeight?: 'normal' | 'bold' | 'lighter' | 'bolder' | string;\n color?: string;\n letterSpacingMm?: number;\n lineHeightMm?: number;\n}\n\nexport interface StyleMap {\n [styleName: string]: TextStyle;\n}\n\nexport interface AssetSpec {\n id: string;\n src?: string;\n content?: string;\n}\n\nexport type WhereCondition = Record<string, string | number | boolean | Array<string | number>>;\n\nexport interface ClipConfig {\n class?: string | string[];\n where: WhereCondition;\n expandByM?: number;\n clipLabels?: boolean;\n stroke?: string;\n strokeWidthMm?: number;\n strokeOpacity?: number;\n fill?: string;\n fillOpacity?: number;\n}\n\n/**\n * Template-driven fill rule for a viewport.\n *\n * Fill rules control which products get filled polygons in the drawing.\n * When no `fills` array is present on a viewport, no fills are rendered.\n *\n * Example:\n * ```json\n * { \"class\": \"space\", \"fill\": \"sample\", \"fillOpacity\": 0.35 }\n * { \"class\": \"walls\", \"fill\": \"#cccccc\", \"fillOpacity\": 0.5 }\n * ```\n */\nexport interface FillRule {\n /** IFC class filter (e.g. \"space\", \"walls\"). Matches ifcClass or ifcClassId. */\n class?: string | string[];\n /** Property-based filter (same as highlight/clip `where`). */\n where?: WhereCondition;\n /** Fill color: CSS hex (e.g. \"#80c8b4\"), \"sample\" to read from 3D model material, or \"legend\" to use colors assigned by legend. */\n fill: string;\n /** Fill opacity (0–1). Default: 0.35. */\n fillOpacity?: number;\n /** When true, only apply fill to items whose centroid is spatially inside the clip polygon.\n * Useful when expandByM reveals adjacent spaces that shouldn't be filled.\n * @default false */\n matchClipFilter?: boolean;\n}\n\nexport interface HighlightConfig {\n class?: string | string[];\n where: WhereCondition;\n /** Only highlight items that also appear in the referenced viewport (i.e., intersect both planes). */\n intersectsViewport?: string;\n stroke?: string;\n strokeWidthMm?: number;\n strokeOpacity?: number;\n fill?: string;\n fillOpacity?: number;\n}\n\nexport type LayoutElement =\n | ViewportElement\n | TextElement\n | ImageElement\n | LineElement\n | RectElement\n | CircleElement\n | PolylineElement\n | PolygonElement\n | ScaleBarElement\n | QrElement\n | LabelsElement\n | LegendElement\n | GroupElement;\n\nexport interface BaseElement {\n kind: LayoutElement['kind'];\n id: string;\n zIndex?: number;\n}\n\n/** Style for a single line category in a viewport. */\nexport interface ViewportLineStyle {\n /** Line width in mm. */\n widthMm?: number;\n /** Line color (CSS color string). */\n color?: string;\n /** SVG dash array (e.g. \"4 2\"). */\n dash?: string;\n /** Whether this category is rendered. Default: true. */\n enabled?: boolean;\n}\n\n/**\n * Per-category line styles for a viewport.\n *\n * Resolution order: category-specific → `default` → built-in defaults.\n *\n * Example:\n * ```json\n * {\n * \"default\": { \"widthMm\": 0.01, \"color\": \"#1f2937\" },\n * \"cutLines\": { \"widthMm\": 0.02, \"color\": \"#000000\" },\n * \"hiddenLines\": { \"widthMm\": 0.005, \"color\": \"#9ca3af\", \"dash\": \"4 2\" }\n * }\n * ```\n */\nexport interface ViewportLineStyles {\n /** Fallback style for any category not explicitly listed. */\n default?: ViewportLineStyle;\n cutLines?: ViewportLineStyle;\n visibleLines?: ViewportLineStyle;\n hiddenLines?: ViewportLineStyle;\n silhouettes?: ViewportLineStyle;\n featureEdges?: ViewportLineStyle;\n stairArrows?: ViewportLineStyle;\n outlineVisible?: ViewportLineStyle;\n outlineHidden?: ViewportLineStyle;\n}\n\n/**\n * Include filter for viewport geometry.\n *\n * Can be:\n * - String or string array: class names only\n * - Object with class + where: property-based filtering\n * - Preset reference: { preset: \"presetName\" }\n *\n * Examples:\n * ```json\n * \"includeClasses\": [\"space\", \"walls\"]\n * \"includeClasses\": [{ \"class\": \"space\", \"where\": { \"BIP.spacetype\": \"ROOM\" } }]\n * \"includeClasses\": { \"preset\": \"defaultBuilding\" }\n * ```\n */\nexport type IncludeClassesFilter =\n | string\n | string[]\n | { class?: string | string[]; where?: WhereCondition }\n | { preset: string };\n\n/**\n * Resolved include filter (after preset expansion).\n * Either a simple class name or a class+where filter.\n */\nexport type ResolvedIncludeFilter = string | { class?: string | string[]; where: WhereCondition };\n\nexport interface ViewportElement extends BaseElement {\n kind: 'viewport';\n xMm: number;\n yMm: number;\n widthMm: number;\n heightMm: number;\n scaleMode?: 'fit' | 'fitAndRound' | 'fixed';\n fixedScaleDenom?: number;\n preferredScales?: number[];\n scalePreset?: string;\n align?: 'center' | 'topLeft';\n rotationDeg?: number;\n includeClasses?: IncludeClassesFilter | IncludeClassesFilter[];\n /**\n * Per-category line styles. When set, overrides `lineWidthMm`/`lineColor`.\n * Use `lineStyles.default` for a single style across all categories,\n * or specify individual categories (cutLines, hiddenLines, etc.).\n */\n lineStyles?: ViewportLineStyles;\n /** Simple shorthand: default line width for all categories. Overridden by `lineStyles`. */\n lineWidthMm?: number;\n /** Simple shorthand: default line color for all categories. Overridden by `lineStyles`. */\n lineColor?: string;\n /**\n * Fill rules controlling which products get filled polygons.\n * When absent, no fills are rendered (default: no fill).\n */\n fills?: FillRule[];\n northArrow?: { assetId: string; sizeMm?: number; offsetXMm?: number; offsetYMm?: number; rotationDeg?: number };\n border?: { stroke?: string; strokeWidthMm?: number; dashed?: boolean };\n clip?: ClipConfig;\n highlight?: HighlightConfig;\n\n // Section configuration (overrides app defaults if specified)\n /**\n * Override cutting plane height for horizontal plans (Y coordinate).\n * If omitted, uses app default. Shorthand for planeNormal=[0,1,0], planePosition=cutHeight.\n */\n cutHeight?: number;\n /**\n * Override depth above/below cutting plane. If omitted, uses app default.\n * Only applicable for SLAB mode (PLANE mode doesn't use depth).\n */\n depth?: { above?: number; below?: number };\n /**\n * Plane normal vector [x, y, z]. Automatically normalized.\n * Takes precedence over cutHeight if both are specified.\n * Examples:\n * - [1, 0, 0] = vertical section perpendicular to X axis\n * - [0, 1, 0] = horizontal plan (same as using cutHeight)\n * - [0, 0, 1] = vertical section perpendicular to Z axis\n * - [1, 0, 1] = diagonal section (will be normalized)\n */\n planeNormal?: [number, number, number];\n /**\n * Distance from origin along the (normalized) plane normal.\n * Used with planeNormal to define the cutting plane.\n * The plane passes through the point: normalize(planeNormal) * planePosition.\n */\n planePosition?: number;\n /**\n * Section computation mode: 'slab' or 'plane'.\n * - 'slab': Optimized for horizontal floor plans. Uses depth above/below to include geometry.\n * - 'plane': Arbitrary plane cuts (vertical sections, diagonal sections). Computes exact intersection.\n *\n * If omitted, mode is auto-detected from plane orientation:\n * - Horizontal planes (planeNormal ≈ Y-axis) → 'slab'\n * - Vertical or diagonal planes → 'plane'\n */\n mode?: 'slab' | 'plane';\n}\n\nexport interface TextElement extends BaseElement {\n kind: 'text';\n xMm: number;\n yMm: number;\n textStyle?: string;\n text: string;\n maxWidthMm?: number;\n align?: 'left' | 'center' | 'right';\n}\n\nexport interface ImageElement extends BaseElement {\n kind: 'image';\n xMm: number;\n yMm: number;\n widthMm: number;\n heightMm: number;\n assetId: string;\n fit?: 'contain' | 'cover' | 'stretch';\n}\n\nexport interface LineElement extends BaseElement {\n kind: 'line';\n x1Mm: number;\n y1Mm: number;\n x2Mm: number;\n y2Mm: number;\n stroke?: string;\n strokeWidthMm?: number;\n dashed?: boolean;\n}\n\nexport interface RectElement extends BaseElement {\n kind: 'rect';\n xMm: number;\n yMm: number;\n widthMm: number;\n heightMm: number;\n stroke?: string;\n strokeWidthMm?: number;\n fill?: string;\n dashed?: boolean;\n}\n\nexport interface CircleElement extends BaseElement {\n kind: 'circle';\n cxMm: number;\n cyMm: number;\n rMm: number;\n stroke?: string;\n strokeWidthMm?: number;\n fill?: string;\n dashed?: boolean;\n}\n\nexport interface PointMm {\n xMm: number;\n yMm: number;\n}\n\nexport interface PolylineElement extends BaseElement {\n kind: 'polyline';\n points: PointMm[];\n stroke: string;\n strokeWidthMm: number;\n dashed?: boolean;\n}\n\nexport interface PolygonElement extends BaseElement {\n kind: 'polygon';\n points: PointMm[];\n stroke?: string;\n strokeWidthMm?: number;\n fill?: string;\n dashed?: boolean;\n}\n\nexport interface ScaleBarElement extends BaseElement {\n kind: 'scaleBar';\n xMm: number;\n yMm: number;\n maxLengthMm: number;\n unit?: string;\n segments?: number;\n labelEvery?: number;\n stroke?: string;\n strokeWidthMm?: number;\n textStyle?: string;\n viewport?: string;\n}\n\nexport interface QrElement extends BaseElement {\n kind: 'qr';\n xMm: number;\n yMm: number;\n sizeMm: number;\n role?: 'main' | 'anchor' | 'custom';\n anchorCorner?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';\n viewport?: string;\n dataTemplate?: string;\n}\n\nexport interface LabelsElement extends BaseElement {\n kind: 'labels';\n viewport: string;\n class?: string | string[];\n where?: WhereCondition;\n text: string;\n textStyle?: string;\n align?: 'left' | 'center' | 'right';\n maxWidthMm?: number;\n /** When true, only show labels for items whose position is spatially inside the clip polygon.\n * @default false */\n matchClipFilter?: boolean;\n}\n\nexport type LegendAnchor = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';\n\nexport interface LegendColumn {\n id: string;\n label: string;\n property: string;\n format?: { digits?: number; prefix?: string; suffix?: string };\n align?: 'left' | 'center' | 'right';\n minWidthMm?: number;\n maxWidthMm?: number;\n /**\n * Enable summary (total) calculation for this column.\n * When true, the summary row will show the sum of all values in this column.\n * Only works for numerical columns.\n * @default false\n */\n summary?: boolean;\n}\n\nexport interface LegendElement extends BaseElement {\n kind: 'legend';\n viewport: string;\n class?: string | string[];\n where?: WhereCondition;\n clipToViewport?: boolean;\n anchor?: LegendAnchor;\n anchorPositionMm?: { xMm: number; yMm: number };\n marginMm?: number;\n textStyle?: string;\n showColorColumn?: boolean;\n colorColumnWidthMm?: number;\n cellPaddingMm?: number;\n borderStrokeWidthMm?: number;\n borderStrokeColor?: string;\n gridStrokeWidthMm?: number;\n gridStrokeColor?: string;\n headerStrokeWidthMm?: number;\n headerStrokeColor?: string;\n columns: LegendColumn[];\n /**\n * Summary row configuration.\n * Displays a totals row at the bottom of the legend.\n * Columns with `summary: true` will show their sum in the summary row.\n *\n * @example\n * ```json\n * {\n * \"summary\": {\n * \"enabled\": true,\n * \"label\": \"Total\"\n * },\n * \"columns\": [\n * { \"id\": \"name\", \"label\": \"Room\", \"property\": \"BIP.spacename\" },\n * { \"id\": \"area\", \"label\": \"Area\", \"property\": \"BIP.area\", \"summary\": true }\n * ]\n * }\n * ```\n */\n summary?: {\n /** Enable summary row. */\n enabled: boolean;\n /** Label for first column in summary row. Defaults to \"Total\". */\n label?: string;\n };\n /**\n * Group legend rows by a specific column property.\n * When enabled, rows with identical values in the groupBy column are consolidated into a single row.\n * Numerical columns are summed across grouped rows; non-numerical columns take the first value.\n *\n * @example\n * ```json\n * {\n * \"groupBy\": \"BIP.spacename\",\n * \"columns\": [\n * { \"id\": \"name\", \"label\": \"Room\", \"property\": \"BIP.spacename\" },\n * { \"id\": \"area\", \"label\": \"Area\", \"property\": \"BIP.area\", \"format\": { \"digits\": 2 } }\n * ]\n * }\n * ```\n * This groups all rooms with the same name and sums their areas.\n */\n groupBy?: string;\n /**\n * Assign colors to legend rows and apply them to the referenced viewport.\n * When enabled, generates colors for each legend row (or group if groupBy is used).\n * The viewport can use these colors by setting fill: \"legend\" in its fill rules.\n *\n * @example\n * ```json\n * {\n * \"assignColors\": {\n * \"enabled\": true,\n * \"palette\": \"auto\"\n * },\n * \"groupBy\": \"BIP.spacename\"\n * }\n * ```\n * Generates distinct colors for each room type, applied to matching spaces in viewport.\n */\n assignColors?: {\n /** Enable color assignment to viewport items. */\n enabled: boolean;\n /** Color palette: \"auto\" for auto-generated colors, or array of hex colors. */\n palette?: 'auto' | string[];\n };\n}\n\nexport interface GroupTransform {\n translateXMm?: number;\n translateYMm?: number;\n rotateDeg?: number;\n scaleX?: number;\n scaleY?: number;\n}\n\nexport interface GroupClipRect {\n type: 'rect';\n xMm: number;\n yMm: number;\n widthMm: number;\n heightMm: number;\n}\n\nexport interface GroupElement extends BaseElement {\n kind: 'group';\n opacity?: number;\n transform?: GroupTransform;\n clip?: GroupClipRect;\n children: LayoutElement[];\n}\n\n/**\n * Template variable definition.\n *\n * Variables allow templates to be parameterized and reused with different values.\n * All variables MUST have a default value to ensure templates work without user input.\n *\n * Variables are referenced in templates using {{vars.variableName}} syntax.\n *\n * Example:\n * ```json\n * {\n * \"variables\": {\n * \"hyresobjektsnummer\": {\n * \"type\": \"string\",\n * \"default\": \"2022-F2008\",\n * \"label\": \"Hyresobjekt Number\",\n * \"description\": \"The rental object identifier\"\n * }\n * },\n * \"elements\": [\n * {\n * \"kind\": \"text\",\n * \"text\": \"Hyresobjekt: {{vars.hyresobjektsnummer}}\"\n * }\n * ]\n * }\n * ```\n */\nexport interface TemplateVariableDef {\n /** Variable type. Determines validation and UI control type. */\n type: 'string' | 'number' | 'boolean' | 'array';\n\n /**\n * Default value for this variable.\n * REQUIRED - ensures template always works even without user input.\n * Must match the specified type.\n */\n default: any;\n\n /** Human-readable label for UI generation. Defaults to variable name. */\n label?: string;\n\n /** Description/help text for UI generation. */\n description?: string;\n\n /**\n * Semantic format hint for UI generation.\n * Provides additional context for rendering appropriate controls.\n *\n * - `color`: Renders color picker (for type: 'string')\n * - `email`: Renders email input with validation (for type: 'string')\n * - `url`: Renders URL input with validation (for type: 'string')\n * - `date`: Renders date picker (for type: 'string')\n *\n * @example\n * ```json\n * {\n * \"lineColor\": {\n * \"type\": \"string\",\n * \"format\": \"color\",\n * \"default\": \"#1f2937\"\n * }\n * }\n * ```\n */\n format?: 'color' | 'email' | 'url' | 'date';\n\n // String validation\n /** Minimum string length (for type: 'string'). */\n minLength?: number;\n /** Maximum string length (for type: 'string'). */\n maxLength?: number;\n /** Regex pattern for validation (for type: 'string'). */\n pattern?: string;\n /** Allowed values (dropdown options). */\n enum?: string[];\n\n // Number validation\n /** Minimum value (for type: 'number'). */\n min?: number;\n /** Maximum value (for type: 'number'). */\n max?: number;\n /** Step size for UI controls (for type: 'number'). */\n step?: number;\n\n // Array validation\n /** Array item specification (for type: 'array'). */\n items?: {\n type: 'string' | 'number';\n enum?: any[];\n };\n}\n\n/**\n * Variable information extracted from template for UI generation.\n */\nexport interface TemplateVariableInfo {\n name: string;\n type: 'string' | 'number' | 'boolean' | 'array';\n default: any;\n label: string;\n description?: string;\n format?: 'color' | 'email' | 'url' | 'date';\n min?: number;\n max?: number;\n step?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n enum?: any[];\n items?: {\n type: 'string' | 'number';\n enum?: any[];\n };\n}\n\nexport interface LayoutTemplate {\n /** JSON Schema reference for editor autocompletion. Ignored at runtime. */\n $schema?: string;\n id?: string;\n\n /**\n * Template variables for parameterization.\n *\n * Variables allow templates to be reused with different values.\n * All variables MUST have defaults - templates work without user input.\n * Reference variables in template using {{vars.variableName}} syntax.\n *\n * Optional - templates without variables work as before (backward compatible).\n */\n variables?: Record<string, TemplateVariableDef>;\n\n page: PageSpec;\n styles?: StyleMap;\n assets?: AssetSpec[];\n elements: LayoutElement[];\n presets?: {\n includeClasses?: Record<string, string[]>;\n scales?: Record<string, { preferredScales?: number[] }>;\n };\n /** Allow fetching remote assets (images, fonts). Default: false. */\n allowRemoteAssets?: boolean;\n}\n\nexport interface ModelMetadataEntry {\n productId?: string;\n ifcClass?: string;\n properties?: any;\n [key: string]: any;\n}\n\nexport type ModelMetadata = Record<string, ModelMetadataEntry>;\n\nexport interface ProductFieldPaths {\n idPath?: string;\n namePath?: string;\n areaPath?: string;\n classPath?: string;\n}\n\nexport interface NormalizedProductItem {\n gid?: string;\n /** IfcClassType category (e.g. \"space\", \"walls\"). Used for template class filters. */\n ifcClass?: string | null;\n /** PredefinedIfcClassId (e.g. \"IfcSpace\", \"IfcWall\"). Used as fallback for class matching. */\n ifcClassId?: string | null;\n propertySets: Record<string, Record<string, { value: any; unit?: string; type?: string }>>;\n productId: string;\n productName: string;\n area: number | null;\n x?: number | null;\n z?: number | null;\n polylines?: Array<{ points?: Array<{ x: number; z: number }> }>;\n [key: string]: any;\n}\n\nexport type SectionStateValidationResult =\n | { ok: true }\n | {\n ok: false;\n errors: string[];\n };\n\nexport interface LabelStyle {\n /** Text fill color. Default: \"#374151\" */\n fill?: string;\n /** Font family. Default: '\"Segoe UI\", \"Helvetica Neue\", Arial, sans-serif' */\n fontFamily?: string;\n /** Font weight. Default: \"300\" */\n fontWeight?: string;\n}\n\nexport interface SvgModelView {\n groupSvg: string;\n widthModel: number;\n heightModel: number;\n geometryTransform?: string;\n transform?: {\n viewMinX: number;\n viewMaxX: number;\n viewMinZ: number;\n viewMaxZ: number;\n viewWidth: number;\n viewHeight: number;\n };\n rawPaths?: SectionPath[];\n rawLabels?: SectionLabel[];\n labelStyle?: LabelStyle;\n}\n\nexport type ModelContext = Record<string, SvgModelView>;\nexport type LayoutData = Record<string, any>;\nexport type ViewportItemsMap = Record<string, { items: NormalizedProductItem[] }>;\nexport type LayoutDataViewports = { viewports: ViewportItemsMap } & Record<string, any>;\n\nexport interface ValidationOptions {\n allowRemoteAssets?: boolean;\n allowDataUrls?: boolean;\n maxViewports?: number;\n}\n\nexport interface ValidationIssue {\n path: string;\n message: string;\n}\n\nexport type ValidationResult = { ok: true; template: any } | { ok: false; errors: ValidationIssue[] };\n\nexport interface ComputedViewportScale {\n denom: number;\n mmPerModelUnit: number;\n fits: boolean;\n}\n\nexport interface SectionResult {\n perProduct: Array<{\n product: any;\n segments: Array<[any, any]>;\n polylines: Array<{ points: any[]; closed: boolean }>;\n }>;\n}\n\nexport interface RenderDrawingInput {\n template: LayoutTemplate;\n /**\n * Section geometry to render.\n *\n * - Pass a single `SectionResult` to use the same geometry for every viewport.\n * - Pass a `Record<string, SectionResult>` keyed by viewport ID for per-viewport control.\n *\n * Either `sections` or `models` must be provided. When `models` is provided,\n * `sections` is ignored and the pre-built SvgModelViews are used directly.\n */\n sections?: SectionResult | Record<string, SectionResult>;\n /**\n * Pre-built model views keyed by viewport ID.\n *\n * Use this when geometry comes from a source other than `computePlaneSection`\n * (e.g. SlabSection). The caller is responsible for producing `SvgModelView`\n * objects; `renderDrawing` will pass them straight to `renderLayout`.\n */\n models?: ModelContext;\n options?: {\n allowRemoteAssets?: boolean;\n qrContext?: {\n host?: string | null;\n containerName?: string | null;\n floorName?: string | null;\n planeHeight?: number | null;\n };\n /**\n * Embed room labels directly in the section SVG.\n *\n * Only use this when the template does NOT have its own `labels` elements,\n * otherwise you will get duplicate labels. When omitted, renderDrawing\n * auto-detects template `labels` elements and skips embedding.\n */\n roomLabels?: {\n /** Dot-separated path to the label text under product.properties.data. */\n labelPath: string;\n /** IFC class name filter (e.g. \"space\"). If omitted, all entries are included. */\n classFilter?: string;\n };\n /**\n * Raw per-product entries for automatic item building when using `models`.\n *\n * Each entry needs a `product` (BIM object with gid, name, class, properties)\n * and `polylines` (for centroid computation and fill polygon rendering).\n *\n * When provided, renderDrawing builds NormalizedProductItems per viewport,\n * resolving fill colors from each viewport's fill rules. This replaces\n * manual item construction in consumer code.\n *\n * Ignored when `viewportItems` is also provided (explicit items take priority).\n */\n productEntries?: Array<{\n product: unknown;\n polylines: Array<{ points: Array<{ x: number; z: number }> }>;\n }>;\n /**\n * Pre-built product items when `models` is provided directly.\n * Keyed by viewport ID, each containing an array of NormalizedProductItems.\n * Used for legends, labels, and other template elements that need item data.\n *\n * Takes priority over `productEntries` when both are provided.\n */\n viewportItems?: Record<string, { items: NormalizedProductItem[] }>;\n /**\n * App default section settings. Used as fallback when viewports don't specify\n * their own cutHeight/depth configuration.\n */\n sectionDefaults?: {\n cutHeight?: number;\n depth?: { above?: number; below?: number };\n };\n /**\n * Template variable values.\n *\n * When template defines `variables`, provide values here to customize the output.\n * Missing values automatically use defaults from template (fail-safe).\n * Invalid values fall back to defaults with warnings.\n *\n * Optional - if not provided, all template defaults are used.\n *\n * @example\n * ```typescript\n * await renderDrawing({\n * template,\n * models,\n * options: {\n * variables: {\n * hyresobjektsnummer: '2022-F2009',\n * showLegend: true\n * }\n * }\n * });\n * ```\n */\n variables?: Record<string, any>;\n };\n}\n\nexport interface RenderDrawingResult {\n svg: string;\n warnings: string[];\n}\n"]}
|
package/dist/layout/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"/","sources":["layout/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SectionPath } from '@twinfinity/geometry';\
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"/","sources":["layout/utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SectionPath } from '@twinfinity/geometry';\nimport { RGB_MAX } from './colorConstants';\n\nexport const uniquePositiveDenoms = (input?: readonly number[]): number[] => {\n const values = Array.isArray(input) && input.length ? input : [];\n const seen = new Set<number>();\n const filtered: number[] = [];\n values.forEach((d) => {\n if (typeof d === 'number' && Number.isFinite(d) && d > 0 && !seen.has(d)) {\n seen.add(d);\n filtered.push(d);\n }\n });\n return filtered.sort((a, b) => a - b);\n};\n\nexport const computeLabelFontSize = (templateFontSize: number, sectionScale: number): number => {\n const base = typeof templateFontSize === 'number' && Number.isFinite(templateFontSize) ? templateFontSize : 5;\n if (!Number.isFinite(sectionScale) || sectionScale <= 0) return base;\n return base / sectionScale;\n};\n\nexport const colorFromString = (value: string): [number, number, number, number] => {\n let hash = 0;\n for (let i = 0; i < value.length; i++) {\n hash = (hash << 5) - hash + value.charCodeAt(i);\n hash |= 0;\n }\n const hue = Math.abs(hash) % 360;\n const sat = 60;\n const light = 65;\n const c = (1 - Math.abs((2 * light) / 100 - 1)) * (sat / 100);\n const x = c * (1 - Math.abs(((hue / 60) % 2) - 1));\n const m = light / 100 - c / 2;\n let r = 0;\n let g = 0;\n let b = 0;\n if (hue < 60) {\n r = c;\n g = x;\n } else if (hue < 120) {\n r = x;\n g = c;\n } else if (hue < 180) {\n g = c;\n b = x;\n } else if (hue < 240) {\n g = x;\n b = c;\n } else if (hue < 300) {\n r = x;\n b = c;\n } else {\n r = c;\n b = x;\n }\n return [Math.round((r + m) * RGB_MAX), Math.round((g + m) * RGB_MAX), Math.round((b + m) * RGB_MAX), RGB_MAX];\n};\n\nexport const applyLineStyleToPaths = (\n paths: SectionPath[],\n style: { stroke?: string; strokeWidth?: number }\n): SectionPath[] => {\n if (!Array.isArray(paths) || !paths.length) {\n return paths ?? [];\n }\n const stroke = typeof style.stroke === 'string' && style.stroke.trim().length ? style.stroke.trim() : undefined;\n const strokeWidth = typeof style.strokeWidth === 'number' ? style.strokeWidth : undefined;\n if (!stroke && strokeWidth === undefined) {\n return paths;\n }\n return paths.map((path) => {\n if (!path || !path.stroke || path.stroke === 'none') {\n return path;\n }\n const next: SectionPath = { ...path };\n if (stroke) {\n next.stroke = stroke;\n }\n if (strokeWidth !== undefined) {\n next.strokeWidth = strokeWidth;\n }\n return next;\n });\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.js","sourceRoot":"/","sources":["layout/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { isNonEmptyString, isPositiveNumber, isRemoteUrl, isDataUrl } from './validationUtils';\r\nimport { isRecord, getProp } from './validationHelpers';\r\nimport {\r\n ELEMENT_KIND_VIEWPORT,\r\n ELEMENT_KIND_TEXT,\r\n ELEMENT_KIND_IMAGE,\r\n ELEMENT_KIND_LINE,\r\n ELEMENT_KIND_RECT,\r\n ELEMENT_KIND_SCALE_BAR,\r\n ELEMENT_KIND_QR,\r\n ELEMENT_KIND_LABELS,\r\n ELEMENT_KIND_LEGEND,\r\n ELEMENT_KIND_GROUP\r\n} from './elementKinds';\r\n\r\nimport type { ValidationOptions, ValidationIssue, ValidationResult } from './types';\r\nexport type { ValidationOptions, ValidationIssue, ValidationResult };\r\n\r\n/**\r\n * Lightweight template validation (structure only) for v2 schema.\r\n */\r\nexport function validateLayoutTemplate(template: unknown, options?: ValidationOptions): ValidationResult {\r\n const allowRemoteAssets = options?.allowRemoteAssets ?? true;\r\n const allowDataUrls = options?.allowDataUrls ?? true;\r\n const maxViewports = options?.maxViewports ?? 3;\r\n const errors: ValidationIssue[] = [];\r\n\r\n if (!isRecord(template)) {\r\n return { ok: false, errors: [{ path: '', message: 'Template must be an object.' }] };\r\n }\r\n\r\n const t = template;\r\n\r\n const page = getProp(t, 'page');\r\n if (!isRecord(page)) {\r\n errors.push({ path: 'page', message: 'Page definition is required.' });\r\n } else {\r\n if (!isPositiveNumber(getProp(page, 'widthMm')))\r\n errors.push({ path: 'page.widthMm', message: 'widthMm must be > 0.' });\r\n if (!isPositiveNumber(getProp(page, 'heightMm')))\r\n errors.push({ path: 'page.heightMm', message: 'heightMm must be > 0.' });\r\n const marginMm = getProp(page, 'marginMm');\r\n if (marginMm !== undefined && typeof marginMm !== 'number') {\r\n errors.push({ path: 'page.marginMm', message: 'marginMm must be a number when provided.' });\r\n }\r\n }\r\n\r\n const assetIds = new Set<string>();\r\n const assets = getProp(t, 'assets');\r\n if (Array.isArray(assets)) {\r\n assets.forEach((asset: unknown, index: number) => {\r\n if (!isRecord(asset)) {\r\n errors.push({ path: `assets[${index}]`, message: 'Asset must be an object.' });\r\n return;\r\n }\r\n const id = getProp(asset, 'id');\r\n if (!isNonEmptyString(id)) {\r\n errors.push({ path: `assets[${index}].id`, message: 'Asset id is required.' });\r\n } else if (assetIds.has(id as string)) {\r\n errors.push({ path: `assets[${index}].id`, message: 'Duplicate asset id.' });\r\n } else {\r\n assetIds.add(id as string);\r\n }\r\n\r\n const src = getProp(asset, 'src');\r\n const content = getProp(asset, 'content');\r\n if (!isNonEmptyString(src) && !isNonEmptyString(content)) {\r\n errors.push({ path: `assets[${index}]`, message: 'Asset must have either src or content.' });\r\n }\r\n\r\n if (isNonEmptyString(src)) {\r\n if (isRemoteUrl(src as string) && !allowRemoteAssets) {\r\n errors.push({ path: `assets[${index}].src`, message: 'Remote URLs are not allowed for assets.' });\r\n } else if (isDataUrl(src as string) && !allowDataUrls) {\r\n errors.push({ path: `assets[${index}].src`, message: 'Data URLs are not allowed for assets.' });\r\n }\r\n }\r\n });\r\n } else if (assets !== undefined) {\r\n errors.push({ path: 'assets', message: 'Assets must be an array when provided.' });\r\n }\r\n\r\n const styleNames = new Set<string>();\r\n const styles = getProp(t, 'styles');\r\n if (styles !== undefined) {\r\n if (!isRecord(styles)) {\r\n errors.push({ path: 'styles', message: 'Styles must be an object map.' });\r\n } else {\r\n Object.entries(styles).forEach(([name, style]) => {\r\n styleNames.add(name);\r\n if (!isRecord(style)) {\r\n errors.push({ path: `styles.${name}`, message: 'Style must be an object.' });\r\n return;\r\n }\r\n if (!isNonEmptyString(getProp(style, 'fontFamily'))) {\r\n errors.push({ path: `styles.${name}.fontFamily`, message: 'fontFamily is required.' });\r\n }\r\n if (!isPositiveNumber(getProp(style, 'fontSizeMm'))) {\r\n errors.push({ path: `styles.${name}.fontSizeMm`, message: 'fontSizeMm must be > 0.' });\r\n }\r\n });\r\n }\r\n }\r\n\r\n let viewportCount = 0;\r\n\r\n const validateElement = (el: unknown, path: string): void => {\r\n if (!isRecord(el)) {\r\n errors.push({ path, message: 'Element must be an object.' });\r\n return;\r\n }\r\n const kind = getProp(el, 'kind');\r\n if (!kind || typeof kind !== 'string') {\r\n errors.push({ path: `${path}.kind`, message: 'Element kind is required.' });\r\n return;\r\n }\r\n\r\n const ensure = (condition: boolean, subPath: string, message: string): void => {\r\n if (!condition) errors.push({ path: subPath, message });\r\n };\r\n\r\n ensure(isNonEmptyString(getProp(el, 'id')), `${path}.id`, 'Element id is required.');\r\n\r\n if (kind === ELEMENT_KIND_VIEWPORT) {\r\n viewportCount++;\r\n if (viewportCount > maxViewports) {\r\n errors.push({ path, message: 'Too many viewports.' });\r\n }\r\n ensure(isPositiveNumber(getProp(el, 'widthMm')), `${path}.widthMm`, 'widthMm must be > 0.');\r\n ensure(isPositiveNumber(getProp(el, 'heightMm')), `${path}.heightMm`, 'heightMm must be > 0.');\r\n const xMm = getProp(el, 'xMm');\r\n const yMm = getProp(el, 'yMm');\r\n ensure(isPositiveNumber(xMm) || xMm === 0, `${path}.xMm`, 'xMm is required.');\r\n ensure(isPositiveNumber(yMm) || yMm === 0, `${path}.yMm`, 'yMm is required.');\r\n const border = getProp(el, 'border');\r\n if (border) {\r\n // border.stroke and border.strokeWidthMm are optional in v2\r\n }\r\n const clip = getProp(el, 'clip');\r\n if (clip && isRecord(clip)) {\r\n const where = getProp(clip, 'where');\r\n if (!isRecord(where)) {\r\n errors.push({\r\n path: `${path}.clip.where`,\r\n message: 'clip.where is required and must be an object.'\r\n });\r\n }\r\n }\r\n const highlight = getProp(el, 'highlight');\r\n if (highlight && isRecord(highlight)) {\r\n const where = getProp(highlight, 'where');\r\n if (!isRecord(where)) {\r\n errors.push({\r\n path: `${path}.highlight.where`,\r\n message: 'highlight.where is required and must be an object.'\r\n });\r\n }\r\n }\r\n } else if (kind === ELEMENT_KIND_TEXT) {\r\n const xMm = getProp(el, 'xMm');\r\n const yMm = getProp(el, 'yMm');\r\n ensure(isPositiveNumber(xMm) || xMm === 0, `${path}.xMm`, 'xMm is required.');\r\n ensure(isPositiveNumber(yMm) || yMm === 0, `${path}.yMm`, 'yMm is required.');\r\n const textStyle = getProp(el, 'textStyle');\r\n if (textStyle !== undefined) {\r\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\r\n errors.push({\r\n path: `${path}.textStyle`,\r\n message: `Style '${textStyle}' is not defined in styles.`\r\n });\r\n }\r\n }\r\n ensure(isNonEmptyString(getProp(el, 'text')), `${path}.text`, 'text is required.');\r\n } else if (kind === ELEMENT_KIND_IMAGE) {\r\n ensure(isPositiveNumber(getProp(el, 'widthMm')), `${path}.widthMm`, 'widthMm must be > 0.');\r\n ensure(isPositiveNumber(getProp(el, 'heightMm')), `${path}.heightMm`, 'heightMm must be > 0.');\r\n ensure(isNonEmptyString(getProp(el, 'assetId')), `${path}.assetId`, 'assetId is required.');\r\n } else if (kind === ELEMENT_KIND_LINE) {\r\n // stroke and strokeWidthMm optional in v2\r\n } else if (kind === ELEMENT_KIND_RECT) {\r\n ensure(isPositiveNumber(getProp(el, 'widthMm')), `${path}.widthMm`, 'widthMm must be > 0.');\r\n ensure(isPositiveNumber(getProp(el, 'heightMm')), `${path}.heightMm`, 'heightMm must be > 0.');\r\n } else if (kind === ELEMENT_KIND_SCALE_BAR) {\r\n ensure(isPositiveNumber(getProp(el, 'maxLengthMm')), `${path}.maxLengthMm`, 'maxLengthMm must be > 0.');\r\n const textStyle = getProp(el, 'textStyle');\r\n if (textStyle !== undefined) {\r\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\r\n errors.push({\r\n path: `${path}.textStyle`,\r\n message: `Style '${textStyle}' is not defined in styles.`\r\n });\r\n }\r\n }\r\n } else if (kind === ELEMENT_KIND_QR) {\r\n ensure(isPositiveNumber(getProp(el, 'sizeMm')), `${path}.sizeMm`, 'sizeMm must be > 0.');\r\n const xMm = getProp(el, 'xMm');\r\n const yMm = getProp(el, 'yMm');\r\n ensure(isPositiveNumber(xMm) || xMm === 0, `${path}.xMm`, 'xMm is required.');\r\n ensure(isPositiveNumber(yMm) || yMm === 0, `${path}.yMm`, 'yMm is required.');\r\n } else if (kind === ELEMENT_KIND_LABELS) {\r\n ensure(isNonEmptyString(getProp(el, 'viewport')), `${path}.viewport`, 'viewport is required.');\r\n ensure(isNonEmptyString(getProp(el, 'text')), `${path}.text`, 'text is required.');\r\n const textStyle = getProp(el, 'textStyle');\r\n if (textStyle !== undefined) {\r\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\r\n errors.push({\r\n path: `${path}.textStyle`,\r\n message: `Style '${textStyle}' is not defined in styles.`\r\n });\r\n }\r\n }\r\n } else if (kind === ELEMENT_KIND_LEGEND) {\r\n ensure(isNonEmptyString(getProp(el, 'viewport')), `${path}.viewport`, 'viewport is required.');\r\n const columns = getProp(el, 'columns');\r\n if (!Array.isArray(columns) || !columns.length) {\r\n errors.push({ path: `${path}.columns`, message: 'columns is required and must be a non-empty array.' });\r\n } else {\r\n columns.forEach((col: unknown, idx: number) => {\r\n if (!isRecord(col)) {\r\n errors.push({ path: `${path}.columns[${idx}]`, message: 'Column must be an object.' });\r\n return;\r\n }\r\n ensure(\r\n isNonEmptyString(getProp(col, 'id')),\r\n `${path}.columns[${idx}].id`,\r\n 'Column id is required.'\r\n );\r\n ensure(\r\n isNonEmptyString(getProp(col, 'label')),\r\n `${path}.columns[${idx}].label`,\r\n 'Column label is required.'\r\n );\r\n ensure(\r\n isNonEmptyString(getProp(col, 'property')),\r\n `${path}.columns[${idx}].property`,\r\n 'Column property is required.'\r\n );\r\n });\r\n }\r\n const textStyle = getProp(el, 'textStyle');\r\n if (textStyle !== undefined) {\r\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\r\n errors.push({\r\n path: `${path}.textStyle`,\r\n message: `Style '${textStyle}' is not defined in styles.`\r\n });\r\n }\r\n }\r\n const clipToViewport = getProp(el, 'clipToViewport');\r\n if (clipToViewport !== undefined && typeof clipToViewport !== 'boolean') {\r\n errors.push({\r\n path: `${path}.clipToViewport`,\r\n message: 'clipToViewport must be a boolean when provided.'\r\n });\r\n }\r\n } else if (kind === ELEMENT_KIND_GROUP) {\r\n const opacity = getProp(el, 'opacity');\r\n if (opacity !== undefined) {\r\n ensure(\r\n typeof opacity === 'number' && opacity >= 0 && opacity <= 1,\r\n `${path}.opacity`,\r\n 'opacity must be between 0 and 1.'\r\n );\r\n }\r\n const transform = getProp(el, 'transform');\r\n if (transform) {\r\n const scaleX = getProp(transform, 'scaleX');\r\n const scaleY = getProp(transform, 'scaleY');\r\n ensure(\r\n scaleX === undefined || typeof scaleX === 'number',\r\n `${path}.transform.scaleX`,\r\n 'scaleX must be a number'\r\n );\r\n ensure(\r\n scaleY === undefined || typeof scaleY === 'number',\r\n `${path}.transform.scaleY`,\r\n 'scaleY must be a number'\r\n );\r\n }\r\n const clip = getProp(el, 'clip');\r\n if (clip) {\r\n ensure(getProp(clip, 'type') === 'rect', `${path}.clip.type`, \"clip.type must be 'rect'.\");\r\n ensure(isPositiveNumber(getProp(clip, 'widthMm')), `${path}.clip.widthMm`, 'clip.widthMm must be > 0.');\r\n ensure(\r\n isPositiveNumber(getProp(clip, 'heightMm')),\r\n `${path}.clip.heightMm`,\r\n 'clip.heightMm must be > 0.'\r\n );\r\n const clipXMm = getProp(clip, 'xMm');\r\n const clipYMm = getProp(clip, 'yMm');\r\n ensure(isPositiveNumber(clipXMm) || clipXMm === 0, `${path}.clip.xMm`, 'clip.xMm is required.');\r\n ensure(isPositiveNumber(clipYMm) || clipYMm === 0, `${path}.clip.yMm`, 'clip.yMm is required.');\r\n }\r\n const children = getProp(el, 'children');\r\n if (Array.isArray(children)) {\r\n children.forEach((child: unknown, childIdx: number) =>\r\n validateElement(child, `${path}.children[${childIdx}]`)\r\n );\r\n }\r\n } else {\r\n errors.push({ path, message: `Unknown element kind '${kind}'.` });\r\n }\r\n };\r\n\r\n const elements = getProp(t, 'elements');\r\n if (Array.isArray(elements)) {\r\n elements.forEach((el: unknown, index: number) => validateElement(el, `elements[${index}]`));\r\n } else if (elements !== undefined) {\r\n errors.push({ path: 'elements', message: 'Elements must be an array.' });\r\n }\r\n\r\n // Validate template variables (if present)\r\n const variables = getProp(t, 'variables');\r\n if (variables !== undefined) {\r\n if (!isRecord(variables)) {\r\n errors.push({ path: 'variables', message: 'Variables must be an object.' });\r\n } else {\r\n // Validate each variable definition\r\n for (const [varName, varDef] of Object.entries(variables)) {\r\n const varPath = `variables.${varName}`;\r\n\r\n if (!isRecord(varDef)) {\r\n errors.push({ path: varPath, message: 'Variable definition must be an object.' });\r\n continue;\r\n }\r\n\r\n // Validate type\r\n const varType = getProp(varDef, 'type');\r\n if (!['string', 'number', 'boolean', 'array'].includes(varType as string)) {\r\n errors.push({\r\n path: `${varPath}.type`,\r\n message: `Type must be 'string', 'number', 'boolean', or 'array' (got '${varType}').`\r\n });\r\n }\r\n\r\n // Enforce default value (CRITICAL for fail-safe operation)\r\n const defaultValue = getProp(varDef, 'default');\r\n if (defaultValue === undefined || defaultValue === null) {\r\n errors.push({\r\n path: `${varPath}.default`,\r\n message: 'Variable must have a default value (required for fail-safe operation).'\r\n });\r\n } else {\r\n // Validate default value matches declared type\r\n const actualType = Array.isArray(defaultValue) ? 'array' : typeof defaultValue;\r\n if (varType === 'number' && actualType !== 'number') {\r\n errors.push({\r\n path: `${varPath}.default`,\r\n message: `Default value must be a number (got ${actualType}).`\r\n });\r\n } else if (varType === 'string' && actualType !== 'string') {\r\n errors.push({\r\n path: `${varPath}.default`,\r\n message: `Default value must be a string (got ${actualType}).`\r\n });\r\n } else if (varType === 'boolean' && actualType !== 'boolean') {\r\n errors.push({\r\n path: `${varPath}.default`,\r\n message: `Default value must be a boolean (got ${actualType}).`\r\n });\r\n } else if (varType === 'array' && actualType !== 'array') {\r\n errors.push({\r\n path: `${varPath}.default`,\r\n message: `Default value must be an array (got ${actualType}).`\r\n });\r\n }\r\n }\r\n\r\n // Validate numeric constraints (if present)\r\n if (varType === 'number') {\r\n const min = getProp(varDef, 'min');\r\n const max = getProp(varDef, 'max');\r\n if (min !== undefined && typeof min !== 'number') {\r\n errors.push({ path: `${varPath}.min`, message: 'min must be a number.' });\r\n }\r\n if (max !== undefined && typeof max !== 'number') {\r\n errors.push({ path: `${varPath}.max`, message: 'max must be a number.' });\r\n }\r\n if (typeof min === 'number' && typeof max === 'number' && min > max) {\r\n errors.push({ path: `${varPath}`, message: 'min cannot be greater than max.' });\r\n }\r\n }\r\n }\r\n }\r\n }\r\n\r\n if (errors.length) {\r\n return { ok: false, errors };\r\n }\r\n\r\n return { ok: true, template };\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"/","sources":["layout/validation.ts"],"names":[],"mappings":"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","sourcesContent":["import { isNonEmptyString, isPositiveNumber, isRemoteUrl, isDataUrl } from './validationUtils';\nimport { isRecord, getProp } from './validationHelpers';\nimport {\n ELEMENT_KIND_VIEWPORT,\n ELEMENT_KIND_TEXT,\n ELEMENT_KIND_IMAGE,\n ELEMENT_KIND_LINE,\n ELEMENT_KIND_RECT,\n ELEMENT_KIND_SCALE_BAR,\n ELEMENT_KIND_QR,\n ELEMENT_KIND_LABELS,\n ELEMENT_KIND_LEGEND,\n ELEMENT_KIND_GROUP\n} from './elementKinds';\n\nimport type { ValidationOptions, ValidationIssue, ValidationResult } from './types';\nexport type { ValidationOptions, ValidationIssue, ValidationResult };\n\n/**\n * Lightweight template validation (structure only) for v2 schema.\n */\nexport function validateLayoutTemplate(template: unknown, options?: ValidationOptions): ValidationResult {\n const allowRemoteAssets = options?.allowRemoteAssets ?? true;\n const allowDataUrls = options?.allowDataUrls ?? true;\n const maxViewports = options?.maxViewports ?? 3;\n const errors: ValidationIssue[] = [];\n\n if (!isRecord(template)) {\n return { ok: false, errors: [{ path: '', message: 'Template must be an object.' }] };\n }\n\n const t = template;\n\n const page = getProp(t, 'page');\n if (!isRecord(page)) {\n errors.push({ path: 'page', message: 'Page definition is required.' });\n } else {\n if (!isPositiveNumber(getProp(page, 'widthMm')))\n errors.push({ path: 'page.widthMm', message: 'widthMm must be > 0.' });\n if (!isPositiveNumber(getProp(page, 'heightMm')))\n errors.push({ path: 'page.heightMm', message: 'heightMm must be > 0.' });\n const marginMm = getProp(page, 'marginMm');\n if (marginMm !== undefined && typeof marginMm !== 'number') {\n errors.push({ path: 'page.marginMm', message: 'marginMm must be a number when provided.' });\n }\n }\n\n const assetIds = new Set<string>();\n const assets = getProp(t, 'assets');\n if (Array.isArray(assets)) {\n assets.forEach((asset: unknown, index: number) => {\n if (!isRecord(asset)) {\n errors.push({ path: `assets[${index}]`, message: 'Asset must be an object.' });\n return;\n }\n const id = getProp(asset, 'id');\n if (!isNonEmptyString(id)) {\n errors.push({ path: `assets[${index}].id`, message: 'Asset id is required.' });\n } else if (assetIds.has(id as string)) {\n errors.push({ path: `assets[${index}].id`, message: 'Duplicate asset id.' });\n } else {\n assetIds.add(id as string);\n }\n\n const src = getProp(asset, 'src');\n const content = getProp(asset, 'content');\n if (!isNonEmptyString(src) && !isNonEmptyString(content)) {\n errors.push({ path: `assets[${index}]`, message: 'Asset must have either src or content.' });\n }\n\n if (isNonEmptyString(src)) {\n if (isRemoteUrl(src as string) && !allowRemoteAssets) {\n errors.push({ path: `assets[${index}].src`, message: 'Remote URLs are not allowed for assets.' });\n } else if (isDataUrl(src as string) && !allowDataUrls) {\n errors.push({ path: `assets[${index}].src`, message: 'Data URLs are not allowed for assets.' });\n }\n }\n });\n } else if (assets !== undefined) {\n errors.push({ path: 'assets', message: 'Assets must be an array when provided.' });\n }\n\n const styleNames = new Set<string>();\n const styles = getProp(t, 'styles');\n if (styles !== undefined) {\n if (!isRecord(styles)) {\n errors.push({ path: 'styles', message: 'Styles must be an object map.' });\n } else {\n Object.entries(styles).forEach(([name, style]) => {\n styleNames.add(name);\n if (!isRecord(style)) {\n errors.push({ path: `styles.${name}`, message: 'Style must be an object.' });\n return;\n }\n if (!isNonEmptyString(getProp(style, 'fontFamily'))) {\n errors.push({ path: `styles.${name}.fontFamily`, message: 'fontFamily is required.' });\n }\n if (!isPositiveNumber(getProp(style, 'fontSizeMm'))) {\n errors.push({ path: `styles.${name}.fontSizeMm`, message: 'fontSizeMm must be > 0.' });\n }\n });\n }\n }\n\n let viewportCount = 0;\n\n const validateElement = (el: unknown, path: string): void => {\n if (!isRecord(el)) {\n errors.push({ path, message: 'Element must be an object.' });\n return;\n }\n const kind = getProp(el, 'kind');\n if (!kind || typeof kind !== 'string') {\n errors.push({ path: `${path}.kind`, message: 'Element kind is required.' });\n return;\n }\n\n const ensure = (condition: boolean, subPath: string, message: string): void => {\n if (!condition) errors.push({ path: subPath, message });\n };\n\n ensure(isNonEmptyString(getProp(el, 'id')), `${path}.id`, 'Element id is required.');\n\n if (kind === ELEMENT_KIND_VIEWPORT) {\n viewportCount++;\n if (viewportCount > maxViewports) {\n errors.push({ path, message: 'Too many viewports.' });\n }\n ensure(isPositiveNumber(getProp(el, 'widthMm')), `${path}.widthMm`, 'widthMm must be > 0.');\n ensure(isPositiveNumber(getProp(el, 'heightMm')), `${path}.heightMm`, 'heightMm must be > 0.');\n const xMm = getProp(el, 'xMm');\n const yMm = getProp(el, 'yMm');\n ensure(isPositiveNumber(xMm) || xMm === 0, `${path}.xMm`, 'xMm is required.');\n ensure(isPositiveNumber(yMm) || yMm === 0, `${path}.yMm`, 'yMm is required.');\n const border = getProp(el, 'border');\n if (border) {\n // border.stroke and border.strokeWidthMm are optional in v2\n }\n const clip = getProp(el, 'clip');\n if (clip && isRecord(clip)) {\n const where = getProp(clip, 'where');\n if (!isRecord(where)) {\n errors.push({\n path: `${path}.clip.where`,\n message: 'clip.where is required and must be an object.'\n });\n }\n }\n const highlight = getProp(el, 'highlight');\n if (highlight && isRecord(highlight)) {\n const where = getProp(highlight, 'where');\n if (!isRecord(where)) {\n errors.push({\n path: `${path}.highlight.where`,\n message: 'highlight.where is required and must be an object.'\n });\n }\n }\n } else if (kind === ELEMENT_KIND_TEXT) {\n const xMm = getProp(el, 'xMm');\n const yMm = getProp(el, 'yMm');\n ensure(isPositiveNumber(xMm) || xMm === 0, `${path}.xMm`, 'xMm is required.');\n ensure(isPositiveNumber(yMm) || yMm === 0, `${path}.yMm`, 'yMm is required.');\n const textStyle = getProp(el, 'textStyle');\n if (textStyle !== undefined) {\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\n errors.push({\n path: `${path}.textStyle`,\n message: `Style '${textStyle}' is not defined in styles.`\n });\n }\n }\n ensure(isNonEmptyString(getProp(el, 'text')), `${path}.text`, 'text is required.');\n } else if (kind === ELEMENT_KIND_IMAGE) {\n ensure(isPositiveNumber(getProp(el, 'widthMm')), `${path}.widthMm`, 'widthMm must be > 0.');\n ensure(isPositiveNumber(getProp(el, 'heightMm')), `${path}.heightMm`, 'heightMm must be > 0.');\n ensure(isNonEmptyString(getProp(el, 'assetId')), `${path}.assetId`, 'assetId is required.');\n } else if (kind === ELEMENT_KIND_LINE) {\n // stroke and strokeWidthMm optional in v2\n } else if (kind === ELEMENT_KIND_RECT) {\n ensure(isPositiveNumber(getProp(el, 'widthMm')), `${path}.widthMm`, 'widthMm must be > 0.');\n ensure(isPositiveNumber(getProp(el, 'heightMm')), `${path}.heightMm`, 'heightMm must be > 0.');\n } else if (kind === ELEMENT_KIND_SCALE_BAR) {\n ensure(isPositiveNumber(getProp(el, 'maxLengthMm')), `${path}.maxLengthMm`, 'maxLengthMm must be > 0.');\n const textStyle = getProp(el, 'textStyle');\n if (textStyle !== undefined) {\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\n errors.push({\n path: `${path}.textStyle`,\n message: `Style '${textStyle}' is not defined in styles.`\n });\n }\n }\n } else if (kind === ELEMENT_KIND_QR) {\n ensure(isPositiveNumber(getProp(el, 'sizeMm')), `${path}.sizeMm`, 'sizeMm must be > 0.');\n const xMm = getProp(el, 'xMm');\n const yMm = getProp(el, 'yMm');\n ensure(isPositiveNumber(xMm) || xMm === 0, `${path}.xMm`, 'xMm is required.');\n ensure(isPositiveNumber(yMm) || yMm === 0, `${path}.yMm`, 'yMm is required.');\n } else if (kind === ELEMENT_KIND_LABELS) {\n ensure(isNonEmptyString(getProp(el, 'viewport')), `${path}.viewport`, 'viewport is required.');\n ensure(isNonEmptyString(getProp(el, 'text')), `${path}.text`, 'text is required.');\n const textStyle = getProp(el, 'textStyle');\n if (textStyle !== undefined) {\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\n errors.push({\n path: `${path}.textStyle`,\n message: `Style '${textStyle}' is not defined in styles.`\n });\n }\n }\n } else if (kind === ELEMENT_KIND_LEGEND) {\n ensure(isNonEmptyString(getProp(el, 'viewport')), `${path}.viewport`, 'viewport is required.');\n const columns = getProp(el, 'columns');\n if (!Array.isArray(columns) || !columns.length) {\n errors.push({ path: `${path}.columns`, message: 'columns is required and must be a non-empty array.' });\n } else {\n columns.forEach((col: unknown, idx: number) => {\n if (!isRecord(col)) {\n errors.push({ path: `${path}.columns[${idx}]`, message: 'Column must be an object.' });\n return;\n }\n ensure(\n isNonEmptyString(getProp(col, 'id')),\n `${path}.columns[${idx}].id`,\n 'Column id is required.'\n );\n ensure(\n isNonEmptyString(getProp(col, 'label')),\n `${path}.columns[${idx}].label`,\n 'Column label is required.'\n );\n ensure(\n isNonEmptyString(getProp(col, 'property')),\n `${path}.columns[${idx}].property`,\n 'Column property is required.'\n );\n });\n }\n const textStyle = getProp(el, 'textStyle');\n if (textStyle !== undefined) {\n if (typeof textStyle === 'string' && styleNames.size > 0 && !styleNames.has(textStyle)) {\n errors.push({\n path: `${path}.textStyle`,\n message: `Style '${textStyle}' is not defined in styles.`\n });\n }\n }\n const clipToViewport = getProp(el, 'clipToViewport');\n if (clipToViewport !== undefined && typeof clipToViewport !== 'boolean') {\n errors.push({\n path: `${path}.clipToViewport`,\n message: 'clipToViewport must be a boolean when provided.'\n });\n }\n } else if (kind === ELEMENT_KIND_GROUP) {\n const opacity = getProp(el, 'opacity');\n if (opacity !== undefined) {\n ensure(\n typeof opacity === 'number' && opacity >= 0 && opacity <= 1,\n `${path}.opacity`,\n 'opacity must be between 0 and 1.'\n );\n }\n const transform = getProp(el, 'transform');\n if (transform) {\n const scaleX = getProp(transform, 'scaleX');\n const scaleY = getProp(transform, 'scaleY');\n ensure(\n scaleX === undefined || typeof scaleX === 'number',\n `${path}.transform.scaleX`,\n 'scaleX must be a number'\n );\n ensure(\n scaleY === undefined || typeof scaleY === 'number',\n `${path}.transform.scaleY`,\n 'scaleY must be a number'\n );\n }\n const clip = getProp(el, 'clip');\n if (clip) {\n ensure(getProp(clip, 'type') === 'rect', `${path}.clip.type`, \"clip.type must be 'rect'.\");\n ensure(isPositiveNumber(getProp(clip, 'widthMm')), `${path}.clip.widthMm`, 'clip.widthMm must be > 0.');\n ensure(\n isPositiveNumber(getProp(clip, 'heightMm')),\n `${path}.clip.heightMm`,\n 'clip.heightMm must be > 0.'\n );\n const clipXMm = getProp(clip, 'xMm');\n const clipYMm = getProp(clip, 'yMm');\n ensure(isPositiveNumber(clipXMm) || clipXMm === 0, `${path}.clip.xMm`, 'clip.xMm is required.');\n ensure(isPositiveNumber(clipYMm) || clipYMm === 0, `${path}.clip.yMm`, 'clip.yMm is required.');\n }\n const children = getProp(el, 'children');\n if (Array.isArray(children)) {\n children.forEach((child: unknown, childIdx: number) =>\n validateElement(child, `${path}.children[${childIdx}]`)\n );\n }\n } else {\n errors.push({ path, message: `Unknown element kind '${kind}'.` });\n }\n };\n\n const elements = getProp(t, 'elements');\n if (Array.isArray(elements)) {\n elements.forEach((el: unknown, index: number) => validateElement(el, `elements[${index}]`));\n } else if (elements !== undefined) {\n errors.push({ path: 'elements', message: 'Elements must be an array.' });\n }\n\n // Validate template variables (if present)\n const variables = getProp(t, 'variables');\n if (variables !== undefined) {\n if (!isRecord(variables)) {\n errors.push({ path: 'variables', message: 'Variables must be an object.' });\n } else {\n // Validate each variable definition\n for (const [varName, varDef] of Object.entries(variables)) {\n const varPath = `variables.${varName}`;\n\n if (!isRecord(varDef)) {\n errors.push({ path: varPath, message: 'Variable definition must be an object.' });\n continue;\n }\n\n // Validate type\n const varType = getProp(varDef, 'type');\n if (!['string', 'number', 'boolean', 'array'].includes(varType as string)) {\n errors.push({\n path: `${varPath}.type`,\n message: `Type must be 'string', 'number', 'boolean', or 'array' (got '${varType}').`\n });\n }\n\n // Enforce default value (CRITICAL for fail-safe operation)\n const defaultValue = getProp(varDef, 'default');\n if (defaultValue === undefined || defaultValue === null) {\n errors.push({\n path: `${varPath}.default`,\n message: 'Variable must have a default value (required for fail-safe operation).'\n });\n } else {\n // Validate default value matches declared type\n const actualType = Array.isArray(defaultValue) ? 'array' : typeof defaultValue;\n if (varType === 'number' && actualType !== 'number') {\n errors.push({\n path: `${varPath}.default`,\n message: `Default value must be a number (got ${actualType}).`\n });\n } else if (varType === 'string' && actualType !== 'string') {\n errors.push({\n path: `${varPath}.default`,\n message: `Default value must be a string (got ${actualType}).`\n });\n } else if (varType === 'boolean' && actualType !== 'boolean') {\n errors.push({\n path: `${varPath}.default`,\n message: `Default value must be a boolean (got ${actualType}).`\n });\n } else if (varType === 'array' && actualType !== 'array') {\n errors.push({\n path: `${varPath}.default`,\n message: `Default value must be an array (got ${actualType}).`\n });\n }\n }\n\n // Validate numeric constraints (if present)\n if (varType === 'number') {\n const min = getProp(varDef, 'min');\n const max = getProp(varDef, 'max');\n if (min !== undefined && typeof min !== 'number') {\n errors.push({ path: `${varPath}.min`, message: 'min must be a number.' });\n }\n if (max !== undefined && typeof max !== 'number') {\n errors.push({ path: `${varPath}.max`, message: 'max must be a number.' });\n }\n if (typeof min === 'number' && typeof max === 'number' && min > max) {\n errors.push({ path: `${varPath}`, message: 'min cannot be greater than max.' });\n }\n }\n }\n }\n }\n\n if (errors.length) {\n return { ok: false, errors };\n }\n\n return { ok: true, template };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validationHelpers.js","sourceRoot":"/","sources":["layout/validationHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CACjD,OAAO,IAAI,KAAK,QAAQ,IAAI,kDAAkD,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AAErG;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAyB,EAAE,IAAY,EAAE,OAAe,EAAQ,EAAE;IACjG,MAAM,CAAC,IAAI,CAAC;QACR,IAAI;QACJ,OAAO,EAAE,GAAG,OAAO,+EAA+E;KACrG,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAC/B,OAAgB,EAChB,QAAgB,EAChB,MAAyB,EACzB,OAAe,EACX,EAAE;IACN,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAU,EAAE,GAAW,EAAE,EAAE;YACxC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YAChC,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAc,CAAC,EAAE,CAAC;gBACxE,MAAM,CAAC,IAAI,CAAC;oBACR,IAAI,EAAE,GAAG,QAAQ,IAAI,GAAG,QAAQ;oBAChC,OAAO,EAAE,GAAG,OAAO,oFAAoF;iBAC1G,CAAC,CAAC;YACP,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC7B,IAAwB,EACxB,MAAyB,EACzB,SAAiB,EACjB,OAAe,EACX,EAAE;IACN,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,oBAAoB,EAAE,CAAC,CAAC;QAC1E,OAAO;IACX,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,GAAG,OAAO,oFAAoF;SAC1G,CAAC,CAAC;IACP,CAAC;AACL,CAAC,CAAC","sourcesContent":["import { isNonEmptyString } from './validationUtils';\
|
|
1
|
+
{"version":3,"file":"validationHelpers.js","sourceRoot":"/","sources":["layout/validationHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;AAE7B;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAY,EAAW,EAAE,CACjD,OAAO,IAAI,KAAK,QAAQ,IAAI,kDAAkD,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AAErG;;GAEG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,MAAyB,EAAE,IAAY,EAAE,OAAe,EAAQ,EAAE;IACjG,MAAM,CAAC,IAAI,CAAC;QACR,IAAI;QACJ,OAAO,EAAE,GAAG,OAAO,+EAA+E;KACrG,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAC/B,OAAgB,EAChB,QAAgB,EAChB,MAAyB,EACzB,OAAe,EACX,EAAE;IACN,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,OAAO,CAAC,CAAC,CAAU,EAAE,GAAW,EAAE,EAAE;YACxC,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;YAChC,IAAI,QAAQ,CAAC,CAAC,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAc,CAAC,EAAE,CAAC;gBACxE,MAAM,CAAC,IAAI,CAAC;oBACR,IAAI,EAAE,GAAG,QAAQ,IAAI,GAAG,QAAQ;oBAChC,OAAO,EAAE,GAAG,OAAO,oFAAoF;iBAC1G,CAAC,CAAC;YACP,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;AACL,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAC7B,IAAwB,EACxB,MAAyB,EACzB,SAAiB,EACjB,OAAe,EACX,EAAE;IACN,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,OAAO,oBAAoB,EAAE,CAAC,CAAC;QAC1E,OAAO;IACX,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC;YACR,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,GAAG,OAAO,oFAAoF;SAC1G,CAAC,CAAC;IACP,CAAC;AACL,CAAC,CAAC","sourcesContent":["import { isNonEmptyString } from './validationUtils';\nimport { ValidationIssue } from './types';\n\nimport { isRecord, getProp } from './typeGuards';\nexport { isRecord, getProp };\n\n/**\n * Check if path is a valid short path: SetName.propertyName (e.g., \"Pset_SpaceCommon.Reference\")\n * This is the only accepted format for template paths.\n */\nexport const isShortPath = (path: string): boolean =>\n typeof path === 'string' && /^[A-Za-z_][A-Za-z0-9_]*\\.[A-Za-z_][A-Za-z0-9_]*$/.test(path.trim());\n\n/**\n * Push a \"must use short path\" error to the errors array.\n */\nexport const pushShortPathError = (errors: ValidationIssue[], path: string, context: string): void => {\n errors.push({\n path,\n message: `${context} must use short format: SetName.propName (e.g., \"Pset_SpaceCommon.Reference\")`\n });\n};\n\nexport const validateFilterPaths = (\n filters: unknown,\n basePath: string,\n errors: ValidationIssue[],\n context: string\n): void => {\n if (Array.isArray(filters)) {\n filters.forEach((f: unknown, idx: number) => {\n const path = getProp(f, 'path');\n if (isRecord(f) && isNonEmptyString(path) && !isShortPath(path as string)) {\n errors.push({\n path: `${basePath}[${idx}].path`,\n message: `${context} path must use short format: SetName.propName (e.g., \"Pset_SpaceCommon.Reference\")`\n });\n }\n });\n }\n};\n\nexport const validatePathShape = (\n path: string | undefined,\n errors: ValidationIssue[],\n fieldPath: string,\n context: string\n): void => {\n if (!path || !path.trim().length) {\n errors.push({ path: fieldPath, message: `${context} path is required.` });\n return;\n }\n if (!isShortPath(path)) {\n errors.push({\n path: fieldPath,\n message: `${context} path must use short format: SetName.propName (e.g., \"Pset_SpaceCommon.Reference\")`\n });\n }\n};\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validationUtils.js","sourceRoot":"/","sources":["layout/validationUtils.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAChE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAChE,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAW,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC","sourcesContent":["export const isNonEmptyString = (value: unknown): value is string =>\
|
|
1
|
+
{"version":3,"file":"validationUtils.js","sourceRoot":"/","sources":["layout/validationUtils.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAChE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAmB,EAAE,CAChE,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC;AACrE,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,KAAa,EAAW,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACnF,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC","sourcesContent":["export const isNonEmptyString = (value: unknown): value is string =>\n typeof value === 'string' && value.trim().length > 0;\nexport const isPositiveNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0;\nexport const isRemoteUrl = (value: string): boolean => /^https?:\\/\\//i.test(value);\nexport const isDataUrl = (value: string): boolean => /^data:/i.test(value);\n"]}
|