F: Improve OSS UI
This commit is contained in:
parent
3c961c8192
commit
2eff1b27b9
|
@ -44,6 +44,7 @@ class OperationCreateSerializer(serializers.Serializer):
|
|||
'alias', 'operation_type', 'title', 'sync_text', \
|
||||
'comment', 'result', 'position_x', 'position_y'
|
||||
|
||||
create_schema = serializers.BooleanField(default=False, required=False)
|
||||
item_data = OperationData()
|
||||
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
|
||||
positions = serializers.ListField(
|
||||
|
|
|
@ -207,6 +207,46 @@ class TestOssViewset(EndpointTester):
|
|||
new_operation = response.data['new_operation']
|
||||
self.assertEqual(new_operation['result'], self.ks1.model.pk)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_schema(self):
|
||||
self.populateData()
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'title': 'Test title',
|
||||
'comment': 'Comment',
|
||||
'operation_type': OperationType.INPUT
|
||||
},
|
||||
'create_schema': True,
|
||||
'positions': [],
|
||||
}
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
schema = LibraryItem.objects.get(pk=new_operation['result'])
|
||||
self.assertEqual(schema.alias, data['item_data']['alias'])
|
||||
self.assertEqual(schema.title, data['item_data']['title'])
|
||||
self.assertEqual(schema.comment, data['item_data']['comment'])
|
||||
self.assertEqual(schema.visible, False)
|
||||
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
|
||||
self.assertEqual(schema.location, self.owned.model.location)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
|
||||
def test_create_operation_result(self):
|
||||
self.populateData()
|
||||
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.model.pk
|
||||
},
|
||||
'positions': [],
|
||||
}
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
self.assertEqual(new_operation['result'], self.ks1.model.pk)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation(self):
|
||||
|
|
|
@ -98,7 +98,20 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
oss = m.OperationSchema(self.get_object())
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
|
||||
data: dict = serializer.validated_data['item_data']
|
||||
if data['operation_type'] == m.OperationType.INPUT and serializer.validated_data['create_schema']:
|
||||
schema = LibraryItem.objects.create(
|
||||
item_type=LibraryItemType.RSFORM,
|
||||
owner=oss.model.owner,
|
||||
alias=data['alias'],
|
||||
title=data['title'],
|
||||
comment=data['comment'],
|
||||
visible=False,
|
||||
access_policy=oss.model.access_policy,
|
||||
location=oss.model.location
|
||||
)
|
||||
data['result'] = schema
|
||||
new_operation = oss.create_operation(**data)
|
||||
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
|
||||
for argument in serializer.validated_data['arguments']:
|
||||
oss.add_argument(operation=new_operation, argument=argument)
|
||||
|
|
|
@ -1,31 +1,34 @@
|
|||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||
import { labelOperationType } from '@/utils/labels';
|
||||
|
||||
interface TooltipOperationProps {
|
||||
data: IOperation;
|
||||
node: OssNodeInternal;
|
||||
anchor: string;
|
||||
}
|
||||
|
||||
function TooltipOperation({ data, anchor }: TooltipOperationProps) {
|
||||
function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||
return (
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[30rem] dense'>
|
||||
<h2>Операция {data.alias}</h2>
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
|
||||
<h2>{node.data.operation.alias}</h2>
|
||||
<p>
|
||||
<b>Тип:</b> {labelOperationType(data.operation_type)}
|
||||
<b>Тип:</b> {labelOperationType(node.data.operation.operation_type)}
|
||||
</p>
|
||||
{data.title ? (
|
||||
{node.data.operation.title ? (
|
||||
<p>
|
||||
<b>Название: </b>
|
||||
{data.title}
|
||||
{node.data.operation.title}
|
||||
</p>
|
||||
) : null}
|
||||
{data.comment ? (
|
||||
{node.data.operation.comment ? (
|
||||
<p>
|
||||
<b>Комментарий: </b>
|
||||
{data.comment}
|
||||
{node.data.operation.comment}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<b>Положение:</b> [{node.xPos}, {node.yPos}]
|
||||
</p>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,8 +41,12 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
const [inputs, setInputs] = useState<OperationID[]>([]);
|
||||
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
|
||||
const [syncText, setSyncText] = useState(true);
|
||||
const [createSchema, setCreateSchema] = useState(false);
|
||||
|
||||
const isValid = useMemo(() => alias !== '', [alias]);
|
||||
const isValid = useMemo(
|
||||
() => (alias !== '' && activeTab === TabID.INPUT) || inputs.length != 1,
|
||||
[alias, activeTab, inputs]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (attachedID) {
|
||||
|
@ -68,7 +72,8 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
result: activeTab === TabID.INPUT ? attachedID ?? null : null
|
||||
},
|
||||
positions: positions,
|
||||
arguments: activeTab === TabID.INPUT ? undefined : inputs.length > 0 ? inputs : undefined
|
||||
arguments: activeTab === TabID.INPUT ? undefined : inputs.length > 0 ? inputs : undefined,
|
||||
create_schema: createSchema
|
||||
};
|
||||
onCreate(data);
|
||||
};
|
||||
|
@ -88,10 +93,12 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
setAttachedID={setAttachedID}
|
||||
syncText={syncText}
|
||||
setSyncText={setSyncText}
|
||||
createSchema={createSchema}
|
||||
setCreateSchema={setCreateSchema}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[alias, comment, title, attachedID, syncText, oss]
|
||||
[alias, comment, title, attachedID, syncText, oss, createSchema]
|
||||
);
|
||||
|
||||
const synthesisPanel = useMemo(
|
||||
|
@ -105,11 +112,12 @@ function DlgCreateOperation({ hideWindow, oss, insertPosition, positions, onCrea
|
|||
setComment={setComment}
|
||||
title={title}
|
||||
setTitle={setTitle}
|
||||
inputs={inputs}
|
||||
setInputs={setInputs}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[oss, alias, comment, title]
|
||||
[oss, alias, comment, title, inputs]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { IconReset } from '@/components/Icons';
|
||||
import PickSchema from '@/components/select/PickSchema';
|
||||
|
@ -27,6 +27,8 @@ interface TabInputOperationProps {
|
|||
setAttachedID: React.Dispatch<React.SetStateAction<LibraryItemID | undefined>>;
|
||||
syncText: boolean;
|
||||
setSyncText: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
createSchema: boolean;
|
||||
setCreateSchema: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
function TabInputOperation({
|
||||
|
@ -40,10 +42,19 @@ function TabInputOperation({
|
|||
attachedID,
|
||||
setAttachedID,
|
||||
syncText,
|
||||
setSyncText
|
||||
setSyncText,
|
||||
createSchema,
|
||||
setCreateSchema
|
||||
}: TabInputOperationProps) {
|
||||
const baseFilter = useCallback((item: ILibraryItem) => !oss.schemas.includes(item.id), [oss]);
|
||||
|
||||
useEffect(() => {
|
||||
if (createSchema) {
|
||||
setAttachedID(undefined);
|
||||
setSyncText(true);
|
||||
}
|
||||
}, [createSchema, setAttachedID, setSyncText]);
|
||||
|
||||
return (
|
||||
<AnimateFade className='cc-column'>
|
||||
<TextInput
|
||||
|
@ -84,7 +95,8 @@ function TabInputOperation({
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div className='flex gap-3 items-center'>
|
||||
<div className='flex justify-between gap-3 items-center'>
|
||||
<div className='flex gap-3'>
|
||||
<Label text='Загружаемая концептуальная схема' />
|
||||
<MiniButton
|
||||
title='Сбросить выбор схемы'
|
||||
|
@ -95,13 +107,21 @@ function TabInputOperation({
|
|||
disabled={attachedID == undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Checkbox
|
||||
value={createSchema}
|
||||
setValue={setCreateSchema}
|
||||
label='Создать новую схему'
|
||||
titleHtml='Создать пустую схему для загрузки'
|
||||
/>
|
||||
</div>
|
||||
{!createSchema ? (
|
||||
<PickSchema
|
||||
value={attachedID} // prettier: split-line
|
||||
onSelectValue={setAttachedID}
|
||||
rows={8}
|
||||
baseFilter={baseFilter}
|
||||
/>
|
||||
) : null}
|
||||
</AnimateFade>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ interface TabSynthesisOperationProps {
|
|||
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
||||
comment: string;
|
||||
setComment: React.Dispatch<React.SetStateAction<string>>;
|
||||
inputs: OperationID[];
|
||||
setInputs: React.Dispatch<React.SetStateAction<OperationID[]>>;
|
||||
}
|
||||
|
||||
|
@ -30,11 +31,14 @@ function TabSynthesisOperation({
|
|||
setTitle,
|
||||
comment,
|
||||
setComment,
|
||||
inputs,
|
||||
setInputs
|
||||
}: TabSynthesisOperationProps) {
|
||||
const [left, setLeft] = useState<IOperation | undefined>(undefined);
|
||||
const [right, setRight] = useState<IOperation | undefined>(undefined);
|
||||
|
||||
console.log(inputs);
|
||||
|
||||
useEffect(() => {
|
||||
const inputs: OperationID[] = [];
|
||||
if (left) {
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
* Module: Miscellaneous frontend model types. Future targets for refactoring aimed at extracting modules.
|
||||
*/
|
||||
|
||||
import { Node } from 'reactflow';
|
||||
|
||||
import { LibraryItemType, LocationHead } from './library';
|
||||
import { IOperation } from './oss';
|
||||
|
||||
/**
|
||||
* Represents graph dependency mode.
|
||||
|
@ -16,6 +19,31 @@ export enum DependencyMode {
|
|||
EXPAND_INPUTS
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents graph OSS node data.
|
||||
*/
|
||||
export interface OssNode extends Node {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
position: { x: number; y: number };
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents graph OSS node internal data.
|
||||
*/
|
||||
export interface OssNodeInternal {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
xPos: number;
|
||||
yPos: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents graph node coloring scheme.
|
||||
*/
|
||||
|
|
|
@ -66,6 +66,7 @@ export interface IOperationCreateData extends IPositionsData {
|
|||
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result' | 'sync_text'
|
||||
>;
|
||||
arguments: OperationID[] | undefined;
|
||||
create_schema: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,26 +4,17 @@ import { IconRSForm } from '@/components/Icons';
|
|||
import TooltipOperation from '@/components/info/TooltipOperation';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
|
||||
interface InputNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
}
|
||||
|
||||
function InputNode({ id, data }: InputNodeProps) {
|
||||
function InputNode(node: OssNodeInternal) {
|
||||
const controller = useOssEdit();
|
||||
|
||||
const hasFile = !!data.operation.result;
|
||||
const hasFile = !!node.data.operation.result;
|
||||
|
||||
const handleOpenSchema = () => {
|
||||
controller.openOperationSchema(Number(id));
|
||||
controller.openOperationSchema(Number(node.id));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -41,10 +32,10 @@ function InputNode({ id, data }: InputNodeProps) {
|
|||
disabled={!hasFile}
|
||||
/>
|
||||
</Overlay>
|
||||
<div id={`${prefixes.operation_list}${id}`} className='flex-grow text-center text-sm'>
|
||||
{data.label}
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||
{node.data.label}
|
||||
{controller.showTooltip ? (
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${id}`} data={data.operation} />
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
'use client';
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import { IconDestroy, IconEdit2, IconNewItem, IconRSForm } from '@/components/Icons';
|
||||
import Dropdown from '@/components/ui/Dropdown';
|
||||
import DropdownButton from '@/components/ui/DropdownButton';
|
||||
import useClickedOutside from '@/hooks/useClickedOutside';
|
||||
import { IOperation, OperationID, OperationType } from '@/models/oss';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { prepareTooltip } from '@/utils/labels';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
|
||||
export interface ContextMenuData {
|
||||
operation: IOperation;
|
||||
cursorX: number;
|
||||
cursorY: number;
|
||||
}
|
||||
|
||||
interface NodeContextMenuProps extends ContextMenuData {
|
||||
onHide: () => void;
|
||||
onDelete: (target: OperationID) => void;
|
||||
}
|
||||
|
||||
function NodeContextMenu({ operation, cursorX, cursorY, onHide, onDelete }: NodeContextMenuProps) {
|
||||
const controller = useOssEdit();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
const handleHide = useCallback(() => {
|
||||
setIsOpen(false);
|
||||
onHide();
|
||||
}, [onHide]);
|
||||
|
||||
useClickedOutside({ ref, callback: handleHide });
|
||||
|
||||
useEffect(() => setIsOpen(true), []);
|
||||
|
||||
const handleOpenSchema = () => {
|
||||
controller.openOperationSchema(operation.id);
|
||||
};
|
||||
|
||||
const handleEditSchema = () => {
|
||||
toast.error('Not implemented');
|
||||
handleHide();
|
||||
};
|
||||
|
||||
const handleEditOperation = () => {
|
||||
toast.error('Not implemented');
|
||||
handleHide();
|
||||
};
|
||||
|
||||
const handleDeleteOperation = () => {
|
||||
handleHide();
|
||||
onDelete(operation.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
|
||||
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
||||
<DropdownButton
|
||||
text='Свойства операции'
|
||||
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')}
|
||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleEditOperation}
|
||||
/>
|
||||
|
||||
{operation.result ? (
|
||||
<DropdownButton
|
||||
text='Открыть схему'
|
||||
title='Открыть привязанную КС'
|
||||
icon={<IconRSForm size='1rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleOpenSchema}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable && !operation.result && operation.operation_type === OperationType.INPUT ? (
|
||||
<DropdownButton
|
||||
text='Создать схему'
|
||||
title='Создать пустую схему для загрузки'
|
||||
icon={<IconNewItem size='1rem' className='icon-green' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleEditSchema}
|
||||
/>
|
||||
) : null}
|
||||
{controller.isMutable && operation.operation_type === OperationType.INPUT ? (
|
||||
<DropdownButton
|
||||
text='Привязать схему'
|
||||
title='Выбрать схему для загрузки'
|
||||
icon={<IconRSForm size='1rem' className='icon-primary' />}
|
||||
disabled={controller.isProcessing}
|
||||
onClick={handleEditSchema}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<DropdownButton
|
||||
text='Удалить операцию'
|
||||
icon={<IconDestroy size='1rem' className='icon-red' />}
|
||||
disabled={!controller.isMutable || controller.isProcessing}
|
||||
onClick={handleDeleteOperation}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default NodeContextMenu;
|
|
@ -4,25 +4,18 @@ import { IconRSForm } from '@/components/Icons';
|
|||
import TooltipOperation from '@/components/info/TooltipOperation';
|
||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import { IOperation } from '@/models/oss';
|
||||
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
interface OperationNodeProps {
|
||||
id: string;
|
||||
data: {
|
||||
label: string;
|
||||
operation: IOperation;
|
||||
};
|
||||
}
|
||||
|
||||
function OperationNode({ id, data }: OperationNodeProps) {
|
||||
function OperationNode(node: OssNodeInternal) {
|
||||
const controller = useOssEdit();
|
||||
|
||||
const hasFile = !!data.operation.result;
|
||||
const hasFile = !!node.data.operation.result;
|
||||
|
||||
const handleOpenSchema = () => {
|
||||
controller.openOperationSchema(Number(id));
|
||||
controller.openOperationSchema(Number(node.id));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -41,9 +34,9 @@ function OperationNode({ id, data }: OperationNodeProps) {
|
|||
/>
|
||||
</Overlay>
|
||||
|
||||
<div id={`${prefixes.operation_list}${id}`} className='flex-grow text-center text-sm'>
|
||||
{data.label}
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${id}`} data={data.operation} />
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center text-sm'>
|
||||
{node.data.label}
|
||||
<TooltipOperation anchor={`#${prefixes.operation_list}${node.id}`} node={node} />
|
||||
</div>
|
||||
|
||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40 }} />
|
||||
|
|
|
@ -18,15 +18,19 @@ import {
|
|||
useReactFlow
|
||||
} from 'reactflow';
|
||||
|
||||
import { CProps } from '@/components/props';
|
||||
import Overlay from '@/components/ui/Overlay';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useOSS } from '@/context/OssContext';
|
||||
import { OssNode } from '@/models/miscellaneous';
|
||||
import { OperationID } from '@/models/oss';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { errors } from '@/utils/labels';
|
||||
|
||||
import { useOssEdit } from '../OssEditContext';
|
||||
import InputNode from './InputNode';
|
||||
import NodeContextMenu, { ContextMenuData } from './NodeContextMenu';
|
||||
import OperationNode from './OperationNode';
|
||||
import ToolbarOssGraph from './ToolbarOssGraph';
|
||||
|
||||
|
@ -46,6 +50,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
const [nodes, setNodes, onNodesChange] = useNodesState([]);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
|
||||
const [toggleReset, setToggleReset] = useState(false);
|
||||
const [menuProps, setMenuProps] = useState<ContextMenuData | undefined>(undefined);
|
||||
|
||||
const onSelectionChange = useCallback(
|
||||
({ nodes }: { nodes: Node[] }) => {
|
||||
|
@ -118,13 +123,20 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
controller.promptCreateOperation(center.x, center.y, getPositions());
|
||||
}, [controller, getPositions, flow]);
|
||||
|
||||
const handleDeleteOperation = useCallback(() => {
|
||||
const handleDeleteSelected = useCallback(() => {
|
||||
if (controller.selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
controller.deleteOperation(controller.selected[0], getPositions());
|
||||
}, [controller, getPositions]);
|
||||
|
||||
const handleDeleteOperation = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.deleteOperation(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
|
||||
const handleFitView = useCallback(() => {
|
||||
flow.fitView({ duration: PARAMETER.zoomDuration });
|
||||
}, [flow]);
|
||||
|
@ -167,15 +179,30 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
}, [colors, nodes]);
|
||||
|
||||
const handleContextMenu = useCallback(
|
||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
(event: CProps.EventMouse, node: OssNode) => {
|
||||
console.log(node);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
controller.setShowTooltip(prev => !prev);
|
||||
// setShowContextMenu(true);
|
||||
|
||||
setMenuProps({
|
||||
operation: node.data.operation,
|
||||
cursorX: event.clientX,
|
||||
cursorY: event.clientY
|
||||
});
|
||||
controller.setShowTooltip(false);
|
||||
},
|
||||
[controller]
|
||||
);
|
||||
|
||||
const handleContextMenuHide = useCallback(() => {
|
||||
controller.setShowTooltip(true);
|
||||
setMenuProps(undefined);
|
||||
}, [controller]);
|
||||
|
||||
const handleClickCanvas = useCallback(() => {
|
||||
handleContextMenuHide();
|
||||
}, [handleContextMenuHide]);
|
||||
|
||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||
if (controller.isProcessing) {
|
||||
return;
|
||||
|
@ -192,7 +219,7 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
if (event.key === 'Delete') {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
handleDeleteOperation();
|
||||
handleDeleteSelected();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -224,12 +251,23 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
nodesConnectable={false}
|
||||
snapToGrid={true}
|
||||
snapGrid={[10, 10]}
|
||||
onContextMenu={handleContextMenu}
|
||||
onNodeContextMenu={handleContextMenu}
|
||||
onClick={handleClickCanvas}
|
||||
>
|
||||
{showGrid ? <Background gap={10} /> : null}
|
||||
</ReactFlow>
|
||||
),
|
||||
[nodes, edges, proOptions, handleNodesChange, handleContextMenu, onEdgesChange, OssNodeTypes, showGrid]
|
||||
[
|
||||
nodes,
|
||||
edges,
|
||||
proOptions,
|
||||
handleNodesChange,
|
||||
handleContextMenu,
|
||||
handleClickCanvas,
|
||||
onEdgesChange,
|
||||
OssNodeTypes,
|
||||
showGrid
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -240,13 +278,16 @@ function OssFlow({ isModified, setIsModified, showGrid, setShowGrid }: OssFlowPr
|
|||
showGrid={showGrid}
|
||||
onFitView={handleFitView}
|
||||
onCreate={handleCreateOperation}
|
||||
onDelete={handleDeleteOperation}
|
||||
onDelete={handleDeleteSelected}
|
||||
onResetPositions={handleResetPositions}
|
||||
onSavePositions={handleSavePositions}
|
||||
onSaveImage={handleSaveImage}
|
||||
toggleShowGrid={() => setShowGrid(prev => !prev)}
|
||||
/>
|
||||
</Overlay>
|
||||
{menuProps ? (
|
||||
<NodeContextMenu onHide={handleContextMenuHide} onDelete={handleDeleteOperation} {...menuProps} />
|
||||
) : null}
|
||||
<div className='relative' style={{ height: canvasHeight, width: canvasWidth }}>
|
||||
{graph}
|
||||
</div>
|
||||
|
|
|
@ -62,7 +62,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='flex border-b clr-input'>
|
||||
<div className='flex border-b clr-input overflow-hidden'>
|
||||
<SearchBar
|
||||
id='constituents_search'
|
||||
noBorder
|
||||
|
|
|
@ -57,7 +57,7 @@ function ViewConstituents({ expression, schema, activeCst, isBottom, onOpenEdit
|
|||
className={clsx(
|
||||
'border overflow-visible', // prettier: split-lines
|
||||
{
|
||||
'mt-[2.2rem] rounded-l-md rounded-r-none': !isBottom, // prettier: split-lines
|
||||
'mt-[2.2rem] rounded-l-md rounded-r-none': !isBottom,
|
||||
'mt-3 mx-6 rounded-md md:w-[45.8rem]': isBottom
|
||||
}
|
||||
)}
|
||||
|
|
|
@ -14,6 +14,7 @@ export const PARAMETER = {
|
|||
zoomDuration: 500, // milliseconds animation duration
|
||||
ossImageWidth: 1280, // pixels - size of OSS image
|
||||
ossImageHeight: 960, // pixels - size of OSS image
|
||||
ossContextMenuWidth: 200, // pixels - width of OSS context menu
|
||||
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user