@@ -405,10 +269,7 @@ export function TGFlow() {
edgeTypes={TGEdgeTypes}
maxZoom={ZOOM_MAX}
minZoom={ZOOM_MIN}
- onNodeDragStart={() => setIsDragging(true)}
- onNodeDragStop={() => setIsDragging(false)}
- onNodeMouseEnter={(event, node) => handleNodeEnter(event, Number(node.id))}
- onNodeMouseLeave={() => setHoverID(null)}
+ onNodeMouseEnter={(_, node) => handleNodeEnter(Number(node.id))}
onNodeDoubleClick={(event, node) => handleNodeDoubleClick(event, Number(node.id))}
onNodeContextMenu={(event, node) => handleNodeContextMenu(event, Number(node.id))}
/>
diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarFocusedCst.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarFocusedCst.tsx
index 3be982f2..0cac5ab5 100644
--- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarFocusedCst.tsx
+++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarFocusedCst.tsx
@@ -1,5 +1,7 @@
'use client';
+import { useTermGraphStore } from '@/features/rsform/stores/termGraph';
+
import { MiniButton } from '@/components/Control';
import { IconGraphInputs, IconGraphOutputs, IconReset } from '@/components/Icons';
import { APP_COLORS } from '@/styling/colors';
@@ -8,35 +10,40 @@ import { type IConstituenta } from '../../../models/rsform';
import { useRSEdit } from '../RSEditContext';
interface ToolbarFocusedCstProps {
- center: IConstituenta;
- showInputs: boolean;
- showOutputs: boolean;
-
- reset: () => void;
- toggleShowInputs: () => void;
- toggleShowOutputs: () => void;
+ focusedCst: IConstituenta;
+ onResetFocus: () => void;
}
-export function ToolbarFocusedCst({
- center,
- reset,
- showInputs,
- showOutputs,
- toggleShowInputs,
- toggleShowOutputs
-}: ToolbarFocusedCstProps) {
+export function ToolbarFocusedCst({ focusedCst, onResetFocus }: ToolbarFocusedCstProps) {
const { deselectAll } = useRSEdit();
+ const filter = useTermGraphStore(state => state.filter);
+ const setFilter = useTermGraphStore(state => state.setFilter);
+
function resetSelection() {
- reset();
+ onResetFocus();
deselectAll();
}
+ function handleShowInputs() {
+ setFilter({
+ ...filter,
+ focusShowInputs: !filter.focusShowInputs
+ });
+ }
+
+ function handleShowOutputs() {
+ setFilter({
+ ...filter,
+ focusShowOutputs: !filter.focusShowOutputs
+ });
+ }
+
return (
Фокус
- {center.alias}
+ {focusedCst.alias}
}
- onClick={toggleShowInputs}
+ title={filter.focusShowInputs ? 'Скрыть поставщиков' : 'Отобразить поставщиков'}
+ icon={
}
+ onClick={handleShowInputs}
/>
}
- onClick={toggleShowOutputs}
+ title={filter.focusShowOutputs ? 'Скрыть потребителей' : 'Отобразить потребителей'}
+ icon={
}
+ onClick={handleShowOutputs}
/>
);
diff --git a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarTermGraph.tsx b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarTermGraph.tsx
index 06498607..c6461a0b 100644
--- a/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarTermGraph.tsx
+++ b/rsconcept/frontend/src/features/rsform/pages/RSFormPage/EditorTermGraph/ToolbarTermGraph.tsx
@@ -1,7 +1,12 @@
+import { toast } from 'react-toastify';
+import { getNodesBounds, getViewportForBounds, useNodes, useReactFlow } from 'reactflow';
import clsx from 'clsx';
+import { toPng } from 'html-to-image';
import { BadgeHelp, HelpTopic } from '@/features/help';
import { MiniSelectorOSS } from '@/features/library';
+import { CstType } from '@/features/rsform/backend/types';
+import { useTermGraphStore } from '@/features/rsform/stores/termGraph';
import { MiniButton } from '@/components/Control';
import {
@@ -17,39 +22,34 @@ import {
IconTypeGraph
} from '@/components/Icons';
import { useDialogsStore } from '@/stores/dialogs';
+import { usePreferencesStore } from '@/stores/preferences';
+import { APP_COLORS } from '@/styling/colors';
import { PARAMETER } from '@/utils/constants';
+import { errorMsg } from '@/utils/labels';
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
import { useRSEdit } from '../RSEditContext';
-interface ToolbarTermGraphProps {
- noText: boolean;
- foldDerived: boolean;
+import { ZOOM_MAX, ZOOM_MIN } from './TGFlow';
- showParamsDialog: () => void;
- onCreate: () => void;
- onDelete: () => void;
- onFitView: () => void;
- onSaveImage: () => void;
-
- toggleFoldDerived: () => void;
- toggleNoText: () => void;
-}
-
-export function ToolbarTermGraph({
- noText,
- foldDerived,
- toggleNoText,
- toggleFoldDerived,
- showParamsDialog,
- onCreate,
- onDelete,
- onFitView,
- onSaveImage
-}: ToolbarTermGraphProps) {
+export function ToolbarTermGraph() {
const isProcessing = useMutatingRSForm();
+ const darkMode = usePreferencesStore(state => state.darkMode);
+ const {
+ schema, //
+ selected,
+ navigateOss,
+ isContentEditable,
+ canDeleteSelected,
+ createCst,
+ promptDeleteCst
+ } = useRSEdit();
const showTypeGraph = useDialogsStore(state => state.showShowTypeGraph);
- const { schema, navigateOss, isContentEditable, canDeleteSelected } = useRSEdit();
+ const showParams = useDialogsStore(state => state.showGraphParams);
+ const filter = useTermGraphStore(state => state.filter);
+ const setFilter = useTermGraphStore(state => state.setFilter);
+ const nodes = useNodes();
+ const flow = useReactFlow();
function handleShowTypeGraph() {
const typeInfo = schema.items.map(item => ({
@@ -60,6 +60,74 @@ export function ToolbarTermGraph({
showTypeGraph({ items: typeInfo });
}
+ function handleCreateCst() {
+ const definition = selected.map(id => schema.cstByID.get(id)!.alias).join(' ');
+ createCst(selected.length === 0 ? CstType.BASE : CstType.TERM, false, definition);
+ }
+
+ function handleDeleteCst() {
+ if (!canDeleteSelected || isProcessing) {
+ return;
+ }
+ promptDeleteCst();
+ }
+
+ function handleToggleNoText() {
+ setFilter({
+ ...filter,
+ noText: !filter.noText
+ });
+ }
+
+ function handleSaveImage() {
+ const canvas: HTMLElement | null = document.querySelector('.react-flow__viewport');
+ if (canvas === null) {
+ toast.error(errorMsg.imageFailed);
+ return;
+ }
+
+ const imageWidth = PARAMETER.ossImageWidth;
+ const imageHeight = PARAMETER.ossImageHeight;
+ const nodesBounds = getNodesBounds(nodes);
+ const viewport = getViewportForBounds(nodesBounds, imageWidth, imageHeight, ZOOM_MIN, ZOOM_MAX);
+ toPng(canvas, {
+ backgroundColor: darkMode ? APP_COLORS.bgDefaultDark : APP_COLORS.bgDefaultLight,
+ width: imageWidth,
+ height: imageHeight,
+ style: {
+ width: String(imageWidth),
+ height: String(imageHeight),
+ transform: `translate(${viewport.x}px, ${viewport.y}px) scale(${viewport.zoom * 2})`
+ }
+ })
+ .then(dataURL => {
+ const a = document.createElement('a');
+ a.setAttribute('download', `${schema.alias}.png`);
+ a.setAttribute('href', dataURL);
+ a.click();
+ })
+ .catch(error => {
+ console.error(error);
+ toast.error(errorMsg.imageFailed);
+ });
+ }
+
+ function handleFitView() {
+ setTimeout(() => {
+ flow.fitView({ duration: PARAMETER.zoomDuration });
+ }, PARAMETER.minimalTimeout);
+ }
+
+ function handleFoldDerived() {
+ setFilter({
+ ...filter,
+ foldDerived: !filter.foldDerived
+ });
+ setTimeout(() => {
+ flow.fitView({ duration: PARAMETER.zoomDuration });
+ }, PARAMETER.graphRefreshDelay);
+ }
+
return (
{schema.oss.length > 0 ? (
@@ -71,41 +139,41 @@ export function ToolbarTermGraph({
}
- onClick={showParamsDialog}
+ onClick={showParams}
/>
}
title='Граф целиком'
- onClick={onFitView}
+ onClick={handleFitView}
/>
) : (
)
}
- onClick={toggleNoText}
+ onClick={handleToggleNoText}
/>
) : (
)
}
- onClick={toggleFoldDerived}
+ onClick={handleFoldDerived}
/>
{isContentEditable ? (
}
disabled={isProcessing}
- onClick={onCreate}
+ onClick={handleCreateCst}
/>
) : null}
{isContentEditable ? (
@@ -113,7 +181,7 @@ export function ToolbarTermGraph({
title='Удалить выбранные'
icon={
}
disabled={!canDeleteSelected || isProcessing}
- onClick={onDelete}
+ onClick={handleDeleteCst}
/>
) : null}
}
title='Сохранить изображение'
- onClick={onSaveImage}
+ onClick={handleSaveImage}
/>
LABEL_THRESHOLD ? FONT_SIZE_MED : FONT_SIZE_MAX
}}
+ data-tooltip-id={globalIDs.constituenta_tooltip}
>