M: Improve node positioning in OSS

This commit is contained in:
Ivan 2024-08-02 11:17:27 +03:00
parent 044a484607
commit e96206b7db
12 changed files with 68 additions and 35 deletions

View File

@ -197,6 +197,7 @@ class OperationSchema:
parent = parents.get(cst.pk) parent = parents.get(cst.pk)
assert parent is not None assert parent is not None
Inheritance.objects.create( Inheritance.objects.create(
operation=operation,
child=cst, child=cst,
parent=parent parent=parent
) )

View File

@ -140,13 +140,11 @@ function PickSubstitutions({
() => [ () => [
columnHelper.accessor(item => item.substitution_source?.alias ?? 'N/A', { columnHelper.accessor(item => item.substitution_source?.alias ?? 'N/A', {
id: 'left_schema', id: 'left_schema',
header: 'Операция',
size: 100, size: 100,
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-right'>{props.getValue()}</div> cell: props => <div className='min-w-[10.5rem] text-ellipsis text-left'>{props.getValue()}</div>
}), }),
columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', { columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', {
id: 'left_alias', id: 'left_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65, size: 65,
cell: props => cell: props =>
props.row.original.substitution ? ( props.row.original.substitution ? (
@ -157,13 +155,11 @@ function PickSubstitutions({
}), }),
columnHelper.display({ columnHelper.display({
id: 'status', id: 'status',
header: '',
size: 40, size: 40,
cell: () => <IconPageRight size='1.2rem' /> cell: () => <IconPageRight size='1.2rem' />
}), }),
columnHelper.accessor(item => item.original?.alias ?? 'N/A', { columnHelper.accessor(item => item.original?.alias ?? 'N/A', {
id: 'right_alias', id: 'right_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65, size: 65,
cell: props => cell: props =>
props.row.original.original ? ( props.row.original.original ? (
@ -174,9 +170,8 @@ function PickSubstitutions({
}), }),
columnHelper.accessor(item => item.original_source?.alias ?? 'N/A', { columnHelper.accessor(item => item.original_source?.alias ?? 'N/A', {
id: 'right_schema', id: 'right_schema',
header: 'Операция',
size: 100, size: 100,
cell: props => <div className='min-w-[8rem] text-ellipsis'>{props.getValue()}</div> cell: props => <div className='min-w-[8rem] text-ellipsis text-right'>{props.getValue()}</div>
}), }),
columnHelper.display({ columnHelper.display({
id: 'actions', id: 'actions',

View File

@ -118,7 +118,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
/> />
</TabPanel> </TabPanel>
), ),
[alias, comment, title, attachedID, oss, createSchema] [alias, comment, title, attachedID, oss, createSchema, setAlias]
); );
const synthesisPanel = useMemo( const synthesisPanel = useMemo(
@ -137,7 +137,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
/> />
</TabPanel> </TabPanel>
), ),
[oss, alias, comment, title, inputs] [oss, alias, comment, title, inputs, setAlias]
); );
return ( return (

View File

@ -92,7 +92,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
/> />
</TabPanel> </TabPanel>
), ),
[alias, comment, title] [alias, comment, title, setAlias]
); );
const argumentsPanel = useMemo( const argumentsPanel = useMemo(
@ -106,7 +106,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
/> />
</TabPanel> </TabPanel>
), ),
[oss, target, inputs] [oss, target, inputs, setInputs]
); );
const synthesisPanel = useMemo( const synthesisPanel = useMemo(

View File

@ -46,7 +46,7 @@ function FormCreateItem() {
const location = useMemo(() => combineLocation(head, body), [head, body]); const location = useMemo(() => combineLocation(head, body), [head, body]);
const isValid = useMemo(() => validateLocation(location), [location]); const isValid = useMemo(() => validateLocation(location), [location]);
const [initLocation] = useLocalStorage<string>(storage.librarySearchLocation, ''); const [initLocation, setInitLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
const [fileName, setFileName] = useState(''); const [fileName, setFileName] = useState('');
const [file, setFile] = useState<File | undefined>(); const [file, setFile] = useState<File | undefined>();
@ -81,6 +81,7 @@ function FormCreateItem() {
file: file, file: file,
fileName: file?.name fileName: file?.name
}; };
setInitLocation(location);
createItem(data, newItem => { createItem(data, newItem => {
toast.success(information.newLibraryItem); toast.success(information.newLibraryItem);
if (itemType == LibraryItemType.RSFORM) { if (itemType == LibraryItemType.RSFORM) {

View File

@ -33,7 +33,7 @@ function InputNode(node: OssNodeInternal) {
disabled={!hasFile} disabled={!hasFile}
/> />
</Overlay> </Overlay>
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'> <div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
{node.data.label} {node.data.label}
{controller.showTooltip && !node.dragging ? ( {controller.showTooltip && !node.dragging ? (
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} /> <TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />

View File

@ -33,7 +33,7 @@ function OperationNode(node: OssNodeInternal) {
/> />
</Overlay> </Overlay>
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'> <div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
{node.data.label} {node.data.label}
{controller.showTooltip && !node.dragging ? ( {controller.showTooltip && !node.dragging ? (
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} /> <TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />

View File

@ -24,7 +24,7 @@ import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { useOSS } from '@/context/OssContext'; import { useOSS } from '@/context/OssContext';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { OssNode } from '@/models/miscellaneous'; import { OssNode } from '@/models/miscellaneous';
import { OperationID } from '@/models/oss'; import { OperationID, OperationType } from '@/models/oss';
import { PARAMETER, storage } from '@/utils/constants'; import { PARAMETER, storage } from '@/utils/constants';
import { errors } from '@/utils/labels'; import { errors } from '@/utils/labels';
@ -127,19 +127,32 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
if (!controller.schema) { if (!controller.schema) {
return; return;
} }
let target = { x: 0, y: 0 }; let target = { x: 0, y: 0 };
const positions = getPositions(); const positions = getPositions();
if (positions.length == 0) {
if (inputs.length <= 1) {
target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
}
if (inputs.length <= 1) {
let inputsNodes = positions.filter(pos =>
controller.schema!.items.find(
operation => operation.operation_type === OperationType.INPUT && operation.id === pos.id
)
);
if (inputsNodes.length > 0) {
inputsNodes = positions;
}
const maxX = Math.max(...inputsNodes.map(node => node.position_x));
const minY = Math.min(...inputsNodes.map(node => node.position_y));
target.x = maxX + 180;
target.y = minY;
} else { } else {
const inputsNodes = positions.filter(pos => inputs.includes(pos.id)); const inputsNodes = positions.filter(pos => inputs.includes(pos.id));
const maxY = Math.max(...inputsNodes.map(node => node.position_y)); const maxY = Math.max(...inputsNodes.map(node => node.position_y));
const minX = Math.min(...inputsNodes.map(node => node.position_x)); const minX = Math.min(...inputsNodes.map(node => node.position_x));
const maxX = Math.max(...inputsNodes.map(node => node.position_x)); const maxX = Math.max(...inputsNodes.map(node => node.position_x));
target.y = maxY + 100;
target.x = Math.ceil((maxX + minX) / 2 / PARAMETER.ossGridSize) * PARAMETER.ossGridSize; target.x = Math.ceil((maxX + minX) / 2 / PARAMETER.ossGridSize) * PARAMETER.ossGridSize;
target.y = maxY + 100;
} }
let flagIntersect = false; let flagIntersect = false;
@ -154,8 +167,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
target.y += PARAMETER.ossMinDistance; target.y += PARAMETER.ossMinDistance;
} }
} while (flagIntersect); } while (flagIntersect);
controller.promptCreateOperation({
controller.promptCreateOperation(target.x, target.y, inputs, positions); x: target.x,
y: target.y,
inputs: inputs,
positions: positions,
callback: () => flow.fitView({ duration: PARAMETER.zoomDuration })
});
}, },
[controller, getPositions, flow] [controller, getPositions, flow]
); );

View File

@ -26,8 +26,17 @@ import {
OperationID OperationID
} from '@/models/oss'; } from '@/models/oss';
import { UserID, UserLevel } from '@/models/user'; import { UserID, UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
export interface ICreateOperationPrompt {
x: number;
y: number;
inputs: OperationID[];
positions: IOperationPosition[];
callback: (newID: OperationID) => void;
}
export interface IOssEditContext { export interface IOssEditContext {
schema?: IOperationSchema; schema?: IOperationSchema;
selected: OperationID[]; selected: OperationID[];
@ -51,7 +60,7 @@ export interface IOssEditContext {
openOperationSchema: (target: OperationID) => void; openOperationSchema: (target: OperationID) => void;
savePositions: (positions: IOperationPosition[], callback?: () => void) => void; savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
promptCreateOperation: (x: number, y: number, inputs: OperationID[], positions: IOperationPosition[]) => void; promptCreateOperation: (props: ICreateOperationPrompt) => void;
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void; deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
createInput: (target: OperationID, positions: IOperationPosition[]) => void; createInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
@ -97,6 +106,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const [showCreateOperation, setShowCreateOperation] = useState(false); const [showCreateOperation, setShowCreateOperation] = useState(false);
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 }); const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
const [initialInputs, setInitialInputs] = useState<OperationID[]>([]); const [initialInputs, setInitialInputs] = useState<OperationID[]>([]);
const [createCallback, setCreateCallback] = useState<((newID: OperationID) => void) | undefined>(undefined);
const [positions, setPositions] = useState<IOperationPosition[]>([]); const [positions, setPositions] = useState<IOperationPosition[]>([]);
const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined); const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined);
const targetOperation = useMemo( const targetOperation = useMemo(
@ -209,24 +220,27 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
[model] [model]
); );
const promptCreateOperation = useCallback( const promptCreateOperation = useCallback(({ x, y, inputs, positions, callback }: ICreateOperationPrompt) => {
(x: number, y: number, inputs: OperationID[], positions: IOperationPosition[]) => {
setInsertPosition({ x: x, y: y }); setInsertPosition({ x: x, y: y });
setInitialInputs(inputs); setInitialInputs(inputs);
setPositions(positions); setPositions(positions);
setCreateCallback(() => callback);
setShowCreateOperation(true); setShowCreateOperation(true);
}, }, []);
[]
);
const handleCreateOperation = useCallback( const handleCreateOperation = useCallback(
(data: IOperationCreateData) => { (data: IOperationCreateData) => {
data.positions = positions; data.positions = positions;
data.item_data.position_x = insertPosition.x; data.item_data.position_x = insertPosition.x;
data.item_data.position_y = insertPosition.y; data.item_data.position_y = insertPosition.y;
model.createOperation(data, operation => toast.success(information.newOperation(operation.alias))); model.createOperation(data, operation => {
toast.success(information.newOperation(operation.alias));
if (createCallback) {
setTimeout(() => createCallback(operation.id), PARAMETER.refreshTimeout);
}
});
}, },
[model, positions, insertPosition] [model, positions, insertPosition, createCallback]
); );
const promptEditOperation = useCallback((target: OperationID, positions: IOperationPosition[]) => { const promptEditOperation = useCallback((target: OperationID, positions: IOperationPosition[]) => {

View File

@ -145,12 +145,12 @@ function EditorRSExpression({
const controls = useMemo( const controls = useMemo(
() => ( () => (
<RSEditorControls <RSEditorControls
isOpen={showControls && (!disabled || model.processing)} isOpen={showControls && (!disabled || (model.processing && !activeCst?.is_inherited))}
disabled={disabled} disabled={disabled}
onEdit={handleEdit} onEdit={handleEdit}
/> />
), ),
[showControls, disabled, model.processing, handleEdit] [showControls, disabled, model.processing, handleEdit, activeCst]
); );
return ( return (

View File

@ -66,6 +66,8 @@
border: 1px solid; border: 1px solid;
padding: 2px; padding: 2px;
width: 150px; width: 150px;
height: 30px;
font-size: 14px;
border-radius: 5px; border-radius: 5px;
background-color: var(--cl-bg-120); background-color: var(--cl-bg-120);

View File

@ -17,6 +17,8 @@ export const PARAMETER = {
ossContextMenuWidth: 200, // pixels - width of OSS context menu ossContextMenuWidth: 200, // pixels - width of OSS context menu
ossGridSize: 10, // pixels - size of OSS grid ossGridSize: 10, // pixels - size of OSS grid
ossMinDistance: 20, // pixels - minimum distance between node centers ossMinDistance: 20, // pixels - minimum distance between node centers
ossDistanceX: 180, // pixels - insert x-distance between node centers
ossDistanceY: 100, // pixels - insert y-distance between node centers
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be