Compare commits

..

No commits in common. "f390762c7d433539743ba8386fbf076cb477b769" and "3b036f2c9d8b3d19b64cf70caada502f3e52858e" have entirely different histories.

37 changed files with 2571 additions and 1014 deletions

View File

@ -59,7 +59,6 @@ This readme file is used mostly to document project dependencies and conventions
- postcss - postcss
- autoprefixer - autoprefixer
- eslint-plugin-simple-import-sort - eslint-plugin-simple-import-sort
- eslint-plugin-react-hooks
- eslint-plugin-tsdoc - eslint-plugin-tsdoc
- vite - vite
- jest - jest

View File

@ -267,51 +267,7 @@ class OperationSchema:
self.save(update_fields=['time_update']) self.save(update_fields=['time_update'])
return True return True
def relocate_down(self, source: RSForm, destination: RSForm, items: list[Constituenta]): def after_create_cst(self, source: RSForm, cst_list: list[Constituenta]) -> None:
''' Move list of constituents to specific schema inheritor. '''
self.cache.ensure_loaded()
self.cache.insert_schema(source)
self.cache.insert_schema(destination)
operation = self.cache.get_operation(destination.model.pk)
self._undo_substitutions_cst(items, operation, destination)
inheritance_to_delete = [item for item in self.cache.inheritance[operation.pk] if item.parent_id in items]
for item in inheritance_to_delete:
self.cache.remove_inheritance(item)
Inheritance.objects.filter(operation_id=operation.pk, parent__in=items).delete()
def relocate_up(self, source: RSForm, destination: RSForm, items: list[Constituenta]) -> list[Constituenta]:
''' Move list of constituents to specific schema upstream. '''
self.cache.ensure_loaded()
self.cache.insert_schema(source)
self.cache.insert_schema(destination)
operation = self.cache.get_operation(source.model.pk)
alias_mapping: dict[str, str] = {}
for item in self.cache.inheritance[operation.pk]:
if item.parent_id in destination.cache.by_id:
source_cst = source.cache.by_id[item.child_id]
destination_cst = destination.cache.by_id[item.parent_id]
alias_mapping[source_cst.alias] = destination_cst.alias
new_items = destination.insert_copy(items, initial_mapping=alias_mapping)
for index, cst in enumerate(new_items):
new_inheritance = Inheritance.objects.create(
operation=operation,
child=items[index],
parent=cst
)
self.cache.insert_inheritance(new_inheritance)
self.after_create_cst(destination, new_items, exclude=[operation.pk])
return new_items
def after_create_cst(
self, source: RSForm,
cst_list: list[Constituenta],
exclude: Optional[list[int]] = None
) -> None:
''' Trigger cascade resolutions when new constituent is created. ''' ''' Trigger cascade resolutions when new constituent is created. '''
self.cache.insert_schema(source) self.cache.insert_schema(source)
inserted_aliases = [cst.alias for cst in cst_list] inserted_aliases = [cst.alias for cst in cst_list]
@ -325,7 +281,7 @@ class OperationSchema:
if cst is not None: if cst is not None:
alias_mapping[alias] = cst alias_mapping[alias] = cst
operation = self.cache.get_operation(source.model.pk) operation = self.cache.get_operation(source.model.pk)
self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping, exclude) self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping)
def after_change_cst_type(self, source: RSForm, target: Constituenta) -> None: def after_change_cst_type(self, source: RSForm, target: Constituenta) -> None:
''' Trigger cascade resolutions when constituenta type is changed. ''' ''' Trigger cascade resolutions when constituenta type is changed. '''
@ -388,19 +344,17 @@ class OperationSchema:
mapping={} mapping={}
) )
# pylint: disable=too-many-arguments, too-many-positional-arguments
def _cascade_inherit_cst( def _cascade_inherit_cst(
self, target_operation: int, self,
target_operation: int,
source: RSForm, source: RSForm,
items: list[Constituenta], items: list[Constituenta],
mapping: CstMapping, mapping: CstMapping
exclude: Optional[list[int]] = None
) -> None: ) -> None:
children = self.cache.graph.outputs[target_operation] children = self.cache.graph.outputs[target_operation]
if len(children) == 0: if len(children) == 0:
return return
for child_id in children: for child_id in children:
if not exclude or child_id not in exclude:
self._execute_inherit_cst(child_id, source, items, mapping) self._execute_inherit_cst(child_id, source, items, mapping)
def _execute_inherit_cst( def _execute_inherit_cst(
@ -873,10 +827,6 @@ class OssCache:
''' Remove substitution from cache. ''' ''' Remove substitution from cache. '''
self.substitutions[target.operation_id].remove(target) self.substitutions[target.operation_id].remove(target)
def remove_inheritance(self, target: Inheritance) -> None:
''' Remove inheritance from cache. '''
self.inheritance[target.operation_id].remove(target)
def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]: def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]:
operation = self.operation_by_id[sub.operation_id] operation = self.operation_by_id[sub.operation_id]
parents = self.graph.inputs[operation.pk] parents = self.graph.inputs[operation.pk]

View File

@ -1,6 +1,4 @@
''' Models: Change propagation facade - managing all changes in OSS. ''' ''' Models: Change propagation facade - managing all changes in OSS. '''
from typing import Optional
from apps.library.models import LibraryItem, LibraryItemType from apps.library.models import LibraryItem, LibraryItemType
from apps.rsform.models import Constituenta, RSForm from apps.rsform.models import Constituenta, RSForm
@ -16,53 +14,42 @@ class PropagationFacade:
''' Change propagation API. ''' ''' Change propagation API. '''
@staticmethod @staticmethod
def after_create_cst(source: RSForm, new_cst: list[Constituenta], exclude: Optional[list[int]] = None) -> None: def after_create_cst(source: RSForm, new_cst: list[Constituenta]) -> None:
''' Trigger cascade resolutions when new constituent is created. ''' ''' Trigger cascade resolutions when new constituent is created. '''
hosts = _get_oss_hosts(source.model) hosts = _get_oss_hosts(source.model)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchema(host).after_create_cst(source, new_cst) OperationSchema(host).after_create_cst(source, new_cst)
@staticmethod @staticmethod
def after_change_cst_type(source: RSForm, target: Constituenta, exclude: Optional[list[int]] = None) -> None: def after_change_cst_type(source: RSForm, target: Constituenta) -> None:
''' Trigger cascade resolutions when constituenta type is changed. ''' ''' Trigger cascade resolutions when constituenta type is changed. '''
hosts = _get_oss_hosts(source.model) hosts = _get_oss_hosts(source.model)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchema(host).after_change_cst_type(source, target) OperationSchema(host).after_change_cst_type(source, target)
@staticmethod @staticmethod
def after_update_cst( def after_update_cst(source: RSForm, target: Constituenta, data: dict, old_data: dict) -> None:
source: RSForm,
target: Constituenta,
data: dict,
old_data: dict,
exclude: Optional[list[int]] = None
) -> None:
''' Trigger cascade resolutions when constituenta data is changed. ''' ''' Trigger cascade resolutions when constituenta data is changed. '''
hosts = _get_oss_hosts(source.model) hosts = _get_oss_hosts(source.model)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchema(host).after_update_cst(source, target, data, old_data) OperationSchema(host).after_update_cst(source, target, data, old_data)
@staticmethod @staticmethod
def before_delete_cst(source: RSForm, target: list[Constituenta], exclude: Optional[list[int]] = None) -> None: def before_delete_cst(source: RSForm, target: list[Constituenta]) -> 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:
if exclude is None or host.pk not in exclude:
OperationSchema(host).before_delete_cst(source, target) OperationSchema(host).before_delete_cst(source, target)
@staticmethod @staticmethod
def before_substitute(source: RSForm, substitutions: CstSubstitution, exclude: Optional[list[int]] = None) -> None: def before_substitute(source: RSForm, substitutions: CstSubstitution) -> None:
''' Trigger cascade resolutions before constituents are substituted. ''' ''' Trigger cascade resolutions before constituents are substituted. '''
hosts = _get_oss_hosts(source.model) hosts = _get_oss_hosts(source.model)
for host in hosts: for host in hosts:
if exclude is None or host.pk not in exclude:
OperationSchema(host).before_substitute(source, substitutions) OperationSchema(host).before_substitute(source, substitutions)
@staticmethod @staticmethod
def before_delete_schema(item: LibraryItem, exclude: Optional[list[int]] = None) -> None: def before_delete_schema(item: LibraryItem) -> None:
''' Trigger cascade resolutions before schema is deleted. ''' ''' Trigger cascade resolutions before schema is deleted. '''
if item.item_type != LibraryItemType.RSFORM: if item.item_type != LibraryItemType.RSFORM:
return return
@ -71,4 +58,4 @@ class PropagationFacade:
return return
schema = RSForm(item) schema = RSForm(item)
PropagationFacade.before_delete_cst(schema, list(schema.constituents().order_by('order')), exclude) PropagationFacade.before_delete_cst(schema, list(schema.constituents().order_by('order')))

View File

@ -9,7 +9,6 @@ from .data_access import (
OperationSerializer, OperationSerializer,
OperationTargetSerializer, OperationTargetSerializer,
OperationUpdateSerializer, OperationUpdateSerializer,
RelocateConstituentsSerializer,
SetOperationInputSerializer SetOperationInputSerializer
) )
from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse

View File

@ -11,7 +11,7 @@ from apps.rsform.models import Constituenta
from apps.rsform.serializers import SubstitutionSerializerBase from apps.rsform.serializers import SubstitutionSerializerBase
from shared import messages as msg from shared import messages as msg
from ..models import Argument, Inheritance, Operation, OperationSchema, OperationType from ..models import Argument, Operation, OperationSchema, OperationType
from .basics import OperationPositionSerializer, SubstitutionExSerializer from .basics import OperationPositionSerializer, SubstitutionExSerializer
@ -118,6 +118,8 @@ class OperationUpdateSerializer(serializers.Serializer):
return attrs return attrs
class OperationTargetSerializer(serializers.Serializer): class OperationTargetSerializer(serializers.Serializer):
''' Serializer: Target single operation. ''' ''' Serializer: Target single operation. '''
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'))
@ -222,62 +224,3 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
).order_by('pk'): ).order_by('pk'):
result['substitutions'].append(substitution) result['substitutions'].append(substitution)
return result return result
class RelocateConstituentsSerializer(serializers.Serializer):
''' Serializer: Relocate constituents. '''
destination = PKField(
many=False,
queryset=LibraryItem.objects.all().only('id')
)
items = PKField(
many=True,
allow_empty=False,
queryset=Constituenta.objects.all()
)
def validate(self, attrs):
attrs['destination'] = attrs['destination'].id
attrs['source'] = attrs['items'][0].schema_id
# TODO: check permissions for editing source and destination
if attrs['source'] == attrs['destination']:
raise serializers.ValidationError({
'destination': msg.sourceEqualDestination()
})
for cst in attrs['items']:
if cst.schema_id != attrs['source']:
raise serializers.ValidationError({
f'{cst.pk}': msg.constituentaNotInRSform(attrs['items'][0].schema.title)
})
if Inheritance.objects.filter(child__in=attrs['items']).exists():
raise serializers.ValidationError({
'items': msg.RelocatingInherited()
})
oss = LibraryItem.objects \
.filter(operations__result_id=attrs['destination']) \
.filter(operations__result_id=attrs['source']).only('id')
if not oss.exists():
raise serializers.ValidationError({
'destination': msg.schemasNotConnected()
})
attrs['oss'] = oss[0].pk
if Argument.objects.filter(
operation__result_id=attrs['destination'],
argument__result_id=attrs['source']
).exists():
attrs['move_down'] = True
elif Argument.objects.filter(
operation__result_id=attrs['source'],
argument__result_id=attrs['destination']
).exists():
attrs['move_down'] = False
else:
raise serializers.ValidationError({
'destination': msg.schemasNotConnected()
})
return attrs

View File

@ -339,65 +339,3 @@ class TestChangeOperations(EndpointTester):
self.ks5.refresh_from_db() self.ks5.refresh_from_db()
self.assertNotEqual(self.operation4.result, None) self.assertNotEqual(self.operation4.result, None)
self.assertEqual(self.ks5.constituents().count(), 8) self.assertEqual(self.ks5.constituents().count(), 8)
@decl_endpoint('/api/oss/relocate-constituents', method='post')
def test_relocate_constituents_up(self):
ks1_old_count = self.ks1.constituents().count()
ks4_old_count = self.ks4.constituents().count()
operation6 = self.owned.create_operation(
alias='6',
operation_type=OperationType.SYNTHESIS
)
self.owned.set_arguments(operation6.pk, [self.operation1, self.operation2])
self.owned.execute_operation(operation6)
operation6.refresh_from_db()
ks6 = RSForm(operation6.result)
ks6A1 = ks6.insert_new('A1')
ks6_old_count = ks6.constituents().count()
data = {
'destination': self.ks1.model.pk,
'items': [ks6A1.pk]
}
self.executeOK(data=data)
ks6.refresh_from_db()
self.ks1.refresh_from_db()
self.ks4.refresh_from_db()
self.assertEqual(ks6.constituents().count(), ks6_old_count)
self.assertEqual(self.ks1.constituents().count(), ks1_old_count + 1)
self.assertEqual(self.ks4.constituents().count(), ks4_old_count + 1)
@decl_endpoint('/api/oss/relocate-constituents', method='post')
def test_relocate_constituents_down(self):
ks1_old_count = self.ks1.constituents().count()
ks4_old_count = self.ks4.constituents().count()
operation6 = self.owned.create_operation(
alias='6',
operation_type=OperationType.SYNTHESIS
)
self.owned.set_arguments(operation6.pk, [self.operation1, self.operation2])
self.owned.execute_operation(operation6)
operation6.refresh_from_db()
ks6 = RSForm(operation6.result)
ks6_old_count = ks6.constituents().count()
data = {
'destination': ks6.model.pk,
'items': [self.ks1X2.pk]
}
self.executeOK(data=data)
ks6.refresh_from_db()
self.ks1.refresh_from_db()
self.ks4.refresh_from_db()
self.ks4D2.refresh_from_db()
self.ks5D4.refresh_from_db()
self.assertEqual(ks6.constituents().count(), ks6_old_count)
self.assertEqual(self.ks1.constituents().count(), ks1_old_count - 1)
self.assertEqual(self.ks4.constituents().count(), ks4_old_count - 1)
self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 D1')
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 D2 D3')

View File

@ -1,7 +1,7 @@
''' Testing API: Operation Schema. ''' ''' Testing API: Operation Schema. '''
from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType from apps.library.models import AccessPolicy, Editor, LibraryItem, LibraryItemType
from apps.oss.models import Operation, OperationSchema, OperationType from apps.oss.models import Operation, OperationSchema, OperationType
from apps.rsform.models import Constituenta, RSForm from apps.rsform.models import RSForm
from shared.EndpointTester import EndpointTester, decl_endpoint from shared.EndpointTester import EndpointTester, decl_endpoint
@ -18,6 +18,7 @@ class TestOssViewset(EndpointTester):
self.private_id = self.private.model.pk self.private_id = self.private.model.pk
self.invalid_id = self.private.model.pk + 1337 self.invalid_id = self.private.model.pk + 1337
def populateData(self): def populateData(self):
self.ks1 = RSForm.create( self.ks1 = RSForm.create(
alias='KS1', alias='KS1',
@ -134,6 +135,7 @@ class TestOssViewset(EndpointTester):
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)
@decl_endpoint('/api/oss/{item}/create-operation', method='post') @decl_endpoint('/api/oss/{item}/create-operation', method='post')
def test_create_operation(self): def test_create_operation(self):
self.populateData() self.populateData()
@ -497,87 +499,3 @@ class TestOssViewset(EndpointTester):
self.assertEqual(len(items), 1) self.assertEqual(len(items), 1)
self.assertEqual(items[0].alias, 'X1') self.assertEqual(items[0].alias, 'X1')
self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved) self.assertEqual(items[0].term_resolved, self.ks2X1.term_resolved)
@decl_endpoint('/api/oss/get-predecessor', method='post')
def test_get_predecessor(self):
self.populateData()
self.ks1X2 = self.ks1.insert_new('X2')
self.owned.execute_operation(self.operation3)
self.operation3.refresh_from_db()
self.ks3 = RSForm(self.operation3.result)
self.ks3X2 = Constituenta.objects.get(as_child__parent_id=self.ks1X2.pk)
self.executeBadData(data={'target': self.invalid_id})
response = self.executeOK(data={'target': self.ks1X1.pk})
self.assertEqual(response.data['id'], self.ks1X1.pk)
self.assertEqual(response.data['schema'], self.ks1.model.pk)
response = self.executeOK(data={'target': self.ks3X2.pk})
self.assertEqual(response.data['id'], self.ks1X2.pk)
self.assertEqual(response.data['schema'], self.ks1.model.pk)
@decl_endpoint('/api/oss/relocate-constituents', method='post')
def test_relocate_constituents(self):
self.populateData()
self.ks1X2 = self.ks1.insert_new('X2', convention='test')
self.owned.execute_operation(self.operation3)
self.operation3.refresh_from_db()
self.ks3 = RSForm(self.operation3.result)
self.ks3X2 = Constituenta.objects.get(as_child__parent_id=self.ks1X2.pk)
self.ks3X10 = self.ks3.insert_new('X10', convention='test2')
# invalid destination
data = {
'destination': self.invalid_id,
'items': []
}
self.executeBadData(data=data)
# empty items
data = {
'destination': self.ks1.model.pk,
'items': []
}
self.executeBadData(data=data)
# source == destination
data = {
'destination': self.ks1.model.pk,
'items': [self.ks1X1.pk]
}
self.executeBadData(data=data)
# moving inherited
data = {
'destination': self.ks1.model.pk,
'items': [self.ks3X2.pk]
}
self.executeBadData(data=data)
# source and destination are not connected
data = {
'destination': self.ks2.model.pk,
'items': [self.ks1X1.pk]
}
self.executeBadData(data=data)
data = {
'destination': self.ks3.model.pk,
'items': [self.ks1X2.pk]
}
self.ks3X2.refresh_from_db()
self.assertEqual(self.ks3X2.convention, 'test')
self.executeOK(data=data)
self.assertFalse(Constituenta.objects.filter(as_child__parent_id=self.ks1X2.pk).exists())
data = {
'destination': self.ks1.model.pk,
'items': [self.ks3X10.pk]
}
self.executeOK(data=data)
self.assertTrue(Constituenta.objects.filter(as_parent__child_id=self.ks3X10.pk).exists())
self.ks1X3 = Constituenta.objects.get(as_parent__child_id=self.ks3X10.pk)
self.assertEqual(self.ks1X3.convention, 'test2')

View File

@ -14,7 +14,7 @@ from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer from apps.library.serializers import LibraryItemSerializer
from apps.rsform.models import Constituenta, RSForm from apps.rsform.models import Constituenta
from apps.rsform.serializers import CstTargetSerializer from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg from shared import messages as msg
from shared import permissions from shared import permissions
@ -42,8 +42,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'create_input', 'create_input',
'set_input', 'set_input',
'update_operation', 'update_operation',
'execute_operation', 'execute_operation'
'relocate_constituents'
]: ]:
permission_list = [permissions.ItemEditor] permission_list = [permissions.ItemEditor]
elif self.action in ['details']: elif self.action in ['details']:
@ -386,36 +385,3 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
'schema': cst.schema_id 'schema': cst.schema_id
} }
) )
@extend_schema(
summary='relocate constituents from one schema to another',
tags=['OSS'],
request=s.RelocateConstituentsSerializer(),
responses={
c.HTTP_200_OK: s.OperationSchemaSerializer,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=False, methods=['post'], url_path='relocate-constituents')
def relocate_constituents(self, request: Request) -> Response:
''' Relocate constituents from one schema to another. '''
serializer = s.RelocateConstituentsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
oss = m.OperationSchema(LibraryItem.objects.get(pk=data['oss']))
source = RSForm(LibraryItem.objects.get(pk=data['source']))
destination = RSForm(LibraryItem.objects.get(pk=data['destination']))
with transaction.atomic():
if data['move_down']:
oss.relocate_down(source, destination, data['items'])
m.PropagationFacade.before_delete_cst(source, data['items'])
source.delete_cst(data['items'])
else:
new_items = oss.relocate_up(source, destination, data['items'])
m.PropagationFacade.after_create_cst(destination, new_items, exclude=[oss.model.pk])
return Response(status=c.HTTP_200_OK)

View File

@ -64,7 +64,6 @@ class RSForm:
def refresh_from_db(self) -> None: def refresh_from_db(self) -> None:
''' Model wrapper. ''' ''' Model wrapper. '''
self.model.refresh_from_db() self.model.refresh_from_db()
self.cache = RSFormCache(self)
def constituents(self) -> QuerySet[Constituenta]: def constituents(self) -> QuerySet[Constituenta]:
''' Get QuerySet containing all constituents of current RSForm. ''' ''' Get QuerySet containing all constituents of current RSForm. '''

View File

@ -38,18 +38,6 @@ def operationResultFromAnotherOSS():
return 'Схема является результатом другой ОСС' return 'Схема является результатом другой ОСС'
def schemasNotConnected():
return 'Концептуальные схемы не связаны через ОСС'
def sourceEqualDestination():
return 'Схема-источник и схема-получатель не могут быть одинаковыми'
def RelocatingInherited():
return 'Невозможно переместить наследуемые конституенты'
def operationInputAlreadyConnected(): def operationInputAlreadyConnected():
return 'Схема уже подключена к другой операции' return 'Схема уже подключена к другой операции'

View File

@ -2,7 +2,8 @@ import globals from 'globals';
import typescriptPlugin from 'typescript-eslint'; import typescriptPlugin from 'typescript-eslint';
import typescriptParser from '@typescript-eslint/parser'; import typescriptParser from '@typescript-eslint/parser';
import reactPlugin from 'eslint-plugin-react'; import reactPlugin from 'eslint-plugin-react';
import reactHooksPlugin from 'eslint-plugin-react-hooks'; // import { fixupPluginRules } from '@eslint/compat';
// import reactHooksPlugin from 'eslint-plugin-react-hooks';
import simpleImportSort from 'eslint-plugin-simple-import-sort'; import simpleImportSort from 'eslint-plugin-simple-import-sort';
@ -34,7 +35,7 @@ export default [
{ {
plugins: { plugins: {
'react': reactPlugin, 'react': reactPlugin,
'react-hooks': reactHooksPlugin, // 'react-hooks': fixupPluginRules(reactHooksPlugin),
'simple-import-sort': simpleImportSort 'simple-import-sort': simpleImportSort
}, },
settings: { react: { version: 'detect' } }, settings: { react: { version: 'detect' } },
@ -55,9 +56,7 @@ export default [
'react-refresh/only-export-components': ['off', { allowConstantExport: true }], 'react-refresh/only-export-components': ['off', { allowConstantExport: true }],
'simple-import-sort/imports': 'warn', 'simple-import-sort/imports': 'warn',
'simple-import-sort/exports': 'error', 'simple-import-sort/exports': 'error'
...reactHooksPlugin.configs.recommended.rules
} }
}, },
{ {

File diff suppressed because it is too large Load Diff

View File

@ -14,22 +14,21 @@
"dependencies": { "dependencies": {
"@lezer/lr": "^1.4.2", "@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@uiw/codemirror-themes": "^4.23.6", "@uiw/codemirror-themes": "^4.23.5",
"@uiw/react-codemirror": "^4.23.6", "@uiw/react-codemirror": "^4.23.5",
"axios": "^1.7.7", "axios": "^1.7.7",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"eslint-plugin-react-hooks": "^5.0.0", "framer-motion": "^11.5.6",
"framer-motion": "^11.11.10",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-error-boundary": "^4.1.2", "react-error-boundary": "^4.1.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-intl": "^6.8.4", "react-intl": "^6.8.0",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.27.0", "react-router-dom": "^6.27.0",
"react-select": "^5.8.2", "react-select": "^5.8.1",
"react-tabs": "^6.0.2", "react-tabs": "^6.0.2",
"react-toastify": "^10.0.6", "react-toastify": "^10.0.6",
"react-tooltip": "^5.28.0", "react-tooltip": "^5.28.0",
@ -40,16 +39,16 @@
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.13",
"@types/node": "^22.8.1", "@types/node": "^22.7.6",
"@types/react": "^18.3.12", "@types/react": "^18.3.11",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.3", "@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.13.0", "eslint": "^9.12.0",
"eslint-plugin-react": "^7.37.2", "eslint-plugin-react": "^7.37.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.11.0", "globals": "^15.11.0",
"jest": "^29.7.0", "jest": "^29.7.0",
@ -57,8 +56,8 @@
"tailwindcss": "^3.4.14", "tailwindcss": "^3.4.14",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.6.3", "typescript": "^5.6.3",
"typescript-eslint": "^8.11.0", "typescript-eslint": "^8.9.0",
"vite": "^5.4.10" "vite": "^5.4.9"
}, },
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",

View File

@ -3,7 +3,6 @@
*/ */
import { import {
ICstRelocateData,
IInputCreatedResponse, IInputCreatedResponse,
IOperationCreateData, IOperationCreateData,
IOperationCreatedResponse, IOperationCreatedResponse,
@ -77,13 +76,6 @@ export function postExecuteOperation(oss: string, request: FrontExchange<ITarget
}); });
} }
export function postRelocateConstituents(request: FrontPush<ICstRelocateData>) {
AxiosPost({
endpoint: `/api/oss/relocate-constituents`,
request: request
});
}
export function postFindPredecessor(request: FrontExchange<ITargetCst, IConstituentaReference>) { export function postFindPredecessor(request: FrontExchange<ITargetCst, IConstituentaReference>) {
AxiosPost({ AxiosPost({
endpoint: `/api/oss/get-predecessor`, endpoint: `/api/oss/get-predecessor`,

View File

@ -61,7 +61,7 @@ function PickMultiConstituenta({
newGraph.foldNode(item.id); newGraph.foldNode(item.id);
}); });
return newGraph; return newGraph;
}, [data, schema.graph, schema.items]); }, [schema.graph, data]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (filtered.length === 0) { if (filtered.length === 0) {

View File

@ -63,7 +63,7 @@ function PickSchema({
); );
} }
setFiltered(newFiltered); setFiltered(newFiltered);
}, [filterText, filterLocation, baseFiltered]); }, [filterText, filterLocation]);
const columns = useMemo( const columns = useMemo(
() => [ () => [

View File

@ -146,7 +146,7 @@ function DataTable<TData extends RowData>({
colSizes[`--col-${header.column.id}-size`] = header.column.getSize(); colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
} }
return colSizes; return colSizes;
}, [tableImpl]); }, [tableImpl.getState().columnSizingInfo, tableImpl.getState().columnSizing]);
return ( return (
<div tabIndex={-1} id={id} className={className} style={{ minHeight: fixedSize, maxHeight: fixedSize, ...style }}> <div tabIndex={-1} id={id} className={className} style={{ minHeight: fixedSize, maxHeight: fixedSize, ...style }}>

View File

@ -18,7 +18,7 @@ function AnimateFade({ style, noFadeIn, noFadeOut, children, hideContent, ...res
animate={hideContent ? 'hidden' : 'active'} animate={hideContent ? 'hidden' : 'active'}
variants={animateFade.variants} variants={animateFade.variants}
exit={{ ...(!noFadeOut ? animateFade.exit : {}) }} exit={{ ...(!noFadeOut ? animateFade.exit : {}) }}
style={{ display: hideContent ? 'none' : '', willChange: 'auto', ...style }} style={{ display: hideContent ? 'none' : '', ...style }}
{...restProps} {...restProps}
> >
{children} {children}

View File

@ -238,7 +238,7 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
}); });
} }
}, },
[reloadItems] [reloadItems, user]
); );
const destroyItem = useCallback( const destroyItem = useCallback(
@ -254,7 +254,7 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
}) })
}); });
}, },
[reloadItems] [reloadItems, user]
); );
const cloneItem = useCallback( const cloneItem = useCallback(
@ -291,7 +291,7 @@ export const LibraryState = ({ children }: React.PropsWithChildren) => {
}) })
}); });
}, },
[reloadItems] [reloadItems, user]
); );
return ( return (

View File

@ -17,14 +17,12 @@ import {
patchUpdateOperation, patchUpdateOperation,
patchUpdatePositions, patchUpdatePositions,
postCreateOperation, postCreateOperation,
postExecuteOperation, postExecuteOperation
postRelocateConstituents
} from '@/backend/oss'; } from '@/backend/oss';
import { type ErrorData } from '@/components/info/InfoError'; import { type ErrorData } from '@/components/info/InfoError';
import { AccessPolicy, ILibraryItem } from '@/models/library'; import { AccessPolicy, ILibraryItem } from '@/models/library';
import { ILibraryUpdateData } from '@/models/library'; import { ILibraryUpdateData } from '@/models/library';
import { import {
ICstRelocateData,
IOperationCreateData, IOperationCreateData,
IOperationData, IOperationData,
IOperationDeleteData, IOperationDeleteData,
@ -67,7 +65,6 @@ interface IOssContext {
setInput: (data: IOperationSetInputData, callback?: () => void) => void; setInput: (data: IOperationSetInputData, callback?: () => void) => void;
updateOperation: (data: IOperationUpdateData, callback?: () => void) => void; updateOperation: (data: IOperationUpdateData, callback?: () => void) => void;
executeOperation: (data: ITargetOperation, callback?: () => void) => void; executeOperation: (data: ITargetOperation, callback?: () => void) => void;
relocateConstituents: (data: ICstRelocateData, callback?: () => void) => void;
} }
const OssContext = createContext<IOssContext | null>(null); const OssContext = createContext<IOssContext | null>(null);
@ -97,7 +94,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
useEffect(() => { useEffect(() => {
oss.setID(itemID); oss.setID(itemID);
}, [itemID, oss]); }, [itemID, oss.setID]);
const update = useCallback( const update = useCallback(
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => { (data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
@ -118,7 +115,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library, oss] [itemID, model, library.localUpdateItem, oss.setData]
); );
const setOwner = useCallback( const setOwner = useCallback(
@ -142,7 +139,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library] [itemID, model, library.reloadItems]
); );
const setAccessPolicy = useCallback( const setAccessPolicy = useCallback(
@ -166,7 +163,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library] [itemID, model, library.reloadItems]
); );
const setLocation = useCallback( const setLocation = useCallback(
@ -190,7 +187,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library] [itemID, model, library.reloadItems]
); );
const setEditors = useCallback( const setEditors = useCallback(
@ -214,7 +211,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library] [itemID, model, library.reloadItems]
); );
const savePositions = useCallback( const savePositions = useCallback(
@ -231,7 +228,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, library] [itemID, library.localUpdateTimestamp]
); );
const createOperation = useCallback( const createOperation = useCallback(
@ -249,7 +246,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, library, oss] [itemID, library.localUpdateTimestamp, oss.setData]
); );
const deleteOperation = useCallback( const deleteOperation = useCallback(
@ -268,7 +265,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, library, oss] [itemID, library.reloadItems, oss.setData]
); );
const createInput = useCallback( const createInput = useCallback(
@ -287,7 +284,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, library, oss] [itemID, library.reloadItems, oss.setData]
); );
const setInput = useCallback( const setInput = useCallback(
@ -309,7 +306,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library, oss] [itemID, model, library.reloadItems, oss.setData]
); );
const updateOperation = useCallback( const updateOperation = useCallback(
@ -331,7 +328,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library, oss] [itemID, model, library.reloadItems, oss.setData]
); );
const executeOperation = useCallback( const executeOperation = useCallback(
@ -353,29 +350,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
} }
}); });
}, },
[itemID, model, library, oss] [itemID, model, library.reloadItems, oss.setData]
);
const relocateConstituents = useCallback(
(data: ICstRelocateData, callback?: () => void) => {
if (!model) {
return;
}
setProcessingError(undefined);
postRelocateConstituents({
data: data,
showError: true,
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: () => {
oss.reload();
library.reloadItems(() => {
if (callback) callback();
});
}
});
},
[model, library, oss]
); );
return ( return (
@ -401,8 +376,7 @@ export const OssState = ({ itemID, children }: React.PropsWithChildren<OssStateP
createInput, createInput,
setInput, setInput,
updateOperation, updateOperation,
executeOperation, executeOperation
relocateConstituents
}} }}
> >
{children} {children}

View File

@ -151,7 +151,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, setSchema, schema, library, oss] [itemID, setSchema, schema, library.localUpdateItem, oss.invalidateItem]
); );
const upload = useCallback( const upload = useCallback(
@ -172,7 +172,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, setSchema, schema, library] [itemID, setSchema, schema, library.localUpdateItem]
); );
const setOwner = useCallback( const setOwner = useCallback(
@ -195,7 +195,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, schema, library] [itemID, schema, library.localUpdateItem]
); );
const setAccessPolicy = useCallback( const setAccessPolicy = useCallback(
@ -218,7 +218,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, schema, library] [itemID, schema, library.localUpdateItem]
); );
const setLocation = useCallback( const setLocation = useCallback(
@ -240,7 +240,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, schema, library] [itemID, schema, library.reloadItems]
); );
const setEditors = useCallback( const setEditors = useCallback(
@ -283,7 +283,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, schema, user, setSchema, library, oss] [itemID, schema, user, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
); );
const restoreOrder = useCallback( const restoreOrder = useCallback(
@ -303,7 +303,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, schema, user, setSchema, library] [itemID, schema, user, setSchema, library.localUpdateTimestamp]
); );
const produceStructure = useCallback( const produceStructure = useCallback(
@ -322,7 +322,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[setSchema, itemID, library, oss] [setSchema, itemID, library.localUpdateTimestamp, oss.invalidateItem]
); );
const download = useCallback( const download = useCallback(
@ -354,7 +354,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, setSchema, library, oss] [itemID, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
); );
const cstDelete = useCallback( const cstDelete = useCallback(
@ -373,7 +373,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, setSchema, library, oss] [itemID, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
); );
const cstUpdate = useCallback( const cstUpdate = useCallback(
@ -392,7 +392,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
}) })
}); });
}, },
[itemID, reload, library, oss] [itemID, reload, library.localUpdateTimestamp, oss.invalidateItem]
); );
const cstRename = useCallback( const cstRename = useCallback(
@ -411,7 +411,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[setSchema, itemID, library, oss] [setSchema, itemID, library.localUpdateTimestamp, oss.invalidateItem]
); );
const cstSubstitute = useCallback( const cstSubstitute = useCallback(
@ -430,7 +430,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[setSchema, itemID, library, oss] [setSchema, itemID, library.localUpdateTimestamp, oss.invalidateItem]
); );
const cstMoveTo = useCallback( const cstMoveTo = useCallback(
@ -448,7 +448,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, setSchema, library] [itemID, setSchema, library.localUpdateTimestamp]
); );
const versionCreate = useCallback( const versionCreate = useCallback(
@ -466,7 +466,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[itemID, setSchema, library] [itemID, setSchema, library.localUpdateTimestamp]
); );
const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => { const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => {
@ -537,7 +537,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[setSchema, library] [setSchema, library.localUpdateItem]
); );
const inlineSynthesis = useCallback( const inlineSynthesis = useCallback(
@ -556,7 +556,7 @@ export const RSFormState = ({ itemID, versionID, children }: React.PropsWithChil
} }
}); });
}, },
[setSchema, library, oss] [itemID, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
); );
return ( return (

View File

@ -44,7 +44,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
const handleTypeChange = useCallback( const handleTypeChange = useCallback(
(target: CstType) => partialUpdate({ cst_type: target, alias: generateAlias(target, schema) }), (target: CstType) => partialUpdate({ cst_type: target, alias: generateAlias(target, schema) }),
[partialUpdate, schema] [partialUpdate, schema, generateAlias]
); );
return ( return (

View File

@ -62,7 +62,6 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
const cache = useRSFormCache(); const cache = useRSFormCache();
const schemas = useMemo( const schemas = useMemo(
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined), () => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
// eslint-disable-next-line react-hooks/exhaustive-deps
[schemasIDs, cache.getSchema] [schemasIDs, cache.getSchema]
); );
@ -91,7 +90,6 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
useLayoutEffect(() => { useLayoutEffect(() => {
cache.preload(schemasIDs); cache.preload(schemasIDs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [schemasIDs]); }, [schemasIDs]);
useLayoutEffect(() => { useLayoutEffect(() => {
@ -111,7 +109,6 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
return true; return true;
}) })
); );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [schemasIDs, schemas, cache.loading]); }, [schemasIDs, schemas, cache.loading]);
useLayoutEffect(() => { useLayoutEffect(() => {

View File

@ -61,7 +61,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
<TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} /> <TabSchema selected={donorID} setSelected={setDonorID} receiver={receiver} />
</TabPanel> </TabPanel>
), ),
[donorID, receiver] [donorID]
); );
const itemsPanel = useMemo( const itemsPanel = useMemo(
() => ( () => (

View File

@ -30,7 +30,7 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc
...node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null) ...node.outputs.map(id => oss.operationByID.get(id)!.result).filter(id => id !== null)
]; ];
return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined); return ids.map(id => library.items.find(item => item.id === id)).filter(item => item !== undefined);
}, [oss, library.items, target.id]); }, [oss, library.items]);
const [destination, setDestination] = useState<ILibraryItem | undefined>(undefined); const [destination, setDestination] = useState<ILibraryItem | undefined>(undefined);
const [selected, setSelected] = useState<ConstituentaID[]>([]); const [selected, setSelected] = useState<ConstituentaID[]>([]);
@ -42,7 +42,7 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc
} }
const destinationOperation = oss.items.find(item => item.result === destination.id); const destinationOperation = oss.items.find(item => item.result === destination.id);
return getRelocateCandidates(target.id, destinationOperation!.id, source.schema, oss); return getRelocateCandidates(target.id, destinationOperation!.id, source.schema, oss);
}, [destination, target.id, source.schema, oss]); }, [destination, source.schema?.items]);
const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]); const isValid = useMemo(() => !!destination && selected.length > 0, [destination, selected]);
@ -55,15 +55,12 @@ function DlgRelocateConstituents({ oss, hideWindow, target, onSubmit }: DlgReloc
}, []); }, []);
const handleSubmit = useCallback(() => { const handleSubmit = useCallback(() => {
if (!destination) {
return;
}
const data: ICstRelocateData = { const data: ICstRelocateData = {
destination: destination.id, destination: target.result ?? 0,
items: selected items: []
}; };
onSubmit(data); onSubmit(data);
}, [destination, onSubmit, selected]); }, [target, onSubmit]);
return ( return (
<Modal <Modal

View File

@ -15,17 +15,14 @@ function useOssDetails({ target, items }: { target?: string; items: ILibraryItem
const [loading, setLoading] = useState(target != undefined); const [loading, setLoading] = useState(target != undefined);
const [error, setError] = useState<ErrorData>(undefined); const [error, setError] = useState<ErrorData>(undefined);
const setSchema = useCallback( function setSchema(data?: IOperationSchemaData) {
(data?: IOperationSchemaData) => {
if (!data) { if (!data) {
setInner(undefined); setInner(undefined);
return; return;
} }
const newSchema = new OssLoader(data, items).produceOSS(); const newSchema = new OssLoader(data, items).produceOSS();
setInner(newSchema); setInner(newSchema);
}, }
[items]
);
const reload = useCallback( const reload = useCallback(
(setCustomLoading?: typeof setLoading, callback?: () => void) => { (setCustomLoading?: typeof setLoading, callback?: () => void) => {
@ -46,7 +43,7 @@ function useOssDetails({ target, items }: { target?: string; items: ILibraryItem
} }
}); });
}, },
[target, setSchema] [target]
); );
useEffect(() => { useEffect(() => {

View File

@ -76,7 +76,6 @@ function useRSFormCache() {
} }
}) })
); );
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pending]); }, [pending]);
return { preload, getSchema, getConstituenta, getSchemaByCst, loading, error, setError }; return { preload, getSchema, getConstituenta, getSchemaByCst, loading, error, setError };

View File

@ -1,20 +1,19 @@
'use client'; 'use client';
import { useCallback, useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
function useWindowSize() { function useWindowSize() {
const isClient = typeof window === 'object'; const isClient = typeof window === 'object';
const getSize = useCallback( function getSize() {
() => ({ return {
width: isClient ? window.innerWidth : undefined, width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined, height: isClient ? window.innerHeight : undefined,
isSmall: isClient && window.innerWidth < PARAMETER.smallScreen isSmall: isClient && window.innerWidth < PARAMETER.smallScreen
}), };
[isClient] }
);
const [windowSize, setWindowSize] = useState(getSize); const [windowSize, setWindowSize] = useState(getSize);
@ -27,7 +26,7 @@ function useWindowSize() {
} }
window.addEventListener('resize', handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize);
}, [isClient, getSize]); }, []);
return windowSize; return windowSize;
} }

View File

@ -90,7 +90,7 @@ function LibraryPage() {
const toggleVisible = useCallback(() => setIsVisible(prev => toggleTristateFlag(prev)), [setIsVisible]); const toggleVisible = useCallback(() => setIsVisible(prev => toggleTristateFlag(prev)), [setIsVisible]);
const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]); const toggleOwned = useCallback(() => setIsOwned(prev => toggleTristateFlag(prev)), [setIsOwned]);
const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]); const toggleEditor = useCallback(() => setIsEditor(prev => toggleTristateFlag(prev)), [setIsEditor]);
const toggleFolderMode = useCallback(() => options.setFolderMode(prev => !prev), [options]); const toggleFolderMode = useCallback(() => options.setFolderMode(prev => !prev), [options.setFolderMode]);
const toggleSubfolders = useCallback(() => setSubfolders(prev => !prev), [setSubfolders]); const toggleSubfolders = useCallback(() => setSubfolders(prev => !prev), [setSubfolders]);
const resetFilter = useCallback(() => { const resetFilter = useCallback(() => {
@ -102,7 +102,7 @@ function LibraryPage() {
setIsEditor(undefined); setIsEditor(undefined);
setFilterUser(undefined); setFilterUser(undefined);
options.setLocation(''); options.setLocation('');
}, [setHead, setIsVisible, setIsOwned, setIsEditor, setFilterUser, options]); }, [setHead, setIsVisible, setIsOwned, setIsEditor, setFilterUser, options.setLocation]);
const promptRenameLocation = useCallback(() => { const promptRenameLocation = useCallback(() => {
setShowRenameLocation(true); setShowRenameLocation(true);
@ -119,7 +119,7 @@ function LibraryPage() {
toast.success(information.locationRenamed); toast.success(information.locationRenamed);
}); });
}, },
[options, library] [location, library]
); );
const handleDownloadCSV = useCallback(() => { const handleDownloadCSV = useCallback(() => {
@ -159,15 +159,7 @@ function LibraryPage() {
onRenameLocation={promptRenameLocation} onRenameLocation={promptRenameLocation}
/> />
), ),
[ [options.location, library.folders, options.setLocation, toggleFolderMode, subfolders]
options.location,
library.folders,
options.setLocation,
toggleFolderMode,
promptRenameLocation,
toggleSubfolders,
subfolders
]
); );
return ( return (

View File

@ -19,13 +19,6 @@ function ManualsPage() {
const { mainHeight } = useConceptOptions(); const { mainHeight } = useConceptOptions();
const onSelectTopic = useCallback(
(newTopic: HelpTopic) => {
router.push(urls.help_topic(newTopic));
},
[router]
);
if (!Object.values(HelpTopic).includes(activeTopic)) { if (!Object.values(HelpTopic).includes(activeTopic)) {
setTimeout(() => { setTimeout(() => {
router.push(urls.page404); router.push(urls.page404);
@ -34,6 +27,13 @@ function ManualsPage() {
return null; return null;
} }
const onSelectTopic = useCallback(
(newTopic: HelpTopic) => {
router.push(urls.help_topic(newTopic));
},
[router]
);
return ( return (
<div className='flex mx-auto max-w-[80rem]' role='manuals' style={{ minHeight: mainHeight }}> <div className='flex mx-auto max-w-[80rem]' role='manuals' style={{ minHeight: mainHeight }}>
<TopicsList activeTopic={activeTopic} onChangeTopic={topic => onSelectTopic(topic)} /> <TopicsList activeTopic={activeTopic} onChangeTopic={topic => onSelectTopic(topic)} />

View File

@ -145,6 +145,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
[controller, getPositions, flow] [controller, getPositions, flow]
); );
const handleDeleteSelected = useCallback(() => {
if (controller.selected.length !== 1) {
return;
}
handleDeleteOperation(controller.selected[0]);
}, [controller, getPositions]);
const handleDeleteOperation = useCallback( const handleDeleteOperation = useCallback(
(target: OperationID) => { (target: OperationID) => {
if (!controller.canDelete(target)) { if (!controller.canDelete(target)) {
@ -155,13 +162,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
[controller, getPositions] [controller, getPositions]
); );
const handleDeleteSelected = useCallback(() => {
if (controller.selected.length !== 1) {
return;
}
handleDeleteOperation(controller.selected[0]);
}, [controller, handleDeleteOperation]);
const handleCreateInput = useCallback( const handleCreateInput = useCallback(
(target: OperationID) => { (target: OperationID) => {
controller.createInput(target, getPositions()); controller.createInput(target, getPositions());
@ -199,9 +199,9 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
const handleRelocateConstituents = useCallback( const handleRelocateConstituents = useCallback(
(target: OperationID) => { (target: OperationID) => {
controller.promptRelocateConstituents(target, getPositions()); controller.promptRelocateConstituents(target);
}, },
[controller, getPositions] [controller]
); );
const handleFitView = useCallback(() => { const handleFitView = useCallback(() => {
@ -282,7 +282,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
handleEditOperation(Number(node.id)); handleEditOperation(Number(node.id));
} }
}, },
[handleEditOperation, controller] [handleEditOperation, controller.openOperationSchema]
); );
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) { function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {

View File

@ -76,7 +76,7 @@ export interface IOssEditContext extends ILibraryItemEditor {
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void; promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
executeOperation: (target: OperationID, positions: IOperationPosition[]) => void; executeOperation: (target: OperationID, positions: IOperationPosition[]) => void;
promptRelocateConstituents: (target: OperationID, positions: IOperationPosition[]) => void; promptRelocateConstituents: (target: OperationID) => void;
} }
const OssEditContext = createContext<IOssEditContext | null>(null); const OssEditContext = createContext<IOssEditContext | null>(null);
@ -288,11 +288,14 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
[model] [model]
); );
const promptDeleteOperation = useCallback((target: OperationID, positions: IOperationPosition[]) => { const promptDeleteOperation = useCallback(
(target: OperationID, positions: IOperationPosition[]) => {
setPositions(positions); setPositions(positions);
setTargetOperationID(target); setTargetOperationID(target);
setShowDeleteOperation(true); setShowDeleteOperation(true);
}, []); },
[model]
);
const deleteOperation = useCallback( const deleteOperation = useCallback(
(keepConstituents: boolean, deleteSchema: boolean) => { (keepConstituents: boolean, deleteSchema: boolean) => {
@ -360,19 +363,21 @@ export const OssEditState = ({ selected, setSelected, children }: React.PropsWit
[model] [model]
); );
const promptRelocateConstituents = useCallback((target: OperationID, positions: IOperationPosition[]) => { const promptRelocateConstituents = useCallback(
setPositions(positions); (target: OperationID) => {
setTargetOperationID(target); setTargetOperationID(target);
setShowRelocateConstituents(true); setShowRelocateConstituents(true);
}, []); },
[model]
);
const handleRelocateConstituents = useCallback( const handleRelocateConstituents = useCallback(
(data: ICstRelocateData) => { (data: ICstRelocateData) => {
model.savePositions({ positions: positions }, () => // TODO: implement backed call
model.relocateConstituents(data, () => toast.success(information.changesSaved)) console.log(data);
); toast.success('В разработке');
}, },
[model, positions] [model]
); );
return ( return (

View File

@ -169,11 +169,7 @@ function FormConstituenta({
) : null} ) : null}
{state ? ( {state ? (
<AnimatePresence> <AnimatePresence>
<AnimateFade <AnimateFade key='cst_expression_fade' hideContent={!state.definition_formal && isElementary}>
key='cst_expression_fade'
hideContent={!state.definition_formal && isElementary}
style={{ willChange: 'auto' }}
>
<EditorRSExpression <EditorRSExpression
id='cst_expression' id='cst_expression'
label={ label={

View File

@ -95,7 +95,8 @@ function EditorRSExpression({
}); });
} }
const onShowError = useCallback((error: IRSErrorDescription, prefixLen: number) => { const onShowError = useCallback(
(error: IRSErrorDescription, prefixLen: number) => {
if (!rsInput.current) { if (!rsInput.current) {
return; return;
} }
@ -108,7 +109,9 @@ function EditorRSExpression({
} }
}); });
rsInput.current?.view?.focus(); rsInput.current?.view?.focus();
}, []); },
[activeCst]
);
const handleEdit = useCallback((id: TokenID, key?: string) => { const handleEdit = useCallback((id: TokenID, key?: string) => {
if (!rsInput.current?.editor || !rsInput.current.state || !rsInput.current.view) { if (!rsInput.current?.editor || !rsInput.current.state || !rsInput.current.view) {

View File

@ -38,7 +38,7 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
const { accessLevel } = useAccessMode(); const { accessLevel } = useAccessMode();
const intl = useIntl(); const intl = useIntl();
const router = useConceptNavigation(); const router = useConceptNavigation();
const { setLocation, setFolderMode } = useConceptOptions(); const options = useConceptOptions();
const ownerSelector = useDropdown(); const ownerSelector = useDropdown();
const onSelectUser = useCallback( const onSelectUser = useCallback(
@ -60,11 +60,11 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
if (!item) { if (!item) {
return; return;
} }
setLocation(item.location); options.setLocation(item.location);
setFolderMode(true); options.setFolderMode(true);
router.push(urls.library, event.ctrlKey || event.metaKey); router.push(urls.library, event.ctrlKey || event.metaKey);
}, },
[setLocation, setFolderMode, item, router] [options.setLocation, options.setFolderMode, item, router]
); );
if (!item) { if (!item) {

View File

@ -341,7 +341,7 @@ export const RSEditState = ({
toast.success(information.newVersion(data.version)); toast.success(information.newVersion(data.version));
}); });
}, },
[model] [model, viewVersion]
); );
const handleDeleteVersion = useCallback( const handleDeleteVersion = useCallback(

View File

@ -8,16 +8,6 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
*,
*::after,
*::before {
box-sizing: border-box;
/* Uncomment to debug layering and overflow */
/* background: hsla(135, 50%, 50%, 0.05); */
/* outline: 2px solid hotpink; */
}
html { html {
-webkit-text-size-adjust: none; -webkit-text-size-adjust: none;
-moz-text-size-adjust: none; -moz-text-size-adjust: none;
@ -52,8 +42,6 @@ body {
} }
:root { :root {
interpolate-size: allow-keywords;
font-size: var(--font-size-base); font-size: var(--font-size-base);
line-height: var(--line-height); line-height: var(--line-height);
font-family: var(--font-main); font-family: var(--font-main);