F: Implement operation and schema delete consequence for OSS
This commit is contained in:
parent
4716fde331
commit
f59206c182
|
@ -123,3 +123,11 @@ class LibraryItem(Model):
|
||||||
def versions(self) -> QuerySet[Version]:
|
def versions(self) -> QuerySet[Version]:
|
||||||
''' Get all Versions of this item. '''
|
''' Get all Versions of this item. '''
|
||||||
return Version.objects.filter(item=self.pk).order_by('-time_create')
|
return Version.objects.filter(item=self.pk).order_by('-time_create')
|
||||||
|
|
||||||
|
def is_synced(self, target: 'LibraryItem') -> bool:
|
||||||
|
''' Check if item is synced with target. '''
|
||||||
|
if self.owner != target.owner:
|
||||||
|
return False
|
||||||
|
if self.location != target.location:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
|
@ -217,13 +217,10 @@ class TestLibraryViewset(EndpointTester):
|
||||||
|
|
||||||
@decl_endpoint('/api/library/{item}', method='delete')
|
@decl_endpoint('/api/library/{item}', method='delete')
|
||||||
def test_destroy(self):
|
def test_destroy(self):
|
||||||
response = self.execute(item=self.owned.pk)
|
self.executeNoContent(item=self.owned.pk)
|
||||||
self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT])
|
|
||||||
|
|
||||||
self.executeForbidden(item=self.unowned.pk)
|
self.executeForbidden(item=self.unowned.pk)
|
||||||
self.toggle_admin(True)
|
self.toggle_admin(True)
|
||||||
response = self.execute(item=self.unowned.pk)
|
self.executeNoContent(item=self.unowned.pk)
|
||||||
self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT])
|
|
||||||
|
|
||||||
|
|
||||||
@decl_endpoint('/api/library/active', method='get')
|
@decl_endpoint('/api/library/active', method='get')
|
||||||
|
|
|
@ -13,7 +13,7 @@ from rest_framework.decorators import action
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from apps.oss.models import Operation, OperationSchema
|
from apps.oss.models import Operation, OperationSchema, PropagationFacade
|
||||||
from apps.rsform.models import RSForm
|
from apps.rsform.models import RSForm
|
||||||
from apps.rsform.serializers import RSFormParseSerializer
|
from apps.rsform.serializers import RSFormParseSerializer
|
||||||
from apps.users.models import User
|
from apps.users.models import User
|
||||||
|
@ -67,6 +67,10 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
||||||
if update_list:
|
if update_list:
|
||||||
Operation.objects.bulk_update(update_list, ['alias', 'title', 'comment'])
|
Operation.objects.bulk_update(update_list, ['alias', 'title', 'comment'])
|
||||||
|
|
||||||
|
def perform_destroy(self, instance: m.LibraryItem) -> None:
|
||||||
|
PropagationFacade.before_delete_schema(instance)
|
||||||
|
return super().perform_destroy(instance)
|
||||||
|
|
||||||
def get_permissions(self):
|
def get_permissions(self):
|
||||||
if self.action in ['update', 'partial_update']:
|
if self.action in ['update', 'partial_update']:
|
||||||
access_level = permissions.ItemEditor
|
access_level = permissions.ItemEditor
|
||||||
|
|
|
@ -100,7 +100,7 @@ class OperationSchema:
|
||||||
if not keep_constituents:
|
if not keep_constituents:
|
||||||
schema = self.cache.get_schema(target)
|
schema = self.cache.get_schema(target)
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
self.before_delete(schema.cache.constituents, schema)
|
self.before_delete_cst(schema.cache.constituents, schema)
|
||||||
self.cache.remove_operation(target.pk)
|
self.cache.remove_operation(target.pk)
|
||||||
target.delete()
|
target.delete()
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
|
@ -115,7 +115,7 @@ class OperationSchema:
|
||||||
|
|
||||||
if old_schema is not None:
|
if old_schema is not None:
|
||||||
if has_children:
|
if has_children:
|
||||||
self.before_delete(old_schema.cache.constituents, old_schema)
|
self.before_delete_cst(old_schema.cache.constituents, old_schema)
|
||||||
self.cache.remove_schema(old_schema)
|
self.cache.remove_schema(old_schema)
|
||||||
|
|
||||||
operation.result = schema
|
operation.result = schema
|
||||||
|
@ -280,7 +280,7 @@ class OperationSchema:
|
||||||
mapping=alias_mapping
|
mapping=alias_mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
def before_delete(self, target: list[Constituenta], source: RSForm) -> None:
|
def before_delete_cst(self, target: list[Constituenta], source: RSForm) -> None:
|
||||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||||
self.cache.insert(source)
|
self.cache.insert(source)
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
operation = self.cache.get_operation(source.model.pk)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
''' Models: Change propagation facade - managing all changes in OSS. '''
|
''' Models: Change propagation facade - managing all changes in OSS. '''
|
||||||
from apps.library.models import LibraryItem
|
from apps.library.models import LibraryItem, LibraryItemType
|
||||||
from apps.rsform.models import Constituenta, RSForm
|
from apps.rsform.models import Constituenta, RSForm
|
||||||
|
|
||||||
from .OperationSchema import CstSubstitution, OperationSchema
|
from .OperationSchema import CstSubstitution, OperationSchema
|
||||||
|
@ -35,11 +35,11 @@ class PropagationFacade:
|
||||||
OperationSchema(host).after_update_cst(target, data, old_data, source)
|
OperationSchema(host).after_update_cst(target, data, old_data, source)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def before_delete(target: list[Constituenta], source: RSForm) -> None:
|
def before_delete_cst(target: list[Constituenta], source: RSForm) -> None:
|
||||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).before_delete(target, source)
|
OperationSchema(host).before_delete_cst(target, source)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def before_substitute(substitutions: CstSubstitution, source: RSForm) -> None:
|
def before_substitute(substitutions: CstSubstitution, source: RSForm) -> None:
|
||||||
|
@ -47,3 +47,15 @@ class PropagationFacade:
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).before_substitute(substitutions, source)
|
OperationSchema(host).before_substitute(substitutions, source)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def before_delete_schema(item: LibraryItem) -> None:
|
||||||
|
''' Trigger cascade resolutions before schema is deleted. '''
|
||||||
|
if item.item_type != LibraryItemType.RSFORM:
|
||||||
|
return
|
||||||
|
hosts = _get_oss_hosts(item)
|
||||||
|
if len(hosts) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
schema = RSForm(item)
|
||||||
|
PropagationFacade.before_delete_cst(list(schema.constituents()), schema)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from .basics import OperationPositionSerializer, PositionsSerializer, Substituti
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
ArgumentSerializer,
|
ArgumentSerializer,
|
||||||
OperationCreateSerializer,
|
OperationCreateSerializer,
|
||||||
|
OperationDeleteSerializer,
|
||||||
OperationSchemaSerializer,
|
OperationSchemaSerializer,
|
||||||
OperationSerializer,
|
OperationSerializer,
|
||||||
OperationTargetSerializer,
|
OperationTargetSerializer,
|
||||||
|
|
|
@ -138,6 +138,26 @@ class OperationTargetSerializer(serializers.Serializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class OperationDeleteSerializer(serializers.Serializer):
|
||||||
|
''' Serializer: Delete operation. '''
|
||||||
|
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
|
||||||
|
positions = serializers.ListField(
|
||||||
|
child=OperationPositionSerializer(),
|
||||||
|
default=[]
|
||||||
|
)
|
||||||
|
keep_constituents = serializers.BooleanField(default=False, required=False)
|
||||||
|
delete_schema = serializers.BooleanField(default=False, required=False)
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
oss = cast(LibraryItem, self.context['oss'])
|
||||||
|
operation = cast(Operation, attrs['target'])
|
||||||
|
if oss and operation.oss_id != oss.pk:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
'target': msg.operationNotInOSS(oss.title)
|
||||||
|
})
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class SetOperationInputSerializer(serializers.Serializer):
|
class SetOperationInputSerializer(serializers.Serializer):
|
||||||
''' Serializer: Set input schema for operation. '''
|
''' Serializer: Set input schema for operation. '''
|
||||||
target = PKField(many=False, queryset=Operation.objects.all())
|
target = PKField(many=False, queryset=Operation.objects.all())
|
||||||
|
|
|
@ -191,3 +191,61 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
||||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1')
|
self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1')
|
||||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 DEL DEL DEL D1 D2 D3')
|
self.assertEqual(self.ks5D4.definition_formal, r'X1 DEL DEL DEL D1 D2 D3')
|
||||||
|
|
||||||
|
@decl_endpoint('/api/library/{item}', method='delete')
|
||||||
|
def test_delete_schema(self):
|
||||||
|
self.executeNoContent(item=self.ks1.model.pk)
|
||||||
|
self.ks4D2.refresh_from_db()
|
||||||
|
self.ks5D4.refresh_from_db()
|
||||||
|
self.operation1.refresh_from_db()
|
||||||
|
self.assertEqual(self.operation1.result, None)
|
||||||
|
subs1_2 = self.operation4.getSubstitutions()
|
||||||
|
self.assertEqual(subs1_2.count(), 0)
|
||||||
|
subs3_4 = self.operation5.getSubstitutions()
|
||||||
|
self.assertEqual(subs3_4.count(), 0)
|
||||||
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
|
self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 DEL')
|
||||||
|
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 DEL D3')
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||||
|
def test_delete_operation_and_constituents(self):
|
||||||
|
data = {
|
||||||
|
'positions': [],
|
||||||
|
'target': self.operation1.pk,
|
||||||
|
'keep_constituents': False,
|
||||||
|
'delete_schema': True
|
||||||
|
}
|
||||||
|
|
||||||
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
|
self.ks4D2.refresh_from_db()
|
||||||
|
self.ks5D4.refresh_from_db()
|
||||||
|
subs1_2 = self.operation4.getSubstitutions()
|
||||||
|
self.assertEqual(subs1_2.count(), 0)
|
||||||
|
subs3_4 = self.operation5.getSubstitutions()
|
||||||
|
self.assertEqual(subs3_4.count(), 0)
|
||||||
|
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||||
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
|
self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 DEL')
|
||||||
|
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 DEL D3')
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||||
|
def test_delete_operation_keep_constituents(self):
|
||||||
|
data = {
|
||||||
|
'positions': [],
|
||||||
|
'target': self.operation1.pk,
|
||||||
|
'keep_constituents': True,
|
||||||
|
'delete_schema': True
|
||||||
|
}
|
||||||
|
|
||||||
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
|
self.ks4D2.refresh_from_db()
|
||||||
|
self.ks5D4.refresh_from_db()
|
||||||
|
subs1_2 = self.operation4.getSubstitutions()
|
||||||
|
self.assertEqual(subs1_2.count(), 0)
|
||||||
|
subs3_4 = self.operation5.getSubstitutions()
|
||||||
|
self.assertEqual(subs3_4.count(), 1)
|
||||||
|
self.assertEqual(self.ks4.constituents().count(), 6)
|
||||||
|
self.assertEqual(self.ks5.constituents().count(), 8)
|
||||||
|
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 D1')
|
||||||
|
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 D2 D3')
|
||||||
|
|
|
@ -349,6 +349,8 @@ class TestOssViewset(EndpointTester):
|
||||||
}
|
}
|
||||||
self.executeBadData(data=data, item=self.owned_id)
|
self.executeBadData(data=data, item=self.owned_id)
|
||||||
|
|
||||||
|
self.ks2.model.visible = False
|
||||||
|
self.ks2.model.save(update_fields=['visible'])
|
||||||
data = {
|
data = {
|
||||||
'positions': [],
|
'positions': [],
|
||||||
'target': self.operation2.pk,
|
'target': self.operation2.pk,
|
||||||
|
@ -356,7 +358,9 @@ class TestOssViewset(EndpointTester):
|
||||||
}
|
}
|
||||||
self.executeOK(data=data, item=self.owned_id)
|
self.executeOK(data=data, item=self.owned_id)
|
||||||
self.operation2.refresh_from_db()
|
self.operation2.refresh_from_db()
|
||||||
|
self.ks2.model.refresh_from_db()
|
||||||
self.assertEqual(self.operation2.result, None)
|
self.assertEqual(self.operation2.result, None)
|
||||||
|
self.assertEqual(self.ks2.model.visible, True)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'positions': [],
|
'positions': [],
|
||||||
|
|
|
@ -143,7 +143,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='delete operation',
|
summary='delete operation',
|
||||||
tags=['OSS'],
|
tags=['OSS'],
|
||||||
request=s.OperationTargetSerializer,
|
request=s.OperationDeleteSerializer,
|
||||||
responses={
|
responses={
|
||||||
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
||||||
c.HTTP_400_BAD_REQUEST: None,
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
@ -154,20 +154,25 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||||
def delete_operation(self, request: Request, pk) -> HttpResponse:
|
def delete_operation(self, request: Request, pk) -> HttpResponse:
|
||||||
''' Endpoint: Delete operation. '''
|
''' Endpoint: Delete operation. '''
|
||||||
serializer = s.OperationTargetSerializer(
|
serializer = s.OperationDeleteSerializer(
|
||||||
data=request.data,
|
data=request.data,
|
||||||
context={'oss': self.get_object()}
|
context={'oss': self.get_object()}
|
||||||
)
|
)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
oss = m.OperationSchema(self.get_object())
|
oss = m.OperationSchema(self.get_object())
|
||||||
|
operation = cast(m.Operation, serializer.validated_data['target'])
|
||||||
|
old_schema: Optional[LibraryItem] = operation.result
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
oss.update_positions(serializer.validated_data['positions'])
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
|
oss.delete_operation(operation, serializer.validated_data['keep_constituents'])
|
||||||
# TODO: propagate changes to RSForms
|
if old_schema is not None:
|
||||||
|
if serializer.validated_data['delete_schema']:
|
||||||
oss.delete_operation(serializer.validated_data['target'])
|
m.PropagationFacade.before_delete_schema(old_schema)
|
||||||
|
old_schema.delete()
|
||||||
|
elif old_schema.is_synced(oss.model):
|
||||||
|
old_schema.visible = True
|
||||||
|
old_schema.save(update_fields=['visible'])
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.OperationSchemaSerializer(oss.model).data
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
|
@ -249,9 +254,13 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
raise serializers.ValidationError({
|
raise serializers.ValidationError({
|
||||||
'input': msg.operationInputAlreadyConnected()
|
'input': msg.operationInputAlreadyConnected()
|
||||||
})
|
})
|
||||||
|
|
||||||
oss = m.OperationSchema(self.get_object())
|
oss = m.OperationSchema(self.get_object())
|
||||||
|
old_schema: Optional[LibraryItem] = target_operation.result
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
|
if old_schema is not None:
|
||||||
|
if old_schema.is_synced(oss.model):
|
||||||
|
old_schema.visible = True
|
||||||
|
old_schema.save(update_fields=['visible'])
|
||||||
oss.update_positions(serializer.validated_data['positions'])
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
oss.set_input(target_operation.pk, schema)
|
oss.set_input(target_operation.pk, schema)
|
||||||
return Response(
|
return Response(
|
||||||
|
|
|
@ -263,7 +263,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
cst_list: list[m.Constituenta] = serializer.validated_data['items']
|
cst_list: list[m.Constituenta] = serializer.validated_data['items']
|
||||||
schema = m.RSForm(model)
|
schema = m.RSForm(model)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
PropagationFacade.before_delete(cst_list, schema)
|
PropagationFacade.before_delete_cst(cst_list, schema)
|
||||||
schema.delete_cst(cst_list)
|
schema.delete_cst(cst_list)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
IInputCreatedResponse,
|
IInputCreatedResponse,
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
IOperationCreatedResponse,
|
IOperationCreatedResponse,
|
||||||
|
IOperationDeleteData,
|
||||||
IOperationSchemaData,
|
IOperationSchemaData,
|
||||||
IOperationSetInputData,
|
IOperationSetInputData,
|
||||||
IOperationUpdateData,
|
IOperationUpdateData,
|
||||||
|
@ -40,7 +41,7 @@ export function postCreateOperation(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchDeleteOperation(oss: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
export function patchDeleteOperation(oss: string, request: FrontExchange<IOperationDeleteData, IOperationSchemaData>) {
|
||||||
AxiosPatch({
|
AxiosPatch({
|
||||||
endpoint: `/api/oss/${oss}/delete-operation`,
|
endpoint: `/api/oss/${oss}/delete-operation`,
|
||||||
request: request
|
request: request
|
||||||
|
|
|
@ -67,6 +67,11 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||||
<p>
|
<p>
|
||||||
<b>Тип:</b> {labelOperationType(node.data.operation.operation_type)}
|
<b>Тип:</b> {labelOperationType(node.data.operation.operation_type)}
|
||||||
</p>
|
</p>
|
||||||
|
{!node.data.operation.is_owned ? (
|
||||||
|
<p>
|
||||||
|
<b>КС не принадлежит ОСС</b>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
{node.data.operation.title ? (
|
{node.data.operation.title ? (
|
||||||
<p>
|
<p>
|
||||||
<b>Название: </b>
|
<b>Название: </b>
|
||||||
|
|
|
@ -27,7 +27,7 @@ function Checkbox({
|
||||||
}: CheckboxProps) {
|
}: CheckboxProps) {
|
||||||
const cursor = useMemo(() => {
|
const cursor = useMemo(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return 'cursor-auto';
|
return 'cursor-arrow';
|
||||||
} else if (setValue) {
|
} else if (setValue) {
|
||||||
return 'cursor-pointer';
|
return 'cursor-pointer';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -25,7 +25,7 @@ function CheckboxTristate({
|
||||||
}: CheckboxTristateProps) {
|
}: CheckboxTristateProps) {
|
||||||
const cursor = useMemo(() => {
|
const cursor = useMemo(() => {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
return 'cursor-auto';
|
return 'cursor-arrow';
|
||||||
} else if (setValue) {
|
} else if (setValue) {
|
||||||
return 'cursor-pointer';
|
return 'cursor-pointer';
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -91,7 +91,7 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
setSchema: setGlobalOSS,
|
setSchema: setGlobalOSS,
|
||||||
loading: ossLoading,
|
loading: ossLoading,
|
||||||
reload: reloadOssInternal
|
reload: reloadOssInternal
|
||||||
} = useOssDetails({ target: ossID });
|
} = useOssDetails({ target: ossID, items: items });
|
||||||
|
|
||||||
const reloadOSS = useCallback(
|
const reloadOSS = useCallback(
|
||||||
(callback?: () => void) => {
|
(callback?: () => void) => {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import { ILibraryUpdateData } from '@/models/library';
|
||||||
import {
|
import {
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
IOperationData,
|
IOperationData,
|
||||||
|
IOperationDeleteData,
|
||||||
IOperationSchema,
|
IOperationSchema,
|
||||||
IOperationSchemaData,
|
IOperationSchemaData,
|
||||||
IOperationSetInputData,
|
IOperationSetInputData,
|
||||||
|
@ -63,7 +64,7 @@ interface IOssContext {
|
||||||
|
|
||||||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperationData>) => void;
|
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperationData>) => void;
|
||||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
deleteOperation: (data: IOperationDeleteData, callback?: () => void) => void;
|
||||||
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
||||||
setInput: (data: IOperationSetInputData, callback?: () => void) => void;
|
setInput: (data: IOperationSetInputData, callback?: () => void) => void;
|
||||||
updateOperation: (data: IOperationUpdateData, callback?: () => void) => void;
|
updateOperation: (data: IOperationUpdateData, callback?: () => void) => void;
|
||||||
|
@ -309,7 +310,7 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteOperation = useCallback(
|
const deleteOperation = useCallback(
|
||||||
(data: ITargetOperation, callback?: () => void) => {
|
(data: IOperationDeleteData, callback?: () => void) => {
|
||||||
setProcessingError(undefined);
|
setProcessingError(undefined);
|
||||||
patchDeleteOperation(itemID, {
|
patchDeleteOperation(itemID, {
|
||||||
data: data,
|
data: data,
|
||||||
|
|
|
@ -31,10 +31,6 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
|
||||||
setSelected(newValue);
|
setSelected(newValue);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
onSubmit(selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
overflowVisible
|
overflowVisible
|
||||||
|
@ -42,7 +38,7 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
|
||||||
submitText='Подтвердить выбор'
|
submitText='Подтвердить выбор'
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={isValid}
|
canSubmit={isValid}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={() => onSubmit(selected)}
|
||||||
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')}
|
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')}
|
||||||
>
|
>
|
||||||
<div className='flex justify-between gap-3 items-center'>
|
<div className='flex justify-between gap-3 items-center'>
|
||||||
|
|
|
@ -34,10 +34,6 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
|
||||||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
onChangeLocation(location);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
overflowVisible
|
overflowVisible
|
||||||
|
@ -46,7 +42,7 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
|
||||||
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.location_len}`}
|
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.location_len}`}
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={isValid}
|
canSubmit={isValid}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={() => onChangeLocation(location)}
|
||||||
className={clsx('w-[35rem]', 'pb-3 px-6 flex gap-3')}
|
className={clsx('w-[35rem]', 'pb-3 px-6 flex gap-3')}
|
||||||
>
|
>
|
||||||
<div className='flex flex-col gap-2 w-[7rem] h-min'>
|
<div className='flex flex-col gap-2 w-[7rem] h-min'>
|
||||||
|
|
64
rsconcept/frontend/src/dialogs/DlgDeleteOperation.tsx
Normal file
64
rsconcept/frontend/src/dialogs/DlgDeleteOperation.tsx
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import Checkbox from '@/components/ui/Checkbox';
|
||||||
|
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
|
import TextInput from '@/components/ui/TextInput';
|
||||||
|
import { IOperation } from '@/models/oss';
|
||||||
|
|
||||||
|
interface DlgDeleteOperationProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
target: IOperation;
|
||||||
|
onSubmit: (keepConstituents: boolean, deleteSchema: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DlgDeleteOperation({ hideWindow, target, onSubmit }: DlgDeleteOperationProps) {
|
||||||
|
const [keepConstituents, setKeepConstituents] = useState(false);
|
||||||
|
const [deleteSchema, setDeleteSchema] = useState(false);
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
onSubmit(keepConstituents, deleteSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
overflowVisible
|
||||||
|
header='Удаление операции'
|
||||||
|
submitText='Подтвердить удаление'
|
||||||
|
hideWindow={hideWindow}
|
||||||
|
canSubmit={true}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column', 'select-none')}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
disabled
|
||||||
|
dense
|
||||||
|
noBorder
|
||||||
|
id='operation_alias'
|
||||||
|
label='Операция'
|
||||||
|
className='w-full'
|
||||||
|
value={target.alias}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Сохранить наследованные конституенты'
|
||||||
|
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
|
||||||
|
value={keepConstituents}
|
||||||
|
setValue={setKeepConstituents}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label='Удалить схему'
|
||||||
|
titleHtml={
|
||||||
|
!target.is_owned || target.result === undefined
|
||||||
|
? 'Привязанную схему нельзя удалить'
|
||||||
|
: 'Удалить схему вместе с операцией'
|
||||||
|
}
|
||||||
|
value={deleteSchema}
|
||||||
|
setValue={setDeleteSchema}
|
||||||
|
disabled={!target.is_owned || target.result === undefined}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DlgDeleteOperation;
|
|
@ -15,17 +15,12 @@ interface DlgGraphParamsProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps) {
|
function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps) {
|
||||||
const [params, updateParams] = usePartialUpdate(initial);
|
const [params, updateParams] = usePartialUpdate(initial);
|
||||||
|
|
||||||
function handleSubmit() {
|
|
||||||
hideWindow();
|
|
||||||
onConfirm(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
canSubmit
|
canSubmit
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
header='Настройки графа термов'
|
header='Настройки графа термов'
|
||||||
onSubmit={handleSubmit}
|
onSubmit={() => onConfirm(params)}
|
||||||
submitText='Применить'
|
submitText='Применить'
|
||||||
className='flex gap-6 justify-between px-6 pb-3 w-[30rem]'
|
className='flex gap-6 justify-between px-6 pb-3 w-[30rem]'
|
||||||
>
|
>
|
||||||
|
|
|
@ -26,8 +26,6 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
||||||
const [validated, setValidated] = useState(false);
|
const [validated, setValidated] = useState(false);
|
||||||
const [cstData, updateData] = usePartialUpdate(initial);
|
const [cstData, updateData] = usePartialUpdate(initial);
|
||||||
|
|
||||||
const handleSubmit = () => onRename(cstData);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (schema && initial && cstData.cst_type !== initial.cst_type) {
|
if (schema && initial && cstData.cst_type !== initial.cst_type) {
|
||||||
updateData({ alias: generateAlias(cstData.cst_type, schema) });
|
updateData({ alias: generateAlias(cstData.cst_type, schema) });
|
||||||
|
@ -47,7 +45,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
||||||
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
|
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
|
||||||
hideWindow={hideWindow}
|
hideWindow={hideWindow}
|
||||||
canSubmit={validated}
|
canSubmit={validated}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={() => onRename(cstData)}
|
||||||
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex justify-center items-center')}
|
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex justify-center items-center')}
|
||||||
>
|
>
|
||||||
<SelectSingle
|
<SelectSingle
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { useCallback, useEffect, useState } from 'react';
|
||||||
import { getOssDetails } from '@/backend/oss';
|
import { getOssDetails } from '@/backend/oss';
|
||||||
import { type ErrorData } from '@/components/info/InfoError';
|
import { type ErrorData } from '@/components/info/InfoError';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
|
import { ILibraryItem } from '@/models/library';
|
||||||
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||||
import { OssLoader } from '@/models/OssLoader';
|
import { OssLoader } from '@/models/OssLoader';
|
||||||
|
|
||||||
function useOssDetails({ target }: { target?: string }) {
|
function useOssDetails({ target, items }: { target?: string; items: ILibraryItem[] }) {
|
||||||
const { loading: userLoading } = useAuth();
|
const { loading: userLoading } = useAuth();
|
||||||
const [schema, setInner] = useState<IOperationSchema | undefined>(undefined);
|
const [schema, setInner] = useState<IOperationSchema | undefined>(undefined);
|
||||||
const [loading, setLoading] = useState(target != undefined);
|
const [loading, setLoading] = useState(target != undefined);
|
||||||
|
@ -19,7 +20,7 @@ function useOssDetails({ target }: { target?: string }) {
|
||||||
setInner(undefined);
|
setInner(undefined);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newSchema = new OssLoader(data).produceOSS();
|
const newSchema = new OssLoader(data, items).produceOSS();
|
||||||
setInner(newSchema);
|
setInner(newSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Graph } from './Graph';
|
import { Graph } from './Graph';
|
||||||
import { LibraryItemID } from './library';
|
import { ILibraryItem, LibraryItemID } from './library';
|
||||||
import {
|
import {
|
||||||
IOperation,
|
IOperation,
|
||||||
IOperationSchema,
|
IOperationSchema,
|
||||||
|
@ -21,10 +21,12 @@ export class OssLoader {
|
||||||
private oss: IOperationSchemaData;
|
private oss: IOperationSchemaData;
|
||||||
private graph: Graph = new Graph();
|
private graph: Graph = new Graph();
|
||||||
private operationByID = new Map<OperationID, IOperation>();
|
private operationByID = new Map<OperationID, IOperation>();
|
||||||
private schemas: LibraryItemID[] = [];
|
private schemaIDs: LibraryItemID[] = [];
|
||||||
|
private items: ILibraryItem[];
|
||||||
|
|
||||||
constructor(input: IOperationSchemaData) {
|
constructor(input: IOperationSchemaData, items: ILibraryItem[]) {
|
||||||
this.oss = input;
|
this.oss = input;
|
||||||
|
this.items = items;
|
||||||
}
|
}
|
||||||
|
|
||||||
produceOSS(): IOperationSchema {
|
produceOSS(): IOperationSchema {
|
||||||
|
@ -36,7 +38,7 @@ export class OssLoader {
|
||||||
|
|
||||||
result.operationByID = this.operationByID;
|
result.operationByID = this.operationByID;
|
||||||
result.graph = this.graph;
|
result.graph = this.graph;
|
||||||
result.schemas = this.schemas;
|
result.schemas = this.schemaIDs;
|
||||||
result.stats = this.calculateStats();
|
result.stats = this.calculateStats();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -53,12 +55,14 @@ export class OssLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractSchemas() {
|
private extractSchemas() {
|
||||||
this.schemas = this.oss.items.map(operation => operation.result).filter(item => item !== null);
|
this.schemaIDs = this.oss.items.map(operation => operation.result).filter(item => item !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private inferOperationAttributes() {
|
private inferOperationAttributes() {
|
||||||
this.graph.topologicalOrder().forEach(operationID => {
|
this.graph.topologicalOrder().forEach(operationID => {
|
||||||
const operation = this.operationByID.get(operationID)!;
|
const operation = this.operationByID.get(operationID)!;
|
||||||
|
const schema = this.items.find(item => item.id === operation.result);
|
||||||
|
operation.is_owned = !schema || (schema.owner === this.oss.owner && schema.location === this.oss.location);
|
||||||
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
||||||
operation.arguments = this.oss.arguments
|
operation.arguments = this.oss.arguments
|
||||||
.filter(item => item.operation === operationID)
|
.filter(item => item.operation === operationID)
|
||||||
|
@ -72,7 +76,7 @@ export class OssLoader {
|
||||||
count_operations: items.length,
|
count_operations: items.length,
|
||||||
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||||
count_synthesis: items.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
count_synthesis: items.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
||||||
count_schemas: this.schemas.length
|
count_schemas: this.schemaIDs.length
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ export interface IOperation {
|
||||||
|
|
||||||
result: LibraryItemID | null;
|
result: LibraryItemID | null;
|
||||||
|
|
||||||
|
is_owned: boolean;
|
||||||
substitutions: ICstSubstituteEx[];
|
substitutions: ICstSubstituteEx[];
|
||||||
arguments: OperationID[];
|
arguments: OperationID[];
|
||||||
}
|
}
|
||||||
|
@ -85,6 +86,14 @@ export interface IOperationUpdateData extends ITargetOperation {
|
||||||
substitutions: ICstSubstitute[] | undefined;
|
substitutions: ICstSubstitute[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents {@link IOperation} data, used in destruction process.
|
||||||
|
*/
|
||||||
|
export interface IOperationDeleteData extends ITargetOperation {
|
||||||
|
keep_constituents: boolean;
|
||||||
|
delete_schema: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents {@link IOperation} data, used in setInput process.
|
* Represents {@link IOperation} data, used in setInput process.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -33,6 +33,13 @@ function InputNode(node: OssNodeInternal) {
|
||||||
disabled={!hasFile}
|
disabled={!hasFile}
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
|
||||||
|
{!node.data.operation.is_owned ? (
|
||||||
|
<Overlay position='left-[0.2rem] top-[0.1rem]'>
|
||||||
|
<div className='border rounded-none clr-input h-[1.3rem]'></div>
|
||||||
|
</Overlay>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
|
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
|
||||||
{node.data.label}
|
{node.data.label}
|
||||||
{controller.showTooltip && !node.dragging ? (
|
{controller.showTooltip && !node.dragging ? (
|
||||||
|
|
|
@ -155,7 +155,7 @@ function NodeContextMenu({
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Удалить операцию'
|
text='Удалить операцию'
|
||||||
icon={<IconDestroy size='1rem' className='icon-red' />}
|
icon={<IconDestroy size='1rem' className='icon-red' />}
|
||||||
disabled={!controller.isMutable || controller.isProcessing}
|
disabled={!controller.isMutable || controller.isProcessing || !controller.canDelete(operation.id)}
|
||||||
onClick={handleDeleteOperation}
|
onClick={handleDeleteOperation}
|
||||||
/>
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|
|
@ -33,6 +33,12 @@ function OperationNode(node: OssNodeInternal) {
|
||||||
/>
|
/>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
|
||||||
|
{!node.data.operation.is_owned ? (
|
||||||
|
<Overlay position='left-[0.2rem] top-[0.1rem]'>
|
||||||
|
<div className='border rounded-none clr-input h-[1.3rem]'></div>
|
||||||
|
</Overlay>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
|
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
|
||||||
{node.data.label}
|
{node.data.label}
|
||||||
{controller.showTooltip && !node.dragging ? (
|
{controller.showTooltip && !node.dragging ? (
|
||||||
|
|
|
@ -182,12 +182,15 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
if (controller.selected.length !== 1) {
|
if (controller.selected.length !== 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
controller.deleteOperation(controller.selected[0], getPositions());
|
handleDeleteOperation(controller.selected[0]);
|
||||||
}, [controller, getPositions]);
|
}, [controller, getPositions]);
|
||||||
|
|
||||||
const handleDeleteOperation = useCallback(
|
const handleDeleteOperation = useCallback(
|
||||||
(target: OperationID) => {
|
(target: OperationID) => {
|
||||||
controller.deleteOperation(target, getPositions());
|
if (!controller.canDelete(target)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
controller.promptDeleteOperation(target, getPositions());
|
||||||
},
|
},
|
||||||
[controller, getPositions]
|
[controller, getPositions]
|
||||||
);
|
);
|
||||||
|
|
|
@ -175,7 +175,11 @@ function ToolbarOssGraph({
|
||||||
<MiniButton
|
<MiniButton
|
||||||
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
||||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
disabled={
|
||||||
|
controller.selected.length !== 1 ||
|
||||||
|
controller.isProcessing ||
|
||||||
|
!controller.canDelete(controller.selected[0])
|
||||||
|
}
|
||||||
onClick={onDelete}
|
onClick={onDelete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,17 +13,20 @@ import { useOSS } from '@/context/OssContext';
|
||||||
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
|
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
|
||||||
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
||||||
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
||||||
|
import DlgDeleteOperation from '@/dialogs/DlgDeleteOperation';
|
||||||
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
||||||
import DlgEditOperation from '@/dialogs/DlgEditOperation';
|
import DlgEditOperation from '@/dialogs/DlgEditOperation';
|
||||||
import { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library';
|
import { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library';
|
||||||
import { Position2D } from '@/models/miscellaneous';
|
import { Position2D } from '@/models/miscellaneous';
|
||||||
import {
|
import {
|
||||||
IOperationCreateData,
|
IOperationCreateData,
|
||||||
|
IOperationDeleteData,
|
||||||
IOperationPosition,
|
IOperationPosition,
|
||||||
IOperationSchema,
|
IOperationSchema,
|
||||||
IOperationSetInputData,
|
IOperationSetInputData,
|
||||||
IOperationUpdateData,
|
IOperationUpdateData,
|
||||||
OperationID
|
OperationID,
|
||||||
|
OperationType
|
||||||
} from '@/models/oss';
|
} from '@/models/oss';
|
||||||
import { UserID, UserLevel } from '@/models/user';
|
import { UserID, UserLevel } from '@/models/user';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
|
@ -62,7 +65,8 @@ export interface IOssEditContext extends ILibraryItemEditor {
|
||||||
|
|
||||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||||
promptCreateOperation: (props: ICreateOperationPrompt) => void;
|
promptCreateOperation: (props: ICreateOperationPrompt) => void;
|
||||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
canDelete: (target: OperationID) => boolean;
|
||||||
|
promptDeleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||||
|
@ -103,6 +107,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
const [showEditLocation, setShowEditLocation] = useState(false);
|
const [showEditLocation, setShowEditLocation] = useState(false);
|
||||||
const [showEditInput, setShowEditInput] = useState(false);
|
const [showEditInput, setShowEditInput] = useState(false);
|
||||||
const [showEditOperation, setShowEditOperation] = useState(false);
|
const [showEditOperation, setShowEditOperation] = useState(false);
|
||||||
|
const [showDeleteOperation, setShowDeleteOperation] = useState(false);
|
||||||
|
|
||||||
const [showCreateOperation, setShowCreateOperation] = useState(false);
|
const [showCreateOperation, setShowCreateOperation] = useState(false);
|
||||||
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
|
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
|
||||||
|
@ -258,15 +263,48 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
[model, positions]
|
[model, positions]
|
||||||
);
|
);
|
||||||
|
|
||||||
const deleteOperation = useCallback(
|
const canDelete = useCallback(
|
||||||
(target: OperationID, positions: IOperationPosition[]) => {
|
(target: OperationID) => {
|
||||||
model.deleteOperation({ target: target, positions: positions }, () =>
|
if (!model.schema) {
|
||||||
toast.success(information.operationDestroyed)
|
return false;
|
||||||
);
|
}
|
||||||
|
const operation = model.schema.operationByID.get(target);
|
||||||
|
if (!operation) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (operation.operation_type === OperationType.INPUT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return model.schema.graph.expandOutputs([target]).length === 0;
|
||||||
},
|
},
|
||||||
[model]
|
[model]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const promptDeleteOperation = useCallback(
|
||||||
|
(target: OperationID, positions: IOperationPosition[]) => {
|
||||||
|
setPositions(positions);
|
||||||
|
setTargetOperationID(target);
|
||||||
|
setShowDeleteOperation(true);
|
||||||
|
},
|
||||||
|
[model]
|
||||||
|
);
|
||||||
|
|
||||||
|
const deleteOperation = useCallback(
|
||||||
|
(keepConstituents: boolean, deleteSchema: boolean) => {
|
||||||
|
if (!targetOperationID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const data: IOperationDeleteData = {
|
||||||
|
target: targetOperationID,
|
||||||
|
positions: positions,
|
||||||
|
keep_constituents: keepConstituents,
|
||||||
|
delete_schema: deleteSchema
|
||||||
|
};
|
||||||
|
model.deleteOperation(data, () => toast.success(information.operationDestroyed));
|
||||||
|
},
|
||||||
|
[model, targetOperationID, positions]
|
||||||
|
);
|
||||||
|
|
||||||
const createInput = useCallback(
|
const createInput = useCallback(
|
||||||
(target: OperationID, positions: IOperationPosition[]) => {
|
(target: OperationID, positions: IOperationPosition[]) => {
|
||||||
model.createInput({ target: target, positions: positions }, new_schema => {
|
model.createInput({ target: target, positions: positions }, new_schema => {
|
||||||
|
@ -334,7 +372,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
openOperationSchema,
|
openOperationSchema,
|
||||||
savePositions,
|
savePositions,
|
||||||
promptCreateOperation,
|
promptCreateOperation,
|
||||||
deleteOperation,
|
canDelete,
|
||||||
|
promptDeleteOperation,
|
||||||
createInput,
|
createInput,
|
||||||
promptEditInput,
|
promptEditInput,
|
||||||
promptEditOperation,
|
promptEditOperation,
|
||||||
|
@ -381,6 +420,13 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
onSubmit={handleEditOperation}
|
onSubmit={handleEditOperation}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{showDeleteOperation ? (
|
||||||
|
<DlgDeleteOperation
|
||||||
|
hideWindow={() => setShowDeleteOperation(false)}
|
||||||
|
target={targetOperation!}
|
||||||
|
onSubmit={deleteOperation}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user