Compare commits
9 Commits
fbd84ece4d
...
00934d5716
Author | SHA1 | Date | |
---|---|---|---|
![]() |
00934d5716 | ||
![]() |
dee49b789a | ||
![]() |
b82c7315b3 | ||
![]() |
c1c8384024 | ||
![]() |
85c3027d3d | ||
![]() |
259259ec7e | ||
![]() |
a97d1bebb9 | ||
![]() |
60eba81001 | ||
![]() |
3032d90f32 |
|
@ -37,7 +37,6 @@ This readme file is used mostly to document project dependencies and conventions
|
||||||
- react-intl
|
- react-intl
|
||||||
- react-select
|
- react-select
|
||||||
- react-error-boundary
|
- react-error-boundary
|
||||||
- react-pdf
|
|
||||||
- react-tooltip
|
- react-tooltip
|
||||||
- react-zoom-pan-pinch
|
- react-zoom-pan-pinch
|
||||||
- reactflow
|
- reactflow
|
||||||
|
|
|
@ -95,14 +95,15 @@ class OperationSchema:
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def delete_operation(self, target: Operation, keep_constituents: bool = False):
|
def delete_operation(self, target: int, keep_constituents: bool = False):
|
||||||
''' Delete operation. '''
|
''' Delete operation. '''
|
||||||
|
operation = self.cache.operation_by_id[target]
|
||||||
if not keep_constituents:
|
if not keep_constituents:
|
||||||
schema = self.cache.get_schema(target)
|
schema = self.cache.get_schema(operation)
|
||||||
if schema is not None:
|
if schema is not None:
|
||||||
self.before_delete_cst(schema.cache.constituents, schema)
|
self.before_delete_cst(schema, schema.cache.constituents)
|
||||||
self.cache.remove_operation(target.pk)
|
self.cache.remove_operation(target)
|
||||||
target.delete()
|
operation.delete()
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
|
|
||||||
def set_input(self, target: int, schema: Optional[LibraryItem]) -> None:
|
def set_input(self, target: int, schema: Optional[LibraryItem]) -> None:
|
||||||
|
@ -115,7 +116,7 @@ class OperationSchema:
|
||||||
|
|
||||||
if old_schema is not None:
|
if old_schema is not None:
|
||||||
if has_children:
|
if has_children:
|
||||||
self.before_delete_cst(old_schema.cache.constituents, old_schema)
|
self.before_delete_cst(old_schema, old_schema.cache.constituents)
|
||||||
self.cache.remove_schema(old_schema)
|
self.cache.remove_schema(old_schema)
|
||||||
|
|
||||||
operation.result = schema
|
operation.result = schema
|
||||||
|
@ -128,59 +129,76 @@ class OperationSchema:
|
||||||
|
|
||||||
if schema is not None and has_children:
|
if schema is not None and has_children:
|
||||||
rsform = RSForm(schema)
|
rsform = RSForm(schema)
|
||||||
self.after_create_cst(list(rsform.constituents()), rsform)
|
self.after_create_cst(rsform, list(rsform.constituents()))
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
|
|
||||||
def set_arguments(self, operation: Operation, arguments: list[Operation]) -> None:
|
def set_arguments(self, target: int, arguments: list[Operation]) -> None:
|
||||||
''' Set arguments to operation. '''
|
''' Set arguments to operation. '''
|
||||||
|
self.cache.ensure_loaded()
|
||||||
|
operation = self.cache.operation_by_id[target]
|
||||||
processed: list[Operation] = []
|
processed: list[Operation] = []
|
||||||
changed = False
|
deleted: list[Argument] = []
|
||||||
for current in operation.getArguments():
|
for current in operation.getArguments():
|
||||||
if current.argument not in arguments:
|
if current.argument not in arguments:
|
||||||
changed = True
|
deleted.append(current)
|
||||||
current.delete()
|
|
||||||
else:
|
else:
|
||||||
processed.append(current.argument)
|
processed.append(current.argument)
|
||||||
|
if len(deleted) > 0:
|
||||||
|
self.before_delete_arguments(operation, [x.argument for x in deleted])
|
||||||
|
for deleted_arg in deleted:
|
||||||
|
self.cache.remove_argument(deleted_arg)
|
||||||
|
Argument.objects.filter(pk__in=[x.pk for x in deleted]).delete()
|
||||||
|
|
||||||
|
added: list[Operation] = []
|
||||||
for arg in arguments:
|
for arg in arguments:
|
||||||
if arg not in processed:
|
if arg not in processed:
|
||||||
changed = True
|
|
||||||
processed.append(arg)
|
processed.append(arg)
|
||||||
Argument.objects.create(operation=operation, argument=arg)
|
new_arg = Argument.objects.create(operation=operation, argument=arg)
|
||||||
if not changed:
|
self.cache.insert_argument(new_arg)
|
||||||
return
|
added.append(arg)
|
||||||
# TODO: trigger on_change effects
|
if len(added) > 0:
|
||||||
self.save(update_fields=['time_update'])
|
self.after_create_arguments(operation, added)
|
||||||
|
if len(added) > 0 or len(deleted) > 0:
|
||||||
|
self.save(update_fields=['time_update'])
|
||||||
|
|
||||||
def set_substitutions(self, target: Operation, substitutes: list[dict]) -> None:
|
def set_substitutions(self, target: int, substitutes: list[dict]) -> None:
|
||||||
''' Clear all arguments for operation. '''
|
''' Clear all arguments for operation. '''
|
||||||
|
self.cache.ensure_loaded()
|
||||||
|
operation = self.cache.operation_by_id[target]
|
||||||
|
schema = self.cache.get_schema(operation)
|
||||||
processed: list[dict] = []
|
processed: list[dict] = []
|
||||||
changed = False
|
deleted: list[Substitution] = []
|
||||||
|
for current in operation.getSubstitutions():
|
||||||
for current in target.getSubstitutions():
|
|
||||||
subs = [
|
subs = [
|
||||||
x for x in substitutes
|
x for x in substitutes
|
||||||
if x['original'] == current.original and x['substitution'] == current.substitution
|
if x['original'] == current.original and x['substitution'] == current.substitution
|
||||||
]
|
]
|
||||||
if len(subs) == 0:
|
if len(subs) == 0:
|
||||||
changed = True
|
deleted.append(current)
|
||||||
current.delete()
|
|
||||||
else:
|
else:
|
||||||
processed.append(subs[0])
|
processed.append(subs[0])
|
||||||
|
if len(deleted) > 0:
|
||||||
|
if schema is not None:
|
||||||
|
for sub in deleted:
|
||||||
|
self._undo_substitution(schema, sub)
|
||||||
|
else:
|
||||||
|
for sub in deleted:
|
||||||
|
self.cache.remove_substitution(sub)
|
||||||
|
Substitution.objects.filter(pk__in=[x.pk for x in deleted]).delete()
|
||||||
|
|
||||||
for sub in substitutes:
|
added: list[Substitution] = []
|
||||||
if sub not in processed:
|
for sub_item in substitutes:
|
||||||
changed = True
|
if sub_item not in processed:
|
||||||
Substitution.objects.create(
|
new_sub = Substitution.objects.create(
|
||||||
operation=target,
|
operation=operation,
|
||||||
original=sub['original'],
|
original=sub_item['original'],
|
||||||
substitution=sub['substitution']
|
substitution=sub_item['substitution']
|
||||||
)
|
)
|
||||||
|
added.append(new_sub)
|
||||||
|
self._process_added_substitutions(schema, added)
|
||||||
|
|
||||||
if not changed:
|
if len(added) > 0 or len(deleted) > 0:
|
||||||
return
|
self.save(update_fields=['time_update'])
|
||||||
# TODO: trigger on_change effects
|
|
||||||
|
|
||||||
self.save(update_fields=['time_update'])
|
|
||||||
|
|
||||||
def create_input(self, operation: Operation) -> RSForm:
|
def create_input(self, operation: Operation) -> RSForm:
|
||||||
''' Create input RSForm. '''
|
''' Create input RSForm. '''
|
||||||
|
@ -240,9 +258,9 @@ class OperationSchema:
|
||||||
self.save(update_fields=['time_update'])
|
self.save(update_fields=['time_update'])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def after_create_cst(self, cst_list: list[Constituenta], source: RSForm) -> None:
|
def after_create_cst(self, source: RSForm, cst_list: list[Constituenta]) -> None:
|
||||||
''' Trigger cascade resolutions when new constituent is created. '''
|
''' Trigger cascade resolutions when new constituent is created. '''
|
||||||
self.cache.insert(source)
|
self.cache.insert_schema(source)
|
||||||
inserted_aliases = [cst.alias for cst in cst_list]
|
inserted_aliases = [cst.alias for cst in cst_list]
|
||||||
depend_aliases: set[str] = set()
|
depend_aliases: set[str] = set()
|
||||||
for new_cst in cst_list:
|
for new_cst in cst_list:
|
||||||
|
@ -254,17 +272,17 @@ class OperationSchema:
|
||||||
if cst is not None:
|
if cst is not None:
|
||||||
alias_mapping[alias] = cst
|
alias_mapping[alias] = cst
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
operation = self.cache.get_operation(source.model.pk)
|
||||||
self._cascade_create_cst(cst_list, operation, alias_mapping)
|
self._cascade_inherit_cst(operation.pk, source, cst_list, alias_mapping)
|
||||||
|
|
||||||
def after_change_cst_type(self, target: Constituenta, source: RSForm) -> None:
|
def after_change_cst_type(self, source: RSForm, target: Constituenta) -> None:
|
||||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||||
self.cache.insert(source)
|
self.cache.insert_schema(source)
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
operation = self.cache.get_operation(source.model.pk)
|
||||||
self._cascade_change_cst_type(target.pk, target.cst_type, operation.pk)
|
self._cascade_change_cst_type(operation.pk, target.pk, target.cst_type)
|
||||||
|
|
||||||
def after_update_cst(self, target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
def after_update_cst(self, source: RSForm, target: Constituenta, data: dict, old_data: dict) -> None:
|
||||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
''' Trigger cascade resolutions when constituenta data is changed. '''
|
||||||
self.cache.insert(source)
|
self.cache.insert_schema(source)
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
operation = self.cache.get_operation(source.model.pk)
|
||||||
depend_aliases = self._extract_data_references(data, old_data)
|
depend_aliases = self._extract_data_references(data, old_data)
|
||||||
alias_mapping: CstMapping = {}
|
alias_mapping: CstMapping = {}
|
||||||
|
@ -273,56 +291,94 @@ class OperationSchema:
|
||||||
if cst is not None:
|
if cst is not None:
|
||||||
alias_mapping[alias] = cst
|
alias_mapping[alias] = cst
|
||||||
self._cascade_update_cst(
|
self._cascade_update_cst(
|
||||||
cst_id=target.pk,
|
|
||||||
operation=operation.pk,
|
operation=operation.pk,
|
||||||
|
cst_id=target.pk,
|
||||||
data=data,
|
data=data,
|
||||||
old_data=old_data,
|
old_data=old_data,
|
||||||
mapping=alias_mapping
|
mapping=alias_mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
def before_delete_cst(self, target: list[Constituenta], source: RSForm) -> None:
|
def before_delete_cst(self, source: RSForm, target: list[Constituenta]) -> None:
|
||||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||||
self.cache.insert(source)
|
self.cache.insert_schema(source)
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
operation = self.cache.get_operation(source.model.pk)
|
||||||
self._cascade_before_delete(target, operation.pk)
|
self._cascade_delete_inherited(operation.pk, target)
|
||||||
|
|
||||||
def before_substitute(self, substitutions: CstSubstitution, source: RSForm) -> None:
|
def before_substitute(self, source: RSForm, substitutions: CstSubstitution) -> None:
|
||||||
''' Trigger cascade resolutions before constituents are substituted. '''
|
''' Trigger cascade resolutions before constituents are substituted. '''
|
||||||
self.cache.insert(source)
|
self.cache.insert_schema(source)
|
||||||
operation = self.cache.get_operation(source.model.pk)
|
operation = self.cache.get_operation(source.model.pk)
|
||||||
self._cascade_before_substitute(substitutions, operation)
|
self._cascade_before_substitute(substitutions, operation)
|
||||||
|
|
||||||
def _cascade_create_cst(self, cst_list: list[Constituenta], operation: Operation, mapping: CstMapping) -> None:
|
def before_delete_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||||
children = self.cache.graph.outputs[operation.pk]
|
''' Trigger cascade resolutions before arguments are deleted. '''
|
||||||
|
if target.result_id is None:
|
||||||
|
return
|
||||||
|
for argument in arguments:
|
||||||
|
parent_schema = self.cache.get_schema(argument)
|
||||||
|
if parent_schema is not None:
|
||||||
|
self._execute_delete_inherited(target.pk, parent_schema.cache.constituents)
|
||||||
|
|
||||||
|
def after_create_arguments(self, target: Operation, arguments: list[Operation]) -> None:
|
||||||
|
''' Trigger cascade resolutions after arguments are created. '''
|
||||||
|
schema = self.cache.get_schema(target)
|
||||||
|
if schema is None:
|
||||||
|
return
|
||||||
|
for argument in arguments:
|
||||||
|
parent_schema = self.cache.get_schema(argument)
|
||||||
|
if parent_schema is None:
|
||||||
|
continue
|
||||||
|
self._execute_inherit_cst(
|
||||||
|
target_operation=target.pk,
|
||||||
|
source=parent_schema,
|
||||||
|
items=list(parent_schema.constituents()),
|
||||||
|
mapping={}
|
||||||
|
)
|
||||||
|
|
||||||
|
def _cascade_inherit_cst(
|
||||||
|
self,
|
||||||
|
target_operation: int,
|
||||||
|
source: RSForm,
|
||||||
|
items: list[Constituenta],
|
||||||
|
mapping: CstMapping
|
||||||
|
) -> None:
|
||||||
|
children = self.cache.graph.outputs[target_operation]
|
||||||
if len(children) == 0:
|
if len(children) == 0:
|
||||||
return
|
return
|
||||||
source_schema = self.cache.get_schema(operation)
|
|
||||||
assert source_schema is not None
|
|
||||||
for child_id in children:
|
for child_id in children:
|
||||||
child_operation = self.cache.operation_by_id[child_id]
|
self._execute_inherit_cst(child_id, source, items, mapping)
|
||||||
child_schema = self.cache.get_schema(child_operation)
|
|
||||||
if child_schema is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# TODO: update substitutions for diamond synthesis (if needed)
|
def _execute_inherit_cst(
|
||||||
|
self,
|
||||||
|
target_operation: int,
|
||||||
|
source: RSForm,
|
||||||
|
items: list[Constituenta],
|
||||||
|
mapping: CstMapping
|
||||||
|
) -> None:
|
||||||
|
operation = self.cache.operation_by_id[target_operation]
|
||||||
|
destination = self.cache.get_schema(operation)
|
||||||
|
if destination is None:
|
||||||
|
return
|
||||||
|
|
||||||
self.cache.ensure_loaded()
|
# TODO: update substitutions for diamond synthesis (if needed)
|
||||||
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:
|
self.cache.ensure_loaded()
|
||||||
children = self.cache.graph.outputs[operation]
|
new_mapping = self._transform_mapping(mapping, operation, destination)
|
||||||
|
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
|
||||||
|
insert_where = self._determine_insert_position(items[0], operation, source, destination)
|
||||||
|
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
|
||||||
|
for index, cst in enumerate(new_cst_list):
|
||||||
|
new_inheritance = Inheritance.objects.create(
|
||||||
|
operation=operation,
|
||||||
|
child=cst,
|
||||||
|
parent=items[index]
|
||||||
|
)
|
||||||
|
self.cache.insert_inheritance(new_inheritance)
|
||||||
|
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||||
|
self._cascade_inherit_cst(operation.pk, destination, new_cst_list, new_mapping)
|
||||||
|
|
||||||
|
def _cascade_change_cst_type(self, operation_id: int, cst_id: int, ctype: CstType) -> None:
|
||||||
|
children = self.cache.graph.outputs[operation_id]
|
||||||
if len(children) == 0:
|
if len(children) == 0:
|
||||||
return
|
return
|
||||||
self.cache.ensure_loaded()
|
self.cache.ensure_loaded()
|
||||||
|
@ -332,13 +388,16 @@ class OperationSchema:
|
||||||
if successor_id is None:
|
if successor_id is None:
|
||||||
continue
|
continue
|
||||||
child_schema = self.cache.get_schema(child_operation)
|
child_schema = self.cache.get_schema(child_operation)
|
||||||
if child_schema is not None and child_schema.change_cst_type(successor_id, ctype):
|
if child_schema is None:
|
||||||
self._cascade_change_cst_type(successor_id, ctype, child_id)
|
continue
|
||||||
|
if child_schema.change_cst_type(successor_id, ctype):
|
||||||
|
self._cascade_change_cst_type(child_id, successor_id, ctype)
|
||||||
|
|
||||||
# pylint: disable=too-many-arguments
|
# pylint: disable=too-many-arguments
|
||||||
def _cascade_update_cst(
|
def _cascade_update_cst(
|
||||||
self,
|
self,
|
||||||
cst_id: int, operation: int,
|
operation: int,
|
||||||
|
cst_id: int,
|
||||||
data: dict, old_data: dict,
|
data: dict, old_data: dict,
|
||||||
mapping: CstMapping
|
mapping: CstMapping
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -366,30 +425,33 @@ class OperationSchema:
|
||||||
continue
|
continue
|
||||||
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
new_mapping = {alias_mapping[alias]: cst for alias, cst in new_mapping.items()}
|
||||||
self._cascade_update_cst(
|
self._cascade_update_cst(
|
||||||
cst_id=successor_id,
|
|
||||||
operation=child_id,
|
operation=child_id,
|
||||||
|
cst_id=successor_id,
|
||||||
data=new_data,
|
data=new_data,
|
||||||
old_data=new_old_data,
|
old_data=new_old_data,
|
||||||
mapping=new_mapping
|
mapping=new_mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
def _cascade_before_delete(self, target: list[Constituenta], operation: int) -> None:
|
def _cascade_delete_inherited(self, operation: int, target: list[Constituenta]) -> None:
|
||||||
children = self.cache.graph.outputs[operation]
|
children = self.cache.graph.outputs[operation]
|
||||||
if len(children) == 0:
|
if len(children) == 0:
|
||||||
return
|
return
|
||||||
self.cache.ensure_loaded()
|
self.cache.ensure_loaded()
|
||||||
for child_id in children:
|
for child_id in children:
|
||||||
child_operation = self.cache.operation_by_id[child_id]
|
self._execute_delete_inherited(child_id, target)
|
||||||
child_schema = self.cache.get_schema(child_operation)
|
|
||||||
if child_schema is None:
|
def _execute_delete_inherited(self, operation_id: int, parent_cst: list[Constituenta]) -> None:
|
||||||
continue
|
operation = self.cache.operation_by_id[operation_id]
|
||||||
self._undo_substitutions_cst(target, child_operation, child_schema)
|
schema = self.cache.get_schema(operation)
|
||||||
child_target_ids = self.cache.get_inheritors_list([cst.pk for cst in target], child_id)
|
if schema is None:
|
||||||
child_target_cst = [child_schema.cache.by_id[cst_id] for cst_id in child_target_ids]
|
return
|
||||||
self._cascade_before_delete(child_target_cst, child_id)
|
self._undo_substitutions_cst(parent_cst, operation, schema)
|
||||||
if len(child_target_cst) > 0:
|
target_ids = self.cache.get_inheritors_list([cst.pk for cst in parent_cst], operation_id)
|
||||||
self.cache.remove_cst(child_target_ids, child_id)
|
target_cst = [schema.cache.by_id[cst_id] for cst_id in target_ids]
|
||||||
child_schema.delete_cst(child_target_cst)
|
self._cascade_delete_inherited(operation_id, target_cst)
|
||||||
|
if len(target_cst) > 0:
|
||||||
|
self.cache.remove_cst(operation_id, target_ids)
|
||||||
|
schema.delete_cst(target_cst)
|
||||||
|
|
||||||
def _cascade_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None:
|
def _cascade_before_substitute(self, substitutions: CstSubstitution, operation: Operation) -> None:
|
||||||
children = self.cache.graph.outputs[operation.pk]
|
children = self.cache.graph.outputs[operation.pk]
|
||||||
|
@ -411,12 +473,12 @@ class OperationSchema:
|
||||||
self,
|
self,
|
||||||
mapping: CstMapping,
|
mapping: CstMapping,
|
||||||
target: list[int],
|
target: list[int],
|
||||||
operation: Operation,
|
operation: int,
|
||||||
schema: RSForm
|
schema: RSForm
|
||||||
) -> None:
|
) -> None:
|
||||||
alias_mapping = OperationSchema._produce_alias_mapping(mapping)
|
alias_mapping = OperationSchema._produce_alias_mapping(mapping)
|
||||||
schema.apply_partial_mapping(alias_mapping, target)
|
schema.apply_partial_mapping(alias_mapping, target)
|
||||||
children = self.cache.graph.outputs[operation.pk]
|
children = self.cache.graph.outputs[operation]
|
||||||
if len(children) == 0:
|
if len(children) == 0:
|
||||||
return
|
return
|
||||||
self.cache.ensure_loaded()
|
self.cache.ensure_loaded()
|
||||||
|
@ -431,7 +493,7 @@ class OperationSchema:
|
||||||
new_target = self.cache.get_inheritors_list(target, child_id)
|
new_target = self.cache.get_inheritors_list(target, child_id)
|
||||||
if len(new_target) == 0:
|
if len(new_target) == 0:
|
||||||
continue
|
continue
|
||||||
self._cascade_partial_mapping(new_mapping, new_target, child_operation, child_schema)
|
self._cascade_partial_mapping(new_mapping, new_target, child_id, child_schema)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _produce_alias_mapping(mapping: CstMapping) -> dict[str, str]:
|
def _produce_alias_mapping(mapping: CstMapping) -> dict[str, str]:
|
||||||
|
@ -555,37 +617,66 @@ class OperationSchema:
|
||||||
if sub.original_id in target_ids or sub.substitution_id in target_ids:
|
if sub.original_id in target_ids or sub.substitution_id in target_ids:
|
||||||
to_process.append(sub)
|
to_process.append(sub)
|
||||||
for sub in to_process:
|
for sub in to_process:
|
||||||
self._undo_substitution(sub, schema, target_ids)
|
self._undo_substitution(schema, sub, target_ids)
|
||||||
|
|
||||||
def _undo_substitution(self, target: Substitution, schema: RSForm, ignore_parents: list[int]) -> None:
|
def _undo_substitution(
|
||||||
operation = self.cache.operation_by_id[target.operation_id]
|
self,
|
||||||
|
schema: RSForm,
|
||||||
|
target: Substitution,
|
||||||
|
ignore_parents: Optional[list[int]] = None
|
||||||
|
) -> None:
|
||||||
|
if ignore_parents is None:
|
||||||
|
ignore_parents = []
|
||||||
|
operation_id = target.operation_id
|
||||||
original_schema, _, original_cst, substitution_cst = self.cache.unfold_sub(target)
|
original_schema, _, original_cst, substitution_cst = self.cache.unfold_sub(target)
|
||||||
|
|
||||||
dependant = []
|
dependant = []
|
||||||
for cst_id in original_schema.get_dependant([original_cst.pk]):
|
for cst_id in original_schema.get_dependant([original_cst.pk]):
|
||||||
if cst_id not in ignore_parents:
|
if cst_id not in ignore_parents:
|
||||||
inheritor_id = self.cache.get_inheritor(cst_id, operation.pk)
|
inheritor_id = self.cache.get_inheritor(cst_id, operation_id)
|
||||||
if inheritor_id is not None:
|
if inheritor_id is not None:
|
||||||
dependant.append(inheritor_id)
|
dependant.append(inheritor_id)
|
||||||
|
|
||||||
self.cache.substitutions[operation.pk].remove(target)
|
self.cache.substitutions[operation_id].remove(target)
|
||||||
target.delete()
|
target.delete()
|
||||||
|
|
||||||
new_original: Optional[Constituenta] = None
|
new_original: Optional[Constituenta] = None
|
||||||
if original_cst.pk not in ignore_parents:
|
if original_cst.pk not in ignore_parents:
|
||||||
full_cst = Constituenta.objects.get(pk=original_cst.pk)
|
full_cst = Constituenta.objects.get(pk=original_cst.pk)
|
||||||
self.after_create_cst([full_cst], original_schema)
|
self.after_create_cst(original_schema, [full_cst])
|
||||||
new_original_id = self.cache.get_inheritor(original_cst.pk, operation.pk)
|
new_original_id = self.cache.get_inheritor(original_cst.pk, operation_id)
|
||||||
assert new_original_id is not None
|
assert new_original_id is not None
|
||||||
new_original = schema.cache.by_id[new_original_id]
|
new_original = schema.cache.by_id[new_original_id]
|
||||||
if len(dependant) == 0:
|
if len(dependant) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation.pk)
|
substitution_id = self.cache.get_inheritor(substitution_cst.pk, operation_id)
|
||||||
assert substitution_id is not None
|
assert substitution_id is not None
|
||||||
substitution_inheritor = schema.cache.by_id[substitution_id]
|
substitution_inheritor = schema.cache.by_id[substitution_id]
|
||||||
mapping = {cast(str, substitution_inheritor.alias): new_original}
|
mapping = {cast(str, substitution_inheritor.alias): new_original}
|
||||||
self._cascade_partial_mapping(mapping, dependant, operation, schema)
|
self._cascade_partial_mapping(mapping, dependant, operation_id, schema)
|
||||||
|
|
||||||
|
def _process_added_substitutions(self, schema: Optional[RSForm], added: list[Substitution]) -> None:
|
||||||
|
if len(added) == 0:
|
||||||
|
return
|
||||||
|
if schema is None:
|
||||||
|
for sub in added:
|
||||||
|
self.cache.insert_substitution(sub)
|
||||||
|
return
|
||||||
|
|
||||||
|
cst_mapping: CstSubstitution = []
|
||||||
|
for sub in added:
|
||||||
|
original_id = self.cache.get_inheritor(sub.original_id, sub.operation_id)
|
||||||
|
substitution_id = self.cache.get_inheritor(sub.substitution_id, sub.operation_id)
|
||||||
|
if original_id is None or substitution_id is None:
|
||||||
|
raise ValueError('Substitutions not found.')
|
||||||
|
original_cst = schema.cache.by_id[original_id]
|
||||||
|
substitution_cst = schema.cache.by_id[substitution_id]
|
||||||
|
cst_mapping.append((original_cst, substitution_cst))
|
||||||
|
self.before_substitute(schema, cst_mapping)
|
||||||
|
schema.substitute(cst_mapping)
|
||||||
|
for sub in added:
|
||||||
|
self.cache.insert_substitution(sub)
|
||||||
|
|
||||||
|
|
||||||
class OssCache:
|
class OssCache:
|
||||||
|
@ -608,11 +699,18 @@ class OssCache:
|
||||||
self.substitutions: dict[int, list[Substitution]] = {}
|
self.substitutions: dict[int, list[Substitution]] = {}
|
||||||
self.inheritance: dict[int, list[Inheritance]] = {}
|
self.inheritance: dict[int, list[Inheritance]] = {}
|
||||||
|
|
||||||
def insert(self, schema: RSForm) -> None:
|
def ensure_loaded(self) -> None:
|
||||||
''' Insert new schema. '''
|
''' Ensure cache is fully loaded. '''
|
||||||
if not self._schema_by_id.get(schema.model.pk):
|
if self.is_loaded:
|
||||||
schema.cache.ensure_loaded()
|
return
|
||||||
self._insert_new(schema)
|
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_schema(self, operation: Operation) -> Optional[RSForm]:
|
def get_schema(self, operation: Operation) -> Optional[RSForm]:
|
||||||
''' Get schema by Operation. '''
|
''' Get schema by Operation. '''
|
||||||
|
@ -633,19 +731,6 @@ class OssCache:
|
||||||
return operation
|
return operation
|
||||||
raise ValueError(f'Operation for schema {schema} not found')
|
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]:
|
def get_inheritor(self, parent_cst: int, operation: int) -> Optional[int]:
|
||||||
''' Get child for parent inside target RSFrom. '''
|
''' Get child for parent inside target RSFrom. '''
|
||||||
for item in self.inheritance[operation]:
|
for item in self.inheritance[operation]:
|
||||||
|
@ -668,6 +753,12 @@ class OssCache:
|
||||||
return self.get_inheritor(sub.substitution_id, operation)
|
return self.get_inheritor(sub.substitution_id, operation)
|
||||||
return self.get_inheritor(parent_cst, operation)
|
return self.get_inheritor(parent_cst, operation)
|
||||||
|
|
||||||
|
def insert_schema(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 insert_operation(self, operation: Operation) -> None:
|
def insert_operation(self, operation: Operation) -> None:
|
||||||
''' Insert new operation. '''
|
''' Insert new operation. '''
|
||||||
self.operations.append(operation)
|
self.operations.append(operation)
|
||||||
|
@ -677,6 +768,10 @@ class OssCache:
|
||||||
self.substitutions[operation.pk] = []
|
self.substitutions[operation.pk] = []
|
||||||
self.inheritance[operation.pk] = []
|
self.inheritance[operation.pk] = []
|
||||||
|
|
||||||
|
def insert_argument(self, argument: Argument) -> None:
|
||||||
|
''' Insert new argument. '''
|
||||||
|
self.graph.add_edge(argument.operation_id, argument.argument_id)
|
||||||
|
|
||||||
def insert_inheritance(self, inheritance: Inheritance) -> None:
|
def insert_inheritance(self, inheritance: Inheritance) -> None:
|
||||||
''' Insert new inheritance. '''
|
''' Insert new inheritance. '''
|
||||||
self.inheritance[inheritance.operation_id].append(inheritance)
|
self.inheritance[inheritance.operation_id].append(inheritance)
|
||||||
|
@ -685,7 +780,7 @@ class OssCache:
|
||||||
''' Insert new substitution. '''
|
''' Insert new substitution. '''
|
||||||
self.substitutions[sub.operation_id].append(sub)
|
self.substitutions[sub.operation_id].append(sub)
|
||||||
|
|
||||||
def remove_cst(self, target: list[int], operation: int) -> None:
|
def remove_cst(self, operation: int, target: list[int]) -> None:
|
||||||
''' Remove constituents from operation. '''
|
''' Remove constituents from operation. '''
|
||||||
subs_to_delete = [
|
subs_to_delete = [
|
||||||
sub for sub in self.substitutions[operation]
|
sub for sub in self.substitutions[operation]
|
||||||
|
@ -697,9 +792,15 @@ class OssCache:
|
||||||
for item in inherit_to_delete:
|
for item in inherit_to_delete:
|
||||||
self.inheritance[operation].remove(item)
|
self.inheritance[operation].remove(item)
|
||||||
|
|
||||||
|
def remove_schema(self, schema: RSForm) -> None:
|
||||||
|
''' Remove schema from cache. '''
|
||||||
|
self._schemas.remove(schema)
|
||||||
|
del self._schema_by_id[schema.model.pk]
|
||||||
|
|
||||||
def remove_operation(self, operation: int) -> None:
|
def remove_operation(self, operation: int) -> None:
|
||||||
''' Remove operation from cache. '''
|
''' Remove operation from cache. '''
|
||||||
target = self.operation_by_id[operation]
|
target = self.operation_by_id[operation]
|
||||||
|
self.graph.remove_node(operation)
|
||||||
if target.result_id in self._schema_by_id:
|
if target.result_id in self._schema_by_id:
|
||||||
self._schemas.remove(self._schema_by_id[target.result_id])
|
self._schemas.remove(self._schema_by_id[target.result_id])
|
||||||
del self._schema_by_id[target.result_id]
|
del self._schema_by_id[target.result_id]
|
||||||
|
@ -709,10 +810,13 @@ class OssCache:
|
||||||
del self.substitutions[operation]
|
del self.substitutions[operation]
|
||||||
del self.inheritance[operation]
|
del self.inheritance[operation]
|
||||||
|
|
||||||
def remove_schema(self, schema: RSForm) -> None:
|
def remove_argument(self, argument: Argument) -> None:
|
||||||
''' Remove schema from cache. '''
|
''' Remove argument from cache. '''
|
||||||
self._schemas.remove(schema)
|
self.graph.remove_edge(argument.operation_id, argument.argument_id)
|
||||||
del self._schema_by_id[schema.model.pk]
|
|
||||||
|
def remove_substitution(self, target: Substitution) -> None:
|
||||||
|
''' Remove substitution from cache. '''
|
||||||
|
self.substitutions[target.operation_id].remove(target)
|
||||||
|
|
||||||
def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]:
|
def unfold_sub(self, sub: Substitution) -> tuple[RSForm, RSForm, Constituenta, Constituenta]:
|
||||||
operation = self.operation_by_id[sub.operation_id]
|
operation = self.operation_by_id[sub.operation_id]
|
||||||
|
|
|
@ -14,39 +14,39 @@ class PropagationFacade:
|
||||||
''' Change propagation API. '''
|
''' Change propagation API. '''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def after_create_cst(new_cst: list[Constituenta], source: RSForm) -> None:
|
def after_create_cst(source: RSForm, new_cst: list[Constituenta]) -> None:
|
||||||
''' Trigger cascade resolutions when new constituent is created. '''
|
''' Trigger cascade resolutions when new constituent is created. '''
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).after_create_cst(new_cst, source)
|
OperationSchema(host).after_create_cst(source, new_cst)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def after_change_cst_type(target: Constituenta, source: RSForm) -> None:
|
def after_change_cst_type(source: RSForm, target: Constituenta) -> None:
|
||||||
''' Trigger cascade resolutions when constituenta type is changed. '''
|
''' Trigger cascade resolutions when constituenta type is changed. '''
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).after_change_cst_type(target, source)
|
OperationSchema(host).after_change_cst_type(source, target)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def after_update_cst(target: Constituenta, data: dict, old_data: dict, source: RSForm) -> None:
|
def after_update_cst(source: RSForm, target: Constituenta, data: dict, old_data: dict) -> None:
|
||||||
''' Trigger cascade resolutions when constituenta data is changed. '''
|
''' Trigger cascade resolutions when constituenta data is changed. '''
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).after_update_cst(target, data, old_data, source)
|
OperationSchema(host).after_update_cst(source, target, data, old_data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def before_delete_cst(target: list[Constituenta], source: RSForm) -> None:
|
def before_delete_cst(source: RSForm, target: list[Constituenta]) -> None:
|
||||||
''' Trigger cascade resolutions before constituents are deleted. '''
|
''' Trigger cascade resolutions before constituents are deleted. '''
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).before_delete_cst(target, source)
|
OperationSchema(host).before_delete_cst(source, target)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def before_substitute(substitutions: CstSubstitution, source: RSForm) -> None:
|
def before_substitute(source: RSForm, substitutions: CstSubstitution) -> None:
|
||||||
''' Trigger cascade resolutions before constituents are substituted. '''
|
''' Trigger cascade resolutions before constituents are substituted. '''
|
||||||
hosts = _get_oss_hosts(source.model)
|
hosts = _get_oss_hosts(source.model)
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
OperationSchema(host).before_substitute(substitutions, source)
|
OperationSchema(host).before_substitute(source, substitutions)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def before_delete_schema(item: LibraryItem) -> None:
|
def before_delete_schema(item: LibraryItem) -> None:
|
||||||
|
@ -58,4 +58,4 @@ class PropagationFacade:
|
||||||
return
|
return
|
||||||
|
|
||||||
schema = RSForm(item)
|
schema = RSForm(item)
|
||||||
PropagationFacade.before_delete_cst(list(schema.constituents()), schema)
|
PropagationFacade.before_delete_cst(schema, list(schema.constituents()))
|
||||||
|
|
|
@ -51,7 +51,7 @@ class TestChangeConstituents(EndpointTester):
|
||||||
alias='3',
|
alias='3',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
self.owned.set_arguments(self.operation3, [self.operation1, self.operation2])
|
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
||||||
self.owned.execute_operation(self.operation3)
|
self.owned.execute_operation(self.operation3)
|
||||||
self.operation3.refresh_from_db()
|
self.operation3.refresh_from_db()
|
||||||
self.ks3 = RSForm(self.operation3.result)
|
self.ks3 = RSForm(self.operation3.result)
|
||||||
|
|
|
@ -71,8 +71,8 @@ class TestChangeOperations(EndpointTester):
|
||||||
alias='4',
|
alias='4',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
self.owned.set_arguments(self.operation4, [self.operation1, self.operation2])
|
self.owned.set_arguments(self.operation4.pk, [self.operation1, self.operation2])
|
||||||
self.owned.set_substitutions(self.operation4, [{
|
self.owned.set_substitutions(self.operation4.pk, [{
|
||||||
'original': self.ks1X1,
|
'original': self.ks1X1,
|
||||||
'substitution': self.ks2S1
|
'substitution': self.ks2S1
|
||||||
}])
|
}])
|
||||||
|
@ -92,8 +92,8 @@ class TestChangeOperations(EndpointTester):
|
||||||
alias='5',
|
alias='5',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
self.owned.set_arguments(self.operation5, [self.operation4, self.operation3])
|
self.owned.set_arguments(self.operation5.pk, [self.operation4, self.operation3])
|
||||||
self.owned.set_substitutions(self.operation5, [{
|
self.owned.set_substitutions(self.operation5.pk, [{
|
||||||
'original': self.ks4X1,
|
'original': self.ks4X1,
|
||||||
'substitution': self.ks3X1
|
'substitution': self.ks3X1
|
||||||
}])
|
}])
|
||||||
|
@ -249,3 +249,75 @@ class TestChangeOperations(EndpointTester):
|
||||||
self.assertEqual(self.ks5.constituents().count(), 8)
|
self.assertEqual(self.ks5.constituents().count(), 8)
|
||||||
self.assertEqual(self.ks4D2.definition_formal, r'X1 X2 X3 S1 D1')
|
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')
|
self.assertEqual(self.ks5D4.definition_formal, r'X1 X2 X3 S1 D1 D2 D3')
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||||
|
def test_change_substitutions(self):
|
||||||
|
data = {
|
||||||
|
'target': self.operation4.pk,
|
||||||
|
'item_data': {
|
||||||
|
'alias': 'Test4 mod',
|
||||||
|
'title': 'Test title mod',
|
||||||
|
'comment': 'Comment mod'
|
||||||
|
},
|
||||||
|
'positions': [],
|
||||||
|
'substitutions': [
|
||||||
|
{
|
||||||
|
'original': self.ks1X1.pk,
|
||||||
|
'substitution': self.ks2X2.pk
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'original': self.ks2X1.pk,
|
||||||
|
'substitution': self.ks1D1.pk
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), 2)
|
||||||
|
subs3_4 = self.operation5.getSubstitutions()
|
||||||
|
self.assertEqual(subs3_4.count(), 1)
|
||||||
|
self.assertEqual(self.ks4.constituents().count(), 5)
|
||||||
|
self.assertEqual(self.ks5.constituents().count(), 7)
|
||||||
|
self.assertEqual(self.ks4D2.definition_formal, r'X1 D1 X3 S1 D1')
|
||||||
|
self.assertEqual(self.ks5D4.definition_formal, r'X1 D2 X3 S1 D1 D2 D3')
|
||||||
|
|
||||||
|
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
|
||||||
|
def test_change_arguments(self):
|
||||||
|
data = {
|
||||||
|
'target': self.operation4.pk,
|
||||||
|
'item_data': {
|
||||||
|
'alias': 'Test4 mod',
|
||||||
|
'title': 'Test title mod',
|
||||||
|
'comment': 'Comment mod'
|
||||||
|
},
|
||||||
|
'positions': [],
|
||||||
|
'arguments': [self.operation1.pk],
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), 4)
|
||||||
|
self.assertEqual(self.ks5.constituents().count(), 6)
|
||||||
|
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')
|
||||||
|
|
||||||
|
data['arguments'] = [self.operation1.pk, self.operation2.pk]
|
||||||
|
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(), 7)
|
||||||
|
self.assertEqual(self.ks5.constituents().count(), 9)
|
||||||
|
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')
|
||||||
|
|
|
@ -71,8 +71,8 @@ class TestChangeSubstitutions(EndpointTester):
|
||||||
alias='4',
|
alias='4',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
self.owned.set_arguments(self.operation4, [self.operation1, self.operation2])
|
self.owned.set_arguments(self.operation4.pk, [self.operation1, self.operation2])
|
||||||
self.owned.set_substitutions(self.operation4, [{
|
self.owned.set_substitutions(self.operation4.pk, [{
|
||||||
'original': self.ks1X1,
|
'original': self.ks1X1,
|
||||||
'substitution': self.ks2S1
|
'substitution': self.ks2S1
|
||||||
}])
|
}])
|
||||||
|
@ -92,8 +92,8 @@ class TestChangeSubstitutions(EndpointTester):
|
||||||
alias='5',
|
alias='5',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
self.owned.set_arguments(self.operation5, [self.operation4, self.operation3])
|
self.owned.set_arguments(self.operation5.pk, [self.operation4, self.operation3])
|
||||||
self.owned.set_substitutions(self.operation5, [{
|
self.owned.set_substitutions(self.operation5.pk, [{
|
||||||
'original': self.ks4X1,
|
'original': self.ks4X1,
|
||||||
'substitution': self.ks3X1
|
'substitution': self.ks3X1
|
||||||
}])
|
}])
|
||||||
|
|
|
@ -55,8 +55,8 @@ class TestOssViewset(EndpointTester):
|
||||||
alias='3',
|
alias='3',
|
||||||
operation_type=OperationType.SYNTHESIS
|
operation_type=OperationType.SYNTHESIS
|
||||||
)
|
)
|
||||||
self.owned.set_arguments(self.operation3, [self.operation1, self.operation2])
|
self.owned.set_arguments(self.operation3.pk, [self.operation1, self.operation2])
|
||||||
self.owned.set_substitutions(self.operation3, [{
|
self.owned.set_substitutions(self.operation3.pk, [{
|
||||||
'original': self.ks1X1,
|
'original': self.ks1X1,
|
||||||
'substitution': self.ks2X1
|
'substitution': self.ks2X1
|
||||||
}])
|
}])
|
||||||
|
|
|
@ -129,7 +129,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
oss.create_input(new_operation)
|
oss.create_input(new_operation)
|
||||||
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
|
if new_operation.operation_type != m.OperationType.INPUT and 'arguments' in serializer.validated_data:
|
||||||
oss.set_arguments(
|
oss.set_arguments(
|
||||||
operation=new_operation,
|
target=new_operation.pk,
|
||||||
arguments=serializer.validated_data['arguments']
|
arguments=serializer.validated_data['arguments']
|
||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
|
@ -165,7 +165,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
old_schema: Optional[LibraryItem] = operation.result
|
old_schema: Optional[LibraryItem] = operation.result
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
oss.update_positions(serializer.validated_data['positions'])
|
oss.update_positions(serializer.validated_data['positions'])
|
||||||
oss.delete_operation(operation, serializer.validated_data['keep_constituents'])
|
oss.delete_operation(operation.pk, serializer.validated_data['keep_constituents'])
|
||||||
if old_schema is not None:
|
if old_schema is not None:
|
||||||
if serializer.validated_data['delete_schema']:
|
if serializer.validated_data['delete_schema']:
|
||||||
m.PropagationFacade.before_delete_schema(old_schema)
|
m.PropagationFacade.before_delete_schema(old_schema)
|
||||||
|
@ -305,9 +305,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
|
||||||
operation.result.comment = operation.comment
|
operation.result.comment = operation.comment
|
||||||
operation.result.save()
|
operation.result.save()
|
||||||
if 'arguments' in serializer.validated_data:
|
if 'arguments' in serializer.validated_data:
|
||||||
oss.set_arguments(operation, serializer.validated_data['arguments'])
|
oss.set_arguments(operation.pk, serializer.validated_data['arguments'])
|
||||||
if 'substitutions' in serializer.validated_data:
|
if 'substitutions' in serializer.validated_data:
|
||||||
oss.set_substitutions(operation, serializer.validated_data['substitutions'])
|
oss.set_substitutions(operation.pk, serializer.validated_data['substitutions'])
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.OperationSchemaSerializer(oss.model).data
|
data=s.OperationSchemaSerializer(oss.model).data
|
||||||
|
|
|
@ -42,6 +42,28 @@ class Graph(Generic[ItemType]):
|
||||||
if src not in self.inputs[dest]:
|
if src not in self.inputs[dest]:
|
||||||
self.inputs[dest].append(src)
|
self.inputs[dest].append(src)
|
||||||
|
|
||||||
|
def remove_edge(self, src: ItemType, dest: ItemType):
|
||||||
|
''' Remove edge from graph. '''
|
||||||
|
if not self.contains(src) or not self.contains(dest):
|
||||||
|
return
|
||||||
|
if dest in self.outputs[src]:
|
||||||
|
self.outputs[src].remove(dest)
|
||||||
|
if src in self.inputs[dest]:
|
||||||
|
self.inputs[dest].remove(src)
|
||||||
|
|
||||||
|
def remove_node(self, target: ItemType):
|
||||||
|
''' Remove node from graph. '''
|
||||||
|
if not self.contains(target):
|
||||||
|
return
|
||||||
|
del self.outputs[target]
|
||||||
|
del self.inputs[target]
|
||||||
|
for list_out in self.outputs.values():
|
||||||
|
if target in list_out:
|
||||||
|
list_out.remove(target)
|
||||||
|
for list_in in self.inputs.values():
|
||||||
|
if target in list_in:
|
||||||
|
list_in.remove(target)
|
||||||
|
|
||||||
def expand_inputs(self, origin: Iterable[ItemType]) -> list[ItemType]:
|
def expand_inputs(self, origin: Iterable[ItemType]) -> list[ItemType]:
|
||||||
''' Expand origin nodes forward through graph edges. '''
|
''' Expand origin nodes forward through graph edges. '''
|
||||||
result: list[ItemType] = []
|
result: list[ItemType] = []
|
||||||
|
|
|
@ -26,6 +26,32 @@ class TestGraph(unittest.TestCase):
|
||||||
self.assertTrue(graph.has_edge(1, 3))
|
self.assertTrue(graph.has_edge(1, 3))
|
||||||
self.assertTrue(graph.has_edge(2, 1))
|
self.assertTrue(graph.has_edge(2, 1))
|
||||||
|
|
||||||
|
def test_remove_node(self):
|
||||||
|
graph = Graph({
|
||||||
|
1: [2],
|
||||||
|
2: [3, 5],
|
||||||
|
3: [],
|
||||||
|
5: []
|
||||||
|
})
|
||||||
|
self.assertEqual(len(graph.outputs), 4)
|
||||||
|
graph.remove_node(0)
|
||||||
|
graph.remove_node(2)
|
||||||
|
self.assertEqual(graph.outputs[1], [])
|
||||||
|
self.assertEqual(len(graph.outputs), 3)
|
||||||
|
|
||||||
|
def test_remove_edge(self):
|
||||||
|
graph = Graph({
|
||||||
|
1: [2],
|
||||||
|
2: [3, 5],
|
||||||
|
3: [],
|
||||||
|
5: []
|
||||||
|
})
|
||||||
|
graph.remove_edge(0, 1)
|
||||||
|
graph.remove_edge(2, 1)
|
||||||
|
self.assertEqual(graph.outputs[1], [2])
|
||||||
|
graph.remove_edge(1, 2)
|
||||||
|
self.assertEqual(graph.outputs[1], [])
|
||||||
|
graph.remove_edge(1, 2)
|
||||||
|
|
||||||
def test_expand_outputs(self):
|
def test_expand_outputs(self):
|
||||||
graph = Graph({
|
graph = Graph({
|
||||||
|
|
|
@ -87,7 +87,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
schema = m.RSForm(self._get_item())
|
schema = m.RSForm(self._get_item())
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
new_cst = schema.create_cst(data, insert_after)
|
new_cst = schema.create_cst(data, insert_after)
|
||||||
PropagationFacade.after_create_cst([new_cst], schema)
|
PropagationFacade.after_create_cst(schema, [new_cst])
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_201_CREATED,
|
status=c.HTTP_201_CREATED,
|
||||||
data={
|
data={
|
||||||
|
@ -118,7 +118,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
data = serializer.validated_data['item_data']
|
data = serializer.validated_data['item_data']
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
old_data = schema.update_cst(cst, data)
|
old_data = schema.update_cst(cst, data)
|
||||||
PropagationFacade.after_update_cst(cst, data, old_data, schema)
|
PropagationFacade.after_update_cst(schema, cst, data, old_data)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data=s.CstSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data
|
data=s.CstSerializer(m.Constituenta.objects.get(pk=request.data['target'])).data
|
||||||
|
@ -159,7 +159,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
new_cst = schema.produce_structure(cst, cst_parse)
|
new_cst = schema.produce_structure(cst, cst_parse)
|
||||||
PropagationFacade.after_create_cst(new_cst, schema)
|
PropagationFacade.after_create_cst(schema, new_cst)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data={
|
data={
|
||||||
|
@ -197,7 +197,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
schema.save()
|
schema.save()
|
||||||
cst.refresh_from_db()
|
cst.refresh_from_db()
|
||||||
if changed_type:
|
if changed_type:
|
||||||
PropagationFacade.after_change_cst_type(cst, schema)
|
PropagationFacade.after_change_cst_type(schema, cst)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
data={
|
data={
|
||||||
|
@ -233,7 +233,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
original = cast(m.Constituenta, substitution['original'])
|
original = cast(m.Constituenta, substitution['original'])
|
||||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||||
substitutions.append((original, replacement))
|
substitutions.append((original, replacement))
|
||||||
PropagationFacade.before_substitute(substitutions, schema)
|
PropagationFacade.before_substitute(schema, substitutions)
|
||||||
schema.substitute(substitutions)
|
schema.substitute(substitutions)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
@ -263,7 +263,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
cst_list: list[m.Constituenta] = serializer.validated_data['items']
|
cst_list: list[m.Constituenta] = serializer.validated_data['items']
|
||||||
schema = m.RSForm(model)
|
schema = m.RSForm(model)
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
PropagationFacade.before_delete_cst(cst_list, schema)
|
PropagationFacade.before_delete_cst(schema, cst_list)
|
||||||
schema.delete_cst(cst_list)
|
schema.delete_cst(cst_list)
|
||||||
return Response(
|
return Response(
|
||||||
status=c.HTTP_200_OK,
|
status=c.HTTP_200_OK,
|
||||||
|
@ -581,7 +581,7 @@ def inline_synthesis(request: Request) -> HttpResponse:
|
||||||
|
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
new_items = receiver.insert_copy(items)
|
new_items = receiver.insert_copy(items)
|
||||||
PropagationFacade.after_create_cst(new_items, receiver)
|
PropagationFacade.after_create_cst(receiver, new_items)
|
||||||
|
|
||||||
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
|
substitutions: list[tuple[m.Constituenta, m.Constituenta]] = []
|
||||||
for substitution in serializer.validated_data['substitutions']:
|
for substitution in serializer.validated_data['substitutions']:
|
||||||
|
@ -595,7 +595,7 @@ def inline_synthesis(request: Request) -> HttpResponse:
|
||||||
replacement = new_items[index]
|
replacement = new_items[index]
|
||||||
substitutions.append((original, replacement))
|
substitutions.append((original, replacement))
|
||||||
|
|
||||||
PropagationFacade.before_substitute(substitutions, receiver)
|
PropagationFacade.before_substitute(receiver, substitutions)
|
||||||
receiver.substitute(substitutions)
|
receiver.substitute(substitutions)
|
||||||
|
|
||||||
receiver.restore_order()
|
receiver.restore_order()
|
||||||
|
|
671
rsconcept/frontend/package-lock.json
generated
671
rsconcept/frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -27,7 +27,6 @@
|
||||||
"react-icons": "^5.3.0",
|
"react-icons": "^5.3.0",
|
||||||
"react-intl": "^6.6.8",
|
"react-intl": "^6.6.8",
|
||||||
"react-loader-spinner": "^6.1.6",
|
"react-loader-spinner": "^6.1.6",
|
||||||
"react-pdf": "^9.1.0",
|
|
||||||
"react-router-dom": "^6.26.0",
|
"react-router-dom": "^6.26.0",
|
||||||
"react-select": "^5.8.0",
|
"react-select": "^5.8.0",
|
||||||
"react-tabs": "^6.0.2",
|
"react-tabs": "^6.0.2",
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import { IntlProvider } from 'react-intl';
|
import { IntlProvider } from 'react-intl';
|
||||||
import { pdfjs } from 'react-pdf';
|
|
||||||
|
|
||||||
import { AuthState } from '@/context/AuthContext';
|
import { AuthState } from '@/context/AuthContext';
|
||||||
import { OptionsState } from '@/context/ConceptOptionsContext';
|
import { OptionsState } from '@/context/ConceptOptionsContext';
|
||||||
|
@ -12,8 +11,6 @@ import { UsersState } from '@/context/UsersContext';
|
||||||
|
|
||||||
import ErrorFallback from './ErrorFallback';
|
import ErrorFallback from './ErrorFallback';
|
||||||
|
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url).toString();
|
|
||||||
|
|
||||||
const resetState = () => {
|
const resetState = () => {
|
||||||
console.log('Resetting state after error fallback');
|
console.log('Resetting state after error fallback');
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
import { ErrorData } from '@/components/info/InfoError';
|
import { ErrorData } from '@/components/info/InfoError';
|
||||||
|
import { extractErrorMessage } from '@/utils/utils';
|
||||||
|
|
||||||
import { axiosInstance } from './apiConfiguration';
|
import { axiosInstance } from './apiConfiguration';
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ export function AxiosGet<ResponseData>({ endpoint, request, options }: IAxiosReq
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
if (request.setLoading) request.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request.showError) toast.error(error.message);
|
if (request.showError) toast.error(extractErrorMessage(error));
|
||||||
if (request.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -69,7 +70,7 @@ export function AxiosPost<RequestData, ResponseData>({
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
if (request.setLoading) request.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request.showError) toast.error(error.message);
|
if (request.showError) toast.error(extractErrorMessage(error));
|
||||||
if (request.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -88,7 +89,7 @@ export function AxiosDelete<RequestData, ResponseData>({
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
if (request.setLoading) request.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request.showError) toast.error(error.message);
|
if (request.showError) toast.error(extractErrorMessage(error));
|
||||||
if (request.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -108,7 +109,7 @@ export function AxiosPatch<RequestData, ResponseData>({
|
||||||
})
|
})
|
||||||
.catch((error: Error | AxiosError) => {
|
.catch((error: Error | AxiosError) => {
|
||||||
if (request.setLoading) request.setLoading(false);
|
if (request.setLoading) request.setLoading(false);
|
||||||
if (request.showError) toast.error(error.message);
|
if (request.showError) toast.error(extractErrorMessage(error));
|
||||||
if (request.onError) request.onError(error);
|
if (request.onError) request.onError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,6 +64,7 @@ export { IoLibrary as IconLibrary2 } from 'react-icons/io5';
|
||||||
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
export { BiDiamond as IconTemplates } from 'react-icons/bi';
|
||||||
export { TbHexagons as IconOSS } from 'react-icons/tb';
|
export { TbHexagons as IconOSS } from 'react-icons/tb';
|
||||||
export { TbHexagon as IconRSForm } from 'react-icons/tb';
|
export { TbHexagon as IconRSForm } from 'react-icons/tb';
|
||||||
|
export { TbTopologyRing as IconConsolidation } from 'react-icons/tb';
|
||||||
export { GrInherit as IconChild } from 'react-icons/gr';
|
export { GrInherit as IconChild } from 'react-icons/gr';
|
||||||
export { RiParentLine as IconParent } from 'react-icons/ri';
|
export { RiParentLine as IconParent } from 'react-icons/ri';
|
||||||
export { BiSpa as IconPredecessor } from 'react-icons/bi';
|
export { BiSpa as IconPredecessor } from 'react-icons/bi';
|
||||||
|
|
|
@ -72,6 +72,11 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
|
||||||
<b>КС не принадлежит ОСС</b>
|
<b>КС не принадлежит ОСС</b>
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
{node.data.operation.is_consolidation ? (
|
||||||
|
<p>
|
||||||
|
<b>Ромбовидный синтез</b>
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
{node.data.operation.title ? (
|
{node.data.operation.title ? (
|
||||||
<p>
|
<p>
|
||||||
<b>Название: </b>
|
<b>Название: </b>
|
||||||
|
|
|
@ -4,9 +4,8 @@ import { useIntl } from 'react-intl';
|
||||||
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/ui/DataTable';
|
||||||
import SearchBar from '@/components/ui/SearchBar';
|
import SearchBar from '@/components/ui/SearchBar';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
|
||||||
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
|
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
|
||||||
import { ILibraryFilter } from '@/models/miscellaneous';
|
import { matchLibraryItem } from '@/models/libraryAPI';
|
||||||
|
|
||||||
import FlexColumn from '../ui/FlexColumn';
|
import FlexColumn from '../ui/FlexColumn';
|
||||||
|
|
||||||
|
@ -15,6 +14,8 @@ interface PickSchemaProps {
|
||||||
initialFilter?: string;
|
initialFilter?: string;
|
||||||
rows?: number;
|
rows?: number;
|
||||||
|
|
||||||
|
items: ILibraryItem[];
|
||||||
|
itemType: LibraryItemType;
|
||||||
value?: LibraryItemID;
|
value?: LibraryItemID;
|
||||||
baseFilter?: (target: ILibraryItem) => boolean;
|
baseFilter?: (target: ILibraryItem) => boolean;
|
||||||
onSelectValue: (newValue: LibraryItemID) => void;
|
onSelectValue: (newValue: LibraryItemID) => void;
|
||||||
|
@ -22,31 +23,31 @@ interface PickSchemaProps {
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<ILibraryItem>();
|
const columnHelper = createColumnHelper<ILibraryItem>();
|
||||||
|
|
||||||
function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue, baseFilter }: PickSchemaProps) {
|
function PickSchema({
|
||||||
|
id,
|
||||||
|
initialFilter = '',
|
||||||
|
rows = 4,
|
||||||
|
items,
|
||||||
|
itemType,
|
||||||
|
value,
|
||||||
|
onSelectValue,
|
||||||
|
baseFilter
|
||||||
|
}: PickSchemaProps) {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { colors } = useConceptOptions();
|
const { colors } = useConceptOptions();
|
||||||
|
|
||||||
const library = useLibrary();
|
|
||||||
const [filterText, setFilterText] = useState(initialFilter);
|
const [filterText, setFilterText] = useState(initialFilter);
|
||||||
const [filter, setFilter] = useState<ILibraryFilter>({});
|
const [filtered, setFiltered] = useState<ILibraryItem[]>([]);
|
||||||
const [items, setItems] = useState<ILibraryItem[]>([]);
|
const baseFiltered = useMemo(
|
||||||
|
() => items.filter(item => item.item_type === itemType && (!baseFilter || baseFilter(item))),
|
||||||
|
[items, itemType, baseFilter]
|
||||||
|
);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setFilter({
|
const newFiltered = baseFiltered.filter(item => matchLibraryItem(item, filterText));
|
||||||
query: filterText,
|
setFiltered(newFiltered);
|
||||||
type: LibraryItemType.RSFORM
|
|
||||||
});
|
|
||||||
}, [filterText]);
|
}, [filterText]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
|
||||||
const filtered = library.applyFilter(filter);
|
|
||||||
if (baseFilter) {
|
|
||||||
setItems(filtered.filter(baseFilter));
|
|
||||||
} else {
|
|
||||||
setItems(filtered);
|
|
||||||
}
|
|
||||||
}, [library, filter, filter.query, baseFilter]);
|
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
columnHelper.accessor('alias', {
|
columnHelper.accessor('alias', {
|
||||||
|
@ -106,7 +107,7 @@ function PickSchema({ id, initialFilter = '', rows = 4, value, onSelectValue, ba
|
||||||
noHeader
|
noHeader
|
||||||
noFooter
|
noFooter
|
||||||
className='text-sm select-none cc-scroll-y'
|
className='text-sm select-none cc-scroll-y'
|
||||||
data={items}
|
data={filtered}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
conditionalRowStyles={conditionalRowStyles}
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
noDataComponent={
|
noDataComponent={
|
||||||
|
|
|
@ -1,61 +1,29 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { PDFDocumentProxy } from 'pdfjs-dist';
|
import { useMemo } from 'react';
|
||||||
import { useMemo, useState } from 'react';
|
|
||||||
import { Document, Page } from 'react-pdf';
|
|
||||||
|
|
||||||
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import useWindowSize from '@/hooks/useWindowSize';
|
import useWindowSize from '@/hooks/useWindowSize';
|
||||||
import { graphLightT } from '@/styling/color';
|
|
||||||
|
|
||||||
import Overlay from '../Overlay';
|
const MAXIMUM_WIDTH = 1600;
|
||||||
import PageControls from './PageControls';
|
|
||||||
|
|
||||||
const MAXIMUM_WIDTH = 1000;
|
|
||||||
const MINIMUM_WIDTH = 300;
|
const MINIMUM_WIDTH = 300;
|
||||||
|
|
||||||
interface PDFViewerProps {
|
interface PDFViewerProps {
|
||||||
file?: string | ArrayBuffer | Blob;
|
file?: string;
|
||||||
offsetXpx?: number;
|
offsetXpx?: number;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
|
function PDFViewer({ file, offsetXpx, minWidth = MINIMUM_WIDTH }: PDFViewerProps) {
|
||||||
const windowSize = useWindowSize();
|
const windowSize = useWindowSize();
|
||||||
|
const { calculateHeight } = useConceptOptions();
|
||||||
const [pageCount, setPageCount] = useState(0);
|
|
||||||
const [pageNumber, setPageNumber] = useState(1);
|
|
||||||
|
|
||||||
const pageWidth = useMemo(() => {
|
const pageWidth = useMemo(() => {
|
||||||
return Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
|
return Math.max(minWidth, Math.min((windowSize?.width ?? 0) - (offsetXpx ?? 0) - 10, MAXIMUM_WIDTH));
|
||||||
}, [windowSize, offsetXpx, minWidth]);
|
}, [windowSize, offsetXpx, minWidth]);
|
||||||
|
const pageHeight = useMemo(() => calculateHeight('1rem'), [calculateHeight]);
|
||||||
|
|
||||||
function onDocumentLoadSuccess({ numPages }: PDFDocumentProxy) {
|
return <embed src={`${file}#toolbar=0`} className='p-3' style={{ width: pageWidth, height: pageHeight }} />;
|
||||||
setPageCount(numPages);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Document
|
|
||||||
file={file}
|
|
||||||
onLoadSuccess={onDocumentLoadSuccess}
|
|
||||||
loading='Загрузка PDF файла...'
|
|
||||||
error='Не удалось загрузить файл.'
|
|
||||||
>
|
|
||||||
<Overlay position='top-3 left-1/2 -translate-x-1/2' className='flex select-none'>
|
|
||||||
<PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} />
|
|
||||||
</Overlay>
|
|
||||||
<Page
|
|
||||||
className='overflow-hidden pointer-events-none select-none'
|
|
||||||
renderTextLayer={false}
|
|
||||||
renderAnnotationLayer={false}
|
|
||||||
pageNumber={pageNumber}
|
|
||||||
width={pageWidth}
|
|
||||||
canvasBackground={graphLightT.canvas.background}
|
|
||||||
/>
|
|
||||||
<Overlay position='bottom-3 left-1/2 -translate-x-1/2' className='flex select-none'>
|
|
||||||
<PageControls pageCount={pageCount} pageNumber={pageNumber} setPageNumber={setPageNumber} />
|
|
||||||
</Overlay>
|
|
||||||
</Document>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PDFViewer;
|
export default PDFViewer;
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import { IconPageFirst, IconPageLast, IconPageLeft, IconPageRight } from '@/components/Icons';
|
|
||||||
|
|
||||||
interface PageControlsProps {
|
|
||||||
pageNumber: number;
|
|
||||||
pageCount: number;
|
|
||||||
setPageNumber: React.Dispatch<React.SetStateAction<number>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function PageControls({ pageNumber, pageCount, setPageNumber }: PageControlsProps) {
|
|
||||||
return (
|
|
||||||
<div className='flex items-center'>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='clr-hover clr-text-controls'
|
|
||||||
onClick={() => setPageNumber(1)}
|
|
||||||
disabled={pageNumber < 2}
|
|
||||||
>
|
|
||||||
<IconPageFirst size='1.5rem' />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='clr-hover clr-text-controls'
|
|
||||||
onClick={() => setPageNumber(prev => prev - 1)}
|
|
||||||
disabled={pageNumber < 2}
|
|
||||||
>
|
|
||||||
<IconPageLeft size='1.5rem' />
|
|
||||||
</button>
|
|
||||||
<div className='px-3 text-nowrap'>
|
|
||||||
Страница {pageNumber} из {pageCount}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='clr-hover clr-text-controls'
|
|
||||||
onClick={() => setPageNumber(prev => prev + 1)}
|
|
||||||
disabled={pageNumber >= pageCount}
|
|
||||||
>
|
|
||||||
<IconPageRight size='1.5rem' />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type='button'
|
|
||||||
className='clr-hover clr-text-controls'
|
|
||||||
onClick={() => setPageNumber(pageCount)}
|
|
||||||
disabled={pageNumber >= pageCount}
|
|
||||||
>
|
|
||||||
<IconPageLast size='1.5rem' />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PageControls;
|
|
|
@ -8,8 +8,10 @@ import PickSchema from '@/components/select/PickSchema';
|
||||||
import Label from '@/components/ui/Label';
|
import Label from '@/components/ui/Label';
|
||||||
import MiniButton from '@/components/ui/MiniButton';
|
import MiniButton from '@/components/ui/MiniButton';
|
||||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
|
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
|
||||||
import { IOperation, IOperationSchema } from '@/models/oss';
|
import { IOperation, IOperationSchema } from '@/models/oss';
|
||||||
|
import { sortItemsForOSS } from '@/models/ossAPI';
|
||||||
|
|
||||||
interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
|
interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
oss: IOperationSchema;
|
oss: IOperationSchema;
|
||||||
|
@ -19,6 +21,8 @@ interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
|
||||||
function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) {
|
function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) {
|
||||||
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined);
|
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined);
|
||||||
|
const library = useLibrary();
|
||||||
|
const sortedItems = useMemo(() => sortItemsForOSS(oss, library.items), [oss, library.items]);
|
||||||
|
|
||||||
const baseFilter = useCallback(
|
const baseFilter = useCallback(
|
||||||
(item: ILibraryItem) => !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result,
|
(item: ILibraryItem) => !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result,
|
||||||
|
@ -55,6 +59,8 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PickSchema
|
<PickSchema
|
||||||
|
items={sortedItems}
|
||||||
|
itemType={LibraryItemType.RSFORM}
|
||||||
value={selected} // prettier: split-line
|
value={selected} // prettier: split-line
|
||||||
onSelectValue={handleSelectLocation}
|
onSelectValue={handleSelectLocation}
|
||||||
rows={8}
|
rows={8}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { IconReset } from '@/components/Icons';
|
import { IconReset } from '@/components/Icons';
|
||||||
import PickSchema from '@/components/select/PickSchema';
|
import PickSchema from '@/components/select/PickSchema';
|
||||||
|
@ -10,8 +10,10 @@ import MiniButton from '@/components/ui/MiniButton';
|
||||||
import TextArea from '@/components/ui/TextArea';
|
import TextArea from '@/components/ui/TextArea';
|
||||||
import TextInput from '@/components/ui/TextInput';
|
import TextInput from '@/components/ui/TextInput';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { ILibraryItem, LibraryItemID } from '@/models/library';
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
|
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
|
||||||
import { IOperationSchema } from '@/models/oss';
|
import { IOperationSchema } from '@/models/oss';
|
||||||
|
import { sortItemsForOSS } from '@/models/ossAPI';
|
||||||
import { limits, patterns } from '@/utils/constants';
|
import { limits, patterns } from '@/utils/constants';
|
||||||
|
|
||||||
interface TabInputOperationProps {
|
interface TabInputOperationProps {
|
||||||
|
@ -42,6 +44,8 @@ function TabInputOperation({
|
||||||
setCreateSchema
|
setCreateSchema
|
||||||
}: TabInputOperationProps) {
|
}: TabInputOperationProps) {
|
||||||
const baseFilter = useCallback((item: ILibraryItem) => !oss.schemas.includes(item.id), [oss]);
|
const baseFilter = useCallback((item: ILibraryItem) => !oss.schemas.includes(item.id), [oss]);
|
||||||
|
const library = useLibrary();
|
||||||
|
const sortedItems = useMemo(() => sortItemsForOSS(oss, library.items), [oss, library.items]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (createSchema) {
|
if (createSchema) {
|
||||||
|
@ -102,7 +106,9 @@ function TabInputOperation({
|
||||||
</div>
|
</div>
|
||||||
{!createSchema ? (
|
{!createSchema ? (
|
||||||
<PickSchema
|
<PickSchema
|
||||||
value={attachedID} // prettier: split-line
|
items={sortedItems}
|
||||||
|
value={attachedID}
|
||||||
|
itemType={LibraryItemType.RSFORM}
|
||||||
onSelectValue={setAttachedID}
|
onSelectValue={setAttachedID}
|
||||||
rows={8}
|
rows={8}
|
||||||
baseFilter={baseFilter}
|
baseFilter={baseFilter}
|
||||||
|
|
|
@ -67,8 +67,8 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
if (cache.loading || schemas.length !== schemasIDs.length) {
|
if (cache.loading || schemas.length !== schemasIDs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setSubstitutions(prev =>
|
setSubstitutions(() =>
|
||||||
prev.filter(sub => {
|
target.substitutions.filter(sub => {
|
||||||
const original = cache.getSchemaByCst(sub.original);
|
const original = cache.getSchemaByCst(sub.original);
|
||||||
if (!original || !schemasIDs.includes(original.id)) {
|
if (!original || !schemasIDs.includes(original.id)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -80,7 +80,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
|
||||||
return true;
|
return true;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}, [schemasIDs, schemas, cache.loading]);
|
}, [schemasIDs, schemas, cache.loading, target.substitutions]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const data: IOperationUpdateData = {
|
const data: IOperationUpdateData = {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import PickSchema from '@/components/select/PickSchema';
|
||||||
import TextInput from '@/components/ui/TextInput';
|
import TextInput from '@/components/ui/TextInput';
|
||||||
import AnimateFade from '@/components/wrap/AnimateFade';
|
import AnimateFade from '@/components/wrap/AnimateFade';
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
import { LibraryItemID } from '@/models/library';
|
import { LibraryItemID, LibraryItemType } from '@/models/library';
|
||||||
|
|
||||||
interface TabSchemaProps {
|
interface TabSchemaProps {
|
||||||
selected?: LibraryItemID;
|
selected?: LibraryItemID;
|
||||||
|
@ -33,6 +33,8 @@ function TabSchema({ selected, setSelected }: TabSchemaProps) {
|
||||||
</div>
|
</div>
|
||||||
<PickSchema
|
<PickSchema
|
||||||
id='dlg_schema_picker' // prettier: split lines
|
id='dlg_schema_picker' // prettier: split lines
|
||||||
|
items={library.items}
|
||||||
|
itemType={LibraryItemType.RSFORM}
|
||||||
rows={15}
|
rows={15}
|
||||||
value={selected}
|
value={selected}
|
||||||
onSelectValue={setSelected}
|
onSelectValue={setSelected}
|
||||||
|
|
|
@ -62,6 +62,7 @@ export class OssLoader {
|
||||||
this.graph.topologicalOrder().forEach(operationID => {
|
this.graph.topologicalOrder().forEach(operationID => {
|
||||||
const operation = this.operationByID.get(operationID)!;
|
const operation = this.operationByID.get(operationID)!;
|
||||||
const schema = this.items.find(item => item.id === operation.result);
|
const schema = this.items.find(item => item.id === operation.result);
|
||||||
|
operation.is_consolidation = this.inferConsolidation(operationID);
|
||||||
operation.is_owned = !schema || (schema.owner === this.oss.owner && schema.location === this.oss.location);
|
operation.is_owned = !schema || (schema.owner === this.oss.owner && schema.location === this.oss.location);
|
||||||
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
|
||||||
operation.arguments = this.oss.arguments
|
operation.arguments = this.oss.arguments
|
||||||
|
@ -70,6 +71,19 @@ export class OssLoader {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inferConsolidation(operationID: OperationID): boolean {
|
||||||
|
const inputs = this.graph.expandInputs([operationID]);
|
||||||
|
if (inputs.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const ancestors = [...inputs];
|
||||||
|
inputs.forEach(input => {
|
||||||
|
ancestors.push(...this.graph.expandAllInputs([input]));
|
||||||
|
});
|
||||||
|
const unique = new Set(ancestors);
|
||||||
|
return unique.size < ancestors.length;
|
||||||
|
}
|
||||||
|
|
||||||
private calculateStats(): IOperationSchemaStats {
|
private calculateStats(): IOperationSchemaStats {
|
||||||
const items = this.oss.items;
|
const items = this.oss.items;
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -37,6 +37,7 @@ export interface IOperation {
|
||||||
result: LibraryItemID | null;
|
result: LibraryItemID | null;
|
||||||
|
|
||||||
is_owned: boolean;
|
is_owned: boolean;
|
||||||
|
is_consolidation: boolean; // aka 'diamond synthesis'
|
||||||
substitutions: ICstSubstituteEx[];
|
substitutions: ICstSubstituteEx[];
|
||||||
arguments: OperationID[];
|
arguments: OperationID[];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
import { TextMatcher } from '@/utils/utils';
|
import { TextMatcher } from '@/utils/utils';
|
||||||
|
|
||||||
import { IOperation } from './oss';
|
import { ILibraryItem } from './library';
|
||||||
|
import { IOperation, IOperationSchema } from './oss';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a given target {@link IOperation} matches the specified query using.
|
* Checks if a given target {@link IOperation} matches the specified query using.
|
||||||
|
@ -16,3 +17,29 @@ export function matchOperation(target: IOperation, query: string): boolean {
|
||||||
const matcher = new TextMatcher(query);
|
const matcher = new TextMatcher(query);
|
||||||
return matcher.test(target.alias) || matcher.test(target.title);
|
return matcher.test(target.alias) || matcher.test(target.title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts library items relevant for the specified {@link IOperationSchema}.
|
||||||
|
*
|
||||||
|
* @param oss - The {@link IOperationSchema} to be sorted.
|
||||||
|
* @param items - The items to be sorted.
|
||||||
|
*/
|
||||||
|
export function sortItemsForOSS(oss: IOperationSchema, items: ILibraryItem[]): ILibraryItem[] {
|
||||||
|
const result = items.filter(item => item.location === oss.location);
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.visible && item.owner === oss.owner && !result.includes(item)) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.visible && !result.includes(item)) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of items) {
|
||||||
|
if (!result.includes(item)) {
|
||||||
|
result.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ function TableLibraryItems({ items, resetQuery, folderMode, toggleFolderMode }:
|
||||||
{
|
{
|
||||||
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
|
when: (item: ILibraryItem) => item.item_type === LibraryItemType.OSS,
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: colors.bgGreen50
|
color: colors.fgGreen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,39 +1,39 @@
|
||||||
import useWindowSize from '@/hooks/useWindowSize';
|
import useWindowSize from '@/hooks/useWindowSize';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
|
import HelpConceptOSS from './items/cc/HelpConceptOSS';
|
||||||
|
import HelpConceptRelations from './items/cc/HelpConceptRelations';
|
||||||
|
import HelpConceptSynthesis from './items/cc/HelpConceptSynthesis';
|
||||||
|
import HelpConceptSystem from './items/cc/HelpConceptSystem';
|
||||||
|
import HelpCstAttributes from './items/cc/HelpCstAttributes';
|
||||||
import HelpAccess from './items/HelpAccess';
|
import HelpAccess from './items/HelpAccess';
|
||||||
import HelpAPI from './items/HelpAPI';
|
|
||||||
import HelpConcept from './items/HelpConcept';
|
import HelpConcept from './items/HelpConcept';
|
||||||
import HelpConceptOSS from './items/HelpConceptOSS';
|
|
||||||
import HelpConceptRelations from './items/HelpConceptRelations';
|
|
||||||
import HelpConceptSynthesis from './items/HelpConceptSynthesis';
|
|
||||||
import HelpConceptSystem from './items/HelpConceptSystem';
|
|
||||||
import HelpContributors from './items/HelpContributors';
|
|
||||||
import HelpCstAttributes from './items/HelpCstAttributes';
|
|
||||||
import HelpCstClass from './items/HelpCstClass';
|
|
||||||
import HelpCstEditor from './items/HelpCstEditor';
|
|
||||||
import HelpCstStatus from './items/HelpCstStatus';
|
|
||||||
import HelpExteor from './items/HelpExteor';
|
import HelpExteor from './items/HelpExteor';
|
||||||
import HelpFormulaTree from './items/HelpFormulaTree';
|
|
||||||
import HelpInfo from './items/HelpInfo';
|
import HelpInfo from './items/HelpInfo';
|
||||||
import HelpInterface from './items/HelpInterface';
|
import HelpInterface from './items/HelpInterface';
|
||||||
import HelpLibrary from './items/HelpLibrary';
|
import HelpMain from './items/HelpMain';
|
||||||
import HelpOssGraph from './items/HelpOssGraph';
|
|
||||||
import HelpPortal from './items/HelpPortal';
|
|
||||||
import HelpPrivacy from './items/HelpPrivacy';
|
|
||||||
import HelpRSFormCard from './items/HelpRSFormCard';
|
|
||||||
import HelpRSFormItems from './items/HelpRSFormItems';
|
|
||||||
import HelpRSFormMenu from './items/HelpRSFormMenu';
|
|
||||||
import HelpRSLang from './items/HelpRSLang';
|
import HelpRSLang from './items/HelpRSLang';
|
||||||
import HelpRSLangCorrect from './items/HelpRSLangCorrect';
|
|
||||||
import HelpRSLangInterpret from './items/HelpRSLangInterpret';
|
|
||||||
import HelpRSLangOperations from './items/HelpRSLangOperations';
|
|
||||||
import HelpRSLangTemplates from './items/HelpRSLangTemplates';
|
|
||||||
import HelpRSLangTypes from './items/HelpRSLangTypes';
|
|
||||||
import HelpRules from './items/HelpRules';
|
|
||||||
import HelpTermGraph from './items/HelpTermGraph';
|
|
||||||
import HelpTerminologyControl from './items/HelpTerminologyControl';
|
import HelpTerminologyControl from './items/HelpTerminologyControl';
|
||||||
import HelpVersions from './items/HelpVersions';
|
import HelpVersions from './items/HelpVersions';
|
||||||
|
import HelpAPI from './items/info/HelpAPI';
|
||||||
|
import HelpContributors from './items/info/HelpContributors';
|
||||||
|
import HelpPrivacy from './items/info/HelpPrivacy';
|
||||||
|
import HelpRules from './items/info/HelpRules';
|
||||||
|
import HelpRSLangCorrect from './items/rslang/HelpRSLangCorrect';
|
||||||
|
import HelpRSLangInterpret from './items/rslang/HelpRSLangInterpret';
|
||||||
|
import HelpRSLangOperations from './items/rslang/HelpRSLangOperations';
|
||||||
|
import HelpRSLangTemplates from './items/rslang/HelpRSLangTemplates';
|
||||||
|
import HelpRSLangTypes from './items/rslang/HelpRSLangTypes';
|
||||||
|
import HelpCstClass from './items/ui/HelpCstClass';
|
||||||
|
import HelpCstStatus from './items/ui/HelpCstStatus';
|
||||||
|
import HelpFormulaTree from './items/ui/HelpFormulaTree';
|
||||||
|
import HelpLibrary from './items/ui/HelpLibrary';
|
||||||
|
import HelpOssGraph from './items/ui/HelpOssGraph';
|
||||||
|
import HelpRSCard from './items/ui/HelpRSCard';
|
||||||
|
import HelpRSEditor from './items/ui/HelpRSEditor';
|
||||||
|
import HelpRSGraphTerm from './items/ui/HelpRSGraphTerm';
|
||||||
|
import HelpRSList from './items/ui/HelpRSList';
|
||||||
|
import HelpRSMenu from './items/ui/HelpRSMenu';
|
||||||
|
|
||||||
// PDF Viewer setup
|
// PDF Viewer setup
|
||||||
const OFFSET_X_SMALL = 32;
|
const OFFSET_X_SMALL = 32;
|
||||||
|
@ -49,15 +49,15 @@ interface TopicPageProps {
|
||||||
function TopicPage({ topic }: TopicPageProps) {
|
function TopicPage({ topic }: TopicPageProps) {
|
||||||
const size = useWindowSize();
|
const size = useWindowSize();
|
||||||
|
|
||||||
if (topic === HelpTopic.MAIN) return <HelpPortal />;
|
if (topic === HelpTopic.MAIN) return <HelpMain />;
|
||||||
|
|
||||||
if (topic === HelpTopic.INTERFACE) return <HelpInterface />;
|
if (topic === HelpTopic.INTERFACE) return <HelpInterface />;
|
||||||
if (topic === HelpTopic.UI_LIBRARY) return <HelpLibrary />;
|
if (topic === HelpTopic.UI_LIBRARY) return <HelpLibrary />;
|
||||||
if (topic === HelpTopic.UI_RS_MENU) return <HelpRSFormMenu />;
|
if (topic === HelpTopic.UI_RS_MENU) return <HelpRSMenu />;
|
||||||
if (topic === HelpTopic.UI_RS_CARD) return <HelpRSFormCard />;
|
if (topic === HelpTopic.UI_RS_CARD) return <HelpRSCard />;
|
||||||
if (topic === HelpTopic.UI_RS_LIST) return <HelpRSFormItems />;
|
if (topic === HelpTopic.UI_RS_LIST) return <HelpRSList />;
|
||||||
if (topic === HelpTopic.UI_RS_EDITOR) return <HelpCstEditor />;
|
if (topic === HelpTopic.UI_RS_EDITOR) return <HelpRSEditor />;
|
||||||
if (topic === HelpTopic.UI_GRAPH_TERM) return <HelpTermGraph />;
|
if (topic === HelpTopic.UI_GRAPH_TERM) return <HelpRSGraphTerm />;
|
||||||
if (topic === HelpTopic.UI_FORMULA_TREE) return <HelpFormulaTree />;
|
if (topic === HelpTopic.UI_FORMULA_TREE) return <HelpFormulaTree />;
|
||||||
if (topic === HelpTopic.UI_CST_STATUS) return <HelpCstStatus />;
|
if (topic === HelpTopic.UI_CST_STATUS) return <HelpCstStatus />;
|
||||||
if (topic === HelpTopic.UI_CST_CLASS) return <HelpCstClass />;
|
if (topic === HelpTopic.UI_CST_CLASS) return <HelpCstClass />;
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { external_urls, prefixes } from '@/utils/constants';
|
||||||
|
|
||||||
import TopicItem from '../TopicItem';
|
import TopicItem from '../TopicItem';
|
||||||
|
|
||||||
function HelpPortal() {
|
function HelpMain() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Портал</h1>
|
<h1>Портал</h1>
|
||||||
|
@ -79,4 +79,4 @@ function HelpPortal() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpPortal;
|
export default HelpMain;
|
|
@ -1,24 +1,37 @@
|
||||||
|
import { IconOSS, IconPredecessor } from '@/components/Icons';
|
||||||
import LinkTopic from '@/components/ui/LinkTopic';
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpConceptOSS() {
|
function HelpConceptOSS() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='text-justify'>
|
||||||
<h1>Операционная схема синтеза</h1>
|
<h1>Операционная схема синтеза</h1>
|
||||||
<p>
|
<p>
|
||||||
Работа со сложными предметными областями требует многократного{' '}
|
Работа со сложными предметными областями требует многократного{' '}
|
||||||
<LinkTopic text='синтеза' topic={HelpTopic.CC_SYNTHESIS} /> для построения целевых понятий. Последовательность
|
<LinkTopic text='синтеза' topic={HelpTopic.CC_SYNTHESIS} /> для построения целевых понятий. Последовательность
|
||||||
синтезов концептуальных схем задается с помощью <b>Операционной схемы синтеза (ОСС)</b> в форме Графа синтеза.
|
синтезов задается с помощью{' '}
|
||||||
|
<span className='text-nowrap'>
|
||||||
|
<IconOSS className='inline-icon' /> <b>Операционной схемы синтеза (ОСС)</b>
|
||||||
|
</span>{' '}
|
||||||
|
и отображается в форме <LinkTopic text='Графа синтеза' topic={HelpTopic.UI_OSS_GRAPH} />.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Отдельные операции в рамках ОСС задаются <b>таблицами отождествлений</b> понятий из синтезируемых схем. Таким
|
Отдельные операции в рамках ОСС задаются <b>таблицами отождествлений</b> понятий из синтезируемых схем. Таким
|
||||||
образом <LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на
|
образом <LinkTopic text='конституенты' topic={HelpTopic.CC_CONSTITUENTA} /> в каждой КС разделяются на исходные
|
||||||
наследованные, отождествленные и дописанные.
|
(дописанные), наследованные, отождествленные (удаляемые).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Портал поддерживает <b>сквозные изменения</b> в рамках ОСС. Изменения, внесенные в исходные концептуальные схемы
|
Портал поддерживает <b>сквозные изменения</b> в рамках ОСС. Изменения, внесенные в исходные концептуальные схемы
|
||||||
автоматически проносятся через граф синтеза (путем обновления наследованных конституент). Формальные определения
|
автоматически проносятся через граф синтеза (путем обновления наследованных конституент). Формальные определения
|
||||||
наследованных конституент можно редактировать только путем изменения исходных конституент.
|
наследованных конституент можно редактировать только путем изменения{' '}
|
||||||
|
<span className='text-nowrap'>
|
||||||
|
<IconPredecessor className='inline-icon' /> исходных конституент.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<b>Ромбовидным синтезом</b> называется операция, где используются КС, имеющие общих предков. При таком синтезе
|
||||||
|
могут возникать дубликаты и неоднозначности в результате. Необходимо внимательно формировать таблицу
|
||||||
|
отождествлений, добавляя дублирующиеся понятия из синтезируемых схем.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -40,8 +40,8 @@ function HelpConceptSynthesis() {
|
||||||
<LinkTopic text='разделе Операции' topic={HelpTopic.RSL_OPERATIONS} />
|
<LinkTopic text='разделе Операции' topic={HelpTopic.RSL_OPERATIONS} />
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Для управления совокупностью синтезов используются <b>операционные схемы синтеза</b>. В данный момент этот
|
Для управления совокупностью синтезов используются{' '}
|
||||||
функционал еще не реализован в Портале.
|
<LinkTopic text='операционные схемы синтеза' topic={HelpTopic.CC_OSS} />.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
|
@ -5,18 +5,28 @@ import {
|
||||||
IconFolderEmpty,
|
IconFolderEmpty,
|
||||||
IconFolderOpened,
|
IconFolderOpened,
|
||||||
IconFolderTree,
|
IconFolderTree,
|
||||||
|
IconOSS,
|
||||||
|
IconRSForm,
|
||||||
IconSearch,
|
IconSearch,
|
||||||
IconShow,
|
IconShow,
|
||||||
IconSortAsc,
|
IconSortAsc,
|
||||||
IconSortDesc
|
IconSortDesc
|
||||||
} from '@/components/Icons';
|
} from '@/components/Icons';
|
||||||
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
|
||||||
function HelpLibrary() {
|
function HelpLibrary() {
|
||||||
|
const { colors } = useConceptOptions();
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Библиотека схем</h1>
|
<h1>Библиотека схем</h1>
|
||||||
<p>В библиотеке собраны концептуальные схемы, эксплицированные в родоструктурном аппарате</p>
|
<p>
|
||||||
|
В библиотеке собраны <IconRSForm size='1rem' className='inline-icon' /> системы определений (КС) <br />и
|
||||||
|
<IconOSS size='1rem' className='inline-icon' /> операционные схемы синтеза (ОСС).
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<span style={{ color: colors.fgGreen }}>зеленым текстом</span> выделены ОСС
|
||||||
|
</li>
|
||||||
<li>клик по строке - переход к редактированию схемы</li>
|
<li>клик по строке - переход к редактированию схемы</li>
|
||||||
<li>Ctrl + клик по строке откроет схему в новой вкладке</li>
|
<li>Ctrl + клик по строке откроет схему в новой вкладке</li>
|
||||||
<li>Фильтры атрибутов три позиции: да/нет/не применять</li>
|
<li>Фильтры атрибутов три позиции: да/нет/не применять</li>
|
|
@ -2,6 +2,7 @@ import {
|
||||||
IconAnimation,
|
IconAnimation,
|
||||||
IconAnimationOff,
|
IconAnimationOff,
|
||||||
IconConnect,
|
IconConnect,
|
||||||
|
IconConsolidation,
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconEdit2,
|
IconEdit2,
|
||||||
IconExecute,
|
IconExecute,
|
||||||
|
@ -23,7 +24,7 @@ import { HelpTopic } from '@/models/miscellaneous';
|
||||||
function HelpOssGraph() {
|
function HelpOssGraph() {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
<h1>Граф синтеза</h1>
|
<h1 className='sm:pr-[6rem]'>Граф синтеза</h1>
|
||||||
<div className='flex flex-col sm:flex-row'>
|
<div className='flex flex-col sm:flex-row'>
|
||||||
<div className='w-full sm:w-[14rem]'>
|
<div className='w-full sm:w-[14rem]'>
|
||||||
<h1>Настройка графа</h1>
|
<h1>Настройка графа</h1>
|
||||||
|
@ -50,7 +51,10 @@ function HelpOssGraph() {
|
||||||
<li>Клик на операцию – выделение</li>
|
<li>Клик на операцию – выделение</li>
|
||||||
<li>Esc – сбросить выделение</li>
|
<li>Esc – сбросить выделение</li>
|
||||||
<li>
|
<li>
|
||||||
<IconEdit2 className='inline-icon' /> Двойной клик – редактирование
|
Двойной клик – переход к связанной <LinkTopic text='КС' topic={HelpTopic.CC_SYSTEM} />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconEdit2 className='inline-icon' /> Редактирование операции
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconNewItem className='inline-icon icon-green' /> Новая операция
|
<IconNewItem className='inline-icon icon-green' /> Новая операция
|
||||||
|
@ -73,7 +77,7 @@ function HelpOssGraph() {
|
||||||
<IconSave className='inline-icon' /> Сохранить положения
|
<IconSave className='inline-icon' /> Сохранить положения
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconImage className='inline-icon' /> Сохранить в формат SVG
|
<IconImage className='inline-icon' /> Сохранить в SVG
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -82,9 +86,13 @@ function HelpOssGraph() {
|
||||||
<div className='dense w-[21rem]'>
|
<div className='dense w-[21rem]'>
|
||||||
<h1>Контекстное меню</h1>
|
<h1>Контекстное меню</h1>
|
||||||
<li>
|
<li>
|
||||||
<IconRSForm className='inline-icon icon-green' /> Переход к связанной{' '}
|
<IconRSForm className='inline-icon icon-green' /> Статус связанной{' '}
|
||||||
<LinkTopic text='КС' topic={HelpTopic.CC_SYSTEM} />
|
<LinkTopic text='КС' topic={HelpTopic.CC_SYSTEM} />
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconConsolidation className='inline-icon' />{' '}
|
||||||
|
<LinkTopic text='Ромбовидный синтез' topic={HelpTopic.CC_OSS} />
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconNewRSForm className='inline-icon icon-green' /> Создать пустую КС для загрузки
|
<IconNewRSForm className='inline-icon icon-green' /> Создать пустую КС для загрузки
|
||||||
</li>
|
</li>
|
|
@ -13,7 +13,7 @@ import {
|
||||||
import LinkTopic from '@/components/ui/LinkTopic';
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpRSFormCard() {
|
function HelpRSCard() {
|
||||||
return (
|
return (
|
||||||
<div className='dense'>
|
<div className='dense'>
|
||||||
<h1>Карточка схемы</h1>
|
<h1>Карточка схемы</h1>
|
||||||
|
@ -64,4 +64,4 @@ function HelpRSFormCard() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpRSFormCard;
|
export default HelpRSCard;
|
|
@ -22,7 +22,7 @@ import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpCstEditor() {
|
function HelpRSEditor() {
|
||||||
const { colors } = useConceptOptions();
|
const { colors } = useConceptOptions();
|
||||||
return (
|
return (
|
||||||
<div className='dense'>
|
<div className='dense'>
|
||||||
|
@ -110,4 +110,4 @@ function HelpCstEditor() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpCstEditor;
|
export default HelpRSEditor;
|
|
@ -22,7 +22,7 @@ import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpTermGraph() {
|
function HelpRSGraphTerm() {
|
||||||
const { colors } = useConceptOptions();
|
const { colors } = useConceptOptions();
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col'>
|
<div className='flex flex-col'>
|
||||||
|
@ -117,4 +117,4 @@ function HelpTermGraph() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpTermGraph;
|
export default HelpRSGraphTerm;
|
|
@ -14,7 +14,7 @@ import Divider from '@/components/ui/Divider';
|
||||||
import LinkTopic from '@/components/ui/LinkTopic';
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpRSFormItems() {
|
function HelpRSList() {
|
||||||
return (
|
return (
|
||||||
<div className='dense'>
|
<div className='dense'>
|
||||||
<h1>Список конституент</h1>
|
<h1>Список конституент</h1>
|
||||||
|
@ -63,4 +63,4 @@ function HelpRSFormItems() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpRSFormItems;
|
export default HelpRSList;
|
|
@ -17,7 +17,7 @@ import Divider from '@/components/ui/Divider';
|
||||||
import LinkTopic from '@/components/ui/LinkTopic';
|
import LinkTopic from '@/components/ui/LinkTopic';
|
||||||
import { HelpTopic } from '@/models/miscellaneous';
|
import { HelpTopic } from '@/models/miscellaneous';
|
||||||
|
|
||||||
function HelpRSFormMenu() {
|
function HelpRSMenu() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Редактирование схемы</h1>
|
<h1>Редактирование схемы</h1>
|
||||||
|
@ -100,4 +100,4 @@ function HelpRSFormMenu() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default HelpRSFormMenu;
|
export default HelpRSMenu;
|
|
@ -21,11 +21,12 @@ function InputNode(node: OssNodeInternal) {
|
||||||
<>
|
<>
|
||||||
<Handle type='source' position={Position.Bottom} />
|
<Handle type='source' position={Position.Bottom} />
|
||||||
|
|
||||||
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
|
<Overlay position='top-0 right-0' className='flex'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
||||||
noHover
|
noHover
|
||||||
title='Связанная КС'
|
noPadding
|
||||||
|
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
|
||||||
hideTitle={!controller.showTooltip}
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleOpenSchema();
|
handleOpenSchema();
|
||||||
|
|
|
@ -105,7 +105,7 @@ function NodeContextMenu({
|
||||||
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Редактировать'
|
text='Редактировать'
|
||||||
titleHtml={prepareTooltip('Редактировать операцию', 'Двойной клик')}
|
title='Редактировать операцию'
|
||||||
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
icon={<IconEdit2 size='1rem' className='icon-primary' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
onClick={handleEditOperation}
|
onClick={handleEditOperation}
|
||||||
|
@ -114,7 +114,7 @@ function NodeContextMenu({
|
||||||
{operation.result ? (
|
{operation.result ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Открыть схему'
|
text='Открыть схему'
|
||||||
title='Открыть привязанную КС'
|
titleHtml={prepareTooltip('Открыть привязанную КС', 'Двойной клик')}
|
||||||
icon={<IconRSForm size='1rem' className='icon-green' />}
|
icon={<IconRSForm size='1rem' className='icon-green' />}
|
||||||
disabled={controller.isProcessing}
|
disabled={controller.isProcessing}
|
||||||
onClick={handleOpenSchema}
|
onClick={handleOpenSchema}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Handle, Position } from 'reactflow';
|
import { Handle, Position } from 'reactflow';
|
||||||
|
|
||||||
import { IconRSForm } from '@/components/Icons';
|
import { IconConsolidation, IconRSForm } from '@/components/Icons';
|
||||||
import TooltipOperation from '@/components/info/TooltipOperation';
|
import TooltipOperation from '@/components/info/TooltipOperation';
|
||||||
import MiniButton from '@/components/ui/MiniButton.tsx';
|
import MiniButton from '@/components/ui/MiniButton.tsx';
|
||||||
import Overlay from '@/components/ui/Overlay';
|
import Overlay from '@/components/ui/Overlay';
|
||||||
|
@ -22,15 +22,31 @@ function OperationNode(node: OssNodeInternal) {
|
||||||
<>
|
<>
|
||||||
<Handle type='source' position={Position.Bottom} />
|
<Handle type='source' position={Position.Bottom} />
|
||||||
|
|
||||||
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
|
<Overlay position='top-0 right-0' className='flex flex-col gap-1'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
|
icon={
|
||||||
|
<IconRSForm
|
||||||
|
className={hasFile ? 'clr-text-green' : 'clr-text-red'}
|
||||||
|
size={node.data.operation.is_consolidation ? '0.6rem' : '0.75rem'}
|
||||||
|
/>
|
||||||
|
}
|
||||||
noHover
|
noHover
|
||||||
title='Связанная КС'
|
noPadding
|
||||||
|
title={hasFile ? 'Связанная КС' : 'Нет связанной КС'}
|
||||||
hideTitle={!controller.showTooltip}
|
hideTitle={!controller.showTooltip}
|
||||||
onClick={handleOpenSchema}
|
onClick={handleOpenSchema}
|
||||||
disabled={!hasFile}
|
disabled={!hasFile}
|
||||||
/>
|
/>
|
||||||
|
{node.data.operation.is_consolidation ? (
|
||||||
|
<MiniButton
|
||||||
|
icon={<IconConsolidation className='clr-text-primary' size='0.6rem' />}
|
||||||
|
disabled
|
||||||
|
noPadding
|
||||||
|
noHover
|
||||||
|
titleHtml='<b>Внимание!</b><br />Ромбовидный синтез</br/>Возможны дубликаты конституент'
|
||||||
|
hideTitle={!controller.showTooltip}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</Overlay>
|
</Overlay>
|
||||||
|
|
||||||
{!node.data.operation.is_owned ? (
|
{!node.data.operation.is_owned ? (
|
||||||
|
|
|
@ -299,9 +299,13 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
|
||||||
(event: CProps.EventMouse, node: OssNode) => {
|
(event: CProps.EventMouse, node: OssNode) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
handleEditOperation(Number(node.id));
|
if (node.data.operation.result) {
|
||||||
|
controller.openOperationSchema(Number(node.id));
|
||||||
|
} else {
|
||||||
|
handleEditOperation(Number(node.id));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[handleEditOperation]
|
[handleEditOperation, controller.openOperationSchema]
|
||||||
);
|
);
|
||||||
|
|
||||||
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
function handleKeyDown(event: React.KeyboardEvent<HTMLDivElement>) {
|
||||||
|
|
|
@ -124,7 +124,11 @@ function MenuOssTabs({ onDestroy }: MenuOssTabsProps) {
|
||||||
onClick={editMenu.toggle}
|
onClick={editMenu.toggle}
|
||||||
/>
|
/>
|
||||||
<Dropdown isOpen={editMenu.isOpen}>
|
<Dropdown isOpen={editMenu.isOpen}>
|
||||||
<div>операции над ОСС</div>
|
<DropdownButton
|
||||||
|
text='см. Граф синтеза'
|
||||||
|
titleHtml='Редактирование доступно <br/>через Граф синтеза'
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { urls } from '@/app/urls';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
import { useConceptOptions } from '@/context/ConceptOptionsContext';
|
||||||
|
import { useLibrary } from '@/context/LibraryContext';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
import { useOSS } from '@/context/OssContext';
|
import { useOSS } from '@/context/OssContext';
|
||||||
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
|
import DlgChangeInputSchema from '@/dialogs/DlgChangeInputSchema';
|
||||||
|
@ -30,7 +31,7 @@ import {
|
||||||
} from '@/models/oss';
|
} from '@/models/oss';
|
||||||
import { UserID, UserLevel } from '@/models/user';
|
import { UserID, UserLevel } from '@/models/user';
|
||||||
import { PARAMETER } from '@/utils/constants';
|
import { PARAMETER } from '@/utils/constants';
|
||||||
import { information } from '@/utils/labels';
|
import { errors, information } from '@/utils/labels';
|
||||||
|
|
||||||
export interface ICreateOperationPrompt {
|
export interface ICreateOperationPrompt {
|
||||||
x: number;
|
x: number;
|
||||||
|
@ -95,6 +96,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
const { adminMode } = useConceptOptions();
|
const { adminMode } = useConceptOptions();
|
||||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||||
const model = useOSS();
|
const model = useOSS();
|
||||||
|
const library = useLibrary();
|
||||||
|
|
||||||
const isMutable = useMemo(
|
const isMutable = useMemo(
|
||||||
() => accessLevel > UserLevel.READER && !model.schema?.read_only,
|
() => accessLevel > UserLevel.READER && !model.schema?.read_only,
|
||||||
|
@ -307,12 +309,20 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
|
||||||
|
|
||||||
const createInput = useCallback(
|
const createInput = useCallback(
|
||||||
(target: OperationID, positions: IOperationPosition[]) => {
|
(target: OperationID, positions: IOperationPosition[]) => {
|
||||||
|
const operation = model.schema?.operationByID.get(target);
|
||||||
|
if (!model.schema || !operation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (library.items.find(item => item.alias === operation.alias && item.location === model.schema!.location)) {
|
||||||
|
toast.error(errors.inputAlreadyExists);
|
||||||
|
return;
|
||||||
|
}
|
||||||
model.createInput({ target: target, positions: positions }, new_schema => {
|
model.createInput({ target: target, positions: positions }, new_schema => {
|
||||||
toast.success(information.newLibraryItem);
|
toast.success(information.newLibraryItem);
|
||||||
router.push(urls.schema(new_schema.id));
|
router.push(urls.schema(new_schema.id));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[model, router]
|
[model, library.items, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const promptEditInput = useCallback((target: OperationID, positions: IOperationPosition[]) => {
|
const promptEditInput = useCallback((target: OperationID, positions: IOperationPosition[]) => {
|
||||||
|
|
|
@ -89,16 +89,14 @@ function EditorLibraryItem({ item, isModified, controller }: EditorLibraryItemPr
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{accessLevel >= UserLevel.OWNER ? (
|
{accessLevel >= UserLevel.OWNER ? (
|
||||||
<Overlay position='top-[-0.5rem] left-[5.5rem] cc-icons'>
|
<Overlay position='top-[-0.5rem] left-[5.5rem]' className='cc-icons'>
|
||||||
<div className='flex items-start'>
|
<MiniButton
|
||||||
<MiniButton
|
title='Изменить редакторов'
|
||||||
title='Изменить редакторов'
|
noHover
|
||||||
noHover
|
onClick={() => controller.promptEditors()}
|
||||||
onClick={() => controller.promptEditors()}
|
icon={<IconEdit size='1rem' className='mt-1 icon-primary' />}
|
||||||
icon={<IconEdit size='1rem' className='mt-1 icon-primary' />}
|
disabled={isModified || controller.isProcessing}
|
||||||
disabled={isModified || controller.isProcessing}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Overlay>
|
</Overlay>
|
||||||
) : null}
|
) : null}
|
||||||
<LabeledValue
|
<LabeledValue
|
||||||
|
|
|
@ -30,7 +30,7 @@ import Dropdown from '@/components/ui/Dropdown';
|
||||||
import DropdownButton from '@/components/ui/DropdownButton';
|
import DropdownButton from '@/components/ui/DropdownButton';
|
||||||
import { useAccessMode } from '@/context/AccessModeContext';
|
import { useAccessMode } from '@/context/AccessModeContext';
|
||||||
import { useAuth } from '@/context/AuthContext';
|
import { useAuth } from '@/context/AuthContext';
|
||||||
import { useLibrary } from '@/context/LibraryContext';
|
import { useGlobalOss } from '@/context/GlobalOssContext';
|
||||||
import { useConceptNavigation } from '@/context/NavigationContext';
|
import { useConceptNavigation } from '@/context/NavigationContext';
|
||||||
import { useRSForm } from '@/context/RSFormContext';
|
import { useRSForm } from '@/context/RSFormContext';
|
||||||
import useDropdown from '@/hooks/useDropdown';
|
import useDropdown from '@/hooks/useDropdown';
|
||||||
|
@ -50,7 +50,7 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
const router = useConceptNavigation();
|
const router = useConceptNavigation();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const model = useRSForm();
|
const model = useRSForm();
|
||||||
const library = useLibrary();
|
const oss = useGlobalOss();
|
||||||
|
|
||||||
const { accessLevel, setAccessLevel } = useAccessMode();
|
const { accessLevel, setAccessLevel } = useAccessMode();
|
||||||
|
|
||||||
|
@ -185,11 +185,11 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
|
||||||
onClick={handleCreateNew}
|
onClick={handleCreateNew}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{library.globalOSS ? (
|
{oss.schema ? (
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Перейти к ОСС'
|
text='Перейти к ОСС'
|
||||||
icon={<IconOSS size='1rem' className='icon-primary' />}
|
icon={<IconOSS size='1rem' className='icon-primary' />}
|
||||||
onClick={() => router.push(urls.oss(library.globalOSS!.id, OssTabID.GRAPH))}
|
onClick={() => router.push(urls.oss(oss.schema!.id, OssTabID.GRAPH))}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
|
|
|
@ -953,7 +953,8 @@ export const errors = {
|
||||||
passwordsMismatch: 'Пароли не совпадают',
|
passwordsMismatch: 'Пароли не совпадают',
|
||||||
imageFailed: 'Ошибка при создании изображения',
|
imageFailed: 'Ошибка при создании изображения',
|
||||||
reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении',
|
reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении',
|
||||||
substituteInherited: 'Нельзя удалять наследованные конституенты при отождествлении'
|
substituteInherited: 'Нельзя удалять наследованные конституенты при отождествлении',
|
||||||
|
inputAlreadyExists: 'Концептуальная схема с таким именем уже существует'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* Module: Utility functions.
|
* Module: Utility functions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { AxiosHeaderValue, AxiosResponse } from 'axios';
|
import axios, { AxiosError, AxiosHeaderValue, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
import { prompts } from './labels';
|
import { prompts } from './labels';
|
||||||
|
|
||||||
|
@ -139,3 +139,22 @@ export function tripleToggleColor(value: boolean | undefined): string {
|
||||||
}
|
}
|
||||||
return value ? 'clr-text-green' : 'clr-text-red';
|
return value ? 'clr-text-green' : 'clr-text-red';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract error message from error object.
|
||||||
|
*/
|
||||||
|
export function extractErrorMessage(error: Error | AxiosError): string {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
if (error.response && error.response.status === 400) {
|
||||||
|
const data = error.response.data as Record<string, unknown>;
|
||||||
|
const keys = Object.keys(data);
|
||||||
|
if (keys.length === 1) {
|
||||||
|
const value = data[keys[0]];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return `${keys[0]}: ${value}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return error.message;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user