mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-13 20:30:36 +03:00
F: Implementing references
This commit is contained in:
parent
88f8c4523a
commit
9c51e368a5
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -128,6 +128,7 @@
|
|||
"perfectivity",
|
||||
"PNCT",
|
||||
"ponomarev",
|
||||
"popleft",
|
||||
"PRCL",
|
||||
"PRTF",
|
||||
"PRTS",
|
||||
|
|
|
@ -23,6 +23,7 @@ from apps.rsform.models import (
|
|||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation, OperationType
|
||||
from .Reference import Reference
|
||||
from .Substitution import Substitution
|
||||
|
||||
CstMapping = dict[str, Optional[Constituenta]]
|
||||
|
@ -36,32 +37,31 @@ class OperationSchemaCached:
|
|||
self.model = model
|
||||
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. '''
|
||||
if keep_connections:
|
||||
referred_operations = target.getQ_reference_target()
|
||||
if len(referred_operations) == 1:
|
||||
referred_operation = referred_operations[0]
|
||||
for arg in target.getQ_as_argument():
|
||||
arg.pk = None
|
||||
arg.argument = referred_operation
|
||||
arg.save()
|
||||
else:
|
||||
pass
|
||||
# if target.result_id is not None:
|
||||
# self.before_delete_cst(schema, schema.cache.constituents) # TODO: use operation instead of schema
|
||||
target.delete()
|
||||
if not keep_connections:
|
||||
self.delete_operation(target, keep_constituents)
|
||||
return
|
||||
self.cache.ensure_loaded_subs()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
reference_target = self.cache.reference_target.get(target)
|
||||
if reference_target:
|
||||
for arg in operation.getQ_as_argument():
|
||||
arg.argument_id = reference_target
|
||||
arg.save()
|
||||
self.cache.remove_operation(target)
|
||||
operation.delete()
|
||||
|
||||
|
||||
def delete_operation(self, target: int, keep_constituents: bool = False):
|
||||
''' Delete Operation. '''
|
||||
self.cache.ensure_loaded_subs()
|
||||
operation = self.cache.operation_by_id[target]
|
||||
schema = self.cache.get_schema(operation)
|
||||
children = self.cache.graph.outputs[target]
|
||||
if schema is not None and len(children) > 0:
|
||||
ids = [cst.pk for cst in schema.cache.constituents]
|
||||
if operation.result is not None and len(children) > 0:
|
||||
ids = list(Constituenta.objects.filter(schema=operation.result).values_list('pk', flat=True))
|
||||
if not keep_constituents:
|
||||
self.before_delete_cst(schema.model.pk, ids)
|
||||
self._cascade_delete_inherited(operation.pk, ids)
|
||||
else:
|
||||
inheritance_to_delete: list[Inheritance] = []
|
||||
for child_id in children:
|
||||
|
@ -707,17 +707,23 @@ class OssCache:
|
|||
self._schemas: list[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.graph = Graph[int]()
|
||||
for operation in self.operations:
|
||||
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 \
|
||||
.filter(operation__oss=self._oss.model) \
|
||||
.only('operation_id', 'argument_id') \
|
||||
.order_by('order')
|
||||
for argument in arguments:
|
||||
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.substitutions: dict[int, list[Substitution]] = {}
|
||||
|
@ -785,18 +791,12 @@ class OssCache:
|
|||
schema.cache.ensure_loaded()
|
||||
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:
|
||||
''' Insert new argument. '''
|
||||
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:
|
||||
''' Insert new inheritance. '''
|
||||
|
@ -832,6 +832,8 @@ class OssCache:
|
|||
del self._schema_by_id[target.result_id]
|
||||
self.operations.remove(self.operation_by_id[operation])
|
||||
del self.operation_by_id[operation]
|
||||
if operation in self.reference_target:
|
||||
del self.reference_target[operation]
|
||||
if self.is_loaded_subs:
|
||||
del self.substitutions[operation]
|
||||
del self.inheritance[operation]
|
||||
|
@ -839,6 +841,10 @@ class OssCache:
|
|||
def remove_argument(self, argument: Argument) -> None:
|
||||
''' Remove argument from cache. '''
|
||||
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:
|
||||
''' Remove substitution from cache. '''
|
||||
|
|
|
@ -16,6 +16,7 @@ from .data_access import (
|
|||
MoveItemsSerializer,
|
||||
OperationSchemaSerializer,
|
||||
OperationSerializer,
|
||||
ReferenceSerializer,
|
||||
RelocateConstituentsSerializer,
|
||||
SetOperationInputSerializer,
|
||||
TargetOperationSerializer,
|
||||
|
|
|
@ -13,7 +13,16 @@ from apps.rsform.serializers import SubstitutionSerializerBase
|
|||
from shared import messages as msg
|
||||
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
|
||||
|
||||
|
||||
|
@ -45,6 +54,14 @@ class ArgumentSerializer(StrictModelSerializer):
|
|||
fields = ('operation', 'argument')
|
||||
|
||||
|
||||
class ReferenceSerializer(StrictModelSerializer):
|
||||
''' Serializer: Reference data. '''
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Reference
|
||||
fields = ('reference', 'target')
|
||||
|
||||
|
||||
class CreateBlockSerializer(StrictSerializer):
|
||||
''' Serializer: Block creation. '''
|
||||
class BlockCreateData(StrictModelSerializer):
|
||||
|
@ -444,6 +461,7 @@ class DeleteReferenceSerializer(StrictSerializer):
|
|||
)
|
||||
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'operation_type'))
|
||||
keep_connections = serializers.BooleanField(default=False, required=False)
|
||||
keep_constituents = serializers.BooleanField(default=False, required=False)
|
||||
|
||||
def validate(self, attrs):
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
|
@ -517,6 +535,9 @@ class OperationSchemaSerializer(StrictModelSerializer):
|
|||
substitutions = serializers.ListField(
|
||||
child=SubstitutionExSerializer()
|
||||
)
|
||||
references = serializers.ListField(
|
||||
child=ReferenceSerializer()
|
||||
)
|
||||
layout = serializers.ListField(
|
||||
child=NodeSerializer()
|
||||
)
|
||||
|
@ -534,6 +555,7 @@ class OperationSchemaSerializer(StrictModelSerializer):
|
|||
result['blocks'] = []
|
||||
result['arguments'] = []
|
||||
result['substitutions'] = []
|
||||
result['references'] = []
|
||||
for operation in Operation.objects.filter(oss=instance).order_by('pk'):
|
||||
operation_data = OperationSerializer(operation).data
|
||||
operation_result = operation.result
|
||||
|
@ -556,6 +578,9 @@ class OperationSchemaSerializer(StrictModelSerializer):
|
|||
substitution_term=F('substitution__term_resolved'),
|
||||
).order_by('pk'):
|
||||
result['substitutions'].append(substitution)
|
||||
for reference in Reference.objects.filter(target__oss=instance).order_by('pk'):
|
||||
result['references'].append(ReferenceSerializer(reference).data)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
''' Tests for Django Models. '''
|
||||
from .t_Argument import *
|
||||
from .t_Inheritance import *
|
||||
from .t_Layout import *
|
||||
from .t_Operation import *
|
||||
from .t_Reference import *
|
||||
from .t_Substitution import *
|
||||
|
|
39
rsconcept/backend/apps/oss/tests/s_models/t_Layout.py
Normal file
39
rsconcept/backend/apps/oss/tests/s_models/t_Layout.py
Normal 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)
|
44
rsconcept/backend/apps/oss/tests/s_models/t_Reference.py
Normal file
44
rsconcept/backend/apps/oss/tests/s_models/t_Reference.py
Normal 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)
|
|
@ -2,4 +2,5 @@
|
|||
from .t_attributes import *
|
||||
from .t_constituents import *
|
||||
from .t_operations import *
|
||||
from .t_references import *
|
||||
from .t_substitutions import *
|
||||
|
|
146
rsconcept/backend/apps/oss/tests/s_propagation/t_references.py
Normal file
146
rsconcept/backend/apps/oss/tests/s_propagation/t_references.py
Normal file
|
@ -0,0 +1,146 @@
|
|||
''' 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.ks1X2
|
||||
}])
|
||||
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 X4 S1 D1 D2 D3',
|
||||
convention='KS5D4'
|
||||
)
|
||||
|
||||
self.operation6 = self.owned.create_operation(
|
||||
alias='6',
|
||||
operation_type=OperationType.SYNTHESIS
|
||||
)
|
||||
self.owned.set_arguments(self.operation6.pk, [self.operation2, self.operation3])
|
||||
self.owned.set_substitutions(self.operation6.pk, [{
|
||||
'original': self.ks2X1,
|
||||
'substitution': self.ks1X1
|
||||
}])
|
||||
self.owned.execute_operation(self.operation6)
|
||||
self.operation6.refresh_from_db()
|
||||
self.ks6 = RSForm(self.operation6.result)
|
||||
self.ks6D2 = self.ks6.insert_last(
|
||||
alias='D2',
|
||||
definition_formal=r'X1 X2 X3 S1 D1',
|
||||
convention='KS6D2'
|
||||
)
|
||||
|
||||
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},
|
||||
{'nodeID': 'o' + str(self.operation6.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)
|
||||
self.assertEqual(self.ks1.constituentsQ().count(), 3)
|
||||
self.assertEqual(self.ks2.constituentsQ().count(), 3)
|
||||
self.assertEqual(self.ks4.constituentsQ().count(), 6)
|
||||
self.assertEqual(self.ks5.constituentsQ().count(), 9)
|
||||
self.assertEqual(self.ks6.constituentsQ().count(), 6)
|
||||
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_target_propagation(self):
|
||||
''' Test propagation when deleting a target operation. '''
|
||||
data = {
|
||||
'layout': self.layout_data,
|
||||
'target': self.operation1.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.assertEqual(self.ks6.constituentsQ().count(), 4)
|
||||
# self.assertEqual(self.ks5.constituentsQ().count(), 5)
|
||||
|
||||
# TODO: add more tests
|
|
@ -683,7 +683,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
with transaction.atomic():
|
||||
oss = m.OperationSchemaCached(item)
|
||||
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'])
|
||||
|
||||
return Response(
|
||||
|
|
|
@ -45,6 +45,11 @@ const DlgDeleteOperation = React.lazy(() =>
|
|||
default: module.DlgDeleteOperation
|
||||
}))
|
||||
);
|
||||
const DlgDeleteReference = React.lazy(() =>
|
||||
import('@/features/oss/dialogs/dlg-delete-reference').then(module => ({
|
||||
default: module.DlgDeleteReference
|
||||
}))
|
||||
);
|
||||
const DlgEditEditors = React.lazy(() =>
|
||||
import('@/features/library/dialogs/dlg-edit-editors').then(module => ({
|
||||
default: module.DlgEditEditors
|
||||
|
@ -196,6 +201,8 @@ export const GlobalDialogs = () => {
|
|||
return <DlgCreateVersion />;
|
||||
case DialogType.DELETE_OPERATION:
|
||||
return <DlgDeleteOperation />;
|
||||
case DialogType.DELETE_REFERENCE:
|
||||
return <DlgDeleteReference />;
|
||||
case DialogType.GRAPH_PARAMETERS:
|
||||
return <DlgGraphParams />;
|
||||
case DialogType.RELOCATE_CONSTITUENTS:
|
||||
|
|
|
@ -95,6 +95,8 @@ export { TbHexagonLetterD as IconCstTerm } from 'react-icons/tb';
|
|||
export { TbHexagonLetterF as IconCstFunction } from 'react-icons/tb';
|
||||
export { TbHexagonLetterP as IconCstPredicate } 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 { LuDna as IconTerminology } from 'react-icons/lu';
|
||||
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 { LuPower as IconKeepAliasOn } from 'react-icons/lu';
|
||||
export { LuPowerOff as IconKeepAliasOff } from 'react-icons/lu';
|
||||
export { VscReferences as IconPhantom } from 'react-icons/vsc';
|
||||
|
||||
// ===== Domain actions =====
|
||||
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 { FaSortAmountDownAlt as IconSortList } from 'react-icons/fa';
|
||||
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 { LuWandSparkles as IconGenerateNames } from 'react-icons/lu';
|
||||
export { GrConnect as IconConnect } from 'react-icons/gr';
|
||||
|
|
|
@ -9,10 +9,12 @@ import {
|
|||
type ICloneSchemaDTO,
|
||||
type IConstituentaReference,
|
||||
type ICreateBlockDTO,
|
||||
type ICreateReferenceDTO,
|
||||
type ICreateSchemaDTO,
|
||||
type ICreateSynthesisDTO,
|
||||
type IDeleteBlockDTO,
|
||||
type IDeleteOperationDTO,
|
||||
type IDeleteReferenceDTO,
|
||||
type IImportSchemaDTO,
|
||||
type IInputCreatedResponse,
|
||||
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 }) =>
|
||||
axiosPost<ICreateSchemaDTO, IOperationCreatedResponse>({
|
||||
schema: schemaOperationCreatedResponse,
|
||||
|
|
|
@ -27,9 +27,10 @@ export class OssLoader {
|
|||
private itemByNodeID = new Map<string, IOssItem>();
|
||||
private blockByID = new Map<number, IBlock>();
|
||||
private schemaIDs: number[] = [];
|
||||
private extendedGraph = new Graph();
|
||||
|
||||
constructor(input: RO<IOperationSchemaDTO>) {
|
||||
this.oss = structuredClone(input) as IOperationSchema;
|
||||
this.oss = structuredClone(input) as unknown as IOperationSchema;
|
||||
}
|
||||
|
||||
produceOSS(): IOperationSchema {
|
||||
|
@ -47,6 +48,7 @@ export class OssLoader {
|
|||
result.hierarchy = this.hierarchy;
|
||||
result.schemas = this.schemaIDs;
|
||||
result.stats = this.calculateStats();
|
||||
result.extendedGraph = this.extendedGraph;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -57,6 +59,7 @@ export class OssLoader {
|
|||
this.itemByNodeID.set(operation.nodeID, operation);
|
||||
this.operationByID.set(operation.id, operation);
|
||||
this.graph.addNode(operation.id);
|
||||
this.extendedGraph.addNode(operation.id);
|
||||
this.hierarchy.addNode(operation.nodeID);
|
||||
if (operation.parent) {
|
||||
this.hierarchy.addEdge(constructNodeID(NodeType.BLOCK, operation.parent), operation.nodeID);
|
||||
|
@ -75,7 +78,13 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private createGraph() {
|
||||
this.oss.arguments.forEach(argument => this.graph.addEdge(argument.argument, argument.operation));
|
||||
this.oss.arguments.forEach(argument => {
|
||||
this.graph.addEdge(argument.argument, argument.operation);
|
||||
this.extendedGraph.addEdge(argument.argument, argument.operation);
|
||||
});
|
||||
this.oss.references.forEach(reference => {
|
||||
this.extendedGraph.addEdge(reference.target, reference.reference);
|
||||
});
|
||||
}
|
||||
|
||||
private extractSchemas() {
|
||||
|
@ -83,16 +92,37 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private inferOperationAttributes() {
|
||||
const referenceCounts = new Map<number, number>();
|
||||
|
||||
this.graph.topologicalOrder().forEach(operationID => {
|
||||
const operation = this.operationByID.get(operationID)!;
|
||||
const position = this.oss.layout.find(item => item.nodeID === operation.nodeID);
|
||||
operation.x = position?.x ?? 0;
|
||||
operation.y = position?.y ?? 0;
|
||||
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);
|
||||
switch (operation.operation_type) {
|
||||
case OperationType.INPUT:
|
||||
break;
|
||||
case OperationType.SYNTHESIS:
|
||||
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.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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -107,13 +137,13 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private inferConsolidation(operationID: number): boolean {
|
||||
const inputs = this.graph.expandInputs([operationID]);
|
||||
const inputs = this.extendedGraph.expandInputs([operationID]);
|
||||
if (inputs.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const ancestors = [...inputs];
|
||||
inputs.forEach(input => {
|
||||
ancestors.push(...this.graph.expandAllInputs([input]));
|
||||
ancestors.push(...this.extendedGraph.expandAllInputs([input]));
|
||||
});
|
||||
const unique = new Set(ancestors);
|
||||
return unique.size < ancestors.length;
|
||||
|
@ -126,8 +156,11 @@ export class OssLoader {
|
|||
count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||
count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
||||
count_schemas: this.schemaIDs.length,
|
||||
count_owned: operations.filter(item => !!item.result && !item.is_import).length,
|
||||
count_block: this.oss.blocks.length
|
||||
count_owned: operations.filter(
|
||||
item => !!item.result && (item.operation_type !== OperationType.INPUT || !item.is_import)
|
||||
).length,
|
||||
count_block: this.oss.blocks.length,
|
||||
count_references: this.oss.references.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ import { errorMsg } from '@/utils/labels';
|
|||
/** Represents {@link IOperation} type. */
|
||||
export const OperationType = {
|
||||
INPUT: 'input',
|
||||
SYNTHESIS: 'synthesis'
|
||||
SYNTHESIS: 'synthesis',
|
||||
REFERENCE: 'reference'
|
||||
} as const;
|
||||
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. */
|
||||
export type ICreateSchemaDTO = z.infer<typeof schemaCreateSchema>;
|
||||
export type ICreateReferenceDTO = z.infer<typeof schemaCreateReference>;
|
||||
export type ICreateSynthesisDTO = z.infer<typeof schemaCreateSynthesis>;
|
||||
export type IImportSchemaDTO = z.infer<typeof schemaImportSchema>;
|
||||
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. */
|
||||
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}. */
|
||||
export interface ITargetOperation {
|
||||
layout: IOssLayout;
|
||||
|
@ -119,6 +124,11 @@ export const schemaBlock = z.strictObject({
|
|||
parent: z.number().nullable()
|
||||
});
|
||||
|
||||
const schemaReference = z.strictObject({
|
||||
target: z.number(),
|
||||
reference: z.number()
|
||||
});
|
||||
|
||||
export const schemaPosition = z.strictObject({
|
||||
x: z.number(),
|
||||
y: z.number(),
|
||||
|
@ -148,6 +158,7 @@ export const schemaOperationSchema = schemaLibraryItem.extend({
|
|||
editors: z.number().array(),
|
||||
operations: z.array(schemaOperation),
|
||||
blocks: z.array(schemaBlock),
|
||||
references: z.array(schemaReference),
|
||||
layout: schemaOssLayout,
|
||||
arguments: z
|
||||
.object({
|
||||
|
@ -188,6 +199,12 @@ export const schemaCreateSchema = z.strictObject({
|
|||
position: schemaPosition
|
||||
});
|
||||
|
||||
export const schemaCreateReference = z.strictObject({
|
||||
target: z.number(),
|
||||
layout: schemaOssLayout,
|
||||
position: schemaPosition
|
||||
});
|
||||
|
||||
export const schemaCloneSchema = z.strictObject({
|
||||
layout: schemaOssLayout,
|
||||
source_operation: z.number(),
|
||||
|
@ -230,6 +247,13 @@ export const schemaDeleteOperation = z.strictObject({
|
|||
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({
|
||||
layout: schemaOssLayout,
|
||||
operations: z.array(z.number()),
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
};
|
|
@ -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)
|
||||
};
|
||||
};
|
|
@ -47,12 +47,12 @@ export function InfoOperation({ operation }: InfoOperationProps) {
|
|||
<p>
|
||||
<b>Тип:</b> {labelOperationType(operation.operation_type)}
|
||||
</p>
|
||||
{operation.is_import ? (
|
||||
{operation.operation_type === OperationType.INPUT && operation.is_import ? (
|
||||
<p>
|
||||
<b>КС не принадлежит ОСС</b>
|
||||
</p>
|
||||
) : null}
|
||||
{operation.is_consolidation ? (
|
||||
{operation.operation_type === OperationType.SYNTHESIS && operation.is_consolidation ? (
|
||||
<p>
|
||||
<b>Ромбовидный синтез</b>
|
||||
</p>
|
||||
|
@ -69,7 +69,7 @@ export function InfoOperation({ operation }: InfoOperationProps) {
|
|||
{operation.description}
|
||||
</p>
|
||||
) : null}
|
||||
{operation.substitutions.length > 0 ? (
|
||||
{operation.operation_type === OperationType.SYNTHESIS && operation.substitutions.length > 0 ? (
|
||||
<DataTable
|
||||
dense
|
||||
noHeader
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
IconConceptBlock,
|
||||
IconDownload,
|
||||
IconReference,
|
||||
IconRSForm,
|
||||
IconRSFormImported,
|
||||
IconRSFormOwned,
|
||||
|
@ -18,14 +19,16 @@ interface OssStatsProps {
|
|||
|
||||
export function OssStats({ className, stats }: OssStatsProps) {
|
||||
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 '>
|
||||
<span>Всего</span>
|
||||
<span>{stats.count_all}</span>
|
||||
</div>
|
||||
<ValueStats id='count_block' title='Блоки' icon={<IconConceptBlock size='1.25rem' />} value={stats.count_block} />
|
||||
|
||||
<ValueStats
|
||||
id='count_inputs'
|
||||
className='col-start-1'
|
||||
title='Загрузка'
|
||||
icon={<IconDownload size='1.25rem' />}
|
||||
value={stats.count_inputs}
|
||||
|
@ -36,6 +39,12 @@ export function OssStats({ className, stats }: OssStatsProps) {
|
|||
icon={<IconSynthesis size='1.25rem' />}
|
||||
value={stats.count_synthesis}
|
||||
/>
|
||||
<ValueStats
|
||||
id='count_references'
|
||||
title='Синтез'
|
||||
icon={<IconReference size='1.25rem' />}
|
||||
value={stats.count_references}
|
||||
/>
|
||||
|
||||
<ValueStats
|
||||
id='count_schemas'
|
||||
|
|
|
@ -18,6 +18,9 @@ export function TabArguments() {
|
|||
} = useFormContext<ICreateSynthesisDTO>();
|
||||
const inputs = useWatch({ control, name: 'arguments' });
|
||||
|
||||
const references = manager.oss.references.filter(item => inputs.includes(item.target)).map(item => item.reference);
|
||||
const filtered = manager.oss.operations.filter(item => !references.includes(item.id));
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in cc-column'>
|
||||
<TextInput
|
||||
|
@ -64,7 +67,7 @@ export function TabArguments() {
|
|||
name='arguments'
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<PickMultiOperation items={manager.oss.operations} value={field.value} onChange={field.onChange} rows={6} />
|
||||
<PickMultiOperation items={filtered} value={field.value} onChange={field.onChange} rows={6} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -9,13 +9,13 @@ import { Checkbox, TextInput } from '@/components/input';
|
|||
import { ModalForm } from '@/components/modal';
|
||||
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 { type IOperation, type IOperationSchema } from '../models/oss';
|
||||
import { type IOperationInput, type IOperationSchema, type IOperationSynthesis } from '../models/oss';
|
||||
|
||||
export interface DlgDeleteOperationProps {
|
||||
oss: IOperationSchema;
|
||||
target: IOperation;
|
||||
target: IOperationInput | IOperationSynthesis;
|
||||
layout: IOssLayout;
|
||||
}
|
||||
|
||||
|
@ -54,13 +54,13 @@ export function DlgDeleteOperation() {
|
|||
<Checkbox
|
||||
label='Удалить схему'
|
||||
titleHtml={
|
||||
target.is_import || target.result === null
|
||||
(target.operation_type === OperationType.INPUT && target.is_import) || target.result === null
|
||||
? 'Привязанную схему нельзя удалить'
|
||||
: 'Удалить схему вместе с операцией'
|
||||
}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
disabled={target.is_import || target.result === null}
|
||||
disabled={(target.operation_type === OperationType.INPUT && target.is_import) || target.result === null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
'use client';
|
||||
|
||||
import { Controller, useForm, useWatch } 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
|
||||
}
|
||||
});
|
||||
const keep_connections = useWatch({ control, name: 'keep_connections' });
|
||||
|
||||
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_connections'
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
label='Переадресовать связи на оригинал'
|
||||
titleHtml='Связи аргументов будут перенаправлены на оригинал ссылки'
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
disabled={target.result === null}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='keep_constituents'
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
label='Сохранить наследованные конституенты'
|
||||
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
disabled={target.result === null || keep_connections}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</ModalForm>
|
||||
);
|
||||
}
|
|
@ -13,7 +13,7 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
|
||||
import { type IUpdateOperationDTO, OperationType, schemaUpdateOperation } from '../../backend/types';
|
||||
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 { TabArguments } from './tab-arguments';
|
||||
|
@ -22,7 +22,7 @@ import { TabSubstitutions } from './tab-substitutions';
|
|||
|
||||
export interface DlgEditOperationProps {
|
||||
manager: LayoutManager;
|
||||
target: IOperation;
|
||||
target: IOperationInput | IOperationSynthesis;
|
||||
}
|
||||
|
||||
export const TabID = {
|
||||
|
@ -46,11 +46,14 @@ export function DlgEditOperation() {
|
|||
description: target.description,
|
||||
parent: target.parent
|
||||
},
|
||||
arguments: target.arguments,
|
||||
substitutions: target.substitutions.map(sub => ({
|
||||
original: sub.original,
|
||||
substitution: sub.substitution
|
||||
})),
|
||||
arguments: target.operation_type === OperationType.SYNTHESIS ? target.arguments : [],
|
||||
substitutions:
|
||||
target.operation_type === OperationType.SYNTHESIS
|
||||
? target.substitutions.map(sub => ({
|
||||
original: sub.original,
|
||||
substitution: sub.substitution
|
||||
}))
|
||||
: [],
|
||||
layout: manager.layout
|
||||
},
|
||||
mode: 'onChange'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
'use client';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { Label } from '@/components/input';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
@ -12,7 +12,12 @@ import { type DlgEditOperationProps } from './dlg-edit-operation';
|
|||
export function TabArguments() {
|
||||
const { control, setValue } = useFormContext<IUpdateOperationDTO>();
|
||||
const { manager, target } = useDialogsStore(state => state.props as DlgEditOperationProps);
|
||||
const potentialCycle = [target.id, ...manager.oss.graph.expandAllOutputs([target.id])];
|
||||
const args = useWatch({ control, name: 'arguments' });
|
||||
|
||||
const references = manager.oss.references
|
||||
.filter(item => args.includes(item.target) || item.target === target.id)
|
||||
.map(item => item.reference);
|
||||
const potentialCycle = [target.id, ...references, ...manager.oss.graph.expandAllOutputs([target.id])];
|
||||
const filtered = manager.oss.operations.filter(item => !potentialCycle.includes(item.id));
|
||||
|
||||
function handleChangeArguments(prev: number[], newValue: number[]) {
|
||||
|
|
|
@ -11,12 +11,14 @@ import {
|
|||
|
||||
const labelOperationTypeRecord: Record<OperationType, string> = {
|
||||
[OperationType.INPUT]: 'Загрузка',
|
||||
[OperationType.SYNTHESIS]: 'Синтез'
|
||||
[OperationType.SYNTHESIS]: 'Синтез',
|
||||
[OperationType.REFERENCE]: 'Ссылка'
|
||||
};
|
||||
|
||||
const describeOperationTypeRecord: Record<OperationType, string> = {
|
||||
[OperationType.INPUT]: 'Загрузка концептуальной схемы в ОСС',
|
||||
[OperationType.SYNTHESIS]: 'Синтез концептуальных схем'
|
||||
[OperationType.SYNTHESIS]: 'Синтез концептуальных схем',
|
||||
[OperationType.REFERENCE]: 'Создание ссылки на результат операции'
|
||||
};
|
||||
|
||||
/** Retrieves label for {@link OperationType}. */
|
||||
|
|
|
@ -4,7 +4,8 @@ import {
|
|||
type ICreateSynthesisDTO,
|
||||
type IImportSchemaDTO,
|
||||
type INodePosition,
|
||||
type IOssLayout
|
||||
type IOssLayout,
|
||||
OperationType
|
||||
} from '../backend/types';
|
||||
|
||||
import { type IOperationSchema, NodeType } from './oss';
|
||||
|
@ -258,7 +259,11 @@ export class LayoutManager {
|
|||
}
|
||||
|
||||
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);
|
||||
let inputsPositions = this.layout.filter(pos => freeInputs.includes(pos.nodeID));
|
||||
if (inputsPositions.length === 0) {
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
type IBlockDTO,
|
||||
type ICstSubstituteInfo,
|
||||
type IOperationDTO,
|
||||
type IOperationSchemaDTO
|
||||
type IOperationSchemaDTO,
|
||||
type OperationType
|
||||
} from '../backend/types';
|
||||
|
||||
/** Represents OSS node type. */
|
||||
|
@ -18,26 +19,50 @@ export const NodeType = {
|
|||
} as const;
|
||||
export type NodeType = (typeof NodeType)[keyof typeof NodeType];
|
||||
|
||||
/** Represents Operation. */
|
||||
export interface IOperation extends IOperationDTO {
|
||||
/** Represents OSS graph node. */
|
||||
export interface IOssNode {
|
||||
nodeID: string;
|
||||
nodeType: typeof NodeType.OPERATION;
|
||||
nodeType: NodeType;
|
||||
parent: number | null;
|
||||
x: 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;
|
||||
}
|
||||
|
||||
/** 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'
|
||||
substitutions: ICstSubstituteInfo[];
|
||||
arguments: number[];
|
||||
}
|
||||
|
||||
/** Represents Operation. */
|
||||
export type IOperation = IOperationInput | IOperationReference | IOperationSynthesis;
|
||||
|
||||
/** Represents Block. */
|
||||
export interface IBlock extends IBlockDTO {
|
||||
nodeID: string;
|
||||
export interface IBlock extends IOssNode, IBlockDTO {
|
||||
nodeType: typeof NodeType.BLOCK;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** Represents item of OperationSchema. */
|
||||
|
@ -51,14 +76,16 @@ export interface IOperationSchemaStats {
|
|||
count_schemas: number;
|
||||
count_owned: number;
|
||||
count_block: number;
|
||||
count_references: number;
|
||||
}
|
||||
|
||||
/** Represents OperationSchema. */
|
||||
export interface IOperationSchema extends IOperationSchemaDTO {
|
||||
export interface IOperationSchema extends Omit<IOperationSchemaDTO, 'operations'> {
|
||||
operations: IOperation[];
|
||||
blocks: IBlock[];
|
||||
|
||||
graph: Graph;
|
||||
extendedGraph: Graph;
|
||||
hierarchy: Graph<string>;
|
||||
schemas: number[];
|
||||
stats: IOperationSchemaStats;
|
||||
|
|
|
@ -47,10 +47,10 @@ export function ContextMenu({ isOpen, item, cursorX, cursorY, onHide }: ContextM
|
|||
margin={cursorY >= window.innerHeight - MENU_HEIGHT ? 'mb-3' : 'mt-3'}
|
||||
>
|
||||
{!!item ? (
|
||||
item.nodeType === NodeType.OPERATION ? (
|
||||
<MenuOperation operation={item} onHide={onHide} />
|
||||
) : (
|
||||
item.nodeType === NodeType.BLOCK ? (
|
||||
<MenuBlock block={item} onHide={onHide} />
|
||||
) : (
|
||||
<MenuOperation operation={item} onHide={onHide} />
|
||||
)
|
||||
) : null}
|
||||
</Dropdown>
|
||||
|
|
|
@ -3,6 +3,7 @@ import { toast } from 'react-toastify';
|
|||
|
||||
import { useLibrary } from '@/features/library/backend/use-library';
|
||||
import { useCloneSchema } from '@/features/oss/backend/use-clone-schema';
|
||||
import { useCreateReference } from '@/features/oss/backend/use-create-reference';
|
||||
|
||||
import { DropdownButton } from '@/components/dropdown';
|
||||
import {
|
||||
|
@ -13,13 +14,13 @@ import {
|
|||
IconEdit2,
|
||||
IconExecute,
|
||||
IconNewRSForm,
|
||||
IconPhantom,
|
||||
IconReference,
|
||||
IconRSForm
|
||||
} from '@/components/icons';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
import { errorMsg } from '@/utils/labels';
|
||||
import { notImplemented, prepareTooltip } from '@/utils/utils';
|
||||
import { prepareTooltip } from '@/utils/utils';
|
||||
|
||||
import { OperationType } from '../../../../backend/types';
|
||||
import { useCreateInput } from '../../../../backend/use-create-input';
|
||||
|
@ -44,11 +45,13 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
const { createInput: inputCreate } = useCreateInput();
|
||||
const { executeOperation: operationExecute } = useExecuteOperation();
|
||||
const { cloneSchema } = useCloneSchema();
|
||||
const { createReference } = useCreateReference();
|
||||
|
||||
const showEditInput = useDialogsStore(state => state.showChangeInputSchema);
|
||||
const showRelocateConstituents = useDialogsStore(state => state.showRelocateConstituents);
|
||||
const showEditOperation = useDialogsStore(state => state.showEditOperation);
|
||||
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
|
||||
const showDeleteReference = useDialogsStore(state => state.showDeleteReference);
|
||||
|
||||
const readyForSynthesis = (() => {
|
||||
if (operation?.operation_type !== OperationType.SYNTHESIS) {
|
||||
|
@ -92,7 +95,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
}
|
||||
|
||||
function handleEditOperation() {
|
||||
if (!operation) {
|
||||
if (!operation || operation.operation_type === OperationType.REFERENCE) {
|
||||
return;
|
||||
}
|
||||
onHide();
|
||||
|
@ -107,11 +110,22 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
return;
|
||||
}
|
||||
onHide();
|
||||
showDeleteOperation({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
layout: getLayout()
|
||||
});
|
||||
switch (operation.operation_type) {
|
||||
case OperationType.REFERENCE:
|
||||
showDeleteReference({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
layout: getLayout()
|
||||
});
|
||||
break;
|
||||
case OperationType.INPUT:
|
||||
case OperationType.SYNTHESIS:
|
||||
showDeleteOperation({
|
||||
oss: schema,
|
||||
target: operation,
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleOperationExecute() {
|
||||
|
@ -152,9 +166,24 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
});
|
||||
}
|
||||
|
||||
function handleCreatePhantom() {
|
||||
function handleCreateReference() {
|
||||
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() {
|
||||
|
@ -177,17 +206,34 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
}).then(response => setTimeout(() => setSelected([`o${response.new_operation}`]), PARAMETER.refreshTimeout));
|
||||
}
|
||||
|
||||
function handleSelectTarget() {
|
||||
onHide();
|
||||
if (operation.operation_type !== OperationType.REFERENCE) {
|
||||
return;
|
||||
}
|
||||
setSelected([`o${operation.target}`]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownButton
|
||||
text='Редактировать'
|
||||
title='Редактировать операцию'
|
||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||
onClick={handleEditOperation}
|
||||
disabled={!isMutable || isProcessing}
|
||||
/>
|
||||
{operation.operation_type !== OperationType.REFERENCE ? (
|
||||
<DropdownButton
|
||||
text='Редактировать'
|
||||
title='Редактировать операцию'
|
||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||
onClick={handleEditOperation}
|
||||
disabled={!isMutable || isProcessing}
|
||||
/>
|
||||
) : (
|
||||
<DropdownButton
|
||||
text='Оригинал'
|
||||
title='Выделить оригинал'
|
||||
icon={<IconReference size='1rem' className='icon-primary' />}
|
||||
onClick={handleSelectTarget}
|
||||
/>
|
||||
)}
|
||||
|
||||
{operation?.result ? (
|
||||
{operation.result ? (
|
||||
<DropdownButton
|
||||
text='Открыть схему'
|
||||
titleHtml={prepareTooltip('Открыть привязанную КС', 'Двойной клик')}
|
||||
|
@ -197,7 +243,10 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
disabled={isProcessing}
|
||||
/>
|
||||
) : null}
|
||||
{isMutable && !operation?.result && operation?.arguments.length === 0 ? (
|
||||
{isMutable &&
|
||||
!operation.result &&
|
||||
operation.operation_type === OperationType.SYNTHESIS &&
|
||||
operation.arguments.length === 0 ? (
|
||||
<DropdownButton
|
||||
text='Создать схему'
|
||||
title='Создать пустую схему'
|
||||
|
@ -215,7 +264,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
disabled={isProcessing}
|
||||
/>
|
||||
) : null}
|
||||
{isMutable && !operation?.result && operation?.operation_type === OperationType.SYNTHESIS ? (
|
||||
{isMutable && !operation.result && operation.operation_type === OperationType.SYNTHESIS ? (
|
||||
<DropdownButton
|
||||
text='Активировать синтез'
|
||||
titleHtml={
|
||||
|
@ -230,7 +279,7 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
/>
|
||||
) : null}
|
||||
|
||||
{isMutable && operation?.result ? (
|
||||
{isMutable && operation.result && operation.operation_type !== OperationType.REFERENCE ? (
|
||||
<DropdownButton
|
||||
text='Конституенты'
|
||||
titleHtml='Перенос конституент</br>между схемами'
|
||||
|
@ -241,17 +290,17 @@ export function MenuOperation({ operation, onHide }: MenuOperationProps) {
|
|||
/>
|
||||
) : null}
|
||||
|
||||
{isMutable ? (
|
||||
{isMutable && operation.operation_type !== OperationType.REFERENCE ? (
|
||||
<DropdownButton
|
||||
text='Создать ссылку'
|
||||
title='Создать ссылку на результат операции'
|
||||
icon={<IconPhantom size='1rem' className='icon-green' />}
|
||||
onClick={handleCreatePhantom}
|
||||
icon={<IconReference size='1rem' className='icon-green' />}
|
||||
onClick={handleCreateReference}
|
||||
disabled={isProcessing}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{isMutable ? (
|
||||
{isMutable && operation.operation_type !== OperationType.REFERENCE ? (
|
||||
<DropdownButton
|
||||
text='Клонировать'
|
||||
title='Создать и загрузить копию концептуальной схемы'
|
||||
|
|
|
@ -22,6 +22,8 @@ interface NodeCoreProps {
|
|||
|
||||
export function NodeCore({ node }: NodeCoreProps) {
|
||||
const { selectedItems, schema } = useOssEdit();
|
||||
const opType = node.data.operation.operation_type;
|
||||
|
||||
const focus = selectedItems.length === 1 ? selectedItems[0] : null;
|
||||
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(
|
||||
'cc-node-operation h-[40px] w-[150px]',
|
||||
'relative flex items-center justify-center p-[2px]',
|
||||
opType === OperationType.REFERENCE && 'border-dashed',
|
||||
isChild && 'border-accent-orange'
|
||||
)}
|
||||
>
|
||||
|
@ -45,7 +48,7 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
|
||||
icon={<IconRSForm className={hasFile ? 'text-constructive' : 'text-destructive'} size='12px' />}
|
||||
/>
|
||||
{node.data.operation.is_consolidation ? (
|
||||
{opType === OperationType.SYNTHESIS && node.data.operation.is_consolidation ? (
|
||||
<Indicator
|
||||
noPadding
|
||||
titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент'
|
||||
|
@ -66,11 +69,11 @@ export function NodeCore({ node }: NodeCoreProps) {
|
|||
</div>
|
||||
) : 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]' />
|
||||
) : 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]' />
|
||||
) : null}
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ import { type NodeTypes } from 'reactflow';
|
|||
|
||||
import { BlockNode } from './block-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 = {
|
||||
synthesis: OperationNode,
|
||||
input: InputNode,
|
||||
synthesis: SynthesisNode,
|
||||
reference: ReferenceNode,
|
||||
block: BlockNode
|
||||
} as const;
|
||||
|
|
|
@ -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]' />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,7 +6,7 @@ import { type OperationInternalNode } from '../../../../models/oss-layout';
|
|||
|
||||
import { NodeCore } from './node-core';
|
||||
|
||||
export function OperationNode(node: OperationInternalNode) {
|
||||
export function SynthesisNode(node: OperationInternalNode) {
|
||||
return (
|
||||
<>
|
||||
<Handle type='target' position={Position.Top} id='left' style={{ left: 40, top: -2 }} />
|
|
@ -11,6 +11,7 @@ import { PARAMETER } from '@/utils/constants';
|
|||
import { promptText } from '@/utils/labels';
|
||||
import { withPreventDefault } from '@/utils/utils';
|
||||
|
||||
import { OperationType } from '../../../backend/types';
|
||||
import { useDeleteBlock } from '../../../backend/use-delete-block';
|
||||
import { useMutatingOss } from '../../../backend/use-mutating-oss';
|
||||
import { useUpdateLayout } from '../../../backend/use-update-layout';
|
||||
|
@ -68,6 +69,7 @@ export function OssFlow() {
|
|||
const showCreateBlock = useDialogsStore(state => state.showCreateBlock);
|
||||
const showCreateSchema = useDialogsStore(state => state.showCreateSchema);
|
||||
const showDeleteOperation = useDialogsStore(state => state.showDeleteOperation);
|
||||
const showDeleteReference = useDialogsStore(state => state.showDeleteReference);
|
||||
const showEditBlock = useDialogsStore(state => state.showEditBlock);
|
||||
const showImportSchema = useDialogsStore(state => state.showImportSchema);
|
||||
|
||||
|
@ -150,11 +152,22 @@ export function OssFlow() {
|
|||
if (!canDeleteOperation(item)) {
|
||||
return;
|
||||
}
|
||||
showDeleteOperation({
|
||||
oss: schema,
|
||||
target: item,
|
||||
layout: getLayout()
|
||||
});
|
||||
switch (item.operation_type) {
|
||||
case OperationType.REFERENCE:
|
||||
showDeleteReference({
|
||||
oss: schema,
|
||||
target: item,
|
||||
layout: getLayout()
|
||||
});
|
||||
break;
|
||||
case OperationType.INPUT:
|
||||
case OperationType.SYNTHESIS:
|
||||
showDeleteOperation({
|
||||
oss: schema,
|
||||
target: item,
|
||||
layout: getLayout()
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (!window.confirm(promptText.deleteBlock)) {
|
||||
return;
|
||||
|
|
|
@ -16,8 +16,11 @@ export function BlockStats({ target, oss }: BlockStatsProps) {
|
|||
count_inputs: operations.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||
count_synthesis: operations.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
||||
count_schemas: operations.filter(item => !!item.result).length,
|
||||
count_owned: operations.filter(item => !!item.result && !item.is_import).length,
|
||||
count_block: contents.length - operations.length
|
||||
count_owned: operations.filter(
|
||||
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' />;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { useAppLayoutStore, useMainHeight } from '@/stores/app-layout';
|
|||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import { OperationType } from '../../../../backend/types';
|
||||
import { NodeType } from '../../../../models/oss';
|
||||
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>
|
||||
) : selectedOperation && selectedSchema && debouncedMounted ? (
|
||||
<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>
|
||||
) : null}
|
||||
{selectedBlock ? <BlockStats target={selectedBlock} oss={schema} /> : null}
|
||||
|
|
|
@ -227,7 +227,7 @@ export function ToolbarSchema({
|
|||
/>
|
||||
<DropdownButton
|
||||
title='Перейти к концептуальной схеме'
|
||||
text='перейти к схеме'
|
||||
text='Открыть КС'
|
||||
icon={<IconRSForm size='1rem' className='icon-primary' />}
|
||||
onClick={navigateRSForm}
|
||||
/>
|
||||
|
|
|
@ -109,7 +109,7 @@ export const OssEditState = ({ itemID, children }: React.PropsWithChildren<OssEd
|
|||
}
|
||||
|
||||
function canDeleteOperation(target: IOperation) {
|
||||
if (target.operation_type === OperationType.INPUT) {
|
||||
if (target.operation_type === OperationType.INPUT || target.operation_type === OperationType.REFERENCE) {
|
||||
return true;
|
||||
}
|
||||
return schema.graph.expandOutputs([target.id]).length === 0;
|
||||
|
|
|
@ -6,6 +6,7 @@ import { TextArea, TextInput } from '@/components/input';
|
|||
|
||||
import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
|
||||
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||
import { RSInput } from '../../components/rs-input';
|
||||
import { SelectCstType } from '../../components/select-cst-type';
|
||||
import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels';
|
||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||
|
@ -97,9 +98,9 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
name='item_data.definition_formal'
|
||||
render={({ field }) =>
|
||||
!!field.value || (!isElementary && !target.is_inherited) ? (
|
||||
<TextArea
|
||||
<RSInput
|
||||
id='dlg_cst_expression'
|
||||
fitContent
|
||||
noTooltip
|
||||
label={
|
||||
cst_type === CstType.STRUCTURED
|
||||
? 'Область определения'
|
||||
|
@ -109,9 +110,9 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
}
|
||||
placeholder={getRSDefinitionPlaceholder(cst_type)}
|
||||
className='max-h-15'
|
||||
schema={schema}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={errors.item_data?.definition_formal}
|
||||
disabled={target.is_inherited}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -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 DlgCreateSynthesisProps } from '@/features/oss/dialogs/dlg-create-synthesis/dlg-create-synthesis';
|
||||
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 DlgEditOperationProps } from '@/features/oss/dialogs/dlg-edit-operation/dlg-edit-operation';
|
||||
import { type DlgImportSchemaProps } from '@/features/oss/dialogs/dlg-import-schema';
|
||||
|
@ -47,31 +48,32 @@ export const DialogType = {
|
|||
CREATE_SYNTHESIS: 9,
|
||||
EDIT_OPERATION: 10,
|
||||
DELETE_OPERATION: 11,
|
||||
CHANGE_INPUT_SCHEMA: 12,
|
||||
RELOCATE_CONSTITUENTS: 13,
|
||||
OSS_SETTINGS: 14,
|
||||
EDIT_CONSTITUENTA: 15,
|
||||
DELETE_REFERENCE: 12,
|
||||
CHANGE_INPUT_SCHEMA: 13,
|
||||
RELOCATE_CONSTITUENTS: 14,
|
||||
OSS_SETTINGS: 15,
|
||||
EDIT_CONSTITUENTA: 16,
|
||||
|
||||
CLONE_LIBRARY_ITEM: 16,
|
||||
UPLOAD_RSFORM: 17,
|
||||
EDIT_EDITORS: 18,
|
||||
EDIT_VERSIONS: 19,
|
||||
CHANGE_LOCATION: 20,
|
||||
CLONE_LIBRARY_ITEM: 17,
|
||||
UPLOAD_RSFORM: 18,
|
||||
EDIT_EDITORS: 19,
|
||||
EDIT_VERSIONS: 20,
|
||||
CHANGE_LOCATION: 21,
|
||||
|
||||
EDIT_REFERENCE: 21,
|
||||
EDIT_WORD_FORMS: 22,
|
||||
INLINE_SYNTHESIS: 23,
|
||||
EDIT_REFERENCE: 22,
|
||||
EDIT_WORD_FORMS: 23,
|
||||
INLINE_SYNTHESIS: 24,
|
||||
|
||||
SHOW_QR_CODE: 24,
|
||||
SHOW_AST: 25,
|
||||
SHOW_TYPE_GRAPH: 26,
|
||||
GRAPH_PARAMETERS: 27,
|
||||
SHOW_TERM_GRAPH: 28,
|
||||
CREATE_SCHEMA: 29,
|
||||
IMPORT_SCHEMA: 30,
|
||||
SHOW_QR_CODE: 25,
|
||||
SHOW_AST: 26,
|
||||
SHOW_TYPE_GRAPH: 27,
|
||||
GRAPH_PARAMETERS: 28,
|
||||
SHOW_TERM_GRAPH: 29,
|
||||
CREATE_SCHEMA: 30,
|
||||
IMPORT_SCHEMA: 31,
|
||||
|
||||
AI_PROMPT: 31,
|
||||
CREATE_PROMPT_TEMPLATE: 32
|
||||
AI_PROMPT: 32,
|
||||
CREATE_PROMPT_TEMPLATE: 33
|
||||
} as const;
|
||||
export type DialogType = (typeof DialogType)[keyof typeof DialogType];
|
||||
|
||||
|
@ -104,6 +106,7 @@ interface DialogsStore {
|
|||
showCloneLibraryItem: (props: DlgCloneLibraryItemProps) => void;
|
||||
showCreateVersion: (props: DlgCreateVersionProps) => void;
|
||||
showDeleteOperation: (props: DlgDeleteOperationProps) => void;
|
||||
showDeleteReference: (props: DlgDeleteReferenceProps) => void;
|
||||
showGraphParams: () => void;
|
||||
showOssOptions: () => 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 }),
|
||||
showCreateVersion: props => set({ active: DialogType.CREATE_VERSION, 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 }),
|
||||
showOssOptions: () => set({ active: DialogType.OSS_SETTINGS, props: null }),
|
||||
showRelocateConstituents: props => set({ active: DialogType.RELOCATE_CONSTITUENTS, props: props }),
|
||||
|
|
|
@ -165,7 +165,8 @@
|
|||
}
|
||||
|
||||
.react-flow__node-input,
|
||||
.react-flow__node-synthesis {
|
||||
.react-flow__node-synthesis,
|
||||
.react-flow__node-reference {
|
||||
border-radius: 5px;
|
||||
border-width: 0;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user