From 2f08cd480338e19443627225dd86141c2bd1e45e Mon Sep 17 00:00:00 2001 From: khadanovichba <1415926535w@gmail.com> Date: Wed, 5 Jun 2024 21:34:04 +0300 Subject: [PATCH] resolve conflicts --- .../backend/apps/rsform/models/LibraryItem.py | 4 + .../backend/apps/rsform/models/Synthesis.py | 90 +++++ .../apps/rsform/serializers/__init__.py | 11 + .../apps/rsform/serializers/data_access.py | 10 +- .../apps/rsform/serializers/synthesis.py | 18 + rsconcept/backend/apps/rsform/urls.py | 3 +- rsconcept/backend/apps/rsform/utils.py | 13 + .../backend/apps/rsform/views/__init__.py | 5 + .../backend/apps/rsform/views/synthesis.py | 139 +++++++ rsconcept/frontend/package-lock.json | 341 ++++++++++++++++++ rsconcept/frontend/package.json | 1 + rsconcept/frontend/src/app/Router.tsx | 5 + rsconcept/frontend/src/app/backendAPI.ts | 8 + rsconcept/frontend/src/app/urls.ts | 3 +- .../src/components/ui/Synthesis/InputNode.tsx | 24 ++ .../components/ui/Synthesis/OperationNode.tsx | 42 +++ .../components/ui/Synthesis/OperationUI.tsx | 21 ++ .../components/ui/Synthesis/SynthesisFlow.css | 11 + .../components/ui/Synthesis/SynthesisFlow.tsx | 83 +++++ .../frontend/src/dialogs/DlgSynthesis.tsx | 113 ++++++ rsconcept/frontend/src/models/synthesis.ts | 9 + .../pages/SynthesisPage/SynthesisContext.tsx | 62 ++++ .../SynthesisPage/SynthesisOperation.tsx | 9 + .../src/pages/SynthesisPage/SynthesisPage.tsx | 173 +++++++++ .../SynthesisSubstitutionsTab.tsx | 42 +++ .../pages/SynthesisPage/SynthesisToolbar.tsx | 39 ++ .../src/pages/SynthesisPage/index.tsx | 1 + rsconcept/package-lock.json | 6 + 28 files changed, 1279 insertions(+), 7 deletions(-) create mode 100644 rsconcept/backend/apps/rsform/models/Synthesis.py create mode 100644 rsconcept/backend/apps/rsform/serializers/synthesis.py create mode 100644 rsconcept/backend/apps/rsform/views/synthesis.py create mode 100644 rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx create mode 100644 rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx create mode 100644 rsconcept/frontend/src/components/ui/Synthesis/OperationUI.tsx create mode 100644 rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.css create mode 100644 rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx create mode 100644 rsconcept/frontend/src/dialogs/DlgSynthesis.tsx create mode 100644 rsconcept/frontend/src/models/synthesis.ts create mode 100644 rsconcept/frontend/src/pages/SynthesisPage/SynthesisContext.tsx create mode 100644 rsconcept/frontend/src/pages/SynthesisPage/SynthesisOperation.tsx create mode 100644 rsconcept/frontend/src/pages/SynthesisPage/SynthesisPage.tsx create mode 100644 rsconcept/frontend/src/pages/SynthesisPage/SynthesisSubstitutionsTab.tsx create mode 100644 rsconcept/frontend/src/pages/SynthesisPage/SynthesisToolbar.tsx create mode 100644 rsconcept/frontend/src/pages/SynthesisPage/index.tsx create mode 100644 rsconcept/package-lock.json diff --git a/rsconcept/backend/apps/rsform/models/LibraryItem.py b/rsconcept/backend/apps/rsform/models/LibraryItem.py index 56813796..f42b1b2c 100644 --- a/rsconcept/backend/apps/rsform/models/LibraryItem.py +++ b/rsconcept/backend/apps/rsform/models/LibraryItem.py @@ -102,6 +102,10 @@ class LibraryItem(Model): auto_now=True ) + #is_hidden: BooleanField = BooleanField( + # default=False + #) + class Meta: ''' Model metadata. ''' verbose_name = 'Схема' diff --git a/rsconcept/backend/apps/rsform/models/Synthesis.py b/rsconcept/backend/apps/rsform/models/Synthesis.py new file mode 100644 index 00000000..8eebe041 --- /dev/null +++ b/rsconcept/backend/apps/rsform/models/Synthesis.py @@ -0,0 +1,90 @@ +from django.db.models import ( + CASCADE, SET_NULL, ForeignKey, Model, PositiveIntegerField, QuerySet, + TextChoices, TextField, BooleanField, CharField, DateTimeField, JSONField, IntegerField +) + +from rsconcept.backend.apps.rsform.models.api_RSForm import RSForm, LibraryItem, LibraryItemType +import rsconcept.backend.apps.rsform.messages as messages + + +class GraphStatus(TextChoices): + DRAFT = 'Draft', + IN_PROGRESS = 'In progress', + COMPLETED = 'Completed', + FAILED = 'Failed' + + +class OperationStatus(TextChoices): + DRAFT = 'Draft', + # IN_PROGRESS = 'In progress', + COMPLETED = 'Completed', + WARNING = 'Warning', + FAILED = 'Failed' + + +class SynthesisNodeType: + LIBRARY = 'Library', + SYNTHESIZED = 'Synthesized' + + +class ConceptOperation(Model): + name: CharField = CharField( + verbose_name='Название', + max_length=20 + ) + + node_type: CharField = CharField( + verbose_name='Тип звена операции слияния', + max_length=20, + choices=SynthesisNodeType + ) + + status: CharField( + verbose_name='Статус операции слияния', + max_length=20, + choices=OperationStatus + ) + + vertical_coordinate = IntegerField( + verbose_name='Вертикальная координата звена', + ) + + horizontal_coordinate = IntegerField( + verbose_name='Горизонтальная координата звена', + ) + + rsform = ForeignKey( + verbose_name='Схема', + to='rsform.LibraryItem' + ) + + operation_type = CharField() + + +class SynthesisGraph(Model): + name: CharField = CharField( + verbose_name='Название', + max_length=20 + ) + status: CharField = CharField( + verbose_name='Статус схемы слияния', + max_length=20, + choices=GraphStatus, + ) + + +class SynthesisEdge(Model): + synthesis_graph: ForeignKey( + verbose_name='Схема синтеза', + to=SynthesisGraph, + ) + + node_from: ForeignKey( + verbose_name='Звено-предок', + to='rsform.LibraryItem' + ) + + node_to: ForeignKey( + verbose_name='Звено-наследник', + to='rsform.LibraryItem' + ) diff --git a/rsconcept/backend/apps/rsform/serializers/__init__.py b/rsconcept/backend/apps/rsform/serializers/__init__.py index 12c2a4f5..5c0aaca3 100644 --- a/rsconcept/backend/apps/rsform/serializers/__init__.py +++ b/rsconcept/backend/apps/rsform/serializers/__init__.py @@ -38,3 +38,14 @@ from .schema_typing import ( NewVersionResponse, ResultTextResponse ) + +from .io_pyconcept import PyConceptAdapter +from .io_files import ( + FileSerializer, + RSFormUploadSerializer, + RSFormTRSSerializer +) + +from .synthesis import ( + SynthesisGraphSerializer +) diff --git a/rsconcept/backend/apps/rsform/serializers/data_access.py b/rsconcept/backend/apps/rsform/serializers/data_access.py index 2861eff7..c8f40439 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 new file mode 100644 index 00000000..d6c99639 --- /dev/null +++ b/rsconcept/backend/apps/rsform/serializers/synthesis.py @@ -0,0 +1,18 @@ +from rest_framework import serializers +from .data_access import InlineSynthesisSerializer + +class SynthesisEdgeSerializer(serializers.Serializer): + schema_from = serializers.IntegerField() + schema_to = serializers.IntegerField() + + +class SynthesisNodeSerializer(serializers.Serializer): + pk = serializers.IntegerField() + vertical_coordinate = serializers.IntegerField() + horizontal_coordinate = serializers.IntegerField() + + +class SynthesisGraphSerializer(serializers.Serializer): + pk = serializers.IntegerField() + user = serializers.CharField() + edges_list = serializers.ListField(child=SynthesisEdgeSerializer()) diff --git a/rsconcept/backend/apps/rsform/urls.py b/rsconcept/backend/apps/rsform/urls.py index d4df9cb0..d6ebda25 100644 --- a/rsconcept/backend/apps/rsform/urls.py +++ b/rsconcept/backend/apps/rsform/urls.py @@ -30,6 +30,7 @@ urlpatterns = [ path('cctext/inflect', views.inflect), path('cctext/generate-lexeme', views.generate_lexeme), path('cctext/parse', views.parse_text), - + path('synthesis/single', views.run_synthesis_view), + path('synthesis/graph', views.run_sythesis_graph_view), path('', include(library_router.urls)), ] diff --git a/rsconcept/backend/apps/rsform/utils.py b/rsconcept/backend/apps/rsform/utils.py index 5b3e1d80..ed55302a 100644 --- a/rsconcept/backend/apps/rsform/utils.py +++ b/rsconcept/backend/apps/rsform/utils.py @@ -1,4 +1,5 @@ ''' Utility functions ''' +import copy import json import re from io import BytesIO @@ -66,3 +67,15 @@ def filename_for_schema(alias: str) -> str: # are not supported by some browsers return 'Schema.trs' return alias + '.trs' + + +def clone_rsform(rsform): + rsform_copy = copy.deepcopy(rsform) + rsform_copy.item.pk = None + # rsform_copy.item.owner = "System" + 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.item.save() + return rsform_copy diff --git a/rsconcept/backend/apps/rsform/views/__init__.py b/rsconcept/backend/apps/rsform/views/__init__.py index 61c0a1d0..bd23a57b 100644 --- a/rsconcept/backend/apps/rsform/views/__init__.py +++ b/rsconcept/backend/apps/rsform/views/__init__.py @@ -6,3 +6,8 @@ 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 +) diff --git a/rsconcept/backend/apps/rsform/views/synthesis.py b/rsconcept/backend/apps/rsform/views/synthesis.py new file mode 100644 index 00000000..73a79e38 --- /dev/null +++ b/rsconcept/backend/apps/rsform/views/synthesis.py @@ -0,0 +1,139 @@ +import copy + +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 ..models.Constituenta import Constituenta +from ..models.LibraryItem import LibraryItem +from ..models.api_RSForm import RSForm +from ..serializers import RSFormSerializer, SynthesisGraphSerializer, InlineSynthesisSerializer +from typing import cast +from django.contrib.auth.models import User + +from ..utils import clone_rsform + + +@extend_schema( + summary='Run synthesis operation', + tags=['Synthesis'], + request=InlineSynthesisSerializer, + responses={status.HTTP_200_OK: RSFormSerializer}, + auth=None +) +@api_view(['POST']) +def run_synthesis_view(request: Request): + serializer = InlineSynthesisSerializer( + data=request.data, + context={'user': request.user} + ) + serializer.is_valid(raise_exception=True) + return run_synthesis(serializer=serializer) + + +@extend_schema( + summary='Run synthesis graph', + tags=['Synthesis'], + request=InlineSynthesisSerializer, + responses={status.HTTP_200_OK: RSFormSerializer}, + auth=None +) +@api_view(['POST']) +def run_sythesis_graph_view(request: Request): + serializer = SynthesisGraphSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + for atomic_synthesis in serializer.validated_data: + run_synthesis(atomic_synthesis) + + +def run_synthesis(serializer: InlineSynthesisSerializer): + left_schema = RSForm(serializer.validated_data['source']) + right_schema = RSForm(serializer.validated_data['receiver']) + constituents = cast(list[Constituenta], serializer.validated_data['items']) + + left_schema_copy = clone_rsform(left_schema) + copied_constituents = left_schema_copy.item.constituenta_set + used_constiuents = set() + + for substitution in serializer.validated_data['substitutions']: + original = cast(Constituenta, substitution['original']) + replacement = cast(Constituenta, substitution['substitution']) + if original in constituents: + index = next(i for (i, cst) in enumerate(constituents) if cst == original) + original = copied_constituents[index] + else: + index = next(i for (i, cst) in enumerate(constituents) if cst == replacement) + replacement = copied_constituents[index] + left_schema_copy.substitute(original, replacement, substitution['transfer_term']) + if substitution['transfer_term']: + used_constiuents.add(replacement.pk) + unused_constitunents = {cst for cst in right_schema.item.constituenta_set() if cst.pk not in used_constiuents} + left_schema_copy.insert_copy(list(unused_constitunents)) + + left_schema.restore_order() + return Response( + status=status.HTTP_200_OK, + data=RSFormSerializer(left_schema_copy.item).data + ) + + return + right_rsform_copy = clone_rsform(right_schema) + + 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) + + 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() + + 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_rsform_copy.item.delete() + + serializer = RSFormParseSerializer(cast(LibraryItem, left_schema_copy.item)) + + # 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 + ) diff --git a/rsconcept/frontend/package-lock.json b/rsconcept/frontend/package-lock.json index 63fd6d1d..9836107d 100644 --- a/rsconcept/frontend/package-lock.json +++ b/rsconcept/frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "dependencies": { "@lezer/lr": "^1.4.1", + "@reactflow/core": "^11.8.3", "@tanstack/react-table": "^8.17.3", "@uiw/codemirror-themes": "^4.22.1", "@uiw/react-codemirror": "^4.22.1", @@ -2827,6 +2828,53 @@ } } }, + "node_modules/@reactflow/core": { + "version": "11.11.3", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.3.tgz", + "integrity": "sha512-+adHdUa7fJSEM93fWfjQwyWXeI92a1eLKwWbIstoCakHpL8UjzwhEh6sn+mN2h/59MlVI7Ehr1iGTt3MsfcIFA==", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core/node_modules/zustand": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.2.tgz", + "integrity": "sha512-2cN1tPkDVkwCy5ickKrI7vijSjPksFRfqS6237NzT0vqSsztTNnQdHw9mmN7uBdk3gceVXU0a+21jFzFzAc9+g==", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", @@ -2947,12 +2995,239 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/draco3d": { "version": "1.4.10", "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -4204,6 +4479,11 @@ "dev": true, "license": "MIT" }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==" + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -4570,6 +4850,26 @@ "node": ">=12" } }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-force-3d": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.5.tgz", @@ -4647,6 +4947,14 @@ "node": ">=12" } }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-time": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", @@ -4680,6 +4988,39 @@ "node": ">=12" } }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/debounce": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", diff --git a/rsconcept/frontend/package.json b/rsconcept/frontend/package.json index fffb494a..6b640fc6 100644 --- a/rsconcept/frontend/package.json +++ b/rsconcept/frontend/package.json @@ -17,6 +17,7 @@ "@uiw/codemirror-themes": "^4.22.1", "@uiw/react-codemirror": "^4.22.1", "axios": "^1.7.2", + "@reactflow/core": "^11.8.3", "clsx": "^2.1.1", "framer-motion": "^10.18.0", "js-file-download": "^0.4.12", diff --git a/rsconcept/frontend/src/app/Router.tsx b/rsconcept/frontend/src/app/Router.tsx index 621bc3ee..6398de00 100644 --- a/rsconcept/frontend/src/app/Router.tsx +++ b/rsconcept/frontend/src/app/Router.tsx @@ -16,6 +16,7 @@ import UserProfilePage from '@/pages/UserProfilePage'; import ApplicationLayout from './ApplicationLayout'; import { routes } from './urls'; +import SynthesisPage from "@/pages/SynthesisPage"; export const Router = createBrowserRouter([ { @@ -70,6 +71,10 @@ export const Router = createBrowserRouter([ { path: routes.manuals, element: + }, + { + path: routes.synthesis, + element: } ] } diff --git a/rsconcept/frontend/src/app/backendAPI.ts b/rsconcept/frontend/src/app/backendAPI.ts index 77163451..017a1d62 100644 --- a/rsconcept/frontend/src/app/backendAPI.ts +++ b/rsconcept/frontend/src/app/backendAPI.ts @@ -51,6 +51,7 @@ import { IUserUpdatePassword } from '@/models/user'; import { buildConstants } from '@/utils/buildConstants'; +import {ISynthesisData} from "@/models/synthesis.ts"; const defaultOptions = { xsrfCookieName: 'csrftoken', @@ -444,6 +445,13 @@ export function patchInlineSynthesis(request: FrontExchange){ + AxiosPatch({ + endpoint: `/api/synthesis/single`, + request: request + }); +} + export function postResolveText(schema: string, request: FrontExchange) { AxiosPost({ endpoint: `/api/rsforms/${schema}/resolve`, diff --git a/rsconcept/frontend/src/app/urls.ts b/rsconcept/frontend/src/app/urls.ts index d7ff7432..733c0bae 100644 --- a/rsconcept/frontend/src/app/urls.ts +++ b/rsconcept/frontend/src/app/urls.ts @@ -19,7 +19,8 @@ export const routes = { help: 'manuals', rsforms: 'rsforms', oss: 'oss', - icons: 'icons' + icons: 'icons', + synthesis: 'synthesis' }; interface SchemaProps { diff --git a/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx b/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx new file mode 100644 index 00000000..00aca772 --- /dev/null +++ b/rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx @@ -0,0 +1,24 @@ +import {memo, type FC} from 'react'; +import {Handle, Position, type NodeProps} from '@reactflow/core'; + +const InputNode: FC = ({data, xPos, yPos}) => { + return ( + <> + + + + Тип: {data.label} + + + Схема из библиотеки:{' '} + + RSForm + + + + + > + ); +}; + +export default memo(InputNode); diff --git a/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx b/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx new file mode 100644 index 00000000..38c249c5 --- /dev/null +++ b/rsconcept/frontend/src/components/ui/Synthesis/OperationNode.tsx @@ -0,0 +1,42 @@ +import {memo, type FC, type CSSProperties} from 'react'; +import {Handle, Position, type NodeProps} from '@reactflow/core'; + +const sourceHandleStyleA: CSSProperties = {left: 50}; +const sourceHandleStyleB: CSSProperties = { + right: 50, + left: 'auto', +}; + +const OperationNode: FC = ({data, xPos, yPos}) => { + return ( + <> + + + + Тип: {data.label} + + + Схема:{' '} + + {xPos.toFixed(2)},{yPos.toFixed(2)} + + + + + + + > + ); +}; + +export default memo(OperationNode); diff --git a/rsconcept/frontend/src/components/ui/Synthesis/OperationUI.tsx b/rsconcept/frontend/src/components/ui/Synthesis/OperationUI.tsx new file mode 100644 index 00000000..71eb33ec --- /dev/null +++ b/rsconcept/frontend/src/components/ui/Synthesis/OperationUI.tsx @@ -0,0 +1,21 @@ +// Reexporting reaOperation types to wrap in 'use client'. +'use client'; + +import { GraphCanvas as OperationUI } from 'reagraph'; + +export { + type GraphEdge, + type GraphNode, + type GraphCanvasRef, + Sphere, + useSelection, + type CollapseProps +} from 'reagraph'; +export { type LayoutTypes as OperationLayout } from 'reagraph'; + +import { ThreeEvent } from '@react-three/fiber'; + +export type OperationMouseEvent = ThreeEvent; +export type OperationPointerEvent = ThreeEvent; + +export default OperationUI; diff --git a/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.css b/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.css new file mode 100644 index 00000000..38f84a05 --- /dev/null +++ b/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.css @@ -0,0 +1,11 @@ +.Flow { + flex-grow: 1; + font-size: 12px; +} + +.react-flow__node-custom { + border: 1px solid #555; + padding: 10px; + width: 200px; + border-radius: 5px; +} diff --git a/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx b/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx new file mode 100644 index 00000000..a62e8aec --- /dev/null +++ b/rsconcept/frontend/src/components/ui/Synthesis/SynthesisFlow.tsx @@ -0,0 +1,83 @@ +import {useCallback} from 'react'; +import { + ReactFlow, + addEdge, + useNodesState, + useEdgesState, + type Connection, + type Edge, + type Node, +} 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 DlgSynthesis from "@/dialogs/DlgSynthesis.tsx"; + +const nodeTypes = { + custom: OperationNode, + input: InputNode, +}; + +const initialNodes: Node[] = [ + { + id: '1', + type: 'input', + data: {label: 'Node 1'}, + position: {x: 250, y: 5}, + }, + { + type: 'input', + id: '2', + data: {label: 'Node 2'}, + position: {x: 100, y: 100}, + }, + { + id: '3', + data: {label: 'Node 3'}, + position: {x: 400, y: 100}, + type: 'custom', + + }, + { + id: '4', + data: {label: 'Node 4'}, + position: {x: 400, y: 200}, + type: 'custom', + }, +]; + +const initialEdges: Edge[] = [ + //{ id: 'e1-2', source: '1', target: '2', animated: true }, + //{ id: 'e1-3', source: '1', target: '3', animated: true }, +]; + +function Flow() { + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + const onConnect = useCallback( + (params: Connection | Edge) => setEdges((eds) => addEdge(params, eds)), + [setEdges] + ); + + + return ( + + + + ); +} + +export default Flow; diff --git a/rsconcept/frontend/src/dialogs/DlgSynthesis.tsx b/rsconcept/frontend/src/dialogs/DlgSynthesis.tsx new file mode 100644 index 00000000..ee93f32c --- /dev/null +++ b/rsconcept/frontend/src/dialogs/DlgSynthesis.tsx @@ -0,0 +1,113 @@ +'use client'; + +import {useEffect, useMemo, useState} from 'react'; +import {toast} from 'react-toastify'; + +import Checkbox from '@/components/ui/Checkbox'; +import FileInput from '@/components/ui/FileInput'; +import Modal, {ModalProps} from '@/components/ui/Modal'; +import {useRSForm} from '@/context/RSFormContext'; +import {IRSForm, IRSFormUploadData, ISubstitution} from '@/models/rsform'; +import {EXTEOR_TRS_FILE} from '@/utils/constants'; +import {TabList, TabPanel, Tabs} from "react-tabs"; +import clsx from "clsx"; +import TabLabel from "@/components/ui/TabLabel.tsx"; +import SubstitutionsTab from "@/dialogs/DlgInlineSynthesis/SubstitutionsTab.tsx"; +import useRSFormDetails from "@/hooks/useRSFormDetails.ts"; +import {LibraryItemID} from "@/models/library.ts"; +import {ISynthesisData} from "@/models/synthesis.ts"; +import {TabID} from "@/dialogs/DlgInlineSynthesis/DlgInlineSynthesis.tsx"; +import SynthesisSubstitutionsTab from "@/pages/SynthesisPage/SynthesisSubstitutionsTab.tsx"; + +interface DlgCreateSynthesisProps extends Pick { + schemaLeftID: number; + schemaRightID: number; + onSynthesis: (data: ISynthesisData) => void; + +} + +export enum SynthesisTabID { + SCHEMA = 0, + SUBSTITUTIONS = 1 +} + +function DlgSynthesis({hideWindow, schemaLeftID, schemaRightID, onSynthesis}: DlgCreateSynthesisProps) { + const [activeTab, setActiveTab] = useState(SynthesisTabID.SCHEMA); + const sourceLeft = useRSFormDetails({target: schemaLeftID ? String(schemaLeftID) : undefined}); + const sourceRight = useRSFormDetails({target: schemaRightID ? String(schemaRightID) : undefined}); + const [selected, setSelected] = useState([]); + const [substitutions, setSubstitutions] = useState([]); + + //const validated = useMemo(() => !!source.schema && selected.length > 0, [source.schema, selected]); + + function handleSubmit() { + if (!sourceLeft.schema || !sourceRight.schema) { + return; + } + const data: ISynthesisData = { + sourceLeft: schemaLeftID, + sourceRight: schemaRightID, + result: 1, + substitutions: substitutions.map(item => ({ + original: item.deleteRight ? item.rightCst.id : item.leftCst.id, + substitution: item.deleteRight ? item.leftCst.id : item.rightCst.id, + transfer_term: !item.deleteRight && item.takeLeftTerm + })) + }; + onSynthesis(data); + } + + useEffect(() => { + setSelected(sourceLeft.schema && sourceRight.schema ? sourceLeft.schema?.items.map(cst => cst.id) : []); + }, [sourceLeft.schema, sourceRight.schema]); + + const schemaPanel = useMemo( + () => ( + + ), [] + ) + + const substitutesPanel = useMemo( + () => ( + + + + ), + [sourceLeft.schema, sourceRight.schema, sourceLeft.loading, sourceRight.loading, selected, substitutions] + ); + + return ( + + + + + + + {schemaPanel} + {substitutesPanel} + + + + ); +} + +export default DlgSynthesis; diff --git a/rsconcept/frontend/src/models/synthesis.ts b/rsconcept/frontend/src/models/synthesis.ts new file mode 100644 index 00000000..73f863f0 --- /dev/null +++ b/rsconcept/frontend/src/models/synthesis.ts @@ -0,0 +1,9 @@ +import {LibraryItemID} from "@/models/library.ts"; +import {ICstSubstitute} from "@/models/rsform.ts"; + +export interface ISynthesisData { + result: LibraryItemID; + sourceLeft: LibraryItemID; + sourceRight: LibraryItemID; + substitutions: ICstSubstitute[]; +} \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/SynthesisPage/SynthesisContext.tsx b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisContext.tsx new file mode 100644 index 00000000..e5016636 --- /dev/null +++ b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisContext.tsx @@ -0,0 +1,62 @@ +import {IRSForm, IRSFormData} from "@/models/rsform.ts"; +import {DataCallback, postSynthesis} from "@/app/backendAPI.ts"; +import {ISynthesisData} from "@/models/synthesis.ts"; +import {createContext, useCallback, useContext, useState} from "react"; +import {useLibrary} from "@/context/LibraryContext.tsx"; +import {useAuth} from "@/context/AuthContext.tsx"; +import useRSFormDetails from "@/hooks/useRSFormDetails.ts"; +import DlgSynthesis from "@/dialogs/DlgSynthesis.tsx"; + +interface ISynthesisContext { + synthesisSchemaID: string; + singleSynthesis: (data: ISynthesisData, callback?: DataCallback) => void; + showSynthesis: () => void; + +} + +const SynthesisContext = createContext(null); + +interface SynthesisStateProps { + synthesisSchemaID: string; + children: React.ReactNode; +} + +export const useSynthesis = () => { + const context = useContext(SynthesisContext); + if (context === null) { + throw new Error('useSynthesis has to be used within '); + } + return context; +} + +export const SynthesisState = ({synthesisSchemaID, children}: SynthesisStateProps) => { + const [showSynthesisModal, setShowSynthesisModal] = useState(false) + + const singleSynthesis = useCallback( + (data: ISynthesisData, callback?: DataCallback) => { + postSynthesis({ + data: data, + onSuccess: newData => { + } + }); + }, + + [] + ); + + + return ( + setShowSynthesisModal(true), + }}> + {showSynthesisModal ? (< DlgSynthesis + hideWindow={() => setShowSynthesisModal(false)} + schemaLeftID={55} + schemaRightID={56} + onSynthesis={() => singleSynthesis}/>) : null} + {children} + + ) +}; \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/SynthesisPage/SynthesisOperation.tsx b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisOperation.tsx new file mode 100644 index 00000000..4cca2467 --- /dev/null +++ b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisOperation.tsx @@ -0,0 +1,9 @@ + + + +function SynthesisOperation = () => { + return ( + + + ) +} \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/SynthesisPage/SynthesisPage.tsx b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisPage.tsx new file mode 100644 index 00000000..3a1b2058 --- /dev/null +++ b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisPage.tsx @@ -0,0 +1,173 @@ +//import {useConceptTheme} from '@/context/ThemeContext'; +/*import {useCallback, useState} from "react"; +import {GraphNode} from "@/models/Graph.ts"; +import {list} from "postcss";*/ +import Overlay from '@/components/ui/Overlay'; +import Button from '@/components/ui/Button'; +import {MdCallMerge} from "react-icons/md"; +import {IoMdAdd, IoMdRemove} from 'react-icons/io'; +import {BsDownload, BsUpload} from 'react-icons/bs'; +//import {AiOutlineClose} from 'react-icons/ai'; +import {useCallback, useState} from 'react'; +//import PropTypes from "prop-types"; +//import {useConceptNavigation} from "@/context/NavigationContext.tsx"; +import DlgSynthesis from "@/dialogs/DlgSynthesis.tsx"; +import SynthesisFlow from "@/components/ui/Synthesis/SynthesisFlow.tsx"; +import {IRSFormData} from "@/models/rsform.ts"; +import {toast} from "react-toastify"; +import {useRSForm} from "@/context/RSFormContext.tsx"; +import {DataCallback, postSynthesis} from "@/app/backendAPI.ts"; +import {ISynthesisData} from "@/models/synthesis.ts"; +import useRSFormDetails from "@/hooks/useRSFormDetails.ts"; +import {useLibrary} from "@/context/LibraryContext.tsx"; +import {SynthesisState, useSynthesis} from "@/pages/SynthesisPage/SynthesisContext.tsx"; +import SynthesisToolbar from "@/pages/SynthesisPage/SynthesisToolbar.tsx"; +import {useParams} from "react-router-dom"; + +export const SynthesisPage = () => { + const params = useParams(); + + //const router = useConceptNavigation(); + //const [sampleData, setSampleData] = useState([[1, 2, 3], [4, 5, 6]]) + + return ( + + + + + ) +} + +export default SynthesisPage; + + +//onCancel={setShowSynthesisModal(false)} +/* +const modalContainerStyle: React.CSSProperties = { + position: 'fixed', + top: 0, + left: 0, + width: '100%', + height: '100%', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', +}; + +const modalContentStyle: React.CSSProperties = { + backgroundColor: '#fff', + padding: '20px', + borderRadius: '8px', + boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)', +}; + +const modalCloseButtonStyle: React.CSSProperties = { + outline: 'none', + cursor: 'pointer', + background: 'transparent', + color: '#C62828', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' +}; + + +const SynthesisPage = () => { + +}; +*/ + +/* + +const SynthesisGraphBody = () => { + const [synthesisGraphs, setSynthesisGraphs] = useState([]); + + return (synthesisGraphs && + + asd + + ) +} + +function SynthesisGraph({ + nodes, + edges, + onSelect, + onEdit + }) { + const handleNodeClick = useCallback( + (node: GraphNode) => { + + }, [onSelect, onEdit] + ) + + const createNewNode = () => { + + } + + const createNewEdge = () => { + + }; +} +*/ +/*export class SynthesisNode { + private id: number; + private edges: list[number] + + constructor(id) { + this.id = id + } +}*/ + +/*const SynthesisModal = ({title, isOpen, onCancel, onSubmit}) => { + return (showSynthesisModal ? + + + } + onClick={onCancel} + + + /> + + + } + onClick={onSubmit} + /> + + + + ) +} +SynthesisModal.propTypes = { + title: PropTypes.string, + isOpen: PropTypes.bool, + onCancel: PropTypes.func, + onSubmit: PropTypes.func +} + +SynthesisModal.deafaultProps = { + title: "Синтез", + isOpen: false, + onCancel: () => { + }, + onSubmit: () => { + } +} + +const SynthesisTable = () => { + return ( + + + + ) + +} + */ + + diff --git a/rsconcept/frontend/src/pages/SynthesisPage/SynthesisSubstitutionsTab.tsx b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisSubstitutionsTab.tsx new file mode 100644 index 00000000..ed70f81f --- /dev/null +++ b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisSubstitutionsTab.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { ErrorData } from '@/components/info/InfoError'; +import DataLoader from '@/components/wrap/DataLoader'; +import { ConstituentaID, IRSForm, ISubstitution } from '@/models/rsform'; +import { prefixes } from '@/utils/constants'; + +import SubstitutionsPicker from '../../components/select/SubstitutionsPicker'; + +interface SynthesisSubstitutionsTabProps { + receiver?: IRSForm; + source?: IRSForm; + + error?: ErrorData; + + substitutions: ISubstitution[]; + setSubstitutions: React.Dispatch>; +} + +function SynthesisSubstitutionsTab({ + source, + receiver, + error, + + substitutions, + setSubstitutions +}: SynthesisSubstitutionsTabProps) { + return ( + + + + ); +} + +export default SynthesisSubstitutionsTab; diff --git a/rsconcept/frontend/src/pages/SynthesisPage/SynthesisToolbar.tsx b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisToolbar.tsx new file mode 100644 index 00000000..a3f3c466 --- /dev/null +++ b/rsconcept/frontend/src/pages/SynthesisPage/SynthesisToolbar.tsx @@ -0,0 +1,39 @@ +import {useSynthesis} from "@/pages/SynthesisPage/SynthesisContext.tsx"; +import Button from "@/components/ui/Button.tsx"; +import {IoMdAdd, IoMdRemove} from "react-icons/io"; +import {MdCallMerge} from "react-icons/md"; +import {BsDownload, BsUpload} from "react-icons/bs"; +import Overlay from "@/components/ui/Overlay.tsx"; + + +function SynthesisToolbar() { + const controller = useSynthesis() + + return ( + + } + /> + } + /> + } + title='Синтез' + onClick={() => controller.showSynthesis()} + /> + } + title='Импорт формы' + /> + } + /> + + ) +} + +export default SynthesisToolbar; \ No newline at end of file diff --git a/rsconcept/frontend/src/pages/SynthesisPage/index.tsx b/rsconcept/frontend/src/pages/SynthesisPage/index.tsx new file mode 100644 index 00000000..144f103b --- /dev/null +++ b/rsconcept/frontend/src/pages/SynthesisPage/index.tsx @@ -0,0 +1 @@ +export { default } from './SynthesisPage'; diff --git a/rsconcept/package-lock.json b/rsconcept/package-lock.json new file mode 100644 index 00000000..2f317e40 --- /dev/null +++ b/rsconcept/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "rsconcept", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}