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
[Functionality - PROGRESS]
- Operational synthesis schema as LibraryItem ?
- Library organization, search and exploration. Consider new user experience
- Private projects and permissions. Consider cooperative editing
- Design first user experience
- Private projects. Consider cooperative editing
[Functionality - PENDING]

View File

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

View File

@ -17,6 +17,14 @@ class LibraryItemBaseSerializer(serializers.ModelSerializer):
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):
''' Serializer: LibraryItem entry limited access. '''
class Meta:

View File

@ -1,13 +1,11 @@
''' Models: OSS API. '''
from typing import Optional
from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import QuerySet
from apps.library.models import Editor, LibraryItem, LibraryItemType
from apps.rsform.models import RSForm
from shared import messages as msg
from .Argument import Argument
from .Inheritance import Inheritance
@ -66,8 +64,6 @@ class OperationSchema:
@transaction.atomic
def create_operation(self, **kwargs) -> 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)
self.save()
result.refresh_from_db()
@ -198,7 +194,7 @@ class OperationSchema:
# TODO: remove duplicates from diamond
for cst in receiver.constituents():
parent = parents.get(cst.id)
parent = parents.get(cst.pk)
assert parent is not None
Inheritance.objects.create(
child=cst,

View File

@ -10,4 +10,4 @@ from .data_access import (
OperationUpdateSerializer,
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'])
for operation in attrs['arguments']:
if operation.oss != oss:
if operation.oss_id != oss.pk:
raise serializers.ValidationError({
'arguments': msg.operationNotInOSS(oss.title)
})
if 'substitutions' not in 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']
to_delete = {x['original'].pk for x in substitutions}
deleted = set()
for item in substitutions:
original_cst = cast(Constituenta, item['original'])
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({
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({
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:
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({
'alias': msg.substituteTrivial(original_cst.alias)
})
@ -131,7 +131,7 @@ class OperationTargetSerializer(serializers.Serializer):
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss:
if oss and operation.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title)
})
@ -155,7 +155,7 @@ class SetOperationInputSerializer(serializers.Serializer):
def validate(self, attrs):
oss = cast(LibraryItem, self.context['oss'])
operation = cast(Operation, attrs['target'])
if oss and operation.oss != oss:
if oss and operation.oss_id != oss.pk:
raise serializers.ValidationError({
'target': msg.operationNotInOSS(oss.title)
})

View File

@ -16,3 +16,9 @@ class NewSchemaResponse(serializers.Serializer):
''' Serializer: Create RSForm for input operation response. '''
new_schema = LibraryItemSerializer()
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.serializers import LibraryItemSerializer
from apps.rsform.models import Constituenta
from apps.rsform.serializers import CstTargetSerializer
from shared import messages as msg
from shared import permissions
@ -43,6 +45,8 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
permission_list = [permissions.ItemEditor]
elif self.action in ['details']:
permission_list = [permissions.ItemAnyone]
elif self.action in ['get_predecessor']:
permission_list = [permissions.Anyone]
else:
permission_list = [permissions.Anyone]
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:
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.title = operation.title
operation.result.comment = operation.comment
@ -306,3 +310,34 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
status=c.HTTP_200_OK,
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.save()
self.on_term_change([result.id])
self.on_term_change([result.pk])
result.refresh_from_db()
return result
@ -213,7 +213,7 @@ class RSForm:
count_bot = 0
size = len(listCst)
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 count_top + 1 < target:
cst.order = count_top + 1
@ -248,7 +248,7 @@ class RSForm:
mapping = {original.alias: substitution.alias}
self.apply_mapping(mapping)
original.delete()
self.on_term_change([substitution.id])
self.on_term_change([substitution.pk])
def restore_order(self):
''' Restore order based on types and term graph. '''
@ -335,7 +335,7 @@ class RSForm:
definition_formal=text,
cst_type=cst_type
)
result.append(new_item.id)
result.append(new_item.pk)
free_index = free_index + 1
position = position + 1
@ -347,7 +347,7 @@ class RSForm:
return
update_list = \
Constituenta.objects \
.only('id', 'order', 'schema') \
.only('order') \
.filter(schema=self.model, order__gte=start)
for cst in update_list:
cst.order += shift
@ -372,7 +372,7 @@ class RSForm:
@transaction.atomic
def _reset_order(self):
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:
cst.order = order
cst.save()
@ -383,15 +383,15 @@ class RSForm:
result: Graph[int] = Graph()
cst_list = \
self.constituents() \
.only('id', 'order', 'alias', 'definition_formal') \
.only('alias', 'definition_formal') \
.order_by('order')
for cst in cst_list:
result.add_node(cst.id)
result.add_node(cst.pk)
for cst in cst_list:
for alias in extract_globals(cst.definition_formal):
try:
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:
pass
return result
@ -401,15 +401,15 @@ class RSForm:
result: Graph[int] = Graph()
cst_list = \
self.constituents() \
.only('id', 'order', 'alias', 'term_raw') \
.only('alias', 'term_raw') \
.order_by('order')
for cst in cst_list:
result.add_node(cst.id)
result.add_node(cst.pk)
for cst in cst_list:
for alias in extract_entities(cst.term_raw):
try:
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:
pass
return result
@ -419,15 +419,15 @@ class RSForm:
result: Graph[int] = Graph()
cst_list = \
self.constituents() \
.only('id', 'order', 'alias', 'definition_raw') \
.only('alias', 'definition_raw') \
.order_by('order')
for cst in cst_list:
result.add_node(cst.id)
result.add_node(cst.pk)
for cst in cst_list:
for alias in extract_entities(cst.definition_raw):
try:
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:
pass
return result
@ -440,16 +440,16 @@ class SemanticInfo:
self._graph = schema._graph_formal()
self._items = list(
schema.constituents()
.only('id', 'alias', 'cst_type', 'definition_formal')
.only('alias', 'cst_type', 'definition_formal')
.order_by('order')
)
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 = {
cst.id: {
cst.pk: {
'is_simple': False,
'is_template': False,
'parent': cst.id,
'parent': cst.pk,
'children': []
}
for cst in self._items
@ -491,7 +491,7 @@ class SemanticInfo:
if target.cst_type == CstType.STRUCTURED or is_base_set(target.cst_type):
return False
dependencies = self._graph.inputs[target.id]
dependencies = self._graph.inputs[target.pk]
has_complex_dependency = any(
self.is_template(cst_id) and
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:
sources = self._extract_sources(target)
if len(sources) != 1:
return target.id
return target.pk
parent_id = next(iter(sources))
parent = self._cst_by_ID[parent_id]
if is_base_set(parent.cst_type):
return target.id
return target.pk
return parent_id
def _extract_sources(self, target: Constituenta) -> set[int]:
sources: set[int] = set()
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]
if not parent_info['is_template'] or not parent_info['is_simple']:
sources.add(parent_info['parent'])
@ -531,7 +531,7 @@ class SemanticInfo:
if not parent:
continue
parent_info = self[parent.id]
parent_info = self[parent.pk]
if not parent_info['is_template'] or not parent_info['is_simple']:
sources.add(parent_info['parent'])
@ -542,7 +542,7 @@ class SemanticInfo:
if not parent:
continue
parent_info = self[parent.id]
parent_info = self[parent.pk]
if not is_base_set(parent.cst_type) and \
(not parent_info['is_template'] or not parent_info['is_simple']):
sources.add(parent_info['parent'])
@ -567,10 +567,10 @@ class _OrderManager:
self._graph = schema._graph_formal()
self._items = list(
schema.constituents()
.only('id', 'order', 'alias', 'cst_type', 'definition_formal')
.only('order', 'alias', 'cst_type', 'definition_formal')
.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:
''' Implement order restoration process. '''
@ -582,20 +582,20 @@ class _OrderManager:
self._save_order()
def _fix_topological(self) -> None:
sorted_ids = self._graph.sort_stable([cst.id for cst in self._items])
sorted_items = [next(cst for cst in self._items if cst.id == id) for id in sorted_ids]
sorted_ids = self._graph.sort_stable([cst.pk for cst in self._items])
sorted_items = [next(cst for cst in self._items if cst.pk == id) for id in sorted_ids]
self._items = sorted_items
def _fix_kernel(self) -> None:
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]
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
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)
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]
self._items = result
@ -606,11 +606,11 @@ class _OrderManager:
if cst in marked:
continue
result.append(cst)
children = self._semantic[cst.id]['children']
children = self._semantic[cst.pk]['children']
if len(children) == 0:
continue
for child in self._items:
if child.id in children:
if child.pk in children:
marked.add(child)
result.append(child)
self._items = result

View File

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

View File

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

View File

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

View File

@ -12,13 +12,13 @@
"preview": "vite preview"
},
"dependencies": {
"@lezer/lr": "^1.4.1",
"@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.19.3",
"@uiw/codemirror-themes": "^4.23.0",
"@uiw/react-codemirror": "^4.23.0",
"axios": "^1.7.2",
"clsx": "^2.1.1",
"framer-motion": "^11.3.17",
"framer-motion": "^11.3.19",
"html-to-image": "^1.11.11",
"js-file-download": "^0.4.12",
"react": "^18.3.1",
@ -36,16 +36,16 @@
"react-zoom-pan-pinch": "^3.6.1",
"reactflow": "^11.11.4",
"reagraph": "^4.19.2",
"use-debounce": "^10.0.1"
"use-debounce": "^10.0.2"
},
"devDependencies": {
"@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.12",
"@types/node": "^20.14.13",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
@ -56,7 +56,7 @@
"jest": "^29.7.0",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"ts-jest": "^29.2.3",
"ts-jest": "^29.2.4",
"typescript": "^5.5.4",
"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,
ITargetOperation
} from '@/models/oss';
import { IConstituentaReference, ITargetCst } from '@/models/rsform';
import { AxiosGet, AxiosPatch, AxiosPost, FrontExchange, FrontPull, FrontPush } from './apiTransport';
@ -73,3 +74,10 @@ export function postExecuteOperation(oss: string, request: FrontExchange<ITarget
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 { BiDiamond as IconTemplates } from 'react-icons/bi';
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 { LuArchive as IconArchive } 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
'px-1',
'border rounded-md',
value.is_inherited && 'border-dashed',
'text-center font-medium whitespace-nowrap'
)}
style={{

View File

@ -13,7 +13,10 @@ interface InfoConstituentaProps extends CProps.Div {
function InfoConstituenta({ data, className, ...restProps }: InfoConstituentaProps) {
return (
<div className={clsx('dense min-w-[15rem]', className)} {...restProps}>
<h2>Конституента {data.alias}</h2>
<h2>
Конституента {data.alias}
{data.is_inherited ? ' (наследуется)' : ''}
</h2>
{data.term_resolved ? (
<p>
<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 { OssNodeInternal } from '@/models/miscellaneous';
import { ICstSubstituteEx } from '@/models/oss';
import { labelOperationType } from '@/utils/labels';
import { IconPageRight } from '../Icons';
import DataTable from '../ui/DataTable';
interface TooltipOperationProps {
node: OssNodeInternal;
anchor: string;
}
const columnHelper = createColumnHelper<ICstSubstituteEx>();
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 (
<Tooltip layer='z-modalTooltip' anchorSelect={anchor} className='max-w-[35rem] max-h-[40rem] dense my-3'>
<h2>{node.data.operation.alias}</h2>
@ -29,6 +82,7 @@ function TooltipOperation({ node, anchor }: TooltipOperationProps) {
<p>
<b>Положение:</b> [{node.xPos}, {node.yPos}]
</p>
{node.data.operation.substitutions.length > 0 ? table : null}
</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 replacements = substitutions.map(item => item.substitution);
console.log(toDelete, replacements);
console.log(newSubstitution);
if (
toDelete.includes(newSubstitution.original) ||
toDelete.includes(newSubstitution.substitution) ||
@ -112,6 +110,12 @@ function PickSubstitutions({
toast.error(errors.reuseOriginal);
return;
}
if (leftArgument === rightArgument) {
if ((deleteRight && rightCst?.is_inherited) || (!deleteRight && leftCst?.is_inherited)) {
toast.error(errors.substituteInherited);
return;
}
}
setSubstitutions(prev => [...prev, newSubstitution]);
setLeftCst(undefined);
setRightCst(undefined);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,6 +32,7 @@ export class OssLoader {
this.prepareLookups();
this.createGraph();
this.extractSchemas();
this.inferOperationAttributes();
result.operationByID = this.operationByID;
result.graph = this.graph;
@ -42,7 +43,7 @@ export class OssLoader {
private prepareLookups() {
this.oss.items.forEach(operation => {
this.operationByID.set(operation.id, operation);
this.operationByID.set(operation.id, operation as IOperation);
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);
}
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 {
const items = this.oss.items;
return {

View File

@ -59,6 +59,8 @@ export class RSFormLoader {
}
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 => {
const cst = this.cstByID.get(cstID)!;
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.children = [];
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);
if (!cst.is_simple_expression || cst.cst_type === CstType.STRUCTURED) {
return;
@ -165,6 +169,7 @@ export class RSFormLoader {
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 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_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 {
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 {
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 {
schema?: ILibraryItemData;

View File

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

View File

@ -4,7 +4,7 @@
import { Graph } from '@/models/Graph';
import { ILibraryItem, ILibraryItemVersioned, LibraryItemID } from './library';
import { ILibraryItem, ILibraryItemReference, ILibraryItemVersioned, LibraryItemID } from './library';
import { ICstSubstitute } from './oss';
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 {
parse: {
@ -111,12 +111,19 @@ export interface IConstituenta extends IConstituentaData {
status: ExpressionStatus;
is_template: boolean;
is_simple_expression: boolean;
is_inherited: boolean;
is_inherited_parent: boolean;
parent?: ConstituentaID;
parent_alias?: string;
children: number[];
children_alias: string[];
}
/**
* Represents {@link IConstituenta} reference.
*/
export interface IConstituentaReference extends Pick<IConstituentaMeta, 'id' | 'schema'> {}
/**
* Represents Constituenta list.
*/
@ -183,6 +190,7 @@ export interface IRSFormStats {
count_errors: number;
count_property: number;
count_incalculable: number;
count_inherited: number;
count_text_term: number;
count_definition: number;
@ -198,10 +206,19 @@ export interface IRSFormStats {
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.
*/
export interface IRSForm extends ILibraryItemVersioned {
export interface IRSForm extends IRSFormData {
items: IConstituenta[];
stats: IRSFormStats;
graph: Graph;
@ -209,13 +226,6 @@ export interface IRSForm extends ILibraryItemVersioned {
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}.
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import { HelpTopic } from '@/models/miscellaneous';
import {
IconClone,
IconDestroy,
@ -7,11 +5,13 @@ import {
IconEditor,
IconFollow,
IconImmutable,
IconOSS,
IconOwner,
IconPublic,
IconSave
} from '../../../components/Icons';
import LinkTopic from '../../../components/ui/LinkTopic';
} from '@/components/Icons';
import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpRSFormCard() {
return (
@ -30,6 +30,9 @@ function HelpRSFormCard() {
</p>
<h2>Управление</h2>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li>
<IconSave className='inline-icon' /> сохранить изменения: Ctrl + S
</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 {
IconAlias,
IconClone,
@ -10,20 +6,31 @@ import {
IconMoveUp,
IconNewItem,
IconOpenList,
IconOSS,
IconReset
} from '../../../components/Icons';
import LinkTopic from '../../../components/ui/LinkTopic';
} from '@/components/Icons';
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() {
return (
<div className='dense'>
<h1>Список конституент</h1>
<p>
<li>
<IconAlias className='inline-icon' />
Конституенты обладают уникальным <LinkTopic text='Именем' topic={HelpTopic.CC_CONSTITUENTA} />
</p>
</li>
<li>при наведении на имя отображаются атрибуты</li>
<li>
пунктиром отображаются <LinkTopic text='наследованные' topic={HelpTopic.CC_OSS} /> конституенты
</li>
<h2>Управление списком</h2>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li>
<IconReset className='inline-icon' /> сбросить выделение: ESC
</li>

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import { HelpTopic } from '@/models/miscellaneous';
import {
IconGenerateNames,
IconGenerateStructure,
@ -7,8 +5,9 @@ import {
IconReplace,
IconSortList,
IconTemplates
} from '../../../components/Icons';
import LinkTopic from '../../../components/ui/LinkTopic';
} from '@/components/Icons';
import LinkTopic from '@/components/ui/LinkTopic';
import { HelpTopic } from '@/models/miscellaneous';
function HelpRSLangOperations() {
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 {
IconClustering,
IconDestroy,
@ -17,10 +12,15 @@ import {
IconGraphOutputs,
IconImage,
IconNewItem,
IconOSS,
IconReset,
IconRotate3D,
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() {
const { colors } = useConceptOptions();
@ -71,6 +71,9 @@ function HelpTermGraph() {
<div className='flex flex-col-reverse mb-3 sm:flex-row'>
<div className='w-full sm:w-[14rem]'>
<h1>Общие</h1>
<li>
<IconOSS className='inline-icon' /> переход к связанной <LinkTopic text='ОСС' topic={HelpTopic.CC_OSS} />
</li>
<li>
<IconFilter className='inline-icon' /> Открыть настройки
</li>
@ -80,6 +83,9 @@ function HelpTermGraph() {
<li>
<IconImage className='inline-icon' /> Сохранить в формат PNG
</li>
<li>
* <LinkTopic text='наследованные' topic={HelpTopic.CC_OSS} /> в ОСС
</li>
</div>
<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} />
<Overlay position='top-[-0.2rem] right-[-0.2rem]' className='cc-icons'>
<Overlay position='top-[-0.2rem] right-[-0.2rem]'>
<MiniButton
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
noHover

View File

@ -101,11 +101,11 @@ function NodeContextMenu({
};
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}>
<DropdownButton
text='Редактировать'
titleHtml={prepareTooltip('Редактировать операцию', 'Ctrl + клик')}
titleHtml={prepareTooltip('Редактировать операцию', 'Двойной клик')}
icon={<IconEdit2 size='1rem' className='icon-primary' />}
disabled={controller.isProcessing}
onClick={handleEditOperation}

View File

@ -22,7 +22,7 @@ function OperationNode(node: OssNodeInternal) {
<>
<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
icon={<IconRSForm className={hasFile ? 'clr-text-green' : 'clr-text-red'} size='0.75rem' />}
noHover

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ function ToolbarItemAccess({ visible, toggleVisible, readOnly, toggleReadOnly, c
);
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' />
<div className='ml-auto cc-icons'>
<SelectAccessPolicy

View File

@ -5,15 +5,19 @@ import { useMemo } from 'react';
import { SubscribeIcon } from '@/components/DomainIcons';
import { IconDestroy, IconSave, IconShare } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton';
import Overlay from '@/components/ui/Overlay';
import { useAccessMode } from '@/context/AccessModeContext';
import { AccessPolicy, ILibraryItemEditor } from '@/models/library';
import { AccessPolicy, ILibraryItemEditor, LibraryItemType } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous';
import { IRSForm } from '@/models/rsform';
import { UserLevel } from '@/models/user';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip, tooltips } from '@/utils/labels';
import { IRSEditContext } from '../RSEditContext';
interface ToolbarRSFormCardProps {
modified: boolean;
subscribed: boolean;
@ -33,8 +37,26 @@ function ToolbarRSFormCard({
}: ToolbarRSFormCardProps) {
const { accessLevel } = useAccessMode();
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 (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'>
{ossSelector}
{controller.isMutable || modified ? (
<MiniButton
titleHtml={prepareTooltip('Сохранить изменения', 'Ctrl + S')}

View File

@ -7,17 +7,25 @@ import { PARAMETER } from '@/utils/constants';
import { useRSEdit } from '../RSEditContext';
function ToolbarVersioning() {
interface ToolbarVersioningProps {
blockReload?: boolean;
}
function ToolbarVersioning({ blockReload }: ToolbarVersioningProps) {
const controller = useRSEdit();
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 ? (
<>
<MiniButton
titleHtml={
!controller.isContentEditable ? 'Откатить к версии' : 'Переключитесь на <br/>неактуальную версию'
blockReload
? 'Невозможно откатить КС, прикрепленную к операционной схеме'
: !controller.isContentEditable
? 'Откатить к версии'
: 'Переключитесь на <br/>неактуальную версию'
}
disabled={controller.isContentEditable}
disabled={controller.isContentEditable || blockReload}
onClick={() => controller.restoreVersion()}
icon={<IconUpload size='1.25rem' className='icon-red' />}
/>
@ -30,7 +38,7 @@ function ToolbarVersioning() {
<MiniButton
title={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' />}
/>
</>

View File

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

View File

@ -8,6 +8,7 @@ import {
IconReset
} from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import Dropdown from '@/components/ui/Dropdown';
import DropdownButton from '@/components/ui/DropdownButton';
import MiniButton from '@/components/ui/MiniButton';
@ -27,6 +28,12 @@ function ToolbarRSList() {
return (
<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
titleHtml={prepareTooltip('Сбросить выделение', 'ESC')}
icon={<IconReset size='1.25rem' className='icon-primary' />}
@ -79,8 +86,8 @@ function ToolbarRSList() {
<MiniButton
titleHtml={prepareTooltip('Удалить выбранные', 'Delete')}
icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={controller.isProcessing || controller.nothingSelected}
onClick={controller.deleteCst}
disabled={controller.isProcessing || !controller.canDeleteSelected}
onClick={controller.promptDeleteCst}
/>
<BadgeHelp topic={HelpTopic.UI_RS_LIST} offset={5} />
</Overlay>

View File

@ -105,7 +105,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
result.push({
id: String(node.id),
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,
size: applyNodeSizing(cst, sizing)
});
@ -141,10 +141,10 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
}
function handleDeleteCst() {
if (!controller.schema || controller.nothingSelected) {
if (!controller.schema || !controller.canDeleteSelected) {
return;
}
controller.deleteCst();
controller.promptDeleteCst();
}
const handleChangeLayout = useCallback(

View File

@ -13,6 +13,7 @@ import {
IconTextOff
} from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import MiniSelectorOSS from '@/components/select/MiniSelectorOSS';
import MiniButton from '@/components/ui/MiniButton';
import { HelpTopic } from '@/models/miscellaneous';
import { PARAMETER } from '@/utils/constants';
@ -55,6 +56,12 @@ function ToolbarTermGraph({
return (
<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
title='Настройки фильтрации узлов и связей'
icon={<IconFilter size='1.25rem' className='icon-primary' />}
@ -105,7 +112,7 @@ function ToolbarTermGraph({
<MiniButton
title='Удалить выбранные'
icon={<IconDestroy size='1.25rem' className='icon-red' />}
disabled={controller.nothingSelected || controller.isProcessing}
disabled={!controller.canDeleteSelected || controller.isProcessing}
onClick={onDelete}
/>
) : 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'
style={{
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)}
onDoubleClick={() => onEdit(cstID)}
>
{cst.alias}
{cst.is_inherited ? '*' : ''}
</button>
<TooltipConstituenta data={cst} anchor={`#${id}`} />
</div>

View File

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

View File

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

View File

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

View File

@ -15,6 +15,8 @@ export const PARAMETER = {
ossImageWidth: 1280, // pixels - size of OSS image
ossImageHeight: 960, // pixels - size of OSS image
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
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 = {
page_size: 'page_size_',
oss_list: 'oss_list_',
cst_list: 'cst_list_',
cst_inline_synth_list: 'cst_inline_synth_list_',
cst_inline_synth_substitutes: 'cst_inline_synth_substitutes_',

View File

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