From 000bfad0f8f87b3ba64c043ed9dbcfc9918eea63 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 21 Jun 2024 21:46:10 +0300 Subject: [PATCH] Start fixing type errors and basic linting --- .vscode/settings.json | 1 + .../backend/apps/rsform/models/Synthesis.py | 14 +- .../apps/rsform/serializers/data_access.py | 10 +- .../apps/rsform/serializers/synthesis.py | 15 +- rsconcept/backend/apps/rsform/urls.py | 2 +- rsconcept/backend/apps/rsform/utils.py | 2 +- .../backend/apps/rsform/views/__init__.py | 9 +- .../backend/apps/rsform/views/synthesis.py | 132 +++---- rsconcept/backend/requirements-dev.txt | 17 +- rsconcept/frontend/src/app/backendAPI.ts | 39 +- .../src/components/ui/Synthesis/InputNode.tsx | 46 +-- .../components/ui/Synthesis/OperationNode.tsx | 69 ++-- .../components/ui/Synthesis/SynthesisFlow.tsx | 31 +- .../DlgOssGraph/DlgSelectInputScheme.tsx | 36 +- .../src/dialogs/DlgOssGraph/DlgSynthesis.tsx | 63 ++-- rsconcept/frontend/src/hooks/useOssDetails.ts | 4 +- rsconcept/frontend/src/models/OssLoader.ts | 3 +- rsconcept/frontend/src/models/oss.ts | 4 +- .../src/pages/OssPage/SynthesisContext.tsx | 349 +++++++++--------- .../src/pages/OssPage/SynthesisOperation.tsx | 12 +- .../OssPage/SynthesisSubstitutionsTab.tsx | 15 +- 21 files changed, 420 insertions(+), 453 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b666ff7a..07c8043b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -120,6 +120,7 @@ "pymorphy", "Quantor", "razdel", + "reactflow", "reagraph", "redef", "REDOC", diff --git a/rsconcept/backend/apps/rsform/models/Synthesis.py b/rsconcept/backend/apps/rsform/models/Synthesis.py index 02cfa6d6..8b1f0d9f 100644 --- a/rsconcept/backend/apps/rsform/models/Synthesis.py +++ b/rsconcept/backend/apps/rsform/models/Synthesis.py @@ -1,10 +1,19 @@ +''' Models: Synthesis. ''' + from django.db.models import ( - CASCADE, SET_NULL, ForeignKey, Model, PositiveIntegerField, QuerySet, - TextChoices, TextField, BooleanField, CharField, DateTimeField, JSONField, IntegerField, AutoField + CASCADE, + SET_NULL, + BooleanField, + CharField, + ForeignKey, + IntegerField, + Model, + TextChoices ) class OperationStatus(TextChoices): + ''' Operation status enumeration. ''' DRAFT = 'Draft', COMPLETED = 'Completed', WARNING = 'Warning', @@ -12,6 +21,7 @@ class OperationStatus(TextChoices): class GraphStatus(TextChoices): + ''' Graph status enumeration. ''' DRAFT = 'Draft', COMPLETED = 'Completed', WARNING = 'Warning', diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index c8f40439..2861eff7 100644 --- a/rsconcept/backend/apps/rsform/serializers/data_access.py +++ b/rsconcept/backend/apps/rsform/serializers/data_access.py @@ -405,11 +405,11 @@ class InlineSynthesisSerializer(serializers.Serializer): user = cast(User, self.context['user']) schema_in = cast(LibraryItem, attrs['source']) schema_out = cast(LibraryItem, attrs['receiver']) - #if user.is_anonymous or (schema_out.owner != user and not user.is_staff): - # raise PermissionDenied({ - # 'message': msg.schemaNotOwned(), - # 'object_id': schema_in.id - # }) + if user.is_anonymous or (schema_out.owner != user and not user.is_staff): + raise PermissionDenied({ + 'message': msg.schemaNotOwned(), + 'object_id': schema_in.id + }) constituents = cast(list[Constituenta], attrs['items']) for cst in constituents: if cst.schema != schema_in: diff --git a/rsconcept/backend/apps/rsform/serializers/synthesis.py b/rsconcept/backend/apps/rsform/serializers/synthesis.py index 83040da6..91df80bb 100644 --- a/rsconcept/backend/apps/rsform/serializers/synthesis.py +++ b/rsconcept/backend/apps/rsform/serializers/synthesis.py @@ -1,9 +1,14 @@ -from rest_framework import serializers -from .data_access import CstSubstituteSerializerBase -from rest_framework.serializers import PrimaryKeyRelatedField as PKField +''' Synthesis serializers. ''' -from ..models import Constituenta, LibraryItem -from ..models.Synthesis import SynthesisGraph, SynthesisEdge, InputNode, OperationNode, SynthesisSubstitution +from rest_framework import serializers + +from ..models.Synthesis import ( + InputNode, + OperationNode, + SynthesisEdge, + SynthesisGraph, + SynthesisSubstitution +) class SynthesisGraphSerializer(serializers.ModelSerializer): diff --git a/rsconcept/backend/apps/rsform/urls.py b/rsconcept/backend/apps/rsform/urls.py index 8d2743d1..fb4f258f 100644 --- a/rsconcept/backend/apps/rsform/urls.py +++ b/rsconcept/backend/apps/rsform/urls.py @@ -31,7 +31,7 @@ urlpatterns = [ path('cctext/generate-lexeme', views.generate_lexeme), path('cctext/parse', views.parse_text), path('synthesis/run_single', views.run_synthesis_view), - path('synthesis/run_all', views.run_sythesis_graph_view), + path('synthesis/run_all', views.run_synthesis_graph_view), path('synthesis/save', views.save_synthesis_graph), path('synthesis/', views.get_synthesis_graph), path('', include(library_router.urls)), diff --git a/rsconcept/backend/apps/rsform/utils.py b/rsconcept/backend/apps/rsform/utils.py index ed55302a..b096669d 100644 --- a/rsconcept/backend/apps/rsform/utils.py +++ b/rsconcept/backend/apps/rsform/utils.py @@ -76,6 +76,6 @@ def clone_rsform(rsform): rsform_copy.item.comment = "Temporary cloned rsform" rsform_copy.item.save() - rsform_copy.insert_copy(items=[cst for cst in rsform.item.constituenta_set.all()], position=1) + rsform_copy.insert_copy(items=rsform.item.constituenta_set.all(), position=1) rsform_copy.item.save() return rsform_copy diff --git a/rsconcept/backend/apps/rsform/views/__init__.py b/rsconcept/backend/apps/rsform/views/__init__.py index 5e00018f..57cd8ca8 100644 --- a/rsconcept/backend/apps/rsform/views/__init__.py +++ b/rsconcept/backend/apps/rsform/views/__init__.py @@ -5,11 +5,10 @@ from .library import LibraryActiveView, LibraryAdminView, LibraryTemplatesView, from .operations import inline_synthesis from .rsforms import RSFormViewSet, TrsImportView, create_rsform from .rslang import convert_to_ascii, convert_to_math, parse_expression -from .versions import VersionViewset, create_version, export_file, retrieve_version - from .synthesis import ( - run_synthesis_view, - run_sythesis_graph_view, - save_synthesis_graph, get_synthesis_graph, + run_synthesis_graph_view, + run_synthesis_view, + save_synthesis_graph ) +from .versions import VersionViewset, create_version, export_file, retrieve_version diff --git a/rsconcept/backend/apps/rsform/views/synthesis.py b/rsconcept/backend/apps/rsform/views/synthesis.py index 441eda07..4ac74c04 100644 --- a/rsconcept/backend/apps/rsform/views/synthesis.py +++ b/rsconcept/backend/apps/rsform/views/synthesis.py @@ -1,24 +1,26 @@ -import copy +''' Endpoints for operations schema. ''' +from typing import cast from drf_spectacular.utils import extend_schema from rest_framework import status from rest_framework.decorators import api_view from rest_framework.request import Request from rest_framework.response import Response -from django.db.models import Q -from rest_framework.views import APIView +from ..models.api_RSForm import RSForm from ..models.Constituenta import Constituenta from ..models.LibraryItem import LibraryItem -from ..models.api_RSForm import RSForm -from ..models.Synthesis import SynthesisGraph, InputNode, OperationNode, SynthesisSubstitution, SynthesisEdge -from ..serializers import RSFormSerializer, SynthesisGraphSerializer, InlineSynthesisSerializer -from typing import cast -from django.contrib.auth.models import User - -from ..serializers.data_access import CstBaseSerializer, CstSerializer -from ..serializers.synthesis import OperationNodeSerializer, InputNodeSerializer, \ - SynthesisSubstitutionSerializer, SynthesisEdgeSerializer, RunSingleSynthesis, RunSingleSynthesisResponse +from ..models.Synthesis import InputNode, OperationNode, SynthesisEdge, SynthesisSubstitution +from ..serializers import RSFormSerializer, SynthesisGraphSerializer +from ..serializers.data_access import CstSerializer +from ..serializers.synthesis import ( + InputNodeSerializer, + OperationNodeSerializer, + RunSingleSynthesis, + RunSingleSynthesisResponse, + SynthesisEdgeSerializer, + SynthesisSubstitutionSerializer +) from ..utils import clone_rsform @@ -45,8 +47,12 @@ def get_synthesis_graph(request: Request, pk_item: int): edges = SynthesisEdgeSerializer(instance=edges, many=True) substitutions = SynthesisSubstitutionSerializer(instance=substitutions, many=True) for substitution in substitutions.data: - substitution['leftCst'] = CstSerializer(instance=Constituenta.objects.get(id=substitution['leftCst'])).data - substitution['rightCst'] = CstSerializer(instance=Constituenta.objects.get(id=substitution['rightCst'])).data + substitution['leftCst'] = CstSerializer( + instance=Constituenta.objects.get(id=substitution['leftCst']) + ).data + substitution['rightCst'] = CstSerializer( + instance=Constituenta.objects.get(id=substitution['rightCst']) + ).data return Response(data={ 'graph': synthesis_graph.data, 'input_nodes': input_nodes.data, @@ -118,7 +124,7 @@ def save_synthesis_graph(request: Request): auth=None ) @api_view(['POST']) -def run_sythesis_graph_view(request: Request): +def run_synthesis_graph_view(request: Request): serializer = RunSingleSynthesis(data=request.data) serializer.is_valid(raise_exception=True) for atomic_synthesis in serializer.validated_data: @@ -164,61 +170,61 @@ def run_synthesis(serializer: RunSingleSynthesis): data=RSFormSerializer(left_schema_copy.item).data ) - right_rsform_copy = clone_rsform(right_schema) + # right_rsform_copy = clone_rsform(right_schema) - serializer.is_valid(raise_exception=True) + # serializer.is_valid(raise_exception=True) - try: - mapping = serializer.validated_data['mapping'] - left_cst_pks = [x.get("left_cst_pk") for x in mapping] - right_cst_pks = [x.get("right_cst_pk") for x in mapping] - directions = [x.get("mapping_direction") for x in mapping] - left_csts = left_schema.item.constituenta_set.filter(pk__in=left_cst_pks) - right_csts = right_schema.item.constituenta_set.filter(pk__in=right_cst_pks) + # try: + # mapping = serializer.validated_data['mapping'] + # left_cst_pks = [x.get("left_cst_pk") for x in mapping] + # right_cst_pks = [x.get("right_cst_pk") for x in mapping] + # directions = [x.get("mapping_direction") for x in mapping] + # left_csts = left_schema.item.constituenta_set.filter(pk__in=left_cst_pks) + # right_csts = right_schema.item.constituenta_set.filter(pk__in=right_cst_pks) - left_mapping_dict = {left.alias: right.alias for left, right, direction in - zip(left_csts, right_csts, directions) if - not direction} - right_mapping_dict = {right.alias: left.alias for left, right, direction in - zip(left_csts, right_csts, directions) - if direction} + # left_mapping_dict = {left.alias: right.alias for left, right, direction in + # zip(left_csts, right_csts, directions) if + # not direction} + # right_mapping_dict = {right.alias: left.alias for left, right, direction in + # zip(left_csts, right_csts, directions) + # if direction} - left_schema_copy.apply_mapping(mapping=left_mapping_dict) - right_rsform_copy.apply_mapping(mapping=right_mapping_dict) - left_schema_copy.resolve_all_text() - right_rsform_copy.resolve_all_text() - left_schema_copy.item.save() - right_rsform_copy.item.save() + # left_schema_copy.apply_mapping(mapping=left_mapping_dict) + # right_rsform_copy.apply_mapping(mapping=right_mapping_dict) + # left_schema_copy.resolve_all_text() + # right_rsform_copy.resolve_all_text() + # left_schema_copy.item.save() + # right_rsform_copy.item.save() - for left, right in zip(left_csts, right_csts): - # left_rsform_copy.substitute(original=left, substitution=right, transfer_term=False) - # right_rsform_copy.substitute(original=right, substitution=left, transfer_term=False) - left_schema_copy.item.save() - right_rsform_copy.item.save() + # for left, right in zip(left_csts, right_csts): + # # left_rsform_copy.substitute(original=left, substitution=right, transfer_term=False) + # # right_rsform_copy.substitute(original=right, substitution=left, transfer_term=False) + # left_schema_copy.item.save() + # right_rsform_copy.item.save() - right_cst_pks = set(right_cst_pks) - for cst in right_rsform_copy.item.constituenta_set.all(): - if cst.pk not in right_cst_pks: - max_idx = left_schema.get_max_index(cst.cst_type) - left_schema_copy.insert_copy(items=[cst], position=max_idx + 1) - left_schema_copy.item.save() + # right_cst_pks = set(right_cst_pks) + # for cst in right_rsform_copy.item.constituenta_set.all(): + # if cst.pk not in right_cst_pks: + # max_idx = left_schema.get_max_index(cst.cst_type) + # left_schema_copy.insert_copy(items=[cst], position=max_idx + 1) + # left_schema_copy.item.save() - right_rsform_copy.item.delete() + # right_rsform_copy.item.delete() - serializer = RSFormParseSerializer(cast(LibraryItem, left_schema_copy.item)) + # serializer = RSFormParseSerializer(cast(LibraryItem, left_schema_copy.item)) - # TODO: remove next line - left_schema_copy.item.delete() + # # TODO: remove next line + # left_schema_copy.item.delete() - return Response( - status=status.HTTP_200_OK, - data=serializer.data - ) - # TODO: rework 500 - except Exception as e: - left_schema_copy.item.delete() - right_rsform_copy.item.delete() - raise e - return Response( - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) + # return Response( + # status=status.HTTP_200_OK, + # data=serializer.data + # ) + # # TODO: rework 500 + # except Exception as e: + # left_schema_copy.item.delete() + # right_rsform_copy.item.delete() + # raise e + # return Response( + # status=status.HTTP_500_INTERNAL_SERVER_ERROR + # ) diff --git a/rsconcept/backend/requirements-dev.txt b/rsconcept/backend/requirements-dev.txt index e3b3cb24..ab9d5484 100644 --- a/rsconcept/backend/requirements-dev.txt +++ b/rsconcept/backend/requirements-dev.txt @@ -1,19 +1,20 @@ tzdata -django +Django djangorestframework django-cors-headers django-filter drf-spectacular -drf-spectacular[sidecar] +drf-spectacular-sidecar coreapi +django-rest-passwordreset cctext pyconcept +psycopg2-binary +gunicorn + +djangorestframework-stubs[compatible-mypy] + mypy pylint -coverage -djangorestframework-stubs[compatible-mypy] -django-rest-passwordreset - -psycopg2-binary -gunicorn \ No newline at end of file +coverage \ No newline at end of file diff --git a/rsconcept/frontend/src/app/backendAPI.ts b/rsconcept/frontend/src/app/backendAPI.ts index a71a65ed..87272819 100644 --- a/rsconcept/frontend/src/app/backendAPI.ts +++ b/rsconcept/frontend/src/app/backendAPI.ts @@ -7,17 +7,10 @@ import { toast } from 'react-toastify'; import { type ErrorData } from '@/components/info/InfoError'; import { ILexemeData, IResolutionData, ITextRequest, ITextResult, IWordFormPlain } from '@/models/language'; -import { - AccessPolicy, - ILibraryItem, - ILibraryUpdateData, - ITargetAccessPolicy, - ITargetLocation, - IVersionData, - LibraryItemType -} from '@/models/library'; +import { ILibraryItem, ILibraryUpdateData, ITargetAccessPolicy, ITargetLocation, IVersionData } from '@/models/library'; import { ILibraryCreateData } from '@/models/library'; import { IOperationSchemaData, IRunSynthesis, IRunSynthesisResponse } from '@/models/oss'; +import { ISynthesisGraphData } from '@/models/oss.ts'; import { IConstituentaList, IConstituentaMeta, @@ -51,7 +44,6 @@ import { IUserUpdatePassword } from '@/models/user'; import { buildConstants } from '@/utils/buildConstants'; -import { ISynthesisGraphData } from '@/models/oss.ts'; const defaultOptions = { xsrfCookieName: 'csrftoken', @@ -96,8 +88,7 @@ export interface FrontExchange extends IFrontRequest< onSuccess: DataCallback; } -export interface FrontAction extends IFrontRequest { -} +export interface FrontAction extends IFrontRequest {} interface IAxiosRequest { endpoint: string; @@ -522,10 +513,10 @@ function AxiosGet({ endpoint, request, options }: IAxiosRequest({ - endpoint, - request, - options - }: IAxiosRequest) { + endpoint, + request, + options +}: IAxiosRequest) { if (request.setLoading) request.setLoading(true); axiosInstance .post(endpoint, request.data, options) @@ -541,10 +532,10 @@ function AxiosPost({ } function AxiosDelete({ - endpoint, - request, - options - }: IAxiosRequest) { + endpoint, + request, + options +}: IAxiosRequest) { if (request.setLoading) request.setLoading(true); axiosInstance .delete(endpoint, options) @@ -560,10 +551,10 @@ function AxiosDelete({ } function AxiosPatch({ - endpoint, - request, - options - }: IAxiosRequest) { + endpoint, + request, + options +}: IAxiosRequest) { if (request.setLoading) request.setLoading(true); axiosInstance .patch(endpoint, request.data, options) diff --git a/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx b/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx index a59d8f3c..94dae743 100644 --- a/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx +++ b/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx @@ -1,8 +1,8 @@ -import { memo, type FC } from 'react'; -import { Handle, Position, type NodeProps } from '@reactflow/core'; -import Button from '@/components/ui/Button.tsx'; -import { PiPlugsConnected } from 'react-icons/pi'; +import { Handle, Position } from '@reactflow/core'; +import { type FC, memo } from 'react'; import { CiSquareRemove } from 'react-icons/ci'; +import { PiPlugsConnected } from 'react-icons/pi'; + import MiniButton from '@/components/ui/MiniButton.tsx'; import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; @@ -12,48 +12,48 @@ interface InputNodeProps { label: string; onDelete: (nodeId: string) => void; }; - bound_rsform_id: number; } - -const InputNode: FC = ({ id, data,bound_rsform_id }) => { +const InputNode: FC = ({ id, data }) => { const controller = useSynthesis(); - const { label, onDelete } = data; const handleDelete = () => { - onDelete(id); + data.onDelete(id); }; - const handleClick = () =>{ + const handleClick = () => { controller.selectNode(id); controller.showSelectInput(); - } + }; return ( <> - +
- } - title="Удалить" - onClick={handleDelete} - color={'red'} + } + title='Удалить' + onClick={handleDelete} + color={'red'} />
Тип: Ввод
- Схема:{controller.getBind(id) === undefined? '': controller.getBind(id)} + Схема:{controller.getBind(id) === undefined ? '' : controller.getBind(id)} - } - title="Привязать схему" - onClick={() => {handleClick()}} + } + title='Привязать схему' + onClick={() => { + handleClick(); + }} />
- ); }; diff --git a/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx b/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx index 9e1d5564..b3ffc99e 100644 --- a/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx +++ b/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx @@ -1,10 +1,11 @@ -import { memo, type FC, type CSSProperties } from 'react'; -import { Handle, Position, type NodeProps } from '@reactflow/core'; -import MiniButton from '@/components/ui/MiniButton.tsx'; -import { IoGitNetworkSharp } from 'react-icons/io5'; -import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; +import { Handle, Position } from '@reactflow/core'; +import { type CSSProperties, type FC, memo } from 'react'; import { CiSquareRemove } from 'react-icons/ci'; -import { VscDebugStart } from "react-icons/vsc"; +import { IoGitNetworkSharp } from 'react-icons/io5'; +import { VscDebugStart } from 'react-icons/vsc'; + +import MiniButton from '@/components/ui/MiniButton.tsx'; +import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; const sourceHandleStyleA: CSSProperties = { left: 50 }; const sourceHandleStyleB: CSSProperties = { @@ -18,8 +19,8 @@ interface OperationNodeProps { label: string; onDelete: (nodeId: string) => void; }; - xPos: number, - yPos: number, + xPos: number; + yPos: number; } const OperationNode: FC = ({ id, data, xPos, yPos }) => { @@ -30,60 +31,48 @@ const OperationNode: FC = ({ id, data, xPos, yPos }) => { onDelete(id); }; - const handleSubstitution = () =>{ + const handleSubstitution = () => { controller.selectNode(id); controller.showSynthesis(); - } + }; const handleSynthesis = () => { - controller.singleSynthesis(id) - } + controller.singleSynthesis(id); + }; return ( <> - +
- } - title="Удалить" - onClick={handleDelete} - color={'red'} + } + title='Удалить' + onClick={handleDelete} + color={'red'} />
Тип: Отождествление
- Схема:{' '} - - + Схема: } - title="Синтез" + className='float-right' + icon={} + title='Синтез' onClick={() => handleSynthesis()} /> } - title="Отождествления" + className='float-right' + icon={} + title='Отождествления' onClick={() => handleSubstitution()} /> -
- - + + ); }; diff --git a/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx b/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx index 03fba955..2d47cf55 100644 --- a/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx +++ b/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx @@ -1,42 +1,31 @@ -import { useCallback, useMemo } from 'react'; -import { - ReactFlow, - addEdge, - useNodesState, - useEdgesState, - type Connection, - type Edge, - type Node, OnSelectionChangeParams -} from '@reactflow/core'; - - -import OperationNode from './OperationNode'; -import InputNode from './InputNode'; - // this is important! You need to import the styles from the lib to make it work import '@reactflow/core/dist/style.css'; - import './SynthesisFlow.css'; -import { useState } from 'react'; -import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; + +import { NodeTypes, ReactFlow } from '@reactflow/core'; +import { useMemo } from 'react'; + import { useConceptOptions } from '@/context/OptionsContext.tsx'; +import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; +import InputNode from './InputNode'; +import OperationNode from './OperationNode'; -const nodeTypes = { +const nodeTypes: NodeTypes = { custom: OperationNode, input: InputNode }; function Flow() { const controller = useSynthesis(); - const { calculateHeight, darkMode } = useConceptOptions(); + const { calculateHeight } = useConceptOptions(); const canvasWidth = useMemo(() => { return 'calc(100vw - 1rem)'; }, []); const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]); return ( -
+
{ @@ -41,7 +29,6 @@ function DlgSelectInputScheme({ nodeId, hideWindow }: DlgCreateSynthesisProps) { const [substitutions, setSubstitutions] = useState([]); const [donorID, setDonorID] = useState(undefined); - const schemaPanel = useMemo( () => ( @@ -67,22 +54,21 @@ function DlgSelectInputScheme({ nodeId, hideWindow }: DlgCreateSynthesisProps) { return ( - + {schemaPanel} diff --git a/rsconcept/frontend/src/dialogs/DlgOssGraph/DlgSynthesis.tsx b/rsconcept/frontend/src/dialogs/DlgOssGraph/DlgSynthesis.tsx index 308298a3..7f773882 100644 --- a/rsconcept/frontend/src/dialogs/DlgOssGraph/DlgSynthesis.tsx +++ b/rsconcept/frontend/src/dialogs/DlgOssGraph/DlgSynthesis.tsx @@ -1,15 +1,15 @@ 'use client'; -import { useMemo, useState } from 'react'; +import clsx from 'clsx'; +import { useCallback, useMemo, useState } from 'react'; +import { TabList, TabPanel, Tabs } from 'react-tabs'; import Modal, { ModalProps } from '@/components/ui/Modal.tsx'; -import { TabList, TabPanel, Tabs } from 'react-tabs'; -import clsx from 'clsx'; import TabLabel from '@/components/ui/TabLabel.tsx'; import useRSFormDetails from '@/hooks/useRSFormDetails.ts'; -import SynthesisSubstitutionsTab from '@/pages/OssPage/SynthesisSubstitutionsTab.tsx'; -import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; import { ISynthesisSubstitution } from '@/models/oss.ts'; +import { useSynthesis } from '@/pages/OssPage/SynthesisContext.tsx'; +import SynthesisSubstitutionsTab from '@/pages/OssPage/SynthesisSubstitutionsTab.tsx'; interface DlgCreateSynthesisProps extends Pick { nodeId: string; @@ -25,24 +25,24 @@ function DlgSynthesis({ hideWindow, nodeId, onSynthesis }: DlgCreateSynthesisPro const controller = useSynthesis(); const [activeTab, setActiveTab] = useState(SynthesisTabID.SCHEMA); + const sourceLeft = useRSFormDetails({ - target: controller.getNodeParentsRsform(nodeId)[0] ? - String(controller.getNodeParentsRsform(nodeId)[0]) : undefined + target: controller.getNodeParentsRsform(nodeId)[0] ? String(controller.getNodeParentsRsform(nodeId)[0]) : undefined }); + const sourceRight = useRSFormDetails({ - target: controller.getNodeParentsRsform(nodeId)[1] ? - String(controller.getNodeParentsRsform(nodeId)[1]) : undefined + target: controller.getNodeParentsRsform(nodeId)[1] ? String(controller.getNodeParentsRsform(nodeId)[1]) : undefined }); + const validated = useMemo(() => controller.getNodeParentsRsform(nodeId).length == 2, [controller, nodeId]); - //const validated = useMemo(() => !!source.schema && selected.length > 0, [source.schema, selected]); function handleSubmit() { const parents = controller.getNodeParentsRsform(nodeId); if (parents.length != 2) { return; } - const data: ISynthesisSubstitution[] = controller.substitutions.map((item) => ({ + const data: ISynthesisSubstitution[] = controller.substitutions.map(item => ({ id: null, operation_id: nodeId, leftCst: item.leftCst, @@ -53,27 +53,17 @@ function DlgSynthesis({ hideWindow, nodeId, onSynthesis }: DlgCreateSynthesisPro controller.setSubstitutions(data); } - function validated() { - const parents = controller.getNodeParentsRsform(nodeId); - return parents.length == 2; - } + const schemaPanel = useMemo(() => , []); - const schemaPanel = useMemo( - () => ( - - ), [] - ); + const selectedSubstitutions = useMemo(() => controller.getSubstitution(nodeId), [controller, nodeId]); - const selectedSubstitutions = useMemo( - () => controller.getSubstitution(nodeId), + const setSelectedSubstitutions = useCallback( + (newElement: ISynthesisSubstitution[]) => { + controller.updateSubstitution(nodeId, newElement); + }, [controller, nodeId] ); - const setSelectedSubstitutions = (newElement: ISynthesisSubstitution[]) => { - controller.updateSubstitution(nodeId, newElement, controller.setSubstitutions); - }; - - const substitutesPanel = useMemo( () => ( @@ -85,34 +75,31 @@ function DlgSynthesis({ hideWindow, nodeId, onSynthesis }: DlgCreateSynthesisPro /> ), - [sourceLeft.schema, sourceRight.schema, controller] + [sourceLeft.schema, sourceRight.schema, selectedSubstitutions, setSelectedSubstitutions] ); return ( - - + + {schemaPanel} {substitutesPanel} - ); } diff --git a/rsconcept/frontend/src/hooks/useOssDetails.ts b/rsconcept/frontend/src/hooks/useOssDetails.ts index 5c4116e9..ee807ca4 100644 --- a/rsconcept/frontend/src/hooks/useOssDetails.ts +++ b/rsconcept/frontend/src/hooks/useOssDetails.ts @@ -4,9 +4,9 @@ import { useCallback, useEffect, useState } from 'react'; import { getOssDetails } from '@/app/backendAPI'; import { type ErrorData } from '@/components/info/InfoError'; +import { AccessPolicy, LibraryItemType } from '@/models/library.ts'; import { IOperationSchema, IOperationSchemaData } from '@/models/oss'; import { OssLoader } from '@/models/OssLoader'; -import { AccessPolicy, LibraryItemType } from '@/models/library.ts'; function useOssDetails({ target }: { target?: string }) { const [schema, setInner] = useState(undefined); @@ -57,7 +57,7 @@ function useOssDetails({ target }: { target?: string }) { const combinedData = { ...staticData, ...schema - } + }; setSchema(combinedData); if (callback) callback(); } diff --git a/rsconcept/frontend/src/models/OssLoader.ts b/rsconcept/frontend/src/models/OssLoader.ts index e036a3f4..23549640 100644 --- a/rsconcept/frontend/src/models/OssLoader.ts +++ b/rsconcept/frontend/src/models/OssLoader.ts @@ -11,14 +11,13 @@ import { IOperationSchema, IOperationSchemaData } from './oss'; export class OssLoader { private schema: IOperationSchemaData; - constructor(input: IOperationSchemaData) { this.schema = input; } produceOSS(): IOperationSchema { const result = this.schema as IOperationSchema; - //result.producedData = [1, 2, 3]; // TODO: put data processing here + // TODO: put data processing here return result; } } diff --git a/rsconcept/frontend/src/models/oss.ts b/rsconcept/frontend/src/models/oss.ts index 4332a8ca..8d763dfe 100644 --- a/rsconcept/frontend/src/models/oss.ts +++ b/rsconcept/frontend/src/models/oss.ts @@ -2,9 +2,10 @@ * Module: Schema of Synthesis Operations. */ +import { ISubstitution } from '@/models/rsform.ts'; + import { ILibraryItemData } from './library'; import { UserID } from './user'; -import { IConstituenta, ISubstitution } from '@/models/rsform.ts'; /** * Represents backend data for Schema of Synthesis Operations. @@ -65,3 +66,4 @@ export interface IRunSynthesisResponse { export interface IOperationSchema extends IOperationSchemaData { subscribers: UserID[]; editors: UserID[]; +} diff --git a/rsconcept/frontend/src/pages/OssPage/SynthesisContext.tsx b/rsconcept/frontend/src/pages/OssPage/SynthesisContext.tsx index 7b5a96ce..1ca5e0db 100644 --- a/rsconcept/frontend/src/pages/OssPage/SynthesisContext.tsx +++ b/rsconcept/frontend/src/pages/OssPage/SynthesisContext.tsx @@ -1,23 +1,22 @@ -import { IRSFormData, ISubstitution } from '@/models/rsform.ts'; -import { DataCallback, runSingleSynthesis, postSynthesisGraph } from '@/app/backendAPI.ts'; -import { ISynthesisData } from '@/models/synthesis.ts'; -import { createContext, Dispatch, SetStateAction, useCallback, useContext, useEffect, useState } from 'react'; -import DlgSynthesis from '@/dialogs/DlgOssGraph/DlgSynthesis.tsx'; import { - Node, - Edge, - useNodesState, - useEdgesState, - type Connection, addEdge, - getIncomers, - getOutgoers, - getConnectedEdges + type Connection, + Edge, + EdgeChange, + Node, + NodeChange, + useEdgesState, + useNodesState } from '@reactflow/core'; -import DlgSelectInputScheme from '@/dialogs/DlgOssGraph/DlgSelectInputScheme.tsx'; -import { IOperationSchemaData, IRunSynthesis, ISynthesisSubstitution } from '@/models/oss.ts'; +import { createContext, useCallback, useContext, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; + +import { postSynthesisGraph, runSingleSynthesis } from '@/app/backendAPI.ts'; import { useOSS } from '@/context/OssContext.tsx'; -import viewConstituents from '@/pages/RSFormPage/ViewConstituents'; +import DlgSelectInputScheme from '@/dialogs/DlgOssGraph/DlgSelectInputScheme.tsx'; +import DlgSynthesis from '@/dialogs/DlgOssGraph/DlgSynthesis.tsx'; +import { IOperationSchemaData, IRunSynthesis, ISynthesisSubstitution } from '@/models/oss.ts'; +import { ISubstitution } from '@/models/rsform.ts'; interface ISynthesisContext { synthesisSchemaID: string; @@ -32,32 +31,32 @@ interface ISynthesisContext { removeItem: () => void; runSynthesisLayer: () => void; - getNodes: () => Node[], - getEdges: () => Edge[] + getNodes: () => Node[]; + getEdges: () => Edge[]; setNodes: (nodes: Node[]) => void; setEdges: (nodes: Edge[]) => void; - onNodesChange: any, - onEdgesChange: any, - onNodesDelete: any, - onConnect: any, - addBind: () => void, + onNodesChange: (changes: NodeChange[]) => void; + onEdgesChange: (changes: EdgeChange[]) => void; + onNodesDelete: (nodes: Node[]) => void; + onConnect: (connection: Connection) => void; + addBind: () => void; - updateBounds: (nodeId: string, newRsform: number) => void, - getBind: (nodeId: string) => number - getNodeParentsRsform: (nodeId: string) => number[] + updateBounds: (nodeId: string, newRsform: number) => void; + getBind: (nodeId: string) => number | undefined; + getNodeParentsRsform: (nodeId: string) => number[]; saveGraph: () => void; - substitutions: ISynthesisSubstitution[] - setSubstitutions: () => void, - getSubstitution: (id: string) => ISynthesisSubstitution[], - updateSubstitution: (id: string, substitution: ISynthesisSubstitution[], setSubstitutions: React.Dispatch>) => void, + substitutions: ISynthesisSubstitution[]; + setSubstitutions: React.Dispatch>; + getSubstitution: (id: string) => ISynthesisSubstitution[]; + updateSubstitution: (id: string, substitution: ISubstitution[]) => void; } interface IBoundMap { - nodeId: string, - rsformId: number + nodeId: string; + rsformId: number; } const SynthesisContext = createContext(null); @@ -84,69 +83,98 @@ export const SynthesisState = ({ synthesisSchemaID, children }: SynthesisStatePr const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [bounds, setBounds] = useState([]); - const [substitutionBounds, setSubstitutionBounds] = useState([]); + // const [substitutionBounds, setSubstitutionBounds] = useState([]); const [substitutions, setSubstitutions] = useState([]); - const { schema, loading, errorLoading } = useOSS(); - const ossSchema = useOSS(); + const ossSchema = useOSS(); const getSubstitution = (operation_id: string): ISynthesisSubstitution[] => { return substitutions.filter(substitution => substitution.operation_id === operation_id); }; - const updateSubstitution = ( - operation_id: number, - newElements: ISubstitution[], - setSubstitutions: React.Dispatch> - ) => { - if (!Array.isArray(newElements)) { - console.error('newElements should be an array.'); - return; - } + const updateSubstitution = (operation_id: string, newElements: ISubstitution[]) => { + // TODO: Call backend API and reload OSS or use returned data for + console.log(operation_id); + console.log(newElements); + toast('Saving substitution table not supported'); + return; - setSubstitutions((prevSubstitutions) => { - // Обновление существующих элементов - const updatedSubstitutions = prevSubstitutions.map((substitution) => { - const newElement = newElements.find((el) => el.id === substitution.id); - if (newElement) { - // Обновляем только соответствующие элементы - return { ...substitution, ...newElement, operation_id: substitution.operation_id }; - } - return substitution; - }); + // if (!Array.isArray(newElements)) { + // console.error('newElements should be an array.'); + // return; + // } - // Добавление новых элементов с присвоением operation_id - const newSubstitutions = newElements - .filter((newElement) => !prevSubstitutions.some((sub) => sub.id === newElement.id)) - .map((newElement) => ({ ...newElement, operation_id })); + // setSubstitutions(prevSubstitutions => { + // // Обновление существующих элементов + // const updatedSubstitutions = prevSubstitutions.map(substitution => { + // const newElement = newElements.find(el => el.id === substitution.id); + // if (newElement) { + // // Обновляем только соответствующие элементы + // return { ...substitution, ...newElement, operation_id: substitution.operation_id }; + // } + // return substitution; + // }); - return [...updatedSubstitutions, ...newSubstitutions]; - }); + // // Добавление новых элементов с присвоением operation_id + // const newSubstitutions = newElements + // .filter(newElement => !prevSubstitutions.some(sub => sub.id === newElement.id)) + // .map(newElement => ({ ...newElement, operation_id })); + + // return [...updatedSubstitutions, ...newSubstitutions]; + // }); }; - const extractEdgeId = (edgeId: string) => { const matches = edgeId.match(/\d+/g); const combined = matches ? matches.join('') : ''; return Number(combined); }; + const getBind = useCallback( + (nodeId: string) => { + const bound = bounds.find(item => item.nodeId == nodeId); + return bound ? bound.rsformId : undefined; + }, + [bounds] + ); - function saveGraph() { + const getBounds = useCallback( + (nodeId: string[]) => { + const parentBounds = bounds.filter(item => item.nodeId in nodeId); + return parentBounds.map(item => item.rsformId); + }, + [bounds] + ); + + const getNodeParentsRsform = useCallback( + (nodeId: string) => { + const parentEdges = edges.filter(edge => edge.source === nodeId); + const parentNodeIds = parentEdges.map(edge => edge.target); + return getBounds(parentNodeIds); + }, + [getBounds, edges] + ); + + const saveGraph = useCallback(() => { + if (!ossSchema.schema) { + return; + } const data: IOperationSchemaData = { graph: { - id: schema?.id, + id: ossSchema.schema?.id, status: 'Draft' }, - input_nodes: nodes.filter((node) => node.type == 'input').map(item => - ({ + input_nodes: nodes + .filter(node => node.type == 'input') + .map(item => ({ id: item.id, vertical_coordinate: item.position.y, horizontal_coordinate: item.position.x, rsform_id: getBind(item.id) })), - operation_nodes: nodes.filter((node) => node.type == 'custom').map(item => - ({ + operation_nodes: nodes + .filter(node => node.type == 'custom') + .map(item => ({ id: item.id, vertical_coordinate: item.position.y, horizontal_coordinate: item.position.x, @@ -155,34 +183,32 @@ export const SynthesisState = ({ synthesisSchemaID, children }: SynthesisStatePr rsform_id: getBind(item.id), name: 'name', status: 'status' - }) - ), + })), edges: edges.map(item => ({ - id: extractEdgeId(item.id), - decoded_id: item.id, - source_handle: item.sourceHandle, - node_from: item.source, - node_to: item.target - }) - ), + id: extractEdgeId(item.id), + decoded_id: item.id, + source_handle: item.sourceHandle, + node_from: item.source, + node_to: item.target + })), substitutions: substitutions }; postSynthesisGraph({ data: data }); - } + }, [ossSchema, edges, nodes, getBind, substitutions, getNodeParentsRsform]); useEffect(() => { if (ossSchema.schema !== undefined) { const initialNodes = [ - ...ossSchema.schema.input_nodes.map((node) => ({ + ...ossSchema.schema.input_nodes.map(node => ({ id: node.id?.toString() || 'null', data: { label: '123' }, position: { x: node.horizontal_coordinate, y: node.vertical_coordinate }, type: 'input' })), - ...ossSchema.schema.operation_nodes.map((node) => ({ + ...ossSchema.schema.operation_nodes.map(node => ({ id: node.id?.toString() || 'null', data: { label: '123' }, position: { x: node.horizontal_coordinate, y: node.vertical_coordinate }, @@ -190,88 +216,64 @@ export const SynthesisState = ({ synthesisSchemaID, children }: SynthesisStatePr })) ]; - const initialEdges = ossSchema.schema.edges.map((edge) => ({ + const initialEdges = ossSchema.schema.edges.map(edge => ({ id: edge.decoded_id, source: String(edge.node_from), sourceHandle: edge.source_handle, target: String(edge.node_to) })); - //const initialEdges = [{ id: 'reactflow__edge-2a-0', source: '2', target: '0' }, { id: 'reactflow__edge-2b-1', sourceHandle: 'b',source: '2', target: '1' }]; - + // const initialEdges = [ + // { id: 'reactflow__edge-2a-0', source: '2', target: '0' }, + // { id: 'reactflow__edge-2b-1', sourceHandle: 'b', source: '2', target: '1' } + // ]; setNodes(initialNodes); setEdges(initialEdges); setSubstitutions(ossSchema.schema.substitutions); - [...ossSchema.schema.input_nodes, ...ossSchema.schema.operation_nodes].forEach((node) => { + [...ossSchema.schema.input_nodes, ...ossSchema.schema.operation_nodes].forEach(node => { const nodeId = node.id?.toString() || 'null'; const rsformId = node.rsform_id; // Предполагаем, что rsform_id есть в данных нод updateBounds(nodeId, rsformId); }); } - - }, [ossSchema]); - - const getBind = (nodeId: string) => { - const bound = bounds.find((item) => item.nodeId == nodeId); - return bound ? bound.rsformId : null; - - }; - const getBounds = (nodeId: string[]) => { - const parentBounds = bounds.filter((item) => item.nodeId in nodeId); - return parentBounds.map((item) => item.rsformId); - }; - - function getNodeParentsRsform(nodeId: string) { - const parentEdges = edges.filter((edge) => edge.source === nodeId); - const parentNodeIds = parentEdges.map((edge) => edge.target); - return getBounds(parentNodeIds); - } + }, [ossSchema, setEdges, setNodes]); const updateBounds = (nodeId: string, newRsform: number) => { - setBounds((prevItems) => { - const existingItem = prevItems.find((item) => item.nodeId === nodeId); + setBounds(prevItems => { + const existingItem = prevItems.find(item => item.nodeId === nodeId); if (existingItem) { - return prevItems.map((item) => - item.nodeId === nodeId ? { ...item, rsformId: newRsform } : item - ); + return prevItems.map(item => (item.nodeId === nodeId ? { ...item, rsformId: newRsform } : item)); } else { return [...prevItems, { nodeId: nodeId, rsformId: newRsform }]; } }); }; - const onConnect = useCallback( - (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), - [setEdges] - ); + const onConnect = useCallback((params: Connection | Edge) => setEdges(eds => addEdge(params, eds)), [setEdges]); const onNodeDelete = useCallback( (nodeId: string) => { - setNodes((nodes) => nodes.filter((node) => node.id !== nodeId)); - setEdges((edges) => - edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId) - ); + setNodes(nodes => nodes.filter(node => node.id !== nodeId)); + setEdges(edges => edges.filter(edge => edge.source !== nodeId && edge.target !== nodeId)); }, - [] + [setEdges, setNodes] ); - const singleSynthesis = useCallback( - (operationId: number) => { - const data: IRunSynthesis = { - operationId: Number(operationId) - }; - runSingleSynthesis({ data: data }); - }, + const singleSynthesis = useCallback((operationId: number) => { + const data: IRunSynthesis = { + operationId: Number(operationId) + }; + runSingleSynthesis({ data: data }); + }, []); - [] - ); const newLibrarySchema = { id: String(nodes.length > 0 ? 1 + Math.max(...nodes.map(item => Number(item.id))) : 0), type: 'input', position: { x: 250, y: 5 }, data: { - label: 'Node 1', onDelete: onNodeDelete + label: 'Node 1', + onDelete: onNodeDelete } }; @@ -280,7 +282,8 @@ export const SynthesisState = ({ synthesisSchemaID, children }: SynthesisStatePr type: 'custom', position: { x: 350, y: 20 }, data: { - label: 'Node 1', onDelete: onNodeDelete + label: 'Node 1', + onDelete: onNodeDelete } }; @@ -292,54 +295,56 @@ export const SynthesisState = ({ synthesisSchemaID, children }: SynthesisStatePr return; } } - }, [nodes] + }, + [nodes] ); return ( - setShowSynthesisModal(true), - showSelectInput: () => setShowSelectInputModal(true), - selectNode: (nodeId) => selectNode(nodeId), - getSelectedNode: () => selectedNode?.id, - addLibrarySchema: () => { - setNodes([...nodes, newLibrarySchema]); - }, - addSynthesisOperation: () => { - setNodes([...nodes, newOperation]); - }, - setNodes: (nodes: Node[]) => { - setNodes(nodes); - }, - setEdges: (edges: Edge[]) => { - setEdges(edges); - }, - getNodes: () => nodes, - getEdges: () => edges, - onNodesChange: onNodesChange, - onEdgesChange: onEdgesChange, - onConnect: onConnect, - updateBounds: updateBounds, - getNodeParentsRsform: getNodeParentsRsform, - getBind: getBind, - saveGraph: saveGraph, - setSubstitutions: setSubstitutions, - substitutions: substitutions, - updateSubstitution: updateSubstitution, - getSubstitution: getSubstitution - - }}> - {showSynthesisModal ? ( setShowSynthesisModal(false)} - nodeId={selectedNode?.id} - onSynthesis={() => singleSynthesis} - />) : null} - {showSelectInputModal ? ( setShowSelectInputModal(false)} - />) : null} + setShowSynthesisModal(true), + showSelectInput: () => setShowSelectInputModal(true), + selectNode: nodeId => selectNode(nodeId), + getSelectedNode: () => selectedNode?.id, + addLibrarySchema: () => { + setNodes([...nodes, newLibrarySchema]); + }, + addSynthesisOperation: () => { + setNodes([...nodes, newOperation]); + }, + setNodes: (nodes: Node[]) => { + setNodes(nodes); + }, + setEdges: (edges: Edge[]) => { + setEdges(edges); + }, + getNodes: () => nodes, + getEdges: () => edges, + onNodesChange: onNodesChange, + onEdgesChange: onEdgesChange, + onConnect: onConnect, + updateBounds: updateBounds, + getNodeParentsRsform: getNodeParentsRsform, + getBind: getBind, + saveGraph: saveGraph, + setSubstitutions: setSubstitutions, + substitutions: substitutions, + updateSubstitution: updateSubstitution, + getSubstitution: getSubstitution + }} + > + {showSynthesisModal ? ( + setShowSynthesisModal(false)} + nodeId={selectedNode!.id} + onSynthesis={() => singleSynthesis} + /> + ) : null} + {showSelectInputModal ? ( + setShowSelectInputModal(false)} /> + ) : null} {children} - ); -}; \ No newline at end of file +}; diff --git a/rsconcept/frontend/src/pages/OssPage/SynthesisOperation.tsx b/rsconcept/frontend/src/pages/OssPage/SynthesisOperation.tsx index 4cca2467..b9a8b9d3 100644 --- a/rsconcept/frontend/src/pages/OssPage/SynthesisOperation.tsx +++ b/rsconcept/frontend/src/pages/OssPage/SynthesisOperation.tsx @@ -1,9 +1,5 @@ +function SynthesisOperation() { + return
; +} - - -function SynthesisOperation = () => { - return (
- -
- ) -} \ No newline at end of file +export default SynthesisOperation; diff --git a/rsconcept/frontend/src/pages/OssPage/SynthesisSubstitutionsTab.tsx b/rsconcept/frontend/src/pages/OssPage/SynthesisSubstitutionsTab.tsx index 1cb6cc67..eddf2c3c 100644 --- a/rsconcept/frontend/src/pages/OssPage/SynthesisSubstitutionsTab.tsx +++ b/rsconcept/frontend/src/pages/OssPage/SynthesisSubstitutionsTab.tsx @@ -4,6 +4,7 @@ import { ErrorData } from '@/components/info/InfoError.tsx'; import DataLoader from '@/components/wrap/DataLoader.tsx'; import { IRSForm, ISubstitution } from '@/models/rsform.ts'; import { prefixes } from '@/utils/constants.ts'; + import PickSubstitutions from '../../components/select/PickSubstitutions'; interface SynthesisSubstitutionsTabProps { @@ -17,14 +18,14 @@ interface SynthesisSubstitutionsTabProps { } function SynthesisSubstitutionsTab({ - source, - receiver, - error, - substitutions, - setSubstitutions - }: SynthesisSubstitutionsTabProps) { + source, + receiver, + error, + substitutions, + setSubstitutions +}: SynthesisSubstitutionsTabProps) { return ( - +