Compare commits
8 Commits
fde844f5c2
...
fbd84ece4d
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fbd84ece4d | ||
![]() |
f59206c182 | ||
![]() |
4716fde331 | ||
![]() |
e17056ea10 | ||
![]() |
63991f713c | ||
![]() |
4d3f91dd5e | ||
![]() |
e484ad2663 | ||
![]() |
6d3d07dbc0 |
|
@ -123,3 +123,11 @@ class LibraryItem(Model):
|
|||
def versions(self) -> QuerySet[Version]:
|
||||
''' Get all Versions of this item. '''
|
||||
return Version.objects.filter(item=self.pk).order_by('-time_create')
|
||||
|
||||
def is_synced(self, target: 'LibraryItem') -> bool:
|
||||
''' Check if item is synced with target. '''
|
||||
if self.owner != target.owner:
|
||||
return False
|
||||
if self.location != target.location:
|
||||
return False
|
||||
return True
|
||||
|
|
|
@ -217,13 +217,10 @@ class TestLibraryViewset(EndpointTester):
|
|||
|
||||
@decl_endpoint('/api/library/{item}', method='delete')
|
||||
def test_destroy(self):
|
||||
response = self.execute(item=self.owned.pk)
|
||||
self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT])
|
||||
|
||||
self.executeNoContent(item=self.owned.pk)
|
||||
self.executeForbidden(item=self.unowned.pk)
|
||||
self.toggle_admin(True)
|
||||
response = self.execute(item=self.unowned.pk)
|
||||
self.assertTrue(response.status_code in [status.HTTP_202_ACCEPTED, status.HTTP_204_NO_CONTENT])
|
||||
self.executeNoContent(item=self.unowned.pk)
|
||||
|
||||
|
||||
@decl_endpoint('/api/library/active', method='get')
|
||||
|
|
|
@ -13,7 +13,7 @@ from rest_framework.decorators import action
|
|||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
|
||||
from apps.oss.models import Operation, OperationSchema
|
||||
from apps.oss.models import Operation, OperationSchema, PropagationFacade
|
||||
from apps.rsform.models import RSForm
|
||||
from apps.rsform.serializers import RSFormParseSerializer
|
||||
from apps.users.models import User
|
||||
|
@ -67,6 +67,10 @@ class LibraryViewSet(viewsets.ModelViewSet):
|
|||
if update_list:
|
||||
Operation.objects.bulk_update(update_list, ['alias', 'title', 'comment'])
|
||||
|
||||
def perform_destroy(self, instance: m.LibraryItem) -> None:
|
||||
PropagationFacade.before_delete_schema(instance)
|
||||
return super().perform_destroy(instance)
|
||||
|
||||
def get_permissions(self):
|
||||
if self.action in ['update', 'partial_update']:
|
||||
access_level = permissions.ItemEditor
|
||||
|
|
|
@ -1,400 +0,0 @@
|
|||
''' Models: Change propagation manager. '''
|
||||
from typing import Optional, cast
|
||||
|
||||
from cctext import extract_entities
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.rsform.graph import Graph
|
||||
from apps.rsform.models import (
|
||||
INSERT_LAST,
|
||||
Constituenta,
|
||||
CstType,
|
||||
RSForm,
|
||||
extract_globals,
|
||||
replace_entities,
|
||||
replace_globals
|
||||
)
|
||||
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation, OperationType
|
||||
from .OperationSchema import OperationSchema
|
||||
from .Substitution import Substitution
|
||||
|
||||
CstMapping = dict[str, Constituenta]
|
||||
CstSubstitution = list[tuple[Constituenta, Constituenta]]
|
||||
|
||||
|
||||
class ChangeManager:
|
||||
''' Change propagation wrapper for OSS. '''
|
||||
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.oss = OperationSchema(model)
|
||||
self.cache = OssCache(self.oss)
|
||||
|
||||
def after_create_cst(self, cst_list: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when new constituent is created. '''
|
||||
self.cache.insert(source)
|
||||
inserted_aliases = [cst.alias for cst in cst_list]
|
||||
depend_aliases: set[str] = set()
|
||||
for new_cst in cst_list:
|
||||
depend_aliases.update(new_cst.extract_references())
|
||||
depend_aliases.difference_update(inserted_aliases)
|
||||
alias_mapping: CstMapping = {}
|
||||
for alias in depend_aliases:
|
||||
cst = source.cache.by_alias.get(alias)
|
||||
if cst is not None:
|
||||
alias_mapping[alias] = cst
|
||||
operation = self.cache.get_operation(source)
|
||||
self._cascade_create_cst(cst_list, operation, alias_mapping)
|
||||
|
||||
def after_change_cst_type(self, target: Constituenta, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source)
|
||||
self._cascade_change_cst_type(target.pk, target.cst_type, operation)
|
||||
|
||||
def after_update_cst(self, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source)
|
||||
depend_aliases = self._extract_data_references(data, old_data)
|
||||
alias_mapping: CstMapping = {}
|
||||
for alias in depend_aliases:
|
||||
cst = source.cache.by_alias.get(alias)
|
||||
if cst is not None:
|
||||
alias_mapping[alias] = cst
|
||||
self._cascade_update_cst(target.pk, operation, data, old_data, alias_mapping)
|
||||
|
||||
def before_delete(self, target: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source)
|
||||
self._cascade_before_delete(target, operation)
|
||||
|
||||
def before_substitute(self, substitutions: CstSubstitution, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are substituted. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source)
|
||||
self._cascade_before_substitute(substitutions, operation)
|
||||
|
||||
def _cascade_before_substitute(
|
||||
self,
|
||||
substitutions: CstSubstitution,
|
||||
operation: Operation
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
child_schema.cache.ensure_loaded()
|
||||
new_substitutions = self._transform_substitutions(substitutions, child_operation, child_schema)
|
||||
if len(new_substitutions) == 0:
|
||||
continue
|
||||
self._cascade_before_substitute(new_substitutions, child_operation)
|
||||
child_schema.substitute(new_substitutions)
|
||||
|
||||
def _cascade_create_cst(self, cst_list: list[Constituenta], operation: Operation, mapping: CstMapping) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
source_schema = self.cache.get_schema(operation)
|
||||
assert source_schema is not None
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
|
||||
# TODO: update substitutions for diamond synthesis (if needed)
|
||||
|
||||
self.cache.ensure_loaded()
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
alias_mapping = {alias: cst.alias for alias, cst in new_mapping.items()}
|
||||
insert_where = self._determine_insert_position(cst_list[0], child_operation, source_schema, child_schema)
|
||||
new_cst_list = child_schema.insert_copy(cst_list, insert_where, alias_mapping)
|
||||
for index, cst in enumerate(new_cst_list):
|
||||
new_inheritance = Inheritance.objects.create(
|
||||
operation=child_operation,
|
||||
child=cst,
|
||||
parent=cst_list[index]
|
||||
)
|
||||
self.cache.insert_inheritance(new_inheritance)
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_create_cst(new_cst_list, child_operation, new_mapping)
|
||||
|
||||
def _cascade_change_cst_type(self, cst_id: int, ctype: CstType, operation: Operation) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is not None and child_schema.change_cst_type(successor_id, ctype):
|
||||
self._cascade_change_cst_type(successor_id, ctype, child_operation)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def _cascade_update_cst(
|
||||
self,
|
||||
cst_id: int, operation: Operation,
|
||||
data: dict, old_data: dict,
|
||||
mapping: CstMapping
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
assert child_schema is not None
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
alias_mapping = {alias: cst.alias for alias, cst in new_mapping.items()}
|
||||
successor = child_schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
new_data = self._prepare_update_data(successor, data, old_data, alias_mapping)
|
||||
if len(new_data) == 0:
|
||||
continue
|
||||
new_old_data = child_schema.update_cst(successor, new_data)
|
||||
if len(new_old_data) == 0:
|
||||
continue
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_update_cst(successor_id, child_operation, new_data, new_old_data, new_mapping)
|
||||
|
||||
def _cascade_before_delete(self, target: list[Constituenta], operation: Operation) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
child_schema.cache.ensure_loaded()
|
||||
|
||||
# TODO: check if substitutions are affected. Undo substitutions before deletion
|
||||
|
||||
child_target_cst = []
|
||||
child_target_ids = []
|
||||
for cst in target:
|
||||
successor_id = self.cache.get_inheritor(cst.pk, child_id)
|
||||
if successor_id is not None:
|
||||
child_target_ids.append(successor_id)
|
||||
child_target_cst.append(child_schema.cache.by_id[successor_id])
|
||||
self._cascade_before_delete(child_target_cst, child_operation)
|
||||
self.cache.remove_cst(child_target_ids, child_id)
|
||||
child_schema.delete_cst(child_target_cst)
|
||||
|
||||
def _transform_mapping(self, mapping: CstMapping, operation: Operation, schema: RSForm) -> CstMapping:
|
||||
if len(mapping) == 0:
|
||||
return mapping
|
||||
result: CstMapping = {}
|
||||
for alias, cst in mapping.items():
|
||||
successor_id = self.cache.get_successor(cst.pk, operation.pk)
|
||||
if successor_id is None:
|
||||
continue
|
||||
successor = schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
result[alias] = successor
|
||||
return result
|
||||
|
||||
def _determine_insert_position(
|
||||
self, prototype: Constituenta,
|
||||
operation: Operation,
|
||||
source: RSForm,
|
||||
destination: RSForm
|
||||
) -> int:
|
||||
''' Determine insert_after for new constituenta. '''
|
||||
if prototype.order == 1:
|
||||
return 1
|
||||
prev_cst = source.cache.constituents[prototype.order - 2]
|
||||
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
|
||||
if inherited_prev_id is None:
|
||||
return INSERT_LAST
|
||||
prev_cst = destination.cache.by_id[inherited_prev_id]
|
||||
return cast(int, prev_cst.order) + 1
|
||||
|
||||
def _extract_data_references(self, data: dict, old_data: dict) -> set[str]:
|
||||
result: set[str] = set()
|
||||
if 'definition_formal' in data:
|
||||
result.update(extract_globals(data['definition_formal']))
|
||||
result.update(extract_globals(old_data['definition_formal']))
|
||||
if 'term_raw' in data:
|
||||
result.update(extract_entities(data['term_raw']))
|
||||
result.update(extract_entities(old_data['term_raw']))
|
||||
if 'definition_raw' in data:
|
||||
result.update(extract_entities(data['definition_raw']))
|
||||
result.update(extract_entities(old_data['definition_raw']))
|
||||
return result
|
||||
|
||||
def _prepare_update_data(self, cst: Constituenta, data: dict, old_data: dict, mapping: dict[str, str]) -> dict:
|
||||
new_data = {}
|
||||
if 'term_forms' in data:
|
||||
if old_data['term_forms'] == cst.term_forms:
|
||||
new_data['term_forms'] = data['term_forms']
|
||||
if 'convention' in data:
|
||||
if old_data['convention'] == cst.convention:
|
||||
new_data['convention'] = data['convention']
|
||||
if 'definition_formal' in data:
|
||||
new_data['definition_formal'] = replace_globals(data['definition_formal'], mapping)
|
||||
if 'term_raw' in data:
|
||||
if replace_entities(old_data['term_raw'], mapping) == cst.term_raw:
|
||||
new_data['term_raw'] = replace_entities(data['term_raw'], mapping)
|
||||
if 'definition_raw' in data:
|
||||
if replace_entities(old_data['definition_raw'], mapping) == cst.definition_raw:
|
||||
new_data['definition_raw'] = replace_entities(data['definition_raw'], mapping)
|
||||
return new_data
|
||||
|
||||
def _transform_substitutions(
|
||||
self,
|
||||
target: CstSubstitution,
|
||||
operation: Operation,
|
||||
schema: RSForm
|
||||
) -> CstSubstitution:
|
||||
result: CstSubstitution = []
|
||||
for current_sub in target:
|
||||
sub_replaced = False
|
||||
new_substitution_id = self.cache.get_inheritor(current_sub[1].pk, operation.pk)
|
||||
if new_substitution_id is None:
|
||||
for sub in self.cache.substitutions[operation.pk]:
|
||||
if sub.original_id == current_sub[1].pk:
|
||||
sub_replaced = True
|
||||
new_substitution_id = self.cache.get_inheritor(sub.original_id, operation.pk)
|
||||
break
|
||||
|
||||
new_original_id = self.cache.get_inheritor(current_sub[0].pk, operation.pk)
|
||||
original_replaced = False
|
||||
if new_original_id is None:
|
||||
for sub in self.cache.substitutions[operation.pk]:
|
||||
if sub.original_id == current_sub[0].pk:
|
||||
original_replaced = True
|
||||
sub.original_id = current_sub[1].pk
|
||||
sub.save()
|
||||
new_original_id = new_substitution_id
|
||||
new_substitution_id = self.cache.get_inheritor(sub.substitution_id, operation.pk)
|
||||
break
|
||||
|
||||
if sub_replaced and original_replaced:
|
||||
raise ValidationError({'propagation': 'Substitution breaks OSS substitutions.'})
|
||||
|
||||
for sub in self.cache.substitutions[operation.pk]:
|
||||
if sub.substitution_id == current_sub[0].pk:
|
||||
sub.substitution_id = current_sub[1].pk
|
||||
sub.save()
|
||||
|
||||
if new_original_id is not None and new_substitution_id is not None:
|
||||
result.append((schema.cache.by_id[new_original_id], schema.cache.by_id[new_substitution_id]))
|
||||
return result
|
||||
|
||||
|
||||
class OssCache:
|
||||
''' Cache for OSS data. '''
|
||||
|
||||
def __init__(self, oss: OperationSchema):
|
||||
self._oss = oss
|
||||
self._schemas: list[RSForm] = []
|
||||
self._schema_by_id: dict[int, RSForm] = {}
|
||||
|
||||
self.operations = list(oss.operations().only('result_id'))
|
||||
self.operation_by_id = {operation.pk: operation for operation in self.operations}
|
||||
self.graph = Graph[int]()
|
||||
for operation in self.operations:
|
||||
self.graph.add_node(operation.pk)
|
||||
for argument in self._oss.arguments().only('operation_id', 'argument_id'):
|
||||
self.graph.add_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
self.is_loaded = False
|
||||
self.substitutions: dict[int, list[Substitution]] = {}
|
||||
self.inheritance: dict[int, list[Inheritance]] = {}
|
||||
|
||||
def insert(self, schema: RSForm) -> None:
|
||||
''' Insert new schema. '''
|
||||
if not self._schema_by_id.get(schema.model.pk):
|
||||
self._insert_new(schema)
|
||||
|
||||
def get_schema(self, operation: Operation) -> Optional[RSForm]:
|
||||
''' Get schema by Operation. '''
|
||||
if operation.result_id is None:
|
||||
return None
|
||||
if operation.result_id in self._schema_by_id:
|
||||
return self._schema_by_id[operation.result_id]
|
||||
else:
|
||||
schema = RSForm.from_id(operation.result_id)
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
return schema
|
||||
|
||||
def get_operation(self, schema: RSForm) -> Operation:
|
||||
''' Get operation by schema. '''
|
||||
for operation in self.operations:
|
||||
if operation.result_id == schema.model.pk:
|
||||
return operation
|
||||
raise ValueError(f'Operation for schema {schema.model.pk} not found')
|
||||
|
||||
def ensure_loaded(self) -> None:
|
||||
''' Ensure propagation of changes. '''
|
||||
if self.is_loaded:
|
||||
return
|
||||
self.is_loaded = True
|
||||
for operation in self.operations:
|
||||
if operation.operation_type != OperationType.INPUT:
|
||||
self.inheritance[operation.pk] = []
|
||||
self.substitutions[operation.pk] = []
|
||||
for sub in self._oss.substitutions().only('operation_id', 'original_id', 'substitution_id'):
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
for item in self._oss.inheritance().only('operation_id', 'parent_id', 'child_id'):
|
||||
self.inheritance[item.operation_id].append(item)
|
||||
|
||||
def get_inheritor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id == parent_cst:
|
||||
return item.child_id
|
||||
return None
|
||||
|
||||
def get_successor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom including substitutions. '''
|
||||
for sub in self.substitutions[operation]:
|
||||
if sub.original_id == parent_cst:
|
||||
return self.get_inheritor(sub.substitution_id, operation)
|
||||
return self.get_inheritor(parent_cst, operation)
|
||||
|
||||
|
||||
def insert_inheritance(self, inheritance: Inheritance) -> None:
|
||||
''' Insert new inheritance. '''
|
||||
self.inheritance[inheritance.operation_id].append(inheritance)
|
||||
|
||||
def insert_substitution(self, sub: Substitution) -> None:
|
||||
''' Insert new inheritance. '''
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
|
||||
def remove_cst(self, target: list[int], operation: int) -> None:
|
||||
''' Remove constituents from operation. '''
|
||||
subs_to_delete = [
|
||||
sub for sub in self.substitutions[operation]
|
||||
if sub.original_id in target or sub.substitution_id in target
|
||||
]
|
||||
for sub in subs_to_delete:
|
||||
self.substitutions[operation].remove(sub)
|
||||
inherit_to_delete = [item for item in self.inheritance[operation] if item.child_id in target]
|
||||
for item in inherit_to_delete:
|
||||
self.inheritance[operation].remove(item)
|
||||
|
||||
def _insert_new(self, schema: RSForm) -> None:
|
||||
self._schemas.append(schema)
|
||||
self._schema_by_id[schema.model.pk] = schema
|
|
@ -1,22 +1,38 @@
|
|||
''' Models: OSS API. '''
|
||||
from typing import Optional
|
||||
from typing import Optional, cast
|
||||
|
||||
from cctext import extract_entities
|
||||
from django.db.models import QuerySet
|
||||
from rest_framework.serializers import ValidationError
|
||||
|
||||
from apps.library.models import Editor, LibraryItem, LibraryItemType
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
from apps.rsform.graph import Graph
|
||||
from apps.rsform.models import (
|
||||
DELETED_ALIAS,
|
||||
INSERT_LAST,
|
||||
Constituenta,
|
||||
CstType,
|
||||
RSForm,
|
||||
extract_globals,
|
||||
replace_entities,
|
||||
replace_globals
|
||||
)
|
||||
|
||||
from .Argument import Argument
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation
|
||||
from .Substitution import Substitution
|
||||
|
||||
CstMapping = dict[str, Optional[Constituenta]]
|
||||
CstSubstitution = list[tuple[Constituenta, Constituenta]]
|
||||
|
||||
|
||||
class OperationSchema:
|
||||
''' Operations schema API. '''
|
||||
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.model = model
|
||||
self.cache = OssCache(self)
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs) -> 'OperationSchema':
|
||||
|
@ -75,34 +91,45 @@ class OperationSchema:
|
|||
def create_operation(self, **kwargs) -> Operation:
|
||||
''' Insert new operation. '''
|
||||
result = Operation.objects.create(oss=self.model, **kwargs)
|
||||
self.save()
|
||||
result.refresh_from_db()
|
||||
self.cache.insert_operation(result)
|
||||
self.save(update_fields=['time_update'])
|
||||
return result
|
||||
|
||||
def delete_operation(self, operation: Operation):
|
||||
def delete_operation(self, target: Operation, keep_constituents: bool = False):
|
||||
''' Delete operation. '''
|
||||
operation.delete()
|
||||
if not keep_constituents:
|
||||
schema = self.cache.get_schema(target)
|
||||
if schema is not None:
|
||||
self.before_delete_cst(schema.cache.constituents, schema)
|
||||
self.cache.remove_operation(target.pk)
|
||||
target.delete()
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
# TODO: deal with attached schema
|
||||
# TODO: trigger on_change effects
|
||||
|
||||
self.save()
|
||||
|
||||
def set_input(self, target: Operation, schema: Optional[LibraryItem]) -> None:
|
||||
def set_input(self, target: int, schema: Optional[LibraryItem]) -> None:
|
||||
''' Set input schema for operation. '''
|
||||
if schema == target.result:
|
||||
operation = self.cache.operation_by_id[target]
|
||||
has_children = len(self.cache.graph.outputs[target]) > 0
|
||||
old_schema = self.cache.get_schema(operation)
|
||||
if schema == old_schema:
|
||||
return
|
||||
target.result = schema
|
||||
|
||||
if old_schema is not None:
|
||||
if has_children:
|
||||
self.before_delete_cst(old_schema.cache.constituents, old_schema)
|
||||
self.cache.remove_schema(old_schema)
|
||||
|
||||
operation.result = schema
|
||||
if schema is not None:
|
||||
target.result = schema
|
||||
target.alias = schema.alias
|
||||
target.title = schema.title
|
||||
target.comment = schema.comment
|
||||
target.save()
|
||||
operation.result = schema
|
||||
operation.alias = schema.alias
|
||||
operation.title = schema.title
|
||||
operation.comment = schema.comment
|
||||
operation.save(update_fields=['result', 'alias', 'title', 'comment'])
|
||||
|
||||
# TODO: trigger on_change effects
|
||||
|
||||
self.save()
|
||||
if schema is not None and has_children:
|
||||
rsform = RSForm(schema)
|
||||
self.after_create_cst(list(rsform.constituents()), rsform)
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def set_arguments(self, operation: Operation, arguments: list[Operation]) -> None:
|
||||
''' Set arguments to operation. '''
|
||||
|
@ -122,7 +149,7 @@ class OperationSchema:
|
|||
if not changed:
|
||||
return
|
||||
# TODO: trigger on_change effects
|
||||
self.save()
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def set_substitutions(self, target: Operation, substitutes: list[dict]) -> None:
|
||||
''' Clear all arguments for operation. '''
|
||||
|
@ -153,7 +180,7 @@ class OperationSchema:
|
|||
return
|
||||
# TODO: trigger on_change effects
|
||||
|
||||
self.save()
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def create_input(self, operation: Operation) -> RSForm:
|
||||
''' Create input RSForm. '''
|
||||
|
@ -169,7 +196,7 @@ class OperationSchema:
|
|||
Editor.set(schema.model.pk, self.model.editors().values_list('pk', flat=True))
|
||||
operation.result = schema.model
|
||||
operation.save()
|
||||
self.save()
|
||||
self.save(update_fields=['time_update'])
|
||||
return schema
|
||||
|
||||
def execute_operation(self, operation: Operation) -> bool:
|
||||
|
@ -210,5 +237,504 @@ class OperationSchema:
|
|||
|
||||
receiver.restore_order()
|
||||
receiver.reset_aliases()
|
||||
self.save()
|
||||
self.save(update_fields=['time_update'])
|
||||
return True
|
||||
|
||||
def after_create_cst(self, cst_list: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when new constituent is created. '''
|
||||
self.cache.insert(source)
|
||||
inserted_aliases = [cst.alias for cst in cst_list]
|
||||
depend_aliases: set[str] = set()
|
||||
for new_cst in cst_list:
|
||||
depend_aliases.update(new_cst.extract_references())
|
||||
depend_aliases.difference_update(inserted_aliases)
|
||||
alias_mapping: CstMapping = {}
|
||||
for alias in depend_aliases:
|
||||
cst = source.cache.by_alias.get(alias)
|
||||
if cst is not None:
|
||||
alias_mapping[alias] = cst
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_create_cst(cst_list, operation, alias_mapping)
|
||||
|
||||
def after_change_cst_type(self, target: Constituenta, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_change_cst_type(target.pk, target.cst_type, operation.pk)
|
||||
|
||||
def after_update_cst(self, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
depend_aliases = self._extract_data_references(data, old_data)
|
||||
alias_mapping: CstMapping = {}
|
||||
for alias in depend_aliases:
|
||||
cst = source.cache.by_alias.get(alias)
|
||||
if cst is not None:
|
||||
alias_mapping[alias] = cst
|
||||
self._cascade_update_cst(
|
||||
cst_id=target.pk,
|
||||
operation=operation.pk,
|
||||
data=data,
|
||||
old_data=old_data,
|
||||
mapping=alias_mapping
|
||||
)
|
||||
|
||||
def before_delete_cst(self, target: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_before_delete(target, operation.pk)
|
||||
|
||||
def before_substitute(self, substitutions: CstSubstitution, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are substituted. '''
|
||||
self.cache.insert(source)
|
||||
operation = self.cache.get_operation(source.model.pk)
|
||||
self._cascade_before_substitute(substitutions, operation)
|
||||
|
||||
def _cascade_create_cst(self, cst_list: list[Constituenta], operation: Operation, mapping: CstMapping) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
source_schema = self.cache.get_schema(operation)
|
||||
assert source_schema is not None
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
|
||||
# TODO: update substitutions for diamond synthesis (if needed)
|
||||
|
||||
self.cache.ensure_loaded()
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
|
||||
insert_where = self._determine_insert_position(cst_list[0], child_operation, source_schema, child_schema)
|
||||
new_cst_list = child_schema.insert_copy(cst_list, insert_where, alias_mapping)
|
||||
for index, cst in enumerate(new_cst_list):
|
||||
new_inheritance = Inheritance.objects.create(
|
||||
operation=child_operation,
|
||||
child=cst,
|
||||
parent=cst_list[index]
|
||||
)
|
||||
self.cache.insert_inheritance(new_inheritance)
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_create_cst(new_cst_list, child_operation, new_mapping)
|
||||
|
||||
def _cascade_change_cst_type(self, cst_id: int, ctype: CstType, operation: int) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is not None and child_schema.change_cst_type(successor_id, ctype):
|
||||
self._cascade_change_cst_type(successor_id, ctype, child_id)
|
||||
|
||||
# pylint: disable=too-many-arguments
|
||||
def _cascade_update_cst(
|
||||
self,
|
||||
cst_id: int, operation: int,
|
||||
data: dict, old_data: dict,
|
||||
mapping: CstMapping
|
||||
) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
successor_id = self.cache.get_inheritor(cst_id, child_id)
|
||||
if successor_id is None:
|
||||
continue
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
assert child_schema is not None
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
|
||||
successor = child_schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
new_data = self._prepare_update_data(successor, data, old_data, alias_mapping)
|
||||
if len(new_data) == 0:
|
||||
continue
|
||||
new_old_data = child_schema.update_cst(successor, new_data)
|
||||
if len(new_old_data) == 0:
|
||||
continue
|
||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||
self._cascade_update_cst(
|
||||
cst_id=successor_id,
|
||||
operation=child_id,
|
||||
data=new_data,
|
||||
old_data=new_old_data,
|
||||
mapping=new_mapping
|
||||
)
|
||||
|
||||
def _cascade_before_delete(self, target: list[Constituenta], operation: int) -> None:
|
||||
children = self.cache.graph.outputs[operation]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
self._undo_substitutions_cst(target, child_operation, child_schema)
|
||||
child_target_ids = self.cache.get_inheritors_list([cst.pk for cst in target], child_id)
|
||||
child_target_cst = [child_schema.cache.by_id[cst_id] for cst_id in child_target_ids]
|
||||
self._cascade_before_delete(child_target_cst, child_id)
|
||||
if len(child_target_cst) > 0:
|
||||
self.cache.remove_cst(child_target_ids, child_id)
|
||||
child_schema.delete_cst(child_target_cst)
|
||||
|
||||
def _cascade_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None:
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
new_substitutions = self._transform_substitutions(substitutions, child_id, child_schema)
|
||||
if len(new_substitutions) == 0:
|
||||
continue
|
||||
self._cascade_before_substitute(new_substitutions, child_operation)
|
||||
child_schema.substitute(new_substitutions)
|
||||
|
||||
def _cascade_partial_mapping(
|
||||
self,
|
||||
mapping: CstMapping,
|
||||
target: list[int],
|
||||
operation: Operation,
|
||||
schema: RSForm
|
||||
) -> None:
|
||||
alias_mapping = OperationSchema._produce_alias_mapping(mapping)
|
||||
schema.apply_partial_mapping(alias_mapping, target)
|
||||
children = self.cache.graph.outputs[operation.pk]
|
||||
if len(children) == 0:
|
||||
return
|
||||
self.cache.ensure_loaded()
|
||||
for child_id in children:
|
||||
child_operation = self.cache.operation_by_id[child_id]
|
||||
child_schema = self.cache.get_schema(child_operation)
|
||||
if child_schema is None:
|
||||
continue
|
||||
new_mapping = self._transform_mapping(mapping, child_operation, child_schema)
|
||||
if not new_mapping:
|
||||
continue
|
||||
new_target = self.cache.get_inheritors_list(target, child_id)
|
||||
if len(new_target) == 0:
|
||||
continue
|
||||
self._cascade_partial_mapping(new_mapping, new_target, child_operation, child_schema)
|
||||
|
||||
@staticmethod
|
||||
def _produce_alias_mapping(mapping: CstMapping) -> dict[str, str]:
|
||||
result: dict[str, str] = {}
|
||||
for alias, cst in mapping.items():
|
||||
if cst is None:
|
||||
result[alias] = DELETED_ALIAS
|
||||
else:
|
||||
result[alias] = cst.alias
|
||||
return result
|
||||
|
||||
def _transform_mapping(self, mapping: CstMapping, operation: Operation, schema: RSForm) -> CstMapping:
|
||||
if len(mapping) == 0:
|
||||
return mapping
|
||||
result: CstMapping = {}
|
||||
for alias, cst in mapping.items():
|
||||
if cst is None:
|
||||
result[alias] = None
|
||||
continue
|
||||
successor_id = self.cache.get_successor(cst.pk, operation.pk)
|
||||
if successor_id is None:
|
||||
continue
|
||||
successor = schema.cache.by_id.get(successor_id)
|
||||
if successor is None:
|
||||
continue
|
||||
result[alias] = successor
|
||||
return result
|
||||
|
||||
def _determine_insert_position(
|
||||
self, prototype: Constituenta,
|
||||
operation: Operation,
|
||||
source: RSForm,
|
||||
destination: RSForm
|
||||
) -> int:
|
||||
''' Determine insert_after for new constituenta. '''
|
||||
if prototype.order == 1:
|
||||
return 1
|
||||
prev_cst = source.cache.constituents[prototype.order - 2]
|
||||
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
|
||||
if inherited_prev_id is None:
|
||||
return INSERT_LAST
|
||||
prev_cst = destination.cache.by_id[inherited_prev_id]
|
||||
return cast(int, prev_cst.order) + 1
|
||||
|
||||
def _extract_data_references(self, data: dict, old_data: dict) -> set[str]:
|
||||
result: set[str] = set()
|
||||
if 'definition_formal' in data:
|
||||
result.update(extract_globals(data['definition_formal']))
|
||||
result.update(extract_globals(old_data['definition_formal']))
|
||||
if 'term_raw' in data:
|
||||
result.update(extract_entities(data['term_raw']))
|
||||
result.update(extract_entities(old_data['term_raw']))
|
||||
if 'definition_raw' in data:
|
||||
result.update(extract_entities(data['definition_raw']))
|
||||
result.update(extract_entities(old_data['definition_raw']))
|
||||
return result
|
||||
|
||||
def _prepare_update_data(self, cst: Constituenta, data: dict, old_data: dict, mapping: dict[str, str]) -> dict:
|
||||
new_data = {}
|
||||
if 'term_forms' in data:
|
||||
if old_data['term_forms'] == cst.term_forms:
|
||||
new_data['term_forms'] = data['term_forms']
|
||||
if 'convention' in data:
|
||||
if old_data['convention'] == cst.convention:
|
||||
new_data['convention'] = data['convention']
|
||||
if 'definition_formal' in data:
|
||||
new_data['definition_formal'] = replace_globals(data['definition_formal'], mapping)
|
||||
if 'term_raw' in data:
|
||||
if replace_entities(old_data['term_raw'], mapping) == cst.term_raw:
|
||||
new_data['term_raw'] = replace_entities(data['term_raw'], mapping)
|
||||
if 'definition_raw' in data:
|
||||
if replace_entities(old_data['definition_raw'], mapping) == cst.definition_raw:
|
||||
new_data['definition_raw'] = replace_entities(data['definition_raw'], mapping)
|
||||
return new_data
|
||||
|
||||
def _transform_substitutions(
|
||||
self,
|
||||
target: CstSubstitution,
|
||||
operation: int,
|
||||
schema: RSForm
|
||||
) -> CstSubstitution:
|
||||
result: CstSubstitution = []
|
||||
for current_sub in target:
|
||||
sub_replaced = False
|
||||
new_substitution_id = self.cache.get_inheritor(current_sub[1].pk, operation)
|
||||
if new_substitution_id is None:
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.original_id == current_sub[1].pk:
|
||||
sub_replaced = True
|
||||
new_substitution_id = self.cache.get_inheritor(sub.original_id, operation)
|
||||
break
|
||||
|
||||
new_original_id = self.cache.get_inheritor(current_sub[0].pk, operation)
|
||||
original_replaced = False
|
||||
if new_original_id is None:
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.original_id == current_sub[0].pk:
|
||||
original_replaced = True
|
||||
sub.original_id = current_sub[1].pk
|
||||
sub.save()
|
||||
new_original_id = new_substitution_id
|
||||
new_substitution_id = self.cache.get_inheritor(sub.substitution_id, operation)
|
||||
break
|
||||
|
||||
if sub_replaced and original_replaced:
|
||||
raise ValidationError({'propagation': 'Substitution breaks OSS substitutions.'})
|
||||
|
||||
for sub in self.cache.substitutions[operation]:
|
||||
if sub.substitution_id == current_sub[0].pk:
|
||||
sub.substitution_id = current_sub[1].pk
|
||||
sub.save()
|
||||
|
||||
if new_original_id is not None and new_substitution_id is not None:
|
||||
result.append((schema.cache.by_id[new_original_id], schema.cache.by_id[new_substitution_id]))
|
||||
return result
|
||||
|
||||
def _undo_substitutions_cst(self, target: list[Constituenta], operation: Operation, schema: RSForm) -> None:
|
||||
target_ids = [cst.pk for cst in target]
|
||||
to_process = []
|
||||
for sub in self.cache.substitutions[operation.pk]:
|
||||
if sub.original_id in target_ids or sub.substitution_id in target_ids:
|
||||
to_process.append(sub)
|
||||
for sub in to_process:
|
||||
self._undo_substitution(sub, schema, target_ids)
|
||||
|
||||
def _undo_substitution(self, target: Substitution, schema: RSForm, ignore_parents: list[int]) -> None:
|
||||
operation = self.cache.operation_by_id[target.operation_id]
|
||||
original_schema, _, original_cst, substitution_cst = self.cache.unfold_sub(target)
|
||||
|
||||
dependant = []
|
||||
for cst_id in original_schema.get_dependant([original_cst.pk]):
|
||||
if cst_id not in ignore_parents:
|
||||
inheritor_id = self.cache.get_inheritor(cst_id, operation.pk)
|
||||
if inheritor_id is not None:
|
||||
dependant.append(inheritor_id)
|
||||
|
||||
self.cache.substitutions[operation.pk].remove(target)
|
||||
target.delete()
|
||||
|
||||
new_original: Optional[Constituenta] = None
|
||||
if original_cst.pk not in ignore_parents:
|
||||
full_cst = Constituenta.objects.get(pk=original_cst.pk)
|
||||
self.after_create_cst([full_cst], original_schema)
|
||||
new_original_id = self.cache.get_inheritor(original_cst.pk, operation.pk)
|
||||
assert new_original_id is not None
|
||||
new_original = schema.cache.by_id[new_original_id]
|
||||
if len(dependant) == 0:
|
||||
return
|
||||
|
||||
substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation.pk)
|
||||
assert substitution_id is not None
|
||||
substitution_inheritor = schema.cache.by_id[substitution_id]
|
||||
mapping = {cast(str, substitution_inheritor.alias): new_original}
|
||||
self._cascade_partial_mapping(mapping, dependant, operation, schema)
|
||||
|
||||
|
||||
class OssCache:
|
||||
''' Cache for OSS data. '''
|
||||
|
||||
def __init__(self, oss: OperationSchema):
|
||||
self._oss = oss
|
||||
self._schemas: list[RSForm] = []
|
||||
self._schema_by_id: dict[int, RSForm] = {}
|
||||
|
||||
self.operations = list(oss.operations().only('result_id'))
|
||||
self.operation_by_id = {operation.pk: operation for operation in self.operations}
|
||||
self.graph = Graph[int]()
|
||||
for operation in self.operations:
|
||||
self.graph.add_node(operation.pk)
|
||||
for argument in self._oss.arguments().only('operation_id', 'argument_id'):
|
||||
self.graph.add_edge(argument.argument_id, argument.operation_id)
|
||||
|
||||
self.is_loaded = False
|
||||
self.substitutions: dict[int, list[Substitution]] = {}
|
||||
self.inheritance: dict[int, list[Inheritance]] = {}
|
||||
|
||||
def insert(self, schema: RSForm) -> None:
|
||||
''' Insert new schema. '''
|
||||
if not self._schema_by_id.get(schema.model.pk):
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
|
||||
def get_schema(self, operation: Operation) -> Optional[RSForm]:
|
||||
''' Get schema by Operation. '''
|
||||
if operation.result_id is None:
|
||||
return None
|
||||
if operation.result_id in self._schema_by_id:
|
||||
return self._schema_by_id[operation.result_id]
|
||||
else:
|
||||
schema = RSForm.from_id(operation.result_id)
|
||||
schema.cache.ensure_loaded()
|
||||
self._insert_new(schema)
|
||||
return schema
|
||||
|
||||
def get_operation(self, schema: int) -> Operation:
|
||||
''' Get operation by schema. '''
|
||||
for operation in self.operations:
|
||||
if operation.result_id == schema:
|
||||
return operation
|
||||
raise ValueError(f'Operation for schema {schema} not found')
|
||||
|
||||
def ensure_loaded(self) -> None:
|
||||
''' Ensure cache is fully loaded. '''
|
||||
if self.is_loaded:
|
||||
return
|
||||
self.is_loaded = True
|
||||
for operation in self.operations:
|
||||
self.inheritance[operation.pk] = []
|
||||
self.substitutions[operation.pk] = []
|
||||
for sub in self._oss.substitutions().only('operation_id', 'original_id', 'substitution_id'):
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
for item in self._oss.inheritance().only('operation_id', 'parent_id', 'child_id'):
|
||||
self.inheritance[item.operation_id].append(item)
|
||||
|
||||
def get_inheritor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id == parent_cst:
|
||||
return item.child_id
|
||||
return None
|
||||
|
||||
def get_inheritors_list(self, target: list[int], operation: int) -> list[int]:
|
||||
''' Get child for parent inside target RSFrom. '''
|
||||
result = []
|
||||
for item in self.inheritance[operation]:
|
||||
if item.parent_id in target:
|
||||
result.append(item.child_id)
|
||||
return result
|
||||
|
||||
def get_successor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||
''' Get child for parent inside target RSFrom including substitutions. '''
|
||||
for sub in self.substitutions[operation]:
|
||||
if sub.original_id == parent_cst:
|
||||
return self.get_inheritor(sub.substitution_id, operation)
|
||||
return self.get_inheritor(parent_cst, operation)
|
||||
|
||||
def insert_operation(self, operation: Operation) -> None:
|
||||
''' Insert new operation. '''
|
||||
self.operations.append(operation)
|
||||
self.operation_by_id[operation.pk] = operation
|
||||
self.graph.add_node(operation.pk)
|
||||
if self.is_loaded:
|
||||
self.substitutions[operation.pk] = []
|
||||
self.inheritance[operation.pk] = []
|
||||
|
||||
def insert_inheritance(self, inheritance: Inheritance) -> None:
|
||||
''' Insert new inheritance. '''
|
||||
self.inheritance[inheritance.operation_id].append(inheritance)
|
||||
|
||||
def insert_substitution(self, sub: Substitution) -> None:
|
||||
''' Insert new substitution. '''
|
||||
self.substitutions[sub.operation_id].append(sub)
|
||||
|
||||
def remove_cst(self, target: list[int], operation: int) -> None:
|
||||
''' Remove constituents from operation. '''
|
||||
subs_to_delete = [
|
||||
sub for sub in self.substitutions[operation]
|
||||
if sub.original_id in target or sub.substitution_id in target
|
||||
]
|
||||
for sub in subs_to_delete:
|
||||
self.substitutions[operation].remove(sub)
|
||||
inherit_to_delete = [item for item in self.inheritance[operation] if item.child_id in target]
|
||||
for item in inherit_to_delete:
|
||||
self.inheritance[operation].remove(item)
|
||||
|
||||
def remove_operation(self, operation: int) -> None:
|
||||
''' Remove operation from cache. '''
|
||||
target = self.operation_by_id[operation]
|
||||
if target.result_id in self._schema_by_id:
|
||||
self._schemas.remove(self._schema_by_id[target.result_id])
|
||||
del self._schema_by_id[target.result_id]
|
||||
self.operations.remove(self.operation_by_id[operation])
|
||||
del self.operation_by_id[operation]
|
||||
if self.is_loaded:
|
||||
del self.substitutions[operation]
|
||||
del self.inheritance[operation]
|
||||
|
||||
def remove_schema(self, schema: RSForm) -> None:
|
||||
''' Remove schema from cache. '''
|
||||
self._schemas.remove(schema)
|
||||
del self._schema_by_id[schema.model.pk]
|
||||
|
||||
def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]:
|
||||
operation = self.operation_by_id[sub.operation_id]
|
||||
parents = self.graph.inputs[operation.pk]
|
||||
original_cst = None
|
||||
substitution_cst = None
|
||||
original_schema = None
|
||||
substitution_schema = None
|
||||
for parent_id in parents:
|
||||
parent_schema = self.get_schema(self.operation_by_id[parent_id])
|
||||
if parent_schema is None:
|
||||
continue
|
||||
if sub.original_id in parent_schema.cache.by_id:
|
||||
original_schema = parent_schema
|
||||
original_cst = original_schema.cache.by_id[sub.original_id]
|
||||
if sub.substitution_id in parent_schema.cache.by_id:
|
||||
substitution_schema = parent_schema
|
||||
substitution_cst = substitution_schema.cache.by_id[sub.substitution_id]
|
||||
if original_schema is None or substitution_schema is None or original_cst is None or substitution_cst is None:
|
||||
raise ValueError(f'Parent schema for Substitution-{sub.pk} not found.')
|
||||
return original_schema, substitution_schema, original_cst, substitution_cst
|
||||
|
||||
def _insert_new(self, schema: RSForm) -> None:
|
||||
self._schemas.append(schema)
|
||||
self._schema_by_id[schema.model.pk] = schema
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
''' Models: Change propagation facade - managing all changes in OSS. '''
|
||||
from apps.library.models import LibraryItem
|
||||
from apps.library.models import LibraryItem, LibraryItemType
|
||||
from apps.rsform.models import Constituenta, RSForm
|
||||
|
||||
from .ChangeManager import ChangeManager
|
||||
from .OperationSchema import CstSubstitution, OperationSchema
|
||||
|
||||
|
||||
def _get_oss_hosts(item: LibraryItem) -> list[LibraryItem]:
|
||||
|
@ -13,37 +13,49 @@ def _get_oss_hosts(item: LibraryItem) -> list[LibraryItem]:
|
|||
class PropagationFacade:
|
||||
''' Change propagation API. '''
|
||||
|
||||
@classmethod
|
||||
def after_create_cst(cls, new_cst: list[Constituenta], source: RSForm) -> None:
|
||||
@staticmethod
|
||||
def after_create_cst(new_cst: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when new constituent is created. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).after_create_cst(new_cst, source)
|
||||
OperationSchema(host).after_create_cst(new_cst, source)
|
||||
|
||||
@classmethod
|
||||
def after_change_cst_type(cls, target: Constituenta, source: RSForm) -> None:
|
||||
@staticmethod
|
||||
def after_change_cst_type(target: Constituenta, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).after_change_cst_type(target, source)
|
||||
OperationSchema(host).after_change_cst_type(target, source)
|
||||
|
||||
@classmethod
|
||||
def after_update_cst(cls, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
||||
@staticmethod
|
||||
def after_update_cst(target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).after_update_cst(target, data, old_data, source)
|
||||
OperationSchema(host).after_update_cst(target, data, old_data, source)
|
||||
|
||||
@classmethod
|
||||
def before_delete(cls, target: list[Constituenta], source: RSForm) -> None:
|
||||
@staticmethod
|
||||
def before_delete_cst(target: list[Constituenta], source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).before_delete(target, source)
|
||||
OperationSchema(host).before_delete_cst(target, source)
|
||||
|
||||
@classmethod
|
||||
def before_substitute(cls, substitutions: list[tuple[Constituenta, Constituenta]], source: RSForm) -> None:
|
||||
@staticmethod
|
||||
def before_substitute(substitutions: CstSubstitution, source: RSForm) -> None:
|
||||
''' Trigger cascade resolutions before constituents are substituted. '''
|
||||
hosts = _get_oss_hosts(source.model)
|
||||
for host in hosts:
|
||||
ChangeManager(host).before_substitute(substitutions, source)
|
||||
OperationSchema(host).before_substitute(substitutions, source)
|
||||
|
||||
@staticmethod
|
||||
def before_delete_schema(item: LibraryItem) -> None:
|
||||
''' Trigger cascade resolutions before schema is deleted. '''
|
||||
if item.item_type != LibraryItemType.RSFORM:
|
||||
return
|
||||
hosts = _get_oss_hosts(item)
|
||||
if len(hosts) == 0:
|
||||
return
|
||||
|
||||
schema = RSForm(item)
|
||||
PropagationFacade.before_delete_cst(list(schema.constituents()), schema)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
''' Django: Models. '''
|
||||
|
||||
from .Argument import Argument
|
||||
from .ChangeManager import ChangeManager
|
||||
from .Inheritance import Inheritance
|
||||
from .Operation import Operation, OperationType
|
||||
from .OperationSchema import OperationSchema
|
||||
from .Substitution import Substitution
|
||||
from .PropagationFacade import PropagationFacade
|
||||
from .Substitution import Substitution
|
||||
|
|
|
@ -4,6 +4,7 @@ from .basics import OperationPositionSerializer, PositionsSerializer, Substituti
|
|||
from .data_access import (
|
||||
ArgumentSerializer,
|
||||
OperationCreateSerializer,
|
||||
OperationDeleteSerializer,
|
||||
OperationSchemaSerializer,
|
||||
OperationSerializer,
|
||||
OperationTargetSerializer,
|
||||
|
|
|
@ -138,6 +138,26 @@ class OperationTargetSerializer(serializers.Serializer):
|
|||
return attrs
|
||||
|
||||
|
||||
class OperationDeleteSerializer(serializers.Serializer):
|
||||
''' Serializer: Delete operation. '''
|
||||
target = PKField(many=False, queryset=Operation.objects.all().only('oss_id', 'result'))
|
||||
positions = serializers.ListField(
|
||||
child=OperationPositionSerializer(),
|
||||
default=[]
|
||||
)
|
||||
keep_constituents = serializers.BooleanField(default=False, required=False)
|
||||
delete_schema = serializers.BooleanField(default=False, required=False)
|
||||
|
||||
def validate(self, attrs):
|
||||
oss = cast(LibraryItem, self.context['oss'])
|
||||
operation = cast(Operation, attrs['target'])
|
||||
if oss and operation.oss_id != oss.pk:
|
||||
raise serializers.ValidationError({
|
||||
'target': msg.operationNotInOSS(oss.title)
|
||||
})
|
||||
return attrs
|
||||
|
||||
|
||||
class SetOperationInputSerializer(serializers.Serializer):
|
||||
''' Serializer: Set input schema for operation. '''
|
||||
target = PKField(many=False, queryset=Operation.objects.all())
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
''' Tests for REST API OSS propagation. '''
|
||||
from .t_attributes import *
|
||||
from .t_constituents import *
|
||||
from .t_operations import *
|
||||
from .t_substitutions import *
|
||||
|
|
251
rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py
Normal file
251
rsconcept/backend/apps/oss/tests/s_propagation/t_operations.py
Normal file
|
@ -0,0 +1,251 @@
|
|||
''' Testing API: Change substitutions in OSS. '''
|
||||
|
||||
from apps.oss.models import OperationSchema, OperationType
|
||||
from apps.rsform.models import Constituenta, CstType, RSForm
|
||||
from shared.EndpointTester import EndpointTester, decl_endpoint
|
||||
|
||||
|
||||
class TestChangeOperations(EndpointTester):
|
||||
''' Testing Operations change propagation in OSS. '''
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.owned = OperationSchema.create(
|
||||
title='Test',
|
||||
alias='T1',
|
||||
owner=self.user
|
||||
)
|
||||
self.owned_id = self.owned.model.pk
|
||||
|
||||
self.ks1 = RSForm.create(
|
||||
alias='KS1',
|
||||
title='Test1',
|
||||
owner=self.user
|
||||
)
|
||||
self.ks1X1 = self.ks1.insert_new('X1', convention='KS1X1')
|
||||
self.ks1X2 = self.ks1.insert_new('X2', convention='KS1X2')
|
||||
self.ks1D1 = self.ks1.insert_new('D1', definition_formal='X1 X2', convention='KS1D1')
|
||||
|
||||
self.ks2 = RSForm.create(
|
||||
alias='KS2',
|
||||
title='Test2',
|
||||
owner=self.user
|
||||
)
|
||||
self.ks2X1 = self.ks2.insert_new('X1', convention='KS2X1')
|
||||
self.ks2X2 = self.ks2.insert_new('X2', convention='KS2X2')
|
||||
self.ks2S1 = self.ks2.insert_new(
|
||||
alias='S1',
|
||||
definition_formal=r'X1',
|
||||
convention='KS2S1'
|
||||
)
|
||||
|
||||
self.ks3 = RSForm.create(
|
||||
alias='KS3',
|
||||
title='Test3',
|
||||
owner=self.user
|
||||
)
|
||||
self.ks3X1 = self.ks3.insert_new('X1', convention='KS3X1')
|
||||
self.ks3D1 = self.ks3.insert_new(
|
||||
alias='D1',
|
||||
definition_formal='X1 X1',
|
||||
convention='KS3D1'
|
||||
)
|
||||
|
||||
self.operation1 = self.owned.create_operation(
|
||||
alias='1',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks1.model
|
||||
)
|
||||
self.operation2 = self.owned.create_operation(
|
||||
alias='2',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks2.model
|
||||
)
|
||||
self.operation3 = self.owned.create_operation(
|
||||
alias='3',
|
||||
operation_type=OperationType.INPUT,
|
||||
result=self.ks3.model
|
||||
)
|
||||
|
||||
self.operation4 = self.owned.create_operation(
|
||||
alias='4',
|
||||
operation_type=OperationType.SYNTHESIS
|
||||
)
|
||||
self.owned.set_arguments(self.operation4, [self.operation1, self.operation2])
|
||||
self.owned.set_substitutions(self.operation4, [{
|
||||
'original': self.ks1X1,
|
||||
'substitution': self.ks2S1
|
||||
}])
|
||||
self.owned.execute_operation(self.operation4)
|
||||
self.operation4.refresh_from_db()
|
||||
self.ks4 = RSForm(self.operation4.result)
|
||||
self.ks4X1 = Constituenta.objects.get(as_child__parent_id=self.ks1X2.pk)
|
||||
self.ks4S1 = Constituenta.objects.get(as_child__parent_id=self.ks2S1.pk)
|
||||
self.ks4D1 = Constituenta.objects.get(as_child__parent_id=self.ks1D1.pk)
|
||||
self.ks4D2 = self.ks4.insert_new(
|
||||
alias='D2',
|
||||
definition_formal=r'X1 X2 X3 S1 D1',
|
||||
convention='KS4D2'
|
||||
)
|
||||
|
||||
self.operation5 = self.owned.create_operation(
|
||||
alias='5',
|
||||
operation_type=OperationType.SYNTHESIS
|
||||
)
|
||||
self.owned.set_arguments(self.operation5, [self.operation4, self.operation3])
|
||||
self.owned.set_substitutions(self.operation5, [{
|
||||
'original': self.ks4X1,
|
||||
'substitution': self.ks3X1
|
||||
}])
|
||||
self.owned.execute_operation(self.operation5)
|
||||
self.operation5.refresh_from_db()
|
||||
self.ks5 = RSForm(self.operation5.result)
|
||||
self.ks5D4 = self.ks5.insert_new(
|
||||
alias='D4',
|
||||
definition_formal=r'X1 X2 X3 S1 D1 D2 D3',
|
||||
convention='KS5D4'
|
||||
)
|
||||
|
||||
def test_oss_setup(self):
|
||||
self.assertEqual(self.ks1.constituents().count(), 3)
|
||||
self.assertEqual(self.ks2.constituents().count(), 3)
|
||||
self.assertEqual(self.ks3.constituents().count(), 2)
|
||||
self.assertEqual(self.ks4.constituents().count(), 6)
|
||||
self.assertEqual(self.ks5.constituents().count(), 8)
|
||||
self.assertEqual(self.ks4D1.definition_formal, 'S1 X1')
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_input_operation(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation2.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.ks4D1.refresh_from_db()
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 1)
|
||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||
self.assertEqual(self.ks5.constituents().count(), 6)
|
||||
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 DEL DEL DEL D1 D2 D3')
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_null(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation2.pk,
|
||||
'input': None
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.ks4D1.refresh_from_db()
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
self.operation2.refresh_from_db()
|
||||
self.assertEqual(self.operation2.result, None)
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 1)
|
||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||
self.assertEqual(self.ks5.constituents().count(), 6)
|
||||
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 DEL DEL DEL D1 D2 D3')
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/set-input', method='patch')
|
||||
def test_set_input_change_schema(self):
|
||||
ks6 = RSForm.create(
|
||||
alias='KS6',
|
||||
title='Test6',
|
||||
owner=self.user
|
||||
)
|
||||
ks6X1 = ks6.insert_new('X1', convention='KS6X1')
|
||||
ks6X2 = ks6.insert_new('X2', convention='KS6X2')
|
||||
ks6D1 = ks6.insert_new('D1', definition_formal='X1 X2', convention='KS6D1')
|
||||
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation2.pk,
|
||||
'input': ks6.model.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
ks4Dks6 = Constituenta.objects.get(as_child__parent_id=ks6D1.pk)
|
||||
self.ks4D1.refresh_from_db()
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
self.operation2.refresh_from_db()
|
||||
self.assertEqual(self.operation2.result, ks6.model)
|
||||
self.assertEqual(self.operation2.alias, ks6.model.alias)
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 1)
|
||||
self.assertEqual(self.ks4.constituents().count(), 7)
|
||||
self.assertEqual(self.ks5.constituents().count(), 9)
|
||||
self.assertEqual(ks4Dks6.definition_formal, r'X5 X6')
|
||||
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 DEL DEL DEL D1')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 DEL DEL DEL D1 D2 D3')
|
||||
|
||||
@decl_endpoint('/api/library/{item}', method='delete')
|
||||
def test_delete_schema(self):
|
||||
self.executeNoContent(item=self.ks1.model.pk)
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, None)
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 0)
|
||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 DEL')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 DEL D3')
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation_and_constituents(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation1.pk,
|
||||
'keep_constituents': False,
|
||||
'delete_schema': True
|
||||
}
|
||||
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 0)
|
||||
self.assertEqual(self.ks4.constituents().count(), 4)
|
||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'DEL X2 X3 S1 DEL')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 DEL D3')
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/delete-operation', method='patch')
|
||||
def test_delete_operation_keep_constituents(self):
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation1.pk,
|
||||
'keep_constituents': True,
|
||||
'delete_schema': True
|
||||
}
|
||||
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 1)
|
||||
self.assertEqual(self.ks4.constituents().count(), 6)
|
||||
self.assertEqual(self.ks5.constituents().count(), 8)
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 D1')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 D2 D3')
|
|
@ -158,3 +158,33 @@ class TestChangeSubstitutions(EndpointTester):
|
|||
self.assertEqual(self.ks4D1.definition_formal, r'X2 X1')
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 X2 D1')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 X2 D1 D2 D3')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch')
|
||||
def test_delete_original(self):
|
||||
data = {'items': [self.ks1X1.pk, self.ks1D1.pk]}
|
||||
self.executeOK(data=data, schema=self.ks1.model.pk)
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 1)
|
||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 DEL')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 DEL D3')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/delete-multiple-cst', method='patch')
|
||||
def test_delete_substitution(self):
|
||||
data = {'items': [self.ks2S1.pk, self.ks2X2.pk]}
|
||||
self.executeOK(data=data, schema=self.ks2.model.pk)
|
||||
self.ks4D1.refresh_from_db()
|
||||
self.ks4D2.refresh_from_db()
|
||||
self.ks5D4.refresh_from_db()
|
||||
subs1_2 = self.operation4.getSubstitutions()
|
||||
self.assertEqual(subs1_2.count(), 0)
|
||||
subs3_4 = self.operation5.getSubstitutions()
|
||||
self.assertEqual(subs3_4.count(), 1)
|
||||
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||
self.assertEqual(self.ks4D1.definition_formal, r'X4 X1')
|
||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 DEL DEL D1')
|
||||
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 DEL DEL D1 D2 D3')
|
||||
|
|
|
@ -201,6 +201,9 @@ class TestOssViewset(EndpointTester):
|
|||
def test_create_operation_result(self):
|
||||
self.populateData()
|
||||
|
||||
self.operation1.result = None
|
||||
self.operation1.save()
|
||||
|
||||
data = {
|
||||
'item_data': {
|
||||
'alias': 'Test4',
|
||||
|
@ -223,11 +226,14 @@ class TestOssViewset(EndpointTester):
|
|||
'alias': 'Test4',
|
||||
'title': 'Test title',
|
||||
'comment': 'Comment',
|
||||
'operation_type': OperationType.INPUT
|
||||
'operation_type': OperationType.INPUT,
|
||||
'result': self.ks1.model.pk
|
||||
},
|
||||
'create_schema': True,
|
||||
'positions': [],
|
||||
}
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
data['item_data']['result'] = None
|
||||
response = self.executeCreated(data=data, item=self.owned_id)
|
||||
self.owned.refresh_from_db()
|
||||
new_operation = response.data['new_operation']
|
||||
|
@ -309,8 +315,6 @@ class TestOssViewset(EndpointTester):
|
|||
|
||||
data['target'] = self.operation1.pk
|
||||
data['input'] = None
|
||||
|
||||
data['target'] = self.operation1.pk
|
||||
self.toggle_admin(True)
|
||||
self.executeBadData(data=data, item=self.unowned_id)
|
||||
self.logout()
|
||||
|
@ -343,9 +347,29 @@ class TestOssViewset(EndpointTester):
|
|||
'target': self.operation1.pk,
|
||||
'input': self.ks2.model.pk
|
||||
}
|
||||
response = self.executeOK(data=data, item=self.owned_id)
|
||||
self.executeBadData(data=data, item=self.owned_id)
|
||||
|
||||
self.ks2.model.visible = False
|
||||
self.ks2.model.save(update_fields=['visible'])
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation2.pk,
|
||||
'input': None
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation2.refresh_from_db()
|
||||
self.assertEqual(self.operation2.result, self.ks2.model)
|
||||
self.ks2.model.refresh_from_db()
|
||||
self.assertEqual(self.operation2.result, None)
|
||||
self.assertEqual(self.ks2.model.visible, True)
|
||||
|
||||
data = {
|
||||
'positions': [],
|
||||
'target': self.operation1.pk,
|
||||
'input': self.ks2.model.pk
|
||||
}
|
||||
self.executeOK(data=data, item=self.owned_id)
|
||||
self.operation1.refresh_from_db()
|
||||
self.assertEqual(self.operation1.result, self.ks2.model)
|
||||
|
||||
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||
def test_update_operation(self):
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
''' Endpoints for OSS. '''
|
||||
from typing import cast
|
||||
from typing import Optional, cast
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
from rest_framework import generics, serializers
|
||||
|
@ -109,6 +110,21 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
new_operation = oss.create_operation(**serializer.validated_data['item_data'])
|
||||
schema = new_operation.result
|
||||
if schema is not None:
|
||||
connected_operations = \
|
||||
m.Operation.objects \
|
||||
.filter(Q(result=schema) & ~Q(pk=new_operation.pk)) \
|
||||
.only('operation_type', 'oss_id')
|
||||
for operation in connected_operations:
|
||||
if operation.operation_type != m.OperationType.INPUT:
|
||||
raise serializers.ValidationError({
|
||||
'item_data': msg.operationResultFromAnotherOSS()
|
||||
})
|
||||
if operation.oss_id == new_operation.oss_id:
|
||||
raise serializers.ValidationError({
|
||||
'item_data': msg.operationInputAlreadyConnected()
|
||||
})
|
||||
if new_operation.operation_type == m.OperationType.INPUT and serializer.validated_data['create_schema']:
|
||||
oss.create_input(new_operation)
|
||||
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
|
||||
|
@ -127,7 +143,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@extend_schema(
|
||||
summary='delete operation',
|
||||
tags=['OSS'],
|
||||
request=s.OperationTargetSerializer,
|
||||
request=s.OperationDeleteSerializer,
|
||||
responses={
|
||||
c.HTTP_200_OK: s.OperationSchemaSerializer,
|
||||
c.HTTP_400_BAD_REQUEST: None,
|
||||
|
@ -138,17 +154,25 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
@action(detail=True, methods=['patch'], url_path='delete-operation')
|
||||
def delete_operation(self, request: Request, pk) -> HttpResponse:
|
||||
''' Endpoint: Delete operation. '''
|
||||
serializer = s.OperationTargetSerializer(
|
||||
serializer = s.OperationDeleteSerializer(
|
||||
data=request.data,
|
||||
context={'oss': self.get_object()}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
old_schema: Optional[LibraryItem] = operation.result
|
||||
with transaction.atomic():
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.delete_operation(serializer.validated_data['target'])
|
||||
|
||||
oss.delete_operation(operation, serializer.validated_data['keep_constituents'])
|
||||
if old_schema is not None:
|
||||
if serializer.validated_data['delete_schema']:
|
||||
m.PropagationFacade.before_delete_schema(old_schema)
|
||||
old_schema.delete()
|
||||
elif old_schema.is_synced(oss.model):
|
||||
old_schema.visible = True
|
||||
old_schema.save(update_fields=['visible'])
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
|
@ -217,11 +241,28 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
|||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
target_operation: m.Operation = cast(m.Operation, serializer.validated_data['target'])
|
||||
schema: Optional[LibraryItem] = serializer.validated_data['input']
|
||||
if schema is not None:
|
||||
connected_operations = m.Operation.objects.filter(result=schema).only('operation_type', 'oss_id')
|
||||
for operation in connected_operations:
|
||||
if operation.operation_type != m.OperationType.INPUT:
|
||||
raise serializers.ValidationError({
|
||||
'input': msg.operationResultFromAnotherOSS()
|
||||
})
|
||||
if operation != target_operation and operation.oss_id == target_operation.oss_id:
|
||||
raise serializers.ValidationError({
|
||||
'input': msg.operationInputAlreadyConnected()
|
||||
})
|
||||
oss = m.OperationSchema(self.get_object())
|
||||
old_schema: Optional[LibraryItem] = target_operation.result
|
||||
with transaction.atomic():
|
||||
if old_schema is not None:
|
||||
if old_schema.is_synced(oss.model):
|
||||
old_schema.visible = True
|
||||
old_schema.save(update_fields=['visible'])
|
||||
oss.update_positions(serializer.validated_data['positions'])
|
||||
oss.set_input(operation, serializer.validated_data['input'])
|
||||
oss.set_input(target_operation.pk, schema)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
data=s.OperationSchemaSerializer(oss.model).data
|
||||
|
|
|
@ -29,71 +29,9 @@ DELETED_ALIAS = 'DEL'
|
|||
class RSForm:
|
||||
''' RSForm is math form of conceptual schema. '''
|
||||
|
||||
class Cache:
|
||||
''' Cache for RSForm constituents. '''
|
||||
|
||||
def __init__(self, schema: 'RSForm'):
|
||||
self._schema = schema
|
||||
self.constituents: list[Constituenta] = []
|
||||
self.by_id: dict[int, Constituenta] = {}
|
||||
self.by_alias: dict[str, Constituenta] = {}
|
||||
self.is_loaded = False
|
||||
|
||||
def reload(self) -> None:
|
||||
self.constituents = list(
|
||||
self._schema.constituents().only(
|
||||
'order',
|
||||
'alias',
|
||||
'cst_type',
|
||||
'definition_formal',
|
||||
'term_raw',
|
||||
'definition_raw'
|
||||
).order_by('order')
|
||||
)
|
||||
self.by_id = {cst.pk: cst for cst in self.constituents}
|
||||
self.by_alias = {cst.alias: cst for cst in self.constituents}
|
||||
self.is_loaded = True
|
||||
|
||||
def ensure_loaded(self) -> None:
|
||||
if not self.is_loaded:
|
||||
self.reload()
|
||||
|
||||
def clear(self) -> None:
|
||||
self.constituents = []
|
||||
self.by_id = {}
|
||||
self.by_alias = {}
|
||||
self.is_loaded = False
|
||||
|
||||
def insert(self, cst: Constituenta) -> None:
|
||||
if self.is_loaded:
|
||||
self.constituents.insert(cst.order - 1, cst)
|
||||
self.by_id[cst.pk] = cst
|
||||
self.by_alias[cst.alias] = cst
|
||||
|
||||
def insert_multi(self, items: Iterable[Constituenta]) -> None:
|
||||
if self.is_loaded:
|
||||
for cst in items:
|
||||
self.constituents.insert(cst.order - 1, cst)
|
||||
self.by_id[cst.pk] = cst
|
||||
self.by_alias[cst.alias] = cst
|
||||
|
||||
def remove(self, target: Constituenta) -> None:
|
||||
if self.is_loaded:
|
||||
self.constituents.remove(self.by_id[target.pk])
|
||||
del self.by_id[target.pk]
|
||||
del self.by_alias[target.alias]
|
||||
|
||||
def remove_multi(self, target: Iterable[Constituenta]) -> None:
|
||||
if self.is_loaded:
|
||||
for cst in target:
|
||||
self.constituents.remove(self.by_id[cst.pk])
|
||||
del self.by_id[cst.pk]
|
||||
del self.by_alias[cst.alias]
|
||||
|
||||
|
||||
def __init__(self, model: LibraryItem):
|
||||
self.model = model
|
||||
self.cache: RSForm.Cache = RSForm.Cache(self)
|
||||
self.cache: RSFormCache = RSFormCache(self)
|
||||
|
||||
@staticmethod
|
||||
def create(**kwargs) -> 'RSForm':
|
||||
|
@ -107,6 +45,18 @@ class RSForm:
|
|||
model = LibraryItem.objects.get(pk=pk)
|
||||
return RSForm(model)
|
||||
|
||||
def get_dependant(self, target: Iterable[int]) -> set[int]:
|
||||
''' Get list of constituents depending on target (only 1st degree). '''
|
||||
result: set[int] = set()
|
||||
terms = self._graph_term()
|
||||
formal = self._graph_formal()
|
||||
definitions = self._graph_text()
|
||||
for cst_id in target:
|
||||
result.update(formal.outputs[cst_id])
|
||||
result.update(terms.outputs[cst_id])
|
||||
result.update(definitions.outputs[cst_id])
|
||||
return result
|
||||
|
||||
def save(self, *args, **kwargs) -> None:
|
||||
''' Model wrapper. '''
|
||||
self.model.save(*args, **kwargs)
|
||||
|
@ -138,7 +88,7 @@ class RSForm:
|
|||
''' Access semantic information on constituents. '''
|
||||
return SemanticInfo(self)
|
||||
|
||||
def on_term_change(self, changed: list[int]) -> None:
|
||||
def after_term_change(self, changed: list[int]) -> None:
|
||||
''' Trigger cascade resolutions when term changes. '''
|
||||
self.cache.ensure_loaded()
|
||||
graph_terms = self._graph_term()
|
||||
|
@ -209,7 +159,7 @@ class RSForm:
|
|||
|
||||
result.save()
|
||||
self.cache.insert(result)
|
||||
self.on_term_change([result.pk])
|
||||
self.after_term_change([result.pk])
|
||||
result.refresh_from_db()
|
||||
return result
|
||||
|
||||
|
@ -239,8 +189,12 @@ class RSForm:
|
|||
self.save(update_fields=['time_update'])
|
||||
return result
|
||||
|
||||
def insert_copy(self, items: list[Constituenta], position: int = INSERT_LAST,
|
||||
initial_mapping: Optional[dict[str, str]] = None) -> list[Constituenta]:
|
||||
def insert_copy(
|
||||
self,
|
||||
items: list[Constituenta],
|
||||
position: int = INSERT_LAST,
|
||||
initial_mapping: Optional[dict[str, str]] = None
|
||||
) -> list[Constituenta]:
|
||||
''' Insert copy of target constituents updating references. '''
|
||||
count = len(items)
|
||||
if count == 0:
|
||||
|
@ -252,10 +206,12 @@ class RSForm:
|
|||
|
||||
indices: dict[str, int] = {}
|
||||
for (value, _) in CstType.choices:
|
||||
indices[value] = self.get_max_index(cast(CstType, value))
|
||||
indices[value] = -1
|
||||
|
||||
mapping: dict[str, str] = initial_mapping.copy() if initial_mapping else {}
|
||||
for cst in items:
|
||||
if indices[cst.cst_type] == -1:
|
||||
indices[cst.cst_type] = self.get_max_index(cst.cst_type)
|
||||
indices[cst.cst_type] = indices[cst.cst_type] + 1
|
||||
newAlias = f'{get_type_prefix(cst.cst_type)}{indices[cst.cst_type]}'
|
||||
mapping[cst.alias] = newAlias
|
||||
|
@ -318,7 +274,7 @@ class RSForm:
|
|||
cst.definition_resolved = resolver.resolve(cst.definition_raw)
|
||||
cst.save()
|
||||
if term_changed:
|
||||
self.on_term_change([cst.pk])
|
||||
self.after_term_change([cst.pk])
|
||||
self.save(update_fields=['time_update'])
|
||||
return old_data
|
||||
|
||||
|
@ -370,7 +326,7 @@ class RSForm:
|
|||
Constituenta.objects.filter(pk__in=[cst.pk for cst in deleted]).delete()
|
||||
self._reset_order()
|
||||
self.apply_mapping(mapping)
|
||||
self.on_term_change([substitution.pk for substitution in replacements])
|
||||
self.after_term_change([substitution.pk for substitution in replacements])
|
||||
|
||||
def restore_order(self) -> None:
|
||||
''' Restore order based on types and term graph. '''
|
||||
|
@ -382,19 +338,6 @@ class RSForm:
|
|||
mapping = self._create_reset_mapping()
|
||||
self.apply_mapping(mapping, change_aliases=True)
|
||||
|
||||
def _create_reset_mapping(self) -> dict[str, str]:
|
||||
bases = cast(dict[str, int], {})
|
||||
mapping = cast(dict[str, str], {})
|
||||
for cst_type in CstType.values:
|
||||
bases[cst_type] = 1
|
||||
cst_list = self.constituents().order_by('order')
|
||||
for cst in cst_list:
|
||||
alias = f'{get_type_prefix(cst.cst_type)}{bases[cst.cst_type]}'
|
||||
bases[cst.cst_type] += 1
|
||||
if cst.alias != alias:
|
||||
mapping[cst.alias] = alias
|
||||
return mapping
|
||||
|
||||
def change_cst_type(self, target: int, new_type: CstType) -> bool:
|
||||
''' Change type of constituenta generating alias automatically. '''
|
||||
self.cache.ensure_loaded()
|
||||
|
@ -419,6 +362,17 @@ class RSForm:
|
|||
Constituenta.objects.bulk_update(update_list, ['alias', 'definition_formal', 'term_raw', 'definition_raw'])
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def apply_partial_mapping(self, mapping: dict[str, str], target: list[int]) -> None:
|
||||
''' Apply rename mapping to target constituents. '''
|
||||
self.cache.ensure_loaded()
|
||||
update_list: list[Constituenta] = []
|
||||
for cst in self.cache.constituents:
|
||||
if cst.pk in target:
|
||||
if cst.apply_mapping(mapping):
|
||||
update_list.append(cst)
|
||||
Constituenta.objects.bulk_update(update_list, ['definition_formal', 'term_raw', 'definition_raw'])
|
||||
self.save(update_fields=['time_update'])
|
||||
|
||||
def resolve_all_text(self) -> None:
|
||||
''' Trigger reference resolution for all texts. '''
|
||||
self.cache.ensure_loaded()
|
||||
|
@ -482,6 +436,19 @@ class RSForm:
|
|||
self.save(update_fields=['time_update'])
|
||||
return result
|
||||
|
||||
def _create_reset_mapping(self) -> dict[str, str]:
|
||||
bases = cast(dict[str, int], {})
|
||||
mapping = cast(dict[str, str], {})
|
||||
for cst_type in CstType.values:
|
||||
bases[cst_type] = 1
|
||||
cst_list = self.constituents().order_by('order')
|
||||
for cst in cst_list:
|
||||
alias = f'{get_type_prefix(cst.cst_type)}{bases[cst.cst_type]}'
|
||||
bases[cst.cst_type] += 1
|
||||
if cst.alias != alias:
|
||||
mapping[cst.alias] = alias
|
||||
return mapping
|
||||
|
||||
def _shift_positions(self, start: int, shift: int) -> None:
|
||||
if shift == 0:
|
||||
return
|
||||
|
@ -561,6 +528,68 @@ class RSForm:
|
|||
return result
|
||||
|
||||
|
||||
class RSFormCache:
|
||||
''' Cache for RSForm constituents. '''
|
||||
|
||||
def __init__(self, schema: 'RSForm'):
|
||||
self._schema = schema
|
||||
self.constituents: list[Constituenta] = []
|
||||
self.by_id: dict[int, Constituenta] = {}
|
||||
self.by_alias: dict[str, Constituenta] = {}
|
||||
self.is_loaded = False
|
||||
|
||||
def reload(self) -> None:
|
||||
self.constituents = list(
|
||||
self._schema.constituents().only(
|
||||
'order',
|
||||
'alias',
|
||||
'cst_type',
|
||||
'definition_formal',
|
||||
'term_raw',
|
||||
'definition_raw'
|
||||
).order_by('order')
|
||||
)
|
||||
self.by_id = {cst.pk: cst for cst in self.constituents}
|
||||
self.by_alias = {cst.alias: cst for cst in self.constituents}
|
||||
self.is_loaded = True
|
||||
|
||||
def ensure_loaded(self) -> None:
|
||||
if not self.is_loaded:
|
||||
self.reload()
|
||||
|
||||
def clear(self) -> None:
|
||||
self.constituents = []
|
||||
self.by_id = {}
|
||||
self.by_alias = {}
|
||||
self.is_loaded = False
|
||||
|
||||
def insert(self, cst: Constituenta) -> None:
|
||||
if self.is_loaded:
|
||||
self.constituents.insert(cst.order - 1, cst)
|
||||
self.by_id[cst.pk] = cst
|
||||
self.by_alias[cst.alias] = cst
|
||||
|
||||
def insert_multi(self, items: Iterable[Constituenta]) -> None:
|
||||
if self.is_loaded:
|
||||
for cst in items:
|
||||
self.constituents.insert(cst.order - 1, cst)
|
||||
self.by_id[cst.pk] = cst
|
||||
self.by_alias[cst.alias] = cst
|
||||
|
||||
def remove(self, target: Constituenta) -> None:
|
||||
if self.is_loaded:
|
||||
self.constituents.remove(self.by_id[target.pk])
|
||||
del self.by_id[target.pk]
|
||||
del self.by_alias[target.alias]
|
||||
|
||||
def remove_multi(self, target: Iterable[Constituenta]) -> None:
|
||||
if self.is_loaded:
|
||||
for cst in target:
|
||||
self.constituents.remove(self.by_id[cst.pk])
|
||||
del self.by_id[cst.pk]
|
||||
del self.by_alias[cst.alias]
|
||||
|
||||
|
||||
class SemanticInfo:
|
||||
''' Semantic information derived from constituents. '''
|
||||
|
||||
|
|
|
@ -388,7 +388,7 @@ class TestRSForm(DBTester):
|
|||
x1.term_resolved = 'слон'
|
||||
x1.save()
|
||||
|
||||
self.schema.on_term_change([x1.pk])
|
||||
self.schema.after_term_change([x1.pk])
|
||||
x1.refresh_from_db()
|
||||
x2.refresh_from_db()
|
||||
x3.refresh_from_db()
|
||||
|
|
|
@ -263,7 +263,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
cst_list: list[m.Constituenta] = serializer.validated_data['items']
|
||||
schema = m.RSForm(model)
|
||||
with transaction.atomic():
|
||||
PropagationFacade.before_delete(cst_list, schema)
|
||||
PropagationFacade.before_delete_cst(cst_list, schema)
|
||||
schema.delete_cst(cst_list)
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
|
@ -594,9 +594,9 @@ def inline_synthesis(request: Request) -> HttpResponse:
|
|||
index = next(i for (i, cst) in enumerate(items) if cst.pk == replacement.pk)
|
||||
replacement = new_items[index]
|
||||
substitutions.append((original, replacement))
|
||||
receiver.substitute(substitutions)
|
||||
|
||||
# TODO: propagate substitutions
|
||||
PropagationFacade.before_substitute(substitutions, receiver)
|
||||
receiver.substitute(substitutions)
|
||||
|
||||
receiver.restore_order()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
tzdata==2024.1
|
||||
Django==5.0.7
|
||||
Django==5.1
|
||||
djangorestframework==3.15.2
|
||||
django-cors-headers==4.4.0
|
||||
django-filter==24.2
|
||||
django-filter==24.3
|
||||
drf-spectacular==0.27.2
|
||||
drf-spectacular-sidecar==2024.7.1
|
||||
coreapi==2.3.3
|
||||
|
@ -11,4 +11,4 @@ cctext==0.1.4
|
|||
pyconcept==0.1.6
|
||||
|
||||
psycopg2-binary==2.9.9
|
||||
gunicorn==22.0.0
|
||||
gunicorn==23.0.0
|
|
@ -30,6 +30,14 @@ def operationNotInput(title: str):
|
|||
return f'Операция не является Загрузкой: {title}'
|
||||
|
||||
|
||||
def operationResultFromAnotherOSS():
|
||||
return 'Схема является результатом другой ОСС'
|
||||
|
||||
|
||||
def operationInputAlreadyConnected():
|
||||
return 'Схема уже подключена к другой операции'
|
||||
|
||||
|
||||
def operationNotSynthesis(title: str):
|
||||
return f'Операция не является Синтезом: {title}'
|
||||
|
||||
|
|
217
rsconcept/frontend/package-lock.json
generated
217
rsconcept/frontend/package-lock.json
generated
|
@ -12,7 +12,7 @@
|
|||
"@tanstack/react-table": "^8.20.1",
|
||||
"@uiw/codemirror-themes": "^4.23.0",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"axios": "^1.7.3",
|
||||
"axios": "^1.7.4",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.3.24",
|
||||
"html-to-image": "^1.11.11",
|
||||
|
@ -20,7 +20,7 @@
|
|||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-intl": "^6.6.8",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-pdf": "^9.1.0",
|
||||
|
@ -53,7 +53,7 @@
|
|||
"tailwindcss": "^3.4.9",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"typescript-eslint": "^8.1.0",
|
||||
"vite": "^5.4.0"
|
||||
}
|
||||
},
|
||||
|
@ -357,6 +357,38 @@
|
|||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-class-static-block": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
|
||||
"integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-import-attributes": {
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz",
|
||||
"integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.24.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-import-meta": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
|
||||
|
@ -477,6 +509,22 @@
|
|||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-private-property-in-object": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
|
||||
"integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-top-level-await": {
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
|
||||
|
@ -700,9 +748,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@codemirror/view": {
|
||||
"version": "6.30.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.30.0.tgz",
|
||||
"integrity": "sha512-96Nmn8OeLh6aONQprIeYk8hGVnEuYpWuxKSkdsODOx9hWPxyuyZGvmvxV/JmLsp+CubMO1PsLaN5TNNgrl0UrQ==",
|
||||
"version": "6.32.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.32.0.tgz",
|
||||
"integrity": "sha512-AgVNvED2QTsZp5e3syoHLsrWtwJFYWdx1Vr/m3f4h1ATQz0ax60CfXF3Htdmk69k2MlYZw8gXesnQdHtzyVmAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@codemirror/state": "^6.4.0",
|
||||
|
@ -3606,9 +3654,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||
"integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
|
||||
"version": "4.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz",
|
||||
"integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/react": "*"
|
||||
|
@ -3671,17 +3719,17 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz",
|
||||
"integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.1.0.tgz",
|
||||
"integrity": "sha512-LlNBaHFCEBPHyD4pZXb35mzjGkuGKXU5eeCA1SxvHfiRES0E82dOounfVpL4DCqYvJEKab0bZIA0gCRpdLKkCw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.0.1",
|
||||
"@typescript-eslint/type-utils": "8.0.1",
|
||||
"@typescript-eslint/utils": "8.0.1",
|
||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
||||
"@typescript-eslint/scope-manager": "8.1.0",
|
||||
"@typescript-eslint/type-utils": "8.1.0",
|
||||
"@typescript-eslint/utils": "8.1.0",
|
||||
"@typescript-eslint/visitor-keys": "8.1.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
|
@ -3705,16 +3753,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz",
|
||||
"integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.1.0.tgz",
|
||||
"integrity": "sha512-U7iTAtGgJk6DPX9wIWPPOlt1gO57097G06gIcl0N0EEnNw8RGD62c+2/DiP/zL7KrkqnnqF7gtFGR7YgzPllTA==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.0.1",
|
||||
"@typescript-eslint/types": "8.0.1",
|
||||
"@typescript-eslint/typescript-estree": "8.0.1",
|
||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
||||
"@typescript-eslint/scope-manager": "8.1.0",
|
||||
"@typescript-eslint/types": "8.1.0",
|
||||
"@typescript-eslint/typescript-estree": "8.1.0",
|
||||
"@typescript-eslint/visitor-keys": "8.1.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -3734,14 +3782,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz",
|
||||
"integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.1.0.tgz",
|
||||
"integrity": "sha512-DsuOZQji687sQUjm4N6c9xABJa7fjvfIdjqpSIIVOgaENf2jFXiM9hIBZOL3hb6DHK9Nvd2d7zZnoMLf9e0OtQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.0.1",
|
||||
"@typescript-eslint/visitor-keys": "8.0.1"
|
||||
"@typescript-eslint/types": "8.1.0",
|
||||
"@typescript-eslint/visitor-keys": "8.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
@ -3752,14 +3800,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz",
|
||||
"integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.1.0.tgz",
|
||||
"integrity": "sha512-oLYvTxljVvsMnldfl6jIKxTaU7ok7km0KDrwOt1RHYu6nxlhN3TIx8k5Q52L6wR33nOwDgM7VwW1fT1qMNfFIA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.0.1",
|
||||
"@typescript-eslint/utils": "8.0.1",
|
||||
"@typescript-eslint/typescript-estree": "8.1.0",
|
||||
"@typescript-eslint/utils": "8.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
|
@ -3777,9 +3825,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz",
|
||||
"integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.1.0.tgz",
|
||||
"integrity": "sha512-q2/Bxa0gMOu/2/AKALI0tCKbG2zppccnRIRCW6BaaTlRVaPKft4oVYPp7WOPpcnsgbr0qROAVCVKCvIQ0tbWog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -3791,14 +3839,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz",
|
||||
"integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.1.0.tgz",
|
||||
"integrity": "sha512-NTHhmufocEkMiAord/g++gWKb0Fr34e9AExBRdqgWdVBaKoei2dIyYKD9Q0jBnvfbEA5zaf8plUFMUH6kQ0vGg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.0.1",
|
||||
"@typescript-eslint/visitor-keys": "8.0.1",
|
||||
"@typescript-eslint/types": "8.1.0",
|
||||
"@typescript-eslint/visitor-keys": "8.1.0",
|
||||
"debug": "^4.3.4",
|
||||
"globby": "^11.1.0",
|
||||
"is-glob": "^4.0.3",
|
||||
|
@ -3820,16 +3868,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz",
|
||||
"integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.1.0.tgz",
|
||||
"integrity": "sha512-ypRueFNKTIFwqPeJBfeIpxZ895PQhNyH4YID6js0UoBImWYoSjBsahUn9KMiJXh94uOjVBgHD9AmkyPsPnFwJA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.0.1",
|
||||
"@typescript-eslint/types": "8.0.1",
|
||||
"@typescript-eslint/typescript-estree": "8.0.1"
|
||||
"@typescript-eslint/scope-manager": "8.1.0",
|
||||
"@typescript-eslint/types": "8.1.0",
|
||||
"@typescript-eslint/typescript-estree": "8.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
@ -3843,13 +3891,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz",
|
||||
"integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.1.0.tgz",
|
||||
"integrity": "sha512-ba0lNI19awqZ5ZNKh6wCModMwoZs457StTebQ0q1NP58zSi2F6MOZRXwfKZy+jB78JNJ/WH8GSh2IQNzXX8Nag==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.0.1",
|
||||
"@typescript-eslint/types": "8.1.0",
|
||||
"eslint-visitor-keys": "^3.4.3"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -4346,9 +4394,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz",
|
||||
"integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==",
|
||||
"version": "1.7.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
|
||||
"integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
|
@ -4547,24 +4595,27 @@
|
|||
}
|
||||
},
|
||||
"node_modules/babel-preset-current-node-syntax": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz",
|
||||
"integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz",
|
||||
"integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/plugin-syntax-async-generators": "^7.8.4",
|
||||
"@babel/plugin-syntax-bigint": "^7.8.3",
|
||||
"@babel/plugin-syntax-class-properties": "^7.8.3",
|
||||
"@babel/plugin-syntax-import-meta": "^7.8.3",
|
||||
"@babel/plugin-syntax-class-properties": "^7.12.13",
|
||||
"@babel/plugin-syntax-class-static-block": "^7.14.5",
|
||||
"@babel/plugin-syntax-import-attributes": "^7.24.7",
|
||||
"@babel/plugin-syntax-import-meta": "^7.10.4",
|
||||
"@babel/plugin-syntax-json-strings": "^7.8.3",
|
||||
"@babel/plugin-syntax-logical-assignment-operators": "^7.8.3",
|
||||
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
|
||||
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
|
||||
"@babel/plugin-syntax-numeric-separator": "^7.8.3",
|
||||
"@babel/plugin-syntax-numeric-separator": "^7.10.4",
|
||||
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
|
||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
|
||||
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
|
||||
"@babel/plugin-syntax-top-level-await": "^7.8.3"
|
||||
"@babel/plugin-syntax-private-property-in-object": "^7.14.5",
|
||||
"@babel/plugin-syntax-top-level-await": "^7.14.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0"
|
||||
|
@ -5634,9 +5685,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/detect-gpu": {
|
||||
"version": "5.0.42",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.42.tgz",
|
||||
"integrity": "sha512-Vdhe87ZNhxIS+OGesy9DOx8P3YBbCBapoomGR9kH26HuDAZ6c0FohsrK47j9efL972kLCaD22EbNUYHVLkqx/w==",
|
||||
"version": "5.0.43",
|
||||
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.43.tgz",
|
||||
"integrity": "sha512-KVcUS/YzsZIBIACz6p2xpuBpAjaY4wiELImJ7M8rb9i16NE6frnVpSV/UBpkK6DYj4Wd3NJeE4sghcaypuM8bg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"webgl-constants": "^1.1.1"
|
||||
|
@ -7401,9 +7452,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ignore": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
|
||||
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -11142,9 +11193,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/postcss-selector-parser": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz",
|
||||
"integrity": "sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg==",
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -11340,9 +11391,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz",
|
||||
"integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
|
||||
"integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
@ -12815,9 +12866,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/three-stdlib": {
|
||||
"version": "2.32.1",
|
||||
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.32.1.tgz",
|
||||
"integrity": "sha512-ZgxxLAwtEaKkvfGP+hkW4s6IaDzif47evTdBPwVvdvLsOul3M6l0D4vO4/fzFguXT6FdoBlaTLhteOcn3uDzPg==",
|
||||
"version": "2.32.2",
|
||||
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.32.2.tgz",
|
||||
"integrity": "sha512-ZN25Na/Xg7APhGKwJ1zhGdhZDsDGGnnm1k5Z+9LLlnfsFye4jigvbN3eA/Ta8hQmBNmEHXoozpmpKK1x8dCePQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/draco3d": "^1.4.0",
|
||||
|
@ -13121,15 +13172,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.1.tgz",
|
||||
"integrity": "sha512-V3Y+MdfhawxEjE16dWpb7/IOgeXnLwAEEkS7v8oDqNcR1oYlqWhGH/iHqHdKVdpWme1VPZ0SoywXAkCqawj2eQ==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.1.0.tgz",
|
||||
"integrity": "sha512-prB2U3jXPJLpo1iVLN338Lvolh6OrcCZO+9Yv6AR+tvegPPptYCDBIHiEEUdqRi8gAv2bXNKfMUrgAd2ejn/ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.0.1",
|
||||
"@typescript-eslint/parser": "8.0.1",
|
||||
"@typescript-eslint/utils": "8.0.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.1.0",
|
||||
"@typescript-eslint/parser": "8.1.0",
|
||||
"@typescript-eslint/utils": "8.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"@tanstack/react-table": "^8.20.1",
|
||||
"@uiw/codemirror-themes": "^4.23.0",
|
||||
"@uiw/react-codemirror": "^4.23.0",
|
||||
"axios": "^1.7.3",
|
||||
"axios": "^1.7.4",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^11.3.24",
|
||||
"html-to-image": "^1.11.11",
|
||||
|
@ -24,7 +24,7 @@
|
|||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"react-icons": "^5.2.1",
|
||||
"react-icons": "^5.3.0",
|
||||
"react-intl": "^6.6.8",
|
||||
"react-loader-spinner": "^6.1.6",
|
||||
"react-pdf": "^9.1.0",
|
||||
|
@ -57,7 +57,7 @@
|
|||
"tailwindcss": "^3.4.9",
|
||||
"ts-jest": "^29.2.4",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"typescript-eslint": "^8.1.0",
|
||||
"vite": "^5.4.0"
|
||||
},
|
||||
"jest": {
|
||||
|
|
|
@ -11,7 +11,7 @@ function ApplicationLayout() {
|
|||
const { viewportHeight, mainHeight, showScroll } = useConceptOptions();
|
||||
return (
|
||||
<NavigationState>
|
||||
<div className='min-w-[20rem] clr-app antialiased'>
|
||||
<div className='min-w-[20rem] clr-app antialiased h-full'>
|
||||
<ConceptToaster
|
||||
className='mt-[4rem] text-sm' // prettier: split lines
|
||||
autoClose={3000}
|
||||
|
@ -29,7 +29,7 @@ function ApplicationLayout() {
|
|||
}}
|
||||
>
|
||||
<main
|
||||
className='w-full cc-scroll-y'
|
||||
className='w-full h-full cc-scroll-y'
|
||||
style={{ overflowY: showScroll ? 'scroll' : 'auto', minHeight: mainHeight }}
|
||||
>
|
||||
<Outlet />
|
||||
|
|
|
@ -6,6 +6,7 @@ import { pdfjs } from 'react-pdf';
|
|||
|
||||
import { AuthState } from '@/context/AuthContext';
|
||||
import { OptionsState } from '@/context/ConceptOptionsContext';
|
||||
import { GlobalOssState } from '@/context/GlobalOssContext';
|
||||
import { LibraryState } from '@/context/LibraryContext';
|
||||
import { UsersState } from '@/context/UsersContext';
|
||||
|
||||
|
@ -37,9 +38,11 @@ function GlobalProviders({ children }: { children: React.ReactNode }) {
|
|||
<UsersState>
|
||||
<AuthState>
|
||||
<LibraryState>
|
||||
<GlobalOssState>
|
||||
|
||||
{children}
|
||||
|
||||
|
||||
</GlobalOssState>
|
||||
</LibraryState>
|
||||
</AuthState>
|
||||
</UsersState>
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IInputCreatedResponse,
|
||||
IOperationCreateData,
|
||||
IOperationCreatedResponse,
|
||||
IOperationDeleteData,
|
||||
IOperationSchemaData,
|
||||
IOperationSetInputData,
|
||||
IOperationUpdateData,
|
||||
|
@ -40,7 +41,7 @@ export function postCreateOperation(
|
|||
});
|
||||
}
|
||||
|
||||
export function patchDeleteOperation(oss: string, request: FrontExchange<ITargetOperation, IOperationSchemaData>) {
|
||||
export function patchDeleteOperation(oss: string, request: FrontExchange<IOperationDeleteData, IOperationSchemaData>) {
|
||||
AxiosPatch({
|
||||
endpoint: `/api/oss/${oss}/delete-operation`,
|
||||
request: request
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useMemo } from 'react';
|
|||
|
||||
import Tooltip from '@/components/ui/Tooltip';
|
||||
import { OssNodeInternal } from '@/models/miscellaneous';
|
||||
import { ICstSubstituteEx } from '@/models/oss';
|
||||
import { ICstSubstituteEx, OperationType } from '@/models/oss';
|
||||
import { labelOperationType } from '@/utils/labels';
|
||||
|
||||
import { IconPageRight } from '../Icons';
|
||||
|
@ -62,11 +62,16 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
|||
);
|
||||
|
||||
return (
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
|
||||
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense'>
|
||||
<h2>{node.data.operation.alias}</h2>
|
||||
<p>
|
||||
<b>Тип:</b> {labelOperationType(node.data.operation.operation_type)}
|
||||
</p>
|
||||
{!node.data.operation.is_owned ? (
|
||||
<p>
|
||||
<b>КС не принадлежит ОСС</b>
|
||||
</p>
|
||||
) : null}
|
||||
{node.data.operation.title ? (
|
||||
<p>
|
||||
<b>Название: </b>
|
||||
|
@ -79,10 +84,13 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
|||
{node.data.operation.comment}
|
||||
</p>
|
||||
) : null}
|
||||
<p>
|
||||
<b>Положение:</b> [{node.xPos}, {node.yPos}]
|
||||
</p>
|
||||
{node.data.operation.substitutions.length > 0 ? table : null}
|
||||
{node.data.operation.substitutions.length > 0 ? (
|
||||
table
|
||||
) : node.data.operation.operation_type !== OperationType.INPUT ? (
|
||||
<p>
|
||||
<b>Отождествления:</b> Отсутствуют
|
||||
</p>
|
||||
) : null}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -45,15 +45,15 @@ function PickMultiOperation({ rows, items, selected, setSelected }: PickMultiOpe
|
|||
columnHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Шифр',
|
||||
size: 150,
|
||||
minSize: 80,
|
||||
maxSize: 150
|
||||
size: 300,
|
||||
minSize: 150,
|
||||
maxSize: 300
|
||||
}),
|
||||
columnHelper.accessor('title', {
|
||||
id: 'title',
|
||||
header: 'Название',
|
||||
size: 1200,
|
||||
minSize: 200,
|
||||
minSize: 300,
|
||||
maxSize: 1200,
|
||||
cell: props => <div className='text-ellipsis'>{props.getValue()}</div>
|
||||
}),
|
||||
|
|
|
@ -84,10 +84,10 @@ function PickSubstitutions({
|
|||
const substitutionData: IMultiSubstitution[] = useMemo(
|
||||
() =>
|
||||
substitutions.map(item => ({
|
||||
original_source: getSchemaByCst(item.original),
|
||||
original: getConstituenta(item.original),
|
||||
substitution: getConstituenta(item.substitution),
|
||||
substitution_source: getSchemaByCst(item.substitution)
|
||||
original_source: getSchemaByCst(item.original)!,
|
||||
original: getConstituenta(item.original)!,
|
||||
substitution: getConstituenta(item.substitution)!,
|
||||
substitution_source: getSchemaByCst(item.substitution)!
|
||||
})),
|
||||
[getConstituenta, getSchemaByCst, substitutions]
|
||||
);
|
||||
|
@ -138,37 +138,31 @@ function PickSubstitutions({
|
|||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
columnHelper.accessor(item => item.substitution_source?.alias ?? 'N/A', {
|
||||
columnHelper.accessor(item => item.substitution_source.alias, {
|
||||
id: 'left_schema',
|
||||
size: 100,
|
||||
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-left'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', {
|
||||
columnHelper.accessor(item => item.substitution.alias, {
|
||||
id: 'left_alias',
|
||||
size: 65,
|
||||
cell: props =>
|
||||
props.row.original.substitution ? (
|
||||
<BadgeConstituenta theme={colors} value={props.row.original.substitution} prefixID={`${prefixID}_1_`} />
|
||||
) : (
|
||||
'N/A'
|
||||
)
|
||||
cell: props => (
|
||||
<BadgeConstituenta theme={colors} value={props.row.original.substitution} prefixID={`${prefixID}_1_`} />
|
||||
)
|
||||
}),
|
||||
columnHelper.display({
|
||||
id: 'status',
|
||||
size: 40,
|
||||
cell: () => <IconPageRight size='1.2rem' />
|
||||
}),
|
||||
columnHelper.accessor(item => item.original?.alias ?? 'N/A', {
|
||||
columnHelper.accessor(item => item.original.alias, {
|
||||
id: 'right_alias',
|
||||
size: 65,
|
||||
cell: props =>
|
||||
props.row.original.original ? (
|
||||
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_1_`} />
|
||||
) : (
|
||||
'N/A'
|
||||
)
|
||||
cell: props => (
|
||||
<BadgeConstituenta theme={colors} value={props.row.original.original} prefixID={`${prefixID}_1_`} />
|
||||
)
|
||||
}),
|
||||
columnHelper.accessor(item => item.original_source?.alias ?? 'N/A', {
|
||||
columnHelper.accessor(item => item.original_source.alias, {
|
||||
id: 'right_schema',
|
||||
size: 100,
|
||||
cell: props => <div className='min-w-[8rem] text-ellipsis text-right'>{props.getValue()}</div>
|
||||
|
|
|
@ -27,7 +27,7 @@ function Checkbox({
|
|||
}: CheckboxProps) {
|
||||
const cursor = useMemo(() => {
|
||||
if (disabled) {
|
||||
return 'cursor-auto';
|
||||
return 'cursor-arrow';
|
||||
} else if (setValue) {
|
||||
return 'cursor-pointer';
|
||||
} else {
|
||||
|
|
|
@ -25,7 +25,7 @@ function CheckboxTristate({
|
|||
}: CheckboxTristateProps) {
|
||||
const cursor = useMemo(() => {
|
||||
if (disabled) {
|
||||
return 'cursor-auto';
|
||||
return 'cursor-arrow';
|
||||
} else if (setValue) {
|
||||
return 'cursor-pointer';
|
||||
} else {
|
||||
|
|
111
rsconcept/frontend/src/context/GlobalOssContext.tsx
Normal file
111
rsconcept/frontend/src/context/GlobalOssContext.tsx
Normal file
|
@ -0,0 +1,111 @@
|
|||
'use client';
|
||||
|
||||
import { createContext, useCallback, useContext, useState } from 'react';
|
||||
|
||||
import { ErrorData } from '@/components/info/InfoError';
|
||||
import useOssDetails from '@/hooks/useOssDetails';
|
||||
import { LibraryItemID } from '@/models/library';
|
||||
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
import { useLibrary } from './LibraryContext';
|
||||
|
||||
interface IGlobalOssContext {
|
||||
schema: IOperationSchema | undefined;
|
||||
setID: (id: string | undefined) => void;
|
||||
setData: (data: IOperationSchemaData) => void;
|
||||
loading: boolean;
|
||||
loadingError: ErrorData;
|
||||
isValid: boolean;
|
||||
|
||||
invalidate: () => void;
|
||||
invalidateItem: (target: LibraryItemID) => void;
|
||||
reload: (callback?: () => void) => void;
|
||||
}
|
||||
|
||||
const GlobalOssContext = createContext<IGlobalOssContext | null>(null);
|
||||
export const useGlobalOss = (): IGlobalOssContext => {
|
||||
const context = useContext(GlobalOssContext);
|
||||
if (context === null) {
|
||||
throw new Error(contextOutsideScope('useGlobalOss', 'GlobalOssState'));
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface GlobalOssStateProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const GlobalOssState = ({ children }: GlobalOssStateProps) => {
|
||||
const library = useLibrary();
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [ossID, setIdInternal] = useState<string | undefined>(undefined);
|
||||
const {
|
||||
schema: schema, // prettier: split lines
|
||||
error: loadingError,
|
||||
setSchema: setDataInternal,
|
||||
loading: loading,
|
||||
reload: reloadInternal
|
||||
} = useOssDetails({ target: ossID, items: library.items });
|
||||
|
||||
const reload = useCallback(
|
||||
(callback?: () => void) => {
|
||||
reloadInternal(undefined, () => {
|
||||
setIsValid(true);
|
||||
if (callback) callback();
|
||||
});
|
||||
},
|
||||
[reloadInternal]
|
||||
);
|
||||
|
||||
const setData = useCallback(
|
||||
(data: IOperationSchemaData) => {
|
||||
setDataInternal(data);
|
||||
setIsValid(true);
|
||||
},
|
||||
[setDataInternal]
|
||||
);
|
||||
|
||||
const setID = useCallback(
|
||||
(id: string | undefined) => {
|
||||
setIdInternal(prev => {
|
||||
if (prev === id && !isValid) {
|
||||
reload();
|
||||
}
|
||||
return id;
|
||||
});
|
||||
},
|
||||
[setIdInternal, isValid, reload]
|
||||
);
|
||||
|
||||
const invalidate = useCallback(() => {
|
||||
setIsValid(false);
|
||||
}, []);
|
||||
|
||||
const invalidateItem = useCallback(
|
||||
(target: LibraryItemID) => {
|
||||
if (schema?.schemas.includes(target)) {
|
||||
setIsValid(false);
|
||||
}
|
||||
},
|
||||
[schema]
|
||||
);
|
||||
|
||||
return (
|
||||
<GlobalOssContext.Provider
|
||||
value={{
|
||||
schema,
|
||||
setID,
|
||||
setData,
|
||||
loading,
|
||||
loadingError,
|
||||
reload,
|
||||
isValid,
|
||||
invalidateItem,
|
||||
invalidate
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</GlobalOssContext.Provider>
|
||||
);
|
||||
};
|
|
@ -13,13 +13,11 @@ import {
|
|||
} from '@/backend/library';
|
||||
import { getRSFormDetails, postRSFormFromFile } from '@/backend/rsforms';
|
||||
import { ErrorData } from '@/components/info/InfoError';
|
||||
import useOssDetails from '@/hooks/useOssDetails';
|
||||
import { FolderTree } from '@/models/FolderTree';
|
||||
import { ILibraryItem, LibraryItemID, LocationHead } from '@/models/library';
|
||||
import { ILibraryCreateData } from '@/models/library';
|
||||
import { matchLibraryItem, matchLibraryItemLocation } from '@/models/libraryAPI';
|
||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
||||
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||
import { IRSForm, IRSFormCloneData, IRSFormData } from '@/models/rsform';
|
||||
import { RSFormLoader } from '@/models/RSFormLoader';
|
||||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
@ -36,17 +34,10 @@ interface ILibraryContext {
|
|||
loadingError: ErrorData;
|
||||
setLoadingError: (error: ErrorData) => void;
|
||||
|
||||
globalOSS: IOperationSchema | undefined;
|
||||
setGlobalID: (id: string | undefined) => void;
|
||||
setGlobalOSS: (data: IOperationSchemaData) => void;
|
||||
ossLoading: boolean;
|
||||
ossError: ErrorData;
|
||||
|
||||
processing: boolean;
|
||||
processingError: ErrorData;
|
||||
setProcessingError: (error: ErrorData) => void;
|
||||
|
||||
reloadOSS: (callback?: () => void) => void;
|
||||
reloadItems: (callback?: () => void) => void;
|
||||
|
||||
applyFilter: (params: ILibraryFilter) => ILibraryItem[];
|
||||
|
@ -84,22 +75,6 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||
const [cachedTemplates, setCachedTemplates] = useState<IRSForm[]>([]);
|
||||
|
||||
const [ossID, setGlobalID] = useState<string | undefined>(undefined);
|
||||
const {
|
||||
schema: globalOSS, // prettier: split lines
|
||||
error: ossError,
|
||||
setSchema: setGlobalOSS,
|
||||
loading: ossLoading,
|
||||
reload: reloadOssInternal
|
||||
} = useOssDetails({ target: ossID });
|
||||
|
||||
const reloadOSS = useCallback(
|
||||
(callback?: () => void) => {
|
||||
reloadOssInternal(setProcessing, callback);
|
||||
},
|
||||
[reloadOssInternal]
|
||||
);
|
||||
|
||||
const folders = useMemo(() => {
|
||||
const result = new FolderTree();
|
||||
result.addPath(LocationHead.USER, 0);
|
||||
|
@ -280,17 +255,11 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
1
|
||||
);
|
||||
}
|
||||
if (globalOSS?.schemas.includes(target)) {
|
||||
reloadOSS(() => {
|
||||
if (callback) callback();
|
||||
});
|
||||
} else {
|
||||
if (callback) callback();
|
||||
}
|
||||
if (callback) callback();
|
||||
})
|
||||
});
|
||||
},
|
||||
[reloadItems, reloadOSS, user, globalOSS]
|
||||
[reloadItems, user]
|
||||
);
|
||||
|
||||
const cloneItem = useCallback(
|
||||
|
@ -326,20 +295,12 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
|||
loading,
|
||||
loadingError,
|
||||
setLoadingError,
|
||||
reloadItems,
|
||||
|
||||
processing,
|
||||
processingError,
|
||||
setProcessingError,
|
||||
|
||||
globalOSS,
|
||||
setGlobalID,
|
||||
setGlobalOSS,
|
||||
ossLoading,
|
||||
ossError,
|
||||
reloadOSS,
|
||||
|
||||
reloadItems,
|
||||
|
||||
applyFilter,
|
||||
createItem,
|
||||
cloneItem,
|
||||
|
|
|
@ -27,6 +27,7 @@ import { ILibraryUpdateData } from '@/models/library';
|
|||
import {
|
||||
IOperationCreateData,
|
||||
IOperationData,
|
||||
IOperationDeleteData,
|
||||
IOperationSchema,
|
||||
IOperationSchemaData,
|
||||
IOperationSetInputData,
|
||||
|
@ -38,6 +39,7 @@ import { UserID } from '@/models/user';
|
|||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
import { useAuth } from './AuthContext';
|
||||
import { useGlobalOss } from './GlobalOssContext';
|
||||
import { useLibrary } from './LibraryContext';
|
||||
|
||||
interface IOssContext {
|
||||
|
@ -45,7 +47,7 @@ interface IOssContext {
|
|||
itemID: string;
|
||||
|
||||
loading: boolean;
|
||||
errorLoading: ErrorData;
|
||||
loadingError: ErrorData;
|
||||
processing: boolean;
|
||||
processingError: ErrorData;
|
||||
|
||||
|
@ -63,7 +65,7 @@ interface IOssContext {
|
|||
|
||||
savePositions: (data: IPositionsData, callback?: () => void) => void;
|
||||
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperationData>) => void;
|
||||
deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
|
||||
deleteOperation: (data: IOperationDeleteData, callback?: () => void) => void;
|
||||
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
|
||||
setInput: (data: IOperationSetInputData, callback?: () => void) => void;
|
||||
updateOperation: (data: IOperationUpdateData, callback?: () => void) => void;
|
||||
|
@ -86,7 +88,8 @@ interface OssStateProps {
|
|||
|
||||
export const OssState = ({ itemID, children }: OssStateProps) => {
|
||||
const library = useLibrary();
|
||||
const schema = library.globalOSS;
|
||||
const oss = useGlobalOss();
|
||||
const model = oss.schema;
|
||||
const { user } = useAuth();
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const [processingError, setProcessingError] = useState<ErrorData>(undefined);
|
||||
|
@ -94,25 +97,23 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
const [toggleTracking, setToggleTracking] = useState(false);
|
||||
|
||||
const isOwned = useMemo(() => {
|
||||
return user?.id === schema?.owner || false;
|
||||
}, [user, schema?.owner]);
|
||||
return user?.id === model?.owner || false;
|
||||
}, [user, model?.owner]);
|
||||
|
||||
const isSubscribed = useMemo(() => {
|
||||
if (!user || !schema || !user.id) {
|
||||
if (!user || !model || !user.id) {
|
||||
return false;
|
||||
}
|
||||
return schema.subscribers.includes(user.id);
|
||||
}, [user, schema, toggleTracking]);
|
||||
return model.subscribers.includes(user.id);
|
||||
}, [user, model, toggleTracking]);
|
||||
|
||||
useEffect(() => {
|
||||
if (schema?.id !== Number(itemID)) {
|
||||
library.setGlobalID(itemID);
|
||||
}
|
||||
}, [itemID, schema, library]);
|
||||
oss.setID(itemID);
|
||||
}, [itemID, oss.setID]);
|
||||
|
||||
const update = useCallback(
|
||||
(data: ILibraryUpdateData, callback?: DataCallback<ILibraryItem>) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -122,19 +123,19 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
const fullData: IOperationSchemaData = Object.assign(schema, newData);
|
||||
library.setGlobalOSS(fullData);
|
||||
const fullData: IOperationSchemaData = Object.assign(model, newData);
|
||||
oss.setData(fullData);
|
||||
library.localUpdateItem(newData);
|
||||
if (callback) callback(newData);
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.localUpdateItem, oss.setData]
|
||||
);
|
||||
|
||||
const subscribe = useCallback(
|
||||
(callback?: () => void) => {
|
||||
if (!schema || !user) {
|
||||
if (!model || !user) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -143,23 +144,23 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
if (user.id && !schema.subscribers.includes(user.id)) {
|
||||
schema.subscribers.push(user.id);
|
||||
if (user.id && !model.subscribers.includes(user.id)) {
|
||||
model.subscribers.push(user.id);
|
||||
}
|
||||
if (!user.subscriptions.includes(schema.id)) {
|
||||
user.subscriptions.push(schema.id);
|
||||
if (!user.subscriptions.includes(model.id)) {
|
||||
user.subscriptions.push(model.id);
|
||||
}
|
||||
setToggleTracking(prev => !prev);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, user, schema]
|
||||
[itemID, user, model]
|
||||
);
|
||||
|
||||
const unsubscribe = useCallback(
|
||||
(callback?: () => void) => {
|
||||
if (!schema || !user) {
|
||||
if (!model || !user) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -168,23 +169,23 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
if (user.id && schema.subscribers.includes(user.id)) {
|
||||
schema.subscribers.splice(schema.subscribers.indexOf(user.id), 1);
|
||||
if (user.id && model.subscribers.includes(user.id)) {
|
||||
model.subscribers.splice(model.subscribers.indexOf(user.id), 1);
|
||||
}
|
||||
if (user.subscriptions.includes(schema.id)) {
|
||||
user.subscriptions.splice(user.subscriptions.indexOf(schema.id), 1);
|
||||
if (user.subscriptions.includes(model.id)) {
|
||||
user.subscriptions.splice(user.subscriptions.indexOf(model.id), 1);
|
||||
}
|
||||
setToggleTracking(prev => !prev);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, user]
|
||||
[itemID, model, user]
|
||||
);
|
||||
|
||||
const setOwner = useCallback(
|
||||
(newOwner: UserID, callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -196,18 +197,18 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
schema.owner = newOwner;
|
||||
library.localUpdateItem(schema);
|
||||
model.owner = newOwner;
|
||||
library.localUpdateItem(model);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const setAccessPolicy = useCallback(
|
||||
(newPolicy: AccessPolicy, callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -219,18 +220,18 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
schema.access_policy = newPolicy;
|
||||
library.localUpdateItem(schema);
|
||||
model.access_policy = newPolicy;
|
||||
library.localUpdateItem(model);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const setLocation = useCallback(
|
||||
(newLocation: string, callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -242,18 +243,18 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
schema.location = newLocation;
|
||||
library.localUpdateItem(schema);
|
||||
model.location = newLocation;
|
||||
library.localUpdateItem(model);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const setEditors = useCallback(
|
||||
(newEditors: UserID[], callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -265,12 +266,12 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: () => {
|
||||
schema.editors = newEditors;
|
||||
model.editors = newEditors;
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema]
|
||||
[itemID, model]
|
||||
);
|
||||
|
||||
const savePositions = useCallback(
|
||||
|
@ -287,7 +288,7 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, library]
|
||||
[itemID, library.localUpdateTimestamp]
|
||||
);
|
||||
|
||||
const createOperation = useCallback(
|
||||
|
@ -299,17 +300,17 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
library.setGlobalOSS(newData.oss);
|
||||
oss.setData(newData.oss);
|
||||
library.localUpdateTimestamp(newData.oss.id);
|
||||
if (callback) callback(newData.new_operation);
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library]
|
||||
[itemID, library.localUpdateTimestamp, oss.setData]
|
||||
);
|
||||
|
||||
const deleteOperation = useCallback(
|
||||
(data: ITargetOperation, callback?: () => void) => {
|
||||
(data: IOperationDeleteData, callback?: () => void) => {
|
||||
setProcessingError(undefined);
|
||||
patchDeleteOperation(itemID, {
|
||||
data: data,
|
||||
|
@ -317,13 +318,13 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
library.setGlobalOSS(newData);
|
||||
oss.setData(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library]
|
||||
[itemID, library.localUpdateTimestamp, oss.setData]
|
||||
);
|
||||
|
||||
const createInput = useCallback(
|
||||
|
@ -335,19 +336,19 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
library.setGlobalOSS(newData.oss);
|
||||
oss.setData(newData.oss);
|
||||
library.reloadItems(() => {
|
||||
if (callback) callback(newData.new_schema);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library]
|
||||
[itemID, library.reloadItems, oss.setData]
|
||||
);
|
||||
|
||||
const setInput = useCallback(
|
||||
(data: IOperationSetInputData, callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -357,18 +358,18 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
library.setGlobalOSS(newData);
|
||||
oss.setData(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.localUpdateTimestamp, oss.setData]
|
||||
);
|
||||
|
||||
const updateOperation = useCallback(
|
||||
(data: IOperationUpdateData, callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -378,19 +379,19 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
library.setGlobalOSS(newData);
|
||||
oss.setData(newData);
|
||||
library.reloadItems(() => {
|
||||
if (callback) callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.reloadItems, oss.setData]
|
||||
);
|
||||
|
||||
const executeOperation = useCallback(
|
||||
(data: ITargetOperation, callback?: () => void) => {
|
||||
if (!schema) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
setProcessingError(undefined);
|
||||
|
@ -400,23 +401,23 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
|
|||
setLoading: setProcessing,
|
||||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
library.setGlobalOSS(newData);
|
||||
oss.setData(newData);
|
||||
library.reloadItems(() => {
|
||||
if (callback) callback();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, model, library.reloadItems, oss.setData]
|
||||
);
|
||||
|
||||
return (
|
||||
<OssContext.Provider
|
||||
value={{
|
||||
schema,
|
||||
schema: model,
|
||||
itemID,
|
||||
loading: library.ossLoading,
|
||||
errorLoading: library.ossError,
|
||||
loading: oss.loading,
|
||||
loadingError: oss.loadingError,
|
||||
processing,
|
||||
processingError,
|
||||
isOwned,
|
||||
|
|
|
@ -53,6 +53,7 @@ import { UserID } from '@/models/user';
|
|||
import { contextOutsideScope } from '@/utils/labels';
|
||||
|
||||
import { useAuth } from './AuthContext';
|
||||
import { useGlobalOss } from './GlobalOssContext';
|
||||
import { useLibrary } from './LibraryContext';
|
||||
|
||||
interface IRSFormContext {
|
||||
|
@ -116,6 +117,7 @@ interface RSFormStateProps {
|
|||
|
||||
export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) => {
|
||||
const library = useLibrary();
|
||||
const oss = useGlobalOss();
|
||||
const { user } = useAuth();
|
||||
const {
|
||||
schema, // prettier: split lines
|
||||
|
@ -159,15 +161,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(Object.assign(schema, newData));
|
||||
library.localUpdateItem(newData);
|
||||
if (library.globalOSS?.schemas.includes(newData.id)) {
|
||||
library.reloadOSS(() => {
|
||||
if (callback) callback(newData);
|
||||
});
|
||||
} else if (callback) callback(newData);
|
||||
oss.invalidateItem(newData.id);
|
||||
if (callback) callback(newData);
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, setSchema, schema, library]
|
||||
[itemID, setSchema, schema, library.localUpdateItem, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const upload = useCallback(
|
||||
|
@ -188,7 +187,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, setSchema, schema, library]
|
||||
[itemID, setSchema, schema, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const subscribe = useCallback(
|
||||
|
@ -261,7 +260,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, schema, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const setAccessPolicy = useCallback(
|
||||
|
@ -284,7 +283,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, schema, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const setLocation = useCallback(
|
||||
|
@ -306,7 +305,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library]
|
||||
[itemID, schema, library.reloadItems]
|
||||
);
|
||||
|
||||
const setEditors = useCallback(
|
||||
|
@ -344,13 +343,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
oss.invalidateItem(newData.id);
|
||||
if (callback) callback();
|
||||
|
||||
// TODO: deal with OSS cache invalidation
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library, user, setSchema]
|
||||
[itemID, schema, user, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const restoreOrder = useCallback(
|
||||
|
@ -370,7 +368,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, schema, library, user, setSchema]
|
||||
[itemID, schema, user, setSchema, library.localUpdateTimestamp]
|
||||
);
|
||||
|
||||
const produceStructure = useCallback(
|
||||
|
@ -384,11 +382,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(newData.schema);
|
||||
library.localUpdateTimestamp(newData.schema.id);
|
||||
oss.invalidateItem(newData.schema.id);
|
||||
if (callback) callback(newData.cst_list);
|
||||
}
|
||||
});
|
||||
},
|
||||
[setSchema, library, itemID]
|
||||
[setSchema, itemID, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const download = useCallback(
|
||||
|
@ -415,13 +414,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(newData.schema);
|
||||
library.localUpdateTimestamp(newData.schema.id);
|
||||
oss.invalidateItem(newData.schema.id);
|
||||
if (callback) callback(newData.new_cst);
|
||||
|
||||
// TODO: deal with OSS cache invalidation
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library, setSchema]
|
||||
[itemID, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const cstDelete = useCallback(
|
||||
|
@ -435,13 +433,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
oss.invalidateItem(newData.id);
|
||||
if (callback) callback();
|
||||
|
||||
// TODO: deal with OSS cache invalidation
|
||||
}
|
||||
});
|
||||
},
|
||||
[itemID, library, setSchema]
|
||||
[itemID, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const cstUpdate = useCallback(
|
||||
|
@ -455,13 +452,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData =>
|
||||
reload(setProcessing, () => {
|
||||
library.localUpdateTimestamp(Number(itemID));
|
||||
oss.invalidateItem(Number(itemID));
|
||||
if (callback) callback(newData);
|
||||
|
||||
// TODO: deal with OSS cache invalidation
|
||||
})
|
||||
});
|
||||
},
|
||||
[itemID, library, reload]
|
||||
[itemID, reload, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const cstRename = useCallback(
|
||||
|
@ -475,15 +471,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(newData.schema);
|
||||
library.localUpdateTimestamp(newData.schema.id);
|
||||
if (library.globalOSS?.schemas.includes(newData.schema.id)) {
|
||||
library.reloadOSS(() => {
|
||||
if (callback) callback(newData.new_cst);
|
||||
});
|
||||
} else if (callback) callback(newData.new_cst);
|
||||
oss.invalidateItem(newData.schema.id);
|
||||
if (callback) callback(newData.new_cst);
|
||||
}
|
||||
});
|
||||
},
|
||||
[setSchema, library, itemID]
|
||||
[setSchema, itemID, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const cstSubstitute = useCallback(
|
||||
|
@ -497,15 +490,12 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
if (library.globalOSS?.schemas.includes(newData.id)) {
|
||||
library.reloadOSS(() => {
|
||||
if (callback) callback();
|
||||
});
|
||||
} else if (callback) callback();
|
||||
oss.invalidateItem(newData.id);
|
||||
if (callback) callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
[setSchema, library, itemID]
|
||||
[setSchema, itemID, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
const cstMoveTo = useCallback(
|
||||
|
@ -523,7 +513,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, library, setSchema]
|
||||
[itemID, setSchema, library.localUpdateTimestamp]
|
||||
);
|
||||
|
||||
const versionCreate = useCallback(
|
||||
|
@ -541,7 +531,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[itemID, library, setSchema]
|
||||
[itemID, setSchema, library.localUpdateTimestamp]
|
||||
);
|
||||
|
||||
const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => {
|
||||
|
@ -612,7 +602,7 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
}
|
||||
});
|
||||
},
|
||||
[setSchema, library]
|
||||
[setSchema, library.localUpdateItem]
|
||||
);
|
||||
|
||||
const inlineSynthesis = useCallback(
|
||||
|
@ -625,14 +615,13 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
|
|||
onError: setProcessingError,
|
||||
onSuccess: newData => {
|
||||
setSchema(newData);
|
||||
library.localUpdateTimestamp(Number(itemID));
|
||||
library.localUpdateTimestamp(newData.id);
|
||||
oss.invalidateItem(newData.id);
|
||||
if (callback) callback(newData);
|
||||
|
||||
// TODO: deal with OSS cache invalidation
|
||||
}
|
||||
});
|
||||
},
|
||||
[library, itemID, setSchema]
|
||||
[itemID, setSchema, library.localUpdateTimestamp, oss.invalidateItem]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -31,10 +31,6 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
|
|||
setSelected(newValue);
|
||||
}, []);
|
||||
|
||||
function handleSubmit() {
|
||||
onSubmit(selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
overflowVisible
|
||||
|
@ -42,7 +38,7 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
|
|||
submitText='Подтвердить выбор'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={() => onSubmit(selected)}
|
||||
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column')}
|
||||
>
|
||||
<div className='flex justify-between gap-3 items-center'>
|
||||
|
|
|
@ -34,10 +34,6 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
|
|||
setBody(newValue.length > 3 ? newValue.substring(3) : '');
|
||||
}, []);
|
||||
|
||||
function handleSubmit() {
|
||||
onChangeLocation(location);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
overflowVisible
|
||||
|
@ -46,7 +42,7 @@ function DlgChangeLocation({ hideWindow, initial, onChangeLocation }: DlgChangeL
|
|||
submitInvalidTooltip={`Допустимы буквы, цифры, подчерк, пробел и "!". Сегмент пути не может начинаться и заканчиваться пробелом. Общая длина (с корнем) не должна превышать ${limits.location_len}`}
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={() => onChangeLocation(location)}
|
||||
className={clsx('w-[35rem]', 'pb-3 px-6 flex gap-3')}
|
||||
>
|
||||
<div className='flex flex-col gap-2 w-[7rem] h-min'>
|
||||
|
|
64
rsconcept/frontend/src/dialogs/DlgDeleteOperation.tsx
Normal file
64
rsconcept/frontend/src/dialogs/DlgDeleteOperation.tsx
Normal file
|
@ -0,0 +1,64 @@
|
|||
'use client';
|
||||
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import TextInput from '@/components/ui/TextInput';
|
||||
import { IOperation } from '@/models/oss';
|
||||
|
||||
interface DlgDeleteOperationProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
target: IOperation;
|
||||
onSubmit: (keepConstituents: boolean, deleteSchema: boolean) => void;
|
||||
}
|
||||
|
||||
function DlgDeleteOperation({ hideWindow, target, onSubmit }: DlgDeleteOperationProps) {
|
||||
const [keepConstituents, setKeepConstituents] = useState(false);
|
||||
const [deleteSchema, setDeleteSchema] = useState(false);
|
||||
|
||||
function handleSubmit() {
|
||||
onSubmit(keepConstituents, deleteSchema);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
overflowVisible
|
||||
header='Удаление операции'
|
||||
submitText='Подтвердить удаление'
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={true}
|
||||
onSubmit={handleSubmit}
|
||||
className={clsx('w-[35rem]', 'pb-3 px-6 cc-column', 'select-none')}
|
||||
>
|
||||
<TextInput
|
||||
disabled
|
||||
dense
|
||||
noBorder
|
||||
id='operation_alias'
|
||||
label='Операция'
|
||||
className='w-full'
|
||||
value={target.alias}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Сохранить наследованные конституенты'
|
||||
titleHtml='Наследованные конституенты <br/>превратятся в дописанные'
|
||||
value={keepConstituents}
|
||||
setValue={setKeepConstituents}
|
||||
/>
|
||||
<Checkbox
|
||||
label='Удалить схему'
|
||||
titleHtml={
|
||||
!target.is_owned || target.result === undefined
|
||||
? 'Привязанную схему нельзя удалить'
|
||||
: 'Удалить схему вместе с операцией'
|
||||
}
|
||||
value={deleteSchema}
|
||||
setValue={setDeleteSchema}
|
||||
disabled={!target.is_owned || target.result === undefined}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgDeleteOperation;
|
|
@ -63,6 +63,25 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
|||
cache.preload(schemasIDs);
|
||||
}, [schemasIDs]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cache.loading || schemas.length !== schemasIDs.length) {
|
||||
return;
|
||||
}
|
||||
setSubstitutions(prev =>
|
||||
prev.filter(sub => {
|
||||
const original = cache.getSchemaByCst(sub.original);
|
||||
if (!original || !schemasIDs.includes(original.id)) {
|
||||
return false;
|
||||
}
|
||||
const substitution = cache.getSchemaByCst(sub.substitution);
|
||||
if (!substitution || !schemasIDs.includes(substitution.id)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
})
|
||||
);
|
||||
}, [schemasIDs, schemas, cache.loading]);
|
||||
|
||||
const handleSubmit = () => {
|
||||
const data: IOperationUpdateData = {
|
||||
target: target.id,
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import PickMultiOperation from '@/components/select/PickMultiOperation';
|
||||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
import Label from '@/components/ui/Label';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { IOperationSchema, OperationID } from '@/models/oss';
|
||||
|
||||
import PickMultiOperation from '../../components/select/PickMultiOperation';
|
||||
|
||||
interface TabArgumentsProps {
|
||||
oss: IOperationSchema;
|
||||
target: OperationID;
|
||||
|
|
|
@ -15,17 +15,12 @@ interface DlgGraphParamsProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
function DlgGraphParams({ hideWindow, initial, onConfirm }: DlgGraphParamsProps) {
|
||||
const [params, updateParams] = usePartialUpdate(initial);
|
||||
|
||||
function handleSubmit() {
|
||||
hideWindow();
|
||||
onConfirm(params);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
canSubmit
|
||||
hideWindow={hideWindow}
|
||||
header='Настройки графа термов'
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={() => onConfirm(params)}
|
||||
submitText='Применить'
|
||||
className='flex gap-6 justify-between px-6 pb-3 w-[30rem]'
|
||||
>
|
||||
|
|
|
@ -26,8 +26,6 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
|||
const [validated, setValidated] = useState(false);
|
||||
const [cstData, updateData] = usePartialUpdate(initial);
|
||||
|
||||
const handleSubmit = () => onRename(cstData);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (schema && initial && cstData.cst_type !== initial.cst_type) {
|
||||
updateData({ alias: generateAlias(cstData.cst_type, schema) });
|
||||
|
@ -47,7 +45,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
|||
submitInvalidTooltip={'Введите незанятое имя, соответствующее типу'}
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={validated}
|
||||
onSubmit={handleSubmit}
|
||||
onSubmit={() => onRename(cstData)}
|
||||
className={clsx('w-[30rem]', 'py-6 pr-3 pl-6 flex justify-center items-center')}
|
||||
>
|
||||
<SelectSingle
|
||||
|
|
|
@ -5,10 +5,11 @@ import { useCallback, useEffect, useState } from 'react';
|
|||
import { getOssDetails } from '@/backend/oss';
|
||||
import { type ErrorData } from '@/components/info/InfoError';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { ILibraryItem } from '@/models/library';
|
||||
import { IOperationSchema, IOperationSchemaData } from '@/models/oss';
|
||||
import { OssLoader } from '@/models/OssLoader';
|
||||
|
||||
function useOssDetails({ target }: { target?: string }) {
|
||||
function useOssDetails({ target, items }: { target?: string; items: ILibraryItem[] }) {
|
||||
const { loading: userLoading } = useAuth();
|
||||
const [schema, setInner] = useState<IOperationSchema | undefined>(undefined);
|
||||
const [loading, setLoading] = useState(target != undefined);
|
||||
|
@ -19,7 +20,7 @@ function useOssDetails({ target }: { target?: string }) {
|
|||
setInner(undefined);
|
||||
return;
|
||||
}
|
||||
const newSchema = new OssLoader(data).produceOSS();
|
||||
const newSchema = new OssLoader(data, items).produceOSS();
|
||||
setInner(newSchema);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,11 +55,11 @@ function useRSFormCache() {
|
|||
|
||||
useEffect(() => {
|
||||
const ids = pending.filter(id => !processing.includes(id) && !cache.find(schema => schema.id === id));
|
||||
setPending([]);
|
||||
if (ids.length === 0) {
|
||||
return;
|
||||
}
|
||||
setProcessing(prev => [...prev, ...ids]);
|
||||
setPending([]);
|
||||
ids.forEach(id =>
|
||||
getRSFormDetails(String(id), '', {
|
||||
showError: false,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { Graph } from './Graph';
|
||||
import { LibraryItemID } from './library';
|
||||
import { ILibraryItem, LibraryItemID } from './library';
|
||||
import {
|
||||
IOperation,
|
||||
IOperationSchema,
|
||||
|
@ -21,10 +21,12 @@ export class OssLoader {
|
|||
private oss: IOperationSchemaData;
|
||||
private graph: Graph = new Graph();
|
||||
private operationByID = new Map<OperationID, IOperation>();
|
||||
private schemas: LibraryItemID[] = [];
|
||||
private schemaIDs: LibraryItemID[] = [];
|
||||
private items: ILibraryItem[];
|
||||
|
||||
constructor(input: IOperationSchemaData) {
|
||||
constructor(input: IOperationSchemaData, items: ILibraryItem[]) {
|
||||
this.oss = input;
|
||||
this.items = items;
|
||||
}
|
||||
|
||||
produceOSS(): IOperationSchema {
|
||||
|
@ -36,7 +38,7 @@ export class OssLoader {
|
|||
|
||||
result.operationByID = this.operationByID;
|
||||
result.graph = this.graph;
|
||||
result.schemas = this.schemas;
|
||||
result.schemas = this.schemaIDs;
|
||||
result.stats = this.calculateStats();
|
||||
return result;
|
||||
}
|
||||
|
@ -53,12 +55,14 @@ export class OssLoader {
|
|||
}
|
||||
|
||||
private extractSchemas() {
|
||||
this.schemas = this.oss.items.map(operation => operation.result).filter(item => item !== null);
|
||||
this.schemaIDs = this.oss.items.map(operation => operation.result).filter(item => item !== null);
|
||||
}
|
||||
|
||||
private inferOperationAttributes() {
|
||||
this.graph.topologicalOrder().forEach(operationID => {
|
||||
const operation = this.operationByID.get(operationID)!;
|
||||
const schema = this.items.find(item => item.id === operation.result);
|
||||
operation.is_owned = !schema || (schema.owner === this.oss.owner && schema.location === this.oss.location);
|
||||
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
||||
operation.arguments = this.oss.arguments
|
||||
.filter(item => item.operation === operationID)
|
||||
|
@ -72,7 +76,7 @@ export class OssLoader {
|
|||
count_operations: items.length,
|
||||
count_inputs: items.filter(item => item.operation_type === OperationType.INPUT).length,
|
||||
count_synthesis: items.filter(item => item.operation_type === OperationType.SYNTHESIS).length,
|
||||
count_schemas: this.schemas.length
|
||||
count_schemas: this.schemaIDs.length
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ export interface IOperation {
|
|||
|
||||
result: LibraryItemID | null;
|
||||
|
||||
is_owned: boolean;
|
||||
substitutions: ICstSubstituteEx[];
|
||||
arguments: OperationID[];
|
||||
}
|
||||
|
@ -85,6 +86,14 @@ export interface IOperationUpdateData extends ITargetOperation {
|
|||
substitutions: ICstSubstitute[] | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} data, used in destruction process.
|
||||
*/
|
||||
export interface IOperationDeleteData extends ITargetOperation {
|
||||
keep_constituents: boolean;
|
||||
delete_schema: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents {@link IOperation} data, used in setInput process.
|
||||
*/
|
||||
|
@ -119,10 +128,10 @@ export interface ICstSubstituteData {
|
|||
* Represents substitution for multi synthesis table.
|
||||
*/
|
||||
export interface IMultiSubstitution {
|
||||
original_source: ILibraryItem | undefined;
|
||||
original: IConstituenta | undefined;
|
||||
substitution: IConstituenta | undefined;
|
||||
substitution_source: ILibraryItem | undefined;
|
||||
original_source: ILibraryItem;
|
||||
original: IConstituenta;
|
||||
substitution: IConstituenta;
|
||||
substitution_source: ILibraryItem;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -33,6 +33,13 @@ function InputNode(node: OssNodeInternal) {
|
|||
disabled={!hasFile}
|
||||
/>
|
||||
</Overlay>
|
||||
|
||||
{!node.data.operation.is_owned ? (
|
||||
<Overlay position='left-[0.2rem] top-[0.1rem]'>
|
||||
<div className='border rounded-none clr-input h-[1.3rem]'></div>
|
||||
</Overlay>
|
||||
) : null}
|
||||
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
|
||||
{node.data.label}
|
||||
{controller.showTooltip && !node.dragging ? (
|
||||
|
|
|
@ -155,7 +155,7 @@ function NodeContextMenu({
|
|||
<DropdownButton
|
||||
text='Удалить операцию'
|
||||
icon={<IconDestroy size='1rem' className='icon-red' />}
|
||||
disabled={!controller.isMutable || controller.isProcessing}
|
||||
disabled={!controller.isMutable || controller.isProcessing || !controller.canDelete(operation.id)}
|
||||
onClick={handleDeleteOperation}
|
||||
/>
|
||||
</Dropdown>
|
||||
|
|
|
@ -33,6 +33,12 @@ function OperationNode(node: OssNodeInternal) {
|
|||
/>
|
||||
</Overlay>
|
||||
|
||||
{!node.data.operation.is_owned ? (
|
||||
<Overlay position='left-[0.2rem] top-[0.1rem]'>
|
||||
<div className='border rounded-none clr-input h-[1.3rem]'></div>
|
||||
</Overlay>
|
||||
) : null}
|
||||
|
||||
<div id={`${prefixes.operation_list}${node.id}`} className='flex-grow text-center'>
|
||||
{node.data.label}
|
||||
{controller.showTooltip && !node.dragging ? (
|
||||
|
|
|
@ -132,8 +132,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
const positions = getPositions();
|
||||
if (positions.length == 0) {
|
||||
target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
|
||||
}
|
||||
if (inputs.length <= 1) {
|
||||
} else if (inputs.length <= 1) {
|
||||
let inputsNodes = positions.filter(pos =>
|
||||
controller.schema!.items.find(
|
||||
operation => operation.operation_type === OperationType.INPUT && operation.id === pos.id
|
||||
|
@ -167,6 +166,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
target.y += PARAMETER.ossMinDistance;
|
||||
}
|
||||
} while (flagIntersect);
|
||||
|
||||
controller.promptCreateOperation({
|
||||
x: target.x,
|
||||
y: target.y,
|
||||
|
@ -182,12 +182,15 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
|||
if (controller.selected.length !== 1) {
|
||||
return;
|
||||
}
|
||||
controller.deleteOperation(controller.selected[0], getPositions());
|
||||
handleDeleteOperation(controller.selected[0]);
|
||||
}, [controller, getPositions]);
|
||||
|
||||
const handleDeleteOperation = useCallback(
|
||||
(target: OperationID) => {
|
||||
controller.deleteOperation(target, getPositions());
|
||||
if (!controller.canDelete(target)) {
|
||||
return;
|
||||
}
|
||||
controller.promptDeleteOperation(target, getPositions());
|
||||
},
|
||||
[controller, getPositions]
|
||||
);
|
||||
|
|
|
@ -175,7 +175,11 @@ function ToolbarOssGraph({
|
|||
<MiniButton
|
||||
titleHtml={prepareTooltip('Удалить выбранную', 'Delete')}
|
||||
icon={<IconDestroy size='1.25rem' className='icon-red' />}
|
||||
disabled={controller.selected.length !== 1 || controller.isProcessing}
|
||||
disabled={
|
||||
controller.selected.length !== 1 ||
|
||||
controller.isProcessing ||
|
||||
!controller.canDelete(controller.selected[0])
|
||||
}
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -13,17 +13,20 @@ import { useOSS } from '@/context/OssContext';
|
|||
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
|
||||
import DlgChangeLocation from '@/dialogs/DlgChangeLocation';
|
||||
import DlgCreateOperation from '@/dialogs/DlgCreateOperation';
|
||||
import DlgDeleteOperation from '@/dialogs/DlgDeleteOperation';
|
||||
import DlgEditEditors from '@/dialogs/DlgEditEditors';
|
||||
import DlgEditOperation from '@/dialogs/DlgEditOperation';
|
||||
import { AccessPolicy, ILibraryItemEditor, LibraryItemID } from '@/models/library';
|
||||
import { Position2D } from '@/models/miscellaneous';
|
||||
import {
|
||||
IOperationCreateData,
|
||||
IOperationDeleteData,
|
||||
IOperationPosition,
|
||||
IOperationSchema,
|
||||
IOperationSetInputData,
|
||||
IOperationUpdateData,
|
||||
OperationID
|
||||
OperationID,
|
||||
OperationType
|
||||
} from '@/models/oss';
|
||||
import { UserID, UserLevel } from '@/models/user';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
@ -62,7 +65,8 @@ export interface IOssEditContext extends ILibraryItemEditor {
|
|||
|
||||
savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
|
||||
promptCreateOperation: (props: ICreateOperationPrompt) => void;
|
||||
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
canDelete: (target: OperationID) => boolean;
|
||||
promptDeleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
createInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
promptEditOperation: (target: OperationID, positions: IOperationPosition[]) => void;
|
||||
|
@ -103,6 +107,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
const [showEditLocation, setShowEditLocation] = useState(false);
|
||||
const [showEditInput, setShowEditInput] = useState(false);
|
||||
const [showEditOperation, setShowEditOperation] = useState(false);
|
||||
const [showDeleteOperation, setShowDeleteOperation] = useState(false);
|
||||
|
||||
const [showCreateOperation, setShowCreateOperation] = useState(false);
|
||||
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
|
||||
|
@ -258,15 +263,48 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
[model, positions]
|
||||
);
|
||||
|
||||
const deleteOperation = useCallback(
|
||||
(target: OperationID, positions: IOperationPosition[]) => {
|
||||
model.deleteOperation({ target: target, positions: positions }, () =>
|
||||
toast.success(information.operationDestroyed)
|
||||
);
|
||||
const canDelete = useCallback(
|
||||
(target: OperationID) => {
|
||||
if (!model.schema) {
|
||||
return false;
|
||||
}
|
||||
const operation = model.schema.operationByID.get(target);
|
||||
if (!operation) {
|
||||
return false;
|
||||
}
|
||||
if (operation.operation_type === OperationType.INPUT) {
|
||||
return true;
|
||||
}
|
||||
return model.schema.graph.expandOutputs([target]).length === 0;
|
||||
},
|
||||
[model]
|
||||
);
|
||||
|
||||
const promptDeleteOperation = useCallback(
|
||||
(target: OperationID, positions: IOperationPosition[]) => {
|
||||
setPositions(positions);
|
||||
setTargetOperationID(target);
|
||||
setShowDeleteOperation(true);
|
||||
},
|
||||
[model]
|
||||
);
|
||||
|
||||
const deleteOperation = useCallback(
|
||||
(keepConstituents: boolean, deleteSchema: boolean) => {
|
||||
if (!targetOperationID) {
|
||||
return;
|
||||
}
|
||||
const data: IOperationDeleteData = {
|
||||
target: targetOperationID,
|
||||
positions: positions,
|
||||
keep_constituents: keepConstituents,
|
||||
delete_schema: deleteSchema
|
||||
};
|
||||
model.deleteOperation(data, () => toast.success(information.operationDestroyed));
|
||||
},
|
||||
[model, targetOperationID, positions]
|
||||
);
|
||||
|
||||
const createInput = useCallback(
|
||||
(target: OperationID, positions: IOperationPosition[]) => {
|
||||
model.createInput({ target: target, positions: positions }, new_schema => {
|
||||
|
@ -334,7 +372,8 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
openOperationSchema,
|
||||
savePositions,
|
||||
promptCreateOperation,
|
||||
deleteOperation,
|
||||
canDelete,
|
||||
promptDeleteOperation,
|
||||
createInput,
|
||||
promptEditInput,
|
||||
promptEditOperation,
|
||||
|
@ -381,6 +420,13 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
|||
onSubmit={handleEditOperation}
|
||||
/>
|
||||
) : null}
|
||||
{showDeleteOperation ? (
|
||||
<DlgDeleteOperation
|
||||
hideWindow={() => setShowDeleteOperation(false)}
|
||||
target={targetOperation!}
|
||||
onSubmit={deleteOperation}
|
||||
/>
|
||||
) : null}
|
||||
</AnimatePresence>
|
||||
) : null}
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ function OssTabs() {
|
|||
const query = useQueryStrings();
|
||||
const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH;
|
||||
|
||||
const { calculateHeight } = useConceptOptions();
|
||||
const { schema, loading, errorLoading } = useOSS();
|
||||
const { calculateHeight, setNoFooter } = useConceptOptions();
|
||||
const { schema, loading, loadingError: errorLoading } = useOSS();
|
||||
const { destroyItem } = useLibrary();
|
||||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
|
@ -53,6 +53,10 @@ function OssTabs() {
|
|||
}
|
||||
}, [schema, schema?.title]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setNoFooter(activeTab === OssTabID.GRAPH);
|
||||
}, [activeTab, setNoFooter]);
|
||||
|
||||
const navigateTab = useCallback(
|
||||
(tab: OssTabID) => {
|
||||
if (!schema) {
|
||||
|
@ -96,7 +100,7 @@ function OssTabs() {
|
|||
});
|
||||
}, [schema, destroyItem, router]);
|
||||
|
||||
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||
const panelHeight = useMemo(() => calculateHeight('1.625rem + 2px'), [calculateHeight]);
|
||||
|
||||
const cardPanel = useMemo(
|
||||
() => (
|
||||
|
|
|
@ -14,6 +14,7 @@ import TabLabel from '@/components/ui/TabLabel';
|
|||
import TextURL from '@/components/ui/TextURL';
|
||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||
import { useGlobalOss } from '@/context/GlobalOssContext';
|
||||
import { useLibrary } from '@/context/LibraryContext';
|
||||
import { useBlockNavigation, useConceptNavigation } from '@/context/NavigationContext';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
|
@ -47,6 +48,7 @@ function RSTabs() {
|
|||
const { setNoFooter, calculateHeight } = useConceptOptions();
|
||||
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
|
||||
const library = useLibrary();
|
||||
const oss = useGlobalOss();
|
||||
|
||||
const [isModified, setIsModified] = useState(false);
|
||||
useBlockNavigation(isModified);
|
||||
|
@ -177,18 +179,19 @@ function RSTabs() {
|
|||
if (!schema || !window.confirm(prompts.deleteLibraryItem)) {
|
||||
return;
|
||||
}
|
||||
const backToOSS = library.globalOSS?.schemas.includes(schema.id);
|
||||
const backToOSS = oss.schema?.schemas.includes(schema.id);
|
||||
library.destroyItem(schema.id, () => {
|
||||
toast.success(information.itemDestroyed);
|
||||
if (backToOSS) {
|
||||
router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH));
|
||||
oss.invalidate();
|
||||
router.push(urls.oss(oss.schema!.id, OssTabID.GRAPH));
|
||||
} else {
|
||||
router.push(urls.library);
|
||||
}
|
||||
});
|
||||
}, [schema, library, router]);
|
||||
}, [schema, library, oss, router]);
|
||||
|
||||
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
|
||||
const panelHeight = useMemo(() => calculateHeight('1.625rem + 2px'), [calculateHeight]);
|
||||
|
||||
const cardPanel = useMemo(
|
||||
() => (
|
||||
|
|
Loading…
Reference in New Issue
Block a user