report editor with text field, lines, frames, and table draw. multiselect etc.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Parse text content and replace {variable.field} expressions with actual data values
|
||||
* @param {string} text - Text containing binding expressions like "Owner: {owner.name}"
|
||||
* @param {object} data - Data object containing the values to bind
|
||||
* @returns {string} - Text with bindings resolved
|
||||
*/
|
||||
export function parseBindings(text, data) {
|
||||
if (!text || typeof text !== 'string') {
|
||||
return text || '';
|
||||
}
|
||||
|
||||
if (!data || typeof data !== 'object') {
|
||||
return text;
|
||||
}
|
||||
|
||||
// Regex to match {variable.field.nested} patterns
|
||||
const bindingRegex = /\{([a-zA-Z0-9_.]+)\}/g;
|
||||
|
||||
return text.replace(bindingRegex, (match, path) => {
|
||||
const value = resolveNestedPath(data, path);
|
||||
|
||||
// If value is found, return it; otherwise keep the original binding
|
||||
if (value !== undefined && value !== null) {
|
||||
return String(value);
|
||||
}
|
||||
|
||||
return match; // Keep {variable} if not found
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a nested path in an object (e.g., "owner.contact.name")
|
||||
* @param {object} obj - The object to traverse
|
||||
* @param {string} path - Dot-separated path (e.g., "owner.name")
|
||||
* @returns {*} - The resolved value or undefined
|
||||
*/
|
||||
function resolveNestedPath(obj, path) {
|
||||
const keys = path.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (const key of keys) {
|
||||
if (current === null || current === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!current.hasOwnProperty(key)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
current = current[key];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if text contains any binding expressions
|
||||
* @param {string} text - Text to check
|
||||
* @returns {boolean} - True if text contains bindings
|
||||
*/
|
||||
export function hasBindings(text) {
|
||||
if (!text || typeof text !== 'string') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return /\{[a-zA-Z0-9_.]+\}/.test(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all binding paths from text
|
||||
* @param {string} text - Text to analyze
|
||||
* @returns {string[]} - Array of binding paths (e.g., ["owner.name", "vessel.id"])
|
||||
*/
|
||||
export function extractBindingPaths(text) {
|
||||
if (!text || typeof text !== 'string') {
|
||||
return [];
|
||||
}
|
||||
|
||||
const bindingRegex = /\{([a-zA-Z0-9_.]+)\}/g;
|
||||
const paths = [];
|
||||
let match;
|
||||
|
||||
while ((match = bindingRegex.exec(text)) !== null) {
|
||||
paths.push(match[1]);
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
/**
|
||||
* Crossroad characters for different line intersections
|
||||
*/
|
||||
const CROSSROAD_CHARS = {
|
||||
single: {
|
||||
cross: '┼', // All 4 directions
|
||||
tTop: '┴', // Left, Right, Up (T pointing up)
|
||||
tBottom: '┬', // Left, Right, Down (T pointing down)
|
||||
tLeft: '┤', // Up, Down, Left (T pointing left)
|
||||
tRight: '├', // Up, Down, Right (T pointing right)
|
||||
horizontal: '─', // Left, Right only
|
||||
vertical: '│', // Up, Down only
|
||||
cornerTL: '┌', // Top-left corner (Down, Right)
|
||||
cornerTR: '┐', // Top-right corner (Down, Left)
|
||||
cornerBL: '└', // Bottom-left corner (Up, Right)
|
||||
cornerBR: '┘' // Bottom-right corner (Up, Left)
|
||||
},
|
||||
double: {
|
||||
cross: '╬',
|
||||
tTop: '╩',
|
||||
tBottom: '╦',
|
||||
tLeft: '╣',
|
||||
tRight: '╠',
|
||||
horizontal: '═',
|
||||
vertical: '║',
|
||||
cornerTL: '╔',
|
||||
cornerTR: '╗',
|
||||
cornerBL: '╚',
|
||||
cornerBR: '╝'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect crossroads and return a map of positions to junction characters
|
||||
* @param {Array} elements - Array of report elements (text, frame, hline, vline)
|
||||
* @returns {Object} - Map of 'x,y' positions to junction characters
|
||||
*/
|
||||
export function detectCrossroads(elements) {
|
||||
// Build occupancy map: { 'x,y': { up, down, left, right, style } }
|
||||
const occupancyMap = {};
|
||||
|
||||
// Process each element and mark occupied positions
|
||||
elements.forEach(element => {
|
||||
if (element.type === 'hline') {
|
||||
// Horizontal line occupies positions from x to x+length
|
||||
for (let i = 0; i < element.length; i++) {
|
||||
const x = element.x + i;
|
||||
const y = element.y;
|
||||
const key = `${x},${y}`;
|
||||
|
||||
if (!occupancyMap[key]) {
|
||||
occupancyMap[key] = { up: false, down: false, left: false, right: false, styles: [] };
|
||||
}
|
||||
|
||||
// Horizontal line means left and right connections
|
||||
if (i > 0) occupancyMap[key].left = true;
|
||||
if (i < element.length - 1) occupancyMap[key].right = true;
|
||||
|
||||
occupancyMap[key].styles.push(element.lineStyle || 'single');
|
||||
}
|
||||
} else if (element.type === 'vline') {
|
||||
// Vertical line occupies positions from y to y+length
|
||||
for (let i = 0; i < element.length; i++) {
|
||||
const x = element.x;
|
||||
const y = element.y + i;
|
||||
const key = `${x},${y}`;
|
||||
|
||||
if (!occupancyMap[key]) {
|
||||
occupancyMap[key] = { up: false, down: false, left: false, right: false, styles: [] };
|
||||
}
|
||||
|
||||
// Vertical line means up and down connections
|
||||
if (i > 0) occupancyMap[key].up = true;
|
||||
if (i < element.length - 1) occupancyMap[key].down = true;
|
||||
|
||||
occupancyMap[key].styles.push(element.lineStyle || 'single');
|
||||
}
|
||||
} else if (element.type === 'frame') {
|
||||
// Frame has borders on all edges
|
||||
const { x, y, width, height, borderStyle } = element;
|
||||
const style = borderStyle || 'single';
|
||||
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let col = 0; col < width; col++) {
|
||||
const posX = x + col;
|
||||
const posY = y + row;
|
||||
const key = `${posX},${posY}`;
|
||||
|
||||
// Only process border positions (edges of frame)
|
||||
const isTopEdge = row === 0;
|
||||
const isBottomEdge = row === height - 1;
|
||||
const isLeftEdge = col === 0;
|
||||
const isRightEdge = col === width - 1;
|
||||
|
||||
if (isTopEdge || isBottomEdge || isLeftEdge || isRightEdge) {
|
||||
if (!occupancyMap[key]) {
|
||||
occupancyMap[key] = { up: false, down: false, left: false, right: false, styles: [] };
|
||||
}
|
||||
|
||||
// Determine connections based on position in frame
|
||||
if (isTopEdge && col > 0 && col < width - 1) {
|
||||
occupancyMap[key].left = true;
|
||||
occupancyMap[key].right = true;
|
||||
}
|
||||
if (isBottomEdge && col > 0 && col < width - 1) {
|
||||
occupancyMap[key].left = true;
|
||||
occupancyMap[key].right = true;
|
||||
}
|
||||
if (isLeftEdge && row > 0 && row < height - 1) {
|
||||
occupancyMap[key].up = true;
|
||||
occupancyMap[key].down = true;
|
||||
}
|
||||
if (isRightEdge && row > 0 && row < height - 1) {
|
||||
occupancyMap[key].up = true;
|
||||
occupancyMap[key].down = true;
|
||||
}
|
||||
|
||||
// Corners
|
||||
if (isTopEdge && isLeftEdge) {
|
||||
occupancyMap[key].right = true;
|
||||
occupancyMap[key].down = true;
|
||||
}
|
||||
if (isTopEdge && isRightEdge) {
|
||||
occupancyMap[key].left = true;
|
||||
occupancyMap[key].down = true;
|
||||
}
|
||||
if (isBottomEdge && isLeftEdge) {
|
||||
occupancyMap[key].right = true;
|
||||
occupancyMap[key].up = true;
|
||||
}
|
||||
if (isBottomEdge && isRightEdge) {
|
||||
occupancyMap[key].left = true;
|
||||
occupancyMap[key].up = true;
|
||||
}
|
||||
|
||||
occupancyMap[key].styles.push(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Build crossroad map by analyzing each occupied position
|
||||
const crossroadMap = {};
|
||||
|
||||
Object.keys(occupancyMap).forEach(key => {
|
||||
const { up, down, left, right, styles } = occupancyMap[key];
|
||||
|
||||
// Determine the predominant style (prefer double if any double exists)
|
||||
const style = styles.includes('double') ? 'double' : 'single';
|
||||
const chars = CROSSROAD_CHARS[style];
|
||||
|
||||
// Count connections
|
||||
const connectionCount = (up ? 1 : 0) + (down ? 1 : 0) + (left ? 1 : 0) + (right ? 1 : 0);
|
||||
|
||||
// Only create crossroad if there's an intersection (more than 2 connections)
|
||||
if (connectionCount >= 3) {
|
||||
// Determine the appropriate junction character
|
||||
if (up && down && left && right) {
|
||||
crossroadMap[key] = chars.cross;
|
||||
} else if (up && left && right) {
|
||||
crossroadMap[key] = chars.tTop;
|
||||
} else if (down && left && right) {
|
||||
crossroadMap[key] = chars.tBottom;
|
||||
} else if (up && down && left) {
|
||||
crossroadMap[key] = chars.tLeft;
|
||||
} else if (up && down && right) {
|
||||
crossroadMap[key] = chars.tRight;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return crossroadMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a position has a crossroad
|
||||
* @param {Object} crossroadMap - Map of crossroads
|
||||
* @param {number} x - Grid x position
|
||||
* @param {number} y - Grid y position
|
||||
* @returns {string|null} - Crossroad character or null
|
||||
*/
|
||||
export function getCrossroadChar(crossroadMap, x, y) {
|
||||
const key = `${x},${y}`;
|
||||
return crossroadMap[key] || null;
|
||||
}
|
||||
Reference in New Issue
Block a user