2024-07-26 17:31:57 +03:00
|
|
|
|
'use client';
|
|
|
|
|
|
2024-07-29 16:56:24 +03:00
|
|
|
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
2024-07-26 17:31:57 +03:00
|
|
|
|
|
2024-07-28 00:37:50 +03:00
|
|
|
|
import { IconConnect, IconDestroy, IconEdit2, IconExecute, IconNewItem, IconRSForm } from '@/components/Icons';
|
2024-07-26 17:31:57 +03:00
|
|
|
|
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;
|
2024-07-28 00:37:50 +03:00
|
|
|
|
onCreateInput: (target: OperationID) => void;
|
2024-07-28 21:30:10 +03:00
|
|
|
|
onEditSchema: (target: OperationID) => void;
|
2024-07-29 16:56:24 +03:00
|
|
|
|
onEditOperation: (target: OperationID) => void;
|
2024-07-29 22:31:11 +03:00
|
|
|
|
onRunOperation: (target: OperationID) => void;
|
2024-07-26 17:31:57 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 21:30:10 +03:00
|
|
|
|
function NodeContextMenu({
|
|
|
|
|
operation,
|
|
|
|
|
cursorX,
|
|
|
|
|
cursorY,
|
|
|
|
|
onHide,
|
|
|
|
|
onDelete,
|
|
|
|
|
onCreateInput,
|
2024-07-29 16:56:24 +03:00
|
|
|
|
onEditSchema,
|
2024-07-29 22:31:11 +03:00
|
|
|
|
onEditOperation,
|
|
|
|
|
onRunOperation
|
2024-07-28 21:30:10 +03:00
|
|
|
|
}: NodeContextMenuProps) {
|
2024-07-26 17:31:57 +03:00
|
|
|
|
const controller = useOssEdit();
|
|
|
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
|
|
|
const ref = useRef(null);
|
2024-07-29 16:56:24 +03:00
|
|
|
|
const readyForSynthesis = useMemo(() => {
|
|
|
|
|
if (operation.operation_type !== OperationType.SYNTHESIS) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!controller.schema || operation.result) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const argumentIDs = controller.schema.graph.expandInputs([operation.id]);
|
|
|
|
|
if (!argumentIDs || argumentIDs.length < 2) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const argumentOperations = argumentIDs.map(id => controller.schema!.operationByID.get(id)!);
|
|
|
|
|
if (argumentOperations.some(item => item.result === null)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}, [operation, controller.schema]);
|
2024-07-26 17:31:57 +03:00
|
|
|
|
|
|
|
|
|
const handleHide = useCallback(() => {
|
|
|
|
|
setIsOpen(false);
|
|
|
|
|
onHide();
|
|
|
|
|
}, [onHide]);
|
|
|
|
|
|
|
|
|
|
useClickedOutside({ ref, callback: handleHide });
|
|
|
|
|
|
|
|
|
|
useEffect(() => setIsOpen(true), []);
|
|
|
|
|
|
|
|
|
|
const handleOpenSchema = () => {
|
|
|
|
|
controller.openOperationSchema(operation.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleEditSchema = () => {
|
|
|
|
|
handleHide();
|
2024-07-28 21:30:10 +03:00
|
|
|
|
onEditSchema(operation.id);
|
2024-07-26 17:31:57 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleEditOperation = () => {
|
|
|
|
|
handleHide();
|
2024-07-29 16:56:24 +03:00
|
|
|
|
onEditOperation(operation.id);
|
2024-07-26 17:31:57 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDeleteOperation = () => {
|
|
|
|
|
handleHide();
|
|
|
|
|
onDelete(operation.id);
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-28 00:37:50 +03:00
|
|
|
|
const handleCreateSchema = () => {
|
|
|
|
|
handleHide();
|
|
|
|
|
onCreateInput(operation.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleRunSynthesis = () => {
|
|
|
|
|
handleHide();
|
2024-07-29 22:31:11 +03:00
|
|
|
|
onRunOperation(operation.id);
|
2024-07-28 00:37:50 +03:00
|
|
|
|
};
|
|
|
|
|
|
2024-07-26 17:31:57 +03:00
|
|
|
|
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
|
2024-07-28 00:37:50 +03:00
|
|
|
|
text='Редактировать'
|
2024-07-26 17:31:57 +03:00
|
|
|
|
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}
|
2024-07-28 00:37:50 +03:00
|
|
|
|
onClick={handleCreateSchema}
|
2024-07-26 17:31:57 +03:00
|
|
|
|
/>
|
|
|
|
|
) : null}
|
2024-07-28 21:30:10 +03:00
|
|
|
|
{controller.isMutable && operation.operation_type === OperationType.INPUT ? (
|
2024-07-26 17:31:57 +03:00
|
|
|
|
<DropdownButton
|
2024-07-28 21:30:10 +03:00
|
|
|
|
text={!operation.result ? 'Загрузить схему' : 'Изменить схему'}
|
2024-07-26 17:31:57 +03:00
|
|
|
|
title='Выбрать схему для загрузки'
|
2024-07-28 00:37:50 +03:00
|
|
|
|
icon={<IconConnect size='1rem' className='icon-primary' />}
|
2024-07-26 17:31:57 +03:00
|
|
|
|
disabled={controller.isProcessing}
|
|
|
|
|
onClick={handleEditSchema}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
2024-07-28 00:37:50 +03:00
|
|
|
|
{controller.isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? (
|
|
|
|
|
<DropdownButton
|
|
|
|
|
text='Выполнить синтез'
|
2024-07-29 16:56:24 +03:00
|
|
|
|
title={
|
|
|
|
|
readyForSynthesis
|
|
|
|
|
? 'Выполнить операцию и получить синтезированную КС'
|
|
|
|
|
: 'Необходимо предоставить все аргументы'
|
|
|
|
|
}
|
2024-07-28 00:37:50 +03:00
|
|
|
|
icon={<IconExecute size='1rem' className='icon-green' />}
|
2024-07-29 16:56:24 +03:00
|
|
|
|
disabled={controller.isProcessing || !readyForSynthesis}
|
2024-07-28 00:37:50 +03:00
|
|
|
|
onClick={handleRunSynthesis}
|
|
|
|
|
/>
|
|
|
|
|
) : null}
|
2024-07-26 17:31:57 +03:00
|
|
|
|
|
|
|
|
|
<DropdownButton
|
|
|
|
|
text='Удалить операцию'
|
|
|
|
|
icon={<IconDestroy size='1rem' className='icon-red' />}
|
|
|
|
|
disabled={!controller.isMutable || controller.isProcessing}
|
|
|
|
|
onClick={handleDeleteOperation}
|
|
|
|
|
/>
|
|
|
|
|
</Dropdown>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default NodeContextMenu;
|