F: Implementing references pt1

This commit is contained in:
Ivan 2025-08-04 15:59:16 +03:00
parent f78ee7c705
commit 13e56f51ea
38 changed files with 775 additions and 119 deletions

View File

@ -128,6 +128,7 @@
"perfectivity", "perfectivity",
"PNCT", "PNCT",
"ponomarev", "ponomarev",
"popleft",
"PRCL", "PRCL",
"PRTF", "PRTF",
"PRTS", "PRTS",

View File

@ -23,6 +23,7 @@ from apps.rsform.models import (
from .Argument import Argument from .Argument import Argument
from .Inheritance import Inheritance from .Inheritance import Inheritance
from .Operation import Operation, OperationType from .Operation import Operation, OperationType
from .Reference import Reference
from .Substitution import Substitution from .Substitution import Substitution
CstMapping = dict[str, Optional[Constituenta]] CstMapping = dict[str, Optional[Constituenta]]
@ -36,13 +37,15 @@ class OperationSchemaCached:
self.model = model self.model = model
self.cache = OssCache(self) self.cache = OssCache(self)
def delete_reference(self, target: Operation, keep_connections: bool = False): def delete_reference(self, target: int, keep_connections: bool = False, keep_constituents: bool = False):
''' Delete Reference Operation. ''' ''' Delete Reference Operation. '''
self.cache.ensure_loaded_subs()
operation = self.cache.operation_by_id[target]
if keep_connections: if keep_connections:
referred_operations = target.getQ_reference_target() referred_operations = operation.getQ_reference_target()
if len(referred_operations) == 1: if len(referred_operations) == 1:
referred_operation = referred_operations[0] referred_operation = referred_operations[0]
for arg in target.getQ_as_argument(): for arg in operation.getQ_as_argument():
arg.pk = None arg.pk = None
arg.argument = referred_operation arg.argument = referred_operation
arg.save() arg.save()
@ -50,7 +53,9 @@ class OperationSchemaCached:
pass pass
# if target.result_id is not None: # if target.result_id is not None:
# self.before_delete_cst(schema, schema.cache.constituents) # TODO: use operation instead of schema # self.before_delete_cst(schema, schema.cache.constituents) # TODO: use operation instead of schema
target.delete()
self.cache.remove_operation(target)
operation.delete()
def delete_operation(self, target: int, keep_constituents: bool = False): def delete_operation(self, target: int, keep_constituents: bool = False):
''' Delete Operation. ''' ''' Delete Operation. '''
@ -707,17 +712,23 @@ class OssCache:
self._schemas: list[RSFormCached] = [] self._schemas: list[RSFormCached] = []
self._schema_by_id: dict[int, RSFormCached] = {} self._schema_by_id: dict[int, RSFormCached] = {}
self.operations = list(Operation.objects.filter(oss=oss.model).only('result_id')) self.operations = list(Operation.objects.filter(oss=oss.model).only('result_id', 'operation_type'))
self.operation_by_id = {operation.pk: operation for operation in self.operations} self.operation_by_id = {operation.pk: operation for operation in self.operations}
self.graph = Graph[int]() self.graph = Graph[int]()
for operation in self.operations: for operation in self.operations:
self.graph.add_node(operation.pk) self.graph.add_node(operation.pk)
references = Reference.objects.filter(reference__oss=self._oss.model).only('reference_id', 'target_id')
self.reference_target = {ref.reference_id: ref.target_id for ref in references}
arguments = Argument.objects \ arguments = Argument.objects \
.filter(operation__oss=self._oss.model) \ .filter(operation__oss=self._oss.model) \
.only('operation_id', 'argument_id') \ .only('operation_id', 'argument_id') \
.order_by('order') .order_by('order')
for argument in arguments: for argument in arguments:
self.graph.add_edge(argument.argument_id, argument.operation_id) self.graph.add_edge(argument.argument_id, argument.operation_id)
target = self.reference_target.get(argument.argument_id)
if target is not None:
self.graph.add_edge(target, argument.operation_id)
self.is_loaded_subs = False self.is_loaded_subs = False
self.substitutions: dict[int, list[Substitution]] = {} self.substitutions: dict[int, list[Substitution]] = {}
@ -785,18 +796,12 @@ class OssCache:
schema.cache.ensure_loaded() schema.cache.ensure_loaded()
self._insert_new(schema) self._insert_new(schema)
def insert_operation(self, operation: Operation) -> None:
''' Insert new operation. '''
self.operations.append(operation)
self.operation_by_id[operation.pk] = operation
self.graph.add_node(operation.pk)
if self.is_loaded_subs:
self.substitutions[operation.pk] = []
self.inheritance[operation.pk] = []
def insert_argument(self, argument: Argument) -> None: def insert_argument(self, argument: Argument) -> None:
''' Insert new argument. ''' ''' Insert new argument. '''
self.graph.add_edge(argument.argument_id, argument.operation_id) self.graph.add_edge(argument.argument_id, argument.operation_id)
target = self.reference_target.get(argument.argument_id)
if target is not None:
self.graph.add_edge(target, argument.operation_id)
def insert_inheritance(self, inheritance: Inheritance) -> None: def insert_inheritance(self, inheritance: Inheritance) -> None:
''' Insert new inheritance. ''' ''' Insert new inheritance. '''
@ -839,6 +844,10 @@ class OssCache:
def remove_argument(self, argument: Argument) -> None: def remove_argument(self, argument: Argument) -> None:
''' Remove argument from cache. ''' ''' Remove argument from cache. '''
self.graph.remove_edge(argument.argument_id, argument.operation_id) self.graph.remove_edge(argument.argument_id, argument.operation_id)
target = self.reference_target.get(argument.argument_id)
if target is not None:
if not Argument.objects.filter(argument_id=target, operation_id=argument.operation_id).exists():
self.graph.remove_edge(target, argument.operation_id)
def remove_substitution(self, target: Substitution) -> None: def remove_substitution(self, target: Substitution) -> None:
''' Remove substitution from cache. ''' ''' Remove substitution from cache. '''

View File

@ -16,6 +16,7 @@ from .data_access import (
MoveItemsSerializer, MoveItemsSerializer,
OperationSchemaSerializer, OperationSchemaSerializer,
OperationSerializer, OperationSerializer,
ReferenceSerializer,
RelocateConstituentsSerializer, RelocateConstituentsSerializer,
SetOperationInputSerializer, SetOperationInputSerializer,
TargetOperationSerializer, TargetOperationSerializer,

View File

@ -13,7 +13,16 @@ from apps.rsform.serializers import SubstitutionSerializerBase
from shared import messages as msg from shared import messages as msg
from shared.serializers import StrictModelSerializer, StrictSerializer from shared.serializers import StrictModelSerializer, StrictSerializer
from ..models import Argument, Block, Inheritance, Layout, Operation, OperationType, Substitution from ..models import (
Argument,
Block,
Inheritance,
Layout,
Operation,
OperationType,
Reference,
Substitution
)
from .basics import NodeSerializer, PositionSerializer, SubstitutionExSerializer from .basics import NodeSerializer, PositionSerializer, SubstitutionExSerializer
@ -45,6 +54,14 @@ class ArgumentSerializer(StrictModelSerializer):
fields = ('operation', 'argument') fields = ('operation', 'argument')
class ReferenceSerializer(StrictModelSerializer):
''' Serializer: Reference data. '''
class Meta:
''' serializer metadata. '''
model = Reference
fields = ('reference', 'target')
class CreateBlockSerializer(StrictSerializer): class CreateBlockSerializer(StrictSerializer):
''' Serializer: Block creation. ''' ''' Serializer: Block creation. '''
class BlockCreateData(StrictModelSerializer): class BlockCreateData(StrictModelSerializer):
@ -444,6 +461,7 @@ class DeleteReferenceSerializer(StrictSerializer):
) )
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'operation_type')) target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'operation_type'))
keep_connections = serializers.BooleanField(default=False, required=False) keep_connections = serializers.BooleanField(default=False, required=False)
keep_constituents = serializers.BooleanField(default=False, required=False)
def validate(self, attrs): def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss']) oss = cast(LibraryItem, self.context['oss'])
@ -517,6 +535,9 @@ class OperationSchemaSerializer(StrictModelSerializer):
substitutions = serializers.ListField( substitutions = serializers.ListField(
child=SubstitutionExSerializer() child=SubstitutionExSerializer()
) )
references = serializers.ListField(
child=ReferenceSerializer()
)
layout = serializers.ListField( layout = serializers.ListField(
child=NodeSerializer() child=NodeSerializer()
) )
@ -534,6 +555,7 @@ class OperationSchemaSerializer(StrictModelSerializer):
result['blocks'] = [] result['blocks'] = []
result['arguments'] = [] result['arguments'] = []
result['substitutions'] = [] result['substitutions'] = []
result['references'] = []
for operation in Operation.objects.filter(oss=instance).order_by('pk'): for operation in Operation.objects.filter(oss=instance).order_by('pk'):
operation_data = OperationSerializer(operation).data operation_data = OperationSerializer(operation).data
operation_result = operation.result operation_result = operation.result
@ -556,6 +578,9 @@ class OperationSchemaSerializer(StrictModelSerializer):
substitution_term=F('substitution__term_resolved'), substitution_term=F('substitution__term_resolved'),
).order_by('pk'): ).order_by('pk'):
result['substitutions'].append(substitution) result['substitutions'].append(substitution)
for reference in Reference.objects.filter(target__oss=instance).order_by('pk'):
result['references'].append(ReferenceSerializer(reference).data)
return result return result

View File

@ -1,5 +1,7 @@
''' Tests for Django Models. ''' ''' Tests for Django Models. '''
from .t_Argument import * from .t_Argument import *
from .t_Inheritance import * from .t_Inheritance import *
from .t_Layout import *
from .t_Operation import * from .t_Operation import *
from .t_Reference import *
from .t_Substitution import * from .t_Substitution import *

View File

@ -0,0 +1,39 @@
''' Testing models: Layout. '''
from django.test import TestCase
from apps.library.models import LibraryItem
from apps.oss.models import Layout
class TestLayout(TestCase):
''' Testing Layout model. '''
def setUp(self):
self.library_item = LibraryItem.objects.create(alias='LIB1')
self.layout = Layout.objects.create(
oss=self.library_item,
data=[{'x': 1, 'y': 2}]
)
def test_str(self):
expected = f'Схема расположения {self.library_item.alias}'
self.assertEqual(str(self.layout), expected)
def test_update_data(self):
new_data = [{'x': 10, 'y': 20}]
Layout.update_data(self.library_item.id, new_data)
self.layout.refresh_from_db()
self.assertEqual(self.layout.data, new_data)
def test_default_data(self):
layout2 = Layout.objects.create(oss=self.library_item)
self.assertEqual(layout2.data, [])
def test_related_name_layout(self):
layouts = self.library_item.layout.all()
self.assertIn(self.layout, layouts)

View File

@ -0,0 +1,44 @@
''' Testing models: Reference. '''
from django.test import TestCase
from apps.oss.models import Operation, OperationSchema, OperationType, Reference
from apps.rsform.models import RSForm
class TestReference(TestCase):
''' Testing Reference model. '''
def setUp(self):
self.oss = OperationSchema.create(alias='T1')
self.operation1 = Operation.objects.create(
oss=self.oss.model,
alias='KS1',
operation_type=OperationType.INPUT,
)
self.operation2 = Operation.objects.create(
oss=self.oss.model,
operation_type=OperationType.REFERENCE,
)
self.reference = Reference.objects.create(
reference=self.operation2,
target=self.operation1
)
def test_str(self):
testStr = f'{self.operation2} -> {self.operation1}'
self.assertEqual(str(self.reference), testStr)
def test_cascade_delete_operation(self):
self.assertEqual(Reference.objects.count(), 1)
self.operation2.delete()
self.assertEqual(Reference.objects.count(), 0)
def test_cascade_delete_target(self):
self.assertEqual(Reference.objects.count(), 1)
self.operation1.delete()
self.assertEqual(Reference.objects.count(), 0)

View File

@ -2,4 +2,5 @@
from .t_attributes import * from .t_attributes import *
from .t_constituents import * from .t_constituents import *
from .t_operations import * from .t_operations import *
from .t_references import *
from .t_substitutions import * from .t_substitutions import *

View File

@ -0,0 +1,199 @@
''' Testing API: Propagate changes through references in OSS. '''
from apps.oss.models import OperationSchema, OperationType
from apps.rsform.models import Constituenta, CstType, RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint
class ReferencePropagationTestCase(EndpointTester):
''' Test propagation through references in OSS. '''
def setUp(self):
super().setUp()
self.owned = OperationSchema.create(
title='Test',
alias='T1',
owner=self.user
)
self.owned_id = self.owned.model.pk
self.ks1 = RSForm.create(
alias='KS1',
title='Test1',
owner=self.user
)
self.ks1X1 = self.ks1.insert_last('X1', convention='KS1X1')
self.ks1X2 = self.ks1.insert_last('X2', convention='KS1X2')
self.ks1D1 = self.ks1.insert_last('D1', definition_formal='X1 X2', convention='KS1D1')
self.ks2 = RSForm.create(
alias='KS2',
title='Test2',
owner=self.user
)
self.ks2X1 = self.ks2.insert_last('X1', convention='KS2X1')
self.ks2X2 = self.ks2.insert_last('X2', convention='KS2X2')
self.ks2S1 = self.ks2.insert_last(
alias='S1',
definition_formal=r'(X1)',
convention='KS2S1'
)
self.operation1 = self.owned.create_operation(
alias='1',
operation_type=OperationType.INPUT,
result=self.ks1.model
)
self.operation2 = self.owned.create_operation(
alias='2',
operation_type=OperationType.INPUT,
result=self.ks2.model
)
self.operation3 = self.owned.create_reference(self.operation1)
self.operation4 = self.owned.create_operation(
alias='4',
operation_type=OperationType.SYNTHESIS
)
self.owned.set_arguments(self.operation4.pk, [self.operation1, self.operation2])
self.owned.set_substitutions(self.operation4.pk, [{
'original': self.ks1X1,
'substitution': self.ks2S1
}])
self.owned.execute_operation(self.operation4)
self.operation4.refresh_from_db()
self.ks4 = RSForm(self.operation4.result)
self.ks4X1 = Constituenta.objects.get(as_child__parent_id=self.ks1X2.pk)
self.ks4S1 = Constituenta.objects.get(as_child__parent_id=self.ks2S1.pk)
self.ks4D1 = Constituenta.objects.get(as_child__parent_id=self.ks1D1.pk)
self.ks4D2 = self.ks4.insert_last(
alias='D2',
definition_formal=r'X1 X2 X3 S1 D1',
convention='KS4D2'
)
self.operation5 = self.owned.create_operation(
alias='5',
operation_type=OperationType.SYNTHESIS
)
self.owned.set_arguments(self.operation5.pk, [self.operation4, self.operation3])
self.owned.set_substitutions(self.operation5.pk, [{
'original': self.ks4X1,
'substitution': self.ks1X1
}])
self.owned.execute_operation(self.operation5)
self.operation5.refresh_from_db()
self.ks5 = RSForm(self.operation5.result)
self.ks5D4 = self.ks5.insert_last(
alias='D4',
definition_formal=r'X1 X2 X3 S1 D1 D2 D3',
convention='KS5D4'
)
self.layout_data = [
{'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'nodeID': 'o' + str(self.operation4.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'nodeID': 'o' + str(self.operation5.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40}
]
layout = OperationSchema.layoutQ(self.owned_id)
layout.data = self.layout_data
layout.save()
def test_reference_creation(self):
''' Test reference creation. '''
self.assertEqual(self.operation1.result, self.operation3.result)
# @decl_endpoint('/api/oss/{item}/delete-reference', method='patch')
# def test_delete_reference_propagation(self):
# ''' Test propagation when deleting a reference operation. '''
# schema_cached = OperationSchemaCached(self.schema2)
# # Ensure reference exists
# self.assertIn(self.reference.pk, schema_cached.cache.operation_by_id)
# # Delete the reference
# schema_cached.delete_reference(self.reference.pk)
# # Reference should be deleted
# with self.assertRaises(Reference.DoesNotExist):
# Reference.objects.get(pk=self.reference.pk)
# # Operation2 should still exist
# self.assertTrue(Operation.objects.filter(pk=self.op2.pk).exists())
# @decl_endpoint('/api/oss/{item}/set-arguments', method='patch')
# def test_set_arguments_propagation(self):
# ''' Test propagation when setting arguments for a reference. '''
# schema_cached = OperationSchemaCached(self.schema2)
# # Add op1 as argument to op2
# schema_cached.set_arguments(self.op2.pk, [self.op1])
# op2 = Operation.objects.get(pk=self.op2.pk)
# args = list(op2.getQ_arguments())
# self.assertEqual(len(args), 1)
# self.assertEqual(args[0].argument, self.op1)
# @decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
# def test_delete_operation_with_reference(self):
# ''' Test propagation when deleting an operation that is referenced. '''
# schema_cached = OperationSchemaCached(self.schema1)
# # op1 is referenced by reference
# self.assertEqual(self.reference.getQ_reference_target(), self.op1)
# # Delete op1
# schema_cached.delete_operation(self.op1.pk)
# # op1 should be deleted
# with self.assertRaises(Operation.DoesNotExist):
# Operation.objects.get(pk=self.op1.pk)
# # Reference should be deleted as well
# self.assertFalse(Reference.objects.filter(pk=self.reference.pk).exists())
# @decl_endpoint('/api/oss/{item}/add-constituent', method='patch')
# def test_add_constituent_propagation(self):
# ''' Test propagation when adding a constituent to a referenced schema. '''
# # Add a new constituent to schema1 (referenced by op1, which is referenced by reference)
# new_cst = Constituenta.objects.create(
# schema=self.schema1, alias='cst_new', title='New Constituenta', cst_type=CstType.ATTRIBUTE
# )
# # Simulate propagation: after adding, the reference should still be valid and schema1 should have the new cst
# self.assertTrue(Constituenta.objects.filter(pk=new_cst.pk, schema=self.schema1).exists())
# # The reference's target operation's result should include the new constituent
# op1 = Operation.objects.get(pk=self.op1.pk)
# self.assertEqual(op1.result, self.schema1)
# self.assertIn(new_cst, Constituenta.objects.filter(schema=op1.result))
# @decl_endpoint('/api/oss/{item}/remove-constituent', method='patch')
# def test_remove_constituent_propagation(self):
# ''' Test propagation when removing a constituent from a referenced schema. '''
# # Remove cst2 from schema1
# self.cst2.delete()
# # The reference's target operation's result should not include cst2
# op1 = Operation.objects.get(pk=self.op1.pk)
# self.assertEqual(op1.result, self.schema1)
# self.assertNotIn(self.cst2, Constituenta.objects.filter(schema=op1.result))
# # Reference should still be valid
# self.assertEqual(self.reference.getQ_reference_target(), self.op1)
# @decl_endpoint('/api/oss/{item}/add-constituent-to-referenced-schema', method='patch')
# def test_propagation_to_multiple_references(self):
# ''' Test propagation when a schema is referenced by multiple references and constituents are added. '''
# # Create another reference to op1
# reference2 = Reference.objects.create(
# alias='ref2', title='Reference 2', type=OperationType.REFERENCE, result=self.schema2
# )
# reference2.setQ_reference_target(self.op1)
# reference2.save()
# # Add a new constituent to schema1
# new_cst = Constituenta.objects.create(
# schema=self.schema1, alias='cst_multi', title='Multi Constituenta', cst_type=CstType.ATTRIBUTE
# )
# # Both references should still be valid and op1's result should include the new constituent
# op1 = Operation.objects.get(pk=self.op1.pk)
# self.assertIn(new_cst, Constituenta.objects.filter(schema=op1.result))
# self.assertEqual(self.reference.getQ_reference_target(), self.op1)
# self.assertEqual(reference2.getQ_reference_target(), self.op1)

View File

@ -683,7 +683,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
with transaction.atomic(): with transaction.atomic():
oss = m.OperationSchemaCached(item) oss = m.OperationSchemaCached(item)
m.Layout.update_data(pk, layout) m.Layout.update_data(pk, layout)
oss.delete_reference(operation, serializer.validated_data['keep_connections']) oss.delete_reference(operation.pk, serializer.validated_data['keep_connections'])
item.save(update_fields=['time_update']) item.save(update_fields=['time_update'])
return Response( return Response(

View File

@ -45,6 +45,11 @@ const DlgDeleteOperation = React.lazy(() =>
default: module.DlgDeleteOperation default: module.DlgDeleteOperation
})) }))
); );
const DlgDeleteReference = React.lazy(() =>
import('@/features/oss/dialogs/dlg-delete-reference').then(module => ({
default: module.DlgDeleteReference
}))
);
const DlgEditEditors = React.lazy(() => const DlgEditEditors = React.lazy(() =>
import('@/features/library/dialogs/dlg-edit-editors').then(module => ({ import('@/features/library/dialogs/dlg-edit-editors').then(module => ({
default: module.DlgEditEditors default: module.DlgEditEditors
@ -196,6 +201,8 @@ export const GlobalDialogs = () => {
return <DlgCreateVersion />; return <DlgCreateVersion />;
case DialogType.DELETE_OPERATION: case DialogType.DELETE_OPERATION:
return <DlgDeleteOperation />; return <DlgDeleteOperation />;
case DialogType.DELETE_REFERENCE:
return <DlgDeleteReference />;
case DialogType.GRAPH_PARAMETERS: case DialogType.GRAPH_PARAMETERS:
return <DlgGraphParams />; return <DlgGraphParams />;
case DialogType.RELOCATE_CONSTITUENTS: case DialogType.RELOCATE_CONSTITUENTS:

View File

@ -95,6 +95,8 @@ export { TbHexagonLetterD as IconCstTerm } from 'react-icons/tb';
export { TbHexagonLetterF as IconCstFunction } from 'react-icons/tb'; export { TbHexagonLetterF as IconCstFunction } from 'react-icons/tb';
export { TbHexagonLetterP as IconCstPredicate } from 'react-icons/tb'; export { TbHexagonLetterP as IconCstPredicate } from 'react-icons/tb';
export { TbHexagonLetterT as IconCstTheorem } from 'react-icons/tb'; export { TbHexagonLetterT as IconCstTheorem } from 'react-icons/tb';
export { PiArrowsMergeFill as IconSynthesis } from 'react-icons/pi';
export { VscReferences as IconReference } from 'react-icons/vsc';
export { LuNewspaper as IconDefinition } from 'react-icons/lu'; export { LuNewspaper as IconDefinition } from 'react-icons/lu';
export { LuDna as IconTerminology } from 'react-icons/lu'; export { LuDna as IconTerminology } from 'react-icons/lu';
export { FaRegHandshake as IconConvention } from 'react-icons/fa6'; export { FaRegHandshake as IconConvention } from 'react-icons/fa6';
@ -129,7 +131,6 @@ export { BiStopCircle as IconStatusIncalculable } from 'react-icons/bi';
export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi'; export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi';
export { LuPower as IconKeepAliasOn } from 'react-icons/lu'; export { LuPower as IconKeepAliasOn } from 'react-icons/lu';
export { LuPowerOff as IconKeepAliasOff } from 'react-icons/lu'; export { LuPowerOff as IconKeepAliasOff } from 'react-icons/lu';
export { VscReferences as IconPhantom } from 'react-icons/vsc';
// ===== Domain actions ===== // ===== Domain actions =====
export { BiUpvote as IconMoveUp } from 'react-icons/bi'; export { BiUpvote as IconMoveUp } from 'react-icons/bi';
@ -144,7 +145,6 @@ export { BiDuplicate as IconClone } from 'react-icons/bi';
export { LuReplace as IconReplace } from 'react-icons/lu'; export { LuReplace as IconReplace } from 'react-icons/lu';
export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa'; export { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
export { LuNetwork as IconGenerateStructure } from 'react-icons/lu'; export { LuNetwork as IconGenerateStructure } from 'react-icons/lu';
export { LuCombine as IconSynthesis } from 'react-icons/lu';
export { LuBookCopy as IconInlineSynthesis } from 'react-icons/lu'; export { LuBookCopy as IconInlineSynthesis } from 'react-icons/lu';
export { LuWandSparkles as IconGenerateNames } from 'react-icons/lu'; export { LuWandSparkles as IconGenerateNames } from 'react-icons/lu';
export { GrConnect as IconConnect } from 'react-icons/gr'; export { GrConnect as IconConnect } from 'react-icons/gr';

View File

@ -9,10 +9,12 @@ import {
type ICloneSchemaDTO, type ICloneSchemaDTO,
type IConstituentaReference, type IConstituentaReference,
type ICreateBlockDTO, type ICreateBlockDTO,
type ICreateReferenceDTO,
type ICreateSchemaDTO, type ICreateSchemaDTO,
type ICreateSynthesisDTO, type ICreateSynthesisDTO,
type IDeleteBlockDTO, type IDeleteBlockDTO,
type IDeleteOperationDTO, type IDeleteOperationDTO,
type IDeleteReferenceDTO,
type IImportSchemaDTO, type IImportSchemaDTO,
type IInputCreatedResponse, type IInputCreatedResponse,
type IMoveItemsDTO, type IMoveItemsDTO,
@ -87,6 +89,28 @@ export const ossApi = {
} }
}), }),
createReference: ({ itemID, data }: { itemID: number; data: ICreateReferenceDTO }) =>
axiosPost<ICreateReferenceDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse,
endpoint: `/api/oss/${itemID}/create-reference`,
request: {
data: data,
successMessage: response => {
const alias = response.oss.operations.find(op => op.id === response.new_operation)?.alias;
return infoMsg.newOperation(alias ?? 'ОШИБКА');
}
}
}),
deleteReference: ({ itemID, data }: { itemID: number; data: IDeleteReferenceDTO }) =>
axiosPatch<IDeleteReferenceDTO, IOperationSchemaDTO>({
schema: schemaOperationSchema,
endpoint: `/api/oss/${itemID}/delete-reference`,
request: {
data: data,
successMessage: infoMsg.operationDestroyed
}
}),
createSchema: ({ itemID, data }: { itemID: number; data: ICreateSchemaDTO }) => createSchema: ({ itemID, data }: { itemID: number; data: ICreateSchemaDTO }) =>
axiosPost<ICreateSchemaDTO, IOperationCreatedResponse>({ axiosPost<ICreateSchemaDTO, IOperationCreatedResponse>({
schema: schemaOperationCreatedResponse, schema: schemaOperationCreatedResponse,

View File

@ -29,7 +29,7 @@ export class OssLoader {
private schemaIDs: number[] = []; private schemaIDs: number[] = [];
constructor(input: RO<IOperationSchemaDTO>) { constructor(input: RO<IOperationSchemaDTO>) {
this.oss = structuredClone(input) as IOperationSchema; this.oss = structuredClone(input) as unknown as IOperationSchema;
} }
produceOSS(): IOperationSchema { produceOSS(): IOperationSchema {
@ -83,16 +83,37 @@ export class OssLoader {
} }
private inferOperationAttributes() { private inferOperationAttributes() {
const referenceCounts = new Map<number, number>();
this.graph.topologicalOrder().forEach(operationID => { this.graph.topologicalOrder().forEach(operationID => {
const operation = this.operationByID.get(operationID)!; const operation = this.operationByID.get(operationID)!;
const position = this.oss.layout.find(item => item.nodeID === operation.nodeID); const position = this.oss.layout.find(item => item.nodeID === operation.nodeID);
operation.x = position?.x ?? 0; operation.x = position?.x ?? 0;
operation.y = position?.y ?? 0; operation.y = position?.y ?? 0;
operation.is_consolidation = this.inferConsolidation(operationID); switch (operation.operation_type) {
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID); case OperationType.INPUT:
operation.arguments = this.oss.arguments break;
.filter(item => item.operation === operationID) case OperationType.SYNTHESIS:
.map(item => item.argument); operation.is_consolidation = this.inferConsolidation(operationID);
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
operation.arguments = this.oss.arguments
.filter(item => item.operation === operationID)
.map(item => item.argument);
break;
case OperationType.REFERENCE:
const ref = this.oss.references.find(item => item.reference === operationID);
const target = !!ref ? this.oss.operationByID.get(ref.target) : null;
if (!target || !ref) {
throw new Error(`Reference ${operationID} not found`);
}
const refCount = (referenceCounts.get(target.id) ?? 0) + 1;
referenceCounts.set(target.id, refCount);
operation.target = ref.target;
operation.alias = `[${refCount}] ${target.alias}`;
operation.title = target.title;
operation.description = target.description;
break;
}
}); });
} }
@ -126,8 +147,11 @@ export class OssLoader {
count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length, count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length,
count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length, count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
count_schemas: this.schemaIDs.length, count_schemas: this.schemaIDs.length,
count_owned: operations.filter(item => !!item.result && !item.is_import).length, count_owned: operations.filter(
count_block: this.oss.blocks.length item => !!item.result && (item.operation_type !== OperationType.INPUT || !item.is_import)
).length,
count_block: this.oss.blocks.length,
count_references: this.oss.references.length
}; };
} }
} }

View File

@ -9,7 +9,8 @@ import { errorMsg } from '@/utils/labels';
/** Represents {@link IOperation} type. */ /** Represents {@link IOperation} type. */
export const OperationType = { export const OperationType = {
INPUT: 'input', INPUT: 'input',
SYNTHESIS: 'synthesis' SYNTHESIS: 'synthesis',
REFERENCE: 'reference'
} as const; } as const;
export type OperationType = (typeof OperationType)[keyof typeof OperationType]; export type OperationType = (typeof OperationType)[keyof typeof OperationType];
@ -48,6 +49,7 @@ export type IMoveItemsDTO = z.infer<typeof schemaMoveItems>;
/** Represents {@link IOperation} data, used in Create action. */ /** Represents {@link IOperation} data, used in Create action. */
export type ICreateSchemaDTO = z.infer<typeof schemaCreateSchema>; export type ICreateSchemaDTO = z.infer<typeof schemaCreateSchema>;
export type ICreateReferenceDTO = z.infer<typeof schemaCreateReference>;
export type ICreateSynthesisDTO = z.infer<typeof schemaCreateSynthesis>; export type ICreateSynthesisDTO = z.infer<typeof schemaCreateSynthesis>;
export type IImportSchemaDTO = z.infer<typeof schemaImportSchema>; export type IImportSchemaDTO = z.infer<typeof schemaImportSchema>;
export type ICloneSchemaDTO = z.infer<typeof schemaCloneSchema>; export type ICloneSchemaDTO = z.infer<typeof schemaCloneSchema>;
@ -61,6 +63,9 @@ export type IUpdateOperationDTO = z.infer<typeof schemaUpdateOperation>;
/** Represents {@link IOperation} data, used in Delete action. */ /** Represents {@link IOperation} data, used in Delete action. */
export type IDeleteOperationDTO = z.infer<typeof schemaDeleteOperation>; export type IDeleteOperationDTO = z.infer<typeof schemaDeleteOperation>;
/** Represents {@link IOperation} reference type data, used in Delete action. */
export type IDeleteReferenceDTO = z.infer<typeof schemaDeleteReference>;
/** Represents target {@link IOperation}. */ /** Represents target {@link IOperation}. */
export interface ITargetOperation { export interface ITargetOperation {
layout: IOssLayout; layout: IOssLayout;
@ -119,6 +124,11 @@ export const schemaBlock = z.strictObject({
parent: z.number().nullable() parent: z.number().nullable()
}); });
const schemaReference = z.strictObject({
target: z.number(),
reference: z.number()
});
export const schemaPosition = z.strictObject({ export const schemaPosition = z.strictObject({
x: z.number(), x: z.number(),
y: z.number(), y: z.number(),
@ -148,6 +158,7 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
editors: z.number().array(), editors: z.number().array(),
operations: z.array(schemaOperation), operations: z.array(schemaOperation),
blocks: z.array(schemaBlock), blocks: z.array(schemaBlock),
references: z.array(schemaReference),
layout: schemaOssLayout, layout: schemaOssLayout,
arguments: z arguments: z
.object({ .object({
@ -188,6 +199,12 @@ export const schemaCreateSchema = z.strictObject({
position: schemaPosition position: schemaPosition
}); });
export const schemaCreateReference = z.strictObject({
target: z.number(),
layout: schemaOssLayout,
position: schemaPosition
});
export const schemaCloneSchema = z.strictObject({ export const schemaCloneSchema = z.strictObject({
layout: schemaOssLayout, layout: schemaOssLayout,
source_operation: z.number(), source_operation: z.number(),
@ -230,6 +247,13 @@ export const schemaDeleteOperation = z.strictObject({
delete_schema: z.boolean() delete_schema: z.boolean()
}); });
export const schemaDeleteReference = z.strictObject({
target: z.number(),
layout: schemaOssLayout,
keep_constituents: z.boolean(),
keep_connections: z.boolean()
});
export const schemaMoveItems = z.strictObject({ export const schemaMoveItems = z.strictObject({
layout: schemaOssLayout, layout: schemaOssLayout,
operations: z.array(z.number()), operations: z.array(z.number()),

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 ICreateReferenceDTO } from './types';
export const useCreateReference = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'create-reference'],
mutationFn: ossApi.createReference,
onSuccess: data => {
updateTimestamp(data.oss.id, data.oss.time_update);
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.oss.id }).queryKey, data.oss);
},
onError: () => client.invalidateQueries()
});
return {
createReference: (data: { itemID: number; data: ICreateReferenceDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -0,0 +1,29 @@
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 IDeleteReferenceDTO } from './types';
export const useDeleteReference = () => {
const client = useQueryClient();
const { updateTimestamp } = useUpdateTimestamp();
const mutation = useMutation({
mutationKey: [KEYS.global_mutation, ossApi.baseKey, 'delete-reference'],
mutationFn: ossApi.deleteReference,
onSuccess: async data => {
updateTimestamp(data.id, data.time_update);
client.setQueryData(ossApi.getOssQueryOptions({ itemID: data.id }).queryKey, data);
await Promise.allSettled([
client.invalidateQueries({ queryKey: KEYS.composite.libraryList }),
client.invalidateQueries({ queryKey: [KEYS.rsform] })
]);
},
onError: () => client.invalidateQueries()
});
return {
deleteReference: (data: { itemID: number; data: IDeleteReferenceDTO }) => mutation.mutateAsync(data)
};
};

View File

@ -47,12 +47,12 @@ export function InfoOperation({ operation }: InfoOperationProps) {
<p> <p>
<b>Тип:</b> {labelOperationType(operation.operation_type)} <b>Тип:</b> {labelOperationType(operation.operation_type)}
</p> </p>
{operation.is_import ? ( {operation.operation_type === OperationType.INPUT && operation.is_import ? (
<p> <p>
<b>КС не принадлежит ОСС</b> <b>КС не принадлежит ОСС</b>
</p> </p>
) : null} ) : null}
{operation.is_consolidation ? ( {operation.operation_type === OperationType.SYNTHESIS && operation.is_consolidation ? (
<p> <p>
<b>Ромбовидный синтез</b> <b>Ромбовидный синтез</b>
</p> </p>
@ -69,7 +69,7 @@ export function InfoOperation({ operation }: InfoOperationProps) {
{operation.description} {operation.description}
</p> </p>
) : null} ) : null}
{operation.substitutions.length > 0 ? ( {operation.operation_type === OperationType.SYNTHESIS && operation.substitutions.length > 0 ? (
<DataTable <DataTable
dense dense
noHeader noHeader

View File

@ -1,6 +1,7 @@
import { import {
IconConceptBlock, IconConceptBlock,
IconDownload, IconDownload,
IconReference,
IconRSForm, IconRSForm,
IconRSFormImported, IconRSFormImported,
IconRSFormOwned, IconRSFormOwned,
@ -18,14 +19,16 @@ interface OssStatsProps {
export function OssStats({ className, stats }: OssStatsProps) { export function OssStats({ className, stats }: OssStatsProps) {
return ( return (
<aside className={cn('grid grid-cols-4 gap-1 justify-items-end h-min select-none', className)}> <aside className={cn('grid grid-cols-3 gap-1 justify-items-end h-min select-none', className)}>
<div id='count_operations' className='w-fit flex gap-3 hover:cursor-default '> <div id='count_operations' className='w-fit flex gap-3 hover:cursor-default '>
<span>Всего</span> <span>Всего</span>
<span>{stats.count_all}</span> <span>{stats.count_all}</span>
</div> </div>
<ValueStats id='count_block' title='Блоки' icon={<IconConceptBlock size='1.25rem' />} value={stats.count_block} /> <ValueStats id='count_block' title='Блоки' icon={<IconConceptBlock size='1.25rem' />} value={stats.count_block} />
<ValueStats <ValueStats
id='count_inputs' id='count_inputs'
className='col-start-1'
title='Загрузка' title='Загрузка'
icon={<IconDownload size='1.25rem' />} icon={<IconDownload size='1.25rem' />}
value={stats.count_inputs} value={stats.count_inputs}
@ -36,6 +39,12 @@ export function OssStats({ className, stats }: OssStatsProps) {
icon={<IconSynthesis size='1.25rem' />} icon={<IconSynthesis size='1.25rem' />}
value={stats.count_synthesis} value={stats.count_synthesis}
/> />
<ValueStats
id='count_references'
title='Синтез'
icon={<IconReference size='1.25rem' />}
value={stats.count_references}
/>
<ValueStats <ValueStats
id='count_schemas' id='count_schemas'

View File

@ -9,13 +9,13 @@ import { Checkbox, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal'; import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { type IDeleteOperationDTO, type IOssLayout, schemaDeleteOperation } from '../backend/types'; import { type IDeleteOperationDTO, type IOssLayout, OperationType, schemaDeleteOperation } from '../backend/types';
import { useDeleteOperation } from '../backend/use-delete-operation'; import { useDeleteOperation } from '../backend/use-delete-operation';
import { type IOperation, type IOperationSchema } from '../models/oss'; import { type IOperationInput, type IOperationSchema, type IOperationSynthesis } from '../models/oss';
export interface DlgDeleteOperationProps { export interface DlgDeleteOperationProps {
oss: IOperationSchema; oss: IOperationSchema;
target: IOperation; target: IOperationInput | IOperationSynthesis;
layout: IOssLayout; layout: IOssLayout;
} }
@ -54,13 +54,13 @@ export function DlgDeleteOperation() {
<Checkbox <Checkbox
label='Удалить схему' label='Удалить схему'
titleHtml={ titleHtml={
target.is_import || target.result === null (target.operation_type === OperationType.INPUT && target.is_import) || target.result === null
? 'Привязанную схему нельзя удалить' ? 'Привязанную схему нельзя удалить'
: 'Удалить схему вместе с операцией' : 'Удалить схему вместе с операцией'
} }
value={field.value} value={field.value}
onChange={field.onChange} onChange={field.onChange}
disabled={target.is_import || target.result === null} disabled={(target.operation_type === OperationType.INPUT && target.is_import) || target.result === null}
/> />
)} )}
/> />

View File

@ -0,0 +1,78 @@
'use client';
import { Controller, useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { HelpTopic } from '@/features/help';
import { Checkbox, TextInput } from '@/components/input';
import { ModalForm } from '@/components/modal';
import { useDialogsStore } from '@/stores/dialogs';
import { type IDeleteReferenceDTO, type IOssLayout, schemaDeleteReference } from '../backend/types';
import { useDeleteReference } from '../backend/use-delete-reference';
import { type IOperationReference, type IOperationSchema } from '../models/oss';
export interface DlgDeleteReferenceProps {
oss: IOperationSchema;
target: IOperationReference;
layout: IOssLayout;
}
export function DlgDeleteReference() {
const { oss, target, layout } = useDialogsStore(state => state.props as DlgDeleteReferenceProps);
const { deleteReference } = useDeleteReference();
const { handleSubmit, control } = useForm<IDeleteReferenceDTO>({
resolver: zodResolver(schemaDeleteReference),
defaultValues: {
target: target.id,
layout: layout,
keep_constituents: false,
keep_connections: false
}
});
function onSubmit(data: IDeleteReferenceDTO) {
return deleteReference({ itemID: oss.id, data: data });
}
return (
<ModalForm
overflowVisible
header='Удаление операции'
submitText='Подтвердить удаление'
onSubmit={event => void handleSubmit(onSubmit)(event)}
className='w-140 pb-3 px-6 cc-column select-none'
helpTopic={HelpTopic.CC_PROPAGATION}
>
<TextInput disabled dense noBorder id='operation_alias' label='Операция' value={target.alias} />
<Controller
control={control}
name='keep_constituents'
render={({ field }) => (
<Checkbox
label='Сохранить наследованные конституенты'
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
value={field.value}
onChange={field.onChange}
disabled={target.result === null}
/>
)}
/>
<Controller
control={control}
name='keep_connections'
render={({ field }) => (
<Checkbox
label='Переадресовать связи на оригинал'
titleHtml='Связи аргументов будут перенаправлены на оригинал ссылки'
value={field.value}
onChange={field.onChange}
disabled={target.result === null}
/>
)}
/>
</ModalForm>
);
}

View File

@ -13,7 +13,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types'; import { type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types';
import { useUpdateOperation } from '../../backend/use-update-operation'; import { useUpdateOperation } from '../../backend/use-update-operation';
import { type IOperation } from '../../models/oss'; import { type IOperationInput, type IOperationSynthesis } from '../../models/oss';
import { type LayoutManager } from '../../models/oss-layout-api'; import { type LayoutManager } from '../../models/oss-layout-api';
import { TabArguments } from './tab-arguments'; import { TabArguments } from './tab-arguments';
@ -22,7 +22,7 @@ import { TabSubstitutions } from './tab-substitutions';
export interface DlgEditOperationProps { export interface DlgEditOperationProps {
manager: LayoutManager; manager: LayoutManager;
target: IOperation; target: IOperationInput | IOperationSynthesis;
} }
export const TabID = { export const TabID = {
@ -46,11 +46,14 @@ export function DlgEditOperation() {
description: target.description, description: target.description,
parent: target.parent parent: target.parent
}, },
arguments: target.arguments, arguments: target.operation_type === OperationType.SYNTHESIS ? target.arguments : [],
substitutions: target.substitutions.map(sub => ({ substitutions:
original: sub.original, target.operation_type === OperationType.SYNTHESIS
substitution: sub.substitution ? target.substitutions.map(sub => ({
})), original: sub.original,
substitution: sub.substitution
}))
: [],
layout: manager.layout layout: manager.layout
}, },
mode: 'onChange' mode: 'onChange'

View File

@ -11,12 +11,14 @@ import {
const labelOperationTypeRecord: Record<OperationType, string> = { const labelOperationTypeRecord: Record<OperationType, string> = {
[OperationType.INPUT]: 'Загрузка', [OperationType.INPUT]: 'Загрузка',
[OperationType.SYNTHESIS]: 'Синтез' [OperationType.SYNTHESIS]: 'Синтез',
[OperationType.REFERENCE]: 'Ссылка'
}; };
const describeOperationTypeRecord: Record<OperationType, string> = { const describeOperationTypeRecord: Record<OperationType, string> = {
[OperationType.INPUT]: 'Загрузка концептуальной схемы в ОСС', [OperationType.INPUT]: 'Загрузка концептуальной схемы в ОСС',
[OperationType.SYNTHESIS]: 'Синтез концептуальных схем' [OperationType.SYNTHESIS]: 'Синтез концептуальных схем',
[OperationType.REFERENCE]: 'Создание ссылки на результат операции'
}; };
/** Retrieves label for {@link OperationType}. */ /** Retrieves label for {@link OperationType}. */

View File

@ -4,7 +4,8 @@ import {
type ICreateSynthesisDTO, type ICreateSynthesisDTO,
type IImportSchemaDTO, type IImportSchemaDTO,
type INodePosition, type INodePosition,
type IOssLayout type IOssLayout,
OperationType
} from '../backend/types'; } from '../backend/types';
import { type IOperationSchema, NodeType } from './oss'; import { type IOperationSchema, NodeType } from './oss';
@ -258,7 +259,11 @@ export class LayoutManager {
} }
const freeInputs = this.oss.operations const freeInputs = this.oss.operations
.filter(operation => operation.arguments.length === 0 && operation.parent === null) .filter(
operation =>
operation.parent === null &&
(operation.operation_type !== OperationType.SYNTHESIS || operation.arguments.length === 0)
)
.map(operation => operation.nodeID); .map(operation => operation.nodeID);
let inputsPositions = this.layout.filter(pos => freeInputs.includes(pos.nodeID)); let inputsPositions = this.layout.filter(pos => freeInputs.includes(pos.nodeID));
if (inputsPositions.length === 0) { if (inputsPositions.length === 0) {

View File

@ -8,7 +8,8 @@ import {
type IBlockDTO, type IBlockDTO,
type ICstSubstituteInfo, type ICstSubstituteInfo,
type IOperationDTO, type IOperationDTO,
type IOperationSchemaDTO type IOperationSchemaDTO,
type OperationType
} from '../backend/types'; } from '../backend/types';
/** Represents OSS node type. */ /** Represents OSS node type. */
@ -18,26 +19,50 @@ export const NodeType = {
} as const; } as const;
export type NodeType = (typeof NodeType)[keyof typeof NodeType]; export type NodeType = (typeof NodeType)[keyof typeof NodeType];
/** Represents Operation. */ /** Represents OSS graph node. */
export interface IOperation extends IOperationDTO { export interface IOssNode {
nodeID: string; nodeID: string;
nodeType: typeof NodeType.OPERATION; nodeType: NodeType;
parent: number | null;
x: number; x: number;
y: number; y: number;
width: number;
height: number;
}
/** Represents Operation common attributes. */
export interface IOperationBase
extends IOssNode,
Pick<IOperationDTO, 'alias' | 'title' | 'description' | 'id' | 'operation_type' | 'result'> {
nodeType: typeof NodeType.OPERATION;
}
/** Represents Input Operation. */
export interface IOperationInput extends IOperationBase {
operation_type: typeof OperationType.INPUT;
is_import: boolean; is_import: boolean;
}
/** Represents Reference Operation. */
export interface IOperationReference extends IOperationBase {
operation_type: typeof OperationType.REFERENCE;
target: number;
}
/** Represents Synthesis Operation. */
export interface IOperationSynthesis extends IOperationBase {
operation_type: typeof OperationType.SYNTHESIS;
is_consolidation: boolean; // aka 'diamond synthesis' is_consolidation: boolean; // aka 'diamond synthesis'
substitutions: ICstSubstituteInfo[]; substitutions: ICstSubstituteInfo[];
arguments: number[]; arguments: number[];
} }
/** Represents Operation. */
export type IOperation = IOperationInput | IOperationReference | IOperationSynthesis;
/** Represents Block. */ /** Represents Block. */
export interface IBlock extends IBlockDTO { export interface IBlock extends IOssNode, IBlockDTO {
nodeID: string;
nodeType: typeof NodeType.BLOCK; nodeType: typeof NodeType.BLOCK;
x: number;
y: number;
width: number;
height: number;
} }
/** Represents item of OperationSchema. */ /** Represents item of OperationSchema. */
@ -51,10 +76,11 @@ export interface IOperationSchemaStats {
count_schemas: number; count_schemas: number;
count_owned: number; count_owned: number;
count_block: number; count_block: number;
count_references: number;
} }
/** Represents OperationSchema. */ /** Represents OperationSchema. */
export interface IOperationSchema extends IOperationSchemaDTO { export interface IOperationSchema extends Omit<IOperationSchemaDTO, 'operations'> {
operations: IOperation[]; operations: IOperation[];
blocks: IBlock[]; blocks: IBlock[];

View File

@ -47,10 +47,10 @@ export function ContextMenu({ isOpen, item, cursorX, cursorY, onHide }: ContextM
margin={cursorY >= window.innerHeight - MENU_HEIGHT ? 'mb-3' : 'mt-3'} margin={cursorY >= window.innerHeight - MENU_HEIGHT ? 'mb-3' : 'mt-3'}
> >
{!!item ? ( {!!item ? (
item.nodeType === NodeType.OPERATION ? ( item.nodeType === NodeType.BLOCK ? (
<MenuOperation operation={item} onHide={onHide} />
) : (
<MenuBlock block={item} onHide={onHide} /> <MenuBlock block={item} onHide={onHide} />
) : (
<MenuOperation operation={item} onHide={onHide} />
) )
) : null} ) : null}
</Dropdown> </Dropdown>

View File

@ -3,6 +3,7 @@ import { toast } from 'react-toastify';
import { useLibrary } from '@/features/library/backend/use-library'; import { useLibrary } from '@/features/library/backend/use-library';
import { useCloneSchema } from '@/features/oss/backend/use-clone-schema'; import { useCloneSchema } from '@/features/oss/backend/use-clone-schema';
import { useCreateReference } from '@/features/oss/backend/use-create-reference';
import { DropdownButton } from '@/components/dropdown'; import { DropdownButton } from '@/components/dropdown';
import { import {
@ -13,13 +14,13 @@ import {
IconEdit2, IconEdit2,
IconExecute, IconExecute,
IconNewRSForm, IconNewRSForm,
IconPhantom, IconReference,
IconRSForm IconRSForm
} from '@/components/icons'; } from '@/components/icons';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { errorMsg } from '@/utils/labels'; import { errorMsg } from '@/utils/labels';
import { notImplemented, prepareTooltip } from '@/utils/utils'; import { prepareTooltip } from '@/utils/utils';
import { OperationType } from '../../../../backend/types'; import { OperationType } from '../../../../backend/types';
import { useCreateInput } from '../../../../backend/use-create-input'; import { useCreateInput } from '../../../../backend/use-create-input';
@ -44,11 +45,13 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
const { createInput: inputCreate } = useCreateInput(); const { createInput: inputCreate } = useCreateInput();
const { executeOperation: operationExecute } = useExecuteOperation(); const { executeOperation: operationExecute } = useExecuteOperation();
const { cloneSchema } = useCloneSchema(); const { cloneSchema } = useCloneSchema();
const { createReference } = useCreateReference();
const showEditInput = useDialogsStore(state => state.showChangeInputSchema); const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents); const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents);
const showEditOperation = useDialogsStore(state => state.showEditOperation); const showEditOperation = useDialogsStore(state => state.showEditOperation);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation); const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
const showDeleteReference = useDialogsStore(state => state.showDeleteReference);
const readyForSynthesis = (() => { const readyForSynthesis = (() => {
if (operation?.operation_type !== OperationType.SYNTHESIS) { if (operation?.operation_type !== OperationType.SYNTHESIS) {
@ -92,7 +95,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
} }
function handleEditOperation() { function handleEditOperation() {
if (!operation) { if (!operation || operation.operation_type === OperationType.REFERENCE) {
return; return;
} }
onHide(); onHide();
@ -107,11 +110,22 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
return; return;
} }
onHide(); onHide();
showDeleteOperation({ switch (operation.operation_type) {
oss: schema, case OperationType.REFERENCE:
target: operation, showDeleteReference({
layout: getLayout() oss: schema,
}); target: operation,
layout: getLayout()
});
break;
case OperationType.INPUT:
case OperationType.SYNTHESIS:
showDeleteOperation({
oss: schema,
target: operation,
layout: getLayout()
});
}
} }
function handleOperationExecute() { function handleOperationExecute() {
@ -152,9 +166,24 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
}); });
} }
function handleCreatePhantom() { function handleCreateReference() {
onHide(); onHide();
notImplemented();
const layout = getLayout();
const manager = new LayoutManager(schema, layout);
const newPosition = manager.newClonePosition(operation.nodeID);
if (!newPosition) {
return;
}
void createReference({
itemID: schema.id,
data: {
target: operation.id,
layout: layout,
position: newPosition
}
}).then(response => setTimeout(() => setSelected([`o${response.new_operation}`]), PARAMETER.refreshTimeout));
} }
function handleClone() { function handleClone() {
@ -187,7 +216,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
disabled={!isMutable || isProcessing} disabled={!isMutable || isProcessing}
/> />
{operation?.result ? ( {operation.result ? (
<DropdownButton <DropdownButton
text='Открыть схему' text='Открыть схему'
titleHtml={prepareTooltip('Открыть привязанную КС', 'Двойной клик')} titleHtml={prepareTooltip('Открыть привязанную КС', 'Двойной клик')}
@ -197,7 +226,10 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
disabled={isProcessing} disabled={isProcessing}
/> />
) : null} ) : null}
{isMutable && !operation?.result && operation?.arguments.length === 0 ? ( {isMutable &&
!operation.result &&
operation.operation_type === OperationType.SYNTHESIS &&
operation.arguments.length === 0 ? (
<DropdownButton <DropdownButton
text='Создать схему' text='Создать схему'
title='Создать пустую схему' title='Создать пустую схему'
@ -215,7 +247,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
disabled={isProcessing} disabled={isProcessing}
/> />
) : null} ) : null}
{isMutable && !operation?.result && operation?.operation_type === OperationType.SYNTHESIS ? ( {isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? (
<DropdownButton <DropdownButton
text='Активировать синтез' text='Активировать синтез'
titleHtml={ titleHtml={
@ -230,7 +262,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
/> />
) : null} ) : null}
{isMutable && operation?.result ? ( {isMutable && operation.result && operation.operation_type !== OperationType.REFERENCE ? (
<DropdownButton <DropdownButton
text='Конституенты' text='Конституенты'
titleHtml='Перенос конституент</br>между схемами' titleHtml='Перенос конституент</br>между схемами'
@ -241,17 +273,17 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
/> />
) : null} ) : null}
{isMutable ? ( {isMutable && operation.operation_type !== OperationType.REFERENCE ? (
<DropdownButton <DropdownButton
text='Создать ссылку' text='Создать ссылку'
title='Создать ссылку на результат операции' title='Создать ссылку на результат операции'
icon={<IconPhantom size='1rem' className='icon-green' />} icon={<IconReference size='1rem' className='icon-green' />}
onClick={handleCreatePhantom} onClick={handleCreateReference}
disabled={isProcessing} disabled={isProcessing}
/> />
) : null} ) : null}
{isMutable ? ( {isMutable && operation.operation_type !== OperationType.REFERENCE ? (
<DropdownButton <DropdownButton
text='Клонировать' text='Клонировать'
title='Создать и загрузить копию концептуальной схемы' title='Создать и загрузить копию концептуальной схемы'

View File

@ -22,6 +22,8 @@ interface NodeCoreProps {
export function NodeCore({ node }: NodeCoreProps) { export function NodeCore({ node }: NodeCoreProps) {
const { selectedItems, schema } = useOssEdit(); const { selectedItems, schema } = useOssEdit();
const opType = node.data.operation.operation_type;
const focus = selectedItems.length === 1 ? selectedItems[0] : null; const focus = selectedItems.length === 1 ? selectedItems[0] : null;
const isChild = (!!focus && schema.hierarchy.at(focus.nodeID)?.outputs.includes(node.data.operation.nodeID)) ?? false; const isChild = (!!focus && schema.hierarchy.at(focus.nodeID)?.outputs.includes(node.data.operation.nodeID)) ?? false;
@ -36,6 +38,7 @@ export function NodeCore({ node }: NodeCoreProps) {
className={cn( className={cn(
'cc-node-operation h-[40px] w-[150px]', 'cc-node-operation h-[40px] w-[150px]',
'relative flex items-center justify-center p-[2px]', 'relative flex items-center justify-center p-[2px]',
opType === OperationType.REFERENCE && 'border-dashed',
isChild && 'border-accent-orange' isChild && 'border-accent-orange'
)} )}
> >
@ -45,7 +48,7 @@ export function NodeCore({ node }: NodeCoreProps) {
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'} title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
icon={<IconRSForm className={hasFile ? 'text-constructive' : 'text-destructive'} size='12px' />} icon={<IconRSForm className={hasFile ? 'text-constructive' : 'text-destructive'} size='12px' />}
/> />
{node.data.operation.is_consolidation ? ( {opType === OperationType.SYNTHESIS && node.data.operation.is_consolidation ? (
<Indicator <Indicator
noPadding noPadding
titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент' titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент'
@ -66,11 +69,11 @@ export function NodeCore({ node }: NodeCoreProps) {
</div> </div>
) : null} ) : null}
{node.data.operation.operation_type === OperationType.INPUT ? ( {opType === OperationType.INPUT ? (
<div className='absolute top-[3px] right-1/2 translate-x-1/2 border-t w-[30px]' /> <div className='absolute top-[3px] right-1/2 translate-x-1/2 border-t w-[30px]' />
) : null} ) : null}
{node.data.operation.is_import ? ( {opType === OperationType.INPUT && node.data.operation.is_import ? (
<div className='absolute left-[3px] top-1/2 -translate-y-1/2 border-r rounded-none bg-input h-[22px]' /> <div className='absolute left-[3px] top-1/2 -translate-y-1/2 border-r rounded-none bg-input h-[22px]' />
) : null} ) : null}

View File

@ -2,10 +2,12 @@ import { type NodeTypes } from 'reactflow';
import { BlockNode } from './block-node'; import { BlockNode } from './block-node';
import { InputNode } from './input-node'; import { InputNode } from './input-node';
import { OperationNode } from './operation-node'; import { ReferenceNode } from './reference-node';
import { SynthesisNode } from './synthesis-node';
export const OssNodeTypes: NodeTypes = { export const OssNodeTypes: NodeTypes = {
synthesis: OperationNode,
input: InputNode, input: InputNode,
synthesis: SynthesisNode,
reference: ReferenceNode,
block: BlockNode block: BlockNode
} as const; } as const;

View File

@ -0,0 +1,14 @@
import { Handle, Position } from 'reactflow';
import { type OperationInternalNode } from '../../../../models/oss-layout';
import { NodeCore } from './node-core';
export function ReferenceNode(node: OperationInternalNode) {
return (
<>
<NodeCore node={node} />
<Handle type='source' position={Position.Bottom} className='-translate-y-[1px]' />
</>
);
}

View File

@ -6,7 +6,7 @@ import { type OperationInternalNode } from '../../../../models/oss-layout';
import { NodeCore } from './node-core'; import { NodeCore } from './node-core';
export function OperationNode(node: OperationInternalNode) { export function SynthesisNode(node: OperationInternalNode) {
return ( return (
<> <>
<Handle type='target' position={Position.Top} id='left' style={{ left: 40, top: -2 }} /> <Handle type='target' position={Position.Top} id='left' style={{ left: 40, top: -2 }} />

View File

@ -11,6 +11,7 @@ import { PARAMETER } from '@/utils/constants';
import { promptText } from '@/utils/labels'; import { promptText } from '@/utils/labels';
import { withPreventDefault } from '@/utils/utils'; import { withPreventDefault } from '@/utils/utils';
import { OperationType } from '../../../backend/types';
import { useDeleteBlock } from '../../../backend/use-delete-block'; import { useDeleteBlock } from '../../../backend/use-delete-block';
import { useMutatingOss } from '../../../backend/use-mutating-oss'; import { useMutatingOss } from '../../../backend/use-mutating-oss';
import { useUpdateLayout } from '../../../backend/use-update-layout'; import { useUpdateLayout } from '../../../backend/use-update-layout';
@ -68,6 +69,7 @@ export function OssFlow() {
const showCreateBlock = useDialogsStore(state => state.showCreateBlock); const showCreateBlock = useDialogsStore(state => state.showCreateBlock);
const showCreateSchema = useDialogsStore(state => state.showCreateSchema); const showCreateSchema = useDialogsStore(state => state.showCreateSchema);
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation); const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
const showDeleteReference = useDialogsStore(state => state.showDeleteReference);
const showEditBlock = useDialogsStore(state => state.showEditBlock); const showEditBlock = useDialogsStore(state => state.showEditBlock);
const showImportSchema = useDialogsStore(state => state.showImportSchema); const showImportSchema = useDialogsStore(state => state.showImportSchema);
@ -150,11 +152,22 @@ export function OssFlow() {
if (!canDeleteOperation(item)) { if (!canDeleteOperation(item)) {
return; return;
} }
showDeleteOperation({ switch (item.operation_type) {
oss: schema, case OperationType.REFERENCE:
target: item, showDeleteReference({
layout: getLayout() oss: schema,
}); target: item,
layout: getLayout()
});
break;
case OperationType.INPUT:
case OperationType.SYNTHESIS:
showDeleteOperation({
oss: schema,
target: item,
layout: getLayout()
});
}
} else { } else {
if (!window.confirm(promptText.deleteBlock)) { if (!window.confirm(promptText.deleteBlock)) {
return; return;

View File

@ -16,8 +16,11 @@ export function BlockStats({ target, oss }: BlockStatsProps) {
count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length, count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length,
count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length, count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
count_schemas: operations.filter(item => !!item.result).length, count_schemas: operations.filter(item => !!item.result).length,
count_owned: operations.filter(item => !!item.result && !item.is_import).length, count_owned: operations.filter(
count_block: contents.length - operations.length item => !!item.result && (item.operation_type !== OperationType.INPUT || !item.is_import)
).length,
count_block: contents.length - operations.length,
count_references: operations.filter(item => item.operation_type === OperationType.REFERENCE).length
}; };
return <OssStats stats={blockStats} className='pr-3' />; return <OssStats stats={blockStats} className='pr-3' />;

View File

@ -10,6 +10,7 @@ import { useAppLayoutStore, useMainHeight } from '@/stores/app-layout';
import { usePreferencesStore } from '@/stores/preferences'; import { usePreferencesStore } from '@/stores/preferences';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { OperationType } from '../../../../backend/types';
import { NodeType } from '../../../../models/oss'; import { NodeType } from '../../../../models/oss';
import { useOssEdit } from '../../oss-edit-context'; import { useOssEdit } from '../../oss-edit-context';
@ -80,7 +81,12 @@ export function SidePanel({ isMounted, className }: SidePanelProps) {
<div className='text-center text-sm cc-fade-in'>Отсутствует концептуальная схема для выбранной операции</div> <div className='text-center text-sm cc-fade-in'>Отсутствует концептуальная схема для выбранной операции</div>
) : selectedOperation && selectedSchema && debouncedMounted ? ( ) : selectedOperation && selectedSchema && debouncedMounted ? (
<Suspense fallback={<Loader />}> <Suspense fallback={<Loader />}>
<ViewSchema schemaID={selectedSchema} isMutable={isMutable && !selectedOperation.is_import} /> <ViewSchema
schemaID={selectedSchema}
isMutable={
isMutable && (selectedOperation.operation_type !== OperationType.INPUT || !selectedOperation.is_import)
}
/>
</Suspense> </Suspense>
) : null} ) : null}
{selectedBlock ? <BlockStats target={selectedBlock} oss={schema} /> : null} {selectedBlock ? <BlockStats target={selectedBlock} oss={schema} /> : null}

View File

@ -227,7 +227,7 @@ export function ToolbarSchema({
/> />
<DropdownButton <DropdownButton
title='Перейти к концептуальной схеме' title='Перейти к концептуальной схеме'
text='перейти к схеме' text='Открыть КС'
icon={<IconRSForm size='1rem' className='icon-primary' />} icon={<IconRSForm size='1rem' className='icon-primary' />}
onClick={navigateRSForm} onClick={navigateRSForm}
/> />

View File

@ -6,6 +6,7 @@ import { TextArea, TextInput } from '@/components/input';
import { CstType, type IUpdateConstituentaDTO } from '../../backend/types'; import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
import { IconCrucialValue } from '../../components/icon-crucial-value'; import { IconCrucialValue } from '../../components/icon-crucial-value';
import { RSInput } from '../../components/rs-input';
import { SelectCstType } from '../../components/select-cst-type'; import { SelectCstType } from '../../components/select-cst-type';
import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels'; import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels';
import { type IConstituenta, type IRSForm } from '../../models/rsform'; import { type IConstituenta, type IRSForm } from '../../models/rsform';
@ -97,9 +98,9 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
name='item_data.definition_formal' name='item_data.definition_formal'
render={({ field }) => render={({ field }) =>
!!field.value || (!isElementary && !target.is_inherited) ? ( !!field.value || (!isElementary && !target.is_inherited) ? (
<TextArea <RSInput
id='dlg_cst_expression' id='dlg_cst_expression'
fitContent noTooltip
label={ label={
cst_type === CstType.STRUCTURED cst_type === CstType.STRUCTURED
? 'Область определения' ? 'Область определения'
@ -109,9 +110,9 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
} }
placeholder={getRSDefinitionPlaceholder(cst_type)} placeholder={getRSDefinitionPlaceholder(cst_type)}
className='max-h-15' className='max-h-15'
schema={schema}
value={field.value} value={field.value}
onChange={field.onChange} onChange={field.onChange}
error={errors.item_data?.definition_formal}
disabled={target.is_inherited} disabled={target.is_inherited}
/> />
) : ( ) : (

View File

@ -11,6 +11,7 @@ import { type DlgCreateBlockProps } from '@/features/oss/dialogs/dlg-create-bloc
import { type DlgCreateSchemaProps } from '@/features/oss/dialogs/dlg-create-schema'; 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 DlgCreateSynthesisProps } from '@/features/oss/dialogs/dlg-create-synthesis/dlg-create-synthesis';
import { type DlgDeleteOperationProps } from '@/features/oss/dialogs/dlg-delete-operation'; import { type DlgDeleteOperationProps } from '@/features/oss/dialogs/dlg-delete-operation';
import { type DlgDeleteReferenceProps } from '@/features/oss/dialogs/dlg-delete-reference';
import { type DlgEditBlockProps } from '@/features/oss/dialogs/dlg-edit-block'; 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 DlgEditOperationProps } from '@/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation';
import { type DlgImportSchemaProps } from '@/features/oss/dialogs/dlg-import-schema'; import { type DlgImportSchemaProps } from '@/features/oss/dialogs/dlg-import-schema';
@ -47,31 +48,32 @@ export const DialogType = {
CREATE_SYNTHESIS: 9, CREATE_SYNTHESIS: 9,
EDIT_OPERATION: 10, EDIT_OPERATION: 10,
DELETE_OPERATION: 11, DELETE_OPERATION: 11,
CHANGE_INPUT_SCHEMA: 12, DELETE_REFERENCE: 12,
RELOCATE_CONSTITUENTS: 13, CHANGE_INPUT_SCHEMA: 13,
OSS_SETTINGS: 14, RELOCATE_CONSTITUENTS: 14,
EDIT_CONSTITUENTA: 15, OSS_SETTINGS: 15,
EDIT_CONSTITUENTA: 16,
CLONE_LIBRARY_ITEM: 16, CLONE_LIBRARY_ITEM: 17,
UPLOAD_RSFORM: 17, UPLOAD_RSFORM: 18,
EDIT_EDITORS: 18, EDIT_EDITORS: 19,
EDIT_VERSIONS: 19, EDIT_VERSIONS: 20,
CHANGE_LOCATION: 20, CHANGE_LOCATION: 21,
EDIT_REFERENCE: 21, EDIT_REFERENCE: 22,
EDIT_WORD_FORMS: 22, EDIT_WORD_FORMS: 23,
INLINE_SYNTHESIS: 23, INLINE_SYNTHESIS: 24,
SHOW_QR_CODE: 24, SHOW_QR_CODE: 25,
SHOW_AST: 25, SHOW_AST: 26,
SHOW_TYPE_GRAPH: 26, SHOW_TYPE_GRAPH: 27,
GRAPH_PARAMETERS: 27, GRAPH_PARAMETERS: 28,
SHOW_TERM_GRAPH: 28, SHOW_TERM_GRAPH: 29,
CREATE_SCHEMA: 29, CREATE_SCHEMA: 30,
IMPORT_SCHEMA: 30, IMPORT_SCHEMA: 31,
AI_PROMPT: 31, AI_PROMPT: 32,
CREATE_PROMPT_TEMPLATE: 32 CREATE_PROMPT_TEMPLATE: 33
} as const; } as const;
export type DialogType = (typeof DialogType)[keyof typeof DialogType]; export type DialogType = (typeof DialogType)[keyof typeof DialogType];
@ -104,6 +106,7 @@ interface DialogsStore {
showCloneLibraryItem: (props: DlgCloneLibraryItemProps) => void; showCloneLibraryItem: (props: DlgCloneLibraryItemProps) => void;
showCreateVersion: (props: DlgCreateVersionProps) => void; showCreateVersion: (props: DlgCreateVersionProps) => void;
showDeleteOperation: (props: DlgDeleteOperationProps) => void; showDeleteOperation: (props: DlgDeleteOperationProps) => void;
showDeleteReference: (props: DlgDeleteReferenceProps) => void;
showGraphParams: () => void; showGraphParams: () => void;
showOssOptions: () => void; showOssOptions: () => void;
showRelocateConstituents: (props: DlgRelocateConstituentsProps) => void; showRelocateConstituents: (props: DlgRelocateConstituentsProps) => void;
@ -148,6 +151,7 @@ export const useDialogsStore = create<DialogsStore>()(set => ({
showCloneLibraryItem: props => set({ active: DialogType.CLONE_LIBRARY_ITEM, props: props }), showCloneLibraryItem: props => set({ active: DialogType.CLONE_LIBRARY_ITEM, props: props }),
showCreateVersion: props => set({ active: DialogType.CREATE_VERSION, props: props }), showCreateVersion: props => set({ active: DialogType.CREATE_VERSION, props: props }),
showDeleteOperation: props => set({ active: DialogType.DELETE_OPERATION, props: props }), showDeleteOperation: props => set({ active: DialogType.DELETE_OPERATION, props: props }),
showDeleteReference: props => set({ active: DialogType.DELETE_REFERENCE, props: props }),
showGraphParams: () => set({ active: DialogType.GRAPH_PARAMETERS, props: null }), showGraphParams: () => set({ active: DialogType.GRAPH_PARAMETERS, props: null }),
showOssOptions: () => set({ active: DialogType.OSS_SETTINGS, props: null }), showOssOptions: () => set({ active: DialogType.OSS_SETTINGS, props: null }),
showRelocateConstituents: props => set({ active: DialogType.RELOCATE_CONSTITUENTS, props: props }), showRelocateConstituents: props => set({ active: DialogType.RELOCATE_CONSTITUENTS, props: props }),

View File

@ -165,7 +165,8 @@
} }
.react-flow__node-input, .react-flow__node-input,
.react-flow__node-synthesis { .react-flow__node-synthesis,
.react-flow__node-reference {
border-radius: 5px; border-radius: 5px;
border-width: 0; border-width: 0;