R: Refactor layout management
Some checks are pending
Frontend CI / build (22.x) (push) Waiting to run
Frontend CI / notify-failure (push) Blocked by required conditions

This commit is contained in:
Ivan 2025-04-28 13:59:38 +03:00
parent 6fa25b51fe
commit 309c1ba323
23 changed files with 217 additions and 210 deletions

View File

@ -10,18 +10,16 @@ import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateBlockDTO, type IOssLayout, schemaCreateBlock } from '../../backend/types';
import { type ICreateBlockDTO, schemaCreateBlock } from '../../backend/types';
import { useCreateBlock } from '../../backend/use-create-block';
import { type IOperationSchema } from '../../models/oss';
import { calculateNewBlockPosition } from '../../models/oss-api';
import { type LayoutManager } from '../../models/oss-layout-api';
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from '../../pages/oss-page/editor-oss-graph/graph/block-node';
import { TabBlockCard } from './tab-block-card';
import { TabBlockChildren } from './tab-block-children';
export interface DlgCreateBlockProps {
oss: IOperationSchema;
layout: IOssLayout;
manager: LayoutManager;
initialInputs: number[];
defaultX: number;
defaultY: number;
@ -37,7 +35,7 @@ export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgCreateBlock() {
const { createBlock } = useCreateBlock();
const { oss, layout, initialInputs, onCreate, defaultX, defaultY } = useDialogsStore(
const { manager, initialInputs, onCreate, defaultX, defaultY } = useDialogsStore(
state => state.props as DlgCreateBlockProps
);
@ -55,21 +53,21 @@ export function DlgCreateBlock() {
height: BLOCK_NODE_MIN_HEIGHT,
children_blocks: initialInputs.filter(id => id < 0).map(id => -id),
children_operations: initialInputs.filter(id => id > 0),
layout: layout
layout: manager.layout
},
mode: 'onChange'
});
const title = useWatch({ control: methods.control, name: 'item_data.title' });
const [activeTab, setActiveTab] = useState<TabID>(TabID.CARD);
const isValid = !!title && !oss.blocks.some(block => block.title === title);
const isValid = !!title && !manager.oss.blocks.some(block => block.title === title);
function onSubmit(data: ICreateBlockDTO) {
const rectangle = calculateNewBlockPosition(data, layout);
const rectangle = manager.calculateNewBlockPosition(data);
data.position_x = rectangle.x;
data.position_y = rectangle.y;
data.width = rectangle.width;
data.height = rectangle.height;
void createBlock({ itemID: oss.id, data: data }).then(response => onCreate?.(response.new_block.id));
void createBlock({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_block.id));
}
return (

View File

@ -11,7 +11,7 @@ import { SelectParent } from '../../components/select-parent';
import { type DlgCreateBlockProps } from './dlg-create-block';
export function TabBlockCard() {
const { oss } = useDialogsStore(state => state.props as DlgCreateBlockProps);
const { manager } = useDialogsStore(state => state.props as DlgCreateBlockProps);
const {
register,
control,
@ -31,8 +31,8 @@ export function TabBlockCard() {
control={control}
render={({ field }) => (
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания не выбран'
onChange={value => field.onChange(value ? value.id : null)}
/>

View File

@ -11,7 +11,7 @@ import { type DlgCreateBlockProps } from './dlg-create-block';
export function TabBlockChildren() {
const { setValue, control } = useFormContext<ICreateBlockDTO>();
const { oss } = useDialogsStore(state => state.props as DlgCreateBlockProps);
const { manager } = useDialogsStore(state => state.props as DlgCreateBlockProps);
const children_blocks = useWatch({ control, name: 'children_blocks' });
const children_operations = useWatch({ control, name: 'children_operations' });
@ -33,7 +33,12 @@ export function TabBlockChildren() {
return (
<div className='cc-fade-in cc-column'>
<Label text={`Выбор содержания: [ ${value.length} ]`} />
<PickContents schema={oss} value={value} onChange={newValue => handleChangeSelected(newValue)} rows={10} />
<PickContents
schema={manager.oss}
value={value}
onChange={newValue => handleChangeSelected(newValue)}
rows={10}
/>
</div>
);
}

View File

@ -10,18 +10,16 @@ import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO, type IOssLayout, OperationType, schemaCreateOperation } from '../../backend/types';
import { type ICreateOperationDTO, OperationType, schemaCreateOperation } from '../../backend/types';
import { useCreateOperation } from '../../backend/use-create-operation';
import { describeOperationType, labelOperationType } from '../../labels';
import { type IOperationSchema } from '../../models/oss';
import { calculateNewOperationPosition } from '../../models/oss-api';
import { type LayoutManager } from '../../models/oss-layout-api';
import { TabInputOperation } from './tab-input-operation';
import { TabSynthesisOperation } from './tab-synthesis-operation';
export interface DlgCreateOperationProps {
oss: IOperationSchema;
layout: IOssLayout;
manager: LayoutManager;
initialParent: number | null;
initialInputs: number[];
defaultX: number;
@ -38,7 +36,7 @@ export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgCreateOperation() {
const { createOperation } = useCreateOperation();
const { oss, layout, initialInputs, initialParent, onCreate, defaultX, defaultY } = useDialogsStore(
const { manager, initialInputs, initialParent, onCreate, defaultX, defaultY } = useDialogsStore(
state => state.props as DlgCreateOperationProps
);
@ -57,19 +55,21 @@ export function DlgCreateOperation() {
position_y: defaultY,
arguments: initialInputs,
create_schema: false,
layout: layout
layout: manager.layout
},
mode: 'onChange'
});
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
const [activeTab, setActiveTab] = useState(initialInputs.length === 0 ? TabID.INPUT : TabID.SYNTHESIS);
const isValid = !!alias && !oss.operations.some(operation => operation.alias === alias);
const isValid = !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
function onSubmit(data: ICreateOperationDTO) {
const target = calculateNewOperationPosition(oss, data, layout);
const target = manager.calculateNewOperationPosition(data);
data.position_x = target.x;
data.position_y = target.y;
void createOperation({ itemID: oss.id, data: data }).then(response => onCreate?.(response.new_operation.id));
void createOperation({ itemID: manager.oss.id, data: data }).then(response =>
onCreate?.(response.new_operation.id)
);
}
function handleSelectTab(newTab: TabID, last: TabID) {

View File

@ -18,9 +18,9 @@ import { sortItemsForOSS } from '../../models/oss-api';
import { type DlgCreateOperationProps } from './dlg-create-operation';
export function TabInputOperation() {
const { oss } = useDialogsStore(state => state.props as DlgCreateOperationProps);
const { manager } = useDialogsStore(state => state.props as DlgCreateOperationProps);
const { items: libraryItems } = useLibrary();
const sortedItems = sortItemsForOSS(oss, libraryItems);
const sortedItems = sortItemsForOSS(manager.oss, libraryItems);
const {
register,
@ -31,7 +31,7 @@ export function TabInputOperation() {
const createSchema = useWatch({ control, name: 'create_schema' });
function baseFilter(item: ILibraryItem) {
return !oss.schemas.includes(item.id);
return !manager.oss.schemas.includes(item.id);
}
function handleChangeCreateSchema(value: boolean) {
@ -75,8 +75,8 @@ export function TabInputOperation() {
control={control}
render={({ field }) => (
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания'
onChange={value => field.onChange(value ? value.id : null)}
/>

View File

@ -10,7 +10,7 @@ import { SelectParent } from '../../components/select-parent';
import { type DlgCreateOperationProps } from './dlg-create-operation';
export function TabSynthesisOperation() {
const { oss } = useDialogsStore(state => state.props as DlgCreateOperationProps);
const { manager } = useDialogsStore(state => state.props as DlgCreateOperationProps);
const {
register,
control,
@ -40,8 +40,8 @@ export function TabSynthesisOperation() {
control={control}
render={({ field }) => (
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания'
onChange={value => field.onChange(value ? value.id : null)}
/>
@ -64,7 +64,7 @@ export function TabSynthesisOperation() {
name='arguments'
control={control}
render={({ field }) => (
<PickMultiOperation items={oss.operations} value={field.value} onChange={field.onChange} rows={6} />
<PickMultiOperation items={manager.oss.operations} value={field.value} onChange={field.onChange} rows={6} />
)}
/>
</div>

View File

@ -7,19 +7,19 @@ import { TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type IOssLayout, type IUpdateBlockDTO, schemaUpdateBlock } from '../backend/types';
import { type IUpdateBlockDTO, schemaUpdateBlock } from '../backend/types';
import { useUpdateBlock } from '../backend/use-update-block';
import { SelectParent } from '../components/select-parent';
import { type IBlock, type IOperationSchema } from '../models/oss';
import { type IBlock } from '../models/oss';
import { type LayoutManager } from '../models/oss-layout-api';
export interface DlgEditBlockProps {
oss: IOperationSchema;
manager: LayoutManager;
target: IBlock;
layout: IOssLayout;
}
export function DlgEditBlock() {
const { oss, target, layout } = useDialogsStore(state => state.props as DlgEditBlockProps);
const { manager, target } = useDialogsStore(state => state.props as DlgEditBlockProps);
const { updateBlock } = useUpdateBlock();
const {
@ -36,13 +36,13 @@ export function DlgEditBlock() {
description: target.description,
parent: target.parent
},
layout: layout
layout: manager.layout
},
mode: 'onChange'
});
function onSubmit(data: IUpdateBlockDTO) {
return updateBlock({ itemID: oss.id, data });
return updateBlock({ itemID: manager.oss.id, data });
}
return (
@ -64,8 +64,8 @@ export function DlgEditBlock() {
control={control}
render={({ field }) => (
<SelectParent
items={oss.blocks.filter(block => block.id !== target.id)}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
items={manager.oss.blocks.filter(block => block.id !== target.id)}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания не выбран'
onChange={value => field.onChange(value ? value.id : null)}
/>

View File

@ -11,18 +11,18 @@ import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { type IOssLayout, type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types';
import { type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types';
import { useUpdateOperation } from '../../backend/use-update-operation';
import { type IOperation, type IOperationSchema } from '../../models/oss';
import { type IOperation } from '../../models/oss';
import { type LayoutManager } from '../../models/oss-layout-api';
import { TabArguments } from './tab-arguments';
import { TabOperation } from './tab-operation';
import { TabSynthesis } from './tab-synthesis';
export interface DlgEditOperationProps {
oss: IOperationSchema;
manager: LayoutManager;
target: IOperation;
layout: IOssLayout;
}
export const TabID = {
@ -33,7 +33,7 @@ export const TabID = {
export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgEditOperation() {
const { oss, target, layout } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { manager, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { updateOperation } = useUpdateOperation();
const methods = useForm<IUpdateOperationDTO>({
@ -51,14 +51,17 @@ export function DlgEditOperation() {
original: sub.original,
substitution: sub.substitution
})),
layout: layout
layout: manager.layout
},
mode: 'onChange'
});
const [activeTab, setActiveTab] = useState<TabID>(TabID.CARD);
function onSubmit(data: IUpdateOperationDTO) {
return updateOperation({ itemID: oss.id, data });
// if (data.item_data.parent !== target.parent) {
// data.layout = updateLayoutOnOperationChange(data.target, data.item_data.parent, data.layout);
// }
return updateOperation({ itemID: manager.oss.id, data });
}
return (

View File

@ -11,9 +11,9 @@ import { type DlgEditOperationProps } from './dlg-edit-operation';
export function TabArguments() {
const { control, setValue } = useFormContext<IUpdateOperationDTO>();
const { oss, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
const potentialCycle = [target.id, ...oss.graph.expandAllOutputs([target.id])];
const filtered = oss.operations.filter(item => !potentialCycle.includes(item.id));
const { manager, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
const potentialCycle = [target.id, ...manager.oss.graph.expandAllOutputs([target.id])];
const filtered = manager.oss.operations.filter(item => !potentialCycle.includes(item.id));
function handleChangeArguments(prev: number[], newValue: number[]) {
setValue('arguments', newValue, { shouldValidate: true });

View File

@ -9,7 +9,7 @@ import { SelectParent } from '../../components/select-parent';
import { type DlgEditOperationProps } from './dlg-edit-operation';
export function TabOperation() {
const { oss } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { manager } = useDialogsStore(state => state.props as DlgEditOperationProps);
const {
register,
control,
@ -37,8 +37,8 @@ export function TabOperation() {
control={control}
render={({ field }) => (
<SelectParent
items={oss.blocks}
value={field.value ? oss.blockByID.get(field.value) ?? null : null}
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Блок содержания'
onChange={value => field.onChange(value ? value.id : null)}
/>

View File

@ -14,13 +14,13 @@ import { SubstitutionValidator } from '../../models/oss-api';
import { type DlgEditOperationProps } from './dlg-edit-operation';
export function TabSynthesis() {
const { oss } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { manager } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { control } = useFormContext<IUpdateOperationDTO>();
const inputs = useWatch({ control, name: 'arguments' });
const substitutions = useWatch({ control, name: 'substitutions' });
const schemasIDs = inputs
.map(id => oss.operationByID.get(id)!)
.map(id => manager.oss.operationByID.get(id)!)
.map(operation => operation.result)
.filter(id => id !== null);
const schemas = useRSForms(schemasIDs);

View File

@ -22,17 +22,9 @@ import {
import { infoMsg } from '@/utils/labels';
import { Graph } from '../../../models/graph';
import { type ICreateBlockDTO, type ICreateOperationDTO, type IOssLayout } from '../backend/types';
import { describeSubstitutionError } from '../labels';
import { OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../pages/oss-page/editor-oss-graph/graph/node-core';
import { type IOperationSchema, type IOssItem, SubstitutionErrorType } 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 STARTING_SUB_INDEX = 900; // max semantic index for starting substitution
@ -477,100 +469,3 @@ export function getRelocateCandidates(
const unreachable = schema.graph.expandAllOutputs(unreachableBases);
return addedCst.filter(cst => !unreachable.includes(cst.id));
}
/** Calculate insert position for a new {@link IOperation} */
export function calculateNewOperationPosition(
oss: IOperationSchema,
data: ICreateOperationDTO,
layout: IOssLayout
): Position2D {
// TODO: check parent node
const result = { x: data.position_x, y: data.position_y };
const operations = layout.operations;
if (operations.length === 0) {
return result;
}
if (data.arguments.length === 0) {
let inputsPositions = operations.filter(pos =>
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} */
export function calculateNewBlockPosition(data: ICreateBlockDTO, layout: IOssLayout): Rectangle2D {
const block_nodes = data.children_blocks
.map(id => layout.blocks.find(block => block.id === id))
.filter(node => !!node);
const operation_nodes = data.children_operations
.map(id => 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);
}
console.log('left, top, right, bottom', left, top, right, bottom);
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
};
}

View File

@ -0,0 +1,120 @@
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);
}
console.log('left, top, right, bottom', left, top, right, bottom);
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
};
}
}

View File

@ -2,11 +2,10 @@
import { useRef } from 'react';
import { isOperation } from '@/features/oss/models/oss-api';
import { Dropdown } from '@/components/dropdown';
import { type IBlock, type IOperation, type IOssItem } from '../../../../models/oss';
import { isOperation } from '../../../../models/oss-api';
import { MenuBlock } from './menu-block';
import { MenuOperation } from './menu-operation';

View File

@ -1,13 +1,13 @@
'use client';
import { useDeleteBlock } from '@/features/oss/backend/use-delete-block';
import { DropdownButton } from '@/components/dropdown';
import { IconDestroy, IconEdit2 } from '@/components/icons';
import { useDialogsStore } from '@/stores/dialogs';
import { useDeleteBlock } from '../../../../backend/use-delete-block';
import { useMutatingOss } from '../../../../backend/use-mutating-oss';
import { type IBlock } from '../../../../models/oss';
import { LayoutManager } from '../../../../models/oss-layout-api';
import { useOssEdit } from '../../oss-edit-context';
import { useGetLayout } from '../use-get-layout';
@ -30,9 +30,8 @@ export function MenuBlock({ block, onHide }: MenuBlockProps) {
}
onHide();
showEditBlock({
oss: schema,
target: block,
layout: getLayout()
manager: new LayoutManager(schema, getLayout()),
target: block
});
}

View File

@ -3,8 +3,6 @@ import { toast } from 'react-toastify';
import { urls, useConceptNavigation } from '@/app';
import { useLibrary } from '@/features/library/backend/use-library';
import { useCreateInput } from '@/features/oss/backend/use-create-input';
import { useExecuteOperation } from '@/features/oss/backend/use-execute-operation';
import { DropdownButton } from '@/components/dropdown';
import {
@ -21,8 +19,11 @@ import { errorMsg } from '@/utils/labels';
import { prepareTooltip } from '@/utils/utils';
import { OperationType } from '../../../../backend/types';
import { useCreateInput } from '../../../../backend/use-create-input';
import { useExecuteOperation } from '../../../../backend/use-execute-operation';
import { useMutatingOss } from '../../../../backend/use-mutating-oss';
import { type IOperation } from '../../../../models/oss';
import { LayoutManager } from '../../../../models/oss-layout-api';
import { useOssEdit } from '../../oss-edit-context';
import { useGetLayout } from '../use-get-layout';
@ -93,9 +94,8 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
}
onHide();
showEditOperation({
oss: schema,
target: operation,
layout: getLayout()
manager: new LayoutManager(schema, getLayout()),
target: operation
});
}

View File

@ -3,13 +3,12 @@
import { NodeResizeControl } from 'reactflow';
import clsx from 'clsx';
import { useOperationTooltipStore } from '@/features/oss/stores/operation-tooltip';
import { useOSSGraphStore } from '@/features/oss/stores/oss-graph';
import { IconResize } from '@/components/icons';
import { globalIDs } from '@/utils/constants';
import { type BlockInternalNode } from '../../../../models/oss-layout';
import { useOperationTooltipStore } from '../../../../stores/operation-tooltip';
import { useOSSGraphStore } from '../../../../stores/oss-graph';
import { useOssEdit } from '../../oss-edit-context';
import { useOssFlow } from '../oss-flow-context';

View File

@ -2,8 +2,6 @@
import clsx from 'clsx';
import { useOSSGraphStore } from '@/features/oss/stores/oss-graph';
import { IconConsolidation, IconRSForm } from '@/components/icons';
import { cn } from '@/components/utils';
import { Indicator } from '@/components/view';
@ -12,11 +10,9 @@ import { globalIDs } from '@/utils/constants';
import { OperationType } from '../../../../backend/types';
import { type OperationInternalNode } from '../../../../models/oss-layout';
import { useOperationTooltipStore } from '../../../../stores/operation-tooltip';
import { useOSSGraphStore } from '../../../../stores/oss-graph';
import { useOssEdit } from '../../oss-edit-context';
export const OPERATION_NODE_WIDTH = 150;
export const OPERATION_NODE_HEIGHT = 40;
// characters - threshold for long labels - small font
const LONG_LABEL_CHARS = 14;

View File

@ -13,20 +13,19 @@ import {
} from 'reactflow';
import clsx from 'clsx';
import { useDeleteBlock } from '@/features/oss/backend/use-delete-block';
import { useMoveItems } from '@/features/oss/backend/use-move-items';
import { type IOperationSchema } from '@/features/oss/models/oss';
import { useThrottleCallback } from '@/hooks/use-throttle-callback';
import { useMainHeight } from '@/stores/app-layout';
import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { promptText } from '@/utils/labels';
import { useDeleteBlock } from '../../../backend/use-delete-block';
import { useMoveItems } from '../../../backend/use-move-items';
import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdateLayout } from '../../../backend/use-update-layout';
import { GRID_SIZE } from '../../../models/oss-api';
import { type IOperationSchema } from '../../../models/oss';
import { type OssNode, type Position2D } from '../../../models/oss-layout';
import { GRID_SIZE, LayoutManager } from '../../../models/oss-layout-api';
import { useOperationTooltipStore } from '../../../stores/operation-tooltip';
import { useOSSGraphStore } from '../../../stores/oss-graph';
import { useOssEdit } from '../oss-edit-context';
@ -153,10 +152,9 @@ export function OssFlow() {
function handleCreateOperation() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateOperation({
oss: schema,
manager: new LayoutManager(schema, getLayout()),
defaultX: targetPosition.x,
defaultY: targetPosition.y,
layout: getLayout(),
initialInputs: selected.filter(id => id > 0),
initialParent: extractSingleBlock(selected),
onCreate: () =>
@ -167,10 +165,9 @@ export function OssFlow() {
function handleCreateBlock() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateBlock({
oss: schema,
manager: new LayoutManager(schema, getLayout()),
defaultX: targetPosition.x,
defaultY: targetPosition.y,
layout: getLayout(),
initialInputs: selected,
onCreate: () =>
setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout)
@ -226,9 +223,8 @@ export function OssFlow() {
const block = schema.blockByID.get(-Number(node.id));
if (block) {
showEditBlock({
oss: schema,
target: block,
layout: getLayout()
manager: new LayoutManager(schema, getLayout()),
target: block
});
}
} else {

View File

@ -5,8 +5,6 @@ import { useReactFlow } from 'reactflow';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { useExecuteOperation } from '@/features/oss/backend/use-execute-operation';
import { useUpdateLayout } from '@/features/oss/backend/use-update-layout';
import { MiniButton } from '@/components/control';
import {
@ -28,7 +26,10 @@ import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { OperationType } from '../../../backend/types';
import { useExecuteOperation } from '../../../backend/use-execute-operation';
import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdateLayout } from '../../../backend/use-update-layout';
import { LayoutManager } from '../../../models/oss-layout-api';
import { useOssEdit } from '../oss-edit-context';
import { VIEW_PADDING } from './oss-flow';
@ -114,15 +115,13 @@ export function ToolbarOssGraph({
function handleEditItem() {
if (selectedOperation) {
showEditOperation({
oss: schema,
target: selectedOperation,
layout: getLayout()
manager: new LayoutManager(schema, getLayout()),
target: selectedOperation
});
} else if (selectedBlock) {
showEditBlock({
oss: schema,
target: selectedBlock,
layout: getLayout()
manager: new LayoutManager(schema, getLayout()),
target: selectedBlock
});
}
}

View File

@ -1,9 +1,8 @@
import { type Node, useReactFlow } from 'reactflow';
import { type IOssLayout } from '@/features/oss/backend/types';
import { type IOperationSchema } from '@/features/oss/models/oss';
import { type Position2D } from '@/features/oss/models/oss-layout';
import { type IOssLayout } from '../../../backend/types';
import { type IOperationSchema } from '../../../models/oss';
import { type Position2D } from '../../../models/oss-layout';
import { useOssEdit } from '../oss-edit-context';
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from './graph/block-node';

View File

@ -3,7 +3,6 @@ import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { MiniSelectorOSS } from '@/features/library/components';
import { CstType } from '@/features/rsform';
import { MiniButton } from '@/components/control';
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
@ -19,6 +18,7 @@ import {
import { prefixes } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
import { CstType } from '../../../backend/types';
import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
import { IconCstType } from '../../../components/icon-cst-type';
import { getCstTypeShortcut, labelCstType } from '../../../labels';

View File

@ -3,12 +3,11 @@
import { Handle, Position } from 'reactflow';
import clsx from 'clsx';
import { labelCstTypification } from '@/features/rsform/labels';
import { APP_COLORS } from '@/styling/colors';
import { globalIDs } from '@/utils/constants';
import { colorBgGraphNode } from '../../../../colors';
import { labelCstTypification } from '../../../../labels';
import { type IConstituenta } from '../../../../models/rsform';
import { useTermGraphStore } from '../../../../stores/term-graph';
import { useRSEdit } from '../../rsedit-context';