mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-25 20:40:36 +03:00
R: Refactor layout management
This commit is contained in:
parent
6fa25b51fe
commit
309c1ba323
|
@ -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 (
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
120
rsconcept/frontend/src/features/oss/models/oss-layout-api.ts
Normal file
120
rsconcept/frontend/src/features/oss/models/oss-layout-api.ts
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue
Block a user