diff --git a/TODO.txt b/TODO.txt
index 1f9e13a6..1d558539 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -15,6 +15,7 @@ User profile:
- Profile pictures
- Custom LibraryItem lists
- Custom user filters and sharing filters
+- Personal prompt templates
- Static analyzer for RSForm as a whole: check term duplication and empty conventions
- OSS clone and versioning
diff --git a/rsconcept/frontend/src/features/oss/components/info-block.tsx b/rsconcept/frontend/src/features/oss/components/info-block.tsx
new file mode 100644
index 00000000..3dfddc4d
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/components/info-block.tsx
@@ -0,0 +1,26 @@
+'use client';
+
+import { type IBlock } from '../models/oss';
+
+interface InfoOperationProps {
+ block: IBlock;
+}
+
+export function InfoBlock({ block }: InfoOperationProps) {
+ return (
+ <>
+ {block.title ? (
+
+ Название:
+ {block.title}
+
+ ) : null}
+ {block.description ? (
+
+ Описание:
+ {block.description}
+
+ ) : null}
+ >
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/components/operation-tooltip.tsx b/rsconcept/frontend/src/features/oss/components/operation-tooltip.tsx
deleted file mode 100644
index fa19caec..00000000
--- a/rsconcept/frontend/src/features/oss/components/operation-tooltip.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Tooltip } from '@/components/container';
-import { globalIDs } from '@/utils/constants';
-
-import { useOperationTooltipStore } from '../stores/operation-tooltip';
-
-import { InfoOperation } from './info-operation';
-
-export function OperationTooltip() {
- const hoverOperation = useOperationTooltipStore(state => state.activeOperation);
- return (
-
- {hoverOperation ? : null}
-
- );
-}
diff --git a/rsconcept/frontend/src/features/oss/components/tooltip-oss-item.tsx b/rsconcept/frontend/src/features/oss/components/tooltip-oss-item.tsx
new file mode 100644
index 00000000..bed24b3a
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/components/tooltip-oss-item.tsx
@@ -0,0 +1,27 @@
+import { Tooltip } from '@/components/container';
+import { globalIDs } from '@/utils/constants';
+
+import { type IBlock, type IOperation } from '../models/oss';
+import { isOperation } from '../models/oss-api';
+import { useOperationTooltipStore } from '../stores/operation-tooltip';
+
+import { InfoBlock } from './info-block';
+import { InfoOperation } from './info-operation';
+
+export function OperationTooltip() {
+ const hoverItem = useOperationTooltipStore(state => state.hoverItem);
+ const isOperationNode = isOperation(hoverItem);
+
+ return (
+
+ {hoverItem && isOperationNode ? : null}
+ {hoverItem && !isOperationNode ? : null}
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/models/oss-api.ts b/rsconcept/frontend/src/features/oss/models/oss-api.ts
index af8364e0..5387c67a 100644
--- a/rsconcept/frontend/src/features/oss/models/oss-api.ts
+++ b/rsconcept/frontend/src/features/oss/models/oss-api.ts
@@ -37,8 +37,8 @@ const DISTANCE_Y = 100; // pixels - insert y-distance between node centers
const STARTING_SUB_INDEX = 900; // max semantic index for starting substitution
/** Checks if element is {@link IOperation} or {@link IBlock}. */
-export function isOperation(item: IOssItem): boolean {
- return 'arguments' in item;
+export function isOperation(item: IOssItem | null): boolean {
+ return !!item && 'arguments' in item;
}
/** Sorts library items relevant for the specified {@link IOperationSchema}. */
@@ -528,7 +528,7 @@ export function calculateNewOperationPosition(
/** 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))
+ .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))
@@ -544,25 +544,27 @@ export function calculateNewBlockPosition(data: ICreateBlockDTO, layout: IOssLay
let bottom = undefined;
for (const block of block_nodes) {
- left = !left ? block.x - GRID_SIZE : Math.min(left, block.x - GRID_SIZE);
- top = !top ? block.y - GRID_SIZE : Math.min(top, block.y - GRID_SIZE);
+ 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 + GRID_SIZE)
- : Math.max(right, block.x + block.width + GRID_SIZE);
+ ? 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 + GRID_SIZE)
- : Math.max(bottom, block.y + block.height + GRID_SIZE);
+ ? 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 - GRID_SIZE : Math.min(left, operation.x - GRID_SIZE);
- top = !top ? operation.y - GRID_SIZE : Math.min(top, operation.y - GRID_SIZE);
+ 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 + GRID_SIZE)
- : Math.max(right, operation.x + OPERATION_NODE_WIDTH + GRID_SIZE);
+ ? 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 + GRID_SIZE)
- : Math.max(bottom, operation.y + OPERATION_NODE_HEIGHT + GRID_SIZE);
+ ? Math.max(top + data.height, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE)
+ : Math.max(bottom, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE);
}
return {
diff --git a/rsconcept/frontend/src/features/oss/models/oss-layout.ts b/rsconcept/frontend/src/features/oss/models/oss-layout.ts
index 9844e6eb..be1e1d95 100644
--- a/rsconcept/frontend/src/features/oss/models/oss-layout.ts
+++ b/rsconcept/frontend/src/features/oss/models/oss-layout.ts
@@ -22,7 +22,8 @@ export interface OssNode extends Node {
id: string;
data: {
label: string;
- operation: IOperation;
+ operation?: IOperation;
+ block?: IBlock;
};
position: { x: number; y: number };
}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/context-menu.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/context-menu.tsx
new file mode 100644
index 00000000..f03c769f
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/context-menu.tsx
@@ -0,0 +1,62 @@
+'use client';
+
+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 { MenuBlock } from './menu-block';
+import { MenuOperation } from './menu-operation';
+
+// pixels - size of OSS context menu
+const MENU_WIDTH = 200;
+const MENU_HEIGHT = 200;
+
+export interface ContextMenuData {
+ item: IOssItem | null;
+ cursorX: number;
+ cursorY: number;
+}
+
+interface ContextMenuProps extends ContextMenuData {
+ isOpen: boolean;
+ onHide: () => void;
+}
+
+export function ContextMenu({ isOpen, item, cursorX, cursorY, onHide }: ContextMenuProps) {
+ const ref = useRef(null);
+ const isOperationNode = isOperation(item);
+
+ function handleBlur(event: React.FocusEvent) {
+ if (!ref.current?.contains(event.relatedTarget as Node)) {
+ onHide();
+ }
+ }
+
+ return (
+
+ = window.innerWidth - MENU_WIDTH}
+ stretchTop={cursorY >= window.innerHeight - MENU_HEIGHT}
+ margin={cursorY >= window.innerHeight - MENU_HEIGHT ? 'mb-3' : 'mt-3'}
+ >
+ {!!item ? (
+ isOperationNode ? (
+
+ ) : (
+
+ )
+ ) : null}
+
+
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/index.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/index.tsx
new file mode 100644
index 00000000..f628d09c
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/index.tsx
@@ -0,0 +1 @@
+export { ContextMenu } from './context-menu';
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-block.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-block.tsx
new file mode 100644
index 00000000..9185a55c
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-block.tsx
@@ -0,0 +1,61 @@
+'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 { useMutatingOss } from '../../../../backend/use-mutating-oss';
+import { type IBlock } from '../../../../models/oss';
+import { useOssEdit } from '../../oss-edit-context';
+import { useGetLayout } from '../use-get-layout';
+
+interface MenuBlockProps {
+ block: IBlock;
+ onHide: () => void;
+}
+
+export function MenuBlock({ block, onHide }: MenuBlockProps) {
+ const { schema, isMutable } = useOssEdit();
+ const isProcessing = useMutatingOss();
+ const getLayout = useGetLayout();
+
+ const showEditBlock = useDialogsStore(state => state.showEditBlock);
+ const { deleteBlock } = useDeleteBlock();
+
+ function handleEditOperation() {
+ if (!block) {
+ return;
+ }
+ onHide();
+ showEditBlock({
+ oss: schema,
+ target: block,
+ layout: getLayout()
+ });
+ }
+
+ function handleDeleteBlock() {
+ onHide();
+ void deleteBlock({ itemID: schema.id, data: { target: block.id, layout: getLayout() } });
+ }
+
+ return (
+ <>
+ }
+ onClick={handleEditOperation}
+ disabled={!isMutable || isProcessing}
+ />
+ }
+ onClick={handleDeleteBlock}
+ disabled={!isMutable || isProcessing}
+ />
+ >
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx
new file mode 100644
index 00000000..706d186d
--- /dev/null
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/context-menu/menu-operation.tsx
@@ -0,0 +1,224 @@
+'use client';
+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 {
+ IconChild,
+ IconConnect,
+ IconDestroy,
+ IconEdit2,
+ IconExecute,
+ IconNewRSForm,
+ IconRSForm
+} from '@/components/icons';
+import { useDialogsStore } from '@/stores/dialogs';
+import { errorMsg } from '@/utils/labels';
+import { prepareTooltip } from '@/utils/utils';
+
+import { OperationType } from '../../../../backend/types';
+import { useMutatingOss } from '../../../../backend/use-mutating-oss';
+import { type IOperation } from '../../../../models/oss';
+import { useOssEdit } from '../../oss-edit-context';
+import { useGetLayout } from '../use-get-layout';
+
+interface MenuOperationProps {
+ operation: IOperation;
+ onHide: () => void;
+}
+
+export function MenuOperation({ operation, onHide }: MenuOperationProps) {
+ const router = useConceptNavigation();
+ const { items: libraryItems } = useLibrary();
+ const { schema, navigateOperationSchema, isMutable, canDeleteOperation } = useOssEdit();
+ const isProcessing = useMutatingOss();
+ const getLayout = useGetLayout();
+
+ const { createInput: inputCreate } = useCreateInput();
+ const { executeOperation: operationExecute } = useExecuteOperation();
+
+ 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 readyForSynthesis = (() => {
+ if (operation?.operation_type !== OperationType.SYNTHESIS) {
+ return false;
+ }
+ if (operation.result) {
+ return false;
+ }
+
+ const argumentIDs = schema.graph.expandInputs([operation.id]);
+ if (!argumentIDs || argumentIDs.length < 1) {
+ return false;
+ }
+
+ const argumentOperations = argumentIDs.map(id => schema.operationByID.get(id)!);
+ if (argumentOperations.some(item => item.result === null)) {
+ return false;
+ }
+
+ return true;
+ })();
+
+ function handleOpenSchema() {
+ if (!operation) {
+ return;
+ }
+ onHide();
+ navigateOperationSchema(operation.id);
+ }
+
+ function handleEditSchema() {
+ if (!operation) {
+ return;
+ }
+ onHide();
+ showEditInput({
+ oss: schema,
+ target: operation,
+ layout: getLayout()
+ });
+ }
+
+ function handleEditOperation() {
+ if (!operation) {
+ return;
+ }
+ onHide();
+ showEditOperation({
+ oss: schema,
+ target: operation,
+ layout: getLayout()
+ });
+ }
+
+ function handleDeleteOperation() {
+ if (!operation || !canDeleteOperation(operation)) {
+ return;
+ }
+ onHide();
+ showDeleteOperation({
+ oss: schema,
+ target: operation,
+ layout: getLayout()
+ });
+ }
+
+ function handleOperationExecute() {
+ if (!operation) {
+ return;
+ }
+ onHide();
+ void operationExecute({
+ itemID: schema.id, //
+ data: { target: operation.id, layout: getLayout() }
+ });
+ }
+
+ function handleInputCreate() {
+ if (!operation) {
+ return;
+ }
+ if (libraryItems.find(item => item.alias === operation.alias && item.location === schema.location)) {
+ toast.error(errorMsg.inputAlreadyExists);
+ return;
+ }
+ onHide();
+ void inputCreate({
+ itemID: schema.id,
+ data: { target: operation.id, layout: getLayout() }
+ }).then(new_schema => router.push({ path: urls.schema(new_schema.id), force: true }));
+ }
+
+ function handleRelocateConstituents() {
+ if (!operation) {
+ return;
+ }
+ onHide();
+ showRelocateConstituents({
+ oss: schema,
+ initialTarget: operation,
+ layout: getLayout()
+ });
+ }
+
+ return (
+ <>
+ }
+ onClick={handleEditOperation}
+ disabled={!isMutable || isProcessing}
+ />
+
+ {operation?.result ? (
+ }
+ onClick={handleOpenSchema}
+ disabled={isProcessing}
+ />
+ ) : null}
+ {isMutable && !operation?.result && operation?.arguments.length === 0 ? (
+ }
+ onClick={handleInputCreate}
+ disabled={isProcessing}
+ />
+ ) : null}
+ {isMutable && operation?.operation_type === OperationType.INPUT ? (
+ }
+ onClick={handleEditSchema}
+ disabled={isProcessing}
+ />
+ ) : null}
+ {isMutable && !operation?.result && operation?.operation_type === OperationType.SYNTHESIS ? (
+ и получить синтезированную КС'
+ : 'Необходимо предоставить все аргументы'
+ }
+ aria-label='Активировать операцию и получить синтезированную КС'
+ icon={}
+ onClick={handleOperationExecute}
+ disabled={isProcessing || !readyForSynthesis}
+ />
+ ) : null}
+
+ {isMutable && operation?.result ? (
+ }
+ onClick={handleRelocateConstituents}
+ disabled={isProcessing}
+ />
+ ) : null}
+
+ }
+ onClick={handleDeleteOperation}
+ disabled={!isMutable || isProcessing || !operation || !canDeleteOperation(operation)}
+ />
+ >
+ );
+}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx
index 5ed22755..3ddf347a 100644
--- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/block-node.tsx
@@ -3,9 +3,11 @@
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 { useOssEdit } from '../../oss-edit-context';
@@ -14,8 +16,10 @@ export const BLOCK_NODE_MIN_WIDTH = 160;
export const BLOCK_NODE_MIN_HEIGHT = 100;
export function BlockNode(node: BlockInternalNode) {
- const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
const { selected, schema } = useOssEdit();
+ const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
+ const setHover = useOperationTooltipStore(state => state.setHoverItem);
+
const focus = selected.length === 1 ? selected[0] : null;
const isParent = (!!focus && schema.hierarchy.at(focus)?.inputs.includes(-node.data.block.id)) ?? false;
const isChild = (!!focus && schema.hierarchy.at(focus)?.outputs.includes(-node.data.block.id)) ?? false;
@@ -50,6 +54,9 @@ export function BlockNode(node: BlockInternalNode) {
'text-[18px]/[20px] line-clamp-2 text-center text-ellipsis',
'pointer-events-auto'
)}
+ data-tooltip-id={globalIDs.operation_tooltip}
+ data-tooltip-hidden={node.dragging}
+ onMouseEnter={() => setHover(node.data.block)}
>
{node.data.label}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx
index 47ff412e..699e2afb 100644
--- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/graph/node-core.tsx
@@ -29,7 +29,7 @@ export function NodeCore({ node }: NodeCoreProps) {
const focus = selected.length === 1 ? selected[0] : null;
const isChild = (!!focus && schema.hierarchy.at(focus)?.outputs.includes(node.data.operation.id)) ?? false;
- const setHover = useOperationTooltipStore(state => state.setActiveOperation);
+ const setHover = useOperationTooltipStore(state => state.setHoverItem);
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
const hasFile = !!node.data.operation.result;
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx
deleted file mode 100644
index 76b15c87..00000000
--- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/node-context-menu.tsx
+++ /dev/null
@@ -1,257 +0,0 @@
-'use client';
-
-import { useRef } from 'react';
-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 { Dropdown, DropdownButton } from '@/components/dropdown';
-import {
- IconChild,
- IconConnect,
- IconDestroy,
- IconEdit2,
- IconExecute,
- IconNewRSForm,
- IconRSForm
-} from '@/components/icons';
-import { useDialogsStore } from '@/stores/dialogs';
-import { errorMsg } from '@/utils/labels';
-import { prepareTooltip } from '@/utils/utils';
-
-import { OperationType } from '../../../backend/types';
-import { useMutatingOss } from '../../../backend/use-mutating-oss';
-import { type IOperation } from '../../../models/oss';
-import { useOssEdit } from '../oss-edit-context';
-
-import { useGetLayout } from './use-get-layout';
-
-// pixels - size of OSS context menu
-const MENU_WIDTH = 200;
-const MENU_HEIGHT = 200;
-
-export interface ContextMenuData {
- operation: IOperation | null;
- cursorX: number;
- cursorY: number;
-}
-
-interface NodeContextMenuProps extends ContextMenuData {
- isOpen: boolean;
- onHide: () => void;
-}
-
-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 getLayout = useGetLayout();
-
- const { createInput: inputCreate } = useCreateInput();
- const { executeOperation: operationExecute } = useExecuteOperation();
-
- 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 readyForSynthesis = (() => {
- if (operation?.operation_type !== OperationType.SYNTHESIS) {
- return false;
- }
- if (operation.result) {
- return false;
- }
-
- const argumentIDs = schema.graph.expandInputs([operation.id]);
- if (!argumentIDs || argumentIDs.length < 1) {
- return false;
- }
-
- const argumentOperations = argumentIDs.map(id => schema.operationByID.get(id)!);
- if (argumentOperations.some(item => item.result === null)) {
- return false;
- }
-
- return true;
- })();
-
- const ref = useRef(null);
-
- function handleBlur(event: React.FocusEvent) {
- if (!ref.current?.contains(event.relatedTarget as Node)) {
- onHide();
- }
- }
-
- function handleOpenSchema() {
- if (!operation) {
- return;
- }
- onHide();
- navigateOperationSchema(operation.id);
- }
-
- function handleEditSchema() {
- if (!operation) {
- return;
- }
- onHide();
- showEditInput({
- oss: schema,
- target: operation,
- layout: getLayout()
- });
- }
-
- function handleEditOperation() {
- if (!operation) {
- return;
- }
- onHide();
- showEditOperation({
- oss: schema,
- target: operation,
- layout: getLayout()
- });
- }
-
- function handleDeleteOperation() {
- if (!operation || !canDelete(operation)) {
- return;
- }
- onHide();
- showDeleteOperation({
- oss: schema,
- target: operation,
- layout: getLayout()
- });
- }
-
- function handleOperationExecute() {
- if (!operation) {
- return;
- }
- onHide();
- void operationExecute({
- itemID: schema.id, //
- data: { target: operation.id, layout: getLayout() }
- });
- }
-
- function handleInputCreate() {
- if (!operation) {
- return;
- }
- if (libraryItems.find(item => item.alias === operation.alias && item.location === schema.location)) {
- toast.error(errorMsg.inputAlreadyExists);
- return;
- }
- onHide();
- void inputCreate({
- itemID: schema.id,
- data: { target: operation.id, layout: getLayout() }
- }).then(new_schema => router.push({ path: urls.schema(new_schema.id), force: true }));
- }
-
- function handleRelocateConstituents() {
- if (!operation) {
- return;
- }
- onHide();
- showRelocateConstituents({
- oss: schema,
- initialTarget: operation,
- layout: getLayout()
- });
- }
-
- return (
-
- = window.innerWidth - MENU_WIDTH}
- stretchTop={cursorY >= window.innerHeight - MENU_HEIGHT}
- margin={cursorY >= window.innerHeight - MENU_HEIGHT ? 'mb-3' : 'mt-3'}
- >
- }
- onClick={handleEditOperation}
- disabled={!isMutable || isProcessing}
- />
-
- {operation?.result ? (
- }
- onClick={handleOpenSchema}
- disabled={isProcessing}
- />
- ) : null}
- {isMutable && !operation?.result && operation?.arguments.length === 0 ? (
- }
- onClick={handleInputCreate}
- disabled={isProcessing}
- />
- ) : null}
- {isMutable && operation?.operation_type === OperationType.INPUT ? (
- }
- onClick={handleEditSchema}
- disabled={isProcessing}
- />
- ) : null}
- {isMutable && !operation?.result && operation?.operation_type === OperationType.SYNTHESIS ? (
- и получить синтезированную КС'
- : 'Необходимо предоставить все аргументы'
- }
- aria-label='Активировать операцию и получить синтезированную КС'
- icon={}
- onClick={handleOperationExecute}
- disabled={isProcessing || !readyForSynthesis}
- />
- ) : null}
-
- {isMutable && operation?.result ? (
- }
- onClick={handleRelocateConstituents}
- disabled={isProcessing}
- />
- ) : null}
-
- }
- onClick={handleDeleteOperation}
- disabled={!isMutable || isProcessing || !operation || !canDelete(operation)}
- />
-
-
- );
-}
diff --git a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx
index 84c043c0..29953dd3 100644
--- a/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx
+++ b/rsconcept/frontend/src/features/oss/pages/oss-page/editor-oss-graph/oss-flow.tsx
@@ -28,8 +28,8 @@ import { useOperationTooltipStore } from '../../../stores/operation-tooltip';
import { useOSSGraphStore } from '../../../stores/oss-graph';
import { useOssEdit } from '../oss-edit-context';
+import { ContextMenu, type ContextMenuData } from './context-menu/context-menu';
import { OssNodeTypes } from './graph/oss-node-types';
-import { type ContextMenuData, NodeContextMenu } from './node-context-menu';
import { ToolbarOssGraph } from './toolbar-oss-graph';
import { useGetLayout } from './use-get-layout';
@@ -53,11 +53,11 @@ export function OssFlow() {
} = useOssEdit();
const { fitView, screenToFlowPosition } = useReactFlow();
const store = useStoreApi();
- const { resetSelectedElements } = store.getState();
+ const { resetSelectedElements, addSelectedNodes } = store.getState();
const isProcessing = useMutatingOss();
- const setHoverOperation = useOperationTooltipStore(state => state.setActiveOperation);
+ const setHoverOperation = useOperationTooltipStore(state => state.setHoverItem);
const showGrid = useOSSGraphStore(state => state.showGrid);
const showCoordinates = useOSSGraphStore(state => state.showCoordinates);
@@ -71,7 +71,7 @@ export function OssFlow() {
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [toggleReset, setToggleReset] = useState(false);
- const [menuProps, setMenuProps] = useState({ operation: null, cursorX: 0, cursorY: 0 });
+ const [menuProps, setMenuProps] = useState({ item: null, cursorX: 0, cursorY: 0 });
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
const [mouseCoords, setMouseCoords] = useState({ x: 0, y: 0 });
@@ -202,12 +202,10 @@ export function OssFlow() {
event.preventDefault();
event.stopPropagation();
- if (node.type === 'block') {
- return;
- }
+ addSelectedNodes([node.id]);
setMenuProps({
- operation: node.data.operation,
+ item: node.type === 'block' ? node.data.block ?? null : node.data.operation ?? null,
cursorX: event.clientX,
cursorY: event.clientY
});
@@ -216,9 +214,12 @@ export function OssFlow() {
}
function handleNodeDoubleClick(event: React.MouseEvent, node: OssNode) {
+ if (node.type === 'block') {
+ return;
+ }
event.preventDefault();
event.stopPropagation();
- if (node.data.operation.result) {
+ if (node.data.operation?.result) {
navigateOperationSchema(Number(node.id));
}
}
@@ -285,7 +286,7 @@ export function OssFlow() {
onResetPositions={() => setToggleReset(prev => !prev)}
/>
- setIsContextMenuOpen(false)} {...menuProps} />
+ setIsContextMenuOpen(false)} {...menuProps} />
void;
+ hoverItem: IOssItem | null;
+ setHoverItem: (value: IOssItem | null) => void;
}
export const useOperationTooltipStore = create()(set => ({
- activeOperation: null,
- setActiveOperation: value => set({ activeOperation: value })
+ hoverItem: null,
+ setHoverItem: value => set({ hoverItem: value })
}));