ConceptPortal-public/rsconcept/frontend/src/features/oss/models/oss-layout-api.ts

129 lines
4.8 KiB
TypeScript
Raw Normal View History

2025-04-28 13:59:38 +03:00
import { type ICreateBlockDTO, type ICreateOperationDTO, type IOssLayout } from '../backend/types';
import { type IOperationSchema } from './oss';
import { type Position2D, type Rectangle2D } from './oss-layout';
export const GRID_SIZE = 10; // pixels - size of OSS grid
const MIN_DISTANCE = 2 * GRID_SIZE; // pixels - minimum distance between nodes
const DISTANCE_X = 180; // pixels - insert x-distance between node centers
const DISTANCE_Y = 100; // pixels - insert y-distance between node centers
const OPERATION_NODE_WIDTH = 150;
const OPERATION_NODE_HEIGHT = 40;
/** Layout manipulations for {@link IOperationSchema}. */
export class LayoutManager {
public oss: IOperationSchema;
public layout: IOssLayout;
constructor(oss: IOperationSchema, layout?: IOssLayout) {
this.oss = oss;
if (layout) {
this.layout = layout;
} else {
this.layout = this.oss.layout;
}
}
/** Calculate insert position for a new {@link IOperation} */
calculateNewOperationPosition(data: ICreateOperationDTO): Position2D {
// TODO: check parent node
const result = { x: data.position_x, y: data.position_y };
const operations = this.layout.operations;
if (operations.length === 0) {
return result;
}
if (data.arguments.length === 0) {
let inputsPositions = operations.filter(pos =>
this.oss.operations.find(operation => operation.arguments.length === 0 && operation.id === pos.id)
);
if (inputsPositions.length === 0) {
inputsPositions = operations;
}
const maxX = Math.max(...inputsPositions.map(node => node.x));
const minY = Math.min(...inputsPositions.map(node => node.y));
result.x = maxX + DISTANCE_X;
result.y = minY;
} else {
const argNodes = operations.filter(pos => data.arguments.includes(pos.id));
const maxY = Math.max(...argNodes.map(node => node.y));
const minX = Math.min(...argNodes.map(node => node.x));
const maxX = Math.max(...argNodes.map(node => node.x));
result.x = Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE;
result.y = maxY + DISTANCE_Y;
}
let flagIntersect = false;
do {
flagIntersect = operations.some(
position => Math.abs(position.x - result.x) < MIN_DISTANCE && Math.abs(position.y - result.y) < MIN_DISTANCE
);
if (flagIntersect) {
result.x += MIN_DISTANCE;
result.y += MIN_DISTANCE;
}
} while (flagIntersect);
return result;
}
/** Calculate insert position for a new {@link IBlock} */
calculateNewBlockPosition(data: ICreateBlockDTO): Rectangle2D {
const block_nodes = data.children_blocks
.map(id => this.layout.blocks.find(block => block.id === id))
.filter(node => !!node);
const operation_nodes = data.children_operations
.map(id => this.layout.operations.find(operation => operation.id === id))
.filter(node => !!node);
if (block_nodes.length === 0 && operation_nodes.length === 0) {
return { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
}
let left = undefined;
let top = undefined;
let right = undefined;
let bottom = undefined;
for (const block of block_nodes) {
left = !left ? block.x - MIN_DISTANCE : Math.min(left, block.x - MIN_DISTANCE);
top = !top ? block.y - MIN_DISTANCE : Math.min(top, block.y - MIN_DISTANCE);
right = !right
? Math.max(left + data.width, block.x + block.width + MIN_DISTANCE)
: Math.max(right, block.x + block.width + MIN_DISTANCE);
bottom = !bottom
? Math.max(top + data.height, block.y + block.height + MIN_DISTANCE)
: Math.max(bottom, block.y + block.height + MIN_DISTANCE);
}
for (const operation of operation_nodes) {
left = !left ? operation.x - MIN_DISTANCE : Math.min(left, operation.x - MIN_DISTANCE);
top = !top ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE);
right = !right
? Math.max(left + data.width, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE)
: Math.max(right, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE);
bottom = !bottom
? Math.max(top + data.height, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE)
: Math.max(bottom, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE);
}
return {
x: left ?? data.position_x,
y: top ?? data.position_y,
width: right && left ? right - left : data.width,
height: bottom && top ? bottom - top : data.height
};
}
2025-05-14 10:56:50 +03:00
/** Update layout when parent changes */
onOperationChangeParent(targetID: number, newParent: number | null) {
console.error('not implemented', targetID, newParent);
}
/** Update layout when parent changes */
onBlockChangeParent(targetID: number, newParent: number | null) {
console.error('not implemented', targetID, newParent);
}
2025-04-28 13:59:38 +03:00
}