mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
resolve conflicts
This commit is contained in:
parent
5378272010
commit
2f08cd4803
|
@ -102,6 +102,10 @@ class LibraryItem(Model):
|
|||
auto_now=True
|
||||
)
|
||||
|
||||
#is_hidden: BooleanField = BooleanField(
|
||||
# default=False
|
||||
#)
|
||||
|
||||
class Meta:
|
||||
''' Model metadata. '''
|
||||
verbose_name = 'Схема'
|
||||
|
|
90
rsconcept/backend/apps/rsform/models/Synthesis.py
Normal file
90
rsconcept/backend/apps/rsform/models/Synthesis.py
Normal file
|
@ -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'
|
||||
)
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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:
|
||||
|
|
18
rsconcept/backend/apps/rsform/serializers/synthesis.py
Normal file
18
rsconcept/backend/apps/rsform/serializers/synthesis.py
Normal file
|
@ -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())
|
|
@ -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)),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
139
rsconcept/backend/apps/rsform/views/synthesis.py
Normal file
139
rsconcept/backend/apps/rsform/views/synthesis.py
Normal file
|
@ -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
|
||||
)
|
341
rsconcept/frontend/package-lock.json
generated
341
rsconcept/frontend/package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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: <ManualsPage />
|
||||
},
|
||||
{
|
||||
path: routes.synthesis,
|
||||
element: <SynthesisPage />
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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<IInlineSynthesisData
|
|||
});
|
||||
}
|
||||
|
||||
export function postSynthesis(request: FrontExchange<ISynthesisData, IRSFormData>){
|
||||
AxiosPatch({
|
||||
endpoint: `/api/synthesis/single`,
|
||||
request: request
|
||||
});
|
||||
}
|
||||
|
||||
export function postResolveText(schema: string, request: FrontExchange<ITextRequest, IResolutionData>) {
|
||||
AxiosPost({
|
||||
endpoint: `/api/rsforms/${schema}/resolve`,
|
||||
|
|
|
@ -19,7 +19,8 @@ export const routes = {
|
|||
help: 'manuals',
|
||||
rsforms: 'rsforms',
|
||||
oss: 'oss',
|
||||
icons: 'icons'
|
||||
icons: 'icons',
|
||||
synthesis: 'synthesis'
|
||||
};
|
||||
|
||||
interface SchemaProps {
|
||||
|
|
24
rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx
Normal file
24
rsconcept/frontend/src/components/ui/Synthesis/InputNode.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import {memo, type FC} from 'react';
|
||||
import {Handle, Position, type NodeProps} from '@reactflow/core';
|
||||
|
||||
const InputNode: FC<NodeProps> = ({data, xPos, yPos}) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Bottom}/>
|
||||
<div>
|
||||
<div>
|
||||
Тип: <strong>{data.label}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема из библиотеки:{' '}
|
||||
<strong>
|
||||
RSForm
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InputNode);
|
|
@ -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<NodeProps> = ({data, xPos, yPos}) => {
|
||||
return (
|
||||
<>
|
||||
<Handle type="target" position={Position.Bottom}/>
|
||||
<div>
|
||||
<div>
|
||||
Тип: <strong>{data.label}</strong>
|
||||
</div>
|
||||
<div>
|
||||
Схема:{' '}
|
||||
<strong>
|
||||
{xPos.toFixed(2)},{yPos.toFixed(2)}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="a"
|
||||
style={sourceHandleStyleA}
|
||||
/>
|
||||
<Handle
|
||||
type="source"
|
||||
position={Position.Top}
|
||||
id="b"
|
||||
style={sourceHandleStyleB}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(OperationNode);
|
|
@ -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<MouseEvent>;
|
||||
export type OperationPointerEvent = ThreeEvent<PointerEvent>;
|
||||
|
||||
export default OperationUI;
|
|
@ -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;
|
||||
}
|
|
@ -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 (
|
||||
<div className="Flow" style={{height: 800, width: 1000}}>
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
onNodesChange={onNodesChange}
|
||||
edges={edges}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
nodeTypes={nodeTypes}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Flow;
|
113
rsconcept/frontend/src/dialogs/DlgSynthesis.tsx
Normal file
113
rsconcept/frontend/src/dialogs/DlgSynthesis.tsx
Normal file
|
@ -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<ModalProps, 'hideWindow'> {
|
||||
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<LibraryItemID[]>([]);
|
||||
const [substitutions, setSubstitutions] = useState<ISubstitution[]>([]);
|
||||
|
||||
//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(
|
||||
() => (
|
||||
<TabPanel></TabPanel>
|
||||
), []
|
||||
)
|
||||
|
||||
const substitutesPanel = useMemo(
|
||||
() => (
|
||||
<TabPanel>
|
||||
<SynthesisSubstitutionsTab
|
||||
receiver={sourceLeft.schema}
|
||||
source={sourceRight.schema}
|
||||
substitutions={substitutions}
|
||||
setSubstitutions={setSubstitutions}
|
||||
/>
|
||||
</TabPanel>
|
||||
),
|
||||
[sourceLeft.schema, sourceRight.schema, sourceLeft.loading, sourceRight.loading, selected, substitutions]
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header='Синтез концептуальных скем'
|
||||
hideWindow={hideWindow}
|
||||
submitText='Запуск'
|
||||
className='w-[25rem] px-6'
|
||||
//canSubmit={validated}
|
||||
|
||||
onSubmit={handleSubmit}
|
||||
|
||||
>
|
||||
<Tabs
|
||||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col'
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
>
|
||||
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
|
||||
<TabLabel label='Схема' title='Источник конституент' className='w-[8rem]'/>
|
||||
<TabLabel label='Отождествления' title='Таблица отождествлений' className='w-[8rem]'/>
|
||||
</TabList>
|
||||
{schemaPanel}
|
||||
{substitutesPanel}
|
||||
</Tabs>
|
||||
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgSynthesis;
|
9
rsconcept/frontend/src/models/synthesis.ts
Normal file
9
rsconcept/frontend/src/models/synthesis.ts
Normal file
|
@ -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[];
|
||||
}
|
|
@ -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<IRSFormData>) => void;
|
||||
showSynthesis: () => void;
|
||||
|
||||
}
|
||||
|
||||
const SynthesisContext = createContext<ISynthesisContext | null>(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 <SynthesisState.Provider>');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
export const SynthesisState = ({synthesisSchemaID, children}: SynthesisStateProps) => {
|
||||
const [showSynthesisModal, setShowSynthesisModal] = useState(false)
|
||||
|
||||
const singleSynthesis = useCallback(
|
||||
(data: ISynthesisData, callback?: DataCallback<IRSFormData>) => {
|
||||
postSynthesis({
|
||||
data: data,
|
||||
onSuccess: newData => {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
return (
|
||||
<SynthesisContext.Provider value={{
|
||||
synthesisSchemaID: synthesisSchemaID,
|
||||
singleSynthesis: singleSynthesis,
|
||||
showSynthesis: () => setShowSynthesisModal(true),
|
||||
}}>
|
||||
{showSynthesisModal ? (< DlgSynthesis
|
||||
hideWindow={() => setShowSynthesisModal(false)}
|
||||
schemaLeftID={55}
|
||||
schemaRightID={56}
|
||||
onSynthesis={() => singleSynthesis}/>) : null}
|
||||
{children}
|
||||
</SynthesisContext.Provider>
|
||||
)
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
|
||||
|
||||
|
||||
function SynthesisOperation = () => {
|
||||
return (<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
173
rsconcept/frontend/src/pages/SynthesisPage/SynthesisPage.tsx
Normal file
173
rsconcept/frontend/src/pages/SynthesisPage/SynthesisPage.tsx
Normal file
|
@ -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 (
|
||||
<SynthesisState synthesisSchemaID="1">
|
||||
<SynthesisToolbar/>
|
||||
<SynthesisFlow/>
|
||||
</SynthesisState>
|
||||
)
|
||||
}
|
||||
|
||||
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 &&
|
||||
<div>
|
||||
asd
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
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 ?
|
||||
<div style={modalContainerStyle}>
|
||||
<div style={modalContentStyle}>
|
||||
<Button
|
||||
style={modalCloseButtonStyle}
|
||||
title='Отмена'
|
||||
icon={<AiOutlineClose/>}
|
||||
onClick={onCancel}
|
||||
|
||||
|
||||
/>
|
||||
|
||||
<SynthesisTable/>
|
||||
<Button
|
||||
title='Синтез'
|
||||
icon={<MdCallMerge/>}
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
SynthesisModal.propTypes = {
|
||||
title: PropTypes.string,
|
||||
isOpen: PropTypes.bool,
|
||||
onCancel: PropTypes.func,
|
||||
onSubmit: PropTypes.func
|
||||
}
|
||||
|
||||
SynthesisModal.deafaultProps = {
|
||||
title: "Синтез",
|
||||
isOpen: false,
|
||||
onCancel: () => {
|
||||
},
|
||||
onSubmit: () => {
|
||||
}
|
||||
}
|
||||
|
||||
const SynthesisTable = () => {
|
||||
return (
|
||||
<tbody>
|
||||
|
||||
</tbody>
|
||||
)
|
||||
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
@ -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<React.SetStateAction<ISubstitution[]>>;
|
||||
}
|
||||
|
||||
function SynthesisSubstitutionsTab({
|
||||
source,
|
||||
receiver,
|
||||
error,
|
||||
|
||||
substitutions,
|
||||
setSubstitutions
|
||||
}: SynthesisSubstitutionsTabProps) {
|
||||
return (
|
||||
<DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={false} error={error} hasNoData={!source}>
|
||||
<SubstitutionsPicker
|
||||
items={substitutions}
|
||||
setItems={setSubstitutions}
|
||||
rows={10}
|
||||
prefixID={prefixes.cst_inline_synth_substitutes}
|
||||
schema1={receiver}
|
||||
schema2={source}
|
||||
/>
|
||||
</DataLoader>
|
||||
);
|
||||
}
|
||||
|
||||
export default SynthesisSubstitutionsTab;
|
|
@ -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 (
|
||||
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
|
||||
<Button
|
||||
title='Добавить форму'
|
||||
icon={<IoMdAdd/>}
|
||||
/>
|
||||
<Button
|
||||
title={'Удалить форму'}
|
||||
icon={<IoMdRemove/>}
|
||||
/>
|
||||
<Button
|
||||
icon={<MdCallMerge/>}
|
||||
title='Синтез'
|
||||
onClick={() => controller.showSynthesis()}
|
||||
/>
|
||||
<Button
|
||||
icon={<BsDownload/>}
|
||||
title='Импорт формы'
|
||||
/>
|
||||
<Button
|
||||
title='Экспорт формы'
|
||||
icon={<BsUpload/>}
|
||||
/>
|
||||
</Overlay>
|
||||
)
|
||||
}
|
||||
|
||||
export default SynthesisToolbar;
|
1
rsconcept/frontend/src/pages/SynthesisPage/index.tsx
Normal file
1
rsconcept/frontend/src/pages/SynthesisPage/index.tsx
Normal file
|
@ -0,0 +1 @@
|
|||
export { default } from './SynthesisPage';
|
6
rsconcept/package-lock.json
generated
Normal file
6
rsconcept/package-lock.json
generated
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "rsconcept",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user