F: Implement different dialogs for operation creation

This commit is contained in:
Ivan 2025-07-10 16:32:31 +03:00
parent 4f47c736ce
commit 073cd5412f
29 changed files with 1125 additions and 544 deletions

View File

@ -5,9 +5,11 @@ from .data_access import (
ArgumentSerializer,
BlockSerializer,
CreateBlockSerializer,
CreateOperationSerializer,
CreateSchemaSerializer,
CreateSynthesisSerializer,
DeleteBlockSerializer,
DeleteOperationSerializer,
ImportSchemaSerializer,
MoveItemsSerializer,
OperationSchemaSerializer,
OperationSerializer,

View File

@ -2,8 +2,16 @@
from rest_framework import serializers
class PositionSerializer(serializers.Serializer):
''' Serializer: Position data. '''
x = serializers.FloatField()
y = serializers.FloatField()
width = serializers.FloatField()
height = serializers.FloatField()
class NodeSerializer(serializers.Serializer):
''' Block position. '''
''' Oss node serializer. '''
nodeID = serializers.CharField()
x = serializers.FloatField()
y = serializers.FloatField()

View File

@ -13,7 +13,7 @@ from apps.rsform.serializers import SubstitutionSerializerBase
from shared import messages as msg
from ..models import Argument, Block, Inheritance, Operation, OperationSchema, OperationType
from .basics import NodeSerializer, SubstitutionExSerializer
from .basics import NodeSerializer, PositionSerializer, SubstitutionExSerializer
class OperationSerializer(serializers.ModelSerializer):
@ -58,10 +58,7 @@ class CreateBlockSerializer(serializers.Serializer):
child=NodeSerializer()
)
item_data = BlockCreateData()
width = serializers.FloatField()
height = serializers.FloatField()
position_x = serializers.FloatField()
position_y = serializers.FloatField()
position = PositionSerializer()
children_operations = PKField(many=True, queryset=Operation.objects.all().only('oss_id'))
children_blocks = PKField(many=True, queryset=Block.objects.all().only('oss_id'))
@ -193,30 +190,21 @@ class MoveItemsSerializer(serializers.Serializer):
return attrs
class CreateOperationSerializer(serializers.Serializer):
''' Serializer: Operation creation. '''
class CreateOperationData(serializers.ModelSerializer):
class CreateOperationData(serializers.ModelSerializer):
''' Serializer: Operation creation data. '''
alias = serializers.CharField()
operation_type = serializers.ChoiceField(OperationType.choices)
class Meta:
''' serializer metadata. '''
model = Operation
fields = \
'alias', 'operation_type', 'title', \
'description', 'result', 'parent'
fields = 'alias', 'title', 'description', 'parent'
layout = serializers.ListField(
child=NodeSerializer()
)
class CreateSchemaSerializer(serializers.Serializer):
''' Serializer: Schema creation for new operation. '''
layout = serializers.ListField(child=NodeSerializer())
item_data = CreateOperationData()
width = serializers.FloatField()
height = serializers.FloatField()
position_x = serializers.FloatField()
position_y = serializers.FloatField()
create_schema = serializers.BooleanField(default=False, required=False)
arguments = PKField(many=True, queryset=Operation.objects.all().only('pk'), required=False)
position = PositionSerializer()
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
@ -225,14 +213,82 @@ class CreateOperationSerializer(serializers.Serializer):
raise serializers.ValidationError({
'parent': msg.parentNotInOSS()
})
if 'arguments' not in attrs:
return attrs
class ImportSchemaSerializer(serializers.Serializer):
''' Serializer: Import schema to new operation. '''
layout = serializers.ListField(child=NodeSerializer())
item_data = CreateOperationData()
position = PositionSerializer()
source = PKField(
many=False,
queryset=LibraryItem.objects.filter(item_type=LibraryItemType.RSFORM)
) # type: ignore
clone_source = serializers.BooleanField()
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
parent = attrs['item_data'].get('parent')
if parent is not None and parent.oss_id != oss.pk:
raise serializers.ValidationError({
'parent': msg.parentNotInOSS()
})
return attrs
class CreateSynthesisSerializer(serializers.Serializer):
''' Serializer: Synthesis operation creation. '''
layout = serializers.ListField(child=NodeSerializer())
item_data = CreateOperationData()
position = PositionSerializer()
arguments = PKField(
many=True,
queryset=Operation.objects.all().only('pk')
)
substitutions = serializers.ListField(
child=SubstitutionSerializerBase(),
)
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
parent = attrs['item_data'].get('parent')
if parent is not None and parent.oss_id != oss.pk:
raise serializers.ValidationError({
'parent': msg.parentNotInOSS()
})
for operation in attrs['arguments']:
if operation.oss_id != oss.pk:
raise serializers.ValidationError({
'arguments': msg.operationNotInOSS()
})
schemas = [arg.result_id for arg in attrs['arguments'] if arg.result is not None]
substitutions = attrs['substitutions']
to_delete = {x['original'].pk for x in substitutions}
deleted = set()
for item in substitutions:
original_cst = cast(Constituenta, item['original'])
substitution_cst = cast(Constituenta, item['substitution'])
if original_cst.schema_id not in schemas:
raise serializers.ValidationError({
f'{original_cst.pk}': msg.constituentaNotFromOperation()
})
if substitution_cst.schema_id not in schemas:
raise serializers.ValidationError({
f'{substitution_cst.pk}': msg.constituentaNotFromOperation()
})
if original_cst.pk in deleted or substitution_cst.pk in to_delete:
raise serializers.ValidationError({
f'{original_cst.pk}': msg.substituteDouble(original_cst.alias)
})
if original_cst.schema_id == substitution_cst.schema_id:
raise serializers.ValidationError({
'alias': msg.substituteTrivial(original_cst.alias)
})
deleted.add(original_cst.pk)
return attrs

View File

@ -3,18 +3,18 @@ from rest_framework import serializers
from apps.library.serializers import LibraryItemSerializer
from .data_access import BlockSerializer, OperationSchemaSerializer, OperationSerializer
from .data_access import OperationSchemaSerializer
class OperationCreatedResponse(serializers.Serializer):
''' Serializer: Create operation response. '''
new_operation = OperationSerializer()
new_operation = serializers.IntegerField()
oss = OperationSchemaSerializer()
class BlockCreatedResponse(serializers.Serializer):
''' Serializer: Create block response. '''
new_block = BlockSerializer()
new_block = serializers.IntegerField()
oss = OperationSchemaSerializer()

View File

@ -73,10 +73,12 @@ class TestOssBlocks(EndpointTester):
'description': 'Тест кириллицы',
},
'layout': self.layout_data,
'position_x': 1337,
'position_y': 1337,
'position': {
'x': 1337,
'y': 1337,
'width': 0.42,
'height': 0.42,
'height': 0.42
},
'children_operations': [],
'children_blocks': []
}
@ -86,14 +88,11 @@ class TestOssBlocks(EndpointTester):
self.assertEqual(len(response.data['oss']['blocks']), 3)
new_block = response.data['new_block']
layout = response.data['oss']['layout']
item = [item for item in layout if item['nodeID'] == 'b' + str(new_block['id'])][0]
self.assertEqual(new_block['title'], data['item_data']['title'])
self.assertEqual(new_block['description'], data['item_data']['description'])
self.assertEqual(new_block['parent'], None)
self.assertEqual(item['x'], data['position_x'])
self.assertEqual(item['y'], data['position_y'])
self.assertEqual(item['width'], data['width'])
self.assertEqual(item['height'], data['height'])
block_node = [item for item in layout if item['nodeID'] == 'b' + str(new_block)][0]
self.assertEqual(block_node['x'], data['position']['x'])
self.assertEqual(block_node['y'], data['position']['y'])
self.assertEqual(block_node['width'], data['position']['width'])
self.assertEqual(block_node['height'], data['position']['height'])
self.operation1.refresh_from_db()
self.executeForbidden(data=data, item=self.unowned_id)
@ -111,10 +110,12 @@ class TestOssBlocks(EndpointTester):
'parent': self.invalid_id
},
'layout': self.layout_data,
'position_x': 1337,
'position_y': 1337,
'position': {
'x': 1337,
'y': 1337,
'width': 0.42,
'height': 0.42,
'height': 0.42
},
'children_operations': [],
'children_blocks': []
}
@ -126,7 +127,8 @@ class TestOssBlocks(EndpointTester):
data['item_data']['parent'] = self.block1.pk
response = self.executeCreated(data=data)
new_block = response.data['new_block']
self.assertEqual(new_block['parent'], self.block1.pk)
block_data = next((block for block in response.data['oss']['blocks'] if block['id'] == new_block), None)
self.assertEqual(block_data['parent'], self.block1.pk)
@decl_endpoint('/api/oss/{item}/create-block', method='post')
@ -138,10 +140,12 @@ class TestOssBlocks(EndpointTester):
'description': 'Тест кириллицы',
},
'layout': self.layout_data,
'position_x': 1337,
'position_y': 1337,
'position': {
'x': 1337,
'y': 1337,
'width': 0.42,
'height': 0.42,
'height': 0.42
},
'children_operations': [self.invalid_id],
'children_blocks': []
}
@ -162,8 +166,8 @@ class TestOssBlocks(EndpointTester):
new_block = response.data['new_block']
self.operation1.refresh_from_db()
self.block1.refresh_from_db()
self.assertEqual(self.operation1.parent.pk, new_block['id'])
self.assertEqual(self.block1.parent.pk, new_block['id'])
self.assertEqual(self.operation1.parent.pk, new_block)
self.assertEqual(self.block1.parent.pk, new_block)
@decl_endpoint('/api/oss/{item}/create-block', method='post')
@ -176,10 +180,12 @@ class TestOssBlocks(EndpointTester):
'parent': self.block2.pk
},
'layout': self.layout_data,
'position_x': 1337,
'position_y': 1337,
'position': {
'x': 1337,
'y': 1337,
'width': 0.42,
'height': 0.42,
'height': 0.42
},
'children_operations': [],
'children_blocks': [self.block1.pk]
}

View File

@ -70,9 +70,10 @@ class TestOssOperations(EndpointTester):
}])
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation(self):
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
def test_create_schema(self):
self.populateData()
Editor.add(self.owned.model.pk, self.user2.pk)
self.executeBadData(item=self.owned_id)
data = {
@ -80,47 +81,50 @@ class TestOssOperations(EndpointTester):
'alias': 'Test3',
'title': 'Test title',
'description': 'Тест кириллицы',
'parent': None
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1,
'position': {
'x': 1,
'y': 1,
'width': 500,
'height': 50
}
self.executeBadData(data=data)
data['item_data']['operation_type'] = 'invalid'
self.executeBadData(data=data)
data['item_data']['operation_type'] = OperationType.INPUT
}
self.executeNotFound(data=data, item=self.invalid_id)
response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(len(response.data['oss']['operations']), 4)
new_operation = response.data['new_operation']
new_operation_id = response.data['new_operation']
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
layout = response.data['oss']['layout']
item = [item for item in layout if item['nodeID'] == 'o' + str(new_operation['id'])][0]
operation_node = [item for item in layout if item['nodeID'] == 'o' + str(new_operation_id)][0]
schema = LibraryItem.objects.get(pk=new_operation['result'])
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type'])
self.assertEqual(new_operation['operation_type'], OperationType.INPUT)
self.assertEqual(new_operation['title'], data['item_data']['title'])
self.assertEqual(new_operation['description'], data['item_data']['description'])
self.assertEqual(new_operation['result'], None)
self.assertEqual(new_operation['parent'], None)
self.assertEqual(item['x'], data['position_x'])
self.assertEqual(item['y'], data['position_y'])
self.assertEqual(item['width'], data['width'])
self.assertEqual(item['height'], data['height'])
self.operation1.refresh_from_db()
self.assertNotEqual(new_operation['result'], None)
self.assertEqual(operation_node['x'], data['position']['x'])
self.assertEqual(operation_node['y'], data['position']['y'])
self.assertEqual(operation_node['width'], data['position']['width'])
self.assertEqual(operation_node['height'], data['position']['height'])
self.assertEqual(schema.alias, data['item_data']['alias'])
self.assertEqual(schema.title, data['item_data']['title'])
self.assertEqual(schema.description, data['item_data']['description'])
self.assertEqual(schema.visible, False)
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
self.assertEqual(schema.location, self.owned.model.location)
self.assertIn(self.user2, schema.getQ_editors())
self.executeForbidden(data=data, item=self.unowned_id)
self.toggle_admin(True)
self.executeCreated(data=data, item=self.unowned_id)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_parent(self):
@decl_endpoint('/api/oss/{item}/create-schema', method='post')
def test_create_schema_parent(self):
self.populateData()
data = {
'item_data': {
@ -128,14 +132,15 @@ class TestOssOperations(EndpointTester):
'alias': 'Test3',
'title': 'Test title',
'description': '',
'operation_type': OperationType.INPUT
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1,
'position': {
'x': 1,
'y': 1,
'width': 500,
'height': 50
}
}
self.executeBadData(data=data, item=self.owned_id)
@ -147,90 +152,40 @@ class TestOssOperations(EndpointTester):
block_owned = self.owned.create_block(title='TestBlock2')
data['item_data']['parent'] = block_owned.id
response = self.executeCreated(data=data, item=self.owned_id)
new_operation_id = response.data['new_operation']
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
self.assertEqual(len(response.data['oss']['operations']), 4)
new_operation = response.data['new_operation']
self.assertEqual(new_operation['parent'], block_owned.id)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_arguments(self):
@decl_endpoint('/api/oss/{item}/create-synthesis', method='post')
def test_create_synthesis(self):
self.populateData()
data = {
'item_data': {
'alias': 'Test4',
'operation_type': OperationType.SYNTHESIS
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1,
'width': 500,
'height': 50,
'arguments': [self.operation1.pk, self.operation3.pk]
}
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
arguments = self.owned.arguments()
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation1))
self.assertTrue(arguments.filter(operation__id=new_operation['id'], argument=self.operation3))
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_result(self):
self.populateData()
self.operation1.result = None
self.operation1.save()
data = {
'item_data': {
'alias': 'Test4',
'operation_type': OperationType.INPUT,
'result': self.ks1.model.pk
},
'layout': self.layout_data,
'position_x': 1,
'position_y': 1,
'width': 500,
'height': 50
}
response = self.executeCreated(data=data, item=self.owned_id)
new_operation = response.data['new_operation']
self.assertEqual(new_operation['result'], self.ks1.model.pk)
@decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation_schema(self):
self.populateData()
Editor.add(self.owned.model.pk, self.user2.pk)
data = {
'item_data': {
'alias': 'Test4',
'title': 'Test title',
'description': 'Comment',
'operation_type': OperationType.INPUT,
'result': self.ks1.model.pk
'description': '',
'parent': None
},
'create_schema': True,
'layout': self.layout_data,
'position_x': 1,
'position_y': 1,
'position': {
'x': 1,
'y': 1,
'width': 500,
'height': 50
},
'arguments': [self.operation1.pk, self.operation3.pk],
'substitutions': []
}
self.executeBadData(data=data, item=self.owned_id)
data['item_data']['result'] = None
response = self.executeCreated(data=data, item=self.owned_id)
self.owned.refresh_from_db()
new_operation = response.data['new_operation']
schema = LibraryItem.objects.get(pk=new_operation['result'])
self.assertEqual(schema.alias, data['item_data']['alias'])
self.assertEqual(schema.title, data['item_data']['title'])
self.assertEqual(schema.description, data['item_data']['description'])
self.assertEqual(schema.visible, False)
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
self.assertEqual(schema.location, self.owned.model.location)
self.assertIn(self.user2, schema.getQ_editors())
new_operation_id = response.data['new_operation']
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
arguments = self.owned.arguments()
self.assertTrue(arguments.filter(operation__id=new_operation_id, argument=self.operation1))
self.assertTrue(arguments.filter(operation__id=new_operation_id, argument=self.operation3))
self.assertNotEqual(new_operation['result'], None)
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
@ -497,3 +452,141 @@ class TestOssOperations(EndpointTester):
self.assertEqual(len(items), 1)
self.assertEqual(items[0].alias, 'X1')
self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved)
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
def test_import_schema(self):
self.populateData()
target_ks = RSForm.create(
alias='KS_Target',
title='Target',
owner=self.user
)
data = {
'item_data': {
'alias': 'ImportedAlias',
'title': 'Imported Title',
'description': 'Imported Description',
'parent': None
},
'layout': self.layout_data,
'position': {
'x': 10,
'y': 20,
'width': 300,
'height': 60
},
'source': target_ks.model.pk,
'clone_source': False
}
response = self.executeCreated(data=data, item=self.owned_id)
new_operation_id = response.data['new_operation']
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
layout = response.data['oss']['layout']
operation_node = [item for item in layout if item['nodeID'] == 'o' + str(new_operation_id)][0]
schema = LibraryItem.objects.get(pk=new_operation['result'])
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
self.assertEqual(new_operation['title'], data['item_data']['title'])
self.assertEqual(new_operation['description'], data['item_data']['description'])
self.assertEqual(new_operation['operation_type'], OperationType.INPUT)
self.assertEqual(schema.pk, target_ks.model.pk) # Not a clone
self.assertEqual(operation_node['x'], data['position']['x'])
self.assertEqual(operation_node['y'], data['position']['y'])
self.assertEqual(operation_node['width'], data['position']['width'])
self.assertEqual(operation_node['height'], data['position']['height'])
self.assertEqual(schema.visible, target_ks.model.visible)
self.assertEqual(schema.access_policy, target_ks.model.access_policy)
self.assertEqual(schema.location, target_ks.model.location)
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
def test_import_schema_clone(self):
self.populateData()
# Use ks2 as the source RSForm
data = {
'item_data': {
'alias': 'ClonedAlias',
'title': 'Cloned Title',
'description': 'Cloned Description',
'parent': None
},
'layout': self.layout_data,
'position': {
'x': 42,
'y': 1337,
'width': 400,
'height': 80
},
'source': self.ks2.model.pk,
'clone_source': True
}
response = self.executeCreated(data=data, item=self.owned_id)
new_operation_id = response.data['new_operation']
new_operation = next(op for op in response.data['oss']['operations'] if op['id'] == new_operation_id)
layout = response.data['oss']['layout']
operation_node = [item for item in layout if item['nodeID'] == 'o' + str(new_operation_id)][0]
schema = LibraryItem.objects.get(pk=new_operation['result'])
self.assertEqual(new_operation['alias'], data['item_data']['alias'])
self.assertEqual(new_operation['title'], data['item_data']['title'])
self.assertEqual(new_operation['description'], data['item_data']['description'])
self.assertEqual(new_operation['operation_type'], OperationType.INPUT)
self.assertNotEqual(schema.pk, self.ks2.model.pk) # Should be a clone
self.assertEqual(schema.alias, data['item_data']['alias'])
self.assertEqual(schema.title, data['item_data']['title'])
self.assertEqual(schema.description, data['item_data']['description'])
self.assertEqual(operation_node['x'], data['position']['x'])
self.assertEqual(operation_node['y'], data['position']['y'])
self.assertEqual(operation_node['width'], data['position']['width'])
self.assertEqual(operation_node['height'], data['position']['height'])
self.assertEqual(schema.visible, False)
self.assertEqual(schema.access_policy, self.owned.model.access_policy)
self.assertEqual(schema.location, self.owned.model.location)
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
def test_import_schema_bad_data(self):
self.populateData()
# Missing source
data = {
'item_data': {
'alias': 'Bad',
'title': 'Bad',
'description': 'Bad',
'parent': None
},
'layout': self.layout_data,
'position': {
'x': 0, 'y': 0, 'width': 1, 'height': 1
},
# 'source' missing
'clone_source': False
}
self.executeBadData(data=data, item=self.owned_id)
# Invalid source
data['source'] = self.invalid_id
self.executeBadData(data=data, item=self.owned_id)
# Invalid OSS
data['source'] = self.ks1.model.pk
self.executeNotFound(data=data, item=self.invalid_id)
@decl_endpoint('/api/oss/{item}/import-schema', method='post')
def test_import_schema_permissions(self):
self.populateData()
data = {
'item_data': {
'alias': 'PermTest',
'title': 'PermTest',
'description': 'PermTest',
'parent': None
},
'layout': self.layout_data,
'position': {
'x': 5, 'y': 5, 'width': 10, 'height': 10
},
'source': self.ks1.model.pk,
'clone_source': False
}
# Not an editor
self.logout()
self.executeForbidden(data=data, item=self.owned_id)
# As admin
self.login()
self.toggle_admin(True)
self.executeCreated(data=data, item=self.owned_id)

View File

@ -1,8 +1,8 @@
''' Endpoints for OSS. '''
from copy import deepcopy
from typing import Optional, cast
from django.db import transaction
from django.db.models import Q
from django.http import HttpResponse
from drf_spectacular.utils import extend_schema, extend_schema_view
from rest_framework import generics, serializers
@ -23,6 +23,28 @@ from .. import models as m
from .. import serializers as s
def _create_clone(prototype: LibraryItem, operation: m.Operation, oss: LibraryItem) -> LibraryItem:
''' Create clone of prototype schema for operation. '''
prototype_schema = RSForm(prototype)
clone = deepcopy(prototype)
clone.pk = None
clone.owner = oss.owner
clone.title = operation.title
clone.alias = operation.alias
clone.description = operation.description
clone.visible = False
clone.read_only = False
clone.access_policy = oss.access_policy
clone.location = oss.location
clone.save()
for cst in prototype_schema.constituents():
cst_copy = deepcopy(cst)
cst_copy.pk = None
cst_copy.schema = clone
cst_copy.save()
return clone
@extend_schema(tags=['OSS'])
@extend_schema_view()
class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.RetrieveAPIView):
@ -41,7 +63,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'update_block',
'delete_block',
'move_items',
'create_operation',
'create_schema',
'import_schema',
'create_synthesis',
'update_operation',
'delete_operation',
'create_input',
@ -116,16 +140,17 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
position = serializer.validated_data['position']
children_blocks: list[m.Block] = serializer.validated_data['children_blocks']
children_operations: list[m.Operation] = serializer.validated_data['children_operations']
with transaction.atomic():
new_block = oss.create_block(**serializer.validated_data['item_data'])
layout.append({
'nodeID': 'b' + str(new_block.pk),
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'],
'height': serializer.validated_data['height'],
'x': position['x'],
'y': position['y'],
'width': position['width'],
'height': position['height'],
})
oss.update_layout(layout)
if len(children_blocks) > 0:
@ -140,7 +165,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
return Response(
status=c.HTTP_201_CREATED,
data={
'new_block': s.BlockSerializer(new_block).data,
'new_block': new_block.pk,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@ -251,9 +276,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
)
@extend_schema(
summary='create operation',
summary='create empty conceptual schema',
tags=['OSS'],
request=s.CreateOperationSerializer(),
request=s.CreateSchemaSerializer(),
responses={
c.HTTP_201_CREATED: s.OperationCreatedResponse,
c.HTTP_400_BAD_REQUEST: None,
@ -261,10 +286,10 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['post'], url_path='create-operation')
def create_operation(self, request: Request, pk) -> HttpResponse:
''' Create Operation. '''
serializer = s.CreateOperationSerializer(
@action(detail=True, methods=['post'], url_path='create-schema')
def create_schema(self, request: Request, pk) -> HttpResponse:
''' Create schema. '''
serializer = s.CreateSchemaSerializer(
data=request.data,
context={'oss': self.get_object()}
)
@ -272,43 +297,124 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
position = serializer.validated_data['position']
data = serializer.validated_data['item_data']
data['operation_type'] = m.OperationType.INPUT
with transaction.atomic():
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout.append({
'nodeID': 'o' + str(new_operation.pk),
'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'],
'height': serializer.validated_data['height']
'x': position['x'],
'y': position['y'],
'width': position['width'],
'height': position['height']
})
oss.update_layout(layout)
schema = new_operation.result
if schema is not None:
connected_operations = \
m.Operation.objects \
.filter(Q(result=schema) & ~Q(pk=new_operation.pk)) \
.only('operation_type', 'oss_id')
for operation in connected_operations:
if operation.operation_type != m.OperationType.INPUT:
raise serializers.ValidationError({
'item_data': msg.operationResultFromAnotherOSS()
})
if operation.oss_id == new_operation.oss_id:
raise serializers.ValidationError({
'item_data': msg.operationInputAlreadyConnected()
})
if new_operation.operation_type == m.OperationType.INPUT and serializer.validated_data['create_schema']:
oss.create_input(new_operation)
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
oss.set_arguments(
target=new_operation.pk,
arguments=serializer.validated_data['arguments']
)
return Response(
status=c.HTTP_201_CREATED,
data={
'new_operation': s.OperationSerializer(new_operation).data,
'new_operation': new_operation.pk,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@extend_schema(
summary='import conceptual schema to new OSS operation',
tags=['OSS'],
request=s.ImportSchemaSerializer(),
responses={
c.HTTP_201_CREATED: s.OperationCreatedResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['post'], url_path='import-schema')
def import_schema(self, request: Request, pk) -> HttpResponse:
''' Create operation with existing schema. '''
serializer = s.ImportSchemaSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
position = serializer.validated_data['position']
data = serializer.validated_data['item_data']
data['operation_type'] = m.OperationType.INPUT
if not serializer.validated_data['clone_source']:
data['result'] = serializer.validated_data['source']
with transaction.atomic():
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout.append({
'nodeID': 'o' + str(new_operation.pk),
'x': position['x'],
'y': position['y'],
'width': position['width'],
'height': position['height']
})
oss.update_layout(layout)
if serializer.validated_data['clone_source']:
prototype: LibraryItem = serializer.validated_data['source']
new_operation.result = _create_clone(prototype, new_operation, oss.model)
new_operation.save(update_fields=["result"])
return Response(
status=c.HTTP_201_CREATED,
data={
'new_operation': new_operation.pk,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)
@extend_schema(
summary='create synthesis operation',
tags=['OSS'],
request=s.CreateSynthesisSerializer(),
responses={
c.HTTP_201_CREATED: s.OperationCreatedResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=True, methods=['post'], url_path='create-synthesis')
def create_synthesis(self, request: Request, pk) -> HttpResponse:
''' Create Synthesis operation from arguments. '''
serializer = s.CreateSynthesisSerializer(
data=request.data,
context={'oss': self.get_object()}
)
serializer.is_valid(raise_exception=True)
oss = m.OperationSchema(self.get_object())
layout = serializer.validated_data['layout']
position = serializer.validated_data['position']
data = serializer.validated_data['item_data']
data['operation_type'] = m.OperationType.SYNTHESIS
with transaction.atomic():
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout.append({
'nodeID': 'o' + str(new_operation.pk),
'x': position['x'],
'y': position['y'],
'width': position['width'],
'height': position['height']
})
oss.set_arguments(new_operation.pk, serializer.validated_data['arguments'])
oss.set_substitutions(new_operation.pk, serializer.validated_data['substitutions'])
oss.execute_operation(new_operation)
oss.update_layout(layout)
return Response(
status=c.HTTP_201_CREATED,
data={
'new_operation': new_operation.pk,
'oss': s.OperationSchemaSerializer(oss.model).data
}
)

View File

@ -20,9 +20,9 @@ const DlgCloneLibraryItem = React.lazy(() =>
const DlgCreateCst = React.lazy(() =>
import('@/features/rsform/dialogs/dlg-create-cst').then(module => ({ default: module.DlgCreateCst }))
);
const DlgCreateOperation = React.lazy(() =>
import('@/features/oss/dialogs/dlg-create-operation').then(module => ({
default: module.DlgCreateOperation
const DlgCreateSynthesis = React.lazy(() =>
import('@/features/oss/dialogs/dlg-create-synthesis').then(module => ({
default: module.DlgCreateSynthesis
}))
);
const DlgCreateVersion = React.lazy(() =>
@ -134,6 +134,12 @@ const DlgEditCst = React.lazy(() =>
const DlgShowTermGraph = React.lazy(() =>
import('@/features/oss/dialogs/dlg-show-term-graph').then(module => ({ default: module.DlgShowTermGraph }))
);
const DlgCreateSchema = React.lazy(() =>
import('@/features/oss/dialogs/dlg-create-schema').then(module => ({ default: module.DlgCreateSchema }))
);
const DlgImportSchema = React.lazy(() =>
import('@/features/oss/dialogs/dlg-import-schema').then(module => ({ default: module.DlgImportSchema }))
);
export const GlobalDialogs = () => {
const active = useDialogsStore(state => state.active);
@ -146,8 +152,8 @@ export const GlobalDialogs = () => {
return <DlgCstTemplate />;
case DialogType.CREATE_CONSTITUENTA:
return <DlgCreateCst />;
case DialogType.CREATE_OPERATION:
return <DlgCreateOperation />;
case DialogType.CREATE_SYNTHESIS:
return <DlgCreateSynthesis />;
case DialogType.CREATE_BLOCK:
return <DlgCreateBlock />;
case DialogType.EDIT_BLOCK:
@ -198,5 +204,9 @@ export const GlobalDialogs = () => {
return <DlgEditCst />;
case DialogType.SHOW_TERM_GRAPH:
return <DlgShowTermGraph />;
case DialogType.CREATE_SCHEMA:
return <DlgCreateSchema />;
case DialogType.IMPORT_SCHEMA:
return <DlgImportSchema />;
}
};

View File

@ -8,9 +8,11 @@ import {
type IBlockCreatedResponse,
type IConstituentaReference,
type ICreateBlockDTO,
type ICreateOperationDTO,
type ICreateSchemaDTO,
type ICreateSynthesisDTO,
type IDeleteBlockDTO,
type IDeleteOperationDTO,
type IImportSchemaDTO,
type IInputCreatedResponse,
type IMoveItemsDTO,
type IOperationCreatedResponse,
@ -83,13 +85,40 @@ export const ossApi = {
}
}),
createOperation: ({ itemID, data }: { itemID: number; data: ICreateOperationDTO }) =>
axiosPost<ICreateOperationDTO, IOperationCreatedResponse>({
createSchema: ({ itemID, data }: { itemID: number; data: ICreateSchemaDTO }) =>
axiosPost<ICreateSchemaDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse,
endpoint: `/api/oss/${itemID}/create-operation`,
endpoint: `/api/oss/${itemID}/create-schema`,
request: {
data: data,
successMessage: response => infoMsg.newOperation(response.new_operation.alias)
successMessage: response => {
const alias = response.oss.operations.find(op => op.id === response.new_operation)?.alias;
return infoMsg.newOperation(alias ?? 'ОШИБКА');
}
}
}),
createSynthesis: ({ itemID, data }: { itemID: number; data: ICreateSynthesisDTO }) =>
axiosPost<ICreateSynthesisDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse,
endpoint: `/api/oss/${itemID}/create-synthesis`,
request: {
data: data,
successMessage: response => {
const alias = response.oss.operations.find(op => op.id === response.new_operation)?.alias;
return infoMsg.newOperation(alias ?? 'ОШИБКА');
}
}
}),
importSchema: ({ itemID, data }: { itemID: number; data: IImportSchemaDTO }) =>
axiosPost<IImportSchemaDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse,
endpoint: `/api/oss/${itemID}/import-schema`,
request: {
data: data,
successMessage: response => {
const alias = response.oss.operations.find(op => op.id === response.new_operation)?.alias;
return infoMsg.newOperation(alias ?? 'ОШИБКА');
}
}
}),
updateOperation: ({ itemID, data }: { itemID: number; data: IUpdateOperationDTO }) =>

View File

@ -43,7 +43,9 @@ export type IDeleteBlockDTO = z.infer<typeof schemaDeleteBlock>;
export type IMoveItemsDTO = z.infer<typeof schemaMoveItems>;
/** Represents {@link IOperation} data, used in Create action. */
export type ICreateOperationDTO = z.infer<typeof schemaCreateOperation>;
export type ICreateSchemaDTO = z.infer<typeof schemaCreateSchema>;
export type ICreateSynthesisDTO = z.infer<typeof schemaCreateSynthesis>;
export type IImportSchemaDTO = z.infer<typeof schemaImportSchema>;
/** Represents data response when creating {@link IOperation}. */
export type IOperationCreatedResponse = z.infer<typeof schemaOperationCreatedResponse>;
@ -90,6 +92,13 @@ export const schemaOperation = z.strictObject({
result: z.number().nullable()
});
export const schemaOperationData = schemaOperation.pick({
alias: true,
title: true,
description: true,
parent: true
});
export const schemaBlock = z.strictObject({
id: z.number(),
oss: z.number(),
@ -98,6 +107,13 @@ export const schemaBlock = z.strictObject({
parent: z.number().nullable()
});
export const schemaPosition = z.strictObject({
x: z.number(),
y: z.number(),
width: z.number(),
height: z.number()
});
export const schemaCstSubstituteInfo = schemaSubstituteConstituents.extend({
operation: z.number(),
original_alias: z.string(),
@ -106,12 +122,8 @@ export const schemaCstSubstituteInfo = schemaSubstituteConstituents.extend({
substitution_term: z.string()
});
export const schemaNodePosition = z.strictObject({
nodeID: z.string(),
x: z.number(),
y: z.number(),
width: z.number(),
height: z.number()
export const schemaNodePosition = schemaPosition.extend({
nodeID: z.string()
});
export const schemaOssLayout = z.array(schemaNodePosition);
@ -137,16 +149,13 @@ export const schemaCreateBlock = z.strictObject({
description: z.string(),
parent: z.number().nullable()
}),
position_x: z.number(),
position_y: z.number(),
width: z.number(),
height: z.number(),
position: schemaPosition,
children_operations: z.array(z.number()),
children_blocks: z.array(z.number())
});
export const schemaBlockCreatedResponse = z.strictObject({
new_block: schemaBlock,
new_block: z.number(),
oss: schemaOperationSchema
});
@ -165,26 +174,35 @@ export const schemaDeleteBlock = z.strictObject({
layout: schemaOssLayout
});
export const schemaCreateOperation = z.strictObject({
export const schemaCreateSchema = z.strictObject({
layout: schemaOssLayout,
item_data: z.strictObject({
alias: z.string().nonempty(),
operation_type: schemaOperationType,
title: z.string(),
description: z.string(),
parent: z.number().nullable(),
result: z.number().nullable()
}),
position_x: z.number(),
position_y: z.number(),
width: z.number(),
height: z.number(),
item_data: schemaOperationData,
position: schemaPosition
});
export const schemaCreateSynthesis = z.strictObject({
layout: schemaOssLayout,
item_data: schemaOperationData,
position: schemaPosition,
arguments: z.array(z.number()),
create_schema: z.boolean()
substitutions: z.array(schemaSubstituteConstituents)
});
export const schemaImportSchema = z.strictObject({
layout: schemaOssLayout,
item_data: schemaOperationData,
position: z.strictObject({
x: z.number(),
y: z.number(),
width: z.number(),
height: z.number()
}),
source: z.number(),
clone_source: z.boolean()
});
export const schemaOperationCreatedResponse = z.strictObject({
new_operation: schemaOperation,
new_operation: z.number(),
oss: schemaOperationSchema
});

View File

@ -5,14 +5,14 @@ import { useUpdateTimestamp } from '@/features/library/backend/use-update-timest
import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type ICreateOperationDTO } from './types';
import { type ICreateSchemaDTO } from './types';
export const useCreateOperation = () => {
export const useCreateSchema = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-operation'],
mutationFn: ossApi.createOperation,
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-schema'],
mutationFn: ossApi.createSchema,
onSuccess: response => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss);
updateTimestamp(response.oss.id);
@ -20,6 +20,6 @@ export const useCreateOperation = () => {
onError: () => client.invalidateQueries()
});
return {
createOperation: (data: { itemID: number; data: ICreateOperationDTO }) => mutation.mutateAsync(data)
createSchema: (data: { itemID: number; data: ICreateSchemaDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type ICreateSynthesisDTO } from './types';
export const useCreateSynthesis = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-synthesis'],
mutationFn: ossApi.createSynthesis,
onSuccess: response => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss);
updateTimestamp(response.oss.id);
},
onError: () => client.invalidateQueries()
});
return {
createSynthesis: (data: { itemID: number; data: ICreateSynthesisDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -0,0 +1,25 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
import { KEYS } from '@/backend/configuration';
import { ossApi } from './api';
import { type IImportSchemaDTO } from './types';
export const useImportSchema = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'import-schema'],
mutationFn: ossApi.importSchema,
onSuccess: response => {
client.setQueryData(ossApi.getOssQueryOptions({ itemID: response.oss.id }).queryKey, response.oss);
updateTimestamp(response.oss.id);
},
onError: () => client.invalidateQueries()
});
return {
importSchema: (data: { itemID: number; data: IImportSchemaDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -49,10 +49,12 @@ export function DlgCreateBlock() {
description: '',
parent: initialParent
},
position_x: defaultX,
position_y: defaultY,
position: {
x: defaultX,
y: defaultY,
width: BLOCK_NODE_MIN_WIDTH,
height: BLOCK_NODE_MIN_HEIGHT,
height: BLOCK_NODE_MIN_HEIGHT
},
children_blocks: initialChildren.filter(item => item.nodeType === NodeType.BLOCK).map(item => item.id),
children_operations: initialChildren.filter(item => item.nodeType === NodeType.OPERATION).map(item => item.id),
layout: manager.layout
@ -66,13 +68,9 @@ export function DlgCreateBlock() {
const isValid = !!title && !manager.oss.blocks.some(block => block.title === title);
function onSubmit(data: ICreateBlockDTO) {
const rectangle = manager.newBlockPosition(data);
data.position_x = rectangle.x;
data.position_y = rectangle.y;
data.width = rectangle.width;
data.height = rectangle.height;
data.position = manager.newBlockPosition(data);
data.layout = manager.layout;
void createBlock({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_block.id));
void createBlock({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_block));
}
return (

View File

@ -1,129 +0,0 @@
'use client';
import { useState } from 'react';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { HelpTopic } from '@/features/help';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO, OperationType, schemaCreateOperation } from '../../backend/types';
import { useCreateOperation } from '../../backend/use-create-operation';
import { describeOperationType, labelOperationType } from '../../labels';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../../models/oss-layout-api';
import { TabInputOperation } from './tab-input-operation';
import { TabSynthesisOperation } from './tab-synthesis-operation';
export interface DlgCreateOperationProps {
manager: LayoutManager;
initialParent: number | null;
initialInputs: number[];
defaultX: number;
defaultY: number;
onCreate?: (newID: number) => void;
}
export const TabID = {
INPUT: 0,
SYNTHESIS: 1
} as const;
export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgCreateOperation() {
const { createOperation } = useCreateOperation();
const { manager, initialInputs, initialParent, onCreate, defaultX, defaultY } = useDialogsStore(
state => state.props as DlgCreateOperationProps
);
const methods = useForm<ICreateOperationDTO>({
resolver: zodResolver(schemaCreateOperation),
defaultValues: {
item_data: {
operation_type: initialInputs.length === 0 ? OperationType.INPUT : OperationType.SYNTHESIS,
alias: '',
title: '',
description: '',
result: null,
parent: initialParent
},
position_x: defaultX,
position_y: defaultY,
arguments: initialInputs,
width: OPERATION_NODE_WIDTH,
height: OPERATION_NODE_HEIGHT,
create_schema: false,
layout: manager.layout
},
mode: 'onChange'
});
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
const [activeTab, setActiveTab] = useState(initialInputs.length === 0 ? TabID.INPUT : TabID.SYNTHESIS);
const isValid = !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
function onSubmit(data: ICreateOperationDTO) {
const target = manager.newOperationPosition(data);
data.position_x = target.x;
data.position_y = target.y;
data.layout = manager.layout;
void createOperation({ itemID: manager.oss.id, data: data }).then(response =>
onCreate?.(response.new_operation.id)
);
}
function handleSelectTab(newTab: TabID, last: TabID) {
if (last === newTab) {
return;
}
if (newTab === TabID.INPUT) {
methods.setValue('item_data.operation_type', OperationType.INPUT);
methods.setValue('item_data.result', null);
methods.setValue('arguments', []);
} else {
methods.setValue('item_data.operation_type', OperationType.SYNTHESIS);
methods.setValue('arguments', initialInputs);
}
setActiveTab(newTab);
}
return (
<ModalForm
header='Создание операции'
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-180 px-6 h-128'
helpTopic={HelpTopic.CC_OSS}
>
<Tabs
className='grid'
selectedIndex={activeTab}
onSelect={(index, last) => handleSelectTab(index as TabID, last as TabID)}
>
<TabList className='z-pop mx-auto -mb-5 flex border divide-x rounded-none'>
<TabLabel
title={describeOperationType(OperationType.INPUT)}
label={labelOperationType(OperationType.INPUT)}
/>
<TabLabel
title={describeOperationType(OperationType.SYNTHESIS)}
label={labelOperationType(OperationType.SYNTHESIS)}
/>
</TabList>
<FormProvider {...methods}>
<TabPanel>
<TabInputOperation />
</TabPanel>
<TabPanel>
<TabSynthesisOperation />
</TabPanel>
</FormProvider>
</Tabs>
</ModalForm>
);
}

View File

@ -1 +0,0 @@
export { DlgCreateOperation } from './dlg-create-operation';

View File

@ -1,129 +0,0 @@
'use client';
import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { type ILibraryItem, LibraryItemType } from '@/features/library';
import { useLibrary } from '@/features/library/backend/use-library';
import { PickSchema } from '@/features/library/components/pick-schema';
import { MiniButton } from '@/components/control';
import { IconReset } from '@/components/icons';
import { Checkbox, Label, TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO } from '../../backend/types';
import { SelectParent } from '../../components/select-parent';
import { sortItemsForOSS } from '../../models/oss-api';
import { type DlgCreateOperationProps } from './dlg-create-operation';
export function TabInputOperation() {
const { manager } = useDialogsStore(state => state.props as DlgCreateOperationProps);
const { items: libraryItems } = useLibrary();
const sortedItems = sortItemsForOSS(manager.oss, libraryItems);
const {
register,
control,
setValue,
formState: { errors }
} = useFormContext<ICreateOperationDTO>();
const createSchema = useWatch({ control, name: 'create_schema' });
function baseFilter(item: ILibraryItem) {
return !manager.oss.schemas.includes(item.id);
}
function handleChangeCreateSchema(value: boolean) {
if (value) {
setValue('item_data.result', null);
}
setValue('create_schema', value);
}
function handleSetInput(inputID: number) {
const schema = libraryItems.find(item => item.id === inputID);
if (!schema) {
return;
}
setValue('item_data.result', inputID);
setValue('create_schema', false);
setValue('item_data.alias', schema.alias);
setValue('item_data.title', schema.title);
setValue('item_data.description', schema.description, { shouldValidate: true });
}
return (
<div className='cc-fade-in cc-column'>
<TextInput
id='operation_title' //
label='Название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>
<div className='flex gap-6'>
<div className='grid gap-1'>
<TextInput
id='operation_alias' //
label='Сокращение'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
<Controller
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectParent
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)}
/>
)}
/>
</div>
<TextArea
id='operation_comment' //
label='Описание'
noResize
rows={3}
{...register('item_data.description')}
/>
</div>
<div className='flex justify-between gap-3 items-center'>
<div className='flex gap-3'>
<Label text='Загружаемая концептуальная схема' />
<MiniButton
title='Сбросить выбор схемы'
noPadding
icon={<IconReset size='1.25rem' className='icon-primary' />}
onClick={() => setValue('item_data.result', null)}
/>
</div>
<Checkbox
value={createSchema} //
onChange={handleChangeCreateSchema}
label='Создать новую схему'
/>
</div>
{!createSchema ? (
<Controller
control={control}
name='item_data.result'
render={({ field }) => (
<PickSchema
items={sortedItems}
value={field.value}
itemType={LibraryItemType.RSFORM}
onChange={handleSetInput}
rows={8}
baseFilter={baseFilter}
/>
)}
/>
) : null}
</div>
);
}

View File

@ -0,0 +1,112 @@
'use client';
import { Controller, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { HelpTopic } from '@/features/help';
import { TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateSchemaDTO, schemaCreateSchema } from '../backend/types';
import { useCreateSchema } from '../backend/use-create-schema';
import { SelectParent } from '../components/select-parent';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../models/oss-layout-api';
export interface DlgCreateSchemaProps {
manager: LayoutManager;
defaultX: number;
defaultY: number;
initialParent: number | null;
onCreate?: (newID: number) => void;
}
export function DlgCreateSchema() {
const { createSchema } = useCreateSchema();
const { manager, initialParent, onCreate, defaultX, defaultY } = useDialogsStore(
state => state.props as DlgCreateSchemaProps
);
const {
control,
register,
handleSubmit,
formState: { errors }
} = useForm<ICreateSchemaDTO>({
resolver: zodResolver(schemaCreateSchema),
defaultValues: {
item_data: {
alias: '',
title: '',
description: '',
parent: initialParent
},
position: {
x: defaultX,
y: defaultY,
width: OPERATION_NODE_WIDTH,
height: OPERATION_NODE_HEIGHT
},
layout: manager.layout
},
mode: 'onChange'
});
const alias = useWatch({ control: control, name: 'item_data.alias' });
const isValid = !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
function onSubmit(data: ICreateSchemaDTO) {
data.position = manager.newOperationPosition(data);
data.layout = manager.layout;
void createSchema({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_operation));
}
return (
<ModalForm
header='Создание операции: Пустая КС'
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-180 px-6 cc-column'
helpTopic={HelpTopic.CC_OSS}
>
<TextInput
id='operation_title' //
label='Название'
{...register('item_data.title')}
error={errors.item_data?.title}
/>
<div className='flex gap-6'>
<div className='grid gap-1'>
<TextInput
id='operation_alias' //
label='Сокращение'
className='w-80'
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
<Controller
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectParent
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)}
/>
)}
/>
</div>
<TextArea
id='operation_comment' //
label='Описание'
noResize
rows={3}
{...register('item_data.description')}
/>
</div>
</ModalForm>
);
}

View File

@ -0,0 +1,107 @@
'use client';
import { Suspense, useState } from 'react';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { HelpTopic } from '@/features/help';
import { Loader } from '@/components/loader';
import { ModalForm } from '@/components/modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/tabs';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateSynthesisDTO, schemaCreateSynthesis } from '../../backend/types';
import { useCreateSynthesis } from '../../backend/use-create-synthesis';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../../models/oss-layout-api';
import { TabArguments } from './tab-arguments';
import { TabSubstitutions } from './tab-substitutions';
export interface DlgCreateSynthesisProps {
manager: LayoutManager;
initialParent: number | null;
initialInputs: number[];
defaultX: number;
defaultY: number;
onCreate?: (newID: number) => void;
}
export const TabID = {
ARGUMENTS: 0,
SUBSTITUTIONS: 1
} as const;
export type TabID = (typeof TabID)[keyof typeof TabID];
export function DlgCreateSynthesis() {
const { createSynthesis } = useCreateSynthesis();
const { manager, initialInputs, initialParent, onCreate, defaultX, defaultY } = useDialogsStore(
state => state.props as DlgCreateSynthesisProps
);
const methods = useForm<ICreateSynthesisDTO>({
resolver: zodResolver(schemaCreateSynthesis),
defaultValues: {
item_data: {
alias: '',
title: '',
description: '',
parent: initialParent
},
position: {
x: defaultX,
y: defaultY,
width: OPERATION_NODE_WIDTH,
height: OPERATION_NODE_HEIGHT
},
arguments: initialInputs,
substitutions: [],
layout: manager.layout
},
mode: 'onChange'
});
const alias = useWatch({ control: methods.control, name: 'item_data.alias' });
const [activeTab, setActiveTab] = useState<TabID>(TabID.ARGUMENTS);
const isValid = !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
function onSubmit(data: ICreateSynthesisDTO) {
data.position = manager.newOperationPosition(data);
data.layout = manager.layout;
void createSynthesis({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_operation));
}
return (
<ModalForm
header='Создание операции'
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-180 px-6 h-128'
helpTopic={HelpTopic.CC_OSS}
>
<Tabs className='grid' selectedIndex={activeTab} onSelect={index => setActiveTab(index as TabID)}>
<TabList className='z-pop mx-auto -mb-5 flex border divide-x rounded-none'>
<TabLabel title='Выбор аргументов операции' label='Аргументы' className='w-32' />
<TabLabel titleHtml='Таблица отождествлений' label='Отождествления' className='w-32' />
</TabList>
<FormProvider {...methods}>
<TabPanel>
<TabArguments />
</TabPanel>
<TabPanel>
<Suspense
fallback={
<div className='w-full h-full flex items-center justify-center'>
<Loader circular />
</div>
}
>
<TabSubstitutions />
</Suspense>
</TabPanel>
</FormProvider>
</Tabs>
</ModalForm>
);
}

View File

@ -0,0 +1 @@
export { DlgCreateSynthesis } from './dlg-create-synthesis';

View File

@ -3,19 +3,19 @@ import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { Label, TextArea, TextInput } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO } from '../../backend/types';
import { type ICreateSynthesisDTO } from '../../backend/types';
import { PickMultiOperation } from '../../components/pick-multi-operation';
import { SelectParent } from '../../components/select-parent';
import { type DlgCreateOperationProps } from './dlg-create-operation';
import { type DlgCreateSynthesisProps } from './dlg-create-synthesis';
export function TabSynthesisOperation() {
const { manager } = useDialogsStore(state => state.props as DlgCreateOperationProps);
export function TabArguments() {
const { manager } = useDialogsStore(state => state.props as DlgCreateSynthesisProps);
const {
register,
control,
formState: { errors }
} = useFormContext<ICreateOperationDTO>();
} = useFormContext<ICreateSynthesisDTO>();
const inputs = useWatch({ control, name: 'arguments' });
return (

View File

@ -0,0 +1,50 @@
'use client';
import { Controller, useFormContext, useWatch } from 'react-hook-form';
import { useRSForms } from '@/features/rsform/backend/use-rsforms';
import { PickSubstitutions } from '@/features/rsform/components/pick-substitutions';
import { TextArea } from '@/components/input';
import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateSynthesisDTO } from '../../backend/types';
import { SubstitutionValidator } from '../../models/oss-api';
import { type DlgCreateSynthesisProps } from './dlg-create-synthesis';
export function TabSubstitutions() {
const { manager } = useDialogsStore(state => state.props as DlgCreateSynthesisProps);
const { control } = useFormContext<ICreateSynthesisDTO>();
const inputs = useWatch({ control, name: 'arguments' });
const substitutions = useWatch({ control, name: 'substitutions' });
const schemasIDs = inputs
.map(id => manager.oss.operationByID.get(id)!)
.map(operation => operation.result)
.filter(id => id !== null);
const schemas = useRSForms(schemasIDs);
const validator = new SubstitutionValidator(schemas, substitutions);
const isCorrect = validator.validate();
return (
<div className='cc-fade-in cc-column mt-9'>
<Controller
name='substitutions'
control={control}
render={({ field }) => (
<PickSubstitutions
schemas={schemas}
rows={8}
value={field.value}
onChange={field.onChange}
suggestions={validator.suggestions}
/>
)}
/>
<TextArea disabled value={validator.msg} rows={4} className={isCorrect ? '' : 'border-(--acc-fg-red) border-2'} />
</div>
);
}

View File

@ -18,7 +18,7 @@ import { type LayoutManager } from '../../models/oss-layout-api';
import { TabArguments } from './tab-arguments';
import { TabOperation } from './tab-operation';
import { TabSynthesis } from './tab-synthesis';
import { TabSubstitutions } from './tab-substitutions';
export interface DlgEditOperationProps {
manager: LayoutManager;
@ -28,7 +28,7 @@ export interface DlgEditOperationProps {
export const TabID = {
CARD: 0,
ARGUMENTS: 1,
SUBSTITUTION: 2
SUBSTITUTIONS: 2
} as const;
export type TabID = (typeof TabID)[keyof typeof TabID];
@ -73,7 +73,7 @@ export function DlgEditOperation() {
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
className='w-160 px-6 h-128'
helpTopic={HelpTopic.UI_SUBSTITUTIONS}
hideHelpWhen={() => activeTab !== TabID.SUBSTITUTION}
hideHelpWhen={() => activeTab !== TabID.SUBSTITUTIONS}
>
<Tabs className='grid' selectedIndex={activeTab} onSelect={index => setActiveTab(index as TabID)}>
<TabList className='mb-3 mx-auto w-fit flex border divide-x rounded-none'>
@ -109,7 +109,7 @@ export function DlgEditOperation() {
{target.operation_type === OperationType.SYNTHESIS ? (
<TabPanel>
<Suspense fallback={<Loader />}>
<TabSynthesis />
<TabSubstitutions />
</Suspense>
</TabPanel>
) : null}

View File

@ -13,7 +13,7 @@ import { SubstitutionValidator } from '../../models/oss-api';
import { type DlgEditOperationProps } from './dlg-edit-operation';
export function TabSynthesis() {
export function TabSubstitutions() {
const { manager } = useDialogsStore(state => state.props as DlgEditOperationProps);
const { control } = useFormContext<IUpdateOperationDTO>();
const inputs = useWatch({ control, name: 'arguments' });

View File

@ -0,0 +1,158 @@
'use client';
import { Controller, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { HelpTopic } from '@/features/help';
import { type ILibraryItem, LibraryItemType } from '@/features/library';
import { useLibrary } from '@/features/library/backend/use-library';
import { PickSchema } from '@/features/library/components/pick-schema';
import { Checkbox, TextArea, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type IImportSchemaDTO, schemaImportSchema } from '../backend/types';
import { useImportSchema } from '../backend/use-import-schema';
import { SelectParent } from '../components/select-parent';
import { sortItemsForOSS } from '../models/oss-api';
import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../models/oss-layout-api';
export interface DlgImportSchemaProps {
manager: LayoutManager;
defaultX: number;
defaultY: number;
initialParent: number | null;
onCreate?: (newID: number) => void;
}
export function DlgImportSchema() {
const { importSchema } = useImportSchema();
const { manager, initialParent, onCreate, defaultX, defaultY } = useDialogsStore(
state => state.props as DlgImportSchemaProps
);
const { items: libraryItems } = useLibrary();
const sortedItems = sortItemsForOSS(manager.oss, libraryItems);
const {
control,
register,
handleSubmit,
setValue,
formState: { errors }
} = useForm<IImportSchemaDTO>({
resolver: zodResolver(schemaImportSchema),
defaultValues: {
item_data: {
alias: '',
title: '',
description: '',
parent: initialParent
},
position: {
x: defaultX,
y: defaultY,
width: OPERATION_NODE_WIDTH,
height: OPERATION_NODE_HEIGHT
},
layout: manager.layout,
source: 0,
clone_source: false
},
mode: 'onChange'
});
const alias = useWatch({ control: control, name: 'item_data.alias' });
const clone_source = useWatch({ control: control, name: 'clone_source' });
const isValid = !!alias && !manager.oss.operations.some(operation => operation.alias === alias);
function onSubmit(data: IImportSchemaDTO) {
data.position = manager.newOperationPosition(data);
data.layout = manager.layout;
void importSchema({ itemID: manager.oss.id, data: data }).then(response => onCreate?.(response.new_operation));
}
function baseFilter(item: ILibraryItem) {
return !manager.oss.schemas.includes(item.id);
}
function handleSetInput(inputID: number) {
const schema = libraryItems.find(item => item.id === inputID);
if (!schema) {
return;
}
setValue('source', inputID);
setValue('item_data.alias', schema.alias);
setValue('item_data.title', schema.title);
setValue('item_data.description', schema.description, { shouldValidate: true });
}
return (
<ModalForm
header='Создание операции: Загрузка'
submitText='Создать'
canSubmit={isValid}
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-180 px-6 cc-column'
helpTopic={HelpTopic.CC_OSS}
>
<Controller
control={control}
name='source'
render={({ field }) => (
<PickSchema
items={sortedItems}
value={field.value}
itemType={LibraryItemType.RSFORM}
onChange={handleSetInput}
rows={8}
baseFilter={baseFilter}
/>
)}
/>
<Controller
control={control}
name='clone_source'
render={({ field }) => <Checkbox label='Клонировать схему' value={field.value} onChange={field.onChange} />}
/>
<TextInput
id='operation_title' //
label='Название'
disabled={!clone_source}
{...register('item_data.title')}
error={errors.item_data?.title}
/>
<div className='flex gap-6'>
<div className='grid gap-1'>
<TextInput
id='operation_alias' //
label='Сокращение'
className='w-80'
disabled={!clone_source}
{...register('item_data.alias')}
error={errors.item_data?.alias}
/>
<Controller
name='item_data.parent'
control={control}
render={({ field }) => (
<SelectParent
items={manager.oss.blocks}
value={field.value ? manager.oss.blockByID.get(field.value) ?? null : null}
placeholder='Родительский блок'
onChange={value => field.onChange(value ? value.id : null)}
/>
)}
/>
</div>
<TextArea
id='operation_comment' //
label='Описание'
rows={3}
disabled={!clone_source}
{...register('item_data.description')}
/>
</div>
</ModalForm>
);
}

View File

@ -1,4 +1,11 @@
import { type ICreateBlockDTO, type ICreateOperationDTO, type INodePosition, type IOssLayout } from '../backend/types';
import {
type ICreateBlockDTO,
type ICreateSchemaDTO,
type ICreateSynthesisDTO,
type IImportSchemaDTO,
type INodePosition,
type IOssLayout
} from '../backend/types';
import { type IOperationSchema } from './oss';
import { type Position2D, type Rectangle2D } from './oss-layout';
@ -24,11 +31,11 @@ export class LayoutManager {
}
/** Calculate insert position for a new {@link IOperation} */
newOperationPosition(data: ICreateOperationDTO): Rectangle2D {
const result = { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
newOperationPosition(data: ICreateSchemaDTO | ICreateSynthesisDTO | IImportSchemaDTO): Rectangle2D {
const result = { ...data.position };
const parentNode = this.layout.find(pos => pos.nodeID === `b${data.item_data.parent}`) ?? null;
const operations = this.layout.filter(pos => pos.nodeID.startsWith('o'));
if (data.arguments.length !== 0) {
if ('arguments' in data && data.arguments.length !== 0) {
const pos = calculatePositionFromArgs(
operations.filter(node => data.arguments.includes(Number(node.nodeID.slice(1))))
);
@ -59,20 +66,16 @@ export class LayoutManager {
.filter(node => !!node);
const parentNode = this.layout.find(pos => pos.nodeID === `b${data.item_data.parent}`) ?? null;
let result: Rectangle2D = { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
let result: Rectangle2D = { ...data.position };
if (block_nodes.length !== 0 || operation_nodes.length !== 0) {
result = calculatePositionFromChildren(
{ x: data.position_x, y: data.position_y, width: data.width, height: data.height },
operation_nodes,
block_nodes
);
result = calculatePositionFromChildren(data.position, operation_nodes, block_nodes);
} else if (parentNode) {
result = {
x: parentNode.x + MIN_DISTANCE,
y: parentNode.y + MIN_DISTANCE,
width: data.width,
height: data.height
width: data.position.width,
height: data.position.height
};
} else {
result = this.calculatePositionForFreeBlock(result);

View File

@ -65,8 +65,10 @@ export function OssFlow() {
const showCreateOperation = useDialogsStore(state => state.showCreateOperation);
const showCreateBlock = useDialogsStore(state => state.showCreateBlock);
const showCreateSchema = useDialogsStore(state => state.showCreateSchema);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
const showEditBlock = useDialogsStore(state => state.showEditBlock);
const showImportSchema = useDialogsStore(state => state.showImportSchema);
const { isOpen: isContextMenuOpen, menuProps, openContextMenu, hideContextMenu } = useContextMenu();
const { handleDragStart, handleDrag, handleDragStop } = useDragging({ hideContextMenu });
@ -101,6 +103,28 @@ export function OssFlow() {
});
}
function handleCreateSchema() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showCreateSchema({
manager: new LayoutManager(schema, getLayout()),
defaultX: targetPosition.x,
defaultY: targetPosition.y,
initialParent: extractBlockParent(selectedItems),
onCreate: resetView
});
}
function handleImportSchema() {
const targetPosition = screenToFlowPosition({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
showImportSchema({
manager: new LayoutManager(schema, getLayout()),
defaultX: targetPosition.x,
defaultY: targetPosition.y,
initialParent: extractBlockParent(selectedItems),
onCreate: resetView
});
}
function handleDeleteSelected() {
if (selected.length !== 1) {
return;
@ -194,8 +218,8 @@ export function OssFlow() {
<ToolbarOssGraph
className='absolute z-pop top-8 right-1/2 translate-x-1/2'
onCreateBlock={handleCreateBlock}
onCreateSchema={handleCreateOperation}
onImportSchema={handleCreateOperation}
onCreateSchema={handleCreateSchema}
onImportSchema={handleImportSchema}
onCreateSynthesis={handleCreateOperation}
onDelete={handleDeleteSelected}
onResetPositions={resetGraph}

View File

@ -21,7 +21,8 @@ import {
IconNewItem,
IconReset,
IconSave,
IconSettings
IconSettings,
IconSynthesis
} from '@/components/icons';
import { type Styling } from '@/components/props';
import { cn } from '@/components/utils';
@ -184,13 +185,13 @@ export function ToolbarOssGraph({
<DropdownButton
text='Импорт КС'
titleHtml={prepareTooltip('Импорт концептуальной схемы', 'Alt + 3')}
icon={<IconDownload size='1.25rem' className='text-constructive' />}
icon={<IconDownload size='1.25rem' className='text-primary' />}
onClick={onImportSchema}
/>
<DropdownButton
text='Синтез'
titleHtml={prepareTooltip('Синтез концептуальных схем', 'Alt + 4')}
icon={<IconConceptBlock size='1.25rem' className='text-primary' />}
icon={<IconSynthesis size='1.25rem' className='text-primary' />}
onClick={onCreateSynthesis}
/>
{user.is_staff ? (

View File

@ -7,10 +7,12 @@ import { type DlgEditEditorsProps } from '@/features/library/dialogs/dlg-edit-ed
import { type DlgEditVersionsProps } from '@/features/library/dialogs/dlg-edit-versions/dlg-edit-versions';
import { type DlgChangeInputSchemaProps } from '@/features/oss/dialogs/dlg-change-input-schema';
import { type DlgCreateBlockProps } from '@/features/oss/dialogs/dlg-create-block/dlg-create-block';
import { type DlgCreateOperationProps } from '@/features/oss/dialogs/dlg-create-operation/dlg-create-operation';
import { type DlgCreateSchemaProps } from '@/features/oss/dialogs/dlg-create-schema';
import { type DlgCreateSynthesisProps } from '@/features/oss/dialogs/dlg-create-synthesis/dlg-create-synthesis';
import { type DlgDeleteOperationProps } from '@/features/oss/dialogs/dlg-delete-operation';
import { type DlgEditBlockProps } from '@/features/oss/dialogs/dlg-edit-block';
import { type DlgEditOperationProps } from '@/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation';
import { type DlgImportSchemaProps } from '@/features/oss/dialogs/dlg-import-schema';
import { type DlgRelocateConstituentsProps } from '@/features/oss/dialogs/dlg-relocate-constituents';
import { type DlgShowTermGraphProps } from '@/features/oss/dialogs/dlg-show-term-graph/dlg-show-term-graph';
import { type DlgCreateCstProps } from '@/features/rsform/dialogs/dlg-create-cst/dlg-create-cst';
@ -41,7 +43,7 @@ export const DialogType = {
CREATE_BLOCK: 7,
EDIT_BLOCK: 8,
CREATE_OPERATION: 9,
CREATE_SYNTHESIS: 9,
EDIT_OPERATION: 10,
DELETE_OPERATION: 11,
CHANGE_INPUT_SCHEMA: 12,
@ -63,7 +65,9 @@ export const DialogType = {
SHOW_AST: 25,
SHOW_TYPE_GRAPH: 26,
GRAPH_PARAMETERS: 27,
SHOW_TERM_GRAPH: 28
SHOW_TERM_GRAPH: 28,
CREATE_SCHEMA: 29,
IMPORT_SCHEMA: 30
} as const;
export type DialogType = (typeof DialogType)[keyof typeof DialogType];
@ -79,7 +83,7 @@ interface DialogsStore {
showCstTemplate: (props: DlgCstTemplateProps) => void;
showCreateCst: (props: DlgCreateCstProps) => void;
showCreateBlock: (props: DlgCreateBlockProps) => void;
showCreateOperation: (props: DlgCreateOperationProps) => void;
showCreateOperation: (props: DlgCreateSynthesisProps) => void;
showDeleteCst: (props: DlgDeleteCstProps) => void;
showEditEditors: (props: DlgEditEditorsProps) => void;
showEditOperation: (props: DlgEditOperationProps) => void;
@ -104,6 +108,8 @@ interface DialogsStore {
showSubstituteCst: (props: DlgSubstituteCstProps) => void;
showUploadRSForm: (props: DlgUploadRSFormProps) => void;
showEditCst: (props: DlgEditCstProps) => void;
showCreateSchema: (props: DlgCreateSchemaProps) => void;
showImportSchema: (props: DlgImportSchemaProps) => void;
}
export const useDialogsStore = create<DialogsStore>()(set => ({
@ -118,7 +124,7 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
showCstTemplate: props => set({ active: DialogType.CONSTITUENTA_TEMPLATE, props: props }),
showCreateCst: props => set({ active: DialogType.CREATE_CONSTITUENTA, props: props }),
showCreateOperation: props => set({ active: DialogType.CREATE_OPERATION, props: props }),
showCreateOperation: props => set({ active: DialogType.CREATE_SYNTHESIS, props: props }),
showCreateBlock: props => set({ active: DialogType.CREATE_BLOCK, props: props }),
showDeleteCst: props => set({ active: DialogType.DELETE_CONSTITUENTA, props: props }),
showEditEditors: props => set({ active: DialogType.EDIT_EDITORS, props: props }),
@ -143,5 +149,7 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
showQR: props => set({ active: DialogType.SHOW_QR_CODE, props: props }),
showSubstituteCst: props => set({ active: DialogType.SUBSTITUTE_CONSTITUENTS, props: props }),
showUploadRSForm: props => set({ active: DialogType.UPLOAD_RSFORM, props: props }),
showEditCst: props => set({ active: DialogType.EDIT_CONSTITUENTA, props: props })
showEditCst: props => set({ active: DialogType.EDIT_CONSTITUENTA, props: props }),
showCreateSchema: props => set({ active: DialogType.CREATE_SCHEMA, props: props }),
showImportSchema: props => set({ active: DialogType.IMPORT_SCHEMA, props: props })
}));