Compare commits

...

6 Commits

Author SHA1 Message Date
Ivan
27ba4a5da8 F: Add function for predecessor tracing
Some checks failed
Backend CI / build (3.12) (push) Has been cancelled
Frontend CI / build (22.x) (push) Has been cancelled
Update oss.py
2024-08-01 21:20:30 +03:00
Ivan
e29f7409c1 F: Improve RSForm and OSS UI + some fixes 2024-08-01 20:15:16 +03:00
Ivan
62cb9a5eeb R: update dependencies 2024-08-01 12:03:58 +03:00
Ivan
967eabde51 F: Improve RSForm UI for inherited cst 2024-08-01 11:55:45 +03:00
Ivan
e5977d259b F: Improve OSS <-> RSForm UI 2024-08-01 00:35:49 +03:00
Ivan
057850678a R: Backend cleanup and query improvements 2024-07-31 21:09:42 +03:00
69 changed files with 1429 additions and 1021 deletions

View File

@ -2,10 +2,8 @@
For more specific TODOs see comments in code For more specific TODOs see comments in code
[Functionality - PROGRESS] [Functionality - PROGRESS]
- Operational synthesis schema as LibraryItem ? - Design first user experience
- Private projects. Consider cooperative editing
- Library organization, search and exploration. Consider new user experience
- Private projects and permissions. Consider cooperative editing
[Functionality - PENDING] [Functionality - PENDING]

View File

@ -5,6 +5,7 @@ from .data_access import (
LibraryItemBaseSerializer, LibraryItemBaseSerializer,
LibraryItemCloneSerializer, LibraryItemCloneSerializer,
LibraryItemDetailsSerializer, LibraryItemDetailsSerializer,
LibraryItemReferenceSerializer,
LibraryItemSerializer, LibraryItemSerializer,
UsersListSerializer, UsersListSerializer,
UserTargetSerializer, UserTargetSerializer,

View File

@ -17,6 +17,14 @@ class LibraryItemBaseSerializer(serializers.ModelSerializer):
read_only_fields = ('id',) read_only_fields = ('id',)
class LibraryItemReferenceSerializer(serializers.ModelSerializer):
''' Serializer: reference to LibraryItem. '''
class Meta:
''' serializer metadata. '''
model = LibraryItem
fields = 'id', 'alias'
class LibraryItemSerializer(serializers.ModelSerializer): class LibraryItemSerializer(serializers.ModelSerializer):
''' Serializer: LibraryItem entry limited access. ''' ''' Serializer: LibraryItem entry limited access. '''
class Meta: class Meta:

View File

@ -1,13 +1,11 @@
''' Models: OSS API. ''' ''' Models: OSS API. '''
from typing import Optional from typing import Optional
from django.core.exceptions import ValidationError
from django.db import transaction from django.db import transaction
from django.db.models import QuerySet from django.db.models import QuerySet
from apps.library.models import Editor, LibraryItem, LibraryItemType from apps.library.models import Editor, LibraryItem, LibraryItemType
from apps.rsform.models import RSForm from apps.rsform.models import RSForm
from shared import messages as msg
from .Argument import Argument from .Argument import Argument
from .Inheritance import Inheritance from .Inheritance import Inheritance
@ -66,8 +64,6 @@ class OperationSchema:
@transaction.atomic @transaction.atomic
def create_operation(self, **kwargs) -> Operation: def create_operation(self, **kwargs) -> Operation:
''' Insert new operation. ''' ''' Insert new operation. '''
if kwargs['alias'] != '' and self.operations().filter(alias=kwargs['alias']).exists():
raise ValidationError(msg.aliasTaken(kwargs['alias']))
result = Operation.objects.create(oss=self.model, **kwargs) result = Operation.objects.create(oss=self.model, **kwargs)
self.save() self.save()
result.refresh_from_db() result.refresh_from_db()
@ -198,7 +194,7 @@ class OperationSchema:
# TODO: remove duplicates from diamond # TODO: remove duplicates from diamond
for cst in receiver.constituents(): for cst in receiver.constituents():
parent = parents.get(cst.id) parent = parents.get(cst.pk)
assert parent is not None assert parent is not None
Inheritance.objects.create( Inheritance.objects.create(
child=cst, child=cst,

View File

@ -10,4 +10,4 @@ from .data_access import (
OperationUpdateSerializer, OperationUpdateSerializer,
SetOperationInputSerializer SetOperationInputSerializer
) )
from .responses import NewOperationResponse, NewSchemaResponse from .responses import ConstituentaReferenceResponse, NewOperationResponse, NewSchemaResponse

View File

@ -84,33 +84,33 @@ class OperationUpdateSerializer(serializers.Serializer):
oss = cast(LibraryItem, self.context['oss']) oss = cast(LibraryItem, self.context['oss'])
for operation in attrs['arguments']: for operation in attrs['arguments']:
if operation.oss != oss: if operation.oss_id != oss.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
'arguments': msg.operationNotInOSS(oss.title) 'arguments': msg.operationNotInOSS(oss.title)
}) })
if 'substitutions' not in attrs: if 'substitutions' not in attrs:
return attrs return attrs
schemas = [arg.result.pk for arg in attrs['arguments'] if arg.result is not None] schemas = [arg.result_id for arg in attrs['arguments'] if arg.result is not None]
substitutions = attrs['substitutions'] substitutions = attrs['substitutions']
to_delete = {x['original'].pk for x in substitutions} to_delete = {x['original'].pk for x in substitutions}
deleted = set() deleted = set()
for item in substitutions: for item in substitutions:
original_cst = cast(Constituenta, item['original']) original_cst = cast(Constituenta, item['original'])
substitution_cst = cast(Constituenta, item['substitution']) substitution_cst = cast(Constituenta, item['substitution'])
if original_cst.schema.pk not in schemas: if original_cst.schema_id not in schemas:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.constituentaNotFromOperation() f'{original_cst.pk}': msg.constituentaNotFromOperation()
}) })
if substitution_cst.schema.pk not in schemas: if substitution_cst.schema_id not in schemas:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{substitution_cst.id}': msg.constituentaNotFromOperation() f'{substitution_cst.pk}': msg.constituentaNotFromOperation()
}) })
if original_cst.pk in deleted or substitution_cst.pk in to_delete: if original_cst.pk in deleted or substitution_cst.pk in to_delete:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.substituteDouble(original_cst.alias) f'{original_cst.pk}': msg.substituteDouble(original_cst.alias)
}) })
if original_cst.schema == substitution_cst.schema: if original_cst.schema_id == substitution_cst.schema_id:
raise serializers.ValidationError({ raise serializers.ValidationError({
'alias': msg.substituteTrivial(original_cst.alias) 'alias': msg.substituteTrivial(original_cst.alias)
}) })
@ -131,7 +131,7 @@ class OperationTargetSerializer(serializers.Serializer):
def validate(self, attrs): def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss']) oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target']) operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss: if oss and operation.oss_id != oss.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title) 'target': msg.operationNotInOSS(oss.title)
}) })
@ -155,7 +155,7 @@ class SetOperationInputSerializer(serializers.Serializer):
def validate(self, attrs): def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss']) oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target']) operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss: if oss and operation.oss_id != oss.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title) 'target': msg.operationNotInOSS(oss.title)
}) })

View File

@ -16,3 +16,9 @@ class NewSchemaResponse(serializers.Serializer):
''' Serializer: Create RSForm for input operation response. ''' ''' Serializer: Create RSForm for input operation response. '''
new_schema = LibraryItemSerializer() new_schema = LibraryItemSerializer()
oss = OperationSchemaSerializer() oss = OperationSchemaSerializer()
class ConstituentaReferenceResponse(serializers.Serializer):
''' Serializer: Constituenta reference. '''
id = serializers.IntegerField()
schema = serializers.IntegerField()

View File

@ -12,6 +12,8 @@ from rest_framework.response import Response
from apps.library.models import LibraryItem, LibraryItemType from apps.library.models import LibraryItem, LibraryItemType
from apps.library.serializers import LibraryItemSerializer from apps.library.serializers import LibraryItemSerializer
from apps.rsform.models import Constituenta
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg from shared import messages as msg
from shared import permissions from shared import permissions
@ -43,6 +45,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
permission_list = [permissions.ItemEditor] permission_list = [permissions.ItemEditor]
elif self.action in ['details']: elif self.action in ['details']:
permission_list = [permissions.ItemAnyone] permission_list = [permissions.ItemAnyone]
elif self.action in ['get_predecessor']:
permission_list = [permissions.Anyone]
else: else:
permission_list = [permissions.Anyone] permission_list = [permissions.Anyone]
return [permission() for permission in permission_list] return [permission() for permission in permission_list]
@ -253,7 +257,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
if operation.result is not None: if operation.result is not None:
can_edit = permissions.can_edit_item(request.user, operation.result) can_edit = permissions.can_edit_item(request.user, operation.result)
if can_edit: if can_edit or operation.operation_type == m.OperationType.SYNTHESIS:
operation.result.alias = operation.alias operation.result.alias = operation.alias
operation.result.title = operation.title operation.result.title = operation.title
operation.result.comment = operation.comment operation.result.comment = operation.comment
@ -306,3 +310,34 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
status=c.HTTP_200_OK, status=c.HTTP_200_OK,
data=s.OperationSchemaSerializer(oss.model).data data=s.OperationSchemaSerializer(oss.model).data
) )
@extend_schema(
summary='get predecessor for target constituenta',
tags=['OSS'],
request=CstTargetSerializer(),
responses={
c.HTTP_200_OK: s.ConstituentaReferenceResponse,
c.HTTP_400_BAD_REQUEST: None,
c.HTTP_403_FORBIDDEN: None,
c.HTTP_404_NOT_FOUND: None
}
)
@action(detail=False, methods=['post'], url_path='get-predecessor')
def get_predecessor(self, request: Request):
''' Get predecessor. '''
# TODO: add tests for this method
serializer = CstTargetSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
cst = cast(Constituenta, serializer.validated_data['target'])
inheritance = m.Inheritance.objects.filter(child=cst)
while inheritance.exists():
cst = cast(m.Inheritance, inheritance.first()).parent
inheritance = m.Inheritance.objects.filter(child=cst)
return Response(
status=c.HTTP_200_OK,
data={
'id': cst.pk,
'schema': cst.schema_id
}
)

View File

@ -142,7 +142,7 @@ class RSForm:
result.definition_resolved = resolver.resolve(result.definition_raw) result.definition_resolved = resolver.resolve(result.definition_raw)
result.save() result.save()
self.on_term_change([result.id]) self.on_term_change([result.pk])
result.refresh_from_db() result.refresh_from_db()
return result return result
@ -213,7 +213,7 @@ class RSForm:
count_bot = 0 count_bot = 0
size = len(listCst) size = len(listCst)
update_list = [] update_list = []
for cst in self.constituents().only('id', 'order').order_by('order'): for cst in self.constituents().only('order').order_by('order'):
if cst not in listCst: if cst not in listCst:
if count_top + 1 < target: if count_top + 1 < target:
cst.order = count_top + 1 cst.order = count_top + 1
@ -248,7 +248,7 @@ class RSForm:
mapping = {original.alias: substitution.alias} mapping = {original.alias: substitution.alias}
self.apply_mapping(mapping) self.apply_mapping(mapping)
original.delete() original.delete()
self.on_term_change([substitution.id]) self.on_term_change([substitution.pk])
def restore_order(self): def restore_order(self):
''' Restore order based on types and term graph. ''' ''' Restore order based on types and term graph. '''
@ -335,7 +335,7 @@ class RSForm:
definition_formal=text, definition_formal=text,
cst_type=cst_type cst_type=cst_type
) )
result.append(new_item.id) result.append(new_item.pk)
free_index = free_index + 1 free_index = free_index + 1
position = position + 1 position = position + 1
@ -347,7 +347,7 @@ class RSForm:
return return
update_list = \ update_list = \
Constituenta.objects \ Constituenta.objects \
.only('id', 'order', 'schema') \ .only('order') \
.filter(schema=self.model, order__gte=start) .filter(schema=self.model, order__gte=start)
for cst in update_list: for cst in update_list:
cst.order += shift cst.order += shift
@ -372,7 +372,7 @@ class RSForm:
@transaction.atomic @transaction.atomic
def _reset_order(self): def _reset_order(self):
order = 1 order = 1
for cst in self.constituents().only('id', 'order').order_by('order'): for cst in self.constituents().only('order').order_by('order'):
if cst.order != order: if cst.order != order:
cst.order = order cst.order = order
cst.save() cst.save()
@ -383,15 +383,15 @@ class RSForm:
result: Graph[int] = Graph() result: Graph[int] = Graph()
cst_list = \ cst_list = \
self.constituents() \ self.constituents() \
.only('id', 'order', 'alias', 'definition_formal') \ .only('alias', 'definition_formal') \
.order_by('order') .order_by('order')
for cst in cst_list: for cst in cst_list:
result.add_node(cst.id) result.add_node(cst.pk)
for cst in cst_list: for cst in cst_list:
for alias in extract_globals(cst.definition_formal): for alias in extract_globals(cst.definition_formal):
try: try:
child = cst_list.get(alias=alias) child = cst_list.get(alias=alias)
result.add_edge(src=child.id, dest=cst.id) result.add_edge(src=child.pk, dest=cst.pk)
except Constituenta.DoesNotExist: except Constituenta.DoesNotExist:
pass pass
return result return result
@ -401,15 +401,15 @@ class RSForm:
result: Graph[int] = Graph() result: Graph[int] = Graph()
cst_list = \ cst_list = \
self.constituents() \ self.constituents() \
.only('id', 'order', 'alias', 'term_raw') \ .only('alias', 'term_raw') \
.order_by('order') .order_by('order')
for cst in cst_list: for cst in cst_list:
result.add_node(cst.id) result.add_node(cst.pk)
for cst in cst_list: for cst in cst_list:
for alias in extract_entities(cst.term_raw): for alias in extract_entities(cst.term_raw):
try: try:
child = cst_list.get(alias=alias) child = cst_list.get(alias=alias)
result.add_edge(src=child.id, dest=cst.id) result.add_edge(src=child.pk, dest=cst.pk)
except Constituenta.DoesNotExist: except Constituenta.DoesNotExist:
pass pass
return result return result
@ -419,15 +419,15 @@ class RSForm:
result: Graph[int] = Graph() result: Graph[int] = Graph()
cst_list = \ cst_list = \
self.constituents() \ self.constituents() \
.only('id', 'order', 'alias', 'definition_raw') \ .only('alias', 'definition_raw') \
.order_by('order') .order_by('order')
for cst in cst_list: for cst in cst_list:
result.add_node(cst.id) result.add_node(cst.pk)
for cst in cst_list: for cst in cst_list:
for alias in extract_entities(cst.definition_raw): for alias in extract_entities(cst.definition_raw):
try: try:
child = cst_list.get(alias=alias) child = cst_list.get(alias=alias)
result.add_edge(src=child.id, dest=cst.id) result.add_edge(src=child.pk, dest=cst.pk)
except Constituenta.DoesNotExist: except Constituenta.DoesNotExist:
pass pass
return result return result
@ -440,16 +440,16 @@ class SemanticInfo:
self._graph = schema._graph_formal() self._graph = schema._graph_formal()
self._items = list( self._items = list(
schema.constituents() schema.constituents()
.only('id', 'alias', 'cst_type', 'definition_formal') .only('alias', 'cst_type', 'definition_formal')
.order_by('order') .order_by('order')
) )
self._cst_by_alias = {cst.alias: cst for cst in self._items} self._cst_by_alias = {cst.alias: cst for cst in self._items}
self._cst_by_ID = {cst.id: cst for cst in self._items} self._cst_by_ID = {cst.pk: cst for cst in self._items}
self.info = { self.info = {
cst.id: { cst.pk: {
'is_simple': False, 'is_simple': False,
'is_template': False, 'is_template': False,
'parent': cst.id, 'parent': cst.pk,
'children': [] 'children': []
} }
for cst in self._items for cst in self._items
@ -491,7 +491,7 @@ class SemanticInfo:
if target.cst_type == CstType.STRUCTURED or is_base_set(target.cst_type): if target.cst_type == CstType.STRUCTURED or is_base_set(target.cst_type):
return False return False
dependencies = self._graph.inputs[target.id] dependencies = self._graph.inputs[target.pk]
has_complex_dependency = any( has_complex_dependency = any(
self.is_template(cst_id) and self.is_template(cst_id) and
not self.is_simple_expression(cst_id) for cst_id in dependencies not self.is_simple_expression(cst_id) for cst_id in dependencies
@ -507,18 +507,18 @@ class SemanticInfo:
def _infer_parent(self, target: Constituenta) -> int: def _infer_parent(self, target: Constituenta) -> int:
sources = self._extract_sources(target) sources = self._extract_sources(target)
if len(sources) != 1: if len(sources) != 1:
return target.id return target.pk
parent_id = next(iter(sources)) parent_id = next(iter(sources))
parent = self._cst_by_ID[parent_id] parent = self._cst_by_ID[parent_id]
if is_base_set(parent.cst_type): if is_base_set(parent.cst_type):
return target.id return target.pk
return parent_id return parent_id
def _extract_sources(self, target: Constituenta) -> set[int]: def _extract_sources(self, target: Constituenta) -> set[int]:
sources: set[int] = set() sources: set[int] = set()
if not is_functional(target.cst_type): if not is_functional(target.cst_type):
for parent_id in self._graph.inputs[target.id]: for parent_id in self._graph.inputs[target.pk]:
parent_info = self[parent_id] parent_info = self[parent_id]
if not parent_info['is_template'] or not parent_info['is_simple']: if not parent_info['is_template'] or not parent_info['is_simple']:
sources.add(parent_info['parent']) sources.add(parent_info['parent'])
@ -531,7 +531,7 @@ class SemanticInfo:
if not parent: if not parent:
continue continue
parent_info = self[parent.id] parent_info = self[parent.pk]
if not parent_info['is_template'] or not parent_info['is_simple']: if not parent_info['is_template'] or not parent_info['is_simple']:
sources.add(parent_info['parent']) sources.add(parent_info['parent'])
@ -542,7 +542,7 @@ class SemanticInfo:
if not parent: if not parent:
continue continue
parent_info = self[parent.id] parent_info = self[parent.pk]
if not is_base_set(parent.cst_type) and \ if not is_base_set(parent.cst_type) and \
(not parent_info['is_template'] or not parent_info['is_simple']): (not parent_info['is_template'] or not parent_info['is_simple']):
sources.add(parent_info['parent']) sources.add(parent_info['parent'])
@ -567,10 +567,10 @@ class _OrderManager:
self._graph = schema._graph_formal() self._graph = schema._graph_formal()
self._items = list( self._items = list(
schema.constituents() schema.constituents()
.only('id', 'order', 'alias', 'cst_type', 'definition_formal') .only('order', 'alias', 'cst_type', 'definition_formal')
.order_by('order') .order_by('order')
) )
self._cst_by_ID = {cst.id: cst for cst in self._items} self._cst_by_ID = {cst.pk: cst for cst in self._items}
def restore_order(self) -> None: def restore_order(self) -> None:
''' Implement order restoration process. ''' ''' Implement order restoration process. '''
@ -582,20 +582,20 @@ class _OrderManager:
self._save_order() self._save_order()
def _fix_topological(self) -> None: def _fix_topological(self) -> None:
sorted_ids = self._graph.sort_stable([cst.id for cst in self._items]) sorted_ids = self._graph.sort_stable([cst.pk for cst in self._items])
sorted_items = [next(cst for cst in self._items if cst.id == id) for id in sorted_ids] sorted_items = [next(cst for cst in self._items if cst.pk == id) for id in sorted_ids]
self._items = sorted_items self._items = sorted_items
def _fix_kernel(self) -> None: def _fix_kernel(self) -> None:
result = [cst for cst in self._items if cst.cst_type == CstType.BASE] result = [cst for cst in self._items if cst.cst_type == CstType.BASE]
result = result + [cst for cst in self._items if cst.cst_type == CstType.CONSTANT] result = result + [cst for cst in self._items if cst.cst_type == CstType.CONSTANT]
kernel = [ kernel = [
cst.id for cst in self._items if cst.pk for cst in self._items if
cst.cst_type in [CstType.STRUCTURED, CstType.AXIOM] or cst.cst_type in [CstType.STRUCTURED, CstType.AXIOM] or
self._cst_by_ID[self._semantic.parent(cst.id)].cst_type == CstType.STRUCTURED self._cst_by_ID[self._semantic.parent(cst.pk)].cst_type == CstType.STRUCTURED
] ]
kernel = kernel + self._graph.expand_inputs(kernel) kernel = kernel + self._graph.expand_inputs(kernel)
result = result + [cst for cst in self._items if result.count(cst) == 0 and cst.id in kernel] result = result + [cst for cst in self._items if result.count(cst) == 0 and cst.pk in kernel]
result = result + [cst for cst in self._items if result.count(cst) == 0] result = result + [cst for cst in self._items if result.count(cst) == 0]
self._items = result self._items = result
@ -606,11 +606,11 @@ class _OrderManager:
if cst in marked: if cst in marked:
continue continue
result.append(cst) result.append(cst)
children = self._semantic[cst.id]['children'] children = self._semantic[cst.pk]['children']
if len(children) == 0: if len(children) == 0:
continue continue
for child in self._items: for child in self._items:
if child.id in children: if child.pk in children:
marked.add(child) marked.add(child)
result.append(child) result.append(child)
self._items = result self._items = result

View File

@ -4,11 +4,17 @@ from typing import Optional, cast
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import Q
from rest_framework import serializers from rest_framework import serializers
from rest_framework.serializers import PrimaryKeyRelatedField as PKField from rest_framework.serializers import PrimaryKeyRelatedField as PKField
from apps.library.models import LibraryItem from apps.library.models import LibraryItem
from apps.library.serializers import LibraryItemBaseSerializer, LibraryItemDetailsSerializer from apps.library.serializers import (
LibraryItemBaseSerializer,
LibraryItemDetailsSerializer,
LibraryItemReferenceSerializer
)
from apps.oss.models import Inheritance
from shared import messages as msg from shared import messages as msg
from ..models import Constituenta, CstType, RSForm from ..models import Constituenta, CstType, RSForm
@ -48,7 +54,7 @@ class CstSerializer(serializers.ModelSerializer):
term_changed = data['term_resolved'] != instance.term_resolved term_changed = data['term_resolved'] != instance.term_resolved
result: Constituenta = super().update(instance, data) result: Constituenta = super().update(instance, data)
if term_changed: if term_changed:
schema.on_term_change([result.id]) schema.on_term_change([result.pk])
result.refresh_from_db() result.refresh_from_db()
schema.save() schema.save()
return result return result
@ -90,6 +96,12 @@ class RSFormSerializer(serializers.ModelSerializer):
items = serializers.ListField( items = serializers.ListField(
child=CstSerializer() child=CstSerializer()
) )
inheritance = serializers.ListField(
child=serializers.ListField(child=serializers.IntegerField())
)
oss = serializers.ListField(
child=LibraryItemReferenceSerializer()
)
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
@ -101,6 +113,15 @@ class RSFormSerializer(serializers.ModelSerializer):
result['items'] = [] result['items'] = []
for cst in RSForm(instance).constituents().order_by('order'): for cst in RSForm(instance).constituents().order_by('order'):
result['items'].append(CstSerializer(cst).data) result['items'].append(CstSerializer(cst).data)
result['inheritance'] = []
for link in Inheritance.objects.filter(Q(child__schema=instance) | Q(parent__schema=instance)):
result['inheritance'].append([link.child.pk, link.parent.pk])
result['oss'] = []
for oss in LibraryItem.objects.filter(items__result=instance).only('alias'):
result['oss'].append({
'id': oss.pk,
'alias': oss.alias
})
return result return result
def to_versioned_data(self) -> dict: def to_versioned_data(self) -> dict:
@ -109,6 +130,8 @@ class RSFormSerializer(serializers.ModelSerializer):
del result['versions'] del result['versions']
del result['subscribers'] del result['subscribers']
del result['editors'] del result['editors']
del result['inheritance']
del result['oss']
del result['owner'] del result['owner']
del result['visible'] del result['visible']
@ -208,23 +231,19 @@ class CstTargetSerializer(serializers.Serializer):
target = PKField(many=False, queryset=Constituenta.objects.all()) target = PKField(many=False, queryset=Constituenta.objects.all())
def validate(self, attrs): def validate(self, attrs):
schema = cast(LibraryItem, self.context['schema']) if 'schema' in self.context:
cst = cast(Constituenta, attrs['target']) schema = cast(LibraryItem, self.context['schema'])
if schema and cst.schema != schema: cst = cast(Constituenta, attrs['target'])
raise serializers.ValidationError({ if schema and cst.schema_id != schema.pk:
f'{cst.id}': msg.constituentaNotInRSform(schema.title) raise serializers.ValidationError({
}) f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
if cst.cst_type not in [CstType.FUNCTION, CstType.STRUCTURED, CstType.TERM]: })
raise serializers.ValidationError({
f'{cst.id}': msg.constituentaNoStructure()
})
self.instance = cst
return attrs return attrs
class CstRenameSerializer(serializers.Serializer): class CstRenameSerializer(serializers.Serializer):
''' Serializer: Constituenta renaming. ''' ''' Serializer: Constituenta renaming. '''
target = PKField(many=False, queryset=Constituenta.objects.all()) target = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
alias = serializers.CharField() alias = serializers.CharField()
cst_type = serializers.CharField() cst_type = serializers.CharField()
@ -232,9 +251,9 @@ class CstRenameSerializer(serializers.Serializer):
attrs = super().validate(attrs) attrs = super().validate(attrs)
schema = cast(LibraryItem, self.context['schema']) schema = cast(LibraryItem, self.context['schema'])
cst = cast(Constituenta, attrs['target']) cst = cast(Constituenta, attrs['target'])
if cst.schema != schema: if cst.schema_id != schema.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.id}': msg.constituentaNotInRSform(schema.title) f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
}) })
new_alias = self.initial_data['alias'] new_alias = self.initial_data['alias']
if cst.alias == new_alias: if cst.alias == new_alias:
@ -258,9 +277,9 @@ class CstListSerializer(serializers.Serializer):
return attrs return attrs
for item in attrs['items']: for item in attrs['items']:
if item.schema != schema: if item.schema_id != schema.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{item.id}': msg.constituentaNotInRSform(schema.title) f'{item.pk}': msg.constituentaNotInRSform(schema.title)
}) })
return attrs return attrs
@ -272,8 +291,8 @@ class CstMoveSerializer(CstListSerializer):
class SubstitutionSerializerBase(serializers.Serializer): class SubstitutionSerializerBase(serializers.Serializer):
''' Serializer: Basic substitution. ''' ''' Serializer: Basic substitution. '''
original = PKField(many=False, queryset=Constituenta.objects.all()) original = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
substitution = PKField(many=False, queryset=Constituenta.objects.all()) substitution = PKField(many=False, queryset=Constituenta.objects.only('alias', 'schema'))
class CstSubstituteSerializer(serializers.Serializer): class CstSubstituteSerializer(serializers.Serializer):
@ -291,17 +310,17 @@ class CstSubstituteSerializer(serializers.Serializer):
substitution_cst = cast(Constituenta, item['substitution']) substitution_cst = cast(Constituenta, item['substitution'])
if original_cst.pk in deleted: if original_cst.pk in deleted:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.substituteDouble(original_cst.alias) f'{original_cst.pk}': msg.substituteDouble(original_cst.alias)
}) })
if original_cst.alias == substitution_cst.alias: if original_cst.alias == substitution_cst.alias:
raise serializers.ValidationError({ raise serializers.ValidationError({
'alias': msg.substituteTrivial(original_cst.alias) 'alias': msg.substituteTrivial(original_cst.alias)
}) })
if original_cst.schema != schema: if original_cst.schema_id != schema.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
'original': msg.constituentaNotInRSform(schema.title) 'original': msg.constituentaNotInRSform(schema.title)
}) })
if substitution_cst.schema != schema: if substitution_cst.schema_id != schema.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
'substitution': msg.constituentaNotInRSform(schema.title) 'substitution': msg.constituentaNotInRSform(schema.title)
}) })
@ -325,39 +344,39 @@ class InlineSynthesisSerializer(serializers.Serializer):
if user.is_anonymous or (schema_out.owner != user and not user.is_staff): if user.is_anonymous or (schema_out.owner != user and not user.is_staff):
raise PermissionDenied({ raise PermissionDenied({
'message': msg.schemaForbidden(), 'message': msg.schemaForbidden(),
'object_id': schema_in.id 'object_id': schema_in.pk
}) })
constituents = cast(list[Constituenta], attrs['items']) constituents = cast(list[Constituenta], attrs['items'])
for cst in constituents: for cst in constituents:
if cst.schema != schema_in: if cst.schema_id != schema_in.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{cst.id}': msg.constituentaNotInRSform(schema_in.title) f'{cst.pk}': msg.constituentaNotInRSform(schema_in.title)
}) })
deleted = set() deleted = set()
for item in attrs['substitutions']: for item in attrs['substitutions']:
original_cst = cast(Constituenta, item['original']) original_cst = cast(Constituenta, item['original'])
substitution_cst = cast(Constituenta, item['substitution']) substitution_cst = cast(Constituenta, item['substitution'])
if original_cst.schema == schema_in: if original_cst.schema_id == schema_in.pk:
if original_cst not in constituents: if original_cst not in constituents:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.substitutionNotInList() f'{original_cst.pk}': msg.substitutionNotInList()
}) })
if substitution_cst.schema != schema_out: if substitution_cst.schema_id != schema_out.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{substitution_cst.id}': msg.constituentaNotInRSform(schema_out.title) f'{substitution_cst.pk}': msg.constituentaNotInRSform(schema_out.title)
}) })
else: else:
if substitution_cst not in constituents: if substitution_cst not in constituents:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{substitution_cst.id}': msg.substitutionNotInList() f'{substitution_cst.pk}': msg.substitutionNotInList()
}) })
if original_cst.schema != schema_out: if original_cst.schema_id != schema_out.pk:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.constituentaNotInRSform(schema_out.title) f'{original_cst.pk}': msg.constituentaNotInRSform(schema_out.title)
}) })
if original_cst.pk in deleted: if original_cst.pk in deleted:
raise serializers.ValidationError({ raise serializers.ValidationError({
f'{original_cst.id}': msg.substituteDouble(original_cst.alias) f'{original_cst.pk}': msg.substituteDouble(original_cst.alias)
}) })
deleted.add(original_cst.pk) deleted.add(original_cst.pk)
return attrs return attrs

View File

@ -102,6 +102,8 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved) self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved)
self.assertEqual(response.data['subscribers'], [self.user.pk]) self.assertEqual(response.data['subscribers'], [self.user.pk])
self.assertEqual(response.data['editors'], []) self.assertEqual(response.data['editors'], [])
self.assertEqual(response.data['inheritance'], [])
self.assertEqual(response.data['oss'], [])
self.executeOK(item=self.unowned_id) self.executeOK(item=self.unowned_id)
self.executeForbidden(item=self.private_id) self.executeForbidden(item=self.private_id)

View File

@ -147,13 +147,17 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema}) serializer = s.CstTargetSerializer(data=request.data, context={'schema': schema})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
cst = cast(m.Constituenta, serializer.validated_data['target']) cst = cast(m.Constituenta, serializer.validated_data['target'])
if cst.cst_type not in [m.CstType.FUNCTION, m.CstType.STRUCTURED, m.CstType.TERM]:
raise ValidationError({
f'{cst.pk}': msg.constituentaNoStructure()
})
schema_details = s.RSFormParseSerializer(schema).data['items'] schema_details = s.RSFormParseSerializer(schema).data['items']
cst_parse = next(item for item in schema_details if item['id'] == cst.id)['parse'] cst_parse = next(item for item in schema_details if item['id'] == cst.pk)['parse']
if not cst_parse['typification']: if not cst_parse['typification']:
return Response( return Response(
status=c.HTTP_400_BAD_REQUEST, status=c.HTTP_400_BAD_REQUEST,
data={f'{cst.id}': msg.constituentaNoStructure()} data={f'{cst.pk}': msg.constituentaNoStructure()}
) )
result = m.RSForm(schema).produce_structure(cst, cst_parse) result = m.RSForm(schema).produce_structure(cst, cst_parse)

View File

@ -28,7 +28,7 @@ def _extract_item(obj: Any) -> LibraryItem:
return cast(LibraryItem, obj.item) return cast(LibraryItem, obj.item)
raise PermissionDenied({ raise PermissionDenied({
'message': 'Invalid type error. Please contact developers', 'message': 'Invalid type error. Please contact developers',
'object_id': obj.id 'object_id': obj.pk
}) })

View File

@ -8,13 +8,13 @@
"name": "frontend", "name": "frontend",
"version": "1.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@lezer/lr": "^1.4.1", "@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.19.3", "@tanstack/react-table": "^8.19.3",
"@uiw/codemirror-themes": "^4.23.0", "@uiw/codemirror-themes": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0", "@uiw/react-codemirror": "^4.23.0",
"axios": "^1.7.2", "axios": "^1.7.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^11.3.17", "framer-motion": "^11.3.19",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"react": "^18.3.1", "react": "^18.3.1",
@ -32,16 +32,16 @@
"react-zoom-pan-pinch": "^3.6.1", "react-zoom-pan-pinch": "^3.6.1",
"reactflow": "^11.11.4", "reactflow": "^11.11.4",
"reagraph": "^4.19.2", "reagraph": "^4.19.2",
"use-debounce": "^10.0.1" "use-debounce": "^10.0.2"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^20.14.12", "@types/node": "^20.14.13",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.17.0", "@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.57.0", "eslint": "^8.57.0",
@ -52,7 +52,7 @@
"jest": "^29.7.0", "jest": "^29.7.0",
"postcss": "^8.4.40", "postcss": "^8.4.40",
"tailwindcss": "^3.4.7", "tailwindcss": "^3.4.7",
"ts-jest": "^29.2.3", "ts-jest": "^29.2.4",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vite": "^5.3.5" "vite": "^5.3.5"
} }
@ -98,9 +98,9 @@
} }
}, },
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.24.9", "version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz",
"integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -108,22 +108,22 @@
} }
}, },
"node_modules/@babel/core": { "node_modules/@babel/core": {
"version": "7.24.9", "version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz",
"integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ampproject/remapping": "^2.2.0", "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.24.7", "@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.9", "@babel/generator": "^7.25.0",
"@babel/helper-compilation-targets": "^7.24.8", "@babel/helper-compilation-targets": "^7.25.2",
"@babel/helper-module-transforms": "^7.24.9", "@babel/helper-module-transforms": "^7.25.2",
"@babel/helpers": "^7.24.8", "@babel/helpers": "^7.25.0",
"@babel/parser": "^7.24.8", "@babel/parser": "^7.25.0",
"@babel/template": "^7.24.7", "@babel/template": "^7.25.0",
"@babel/traverse": "^7.24.8", "@babel/traverse": "^7.25.2",
"@babel/types": "^7.24.9", "@babel/types": "^7.25.2",
"convert-source-map": "^2.0.0", "convert-source-map": "^2.0.0",
"debug": "^4.1.0", "debug": "^4.1.0",
"gensync": "^1.0.0-beta.2", "gensync": "^1.0.0-beta.2",
@ -149,12 +149,12 @@
} }
}, },
"node_modules/@babel/generator": { "node_modules/@babel/generator": {
"version": "7.24.10", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.10.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz",
"integrity": "sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg==", "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.24.9", "@babel/types": "^7.25.0",
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25", "@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^2.5.1" "jsesc": "^2.5.1"
@ -164,13 +164,13 @@
} }
}, },
"node_modules/@babel/helper-compilation-targets": { "node_modules/@babel/helper-compilation-targets": {
"version": "7.24.8", "version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz",
"integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/compat-data": "^7.24.8", "@babel/compat-data": "^7.25.2",
"@babel/helper-validator-option": "^7.24.8", "@babel/helper-validator-option": "^7.24.8",
"browserslist": "^4.23.1", "browserslist": "^4.23.1",
"lru-cache": "^5.1.1", "lru-cache": "^5.1.1",
@ -190,43 +190,6 @@
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
}, },
"node_modules/@babel/helper-environment-visitor": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz",
"integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-function-name": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz",
"integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.24.7",
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz",
"integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": { "node_modules/@babel/helper-module-imports": {
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz",
@ -241,17 +204,16 @@
} }
}, },
"node_modules/@babel/helper-module-transforms": { "node_modules/@babel/helper-module-transforms": {
"version": "7.24.9", "version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz",
"integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-environment-visitor": "^7.24.7",
"@babel/helper-module-imports": "^7.24.7", "@babel/helper-module-imports": "^7.24.7",
"@babel/helper-simple-access": "^7.24.7", "@babel/helper-simple-access": "^7.24.7",
"@babel/helper-split-export-declaration": "^7.24.7", "@babel/helper-validator-identifier": "^7.24.7",
"@babel/helper-validator-identifier": "^7.24.7" "@babel/traverse": "^7.25.2"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -284,18 +246,6 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-split-export-declaration": {
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz",
"integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.24.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.24.8", "version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
@ -325,14 +275,14 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.24.8", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz",
"integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.24.7", "@babel/template": "^7.25.0",
"@babel/types": "^7.24.8" "@babel/types": "^7.25.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -354,10 +304,13 @@
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.24.8", "version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
"license": "MIT", "license": "MIT",
"dependencies": {
"@babel/types": "^7.25.2"
},
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -589,9 +542,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.24.8", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
"integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
@ -601,33 +554,30 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.24.7", "version": "7.25.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz",
"integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.24.7", "@babel/code-frame": "^7.24.7",
"@babel/parser": "^7.24.7", "@babel/parser": "^7.25.0",
"@babel/types": "^7.24.7" "@babel/types": "^7.25.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/traverse": { "node_modules/@babel/traverse": {
"version": "7.24.8", "version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz",
"integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.24.7", "@babel/code-frame": "^7.24.7",
"@babel/generator": "^7.24.8", "@babel/generator": "^7.25.0",
"@babel/helper-environment-visitor": "^7.24.7", "@babel/parser": "^7.25.3",
"@babel/helper-function-name": "^7.24.7", "@babel/template": "^7.25.0",
"@babel/helper-hoist-variables": "^7.24.7", "@babel/types": "^7.25.2",
"@babel/helper-split-export-declaration": "^7.24.7",
"@babel/parser": "^7.24.8",
"@babel/types": "^7.24.8",
"debug": "^4.3.1", "debug": "^4.3.1",
"globals": "^11.1.0" "globals": "^11.1.0"
}, },
@ -636,9 +586,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.24.9", "version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.24.8", "@babel/helper-string-parser": "^7.24.8",
@ -741,9 +691,9 @@
} }
}, },
"node_modules/@codemirror/view": { "node_modules/@codemirror/view": {
"version": "6.29.0", "version": "6.29.1",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.29.0.tgz", "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.29.1.tgz",
"integrity": "sha512-ED4ims4fkf7eOA+HYLVP8VVg3NMllt1FPm9PEJBfYFnidKlRITBaua38u68L1F60eNtw2YNcDN5jsIzhKZwWQA==", "integrity": "sha512-7r+DlO/QFwPqKp73uq5mmrS4TuLPUVotbNOKYzN3OLP5ScrOVXcm4g13/48b6ZXGhdmzMinzFYqH0vo+qihIkQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/state": "^6.4.0", "@codemirror/state": "^6.4.0",
@ -2562,9 +2512,9 @@
} }
}, },
"node_modules/@lezer/lr": { "node_modules/@lezer/lr": {
"version": "1.4.1", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.1.tgz", "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
"integrity": "sha512-CHsKq8DMKBf9b3yXPDIU4DbH+ZJd/sJdYOW2llbW/HudP5u0VS6Bfq1hLYfgU7uAYGFIyGGQIsSOXGPEErZiJw==", "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@lezer/common": "^1.0.0" "@lezer/common": "^1.0.0"
@ -2970,9 +2920,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.19.2.tgz",
"integrity": "sha512-JlPfZ/C7yn5S5p0yKk7uhHTTnFlvTgLetl2VxqE518QgyM7C9bSfFTYvB/Q/ftkq0RIPY4ySxTz+/wKJ/dXC0w==", "integrity": "sha512-OHflWINKtoCFSpm/WmuQaWW4jeX+3Qt3XQDepkkiFTsoxFc5BpF3Z5aDxFZgBqRjO6ATP5+b1iilp4kGIZVWlA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -2984,9 +2934,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.19.2.tgz",
"integrity": "sha512-RDxUSY8D1tWYfn00DDi5myxKgOk6RvWPxhmWexcICt/MEC6yEMr4HNCu1sXXYLw8iAsg0D44NuU+qNq7zVWCrw==", "integrity": "sha512-k0OC/b14rNzMLDOE6QMBCjDRm3fQOHAL8Ldc9bxEWvMo4Ty9RY6rWmGetNTWhPo+/+FNd1lsQYRd0/1OSix36A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -2998,9 +2948,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.19.2.tgz",
"integrity": "sha512-emvKHL4B15x6nlNTBMtIaC9tLPRpeA5jMvRLXVbl/W9Ie7HhkrE7KQjvgS9uxgatL1HmHWDXk5TTS4IaNJxbAA==", "integrity": "sha512-IIARRgWCNWMTeQH+kr/gFTHJccKzwEaI0YSvtqkEBPj7AshElFq89TyreKNFAGh5frLfDCbodnq+Ye3dqGKPBw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3012,9 +2962,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.19.2.tgz",
"integrity": "sha512-fO28cWA1dC57qCd+D0rfLC4VPbh6EOJXrreBmFLWPGI9dpMlER2YwSPZzSGfq11XgcEpPukPTfEVFtw2q2nYJg==", "integrity": "sha512-52udDMFDv54BTAdnw+KXNF45QCvcJOcYGl3vQkp4vARyrcdI/cXH8VXTEv/8QWfd6Fru8QQuw1b2uNersXOL0g==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3026,9 +2976,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.19.2.tgz",
"integrity": "sha512-2Rn36Ubxdv32NUcfm0wB1tgKqkQuft00PtM23VqLuCUR4N5jcNWDoV5iBC9jeGdgS38WK66ElncprqgMUOyomw==", "integrity": "sha512-r+SI2t8srMPYZeoa1w0o/AfoVt9akI1ihgazGYPQGRilVAkuzMGiTtexNZkrPkQsyFrvqq/ni8f3zOnHw4hUbA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3040,9 +2990,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-musleabihf": { "node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.19.2.tgz",
"integrity": "sha512-gJuzIVdq/X1ZA2bHeCGCISe0VWqCoNT8BvkQ+BfsixXwTOndhtLUpOg0A1Fcx/+eA6ei6rMBzlOz4JzmiDw7JQ==", "integrity": "sha512-+tYiL4QVjtI3KliKBGtUU7yhw0GMcJJuB9mLTCEauHEsqfk49gtUBXGtGP3h1LW8MbaTY6rSFIQV1XOBps1gBA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3054,9 +3004,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.19.2.tgz",
"integrity": "sha512-0EkX2HYPkSADo9cfeGFoQ7R0/wTKb7q6DdwI4Yn/ULFE1wuRRCHybxpl2goQrx4c/yzK3I8OlgtBu4xvted0ug==", "integrity": "sha512-OR5DcvZiYN75mXDNQQxlQPTv4D+uNCUsmSCSY2FolLf9W5I4DSoJyg7z9Ea3TjKfhPSGgMJiey1aWvlWuBzMtg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3068,9 +3018,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.19.2.tgz",
"integrity": "sha512-GlIQRj9px52ISomIOEUq/IojLZqzkvRpdP3cLgIE1wUWaiU5Takwlzpz002q0Nxxr1y2ZgxC2obWxjr13lvxNQ==", "integrity": "sha512-Hw3jSfWdUSauEYFBSFIte6I8m6jOj+3vifLg8EU3lreWulAUpch4JBjDMtlKosrBzkr0kwKgL9iCfjA8L3geoA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3082,9 +3032,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": { "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.19.2.tgz",
"integrity": "sha512-N6cFJzssruDLUOKfEKeovCKiHcdwVYOT1Hs6dovDQ61+Y9n3Ek4zXvtghPPelt6U0AH4aDGnDLb83uiJMkWYzQ==", "integrity": "sha512-rhjvoPBhBwVnJRq/+hi2Q3EMiVF538/o9dBuj9TVLclo9DuONqt5xfWSaE6MYiFKpo/lFPJ/iSI72rYWw5Hc7w==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -3096,9 +3046,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.19.2.tgz",
"integrity": "sha512-2DnD3mkS2uuam/alF+I7M84koGwvn3ZVD7uG+LEWpyzo/bq8+kKnus2EVCkcvh6PlNB8QPNFOz6fWd5N8o1CYg==", "integrity": "sha512-EAz6vjPwHHs2qOCnpQkw4xs14XJq84I81sDRGPEjKPFVPBw7fwvtwhVjcZR6SLydCv8zNK8YGFblKWd/vRmP8g==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -3110,9 +3060,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": { "node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.19.2.tgz",
"integrity": "sha512-D6pkaF7OpE7lzlTOFCB2m3Ngzu2ykw40Nka9WmKGUOTS3xcIieHe82slQlNq69sVB04ch73thKYIWz/Ian8DUA==", "integrity": "sha512-IJSUX1xb8k/zN9j2I7B5Re6B0NNJDJ1+soezjNojhT8DEVeDNptq2jgycCOpRhyGj0+xBn7Cq+PK7Q+nd2hxLA==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -3124,9 +3074,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.19.2.tgz",
"integrity": "sha512-HBndjQLP8OsdJNSxpNIN0einbDmRFg9+UQeZV1eiYupIRuZsDEoeGU43NQsS34Pp166DtwQOnpcbV/zQxM+rWA==", "integrity": "sha512-OgaToJ8jSxTpgGkZSkwKE+JQGihdcaqnyHEFOSAU45utQ+yLruE1dkonB2SDI8t375wOKgNn8pQvaWY9kPzxDQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3138,9 +3088,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.19.2.tgz",
"integrity": "sha512-HxfbvfCKJe/RMYJJn0a12eiOI9OOtAUF4G6ozrFUK95BNyoJaSiBjIOHjZskTUffUrB84IPKkFG9H9nEvJGW6A==", "integrity": "sha512-5V3mPpWkB066XZZBgSd1lwozBk7tmOkKtquyCJ6T4LN3mzKENXyBwWNQn8d0Ci81hvlBw5RoFgleVpL6aScLYg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3152,9 +3102,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.19.2.tgz",
"integrity": "sha512-HxDMKIhmcguGTiP5TsLNolwBUK3nGGUEoV/BO9ldUBoMLBssvh4J0X8pf11i1fTV7WShWItB1bKAKjX4RQeYmg==", "integrity": "sha512-ayVstadfLeeXI9zUPiKRVT8qF55hm7hKa+0N1V6Vj+OTNFfKSoUxyZvzVvgtBxqSb5URQ8sK6fhwxr9/MLmxdA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3166,9 +3116,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.19.2.tgz",
"integrity": "sha512-xItlIAZZaiG/u0wooGzRsx11rokP4qyc/79LkAOdznGRAbOFc+SfEdfUOszG1odsHNgwippUJavag/+W/Etc6Q==", "integrity": "sha512-Mda7iG4fOLHNsPqjWSjANvNZYoW034yxgrndof0DwCy0D3FvTjeNo+HGE6oGWgvcLZNLlcp0hLEFcRs+UGsMLg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -3180,9 +3130,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.19.2.tgz",
"integrity": "sha512-xNo5fV5ycvCCKqiZcpB65VMR11NJB+StnxHz20jdqRAktfdfzhgjTiJ2doTDQE/7dqGaV5I7ZGqKpgph6lCIag==", "integrity": "sha512-DPi0ubYhSow/00YqmG1jWm3qt1F8aXziHc/UNy8bo9cpCacqhuWu+iSq/fp2SyEQK7iYTZ60fBU9cat3MXTjIQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3635,9 +3585,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.14.12", "version": "20.14.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.13.tgz",
"integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==", "integrity": "sha512-+bHoGiZb8UiQ0+WEtmph2IWQCjIqg8MDZMAV+ppRRhUZnquF5mQkP/9vpSwJClEiSM/C7fZZExPzfU0vJTyp8w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -3720,9 +3670,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/three": { "node_modules/@types/three": {
"version": "0.167.0", "version": "0.167.1",
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.0.tgz", "resolved": "https://registry.npmjs.org/@types/three/-/three-0.167.1.tgz",
"integrity": "sha512-BC+Vbm0d6yMzct7dhTBe9ZjEh6ygupyn1k/UcZncIIS/5aNIbfvF77gQw1IFP09Oyj1UxWj0EUBBqc1GkqzsOw==", "integrity": "sha512-OCd2Uv/8/4TbmSaIRFawrCOnDMLdpaa+QGJdhlUBmdfbHjLY8k6uFc0tde2/UvcaHQ6NtLl28onj/vJfofV+Tg==",
"license": "MIT", "license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
@ -3757,17 +3707,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
"integrity": "sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==", "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.17.0", "@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/type-utils": "7.17.0", "@typescript-eslint/type-utils": "7.18.0",
"@typescript-eslint/utils": "7.17.0", "@typescript-eslint/utils": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0", "@typescript-eslint/visitor-keys": "7.18.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -3791,16 +3741,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
"integrity": "sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==", "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "7.17.0", "@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/typescript-estree": "7.17.0", "@typescript-eslint/typescript-estree": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0", "@typescript-eslint/visitor-keys": "7.18.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -3820,14 +3770,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
"integrity": "sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==", "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0" "@typescript-eslint/visitor-keys": "7.18.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -3838,14 +3788,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
"integrity": "sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==", "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "7.17.0", "@typescript-eslint/typescript-estree": "7.18.0",
"@typescript-eslint/utils": "7.17.0", "@typescript-eslint/utils": "7.18.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@ -3866,9 +3816,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
"integrity": "sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==", "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -3880,14 +3830,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
"integrity": "sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==", "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/visitor-keys": "7.17.0", "@typescript-eslint/visitor-keys": "7.18.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"globby": "^11.1.0", "globby": "^11.1.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -3909,16 +3859,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
"integrity": "sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==", "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "7.17.0", "@typescript-eslint/scope-manager": "7.18.0",
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"@typescript-eslint/typescript-estree": "7.17.0" "@typescript-eslint/typescript-estree": "7.18.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || >=20.0.0" "node": "^18.18.0 || >=20.0.0"
@ -3932,13 +3882,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "7.17.0", "version": "7.18.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
"integrity": "sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==", "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "7.17.0", "@typescript-eslint/types": "7.18.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@ -4676,9 +4626,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001643", "version": "1.0.30001646",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001646.tgz",
"integrity": "sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg==", "integrity": "sha512-dRg00gudiBDDTmUhClSdv3hqRfpbOnU28IpI1T6PBTLWa+kOj0681C8uML3PifYfREuBrVjDGhL3adYpBT6spw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -5363,9 +5313,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.5", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
@ -5450,9 +5400,9 @@
} }
}, },
"node_modules/detect-gpu": { "node_modules/detect-gpu": {
"version": "5.0.40", "version": "5.0.41",
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.40.tgz", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.41.tgz",
"integrity": "sha512-5v4jDN/ERdZZitD29UiLjV9Q9+lDfw2OhEJACIqnvdWulVZCy2K6EwonZ/VKyo4YMqvSIzGIDmojX3jGL3dLpA==", "integrity": "sha512-0avjQwm8zyDPLmmp2PlaUxOWp/CNLbOU4t61x1IOTmBvC7UO+NMWDlEJcIjtbRBSnulC2ote81Xyillssam0CA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"webgl-constants": "^1.1.1" "webgl-constants": "^1.1.1"
@ -5568,9 +5518,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.1", "version": "1.5.4",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz",
"integrity": "sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w==", "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -6326,9 +6276,9 @@
} }
}, },
"node_modules/framer-motion": { "node_modules/framer-motion": {
"version": "11.3.17", "version": "11.3.19",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.17.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.3.19.tgz",
"integrity": "sha512-LZcckvZL8Rjod03bud8LQcp+R0PLmWIlOSu+NVc+v6Uh43fQr4IBsEAX7sSn7CdBQ1L0fZ/IqSXZVPnGFSMxHw==", "integrity": "sha512-+luuQdx4AsamyMcvzW7jUAJYIKvQs1KE7oHvKkW3eNzmo0S+3PSDWjBuQkuIP9WyneGnKGMLUSuHs8OP7jKpQg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
@ -10797,9 +10747,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.19.0", "version": "4.19.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.19.2.tgz",
"integrity": "sha512-5r7EYSQIowHsK4eTZ0Y81qpZuJz+MUuYeqmmYmRMl1nwhdmbiYqt5jwzf6u7wyOzJgYqtCRMtVRKOtHANBz7rA==", "integrity": "sha512-6/jgnN1svF9PjNYJ4ya3l+cqutg49vOZ4rVgsDKxdl+5gpGPnByFXWGyfH9YGx9i3nfBwSu1Iyu6vGwFFA0BdQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -10813,22 +10763,22 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.19.0", "@rollup/rollup-android-arm-eabi": "4.19.2",
"@rollup/rollup-android-arm64": "4.19.0", "@rollup/rollup-android-arm64": "4.19.2",
"@rollup/rollup-darwin-arm64": "4.19.0", "@rollup/rollup-darwin-arm64": "4.19.2",
"@rollup/rollup-darwin-x64": "4.19.0", "@rollup/rollup-darwin-x64": "4.19.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.19.0", "@rollup/rollup-linux-arm-gnueabihf": "4.19.2",
"@rollup/rollup-linux-arm-musleabihf": "4.19.0", "@rollup/rollup-linux-arm-musleabihf": "4.19.2",
"@rollup/rollup-linux-arm64-gnu": "4.19.0", "@rollup/rollup-linux-arm64-gnu": "4.19.2",
"@rollup/rollup-linux-arm64-musl": "4.19.0", "@rollup/rollup-linux-arm64-musl": "4.19.2",
"@rollup/rollup-linux-powerpc64le-gnu": "4.19.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.19.2",
"@rollup/rollup-linux-riscv64-gnu": "4.19.0", "@rollup/rollup-linux-riscv64-gnu": "4.19.2",
"@rollup/rollup-linux-s390x-gnu": "4.19.0", "@rollup/rollup-linux-s390x-gnu": "4.19.2",
"@rollup/rollup-linux-x64-gnu": "4.19.0", "@rollup/rollup-linux-x64-gnu": "4.19.2",
"@rollup/rollup-linux-x64-musl": "4.19.0", "@rollup/rollup-linux-x64-musl": "4.19.2",
"@rollup/rollup-win32-arm64-msvc": "4.19.0", "@rollup/rollup-win32-arm64-msvc": "4.19.2",
"@rollup/rollup-win32-ia32-msvc": "4.19.0", "@rollup/rollup-win32-ia32-msvc": "4.19.2",
"@rollup/rollup-win32-x64-msvc": "4.19.0", "@rollup/rollup-win32-x64-msvc": "4.19.2",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -11514,9 +11464,9 @@
} }
}, },
"node_modules/three-stdlib": { "node_modules/three-stdlib": {
"version": "2.30.4", "version": "2.30.5",
"resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.30.4.tgz", "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.30.5.tgz",
"integrity": "sha512-E7sN8UkaorSq2uRZU14AE7wXkdCBa2oFwPkPt92zaecuzrgd98BXkTt+2tFQVF1tPJRvfs7aMZV5dSOq4/vNVg==", "integrity": "sha512-BBZkKnTDmUacXU9mv7fA5R7Brb89uUbOUWXXZKNrzdx6JEozJt3e6I5zPMRbb1FC2aw/2QFtgwPi1PI8VjX6FQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/draco3d": "^1.4.0", "@types/draco3d": "^1.4.0",
@ -11629,9 +11579,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/ts-jest": { "node_modules/ts-jest": {
"version": "29.2.3", "version": "29.2.4",
"resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.3.tgz", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz",
"integrity": "sha512-yCcfVdiBFngVz9/keHin9EnsrQtQtEu3nRykNy9RVp+FiPFFbPJ3Sg6Qg4+TkmH0vMP5qsTKgXSsk80HRwvdgQ==", "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -11791,9 +11741,9 @@
} }
}, },
"node_modules/use-debounce": { "node_modules/use-debounce": {
"version": "10.0.1", "version": "10.0.2",
"resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.1.tgz", "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.2.tgz",
"integrity": "sha512-0uUXjOfm44e6z4LZ/woZvkM8FwV1wiuoB6xnrrOmeAEjRDDzTLQNRFtYHvqUsJdrz1X37j0rVGIVp144GLHGKg==", "integrity": "sha512-MwBiJK2dk+2qhMDVDCPRPeLuIekKfH2t1UYMnrW9pwcJJGFDbTLliSMBz2UKGmE1PJs8l3XoMqbIU1MemMAJ8g==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 16.0.0" "node": ">= 16.0.0"

View File

@ -12,13 +12,13 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@lezer/lr": "^1.4.1", "@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.19.3", "@tanstack/react-table": "^8.19.3",
"@uiw/codemirror-themes": "^4.23.0", "@uiw/codemirror-themes": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0", "@uiw/react-codemirror": "^4.23.0",
"axios": "^1.7.2", "axios": "^1.7.2",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^11.3.17", "framer-motion": "^11.3.19",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"react": "^18.3.1", "react": "^18.3.1",
@ -36,16 +36,16 @@
"react-zoom-pan-pinch": "^3.6.1", "react-zoom-pan-pinch": "^3.6.1",
"reactflow": "^11.11.4", "reactflow": "^11.11.4",
"reagraph": "^4.19.2", "reagraph": "^4.19.2",
"use-debounce": "^10.0.1" "use-debounce": "^10.0.2"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^20.14.12", "@types/node": "^20.14.13",
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.17.0", "@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.57.0", "eslint": "^8.57.0",
@ -56,7 +56,7 @@
"jest": "^29.7.0", "jest": "^29.7.0",
"postcss": "^8.4.40", "postcss": "^8.4.40",
"tailwindcss": "^3.4.7", "tailwindcss": "^3.4.7",
"ts-jest": "^29.2.3", "ts-jest": "^29.2.4",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vite": "^5.3.5" "vite": "^5.3.5"
}, },

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 121 KiB

View File

@ -12,6 +12,7 @@ import {
IPositionsData, IPositionsData,
ITargetOperation ITargetOperation
} from '@/models/oss'; } from '@/models/oss';
import { IConstituentaReference, ITargetCst } from '@/models/rsform';
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport'; import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport';
@ -73,3 +74,10 @@ export function postExecuteOperation(oss: string, request: FrontExchange<ITarget
request: request request: request
}); });
} }
export function postFindPredecessor(request: FrontExchange<ITargetCst, IConstituentaReference>) {
AxiosPost({
endpoint: `/api/oss/get-predecessor`,
request: request
});
}

View File

@ -62,6 +62,9 @@ export { VscLibrary as IconLibrary } from 'react-icons/vsc';
export { IoLibrary as IconLibrary2 } from 'react-icons/io5'; 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 { GiHoneycomb as IconOSS } from 'react-icons/gi'; export { GiHoneycomb as IconOSS } from 'react-icons/gi';
export { LuBaby as IconChild } from 'react-icons/lu';
export { RiParentLine as IconParent } from 'react-icons/ri';
export { BiSpa as IconPredecessor } from 'react-icons/bi';
export { RiHexagonLine as IconRSForm } from 'react-icons/ri'; export { RiHexagonLine as IconRSForm } from 'react-icons/ri';
export { LuArchive as IconArchive } from 'react-icons/lu'; export { LuArchive as IconArchive } from 'react-icons/lu';
export { LuDatabase as IconDatabase } from 'react-icons/lu'; export { LuDatabase as IconDatabase } from 'react-icons/lu';

View File

@ -20,6 +20,7 @@ function BadgeConstituenta({ value, prefixID, theme }: BadgeConstituentaProps) {
'min-w-[3.1rem] max-w-[3.1rem]', // prettier: split lines 'min-w-[3.1rem] max-w-[3.1rem]', // prettier: split lines
'px-1', 'px-1',
'border rounded-md', 'border rounded-md',
value.is_inherited && 'border-dashed',
'text-center font-medium whitespace-nowrap' 'text-center font-medium whitespace-nowrap'
)} )}
style={{ style={{

View File

@ -13,7 +13,10 @@ interface InfoConstituentaProps extends CProps.Div {
function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) { function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) {
return ( return (
<div className={clsx('dense min-w-[15rem]', className)} {...restProps}> <div className={clsx('dense min-w-[15rem]', className)} {...restProps}>
<h2>Конституента {data.alias}</h2> <h2>
Конституента {data.alias}
{data.is_inherited ? ' (наследуется)' : ''}
</h2>
{data.term_resolved ? ( {data.term_resolved ? (
<p> <p>
<b>Термин: </b> <b>Термин: </b>

View File

@ -1,13 +1,66 @@
'use client';
import { createColumnHelper } from '@tanstack/react-table';
import { useMemo } from 'react';
import Tooltip from '@/components/ui/Tooltip'; import Tooltip from '@/components/ui/Tooltip';
import { OssNodeInternal } from '@/models/miscellaneous'; import { OssNodeInternal } from '@/models/miscellaneous';
import { ICstSubstituteEx } from '@/models/oss';
import { labelOperationType } from '@/utils/labels'; import { labelOperationType } from '@/utils/labels';
import { IconPageRight } from '../Icons';
import DataTable from '../ui/DataTable';
interface TooltipOperationProps { interface TooltipOperationProps {
node: OssNodeInternal; node: OssNodeInternal;
anchor: string; anchor: string;
} }
const columnHelper = createColumnHelper<ICstSubstituteEx>();
function TooltipOperation({ node, anchor }: TooltipOperationProps) { function TooltipOperation({ node, anchor }: TooltipOperationProps) {
const columns = useMemo(
() => [
columnHelper.accessor('substitution_term', {
id: 'substitution_term',
size: 200
}),
columnHelper.accessor('substitution_alias', {
id: 'substitution_alias',
size: 50
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: () => <IconPageRight size='1.2rem' />
}),
columnHelper.accessor('original_alias', {
id: 'original_alias',
size: 50
}),
columnHelper.accessor('original_term', {
id: 'original_term',
size: 200
})
],
[]
);
const table = useMemo(
() => (
<DataTable
dense
noHeader
noFooter
className='w-full text-sm border select-none mb-2'
data={node.data.operation.substitutions}
columns={columns}
/>
),
[columns, node]
);
return ( return (
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'> <Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
<h2>{node.data.operation.alias}</h2> <h2>{node.data.operation.alias}</h2>
@ -29,6 +82,7 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
<p> <p>
<b>Положение:</b> [{node.xPos}, {node.yPos}] <b>Положение:</b> [{node.xPos}, {node.yPos}]
</p> </p>
{node.data.operation.substitutions.length > 0 ? table : null}
</Tooltip> </Tooltip>
); );
} }

View File

@ -0,0 +1,43 @@
'use client';
import { IconOSS } from '@/components/Icons';
import { CProps } from '@/components/props';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import useDropdown from '@/hooks/useDropdown';
import { ILibraryItemReference } from '@/models/library';
import { prefixes } from '@/utils/constants';
interface MiniSelectorOSSProps {
items: ILibraryItemReference[];
onSelect: (event: CProps.EventMouse, newValue: ILibraryItemReference) => void;
}
function MiniSelectorOSS({ items, onSelect }: MiniSelectorOSSProps) {
const ossMenu = useDropdown();
return (
<div ref={ossMenu.ref} className='flex items-center'>
<MiniButton
icon={<IconOSS size='1.25rem' className='icon-primary' />}
title='Связанные операционные схемы'
hideTitle={ossMenu.isOpen}
onClick={() => ossMenu.toggle()}
/>
<Dropdown isOpen={ossMenu.isOpen}>
<Label text='Список ОСС' className='border-b px-3 py-1' />
{items.map((reference, index) => (
<DropdownButton
className='min-w-[5rem]'
key={`${prefixes.oss_list}${index}`}
text={reference.alias}
onClick={event => onSelect(event, reference)}
/>
))}
</Dropdown>
</div>
);
}
export default MiniSelectorOSS;

View File

@ -102,8 +102,6 @@ function PickSubstitutions({
}; };
const toDelete = substitutions.map(item => item.original); const toDelete = substitutions.map(item => item.original);
const replacements = substitutions.map(item => item.substitution); const replacements = substitutions.map(item => item.substitution);
console.log(toDelete, replacements);
console.log(newSubstitution);
if ( if (
toDelete.includes(newSubstitution.original) || toDelete.includes(newSubstitution.original) ||
toDelete.includes(newSubstitution.substitution) || toDelete.includes(newSubstitution.substitution) ||
@ -112,6 +110,12 @@ function PickSubstitutions({
toast.error(errors.reuseOriginal); toast.error(errors.reuseOriginal);
return; return;
} }
if (leftArgument === rightArgument) {
if ((deleteRight && rightCst?.is_inherited) || (!deleteRight && leftCst?.is_inherited)) {
toast.error(errors.substituteInherited);
return;
}
}
setSubstitutions(prev => [...prev, newSubstitution]); setSubstitutions(prev => [...prev, newSubstitution]);
setLeftCst(undefined); setLeftCst(undefined);
setRightCst(undefined); setRightCst(undefined);

View File

@ -32,7 +32,7 @@ function SelectConstituenta({
return ( return (
items?.map(cst => ({ items?.map(cst => ({
value: cst.id, value: cst.id,
label: `${cst.alias}: ${describeConstituenta(cst)}` label: `${cst.alias}${cst.is_inherited ? '*' : ''}: ${describeConstituenta(cst)}`
})) ?? [] })) ?? []
); );
}, [items]); }, [items]);

View File

@ -25,8 +25,8 @@ import { type ErrorData } from '@/components/info/InfoError';
import { AccessPolicy, ILibraryItem } from '@/models/library'; import { AccessPolicy, ILibraryItem } from '@/models/library';
import { ILibraryUpdateData } from '@/models/library'; import { ILibraryUpdateData } from '@/models/library';
import { import {
IOperation,
IOperationCreateData, IOperationCreateData,
IOperationData,
IOperationSchema, IOperationSchema,
IOperationSchemaData, IOperationSchemaData,
IOperationSetInputData, IOperationSetInputData,
@ -62,7 +62,7 @@ interface IOssContext {
setEditors: (newEditors: UserID[], callback?: () => void) => void; setEditors: (newEditors: UserID[], callback?: () => void) => void;
savePositions: (data: IPositionsData, callback?: () => void) => void; savePositions: (data: IPositionsData, callback?: () => void) => void;
createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperation>) => void; createOperation: (data: IOperationCreateData, callback?: DataCallback<IOperationData>) => void;
deleteOperation: (data: ITargetOperation, callback?: () => void) => void; deleteOperation: (data: ITargetOperation, callback?: () => void) => void;
createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void; createInput: (data: ITargetOperation, callback?: DataCallback<ILibraryItem>) => void;
setInput: (data: IOperationSetInputData, callback?: () => void) => void; setInput: (data: IOperationSetInputData, callback?: () => void) => void;
@ -292,7 +292,7 @@ export const OssState = ({ itemID, children }: OssStateProps) => {
); );
const createOperation = useCallback( const createOperation = useCallback(
(data: IOperationCreateData, callback?: DataCallback<IOperation>) => { (data: IOperationCreateData, callback?: DataCallback<IOperationData>) => {
setProcessingError(undefined); setProcessingError(undefined);
postCreateOperation(itemID, { postCreateOperation(itemID, {
data: data, data: data,

View File

@ -13,6 +13,7 @@ import {
postCreateVersion, postCreateVersion,
postSubscribe postSubscribe
} from '@/backend/library'; } from '@/backend/library';
import { postFindPredecessor } from '@/backend/oss';
import { import {
getTRSFile, getTRSFile,
patchDeleteConstituenta, patchDeleteConstituenta,
@ -37,6 +38,7 @@ import {
ConstituentaID, ConstituentaID,
IConstituentaList, IConstituentaList,
IConstituentaMeta, IConstituentaMeta,
IConstituentaReference,
ICstCreateData, ICstCreateData,
ICstMovetoData, ICstMovetoData,
ICstRenameData, ICstRenameData,
@ -89,6 +91,7 @@ interface IRSFormContext {
cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void; cstUpdate: (data: ICstUpdateData, callback?: DataCallback<IConstituentaMeta>) => void;
cstDelete: (data: IConstituentaList, callback?: () => void) => void; cstDelete: (data: IConstituentaList, callback?: () => void) => void;
cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void; cstMoveTo: (data: ICstMovetoData, callback?: () => void) => void;
findPredecessor: (data: ITargetCst, callback: (reference: IConstituentaReference) => void) => void;
versionCreate: (data: IVersionData, callback?: (version: VersionID) => void) => void; versionCreate: (data: IVersionData, callback?: (version: VersionID) => void) => void;
versionUpdate: (target: VersionID, data: IVersionData, callback?: () => void) => void; versionUpdate: (target: VersionID, data: IVersionData, callback?: () => void) => void;
@ -526,6 +529,17 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
[itemID, library, setSchema] [itemID, library, setSchema]
); );
const findPredecessor = useCallback((data: ITargetCst, callback: (reference: IConstituentaReference) => void) => {
setProcessingError(undefined);
postFindPredecessor({
data: data,
showError: true,
setLoading: setProcessing,
onError: setProcessingError,
onSuccess: callback
});
}, []);
const versionUpdate = useCallback( const versionUpdate = useCallback(
(target: number, data: IVersionData, callback?: () => void) => { (target: number, data: IVersionData, callback?: () => void) => {
setProcessingError(undefined); setProcessingError(undefined);
@ -638,6 +652,8 @@ export const RSFormState = ({ itemID, versionID, children }: RSFormStateProps) =
cstSubstitute, cstSubstitute,
cstDelete, cstDelete,
cstMoveTo, cstMoveTo,
findPredecessor,
versionCreate, versionCreate,
versionUpdate, versionUpdate,
versionDelete, versionDelete,

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useLayoutEffect, useMemo, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
@ -22,6 +22,7 @@ interface DlgCreateOperationProps {
hideWindow: () => void; hideWindow: () => void;
oss: IOperationSchema; oss: IOperationSchema;
onCreate: (data: IOperationCreateData) => void; onCreate: (data: IOperationCreateData) => void;
initialInputs: OperationID[];
} }
export enum TabID { export enum TabID {
@ -29,21 +30,31 @@ export enum TabID {
SYNTHESIS = 1 SYNTHESIS = 1
} }
function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationProps) { function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCreateOperationProps) {
const library = useLibrary(); const library = useLibrary();
const [activeTab, setActiveTab] = useState(TabID.INPUT); const [activeTab, setActiveTab] = useState(initialInputs.length > 0 ? TabID.SYNTHESIS : TabID.INPUT);
const [alias, setAlias] = useState(''); const [alias, setAlias] = useState('');
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const [inputs, setInputs] = useState<OperationID[]>([]); const [inputs, setInputs] = useState<OperationID[]>(initialInputs);
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined); const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
const [createSchema, setCreateSchema] = useState(false); const [createSchema, setCreateSchema] = useState(false);
const isValid = useMemo( const isValid = useMemo(() => {
() => (alias !== '' && activeTab === TabID.INPUT) || inputs.length != 1, if (alias === '') {
[alias, activeTab, inputs] return false;
); }
if (activeTab === TabID.SYNTHESIS && inputs.length === 1) {
return false;
}
if (activeTab === TabID.INPUT && !attachedID) {
if (oss.items.some(operation => operation.alias === alias)) {
return false;
}
}
return true;
}, [alias, activeTab, inputs, attachedID, oss.items]);
useLayoutEffect(() => { useLayoutEffect(() => {
if (attachedID) { if (attachedID) {
@ -74,6 +85,21 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
onCreate(data); onCreate(data);
}; };
const handleSelectTab = useCallback(
(newTab: TabID, last: TabID) => {
if (last === newTab) {
return;
}
if (newTab === TabID.INPUT) {
setAttachedID(undefined);
} else {
setInputs(initialInputs);
}
setActiveTab(newTab);
},
[setActiveTab, initialInputs]
);
const inputPanel = useMemo( const inputPanel = useMemo(
() => ( () => (
<TabPanel> <TabPanel>
@ -131,7 +157,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
selectedTabClassName='clr-selected' selectedTabClassName='clr-selected'
className='flex flex-col' className='flex flex-col'
selectedIndex={activeTab} selectedIndex={activeTab}
onSelect={setActiveTab} onSelect={handleSelectTab}
> >
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}> <TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<TabLabel <TabLabel

View File

@ -50,7 +50,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
() => inputOperations.map(operation => operation.result).filter(id => id !== null), () => inputOperations.map(operation => operation.result).filter(id => id !== null),
[inputOperations] [inputOperations]
); );
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(oss.substitutions); const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(target.substitutions);
const cache = useRSFormCache(); const cache = useRSFormCache();
const schemas = useMemo( const schemas = useMemo(
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined), () => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),

View File

@ -32,6 +32,7 @@ export class OssLoader {
this.prepareLookups(); this.prepareLookups();
this.createGraph(); this.createGraph();
this.extractSchemas(); this.extractSchemas();
this.inferOperationAttributes();
result.operationByID = this.operationByID; result.operationByID = this.operationByID;
result.graph = this.graph; result.graph = this.graph;
@ -42,7 +43,7 @@ export class OssLoader {
private prepareLookups() { private prepareLookups() {
this.oss.items.forEach(operation => { this.oss.items.forEach(operation => {
this.operationByID.set(operation.id, operation); this.operationByID.set(operation.id, operation as IOperation);
this.graph.addNode(operation.id); this.graph.addNode(operation.id);
}); });
} }
@ -55,6 +56,16 @@ export class OssLoader {
this.schemas = this.oss.items.map(operation => operation.result as LibraryItemID).filter(item => item !== null); this.schemas = this.oss.items.map(operation => operation.result as LibraryItemID).filter(item => item !== null);
} }
private inferOperationAttributes() {
this.graph.topologicalOrder().forEach(operationID => {
const operation = this.operationByID.get(operationID)!;
operation.substitutions = this.oss.substitutions.filter(item => item.operation === operationID);
operation.arguments = this.oss.arguments
.filter(item => item.operation === operationID)
.map(item => item.argument);
});
}
private calculateStats(): IOperationSchemaStats { private calculateStats(): IOperationSchemaStats {
const items = this.oss.items; const items = this.oss.items;
return { return {

View File

@ -59,6 +59,8 @@ export class RSFormLoader {
} }
private inferCstAttributes() { private inferCstAttributes() {
const inherit_children = new Set(this.schema.inheritance.map(item => item[0]));
const inherit_parents = new Set(this.schema.inheritance.map(item => item[1]));
this.graph.topologicalOrder().forEach(cstID => { this.graph.topologicalOrder().forEach(cstID => {
const cst = this.cstByID.get(cstID)!; const cst = this.cstByID.get(cstID)!;
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass); cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
@ -66,6 +68,8 @@ export class RSFormLoader {
cst.cst_class = inferClass(cst.cst_type, cst.is_template); cst.cst_class = inferClass(cst.cst_type, cst.is_template);
cst.children = []; cst.children = [];
cst.children_alias = []; cst.children_alias = [];
cst.is_inherited = inherit_children.has(cst.id);
cst.is_inherited_parent = inherit_parents.has(cst.id);
cst.is_simple_expression = this.inferSimpleExpression(cst); cst.is_simple_expression = this.inferSimpleExpression(cst);
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) { if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
return; return;
@ -165,6 +169,7 @@ export class RSFormLoader {
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0), sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
0 0
), ),
count_inherited: items.reduce((sum, cst) => sum + ((cst as IConstituenta).is_inherited ? 1 : 0), 0),
count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0), count_text_term: items.reduce((sum, cst) => sum + (cst.term_raw ? 1 : 0), 0),
count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0), count_definition: items.reduce((sum, cst) => sum + (cst.definition_raw ? 1 : 0), 0),

View File

@ -75,7 +75,7 @@ export interface ILibraryItem {
} }
/** /**
* Represents library item constant data loaded for both OSS and RSForm. * Represents {@link ILibraryItem} constant data loaded for both OSS and RSForm.
*/ */
export interface ILibraryItemData extends ILibraryItem { export interface ILibraryItemData extends ILibraryItem {
subscribers: UserID[]; subscribers: UserID[];
@ -83,7 +83,12 @@ export interface ILibraryItemData extends ILibraryItem {
} }
/** /**
* Represents library item extended data with versions. * Represents {@link ILibraryItem} minimal reference data.
*/
export interface ILibraryItemReference extends Pick<ILibraryItem, 'id' | 'alias'> {}
/**
* Represents {@link ILibraryItem} extended data with versions.
*/ */
export interface ILibraryItemVersioned extends ILibraryItemData { export interface ILibraryItemVersioned extends ILibraryItemData {
version?: VersionID; version?: VersionID;
@ -91,7 +96,7 @@ export interface ILibraryItemVersioned extends ILibraryItemData {
} }
/** /**
* Represents common library item editor controller. * Represents common {@link ILibraryItem} editor controller.
*/ */
export interface ILibraryItemEditor { export interface ILibraryItemEditor {
schema?: ILibraryItemData; schema?: ILibraryItemData;

View File

@ -35,8 +35,16 @@ export interface IOperation {
position_y: number; position_y: number;
result: LibraryItemID | null; result: LibraryItemID | null;
substitutions: ICstSubstituteEx[];
arguments: OperationID[];
} }
/**
* Represents {@link IOperation} data from server.
*/
export interface IOperationData extends Omit<IOperation, 'substitutions' | 'arguments'> {}
/** /**
* Represents {@link IOperation} position. * Represents {@link IOperation} position.
*/ */
@ -121,6 +129,7 @@ export interface IMultiSubstitution {
* Represents {@link ICstSubstitute} extended data. * Represents {@link ICstSubstitute} extended data.
*/ */
export interface ICstSubstituteEx extends ICstSubstitute { export interface ICstSubstituteEx extends ICstSubstitute {
operation: OperationID;
original_alias: string; original_alias: string;
original_term: string; original_term: string;
substitution_alias: string; substitution_alias: string;
@ -141,7 +150,7 @@ export interface IOperationSchemaStats {
* Represents backend data for {@link IOperationSchema}. * Represents backend data for {@link IOperationSchema}.
*/ */
export interface IOperationSchemaData extends ILibraryItemData { export interface IOperationSchemaData extends ILibraryItemData {
items: IOperation[]; items: IOperationData[];
arguments: IArgument[]; arguments: IArgument[];
substitutions: ICstSubstituteEx[]; substitutions: ICstSubstituteEx[];
} }
@ -150,6 +159,7 @@ export interface IOperationSchemaData extends ILibraryItemData {
* Represents OperationSchema. * Represents OperationSchema.
*/ */
export interface IOperationSchema extends IOperationSchemaData { export interface IOperationSchema extends IOperationSchemaData {
items: IOperation[];
graph: Graph; graph: Graph;
schemas: LibraryItemID[]; schemas: LibraryItemID[];
stats: IOperationSchemaStats; stats: IOperationSchemaStats;
@ -160,7 +170,7 @@ export interface IOperationSchema extends IOperationSchemaData {
* Represents data response when creating {@link IOperation}. * Represents data response when creating {@link IOperation}.
*/ */
export interface IOperationCreatedResponse { export interface IOperationCreatedResponse {
new_operation: IOperation; new_operation: IOperationData;
oss: IOperationSchemaData; oss: IOperationSchemaData;
} }

View File

@ -4,7 +4,7 @@
import { Graph } from '@/models/Graph'; import { Graph } from '@/models/Graph';
import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library'; import { ILibraryItem, ILibraryItemReference, ILibraryItemVersioned, LibraryItemID } from './library';
import { ICstSubstitute } from './oss'; import { ICstSubstitute } from './oss';
import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang'; import { IArgumentInfo, ParsingStatus, ValueClass } from './rslang';
@ -91,7 +91,7 @@ export interface ITargetCst {
} }
/** /**
* Represents Constituenta data from server. * Represents {@link IConstituenta} data from server.
*/ */
export interface IConstituentaData extends IConstituentaMeta { export interface IConstituentaData extends IConstituentaMeta {
parse: { parse: {
@ -111,12 +111,19 @@ export interface IConstituenta extends IConstituentaData {
status: ExpressionStatus; status: ExpressionStatus;
is_template: boolean; is_template: boolean;
is_simple_expression: boolean; is_simple_expression: boolean;
is_inherited: boolean;
is_inherited_parent: boolean;
parent?: ConstituentaID; parent?: ConstituentaID;
parent_alias?: string; parent_alias?: string;
children: number[]; children: number[];
children_alias: string[]; children_alias: string[];
} }
/**
* Represents {@link IConstituenta} reference.
*/
export interface IConstituentaReference extends Pick<IConstituentaMeta, 'id' | 'schema'> {}
/** /**
* Represents Constituenta list. * Represents Constituenta list.
*/ */
@ -183,6 +190,7 @@ export interface IRSFormStats {
count_errors: number; count_errors: number;
count_property: number; count_property: number;
count_incalculable: number; count_incalculable: number;
count_inherited: number;
count_text_term: number; count_text_term: number;
count_definition: number; count_definition: number;
@ -198,10 +206,19 @@ export interface IRSFormStats {
count_theorem: number; count_theorem: number;
} }
/**
* Represents data for {@link IRSForm} provided by backend.
*/
export interface IRSFormData extends ILibraryItemVersioned {
items: IConstituentaData[];
inheritance: ConstituentaID[][];
oss: ILibraryItemReference[];
}
/** /**
* Represents formal explication for set of concepts. * Represents formal explication for set of concepts.
*/ */
export interface IRSForm extends ILibraryItemVersioned { export interface IRSForm extends IRSFormData {
items: IConstituenta[]; items: IConstituenta[];
stats: IRSFormStats; stats: IRSFormStats;
graph: Graph; graph: Graph;
@ -209,13 +226,6 @@ export interface IRSForm extends ILibraryItemVersioned {
cstByID: Map<ConstituentaID, IConstituenta>; cstByID: Map<ConstituentaID, IConstituenta>;
} }
/**
* Represents data for {@link IRSForm} provided by backend.
*/
export interface IRSFormData extends ILibraryItemVersioned {
items: IConstituentaData[];
}
/** /**
* Represents data, used for cloning {@link IRSForm}. * Represents data, used for cloning {@link IRSForm}.
*/ */

View File

@ -133,6 +133,8 @@ export function createMockConstituenta(id: ConstituentaID, alias: string, commen
definition_resolved: '', definition_resolved: '',
status: ExpressionStatus.INCORRECT, status: ExpressionStatus.INCORRECT,
is_template: false, is_template: false,
is_inherited: false,
is_inherited_parent: false,
cst_class: CstClass.DERIVED, cst_class: CstClass.DERIVED,
parse: { parse: {
status: ParsingStatus.INCORRECT, status: ParsingStatus.INCORRECT,

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
@ -22,10 +22,11 @@ import TextInput from '@/components/ui/TextInput';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import useLocalStorage from '@/hooks/useLocalStorage';
import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library'; import { AccessPolicy, LibraryItemType, LocationHead } from '@/models/library';
import { ILibraryCreateData } from '@/models/library'; import { ILibraryCreateData } from '@/models/library';
import { combineLocation, validateLocation } from '@/models/libraryAPI'; import { combineLocation, validateLocation } from '@/models/libraryAPI';
import { EXTEOR_TRS_FILE, limits, patterns } from '@/utils/constants'; import { EXTEOR_TRS_FILE, limits, patterns, storage } from '@/utils/constants';
import { information } from '@/utils/labels'; import { information } from '@/utils/labels';
function FormCreateItem() { function FormCreateItem() {
@ -45,6 +46,7 @@ function FormCreateItem() {
const location = useMemo(() => combineLocation(head, body), [head, body]); const location = useMemo(() => combineLocation(head, body), [head, body]);
const isValid = useMemo(() => validateLocation(location), [location]); const isValid = useMemo(() => validateLocation(location), [location]);
const [initLocation] = useLocalStorage<string>(storage.librarySearchLocation, '');
const [fileName, setFileName] = useState(''); const [fileName, setFileName] = useState('');
const [file, setFile] = useState<File | undefined>(); const [file, setFile] = useState<File | undefined>();
@ -104,6 +106,13 @@ function FormCreateItem() {
setBody(newValue.length > 3 ? newValue.substring(3) : ''); setBody(newValue.length > 3 ? newValue.substring(3) : '');
}, []); }, []);
useLayoutEffect(() => {
if (!initLocation) {
return;
}
handleSelectLocation(initLocation);
}, [initLocation, handleSelectLocation]);
return ( return (
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}> <form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
<h1> <h1>

View File

@ -1,6 +1,6 @@
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import LinkTopic from '../../../components/ui/LinkTopic'; import LinkTopic from '@/components/ui/LinkTopic';
function HelpCstAttributes() { function HelpCstAttributes() {
return ( return (

View File

@ -7,6 +7,7 @@ import {
IconMoveDown, IconMoveDown,
IconMoveUp, IconMoveUp,
IconNewItem, IconNewItem,
IconOSS,
IconReset, IconReset,
IconSave, IconSave,
IconStatusOK, IconStatusOK,
@ -22,6 +23,9 @@ function HelpCstEditor() {
return ( return (
<div className='dense'> <div className='dense'>
<h1>Редактор конституенты</h1> <h1>Редактор конституенты</h1>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li> <li>
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S <IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
</li> </li>
@ -74,7 +78,6 @@ function HelpCstEditor() {
<IconMoveUp className='inline-icon' /> Alt + вверх/вниз перемещение <IconMoveUp className='inline-icon' /> Alt + вверх/вниз перемещение
</li> </li>
<li>фильтрация в верхней части</li> <li>фильтрация в верхней части</li>
<li>при наведении на имя конституенты отображаются атрибуты</li>
<li> <li>
<span style={{ backgroundColor: colors.bgSelected }}>цветом фона</span> выделена текущая конституента <span style={{ backgroundColor: colors.bgSelected }}>цветом фона</span> выделена текущая конституента
</li> </li>

View File

@ -1,10 +1,10 @@
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import { IconLibrary2, IconManuals, IconUser2 } from '@/components/Icons'; import { IconLibrary2, IconManuals, IconUser2 } from '@/components/Icons';
import LinkTopic from '@/components/ui/LinkTopic';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { external_urls, prefixes } from '@/utils/constants'; import { external_urls, prefixes } from '@/utils/constants';
import LinkTopic from '../../../components/ui/LinkTopic';
import TopicItem from '../TopicItem'; import TopicItem from '../TopicItem';
function HelpPortal() { function HelpPortal() {

View File

@ -1,5 +1,3 @@
import { HelpTopic } from '@/models/miscellaneous';
import { import {
IconClone, IconClone,
IconDestroy, IconDestroy,
@ -7,11 +5,13 @@ import {
IconEditor, IconEditor,
IconFollow, IconFollow,
IconImmutable, IconImmutable,
IconOSS,
IconOwner, IconOwner,
IconPublic, IconPublic,
IconSave IconSave
} from '../../../components/Icons'; } from '@/components/Icons';
import LinkTopic from '../../../components/ui/LinkTopic'; import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpRSFormCard() { function HelpRSFormCard() {
return ( return (
@ -30,6 +30,9 @@ function HelpRSFormCard() {
</p> </p>
<h2>Управление</h2> <h2>Управление</h2>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li> <li>
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S <IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
</li> </li>

View File

@ -1,7 +1,3 @@
import InfoCstStatus from '@/components/info/InfoCstStatus';
import Divider from '@/components/ui/Divider';
import { HelpTopic } from '@/models/miscellaneous';
import { import {
IconAlias, IconAlias,
IconClone, IconClone,
@ -10,20 +6,31 @@ import {
IconMoveUp, IconMoveUp,
IconNewItem, IconNewItem,
IconOpenList, IconOpenList,
IconOSS,
IconReset IconReset
} from '../../../components/Icons'; } from '@/components/Icons';
import LinkTopic from '../../../components/ui/LinkTopic'; import InfoCstStatus from '@/components/info/InfoCstStatus';
import Divider from '@/components/ui/Divider';
import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpRSFormItems() { function HelpRSFormItems() {
return ( return (
<div className='dense'> <div className='dense'>
<h1>Список конституент</h1> <h1>Список конституент</h1>
<p> <li>
<IconAlias className='inline-icon' /> <IconAlias className='inline-icon' />
Конституенты обладают уникальным <LinkTopic text='Именем' topic={HelpTopic.CC_CONSTITUENTA} /> Конституенты обладают уникальным <LinkTopic text='Именем' topic={HelpTopic.CC_CONSTITUENTA} />
</p> </li>
<li>при наведении на имя отображаются атрибуты</li>
<li>
пунктиром отображаются <LinkTopic text='наследованные' topic={HelpTopic.CC_OSS} /> конституенты
</li>
<h2>Управление списком</h2> <h2>Управление списком</h2>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li> <li>
<IconReset className='inline-icon' /> сбросить выделение: ESC <IconReset className='inline-icon' /> сбросить выделение: ESC
</li> </li>

View File

@ -1,7 +1,6 @@
import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import LinkTopic from '../../../components/ui/LinkTopic';
function HelpRSLangCorrect() { function HelpRSLangCorrect() {
return ( return (
<div className='text-justify'> <div className='text-justify'>

View File

@ -1,7 +1,6 @@
import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import LinkTopic from '../../../components/ui/LinkTopic';
function HelpRSLangInterpret() { function HelpRSLangInterpret() {
return ( return (
<div className='text-justify'> <div className='text-justify'>

View File

@ -1,5 +1,3 @@
import { HelpTopic } from '@/models/miscellaneous';
import { import {
IconGenerateNames, IconGenerateNames,
IconGenerateStructure, IconGenerateStructure,
@ -7,8 +5,9 @@ import {
IconReplace, IconReplace,
IconSortList, IconSortList,
IconTemplates IconTemplates
} from '../../../components/Icons'; } from '@/components/Icons';
import LinkTopic from '../../../components/ui/LinkTopic'; import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpRSLangOperations() { function HelpRSLangOperations() {
return ( return (

View File

@ -1,8 +1,3 @@
import Divider from '@/components/ui/Divider';
import LinkTopic from '@/components/ui/LinkTopic';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { HelpTopic } from '@/models/miscellaneous';
import { import {
IconClustering, IconClustering,
IconDestroy, IconDestroy,
@ -17,10 +12,15 @@ import {
IconGraphOutputs, IconGraphOutputs,
IconImage, IconImage,
IconNewItem, IconNewItem,
IconOSS,
IconReset, IconReset,
IconRotate3D, IconRotate3D,
IconText IconText
} from '../../../components/Icons'; } from '@/components/Icons';
import Divider from '@/components/ui/Divider';
import LinkTopic from '@/components/ui/LinkTopic';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { HelpTopic } from '@/models/miscellaneous';
function HelpTermGraph() { function HelpTermGraph() {
const { colors } = useConceptOptions(); const { colors } = useConceptOptions();
@ -71,6 +71,9 @@ function HelpTermGraph() {
<div className='flex flex-col-reverse mb-3 sm:flex-row'> <div className='flex flex-col-reverse mb-3 sm:flex-row'>
<div className='w-full sm:w-[14rem]'> <div className='w-full sm:w-[14rem]'>
<h1>Общие</h1> <h1>Общие</h1>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li> <li>
<IconFilter className='inline-icon' /> Открыть настройки <IconFilter className='inline-icon' /> Открыть настройки
</li> </li>
@ -80,6 +83,9 @@ function HelpTermGraph() {
<li> <li>
<IconImage className='inline-icon' /> Сохранить в формат PNG <IconImage className='inline-icon' /> Сохранить в формат PNG
</li> </li>
<li>
* <LinkTopic text='наследованные' topic={HelpTopic.CC_OSS} /> в ОСС
</li>
</div> </div>
<Divider vertical margins='mx-3' className='hidden sm:block' /> <Divider vertical margins='mx-3' className='hidden sm:block' />

View File

@ -21,7 +21,7 @@ function InputNode(node: OssNodeInternal) {
<> <>
<Handle type='source' position={Position.Bottom} /> <Handle type='source' position={Position.Bottom} />
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'> <Overlay position='top-[-0.2rem] right-[-0.2rem]'>
<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

View File

@ -101,11 +101,11 @@ function NodeContextMenu({
}; };
return ( return (
<div ref={ref} className='absolute' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}> <div ref={ref} className='absolute select-none' style={{ top: cursorY, left: cursorX, width: 10, height: 10 }}>
<Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}> <Dropdown isOpen={isOpen} stretchLeft={cursorX >= window.innerWidth - PARAMETER.ossContextMenuWidth}>
<DropdownButton <DropdownButton
text='Редактировать' text='Редактировать'
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')} titleHtml={prepareTooltip('Редактировать операцию', 'Двойной клик')}
icon={<IconEdit2 size='1rem' className='icon-primary' />} icon={<IconEdit2 size='1rem' className='icon-primary' />}
disabled={controller.isProcessing} disabled={controller.isProcessing}
onClick={handleEditOperation} onClick={handleEditOperation}

View File

@ -22,7 +22,7 @@ function OperationNode(node: OssNodeInternal) {
<> <>
<Handle type='source' position={Position.Bottom} /> <Handle type='source' position={Position.Bottom} />
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'> <Overlay position='top-[-0.2rem] right-[-0.2rem]'>
<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

View File

@ -122,10 +122,43 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
controller.savePositions(getPositions(), () => setIsModified(false)); controller.savePositions(getPositions(), () => setIsModified(false));
}, [controller, getPositions, setIsModified]); }, [controller, getPositions, setIsModified]);
const handleCreateOperation = useCallback(() => { const handleCreateOperation = useCallback(
const center = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 }); (inputs: OperationID[]) => () => {
controller.promptCreateOperation(center.x, center.y, getPositions()); if (!controller.schema) {
}, [controller, getPositions, flow]); return;
}
let target = { x: 0, y: 0 };
const positions = getPositions();
if (inputs.length <= 1) {
target = flow.project({ x: window.innerWidth / 2, y: window.innerHeight / 2 });
} else {
const inputsNodes = positions.filter(pos => inputs.includes(pos.id));
const maxY = Math.max(...inputsNodes.map(node => node.position_y));
const minX = Math.min(...inputsNodes.map(node => node.position_x));
const maxX = Math.max(...inputsNodes.map(node => node.position_x));
target.y = maxY + 100;
target.x = Math.ceil((maxX + minX) / 2 / PARAMETER.ossGridSize) * PARAMETER.ossGridSize;
}
let flagIntersect = false;
do {
flagIntersect = positions.some(
position =>
Math.abs(position.position_x - target.x) < PARAMETER.ossMinDistance &&
Math.abs(position.position_y - target.y) < PARAMETER.ossMinDistance
);
if (flagIntersect) {
target.x += PARAMETER.ossMinDistance;
target.y += PARAMETER.ossMinDistance;
}
} while (flagIntersect);
controller.promptCreateOperation(target.x, target.y, inputs, positions);
},
[controller, getPositions, flow]
);
const handleDeleteSelected = useCallback(() => { const handleDeleteSelected = useCallback(() => {
if (controller.selected.length !== 1) { if (controller.selected.length !== 1) {
@ -241,13 +274,11 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
handleContextMenuHide(); handleContextMenuHide();
}, [handleContextMenuHide]); }, [handleContextMenuHide]);
const handleNodeClick = useCallback( const handleNodeDoubleClick = useCallback(
(event: CProps.EventMouse, node: OssNode) => { (event: CProps.EventMouse, node: OssNode) => {
if (event.ctrlKey || event.metaKey) { event.preventDefault();
event.preventDefault(); event.stopPropagation();
event.stopPropagation(); handleEditOperation(Number(node.id));
handleEditOperation(Number(node.id));
}
}, },
[handleEditOperation] [handleEditOperation]
); );
@ -268,7 +299,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
if ((event.ctrlKey || event.metaKey) && event.key === 'q') { if ((event.ctrlKey || event.metaKey) && event.key === 'q') {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
handleCreateOperation(); handleCreateOperation(controller.selected);
return; return;
} }
if (event.key === 'Delete') { if (event.key === 'Delete') {
@ -297,7 +328,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
edges={edges} edges={edges}
onNodesChange={handleNodesChange} onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodeClick={handleNodeClick} onNodeDoubleClick={handleNodeDoubleClick}
proOptions={{ hideAttribution: true }} proOptions={{ hideAttribution: true }}
fitView fitView
nodeTypes={OssNodeTypes} nodeTypes={OssNodeTypes}
@ -305,11 +336,11 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
minZoom={0.75} minZoom={0.75}
nodesConnectable={false} nodesConnectable={false}
snapToGrid={true} snapToGrid={true}
snapGrid={[10, 10]} snapGrid={[PARAMETER.ossGridSize, PARAMETER.ossGridSize]}
onNodeContextMenu={handleContextMenu} onNodeContextMenu={handleContextMenu}
onClick={handleClickCanvas} onClick={handleClickCanvas}
> >
{showGrid ? <Background gap={10} /> : null} {showGrid ? <Background gap={PARAMETER.ossGridSize} /> : null}
</ReactFlow> </ReactFlow>
), ),
[ [
@ -319,7 +350,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
handleContextMenu, handleContextMenu,
handleClickCanvas, handleClickCanvas,
onEdgesChange, onEdgesChange,
handleNodeClick, handleNodeDoubleClick,
OssNodeTypes, OssNodeTypes,
showGrid showGrid
] ]
@ -334,7 +365,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
edgeAnimate={edgeAnimate} edgeAnimate={edgeAnimate}
edgeStraight={edgeStraight} edgeStraight={edgeStraight}
onFitView={handleFitView} onFitView={handleFitView}
onCreate={handleCreateOperation} onCreate={handleCreateOperation(controller.selected)}
onDelete={handleDeleteSelected} onDelete={handleDeleteSelected}
onEdit={() => handleEditOperation(controller.selected[0])} onEdit={() => handleEditOperation(controller.selected[0])}
onExecute={handleExecuteSelected} onExecute={handleExecuteSelected}

View File

@ -167,7 +167,7 @@ function ToolbarOssGraph({
onClick={onExecute} onClick={onExecute}
/> />
<MiniButton <MiniButton
titleHtml={prepareTooltip('Редактировать выбранную', 'Ctrl + клик')} titleHtml={prepareTooltip('Редактировать выбранную', 'Двойной клик')}
icon={<IconEdit2 size='1.25rem' className='icon-primary' />} icon={<IconEdit2 size='1.25rem' className='icon-primary' />}
disabled={controller.selected.length !== 1 || controller.isProcessing} disabled={controller.selected.length !== 1 || controller.isProcessing}
onClick={onEdit} onClick={onEdit}

View File

@ -51,7 +51,7 @@ export interface IOssEditContext {
openOperationSchema: (target: OperationID) => void; openOperationSchema: (target: OperationID) => void;
savePositions: (positions: IOperationPosition[], callback?: () => void) => void; savePositions: (positions: IOperationPosition[], callback?: () => void) => void;
promptCreateOperation: (x: number, y: number, positions: IOperationPosition[]) => void; promptCreateOperation: (x: number, y: number, inputs: OperationID[], positions: IOperationPosition[]) => void;
deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void; deleteOperation: (target: OperationID, positions: IOperationPosition[]) => void;
createInput: (target: OperationID, positions: IOperationPosition[]) => void; createInput: (target: OperationID, positions: IOperationPosition[]) => void;
promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void; promptEditInput: (target: OperationID, positions: IOperationPosition[]) => void;
@ -96,6 +96,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
const [showCreateOperation, setShowCreateOperation] = useState(false); const [showCreateOperation, setShowCreateOperation] = useState(false);
const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 }); const [insertPosition, setInsertPosition] = useState<Position2D>({ x: 0, y: 0 });
const [initialInputs, setInitialInputs] = useState<OperationID[]>([]);
const [positions, setPositions] = useState<IOperationPosition[]>([]); const [positions, setPositions] = useState<IOperationPosition[]>([]);
const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined); const [targetOperationID, setTargetOperationID] = useState<OperationID | undefined>(undefined);
const targetOperation = useMemo( const targetOperation = useMemo(
@ -208,11 +209,15 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
[model] [model]
); );
const promptCreateOperation = useCallback((x: number, y: number, positions: IOperationPosition[]) => { const promptCreateOperation = useCallback(
setInsertPosition({ x: x, y: y }); (x: number, y: number, inputs: OperationID[], positions: IOperationPosition[]) => {
setPositions(positions); setInsertPosition({ x: x, y: y });
setShowCreateOperation(true); setInitialInputs(inputs);
}, []); setPositions(positions);
setShowCreateOperation(true);
},
[]
);
const handleCreateOperation = useCallback( const handleCreateOperation = useCallback(
(data: IOperationCreateData) => { (data: IOperationCreateData) => {
@ -341,6 +346,7 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
hideWindow={() => setShowCreateOperation(false)} hideWindow={() => setShowCreateOperation(false)}
oss={model.schema} oss={model.schema}
onCreate={handleCreateOperation} onCreate={handleCreateOperation}
initialInputs={initialInputs}
/> />
) : null} ) : null}
{showEditInput ? ( {showEditInput ? (

View File

@ -80,21 +80,15 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
return ( return (
<div className='overflow-y-auto' style={{ maxHeight: panelHeight }}> <div className='overflow-y-auto' style={{ maxHeight: panelHeight }}>
{controller.isContentEditable ? ( <ToolbarConstituenta
<ToolbarConstituenta activeCst={activeCst}
disabled={disabled} disabled={disabled}
modified={isModified} modified={isModified}
showList={showList} showList={showList}
onMoveUp={controller.moveUp} onSubmit={initiateSubmit}
onMoveDown={controller.moveDown} onReset={() => setToggleReset(prev => !prev)}
onSubmit={initiateSubmit} onToggleList={() => setShowList(prev => !prev)}
onReset={() => setToggleReset(prev => !prev)} />
onDelete={controller.deleteCst}
onClone={controller.cloneCst}
onCreate={() => controller.createCst(activeCst?.cst_type, false)}
onToggleList={() => setShowList(prev => !prev)}
/>
) : null}
<div <div
tabIndex={-1} tabIndex={-1}
className={clsx( className={clsx(

View File

@ -5,8 +5,10 @@ import { AnimatePresence } from 'framer-motion';
import { useEffect, useLayoutEffect, useMemo, useState } from 'react'; import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { IconSave } from '@/components/Icons'; import { IconChild, IconParent, IconSave } from '@/components/Icons';
import RefsInput from '@/components/RefsInput'; import RefsInput from '@/components/RefsInput';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
@ -182,7 +184,7 @@ function FormConstituenta({
} }
value={expression} value={expression}
activeCst={state} activeCst={state}
disabled={disabled} disabled={disabled || state?.is_inherited}
toggleReset={toggleReset} toggleReset={toggleReset}
onChange={newValue => setExpression(newValue)} onChange={newValue => setExpression(newValue)}
setTypification={setTypification} setTypification={setTypification}
@ -229,15 +231,33 @@ function FormConstituenta({
Добавить комментарий Добавить комментарий
</button> </button>
) : null} ) : null}
{!disabled || processing ? ( {!disabled || processing ? (
<SubmitButton <div className='self-center flex'>
key='cst_form_submit' <SubmitButton
id='cst_form_submit' key='cst_form_submit'
text='Сохранить изменения' id='cst_form_submit'
className='self-center' text='Сохранить изменения'
disabled={disabled || !isModified} disabled={disabled || !isModified}
icon={<IconSave size='1.25rem' />} icon={<IconSave size='1.25rem' />}
/> />
<Overlay position='top-[0.1rem] left-[0.4rem]' className='cc-icons'>
{state?.is_inherited_parent ? (
<MiniButton
icon={<IconChild size='1.25rem' className='clr-text-red' />}
disabled
titleHtml='Внимание!</br> Конституента имеет потомков<br/> в операционной схеме синтеза'
/>
) : null}
{state?.is_inherited ? (
<MiniButton
icon={<IconParent size='1.25rem' className='clr-text-red' />}
disabled
titleHtml='Внимание!</br> Конституента является наследником<br/>'
/>
) : null}
</Overlay>
</div>
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
</form> </form>

View File

@ -1,3 +1,5 @@
'use client';
import { import {
IconClone, IconClone,
IconDestroy, IconDestroy,
@ -6,95 +8,116 @@ import {
IconMoveDown, IconMoveDown,
IconMoveUp, IconMoveUp,
IconNewItem, IconNewItem,
IconPredecessor,
IconReset, IconReset,
IconSave IconSave
} from '@/components/Icons'; } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IConstituenta } from '@/models/rsform';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels'; import { prepareTooltip, tooltips } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext';
interface ToolbarConstituentaProps { interface ToolbarConstituentaProps {
activeCst?: IConstituenta;
disabled: boolean; disabled: boolean;
modified: boolean; modified: boolean;
showList: boolean; showList: boolean;
onSubmit: () => void; onSubmit: () => void;
onReset: () => void; onReset: () => void;
onMoveUp: () => void;
onMoveDown: () => void;
onDelete: () => void;
onClone: () => void;
onCreate: () => void;
onToggleList: () => void; onToggleList: () => void;
} }
function ToolbarConstituenta({ function ToolbarConstituenta({
activeCst,
disabled, disabled,
modified, modified,
showList, showList,
onSubmit, onSubmit,
onReset, onReset,
onMoveUp,
onMoveDown,
onDelete,
onClone,
onCreate,
onToggleList onToggleList
}: ToolbarConstituentaProps) { }: ToolbarConstituentaProps) {
const controller = useRSEdit();
return ( return (
<Overlay position='top-1 right-4' className='cc-icons sm:right-1/2 sm:translate-x-1/2'> <Overlay position='top-1 right-4' className='cc-icons sm:right-1/2 sm:translate-x-1/2'>
<MiniButton {controller.schema && controller.schema?.oss.length > 0 ? (
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')} <MiniSelectorOSS
icon={<IconSave size='1.25rem' className='icon-primary' />} items={controller.schema.oss}
disabled={disabled || !modified} onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
onClick={onSubmit} />
/> ) : null}
<MiniButton {activeCst && activeCst.is_inherited ? (
title='Сбросить несохраненные изменения' <MiniButton
icon={<IconReset size='1.25rem' className='icon-primary' />} title='Перейти к исходной конституенте в ОСС'
disabled={disabled || !modified} onClick={() => controller.viewPredecessor(activeCst.id)}
onClick={onReset} icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
/> />
<MiniButton ) : null}
title='Создать конституенту после данной' {controller.isContentEditable ? (
icon={<IconNewItem size={'1.25rem'} className='icon-green' />} <>
disabled={disabled} <MiniButton
onClick={onCreate} titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}
/> icon={<IconSave size='1.25rem' className='icon-primary' />}
<MiniButton disabled={disabled || !modified}
titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')} onClick={onSubmit}
icon={<IconClone size='1.25rem' className='icon-green' />} />
disabled={disabled || modified} <MiniButton
onClick={onClone} title='Сбросить несохраненные изменения'
/> icon={<IconReset size='1.25rem' className='icon-primary' />}
<MiniButton disabled={disabled || !modified}
title='Удалить редактируемую конституенту' onClick={onReset}
disabled={disabled} />
onClick={onDelete} <MiniButton
icon={<IconDestroy size='1.25rem' className='icon-red' />} title='Создать конституенту после данной'
/> icon={<IconNewItem size={'1.25rem'} className='icon-green' />}
disabled={disabled}
onClick={() => controller.createCst(activeCst?.cst_type, false)}
/>
<MiniButton
titleHtml={modified ? tooltips.unsaved : prepareTooltip('Клонировать конституенту', 'Alt + V')}
icon={<IconClone size='1.25rem' className='icon-green' />}
disabled={disabled || modified}
onClick={controller.cloneCst}
/>
<MiniButton
title='Удалить редактируемую конституенту'
disabled={disabled || !controller.canDeleteSelected}
onClick={controller.promptDeleteCst}
icon={<IconDestroy size='1.25rem' className='icon-red' />}
/>
</>
) : null}
<MiniButton <MiniButton
title='Отображение списка конституент' title='Отображение списка конституент'
icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />} icon={showList ? <IconList size='1.25rem' className='icon-primary' /> : <IconListOff size='1.25rem' />}
onClick={onToggleList} onClick={onToggleList}
/> />
<MiniButton
titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')} {controller.isContentEditable ? (
icon={<IconMoveUp size='1.25rem' className='icon-primary' />} <>
disabled={disabled || modified} <MiniButton
onClick={onMoveUp} titleHtml={prepareTooltip('Переместить вверх', 'Alt + вверх')}
/> icon={<IconMoveUp size='1.25rem' className='icon-primary' />}
<MiniButton disabled={disabled || modified}
titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')} onClick={controller.moveUp}
icon={<IconMoveDown size='1.25rem' className='icon-primary' />} />
disabled={disabled || modified} <MiniButton
onClick={onMoveDown} titleHtml={prepareTooltip('Переместить вниз', 'Alt + вниз')}
/> icon={<IconMoveDown size='1.25rem' className='icon-primary' />}
disabled={disabled || modified}
onClick={controller.moveDown}
/>
</>
) : null}
<BadgeHelp topic={HelpTopic.UI_RS_EDITOR} offset={4} className={PARAMETER.TOOLTIP_WIDTH} /> <BadgeHelp topic={HelpTopic.UI_RS_EDITOR} offset={4} className={PARAMETER.TOOLTIP_WIDTH} />
</Overlay> </Overlay>
); );

View File

@ -15,7 +15,7 @@ import { UserID, UserLevel } from '@/models/user';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
import { prompts } from '@/utils/labels'; import { prompts } from '@/utils/labels';
import LabeledValue from '../../../components/ui/LabeledValue'; import LabeledValue from '@/components/ui/LabeledValue';
interface EditorLibraryItemProps { interface EditorLibraryItemProps {
item?: ILibraryItemData; item?: ILibraryItemData;

View File

@ -2,7 +2,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useEffect, useLayoutEffect, useState } from 'react'; import { useEffect, useLayoutEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { IconSave } from '@/components/Icons'; import { IconSave } from '@/components/Icons';
import SelectVersion from '@/components/select/SelectVersion'; import SelectVersion from '@/components/select/SelectVersion';
@ -10,10 +9,8 @@ import Label from '@/components/ui/Label';
import SubmitButton from '@/components/ui/SubmitButton'; import SubmitButton from '@/components/ui/SubmitButton';
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 { useRSForm } from '@/context/RSFormContext';
import { ILibraryUpdateData, LibraryItemType } from '@/models/library'; import { ILibraryUpdateData, LibraryItemType } from '@/models/library';
import { limits, patterns } from '@/utils/constants'; import { limits, patterns } from '@/utils/constants';
import { information } from '@/utils/labels';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
import ToolbarItemAccess from './ToolbarItemAccess'; import ToolbarItemAccess from './ToolbarItemAccess';
@ -26,8 +23,8 @@ interface FormRSFormProps {
} }
function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) { function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
const { schema, update, processing } = useRSForm();
const controller = useRSEdit(); const controller = useRSEdit();
const schema = controller.schema;
const [title, setTitle] = useState(''); const [title, setTitle] = useState('');
const [alias, setAlias] = useState(''); const [alias, setAlias] = useState('');
@ -85,7 +82,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
visible: visible, visible: visible,
read_only: readOnly read_only: readOnly
}; };
update(data, () => toast.success(information.changesSaved)); controller.updateSchema(data);
}; };
return ( return (
@ -112,7 +109,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
onChange={event => setAlias(event.target.value)} onChange={event => setAlias(event.target.value)}
/> />
<div className='flex flex-col'> <div className='flex flex-col'>
<ToolbarVersioning /> <ToolbarVersioning blockReload={schema && schema?.oss.length > 0} />
<ToolbarItemAccess <ToolbarItemAccess
visible={visible} visible={visible}
toggleVisible={() => setVisible(prev => !prev)} toggleVisible={() => setVisible(prev => !prev)}
@ -143,7 +140,7 @@ function FormRSForm({ id, isModified, setIsModified }: FormRSFormProps) {
<SubmitButton <SubmitButton
text='Сохранить изменения' text='Сохранить изменения'
className='self-center mt-4' className='self-center mt-4'
loading={processing} loading={controller.isProcessing}
disabled={!isModified} disabled={!isModified}
icon={<IconSave size='1.25rem' />} icon={<IconSave size='1.25rem' />}
/> />

View File

@ -14,13 +14,16 @@ function RSFormStats({ stats }: RSFormStatsProps) {
<div className='flex flex-col sm:gap-1 sm:ml-6 sm:mt-8 sm:w-[16rem]'> <div className='flex flex-col sm:gap-1 sm:ml-6 sm:mt-8 sm:w-[16rem]'>
<Divider margins='my-2' className='sm:hidden' /> <Divider margins='my-2' className='sm:hidden' />
<LabeledValue id='count_all' label='Всего конституент ' text={stats.count_all} /> <LabeledValue id='count_all' label='Всего конституент' text={stats.count_all} />
<LabeledValue id='count_errors' label='Некорректных' text={stats.count_errors} /> {stats.count_inherited !== 0 ? (
<LabeledValue id='count_inherited' label='Наследованные' text={stats.count_inherited} />
) : null}
<LabeledValue id='count_errors' label='Некорректные' text={stats.count_errors} />
{stats.count_property !== 0 ? ( {stats.count_property !== 0 ? (
<LabeledValue id='count_property' label='Неразмерных' text={stats.count_property} /> <LabeledValue id='count_property' label='Неразмерные' text={stats.count_property} />
) : null} ) : null}
{stats.count_incalculable !== 0 ? ( {stats.count_incalculable !== 0 ? (
<LabeledValue id='count_incalculable' label='Невычислимых' text={stats.count_incalculable} /> <LabeledValue id='count_incalculable' label='Невычислимые' text={stats.count_incalculable} />
) : null} ) : null}
<Divider margins='my-2' /> <Divider margins='my-2' />

View File

@ -29,7 +29,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
); );
return ( return (
<Overlay position='top-[4.5rem] right-0 w-[12rem] pr-2' className='flex'> <Overlay position='top-[4.5rem] right-0 w-[12rem] pr-2' className='flex' layer='z-bottom'>
<Label text='Доступ' className='self-center select-none' /> <Label text='Доступ' className='self-center select-none' />
<div className='ml-auto cc-icons'> <div className='ml-auto cc-icons'>
<SelectAccessPolicy <SelectAccessPolicy

View File

@ -5,15 +5,19 @@ import { useMemo } from 'react';
import { SubscribeIcon } from '@/components/DomainIcons'; import { SubscribeIcon } from '@/components/DomainIcons';
import { IconDestroy, IconSave, IconShare } from '@/components/Icons'; import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
import { AccessPolicy, ILibraryItemEditor } from '@/models/library'; import { AccessPolicy, ILibraryItemEditor, LibraryItemType } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IRSForm } from '@/models/rsform';
import { UserLevel } from '@/models/user'; import { UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels'; import { prepareTooltip, tooltips } from '@/utils/labels';
import { IRSEditContext } from '../RSEditContext';
interface ToolbarRSFormCardProps { interface ToolbarRSFormCardProps {
modified: boolean; modified: boolean;
subscribed: boolean; subscribed: boolean;
@ -33,8 +37,26 @@ function ToolbarRSFormCard({
}: ToolbarRSFormCardProps) { }: ToolbarRSFormCardProps) {
const { accessLevel } = useAccessMode(); const { accessLevel } = useAccessMode();
const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]); const canSave = useMemo(() => modified && !controller.isProcessing, [modified, controller.isProcessing]);
const ossSelector = useMemo(() => {
if (!controller.schema || controller.schema?.item_type !== LibraryItemType.RSFORM) {
return null;
}
const schema = controller.schema as IRSForm;
if (schema.oss.length <= 0) {
return null;
}
return (
<MiniSelectorOSS
items={schema.oss}
onSelect={(event, value) => (controller as IRSEditContext).viewOSS(value.id, event.ctrlKey || event.metaKey)}
/>
);
}, [controller]);
return ( return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'> <Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
{ossSelector}
{controller.isMutable || modified ? ( {controller.isMutable || modified ? (
<MiniButton <MiniButton
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')} titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}

View File

@ -7,17 +7,25 @@ import { PARAMETER } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext'; import { useRSEdit } from '../RSEditContext';
function ToolbarVersioning() { interface ToolbarVersioningProps {
blockReload?: boolean;
}
function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
const controller = useRSEdit(); const controller = useRSEdit();
return ( return (
<Overlay position='top-[-0.4rem] right-[0rem]' className='pr-2 cc-icons'> <Overlay position='top-[-0.4rem] right-[0rem]' className='pr-2 cc-icons' layer='z-bottom'>
{controller.isMutable ? ( {controller.isMutable ? (
<> <>
<MiniButton <MiniButton
titleHtml={ titleHtml={
!controller.isContentEditable ? 'Откатить к версии' : 'Переключитесь на <br/>неактуальную версию' blockReload
? 'Невозможно откатить КС, прикрепленную к операционной схеме'
: !controller.isContentEditable
? 'Откатить к версии'
: 'Переключитесь на <br/>неактуальную версию'
} }
disabled={controller.isContentEditable} disabled={controller.isContentEditable || blockReload}
onClick={() => controller.restoreVersion()} onClick={() => controller.restoreVersion()}
icon={<IconUpload size='1.25rem' className='icon-red' />} icon={<IconUpload size='1.25rem' className='icon-red' />}
/> />
@ -30,7 +38,7 @@ function ToolbarVersioning() {
<MiniButton <MiniButton
title={controller.schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'} title={controller.schema?.versions.length === 0 ? 'Список версий пуст' : 'Редактировать версии'}
disabled={!controller.schema || controller.schema?.versions.length === 0} disabled={!controller.schema || controller.schema?.versions.length === 0}
onClick={controller.editVersions} onClick={controller.promptEditVersions}
icon={<IconVersions size='1.25rem' className='icon-primary' />} icon={<IconVersions size='1.25rem' className='icon-primary' />}
/> />
</> </>

View File

@ -59,10 +59,10 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
if (!controller.isContentEditable || controller.isProcessing) { if (!controller.isContentEditable || controller.isProcessing) {
return; return;
} }
if (event.key === 'Delete' && controller.selected.length > 0) { if (event.key === 'Delete' && controller.canDeleteSelected) {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
controller.deleteCst(); controller.promptDeleteCst();
return; return;
} }
if (!event.altKey || event.shiftKey) { if (!event.altKey || event.shiftKey) {

View File

@ -8,6 +8,7 @@ import {
IconReset IconReset
} from '@/components/Icons'; } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import Dropdown from '@/components/ui/Dropdown'; import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton'; import DropdownButton from '@/components/ui/DropdownButton';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
@ -27,6 +28,12 @@ function ToolbarRSList() {
return ( return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='items-start cc-icons'> <Overlay position='top-1 right-1/2 translate-x-1/2' className='items-start cc-icons'>
{controller.schema && controller.schema?.oss.length > 0 ? (
<MiniSelectorOSS
items={controller.schema.oss}
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
/>
) : null}
<MiniButton <MiniButton
titleHtml={prepareTooltip('Сбросить выделение', 'ESC')} titleHtml={prepareTooltip('Сбросить выделение', 'ESC')}
icon={<IconReset size='1.25rem' className='icon-primary' />} icon={<IconReset size='1.25rem' className='icon-primary' />}
@ -79,8 +86,8 @@ function ToolbarRSList() {
<MiniButton <MiniButton
titleHtml={prepareTooltip('Удалить выбранные', 'Delete')} titleHtml={prepareTooltip('Удалить выбранные', 'Delete')}
icon={<IconDestroy size='1.25rem' className='icon-red' />} icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={controller.isProcessing || controller.nothingSelected} disabled={controller.isProcessing || !controller.canDeleteSelected}
onClick={controller.deleteCst} onClick={controller.promptDeleteCst}
/> />
<BadgeHelp topic={HelpTopic.UI_RS_LIST} offset={5} /> <BadgeHelp topic={HelpTopic.UI_RS_LIST} offset={5} />
</Overlay> </Overlay>

View File

@ -105,7 +105,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
result.push({ result.push({
id: String(node.id), id: String(node.id),
fill: focusCst === cst ? colors.bgPurple : colorBgGraphNode(cst, coloring, colors), fill: focusCst === cst ? colors.bgPurple : colorBgGraphNode(cst, coloring, colors),
label: cst.alias, label: `${cst.alias}${cst.is_inherited ? '*' : ''}`,
subLabel: !filterParams.noText ? cst.term_resolved : undefined, subLabel: !filterParams.noText ? cst.term_resolved : undefined,
size: applyNodeSizing(cst, sizing) size: applyNodeSizing(cst, sizing)
}); });
@ -141,10 +141,10 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
} }
function handleDeleteCst() { function handleDeleteCst() {
if (!controller.schema || controller.nothingSelected) { if (!controller.schema || !controller.canDeleteSelected) {
return; return;
} }
controller.deleteCst(); controller.promptDeleteCst();
} }
const handleChangeLayout = useCallback( const handleChangeLayout = useCallback(

View File

@ -13,6 +13,7 @@ import {
IconTextOff IconTextOff
} from '@/components/Icons'; } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
@ -55,6 +56,12 @@ function ToolbarTermGraph({
return ( return (
<div className='cc-icons'> <div className='cc-icons'>
{controller.schema && controller.schema?.oss.length > 0 ? (
<MiniSelectorOSS
items={controller.schema.oss}
onSelect={(event, value) => controller.viewOSS(value.id, event.ctrlKey || event.metaKey)}
/>
) : null}
<MiniButton <MiniButton
title='Настройки фильтрации узлов и связей' title='Настройки фильтрации узлов и связей'
icon={<IconFilter size='1.25rem' className='icon-primary' />} icon={<IconFilter size='1.25rem' className='icon-primary' />}
@ -105,7 +112,7 @@ function ToolbarTermGraph({
<MiniButton <MiniButton
title='Удалить выбранные' title='Удалить выбранные'
icon={<IconDestroy size='1.25rem' className='icon-red' />} icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={controller.nothingSelected || controller.isProcessing} disabled={!controller.canDeleteSelected || controller.isProcessing}
onClick={onDelete} onClick={onDelete}
/> />
) : null} ) : null}

View File

@ -105,12 +105,19 @@ function ViewHidden({ items, selected, toggleSelection, setFocus, schema, colori
className='min-w-[3rem] rounded-md text-center select-none' className='min-w-[3rem] rounded-md text-center select-none'
style={{ style={{
backgroundColor: colorBgGraphNode(cst, adjustedColoring, colors), backgroundColor: colorBgGraphNode(cst, adjustedColoring, colors),
...(localSelected.includes(cstID) ? { outlineWidth: '2px', outlineStyle: 'solid' } : {}) ...(localSelected.includes(cstID)
? {
outlineWidth: '2px',
outlineStyle: cst.is_inherited ? 'dashed' : 'solid',
outlineColor: colors.fgDefault
}
: {})
}} }}
onClick={event => handleClick(cstID, event)} onClick={event => handleClick(cstID, event)}
onDoubleClick={() => onEdit(cstID)} onDoubleClick={() => onEdit(cstID)}
> >
{cst.alias} {cst.alias}
{cst.is_inherited ? '*' : ''}
</button> </button>
<TooltipConstituenta data={cst} anchor={`#${id}`} /> <TooltipConstituenta data={cst} anchor={`#${id}`} />
</div> </div>

View File

@ -163,9 +163,9 @@ function MenuRSTabs({ onDestroy }: MenuRSTabsProps) {
/> />
{controller.isContentEditable ? ( {controller.isContentEditable ? (
<DropdownButton <DropdownButton
text='Загрузить из Экстеора' text='Загрузить из Экстеор'
icon={<IconUpload size='1rem' className='icon-red' />} icon={<IconUpload size='1rem' className='icon-red' />}
disabled={controller.isProcessing} disabled={controller.isProcessing || controller.schema?.oss.length !== 0}
onClick={handleUpload} onClick={handleUpload}
/> />
) : null} ) : null}

View File

@ -24,7 +24,14 @@ import DlgInlineSynthesis from '@/dialogs/DlgInlineSynthesis';
import DlgRenameCst from '@/dialogs/DlgRenameCst'; import DlgRenameCst from '@/dialogs/DlgRenameCst';
import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst'; import DlgSubstituteCst from '@/dialogs/DlgSubstituteCst';
import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm'; import DlgUploadRSForm from '@/dialogs/DlgUploadRSForm';
import { AccessPolicy, IVersionData, LocationHead, VersionID } from '@/models/library'; import {
AccessPolicy,
ILibraryUpdateData,
IVersionData,
LibraryItemID,
LocationHead,
VersionID
} from '@/models/library';
import { ICstSubstituteData } from '@/models/oss'; import { ICstSubstituteData } from '@/models/oss';
import { import {
ConstituentaID, ConstituentaID,
@ -46,6 +53,8 @@ import { EXTEOR_TRS_FILE } from '@/utils/constants';
import { information, prompts } from '@/utils/labels'; import { information, prompts } from '@/utils/labels';
import { promptUnsaved } from '@/utils/utils'; import { promptUnsaved } from '@/utils/utils';
import { RSTabID } from './RSTabs';
export interface IRSEditContext { export interface IRSEditContext {
schema?: IRSForm; schema?: IRSForm;
selected: ConstituentaID[]; selected: ConstituentaID[];
@ -55,6 +64,9 @@ export interface IRSEditContext {
isProcessing: boolean; isProcessing: boolean;
canProduceStructure: boolean; canProduceStructure: boolean;
nothingSelected: boolean; nothingSelected: boolean;
canDeleteSelected: boolean;
updateSchema: (data: ILibraryUpdateData) => void;
setOwner: (newOwner: UserID) => void; setOwner: (newOwner: UserID) => void;
setAccessPolicy: (newPolicy: AccessPolicy) => void; setAccessPolicy: (newPolicy: AccessPolicy) => void;
@ -68,17 +80,19 @@ export interface IRSEditContext {
toggleSelect: (target: ConstituentaID) => void; toggleSelect: (target: ConstituentaID) => void;
deselectAll: () => void; deselectAll: () => void;
viewOSS: (target: LibraryItemID, newTab?: boolean) => void;
viewVersion: (version?: VersionID, newTab?: boolean) => void; viewVersion: (version?: VersionID, newTab?: boolean) => void;
viewPredecessor: (target: ConstituentaID) => void;
createVersion: () => void; createVersion: () => void;
restoreVersion: () => void; restoreVersion: () => void;
editVersions: () => void; promptEditVersions: () => void;
moveUp: () => void; moveUp: () => void;
moveDown: () => void; moveDown: () => void;
createCst: (type: CstType | undefined, skipDialog: boolean, definition?: string) => void; createCst: (type: CstType | undefined, skipDialog: boolean, definition?: string) => void;
renameCst: () => void; renameCst: () => void;
cloneCst: () => void; cloneCst: () => void;
deleteCst: () => void; promptDeleteCst: () => void;
editTermForms: () => void; editTermForms: () => void;
promptTemplate: () => void; promptTemplate: () => void;
@ -135,6 +149,10 @@ export const RSEditState = ({
); );
const isContentEditable = useMemo(() => isMutable && !model.isArchive, [isMutable, model.isArchive]); const isContentEditable = useMemo(() => isMutable && !model.isArchive, [isMutable, model.isArchive]);
const nothingSelected = useMemo(() => selected.length === 0, [selected]); const nothingSelected = useMemo(() => selected.length === 0, [selected]);
const canDeleteSelected = useMemo(
() => !nothingSelected && selected.every(id => !model.schema?.cstByID.get(id)?.is_inherited),
[selected, nothingSelected, model.schema]
);
const [showUpload, setShowUpload] = useState(false); const [showUpload, setShowUpload] = useState(false);
const [showClone, setShowClone] = useState(false); const [showClone, setShowClone] = useState(false);
@ -177,11 +195,35 @@ export const RSEditState = ({
[model.schema, setAccessLevel, model.isOwned, user, adminMode] [model.schema, setAccessLevel, model.isOwned, user, adminMode]
); );
const updateSchema = useCallback(
(data: ILibraryUpdateData) => model.update(data, () => toast.success(information.changesSaved)),
[model]
);
const viewVersion = useCallback( const viewVersion = useCallback(
(version?: VersionID, newTab?: boolean) => router.push(urls.schema(model.itemID, version), newTab), (version?: VersionID, newTab?: boolean) => router.push(urls.schema(model.itemID, version), newTab),
[router, model] [router, model]
); );
const viewPredecessor = useCallback(
(target: ConstituentaID) =>
model.findPredecessor({ target: target }, reference =>
router.push(
urls.schema_props({
id: reference.schema,
active: reference.id,
tab: RSTabID.CST_EDIT
})
)
),
[router, model]
);
const viewOSS = useCallback(
(target: LibraryItemID, newTab?: boolean) => router.push(urls.oss(target), newTab),
[router]
);
const createVersion = useCallback(() => { const createVersion = useCallback(() => {
if (isModified && !promptUnsaved()) { if (isModified && !promptUnsaved()) {
return; return;
@ -571,12 +613,14 @@ export const RSEditState = ({
<RSEditContext.Provider <RSEditContext.Provider
value={{ value={{
schema: model.schema, schema: model.schema,
updateSchema,
selected, selected,
isMutable, isMutable,
isContentEditable, isContentEditable,
isProcessing: model.processing, isProcessing: model.processing,
canProduceStructure, canProduceStructure,
nothingSelected, nothingSelected,
canDeleteSelected,
toggleSubscribe, toggleSubscribe,
setOwner, setOwner,
@ -591,17 +635,19 @@ export const RSEditState = ({
setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])), setSelected(prev => (prev.includes(target) ? prev.filter(id => id !== target) : [...prev, target])),
deselectAll: () => setSelected([]), deselectAll: () => setSelected([]),
viewOSS,
viewVersion, viewVersion,
viewPredecessor,
createVersion, createVersion,
restoreVersion, restoreVersion,
editVersions: () => setShowEditVersions(true), promptEditVersions: () => setShowEditVersions(true),
moveUp, moveUp,
moveDown, moveDown,
createCst, createCst,
cloneCst, cloneCst,
renameCst, renameCst,
deleteCst: () => setShowDeleteCst(true), promptDeleteCst: () => setShowDeleteCst(true),
editTermForms, editTermForms,
promptTemplate, promptTemplate,

View File

@ -62,7 +62,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, dense, setFilt
); );
return ( return (
<div className='flex border-b clr-input overflow-hidden'> <div className='flex border-b clr-input'>
<SearchBar <SearchBar
id='constituents_search' id='constituents_search'
noBorder noBorder

View File

@ -15,6 +15,8 @@ export const PARAMETER = {
ossImageWidth: 1280, // pixels - size of OSS image ossImageWidth: 1280, // pixels - size of OSS image
ossImageHeight: 960, // pixels - size of OSS image ossImageHeight: 960, // pixels - size of OSS image
ossContextMenuWidth: 200, // pixels - width of OSS context menu ossContextMenuWidth: 200, // pixels - width of OSS context menu
ossGridSize: 10, // pixels - size of OSS grid
ossMinDistance: 20, // pixels - minimum distance between node centers
graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be graphHoverXLimit: 0.4, // ratio to clientWidth used to determine which side of screen popup should be
graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be graphHoverYLimit: 0.6, // ratio to clientHeight used to determine which side of screen popup should be
@ -139,6 +141,7 @@ export const globals = {
*/ */
export const prefixes = { export const prefixes = {
page_size: 'page_size_', page_size: 'page_size_',
oss_list: 'oss_list_',
cst_list: 'cst_list_', cst_list: 'cst_list_',
cst_inline_synth_list: 'cst_inline_synth_list_', cst_inline_synth_list: 'cst_inline_synth_list_',
cst_inline_synth_substitutes: 'cst_inline_synth_substitutes_', cst_inline_synth_substitutes: 'cst_inline_synth_substitutes_',

View File

@ -952,7 +952,8 @@ export const errors = {
astFailed: 'Невозможно построить дерево разбора', astFailed: 'Невозможно построить дерево разбора',
passwordsMismatch: 'Пароли не совпадают', passwordsMismatch: 'Пароли не совпадают',
imageFailed: 'Ошибка при создании изображения', imageFailed: 'Ошибка при создании изображения',
reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении' reuseOriginal: 'Повторное использование удаляемой конституенты при отождествлении',
substituteInherited: 'Нельзя удалять наследованные конституенты при отождествлении'
}; };
/** /**