R: Refactor layout data structure

This commit is contained in:
Ivan 2025-06-11 16:39:12 +03:00
parent e101aea631
commit c92641f671
22 changed files with 277 additions and 195 deletions

View File

@ -68,8 +68,6 @@ class TestLibraryViewset(EndpointTester):
self.assertEqual(response.data['access_policy'], data['access_policy']) self.assertEqual(response.data['access_policy'], data['access_policy'])
self.assertEqual(response.data['visible'], data['visible']) self.assertEqual(response.data['visible'], data['visible'])
self.assertEqual(response.data['read_only'], data['read_only']) self.assertEqual(response.data['read_only'], data['read_only'])
self.assertEqual(oss.layout().data['operations'], [])
self.assertEqual(oss.layout().data['blocks'], [])
self.logout() self.logout()
data = {'title': 'Title2'} data = {'title': 'Title2'}

View File

@ -41,7 +41,7 @@ class LibraryViewSet(viewsets.ModelViewSet):
else: else:
serializer.save() serializer.save()
if serializer.data.get('item_type') == m.LibraryItemType.OPERATION_SCHEMA: if serializer.data.get('item_type') == m.LibraryItemType.OPERATION_SCHEMA:
Layout.objects.create(oss=serializer.instance, data={'operations': [], 'blocks': []}) Layout.objects.create(oss=serializer.instance, data=[])
def perform_update(self, serializer) -> None: def perform_update(self, serializer) -> None:
instance = serializer.save() instance = serializer.save()

View File

@ -0,0 +1,41 @@
from django.db import migrations
def migrate_layout(apps, schema_editor):
Layout = apps.get_model('oss', 'Layout')
for layout in Layout.objects.all():
previous_data = layout.data
new_layout = []
for operation in previous_data['operations']:
new_layout.append({
'nodeID': 'o' + str(operation['id']),
'x': operation['x'],
'y': operation['y'],
'width': 150,
'height': 40
})
for block in previous_data['blocks']:
new_layout.append({
'nodeID': 'b' + str(block['id']),
'x': block['x'],
'y': block['y'],
'width': block['width'],
'height': block['height']
})
layout.data = new_layout
layout.save(update_fields=['data'])
class Migration(migrations.Migration):
dependencies = [
('oss', '0011_remove_operation_position_x_and_more'),
]
operations = [
migrations.RunPython(migrate_layout),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 5.2.1 on 2025-06-11 10:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('oss', '0012 restructure_layout'),
]
operations = [
migrations.AlterField(
model_name='layout',
name='data',
field=models.JSONField(default=list, verbose_name='Расположение'),
),
]

View File

@ -13,7 +13,7 @@ class Layout(Model):
data = JSONField( data = JSONField(
verbose_name='Расположение', verbose_name='Расположение',
default=dict default=list
) )
class Meta: class Meta:

View File

@ -40,7 +40,7 @@ class OperationSchema:
def create(**kwargs) -> 'OperationSchema': def create(**kwargs) -> 'OperationSchema':
''' Create LibraryItem via OperationSchema. ''' ''' Create LibraryItem via OperationSchema. '''
model = LibraryItem.objects.create(item_type=LibraryItemType.OPERATION_SCHEMA, **kwargs) model = LibraryItem.objects.create(item_type=LibraryItemType.OPERATION_SCHEMA, **kwargs)
Layout.objects.create(oss=model, data={'operations': [], 'blocks': []}) Layout.objects.create(oss=model, data=[])
return OperationSchema(model) return OperationSchema(model)
@staticmethod @staticmethod

View File

@ -2,16 +2,9 @@
from rest_framework import serializers from rest_framework import serializers
class OperationNodeSerializer(serializers.Serializer): class NodeSerializer(serializers.Serializer):
''' Operation position. '''
id = serializers.IntegerField()
x = serializers.FloatField()
y = serializers.FloatField()
class BlockNodeSerializer(serializers.Serializer):
''' Block position. ''' ''' Block position. '''
id = serializers.IntegerField() nodeID = serializers.CharField()
x = serializers.FloatField() x = serializers.FloatField()
y = serializers.FloatField() y = serializers.FloatField()
width = serializers.FloatField() width = serializers.FloatField()
@ -19,13 +12,8 @@ class BlockNodeSerializer(serializers.Serializer):
class LayoutSerializer(serializers.Serializer): class LayoutSerializer(serializers.Serializer):
''' Layout for OperationSchema. ''' ''' Serializer: Layout data. '''
blocks = serializers.ListField( data = serializers.ListField(child=NodeSerializer()) # type: ignore
child=BlockNodeSerializer()
)
operations = serializers.ListField(
child=OperationNodeSerializer()
)
class SubstitutionExSerializer(serializers.Serializer): class SubstitutionExSerializer(serializers.Serializer):

View File

@ -13,7 +13,7 @@ from apps.rsform.serializers import SubstitutionSerializerBase
from shared import messages as msg from shared import messages as msg
from ..models import Argument, Block, Inheritance, Operation, OperationSchema, OperationType from ..models import Argument, Block, Inheritance, Operation, OperationSchema, OperationType
from .basics import LayoutSerializer, SubstitutionExSerializer from .basics import NodeSerializer, SubstitutionExSerializer
class OperationSerializer(serializers.ModelSerializer): class OperationSerializer(serializers.ModelSerializer):
@ -52,7 +52,9 @@ class CreateBlockSerializer(serializers.Serializer):
model = Block model = Block
fields = 'title', 'description', 'parent' fields = 'title', 'description', 'parent'
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
item_data = BlockCreateData() item_data = BlockCreateData()
width = serializers.FloatField() width = serializers.FloatField()
height = serializers.FloatField() height = serializers.FloatField()
@ -100,7 +102,10 @@ class UpdateBlockSerializer(serializers.Serializer):
model = Block model = Block
fields = 'title', 'description', 'parent' fields = 'title', 'description', 'parent'
layout = LayoutSerializer(required=False) layout = serializers.ListField(
child=NodeSerializer(),
required=False
)
target = PKField(many=False, queryset=Block.objects.all()) target = PKField(many=False, queryset=Block.objects.all())
item_data = UpdateBlockData() item_data = UpdateBlockData()
@ -127,7 +132,9 @@ class UpdateBlockSerializer(serializers.Serializer):
class DeleteBlockSerializer(serializers.Serializer): class DeleteBlockSerializer(serializers.Serializer):
''' Serializer: Delete block. ''' ''' Serializer: Delete block. '''
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
target = PKField(many=False, queryset=Block.objects.all().only('oss_id')) target = PKField(many=False, queryset=Block.objects.all().only('oss_id'))
def validate(self, attrs): def validate(self, attrs):
@ -142,7 +149,9 @@ class DeleteBlockSerializer(serializers.Serializer):
class MoveItemsSerializer(serializers.Serializer): class MoveItemsSerializer(serializers.Serializer):
''' Serializer: Move items to another parent. ''' ''' Serializer: Move items to another parent. '''
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
operations = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'parent')) operations = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'parent'))
blocks = PKField(many=True, queryset=Block.objects.all().only('oss_id', 'parent')) blocks = PKField(many=True, queryset=Block.objects.all().only('oss_id', 'parent'))
destination = PKField(many=False, queryset=Block.objects.all().only('oss_id'), allow_null=True) destination = PKField(many=False, queryset=Block.objects.all().only('oss_id'), allow_null=True)
@ -196,8 +205,12 @@ class CreateOperationSerializer(serializers.Serializer):
'alias', 'operation_type', 'title', \ 'alias', 'operation_type', 'title', \
'description', 'result', 'parent' 'description', 'result', 'parent'
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
item_data = CreateOperationData() item_data = CreateOperationData()
width = serializers.FloatField()
height = serializers.FloatField()
position_x = serializers.FloatField() position_x = serializers.FloatField()
position_y = serializers.FloatField() position_y = serializers.FloatField()
create_schema = serializers.BooleanField(default=False, required=False) create_schema = serializers.BooleanField(default=False, required=False)
@ -230,7 +243,10 @@ class UpdateOperationSerializer(serializers.Serializer):
model = Operation model = Operation
fields = 'alias', 'title', 'description', 'parent' fields = 'alias', 'title', 'description', 'parent'
layout = LayoutSerializer(required=False) layout = serializers.ListField(
child=NodeSerializer(),
required=False
)
target = PKField(many=False, queryset=Operation.objects.all()) target = PKField(many=False, queryset=Operation.objects.all())
item_data = UpdateOperationData() item_data = UpdateOperationData()
arguments = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'result_id'), required=False) arguments = PKField(many=True, queryset=Operation.objects.all().only('oss_id', 'result_id'), required=False)
@ -297,7 +313,9 @@ class UpdateOperationSerializer(serializers.Serializer):
class DeleteOperationSerializer(serializers.Serializer): class DeleteOperationSerializer(serializers.Serializer):
''' Serializer: Delete operation. ''' ''' Serializer: Delete operation. '''
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result')) target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
keep_constituents = serializers.BooleanField(default=False, required=False) keep_constituents = serializers.BooleanField(default=False, required=False)
delete_schema = serializers.BooleanField(default=False, required=False) delete_schema = serializers.BooleanField(default=False, required=False)
@ -314,7 +332,9 @@ class DeleteOperationSerializer(serializers.Serializer):
class TargetOperationSerializer(serializers.Serializer): class TargetOperationSerializer(serializers.Serializer):
''' Serializer: Target single operation. ''' ''' Serializer: Target single operation. '''
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id')) target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result_id'))
def validate(self, attrs): def validate(self, attrs):
@ -329,7 +349,9 @@ class TargetOperationSerializer(serializers.Serializer):
class SetOperationInputSerializer(serializers.Serializer): class SetOperationInputSerializer(serializers.Serializer):
''' Serializer: Set input schema for operation. ''' ''' Serializer: Set input schema for operation. '''
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
target = PKField(many=False, queryset=Operation.objects.all()) target = PKField(many=False, queryset=Operation.objects.all())
input = PKField( input = PKField(
many=False, many=False,
@ -366,7 +388,9 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
substitutions = serializers.ListField( substitutions = serializers.ListField(
child=SubstitutionExSerializer() child=SubstitutionExSerializer()
) )
layout = LayoutSerializer() layout = serializers.ListField(
child=NodeSerializer()
)
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
@ -459,7 +483,7 @@ class RelocateConstituentsSerializer(serializers.Serializer):
return attrs return attrs
# ====== Internals ================================================================================= # ====== Internals ============
def _collect_descendants(start_blocks: list[Block]) -> set[int]: def _collect_descendants(start_blocks: list[Block]) -> set[int]:

View File

@ -59,14 +59,11 @@ class TestChangeAttributes(EndpointTester):
self.operation3.refresh_from_db() self.operation3.refresh_from_db()
self.ks3 = RSForm(self.operation3.result) self.ks3 = RSForm(self.operation3.result)
self.layout_data = { self.layout_data = [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation3.pk, 'x': 0, 'y': 0}, ]
],
'blocks': []
}
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()

View File

@ -57,14 +57,11 @@ class TestChangeConstituents(EndpointTester):
self.ks3 = RSForm(self.operation3.result) self.ks3 = RSForm(self.operation3.result)
self.assertEqual(self.ks3.constituents().count(), 4) self.assertEqual(self.ks3.constituents().count(), 4)
self.layout_data = { self.layout_data = [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation3.pk, 'x': 0, 'y': 0}, ]
],
'blocks': []
}
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()

View File

@ -107,16 +107,13 @@ class TestChangeOperations(EndpointTester):
convention='KS5D4' convention='KS5D4'
) )
self.layout_data = { self.layout_data = [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation3.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation4.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation4.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation5.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40}
{'id': self.operation5.pk, 'x': 0, 'y': 0}, ]
],
'blocks': []
}
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()

View File

@ -107,16 +107,13 @@ class TestChangeSubstitutions(EndpointTester):
convention='KS5D4' convention='KS5D4'
) )
self.layout_data = { self.layout_data = [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation3.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation4.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation4.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation5.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation5.pk, 'x': 0, 'y': 0}, ]
],
'blocks': []
}
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()

View File

@ -49,16 +49,14 @@ class TestOssBlocks(EndpointTester):
title='3', title='3',
parent=self.block1 parent=self.block1
) )
self.layout_data = { self.layout_data = [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0},
], {'nodeID': 'b' + str(self.block1.pk), 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5},
'blocks': [ {'nodeID': 'b' + str(self.block2.pk), 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5},
{'id': self.block1.pk, 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5}, ]
{'id': self.block2.pk, 'x': 0, 'y': 0, 'width': 0.5, 'height': 0.5},
]
}
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()
@ -88,7 +86,7 @@ class TestOssBlocks(EndpointTester):
self.assertEqual(len(response.data['oss']['blocks']), 3) self.assertEqual(len(response.data['oss']['blocks']), 3)
new_block = response.data['new_block'] new_block = response.data['new_block']
layout = response.data['oss']['layout'] layout = response.data['oss']['layout']
item = [item for item in layout['blocks'] if item['id'] == new_block['id']][0] item = [item for item in layout if item['nodeID'] == 'b' + str(new_block['id'])][0]
self.assertEqual(new_block['title'], data['item_data']['title']) self.assertEqual(new_block['title'], data['item_data']['title'])
self.assertEqual(new_block['description'], data['item_data']['description']) self.assertEqual(new_block['description'], data['item_data']['description'])
self.assertEqual(new_block['parent'], None) self.assertEqual(new_block['parent'], None)

View File

@ -54,14 +54,11 @@ class TestOssOperations(EndpointTester):
alias='3', alias='3',
operation_type=OperationType.SYNTHESIS operation_type=OperationType.SYNTHESIS
) )
self.layout_data = { self.layout_data = [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation3.pk, 'x': 0, 'y': 0}, ]
],
'blocks': []
}
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()
@ -87,7 +84,9 @@ class TestOssOperations(EndpointTester):
}, },
'layout': self.layout_data, 'layout': self.layout_data,
'position_x': 1, 'position_x': 1,
'position_y': 1 'position_y': 1,
'width': 500,
'height': 50
} }
self.executeBadData(data=data) self.executeBadData(data=data)
@ -102,7 +101,7 @@ class TestOssOperations(EndpointTester):
self.assertEqual(len(response.data['oss']['operations']), 4) self.assertEqual(len(response.data['oss']['operations']), 4)
new_operation = response.data['new_operation'] new_operation = response.data['new_operation']
layout = response.data['oss']['layout'] layout = response.data['oss']['layout']
item = [item for item in layout['operations'] if item['id'] == new_operation['id']][0] item = [item for item in layout if item['nodeID'] == 'o' + str(new_operation['id'])][0]
self.assertEqual(new_operation['alias'], data['item_data']['alias']) self.assertEqual(new_operation['alias'], data['item_data']['alias'])
self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type']) self.assertEqual(new_operation['operation_type'], data['item_data']['operation_type'])
self.assertEqual(new_operation['title'], data['item_data']['title']) self.assertEqual(new_operation['title'], data['item_data']['title'])
@ -111,6 +110,8 @@ class TestOssOperations(EndpointTester):
self.assertEqual(new_operation['parent'], None) self.assertEqual(new_operation['parent'], None)
self.assertEqual(item['x'], data['position_x']) self.assertEqual(item['x'], data['position_x'])
self.assertEqual(item['y'], data['position_y']) self.assertEqual(item['y'], data['position_y'])
self.assertEqual(item['width'], data['width'])
self.assertEqual(item['height'], data['height'])
self.operation1.refresh_from_db() self.operation1.refresh_from_db()
self.executeForbidden(data=data, item=self.unowned_id) self.executeForbidden(data=data, item=self.unowned_id)
@ -132,7 +133,9 @@ class TestOssOperations(EndpointTester):
}, },
'layout': self.layout_data, 'layout': self.layout_data,
'position_x': 1, 'position_x': 1,
'position_y': 1 'position_y': 1,
'width': 500,
'height': 50
} }
self.executeBadData(data=data, item=self.owned_id) self.executeBadData(data=data, item=self.owned_id)
@ -160,6 +163,8 @@ class TestOssOperations(EndpointTester):
'layout': self.layout_data, 'layout': self.layout_data,
'position_x': 1, 'position_x': 1,
'position_y': 1, 'position_y': 1,
'width': 500,
'height': 50,
'arguments': [self.operation1.pk, self.operation3.pk] 'arguments': [self.operation1.pk, self.operation3.pk]
} }
response = self.executeCreated(data=data, item=self.owned_id) response = self.executeCreated(data=data, item=self.owned_id)
@ -185,7 +190,9 @@ class TestOssOperations(EndpointTester):
}, },
'layout': self.layout_data, 'layout': self.layout_data,
'position_x': 1, 'position_x': 1,
'position_y': 1 'position_y': 1,
'width': 500,
'height': 50
} }
response = self.executeCreated(data=data, item=self.owned_id) response = self.executeCreated(data=data, item=self.owned_id)
new_operation = response.data['new_operation'] new_operation = response.data['new_operation']
@ -207,7 +214,9 @@ class TestOssOperations(EndpointTester):
'create_schema': True, 'create_schema': True,
'layout': self.layout_data, 'layout': self.layout_data,
'position_x': 1, 'position_x': 1,
'position_y': 1 'position_y': 1,
'width': 500,
'height': 50
} }
self.executeBadData(data=data, item=self.owned_id) self.executeBadData(data=data, item=self.owned_id)
data['item_data']['result'] = None data['item_data']['result'] = None
@ -244,7 +253,7 @@ class TestOssOperations(EndpointTester):
self.login() self.login()
response = self.executeOK(data=data) response = self.executeOK(data=data)
layout = response.data['layout'] layout = response.data['layout']
deleted_items = [item for item in layout['operations'] if item['id'] == data['target']] deleted_items = [item for item in layout if item['nodeID'] == 'o' + str(data['target'])]
self.assertEqual(len(response.data['operations']), 2) self.assertEqual(len(response.data['operations']), 2)
self.assertEqual(len(deleted_items), 0) self.assertEqual(len(deleted_items), 0)

View File

@ -55,11 +55,11 @@ class TestOssViewset(EndpointTester):
alias='3', alias='3',
operation_type=OperationType.SYNTHESIS operation_type=OperationType.SYNTHESIS
) )
self.layout_data = {'operations': [ self.layout_data = [
{'id': self.operation1.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation1.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40},
{'id': self.operation3.pk, 'x': 0, 'y': 0}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 0, 'y': 0, 'width': 150, 'height': 40}
], 'blocks': []} ]
layout = self.owned.layout() layout = self.owned.layout()
layout.data = self.layout_data layout.data = self.layout_data
layout.save() layout.save()
@ -107,10 +107,9 @@ class TestOssViewset(EndpointTester):
self.assertEqual(arguments[1]['argument'], self.operation2.pk) self.assertEqual(arguments[1]['argument'], self.operation2.pk)
layout = response.data['layout'] layout = response.data['layout']
self.assertEqual(layout['blocks'], []) self.assertEqual(layout[0], self.layout_data[0])
self.assertEqual(layout['operations'][0], {'id': self.operation1.pk, 'x': 0, 'y': 0}) self.assertEqual(layout[1], self.layout_data[1])
self.assertEqual(layout['operations'][1], {'id': self.operation2.pk, 'x': 0, 'y': 0}) self.assertEqual(layout[2], self.layout_data[2])
self.assertEqual(layout['operations'][2], {'id': self.operation3.pk, 'x': 0, 'y': 0})
self.executeOK(item=self.unowned_id) self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id) self.executeForbidden(item=self.private_id)
@ -126,23 +125,21 @@ class TestOssViewset(EndpointTester):
self.populateData() self.populateData()
self.executeBadData(item=self.owned_id) self.executeBadData(item=self.owned_id)
data = {'operations': [], 'blocks': []} data = {'data': []}
self.executeOK(data=data) self.executeOK(data=data)
data = { data = {'data': [
'operations': [ {'nodeID': 'o' + str(self.operation1.pk), 'x': 42.1, 'y': 1337, 'width': 150, 'height': 40},
{'id': self.operation1.pk, 'x': 42.1, 'y': 1337}, {'nodeID': 'o' + str(self.operation2.pk), 'x': 36.1, 'y': 1437, 'width': 150, 'height': 40},
{'id': self.operation2.pk, 'x': 36.1, 'y': 1437}, {'nodeID': 'o' + str(self.operation3.pk), 'x': 36.1, 'y': 1435, 'width': 150, 'height': 40}
{'id': self.operation3.pk, 'x': 36.1, 'y': 1435} ]}
], 'blocks': []
}
self.toggle_admin(True) self.toggle_admin(True)
self.executeOK(data=data, item=self.unowned_id) self.executeOK(data=data, item=self.unowned_id)
self.toggle_admin(False) self.toggle_admin(False)
self.executeOK(data=data, item=self.owned_id) self.executeOK(data=data, item=self.owned_id)
self.owned.refresh_from_db() self.owned.refresh_from_db()
self.assertEqual(self.owned.layout().data, data) self.assertEqual(self.owned.layout().data, data['data'])
self.executeForbidden(data=data, item=self.unowned_id) self.executeForbidden(data=data, item=self.unowned_id)
self.executeForbidden(data=data, item=self.private_id) self.executeForbidden(data=data, item=self.private_id)

View File

@ -91,7 +91,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
''' Endpoint: Update schema layout. ''' ''' Endpoint: Update schema layout. '''
serializer = s.LayoutSerializer(data=request.data) serializer = s.LayoutSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
m.OperationSchema(self.get_object()).update_layout(serializer.validated_data) m.OperationSchema(self.get_object()).update_layout(serializer.validated_data['data'])
return Response(status=c.HTTP_200_OK) return Response(status=c.HTTP_200_OK)
@extend_schema( @extend_schema(
@ -120,8 +120,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
children_operations: list[m.Operation] = serializer.validated_data['children_operations'] children_operations: list[m.Operation] = serializer.validated_data['children_operations']
with transaction.atomic(): with transaction.atomic():
new_block = oss.create_block(**serializer.validated_data['item_data']) new_block = oss.create_block(**serializer.validated_data['item_data'])
layout['blocks'].append({ layout.append({
'id': new_block.pk, 'nodeID': 'b' + str(new_block.pk),
'x': serializer.validated_data['position_x'], 'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'], 'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'], 'width': serializer.validated_data['width'],
@ -205,7 +205,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
oss = m.OperationSchema(self.get_object()) oss = m.OperationSchema(self.get_object())
block = cast(m.Block, serializer.validated_data['target']) block = cast(m.Block, serializer.validated_data['target'])
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
layout['blocks'] = [x for x in layout['blocks'] if x['id'] != block.pk] layout = [x for x in layout if x['nodeID'] != 'b' + str(block.pk)]
with transaction.atomic(): with transaction.atomic():
oss.delete_block(block) oss.delete_block(block)
oss.update_layout(layout) oss.update_layout(layout)
@ -274,10 +274,12 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
with transaction.atomic(): with transaction.atomic():
new_operation = oss.create_operation(**serializer.validated_data['item_data']) new_operation = oss.create_operation(**serializer.validated_data['item_data'])
layout['operations'].append({ layout.append({
'id': new_operation.pk, 'nodeID': 'o' + str(new_operation.pk),
'x': serializer.validated_data['position_x'], 'x': serializer.validated_data['position_x'],
'y': serializer.validated_data['position_y'] 'y': serializer.validated_data['position_y'],
'width': serializer.validated_data['width'],
'height': serializer.validated_data['height']
}) })
oss.update_layout(layout) oss.update_layout(layout)
@ -384,7 +386,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
operation = cast(m.Operation, serializer.validated_data['target']) operation = cast(m.Operation, serializer.validated_data['target'])
old_schema = operation.result old_schema = operation.result
layout = serializer.validated_data['layout'] layout = serializer.validated_data['layout']
layout['operations'] = [x for x in layout['operations'] if x['id'] != operation.pk] layout = [x for x in layout if x['nodeID'] != 'o' + str(operation.pk)]
with transaction.atomic(): with transaction.atomic():
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents']) oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
oss.update_layout(layout) oss.update_layout(layout)

View File

@ -50,7 +50,7 @@ export const ossApi = {
axiosPatch({ axiosPatch({
endpoint: `/api/oss/${itemID}/update-layout`, endpoint: `/api/oss/${itemID}/update-layout`,
request: { request: {
data: data, data: { data: data },
successMessage: isSilent ? undefined : infoMsg.changesSaved successMessage: isSilent ? undefined : infoMsg.changesSaved
} }
}), }),

View File

@ -90,7 +90,7 @@ export class OssLoader {
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); const schema = this.items.find(item => item.id === operation.result);
const position = this.oss.layout.operations.find(item => item.id === operationID); const position = this.oss.layout.find(item => item.nodeID === operation.nodeID);
operation.x = position?.x ?? 0; operation.x = position?.x ?? 0;
operation.y = position?.y ?? 0; operation.y = position?.y ?? 0;
operation.is_consolidation = this.inferConsolidation(operationID); operation.is_consolidation = this.inferConsolidation(operationID);
@ -104,7 +104,7 @@ export class OssLoader {
private inferBlockAttributes() { private inferBlockAttributes() {
this.oss.blocks.forEach(block => { this.oss.blocks.forEach(block => {
const geometry = this.oss.layout.blocks.find(item => item.id === block.id); const geometry = this.oss.layout.find(item => item.nodeID === block.nodeID);
block.x = geometry?.x ?? 0; block.x = geometry?.x ?? 0;
block.y = geometry?.y ?? 0; block.y = geometry?.y ?? 0;
block.width = geometry?.width ?? BLOCK_NODE_MIN_WIDTH; block.width = geometry?.width ?? BLOCK_NODE_MIN_WIDTH;

View File

@ -72,11 +72,8 @@ export type IRelocateConstituentsDTO = z.infer<typeof schemaRelocateConstituents
/** Represents {@link IConstituenta} reference. */ /** Represents {@link IConstituenta} reference. */
export type IConstituentaReference = z.infer<typeof schemaConstituentaReference>; export type IConstituentaReference = z.infer<typeof schemaConstituentaReference>;
/** Represents {@link IOperation} position. */ /** Represents {@link IOperationSchema} node position. */
export type IOperationPosition = z.infer<typeof schemaOperationPosition>; export type INodePosition = z.infer<typeof schemaNodePosition>;
/** Represents {@link IBlock} position. */
export type IBlockPosition = z.infer<typeof schemaBlockPosition>;
// ====== Schemas ====== // ====== Schemas ======
export const schemaOperationType = z.enum(Object.values(OperationType) as [OperationType, ...OperationType[]]); export const schemaOperationType = z.enum(Object.values(OperationType) as [OperationType, ...OperationType[]]);
@ -108,24 +105,15 @@ export const schemaCstSubstituteInfo = schemaSubstituteConstituents.extend({
substitution_term: z.string() substitution_term: z.string()
}); });
export const schemaOperationPosition = z.strictObject({ export const schemaNodePosition = z.strictObject({
id: z.number(), nodeID: z.string(),
x: z.number(),
y: z.number()
});
export const schemaBlockPosition = z.strictObject({
id: z.number(),
x: z.number(), x: z.number(),
y: z.number(), y: z.number(),
width: z.number(), width: z.number(),
height: z.number() height: z.number()
}); });
export const schemaOssLayout = z.strictObject({ export const schemaOssLayout = z.array(schemaNodePosition);
operations: z.array(schemaOperationPosition),
blocks: z.array(schemaBlockPosition)
});
export const schemaOperationSchema = schemaLibraryItem.extend({ export const schemaOperationSchema = schemaLibraryItem.extend({
editors: z.number().array(), editors: z.number().array(),
@ -188,6 +176,8 @@ export const schemaCreateOperation = z.strictObject({
}), }),
position_x: z.number(), position_x: z.number(),
position_y: z.number(), position_y: z.number(),
width: z.number(),
height: z.number(),
arguments: z.array(z.number()), arguments: z.array(z.number()),
create_schema: z.boolean() create_schema: z.boolean()
}); });

View File

@ -13,7 +13,7 @@ import { useDialogsStore } from '@/stores/dialogs';
import { type ICreateOperationDTO, OperationType, schemaCreateOperation } from '../../backend/types'; import { type ICreateOperationDTO, OperationType, schemaCreateOperation } from '../../backend/types';
import { useCreateOperation } from '../../backend/use-create-operation'; import { useCreateOperation } from '../../backend/use-create-operation';
import { describeOperationType, labelOperationType } from '../../labels'; import { describeOperationType, labelOperationType } from '../../labels';
import { type LayoutManager } from '../../models/oss-layout-api'; import { type LayoutManager, OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../../models/oss-layout-api';
import { TabInputOperation } from './tab-input-operation'; import { TabInputOperation } from './tab-input-operation';
import { TabSynthesisOperation } from './tab-synthesis-operation'; import { TabSynthesisOperation } from './tab-synthesis-operation';
@ -54,6 +54,8 @@ export function DlgCreateOperation() {
position_x: defaultX, position_x: defaultX,
position_y: defaultY, position_y: defaultY,
arguments: initialInputs, arguments: initialInputs,
width: OPERATION_NODE_WIDTH,
height: OPERATION_NODE_HEIGHT,
create_schema: false, create_schema: false,
layout: manager.layout layout: manager.layout
}, },

View File

@ -1,10 +1,4 @@
import { import { type ICreateBlockDTO, type ICreateOperationDTO, type INodePosition, type IOssLayout } from '../backend/types';
type IBlockPosition,
type ICreateBlockDTO,
type ICreateOperationDTO,
type IOperationPosition,
type IOssLayout
} from '../backend/types';
import { type IOperationSchema } from './oss'; import { type IOperationSchema } from './oss';
import { type Position2D, type Rectangle2D } from './oss-layout'; import { type Position2D, type Rectangle2D } from './oss-layout';
@ -12,8 +6,8 @@ import { type Position2D, type Rectangle2D } from './oss-layout';
export const GRID_SIZE = 10; // pixels - size of OSS grid export const GRID_SIZE = 10; // pixels - size of OSS grid
const MIN_DISTANCE = 2 * GRID_SIZE; // pixels - minimum distance between nodes const MIN_DISTANCE = 2 * GRID_SIZE; // pixels - minimum distance between nodes
const OPERATION_NODE_WIDTH = 150; export const OPERATION_NODE_WIDTH = 150;
const OPERATION_NODE_HEIGHT = 40; export const OPERATION_NODE_HEIGHT = 40;
/** Layout manipulations for {@link IOperationSchema}. */ /** Layout manipulations for {@link IOperationSchema}. */
export class LayoutManager { export class LayoutManager {
@ -30,27 +24,30 @@ export class LayoutManager {
} }
/** Calculate insert position for a new {@link IOperation} */ /** Calculate insert position for a new {@link IOperation} */
newOperationPosition(data: ICreateOperationDTO): Position2D { newOperationPosition(data: ICreateOperationDTO): Rectangle2D {
let result = { x: data.position_x, y: data.position_y }; let result = { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
const operations = this.layout.operations; const parentNode = this.layout.find(pos => pos.nodeID === `b${data.item_data.parent}`);
const parentNode = this.layout.blocks.find(pos => pos.id === data.item_data.parent); if (this.oss.operations.length === 0) {
if (operations.length === 0) {
return result; return result;
} }
const operations = this.layout.filter(pos => pos.nodeID.startsWith('o'));
if (data.arguments.length !== 0) { if (data.arguments.length !== 0) {
result = calculatePositionFromArgs(data.arguments, operations); const pos = calculatePositionFromArgs(
operations.filter(node => data.arguments.includes(Number(node.nodeID.slice(1))))
);
result.x = pos.x;
result.y = pos.y;
} else if (parentNode) { } else if (parentNode) {
result.x = parentNode.x + MIN_DISTANCE; result.x = parentNode.x + MIN_DISTANCE;
result.y = parentNode.y + MIN_DISTANCE; result.y = parentNode.y + MIN_DISTANCE;
} else { } else {
result = this.calculatePositionForFreeOperation(result); const pos = this.calculatePositionForFreeOperation(result);
result.x = pos.x;
result.y = pos.y;
} }
result = preventOverlap( result = preventOverlap(result, operations);
{ ...result, width: OPERATION_NODE_WIDTH, height: OPERATION_NODE_HEIGHT },
operations.map(node => ({ ...node, width: OPERATION_NODE_WIDTH, height: OPERATION_NODE_HEIGHT }))
);
if (parentNode) { if (parentNode) {
const borderX = result.x + OPERATION_NODE_WIDTH + MIN_DISTANCE; const borderX = result.x + OPERATION_NODE_WIDTH + MIN_DISTANCE;
@ -64,18 +61,18 @@ export class LayoutManager {
// TODO: trigger cascading updates // TODO: trigger cascading updates
} }
return { x: result.x, y: result.y }; return result;
} }
/** Calculate insert position for a new {@link IBlock} */ /** Calculate insert position for a new {@link IBlock} */
newBlockPosition(data: ICreateBlockDTO): Rectangle2D { newBlockPosition(data: ICreateBlockDTO): Rectangle2D {
const block_nodes = data.children_blocks const block_nodes = data.children_blocks
.map(id => this.layout.blocks.find(block => block.id === id)) .map(id => this.layout.find(block => block.nodeID === `b${id}`))
.filter(node => !!node); .filter(node => !!node);
const operation_nodes = data.children_operations const operation_nodes = data.children_operations
.map(id => this.layout.operations.find(operation => operation.id === id)) .map(id => this.layout.find(operation => operation.nodeID === `o${id}`))
.filter(node => !!node); .filter(node => !!node);
const parentNode = this.layout.blocks.find(pos => pos.id === data.item_data.parent); const parentNode = this.layout.find(pos => pos.nodeID === `b${data.item_data.parent}`);
let result: Rectangle2D = { x: data.position_x, y: data.position_y, width: data.width, height: data.height }; let result: Rectangle2D = { x: data.position_x, y: data.position_y, width: data.width, height: data.height };
@ -98,19 +95,21 @@ export class LayoutManager {
if (block_nodes.length === 0 && operation_nodes.length === 0) { if (block_nodes.length === 0 && operation_nodes.length === 0) {
if (parentNode) { if (parentNode) {
const siblings = this.oss.blocks.filter(block => block.parent === parentNode.id).map(block => block.id); const siblings = this.oss.blocks
.filter(block => block.parent === data.item_data.parent)
.map(block => block.nodeID);
if (siblings.length > 0) { if (siblings.length > 0) {
result = preventOverlap( result = preventOverlap(
result, result,
this.layout.blocks.filter(block => siblings.includes(block.id)) this.layout.filter(node => siblings.includes(node.nodeID))
); );
} }
} else { } else {
const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.id); const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.nodeID);
if (rootBlocks.length > 0) { if (rootBlocks.length > 0) {
result = preventOverlap( result = preventOverlap(
result, result,
this.layout.blocks.filter(block => rootBlocks.includes(block.id)) this.layout.filter(node => rootBlocks.includes(node.nodeID))
); );
} }
} }
@ -133,7 +132,34 @@ export class LayoutManager {
/** Update layout when parent changes */ /** Update layout when parent changes */
onOperationChangeParent(targetID: number, newParent: number | null) { onOperationChangeParent(targetID: number, newParent: number | null) {
console.error('not implemented', targetID, newParent); const targetNode = this.layout.find(pos => pos.nodeID === `o${targetID}`);
if (!targetNode) {
return;
}
if (newParent === null) {
const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.nodeID);
const blocksPositions = this.layout.filter(pos => rootBlocks.includes(pos.nodeID));
if (blocksPositions.length === 0) {
return;
}
const operationPositions = this.layout.filter(pos => pos.nodeID.startsWith('o') && pos.nodeID !== `o${targetID}`);
const newRect = preventOverlap(targetNode, [...blocksPositions, ...operationPositions]);
targetNode.x = newRect.x;
targetNode.y = newRect.y;
return;
} else {
const parentNode = this.layout.find(pos => pos.nodeID === `b${newParent}`);
if (!parentNode) {
return;
}
if (rectanglesOverlap(parentNode, targetNode)) {
return;
}
// TODO: fix position based on parent
}
} }
/** Update layout when parent changes */ /** Update layout when parent changes */
@ -142,17 +168,16 @@ export class LayoutManager {
} }
private calculatePositionForFreeOperation(initial: Position2D): Position2D { private calculatePositionForFreeOperation(initial: Position2D): Position2D {
const operations = this.layout.operations; if (this.oss.operations.length === 0) {
if (operations.length === 0) {
return initial; return initial;
} }
const freeInputs = this.oss.operations const freeInputs = this.oss.operations
.filter(operation => operation.arguments.length === 0 && operation.parent === null) .filter(operation => operation.arguments.length === 0 && operation.parent === null)
.map(operation => operation.id); .map(operation => operation.nodeID);
let inputsPositions = operations.filter(pos => freeInputs.includes(pos.id)); let inputsPositions = this.layout.filter(pos => freeInputs.includes(pos.nodeID));
if (inputsPositions.length === 0) { if (inputsPositions.length === 0) {
inputsPositions = operations; inputsPositions = this.layout.filter(pos => pos.nodeID.startsWith('o'));
} }
const maxX = Math.max(...inputsPositions.map(node => node.x)); const maxX = Math.max(...inputsPositions.map(node => node.x));
const minY = Math.min(...inputsPositions.map(node => node.y)); const minY = Math.min(...inputsPositions.map(node => node.y));
@ -163,8 +188,8 @@ export class LayoutManager {
} }
private calculatePositionForFreeBlock(initial: Rectangle2D): Rectangle2D { private calculatePositionForFreeBlock(initial: Rectangle2D): Rectangle2D {
const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.id); const rootBlocks = this.oss.blocks.filter(block => block.parent === null).map(block => block.nodeID);
const blocksPositions = this.layout.blocks.filter(pos => rootBlocks.includes(pos.id)); const blocksPositions = this.layout.filter(pos => rootBlocks.includes(pos.nodeID));
if (blocksPositions.length === 0) { if (blocksPositions.length === 0) {
return initial; return initial;
} }
@ -211,11 +236,10 @@ function preventOverlap(target: Rectangle2D, fixedRectangles: Rectangle2D[]): Re
return target; return target;
} }
function calculatePositionFromArgs(args: number[], operations: IOperationPosition[]): Position2D { function calculatePositionFromArgs(args: INodePosition[]): Position2D {
const argNodes = operations.filter(pos => args.includes(pos.id)); const maxY = Math.max(...args.map(node => node.y));
const maxY = Math.max(...argNodes.map(node => node.y)); const minX = Math.min(...args.map(node => node.x));
const minX = Math.min(...argNodes.map(node => node.x)); const maxX = Math.max(...args.map(node => node.x));
const maxX = Math.max(...argNodes.map(node => node.x));
return { return {
x: Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE, x: Math.ceil((maxX + minX) / 2 / GRID_SIZE) * GRID_SIZE,
y: maxY + 2 * OPERATION_NODE_HEIGHT + MIN_DISTANCE y: maxY + 2 * OPERATION_NODE_HEIGHT + MIN_DISTANCE
@ -224,8 +248,8 @@ function calculatePositionFromArgs(args: number[], operations: IOperationPositio
function calculatePositionFromChildren( function calculatePositionFromChildren(
initial: Rectangle2D, initial: Rectangle2D,
operations: IOperationPosition[], operations: INodePosition[],
blocks: IBlockPosition[] blocks: INodePosition[]
): Rectangle2D { ): Rectangle2D {
let left = undefined; let left = undefined;
let top = undefined; let top = undefined;
@ -249,11 +273,11 @@ function calculatePositionFromChildren(
top = top === undefined ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE); top = top === undefined ? operation.y - MIN_DISTANCE : Math.min(top, operation.y - MIN_DISTANCE);
right = right =
right === undefined right === undefined
? Math.max(left + initial.width, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE) ? Math.max(left + initial.width, operation.x + operation.width + MIN_DISTANCE)
: Math.max(right, operation.x + OPERATION_NODE_WIDTH + MIN_DISTANCE); : Math.max(right, operation.x + operation.width + MIN_DISTANCE);
bottom = !bottom bottom = !bottom
? Math.max(top + initial.height, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE) ? Math.max(top + initial.height, operation.y + operation.height + MIN_DISTANCE)
: Math.max(bottom, operation.y + OPERATION_NODE_HEIGHT + MIN_DISTANCE); : Math.max(bottom, operation.y + operation.height + MIN_DISTANCE);
} }
return { return {

View File

@ -3,6 +3,7 @@ import { type Node, useReactFlow } from 'reactflow';
import { type IOssLayout } from '../../../backend/types'; import { type IOssLayout } from '../../../backend/types';
import { type IOperationSchema } from '../../../models/oss'; import { type IOperationSchema } from '../../../models/oss';
import { type Position2D } from '../../../models/oss-layout'; import { type Position2D } from '../../../models/oss-layout';
import { OPERATION_NODE_HEIGHT, OPERATION_NODE_WIDTH } from '../../../models/oss-layout-api';
import { useOssEdit } from '../oss-edit-context'; import { useOssEdit } from '../oss-edit-context';
import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from './graph/block-node'; import { BLOCK_NODE_MIN_HEIGHT, BLOCK_NODE_MIN_WIDTH } from './graph/block-node';
@ -14,22 +15,24 @@ export function useGetLayout() {
return function getLayout(): IOssLayout { return function getLayout(): IOssLayout {
const nodes = getNodes(); const nodes = getNodes();
const nodeById = new Map(nodes.map(node => [node.id, node])); const nodeById = new Map(nodes.map(node => [node.id, node]));
return { return [
operations: nodes ...nodes
.filter(node => node.type !== 'block') .filter(node => node.type !== 'block')
.map(node => ({ .map(node => ({
id: schema.itemByNodeID.get(node.id)!.id, nodeID: node.id,
...computeAbsolutePosition(node, schema, nodeById) ...computeAbsolutePosition(node, schema, nodeById),
width: OPERATION_NODE_WIDTH,
height: OPERATION_NODE_HEIGHT
})), })),
blocks: nodes ...nodes
.filter(node => node.type === 'block') .filter(node => node.type === 'block')
.map(node => ({ .map(node => ({
id: schema.itemByNodeID.get(node.id)!.id, nodeID: node.id,
...computeAbsolutePosition(node, schema, nodeById), ...computeAbsolutePosition(node, schema, nodeById),
width: node.width ?? BLOCK_NODE_MIN_WIDTH, width: node.width ?? BLOCK_NODE_MIN_WIDTH,
height: node.height ?? BLOCK_NODE_MIN_HEIGHT height: node.height ?? BLOCK_NODE_MIN_HEIGHT
})) }))
}; ];
}; };
} }