F: Improve OSS graph interactions

This commit is contained in:
Ivan 2025-02-26 12:54:51 +03:00
parent ba11c1f82b
commit cab9ae8efc
7 changed files with 213 additions and 261 deletions

View File

@ -1,6 +1,12 @@
'use client';
import { useRef } from 'react';
import { toast } from 'react-toastify';
import { urls, useConceptNavigation } from '@/app';
import { useLibrary } from '@/features/library/backend/useLibrary';
import { useInputCreate } from '@/features/oss/backend/useInputCreate';
import { useOperationExecute } from '@/features/oss/backend/useOperationExecute';
import { Dropdown, DropdownButton } from '@/components/Dropdown';
import {
@ -13,6 +19,8 @@ import {
IconRSForm
} from '@/components/Icons';
import { useClickedOutside } from '@/hooks/useClickedOutside';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { prepareTooltip } from '@/utils/utils';
import { OperationType } from '../../../backend/types';
@ -20,12 +28,14 @@ import { useMutatingOss } from '../../../backend/useMutatingOss';
import { type IOperation } from '../../../models/oss';
import { useOssEdit } from '../OssEditContext';
import { useGetPositions } from './useGetPositions';
// pixels - size of OSS context menu
const MENU_WIDTH = 200;
const MENU_HEIGHT = 200;
export interface ContextMenuData {
operation: IOperation;
operation: IOperation | null;
cursorX: number;
cursorY: number;
}
@ -33,33 +43,25 @@ export interface ContextMenuData {
interface NodeContextMenuProps extends ContextMenuData {
isOpen: boolean;
onHide: () => void;
onDelete: (target: number) => void;
onCreateInput: (target: number) => void;
onEditSchema: (target: number) => void;
onEditOperation: (target: number) => void;
onExecuteOperation: (target: number) => void;
onRelocateConstituents: (target: number) => void;
}
export function NodeContextMenu({
isOpen,
operation,
cursorX,
cursorY,
onHide,
onDelete,
onCreateInput,
onEditSchema,
onEditOperation,
onExecuteOperation,
onRelocateConstituents
}: NodeContextMenuProps) {
export function NodeContextMenu({ isOpen, operation, cursorX, cursorY, onHide }: NodeContextMenuProps) {
const router = useConceptNavigation();
const { items: libraryItems } = useLibrary();
const { schema, navigateOperationSchema, isMutable, canDeleteOperation: canDelete } = useOssEdit();
const isProcessing = useMutatingOss();
const { schema, navigateOperationSchema, isMutable, canDelete } = useOssEdit();
const getPositions = useGetPositions();
const { inputCreate } = useInputCreate();
const { operationExecute } = useOperationExecute();
const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents);
const showEditOperation = useDialogsStore(state => state.showEditOperation);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
const ref = useRef<HTMLDivElement>(null);
const readyForSynthesis = (() => {
if (operation.operation_type !== OperationType.SYNTHESIS) {
if (operation?.operation_type !== OperationType.SYNTHESIS) {
return false;
}
if (operation.result) {
@ -79,40 +81,89 @@ export function NodeContextMenu({
return true;
})();
const ref = useRef<HTMLDivElement>(null);
useClickedOutside(isOpen, ref, onHide);
function handleOpenSchema() {
if (!operation) {
return;
}
onHide();
navigateOperationSchema(operation.id);
}
function handleEditSchema() {
if (!operation) {
return;
}
onHide();
onEditSchema(operation.id);
showEditInput({
oss: schema,
target: operation,
positions: getPositions()
});
}
function handleEditOperation() {
if (!operation) {
return;
}
onHide();
onEditOperation(operation.id);
showEditOperation({
oss: schema,
target: operation,
positions: getPositions()
});
}
function handleDeleteOperation() {
if (!operation || !canDelete(operation)) {
return;
}
onHide();
onDelete(operation.id);
showDeleteOperation({
oss: schema,
target: operation,
positions: getPositions()
});
}
function handleCreateSchema() {
function handleOperationExecute() {
if (!operation) {
return;
}
onHide();
onCreateInput(operation.id);
void operationExecute({
itemID: schema.id, //
data: { target: operation.id, positions: getPositions() }
});
}
function handleRunSynthesis() {
function handleInputCreate() {
if (!operation) {
return;
}
if (libraryItems.find(item => item.alias === operation.alias && item.location === schema.location)) {
toast.error(errorMsg.inputAlreadyExists);
return;
}
onHide();
onExecuteOperation(operation.id);
void inputCreate({
itemID: schema.id,
data: { target: operation.id, positions: getPositions() }
}).then(new_schema => router.push(urls.schema(new_schema.id)));
}
function handleRelocateConstituents() {
if (!operation) {
return;
}
onHide();
onRelocateConstituents(operation.id);
showRelocateConstituents({
oss: schema,
initialTarget: operation,
positions: getPositions()
});
}
return (
@ -145,7 +196,7 @@ export function NodeContextMenu({
title='Создать пустую схему для загрузки'
icon={<IconNewRSForm size='1rem' className='icon-green' />}
disabled={isProcessing}
onClick={handleCreateSchema}
onClick={handleInputCreate}
/>
) : null}
{isMutable && operation?.operation_type === OperationType.INPUT ? (
@ -167,7 +218,7 @@ export function NodeContextMenu({
}
icon={<IconExecute size='1rem' className='icon-green' />}
disabled={isProcessing || !readyForSynthesis}
onClick={handleRunSynthesis}
onClick={handleOperationExecute}
/>
) : null}
@ -184,7 +235,7 @@ export function NodeContextMenu({
<DropdownButton
text='Удалить операцию'
icon={<IconDestroy size='1rem' className='icon-red' />}
disabled={!isMutable || isProcessing || !operation || !canDelete(operation.id)}
disabled={!isMutable || isProcessing || !operation || !canDelete(operation)}
onClick={handleDeleteOperation}
/>
</Dropdown>

View File

@ -1,7 +1,6 @@
'use client';
import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import {
Background,
type Node,
@ -12,18 +11,13 @@ import {
useReactFlow
} from 'reactflow';
import { urls, useConceptNavigation } from '@/app';
import { useLibrary } from '@/features/library/backend/useLibrary';
import { Overlay } from '@/components/Container';
import { useMainHeight } from '@/stores/appLayout';
import { useDialogsStore } from '@/stores/dialogs';
import { useTooltipsStore } from '@/stores/tooltips';
import { PARAMETER } from '@/utils/constants';
import { errorMsg } from '@/utils/labels';
import { useInputCreate } from '../../../backend/useInputCreate';
import { useMutatingOss } from '../../../backend/useMutatingOss';
import { useOperationExecute } from '../../../backend/useOperationExecute';
import { useUpdatePositions } from '../../../backend/useUpdatePositions';
import { GRID_SIZE } from '../../../models/ossAPI';
import { type OssNode } from '../../../models/ossLayout';
@ -33,9 +27,11 @@ import { useOssEdit } from '../OssEditContext';
import { OssNodeTypes } from './graph/OssNodeTypes';
import { type ContextMenuData, NodeContextMenu } from './NodeContextMenu';
import { ToolbarOssGraph } from './ToolbarOssGraph';
import { useGetPositions } from './useGetPositions';
const ZOOM_MAX = 2;
const ZOOM_MIN = 0.5;
export const VIEW_PADDING = 0.2;
export function OssFlow() {
const mainHeight = useMainHeight();
@ -45,16 +41,9 @@ export function OssFlow() {
setSelected,
selected,
isMutable,
promptCreateOperation,
canDelete,
promptDeleteOperation,
promptEditInput,
promptEditOperation,
promptRelocateConstituents
canDeleteOperation: canDelete
} = useOssEdit();
const router = useConceptNavigation();
const { items: libraryItems } = useLibrary();
const flow = useReactFlow();
const { fitView, project } = useReactFlow();
const isProcessing = useMutatingOss();
@ -64,16 +53,18 @@ export function OssFlow() {
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
const edgeStraight = useOSSGraphStore(state => state.edgeStraight);
const { inputCreate } = useInputCreate();
const { operationExecute } = useOperationExecute();
const getPositions = useGetPositions();
const { updatePositions } = useUpdatePositions();
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [toggleReset, setToggleReset] = useState(false);
const [menuProps, setMenuProps] = useState<ContextMenuData | null>(null);
const [menuProps, setMenuProps] = useState<ContextMenuData>({ operation: null, cursorX: 0, cursorY: 0 });
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
function onSelectionChange({ nodes }: { nodes: Node[] }) {
const ids = nodes.map(node => Number(node.id));
setSelected(prev => [
@ -109,15 +100,8 @@ export function OssFlow() {
: 'left'
}))
);
}, [schema, setNodes, setEdges, toggleReset, edgeStraight, edgeAnimate]);
function getPositions() {
return nodes.map(node => ({
id: Number(node.id),
position_x: node.position.x,
position_y: node.position.y
}));
}
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
}, [schema, setNodes, setEdges, toggleReset, edgeStraight, edgeAnimate, fitView]);
function handleSavePositions() {
const positions = getPositions();
@ -132,73 +116,34 @@ export function OssFlow() {
});
}
function handleCreateOperation(inputs: number[]) {
const positions = getPositions();
const target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
promptCreateOperation({
defaultX: target.x,
defaultY: target.y,
inputs: inputs,
positions: positions,
callback: () => setTimeout(() => flow.fitView({ duration: PARAMETER.zoomDuration }), PARAMETER.refreshTimeout)
function handleCreateOperation() {
const targetPosition = project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateOperation({
oss: schema,
defaultX: targetPosition.x,
defaultY: targetPosition.y,
positions: getPositions(),
initialInputs: selected,
onCreate: () =>
setTimeout(() => fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING }), PARAMETER.refreshTimeout)
});
}
function handleDeleteOperation(target: number) {
if (!canDelete(target)) {
return;
}
promptDeleteOperation(target, getPositions());
}
function handleDeleteSelected() {
if (selected.length !== 1) {
return;
}
handleDeleteOperation(selected[0]);
}
function handleInputCreate(target: number) {
const operation = schema.operationByID.get(target);
if (!operation) {
const operation = schema.operationByID.get(selected[0]);
if (!operation || !canDelete(operation)) {
return;
}
if (libraryItems.find(item => item.alias === operation.alias && item.location === schema.location)) {
toast.error(errorMsg.inputAlreadyExists);
return;
}
void inputCreate({
itemID: schema.id,
data: { target: target, positions: getPositions() }
}).then(new_schema => router.push(urls.schema(new_schema.id)));
}
function handleEditSchema(target: number) {
promptEditInput(target, getPositions());
}
function handleEditOperation(target: number) {
promptEditOperation(target, getPositions());
}
function handleOperationExecute(target: number) {
void operationExecute({
itemID: schema.id, //
data: { target: target, positions: getPositions() }
showDeleteOperation({
oss: schema,
target: operation,
positions: getPositions()
});
}
function handleExecuteSelected() {
if (selected.length !== 1) {
return;
}
handleOperationExecute(selected[0]);
}
function handleRelocateConstituents(target: number) {
promptRelocateConstituents(target, getPositions());
}
function handleContextMenu(event: React.MouseEvent<Element>, node: OssNode) {
event.preventDefault();
event.stopPropagation();
@ -212,14 +157,6 @@ export function OssFlow() {
setHoverOperation(null);
}
function handleContextMenuHide() {
setIsContextMenuOpen(false);
}
function handleCanvasClick() {
handleContextMenuHide();
}
function handleNodeDoubleClick(event: React.MouseEvent<Element>, node: OssNode) {
event.preventDefault();
event.stopPropagation();
@ -229,10 +166,7 @@ export function OssFlow() {
}
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
if (isProcessing) {
return;
}
if (!isMutable) {
if (isProcessing || !isMutable) {
return;
}
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyS') {
@ -244,7 +178,7 @@ export function OssFlow() {
if ((event.ctrlKey || event.metaKey) && event.code === 'KeyQ') {
event.preventDefault();
event.stopPropagation();
handleCreateOperation(selected);
handleCreateOperation();
return;
}
if (event.key === 'Delete') {
@ -262,35 +196,20 @@ export function OssFlow() {
className='rounded-b-2xl cc-blur hover:bg-prim-100 hover:bg-opacity-50'
>
<ToolbarOssGraph
onFitView={() => flow.fitView({ duration: PARAMETER.zoomDuration })}
onCreate={() => handleCreateOperation(selected)}
onCreate={handleCreateOperation}
onDelete={handleDeleteSelected}
onEdit={() => handleEditOperation(selected[0])}
onExecute={handleExecuteSelected}
onResetPositions={() => setToggleReset(prev => !prev)}
onSavePositions={handleSavePositions}
/>
</Overlay>
{menuProps ? (
<NodeContextMenu
isOpen={isContextMenuOpen}
onHide={handleContextMenuHide}
onDelete={handleDeleteOperation}
onCreateInput={handleInputCreate}
onEditSchema={handleEditSchema}
onEditOperation={handleEditOperation}
onExecuteOperation={handleOperationExecute}
onRelocateConstituents={handleRelocateConstituents}
{...menuProps}
/>
) : null}
<NodeContextMenu isOpen={isContextMenuOpen} onHide={() => setIsContextMenuOpen(false)} {...menuProps} />
<div className='cc-fade-in relative w-[100vw]' style={{ height: mainHeight, fontFamily: 'Rubik' }}>
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onNodeDoubleClick={handleNodeDoubleClick}
edgesFocusable={false}
nodesFocusable={false}
fitView
@ -300,8 +219,10 @@ export function OssFlow() {
nodesConnectable={false}
snapToGrid={true}
snapGrid={[GRID_SIZE, GRID_SIZE]}
onClick={() => setIsContextMenuOpen(false)}
onNodeDoubleClick={handleNodeDoubleClick}
onNodeContextMenu={handleContextMenu}
onClick={handleCanvasClick}
onNodeDragStart={() => setIsContextMenuOpen(false)}
>
{showGrid ? <Background gap={GRID_SIZE} /> : null}
</ReactFlow>

View File

@ -1,9 +1,12 @@
'use client';
import { useReactFlow } from 'reactflow';
import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
import { BadgeHelp } from '@/features/help/components';
import { useOperationExecute } from '@/features/oss/backend/useOperationExecute';
import { useUpdatePositions } from '@/features/oss/backend/useUpdatePositions';
import { MiniButton } from '@/components/Control';
import {
@ -20,6 +23,7 @@ import {
IconReset,
IconSave
} from '@/components/Icons';
import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/utils';
@ -28,28 +32,21 @@ import { useMutatingOss } from '../../../backend/useMutatingOss';
import { useOSSGraphStore } from '../../../stores/ossGraph';
import { useOssEdit } from '../OssEditContext';
import { VIEW_PADDING } from './OssFlow';
import { useGetPositions } from './useGetPositions';
interface ToolbarOssGraphProps {
onCreate: () => void;
onDelete: () => void;
onEdit: () => void;
onExecute: () => void;
onFitView: () => void;
onSavePositions: () => void;
onResetPositions: () => void;
}
export function ToolbarOssGraph({
onCreate,
onDelete,
onEdit,
onExecute,
onFitView,
onSavePositions,
onResetPositions
}: ToolbarOssGraphProps) {
const { schema, selected, isMutable, canDelete } = useOssEdit();
export function ToolbarOssGraph({ onCreate, onDelete, onResetPositions }: ToolbarOssGraphProps) {
const { schema, selected, isMutable, canDeleteOperation: canDelete } = useOssEdit();
const isProcessing = useMutatingOss();
const { fitView } = useReactFlow();
const selectedOperation = schema.operationByID.get(selected[0]);
const getPositions = useGetPositions();
const showGrid = useOSSGraphStore(state => state.showGrid);
const edgeAnimate = useOSSGraphStore(state => state.edgeAnimate);
@ -58,6 +55,11 @@ export function ToolbarOssGraph({
const toggleEdgeAnimate = useOSSGraphStore(state => state.toggleEdgeAnimate);
const toggleEdgeStraight = useOSSGraphStore(state => state.toggleEdgeStraight);
const { updatePositions } = useUpdatePositions();
const { operationExecute } = useOperationExecute();
const showEditOperation = useDialogsStore(state => state.showEditOperation);
const readyForSynthesis = (() => {
if (!selectedOperation || selectedOperation.operation_type !== OperationType.SYNTHESIS) {
return false;
@ -79,6 +81,44 @@ export function ToolbarOssGraph({
return true;
})();
function handleFitView() {
fitView({ duration: PARAMETER.zoomDuration, padding: VIEW_PADDING });
}
function handleSavePositions() {
const positions = getPositions();
void updatePositions({ itemID: schema.id, positions: positions }).then(() => {
positions.forEach(item => {
const operation = schema.operationByID.get(item.id);
if (operation) {
operation.position_x = item.position_x;
operation.position_y = item.position_y;
}
});
});
}
function handleOperationExecute() {
if (selected.length !== 1 || !readyForSynthesis || !selectedOperation) {
return;
}
void operationExecute({
itemID: schema.id, //
data: { target: selectedOperation.id, positions: getPositions() }
});
}
function handleEditOperation() {
if (selected.length !== 1 || !selectedOperation) {
return;
}
showEditOperation({
oss: schema,
target: selectedOperation,
positions: getPositions()
});
}
return (
<div className='flex flex-col items-center'>
<div className='cc-icons'>
@ -90,7 +130,7 @@ export function ToolbarOssGraph({
<MiniButton
icon={<IconFitImage size='1.25rem' className='icon-primary' />}
title='Сбросить вид'
onClick={onFitView}
onClick={handleFitView}
/>
<MiniButton
title={showGrid ? 'Скрыть сетку' : 'Отобразить сетку'}
@ -137,7 +177,7 @@ export function ToolbarOssGraph({
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
icon={<IconSave size='1.25rem' className='icon-primary' />}
disabled={isProcessing}
onClick={onSavePositions}
onClick={handleSavePositions}
/>
<MiniButton
titleHtml={prepareTooltip('Новая операция', 'Ctrl + Q')}
@ -149,18 +189,18 @@ export function ToolbarOssGraph({
title='Активировать операцию'
icon={<IconExecute size='1.25rem' className='icon-green' />}
disabled={isProcessing || selected.length !== 1 || !readyForSynthesis}
onClick={onExecute}
onClick={handleOperationExecute}
/>
<MiniButton
titleHtml={prepareTooltip('Редактировать выбранную', 'Двойной клик')}
icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
disabled={selected.length !== 1 || isProcessing}
onClick={onEdit}
onClick={handleEditOperation}
/>
<MiniButton
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={selected.length !== 1 || isProcessing || !canDelete(selected[0])}
disabled={selected.length !== 1 || isProcessing || !selectedOperation || !canDelete(selectedOperation)}
onClick={onDelete}
/>
</div>

View File

@ -54,6 +54,7 @@ export function NodeCore({ node }: NodeCoreProps) {
<div
className='h-[34px] w-[144px] flex items-center justify-center'
data-tooltip-id={globalIDs.operation_tooltip}
data-tooltip-hidden={node.dragging}
onMouseEnter={() => setHover(node.data.operation)}
>
<div

View File

@ -0,0 +1,12 @@
import { useReactFlow } from 'reactflow';
export function useGetPositions() {
const { getNodes } = useReactFlow();
return function getPositions() {
return getNodes().map(node => ({
id: Number(node.id),
position_x: node.position.x,
position_y: node.position.y
}));
};
}

View File

@ -3,6 +3,7 @@ import { useAuthSuspense } from '@/features/auth';
import { Button } from '@/components/Control';
import { Dropdown, DropdownButton, useDropdown } from '@/components/Dropdown';
import { IconChild, IconEdit2 } from '@/components/Icons';
import { useDialogsStore } from '@/stores/dialogs';
import { useMutatingOss } from '../../backend/useMutatingOss';
@ -11,12 +12,18 @@ import { useOssEdit } from './OssEditContext';
export function MenuEditOss() {
const { isAnonymous } = useAuthSuspense();
const editMenu = useDropdown();
const { promptRelocateConstituents, isMutable } = useOssEdit();
const { schema, isMutable } = useOssEdit();
const isProcessing = useMutatingOss();
const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents);
function handleRelocate() {
editMenu.hide();
promptRelocateConstituents(undefined, []);
showRelocateConstituents({
oss: schema,
initialTarget: undefined,
positions: []
});
}
if (isAnonymous) {

View File

@ -9,13 +9,12 @@ import { useDeleteItem } from '@/features/library/backend/useDeleteItem';
import { RSTabID } from '@/features/rsform/pages/RSFormPage/RSEditContext';
import { useRoleStore, UserRole } from '@/features/users';
import { useDialogsStore } from '@/stores/dialogs';
import { usePreferencesStore } from '@/stores/preferences';
import { promptText } from '@/utils/labels';
import { type IOperationPosition, OperationType } from '../../backend/types';
import { useOssSuspense } from '../../backend/useOSS';
import { type IOperationSchema } from '../../models/oss';
import { type IOperation, type IOperationSchema } from '../../models/oss';
export enum OssTabID {
CARD = 0,
@ -40,15 +39,9 @@ export interface IOssEditContext {
navigateTab: (tab: OssTabID) => void;
navigateOperationSchema: (target: number) => void;
canDeleteOperation: (target: IOperation) => boolean;
deleteSchema: () => void;
setSelected: React.Dispatch<React.SetStateAction<number[]>>;
canDelete: (target: number) => boolean;
promptCreateOperation: (props: ICreateOperationPrompt) => void;
promptDeleteOperation: (target: number, positions: IOperationPosition[]) => void;
promptEditInput: (target: number, positions: IOperationPosition[]) => void;
promptEditOperation: (target: number, positions: IOperationPosition[]) => void;
promptRelocateConstituents: (target: number | undefined, positions: IOperationPosition[]) => void;
}
const OssEditContext = createContext<IOssEditContext | null>(null);
@ -81,12 +74,6 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
const [selected, setSelected] = useState<number[]>([]);
const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
const showEditOperation = useDialogsStore(state => state.showEditOperation);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents);
const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
const { deleteItem } = useDeleteItem();
useEffect(
@ -128,71 +115,11 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
});
}
function promptCreateOperation({ defaultX, defaultY, inputs, positions, callback }: ICreateOperationPrompt) {
showCreateOperation({
oss: schema,
defaultX: defaultX,
defaultY: defaultY,
positions: positions,
initialInputs: inputs,
onCreate: callback
});
}
function canDelete(target: number) {
const operation = schema.operationByID.get(target);
if (!operation) {
return false;
}
if (operation.operation_type === OperationType.INPUT) {
function canDeleteOperation(target: IOperation) {
if (target.operation_type === OperationType.INPUT) {
return true;
}
return schema.graph.expandOutputs([target]).length === 0;
}
function promptEditOperation(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target);
if (!operation) {
return;
}
showEditOperation({
oss: schema,
target: operation,
positions: positions
});
}
function promptDeleteOperation(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target);
if (!operation) {
return;
}
showDeleteOperation({
oss: schema,
positions: positions,
target: operation
});
}
function promptEditInput(target: number, positions: IOperationPosition[]) {
const operation = schema.operationByID.get(target);
if (!operation) {
return;
}
showEditInput({
oss: schema,
target: operation,
positions: positions
});
}
function promptRelocateConstituents(target: number | undefined, positions: IOperationPosition[]) {
const operation = target ? schema.operationByID.get(target) : undefined;
showRelocateConstituents({
oss: schema,
initialTarget: operation,
positions: positions
});
return schema.graph.expandOutputs([target.id]).length === 0;
}
return (
@ -201,22 +128,15 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
schema,
selected,
navigateTab,
deleteSchema,
isOwned,
isMutable,
setSelected,
navigateTab,
navigateOperationSchema,
promptCreateOperation,
canDelete,
promptDeleteOperation,
promptEditInput,
promptEditOperation,
promptRelocateConstituents
canDeleteOperation,
deleteSchema,
setSelected
}}
>
{children}