Compare commits

...

11 Commits

Author SHA1 Message Date
Ivan
d3e9e0eadd B: Fix react-flow attribution styling
Some checks are pending
Backend CI / build (3.12) (push) Waiting to run
Frontend CI / build (22.x) (push) Waiting to run
2024-09-11 21:25:48 +03:00
Ivan
296e740cc7 M: Improve layout for canvas - remove clipping top 2024-09-11 21:15:28 +03:00
Ivan
5d00a10a58 npm update 2024-09-11 20:08:59 +03:00
Ivan
fe678de8e4 R: Refactor Cst order attribute
0-indexed used for storage only
2024-09-11 20:06:01 +03:00
Ivan
6d84b89b14 Update README.md 2024-09-11 13:49:03 +03:00
Ivan
5ca7f4d057 M: Cleanup todos 2024-09-11 13:24:29 +03:00
Ivan
6e40aa06de M: Optimize dialogs height for small screen notebooks 2024-09-10 17:40:30 +03:00
Ivan
1b662ad39f B: Fix placeholder styling 2024-09-09 20:38:43 +03:00
Ivan
0302a99344 M: Fix text wrap for long filenames 2024-09-07 19:12:41 +03:00
Ivan
1078a271fb M: Small UI fixes 2024-09-07 16:26:16 +03:00
Ivan
7100738706 M: Implement projection swap on ctrl + space 2024-09-06 15:59:09 +03:00
56 changed files with 906 additions and 829 deletions

View File

@ -9,6 +9,7 @@
[![Backend CI](https://github.com/IRBorisov/ConceptPortal/actions/workflows/backend.yml/badge.svg?branch=main)](https://github.com/IRBorisov/ConceptPortal/actions/workflows/backend.yml) [![Backend CI](https://github.com/IRBorisov/ConceptPortal/actions/workflows/backend.yml/badge.svg?branch=main)](https://github.com/IRBorisov/ConceptPortal/actions/workflows/backend.yml)
[![Frontend CI](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml/badge.svg?branch=main)](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml) [![Frontend CI](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml/badge.svg?branch=main)](https://github.com/IRBorisov/ConceptPortal/actions/workflows/frontend.yml)
[![Uptime Robot status](https://img.shields.io/uptimerobot/status/m797659312-8ab26c72de49d8d92eccc06e?label=Live%20Server)](https://portal.acconcept.ru)
React + Django based web portal for editing RSForm schemas. React + Django based web portal for editing RSForm schemas.
This readme file is used mostly to document project dependencies and conventions. This readme file is used mostly to document project dependencies and conventions.

View File

@ -9,7 +9,9 @@ For more specific TODOs see comments in code
- Design first user experience - Design first user experience
- Demo sandbox for anonymous users - Demo sandbox for anonymous users
- User profile: Settings + settings persistency - User profile: Settings + settings server persistency
- Profile pictures
- Integrate socials and feedback
- Custom LibraryItem lists - Custom LibraryItem lists
- Custom user filters and sharing filters - Custom user filters and sharing filters
@ -19,6 +21,8 @@ For more specific TODOs see comments in code
- Focus on codemirror editor when label is clicked (need React 19 ref for clean code solution) - Focus on codemirror editor when label is clicked (need React 19 ref for clean code solution)
- Draggable rows in constituents table - Draggable rows in constituents table
- M-graph visualization for typification and RSForm in general
- replace reagraph with react-flow in TermGraph and FormulaGraph - replace reagraph with react-flow in TermGraph and FormulaGraph
- Search functionality for Help Manuals - Search functionality for Help Manuals
- Export PDF (Items list, Graph) - Export PDF (Items list, Graph)
@ -31,6 +35,8 @@ For more specific TODOs see comments in code
- Content based search in Library - Content based search in Library
- Home page (user specific) - Home page (user specific)
- Private projects. Consider cooperative editing - Private projects. Consider cooperative editing
- OSS: synthesis table: auto substitution for diamond synthesis
[Tech] [Tech]
- duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib - duplicate syntax parsing and type info calculations to client. Consider moving backend to Nodejs or embedding c++ lib

View File

@ -84,7 +84,7 @@ class TestVersionViews(EndpointTester):
alias='A1', alias='A1',
cst_type='axiom', cst_type='axiom',
definition_formal='X1=X1', definition_formal='X1=X1',
order=2 order=1
) )
version_id = self._create_version({'version': '1.0.0', 'description': 'test'}) version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
a1.definition_formal = 'X1=X2' a1.definition_formal = 'X1=X2'
@ -163,7 +163,7 @@ class TestVersionViews(EndpointTester):
x1.convention = 'Test2' x1.convention = 'Test2'
x1.term_raw = 'Test' x1.term_raw = 'Test'
x1.save() x1.save()
x3.order = 1 x3.order = 0
x3.save() x3.save()
self.executeNotFound(version=invalid_id) self.executeNotFound(version=invalid_id)
@ -172,10 +172,10 @@ class TestVersionViews(EndpointTester):
x1.refresh_from_db() x1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
self.assertEqual(len(response.data['items']), 3) self.assertEqual(len(response.data['items']), 3)
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
self.assertEqual(x1.convention, 'testStart') self.assertEqual(x1.convention, 'testStart')
self.assertEqual(x1.term_raw, '') self.assertEqual(x1.term_raw, '')
self.assertEqual(x2.order, 2) self.assertEqual(x2.order, 1)
self.assertEqual(response.data['items'][2]['alias'], 'D1') self.assertEqual(response.data['items'][2]['alias'], 'D1')
self.assertEqual(response.data['items'][2]['term_raw'], 'TestTerm') self.assertEqual(response.data['items'][2]['term_raw'], 'TestTerm')

View File

@ -28,8 +28,6 @@ from .. import serializers as s
class LibraryViewSet(viewsets.ModelViewSet): class LibraryViewSet(viewsets.ModelViewSet):
''' Endpoint: Library operations. ''' ''' Endpoint: Library operations. '''
queryset = m.LibraryItem.objects.all() queryset = m.LibraryItem.objects.all()
# TODO: consider using .only() for performance
ordering = '-time_update' ordering = '-time_update'
def get_serializer_class(self): def get_serializer_class(self):

View File

@ -244,7 +244,6 @@ class OperationSchema:
original = children[sub.original.pk] original = children[sub.original.pk]
replacement = children[sub.substitution.pk] replacement = children[sub.substitution.pk]
translated_substitutions.append((original, replacement)) translated_substitutions.append((original, replacement))
# TODO: add auto substitutes for diamond
receiver.substitute(translated_substitutions) receiver.substitute(translated_substitutions)
for cst in receiver.constituents().order_by('order'): for cst in receiver.constituents().order_by('order'):
@ -258,6 +257,7 @@ class OperationSchema:
receiver.restore_order() receiver.restore_order()
receiver.reset_aliases() receiver.reset_aliases()
receiver.resolve_all_text()
if len(self.cache.graph.outputs[operation.pk]) > 0: if len(self.cache.graph.outputs[operation.pk]) > 0:
self.after_create_cst(receiver, list(receiver.constituents().order_by('order'))) self.after_create_cst(receiver, list(receiver.constituents().order_by('order')))
@ -366,12 +366,10 @@ class OperationSchema:
if destination is None: if destination is None:
return return
# TODO: update substitutions for diamond synthesis (if needed)
self.cache.ensure_loaded() self.cache.ensure_loaded()
new_mapping = self._transform_mapping(mapping, operation, destination) new_mapping = self._transform_mapping(mapping, operation, destination)
alias_mapping = OperationSchema._produce_alias_mapping(new_mapping) alias_mapping = OperationSchema._produce_alias_mapping(new_mapping)
insert_where = self._determine_insert_position(items[0], operation, source, destination) insert_where = self._determine_insert_position(items[0].pk, operation, source, destination)
new_cst_list = destination.insert_copy(items, insert_where, alias_mapping) new_cst_list = destination.insert_copy(items, insert_where, alias_mapping)
for index, cst in enumerate(new_cst_list): for index, cst in enumerate(new_cst_list):
new_inheritance = Inheritance.objects.create( new_inheritance = Inheritance.objects.create(
@ -529,20 +527,23 @@ class OperationSchema:
return result return result
def _determine_insert_position( def _determine_insert_position(
self, prototype: Constituenta, self, prototype_id: int,
operation: Operation, operation: Operation,
source: RSForm, source: RSForm,
destination: RSForm destination: RSForm
) -> int: ) -> int:
''' Determine insert_after for new constituenta. ''' ''' Determine insert_after for new constituenta. '''
if prototype.order == 1: prototype = source.cache.by_id[prototype_id]
return 1 prototype_index = source.cache.constituents.index(prototype)
prev_cst = source.cache.constituents[prototype.order - 2] if prototype_index == 0:
return 0
prev_cst = source.cache.constituents[prototype_index - 1]
inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk) inherited_prev_id = self.cache.get_successor(prev_cst.pk, operation.pk)
if inherited_prev_id is None: if inherited_prev_id is None:
return INSERT_LAST return INSERT_LAST
prev_cst = destination.cache.by_id[inherited_prev_id] prev_cst = destination.cache.by_id[inherited_prev_id]
return cast(int, prev_cst.order) + 1 prev_index = destination.cache.constituents.index(prev_cst)
return prev_index + 1
def _extract_data_references(self, data: dict, old_data: dict) -> set[str]: def _extract_data_references(self, data: dict, old_data: dict) -> set[str]:
result: set[str] = set() result: set[str] = set()

View File

@ -70,7 +70,7 @@ class TestChangeConstituents(EndpointTester):
self.assertEqual(self.ks1.constituents().count(), 3) self.assertEqual(self.ks1.constituents().count(), 3)
self.assertEqual(self.ks3.constituents().count(), 5) self.assertEqual(self.ks3.constituents().count(), 5)
self.assertEqual(inherited_cst.alias, 'X4') self.assertEqual(inherited_cst.alias, 'X4')
self.assertEqual(inherited_cst.order, 3) self.assertEqual(inherited_cst.order, 2)
self.assertEqual(inherited_cst.definition_formal, 'X1 = X2') self.assertEqual(inherited_cst.definition_formal, 'X1 = X2')
@decl_endpoint('/api/rsforms/{schema}/rename-cst', method='patch') @decl_endpoint('/api/rsforms/{schema}/rename-cst', method='patch')
@ -133,5 +133,5 @@ class TestChangeConstituents(EndpointTester):
d2.refresh_from_db() d2.refresh_from_db()
self.assertEqual(self.ks1.constituents().count(), 1) self.assertEqual(self.ks1.constituents().count(), 1)
self.assertEqual(self.ks3.constituents().count(), 4) self.assertEqual(self.ks3.constituents().count(), 4)
self.assertEqual(self.ks1X2.order, 1) self.assertEqual(self.ks1X2.order, 0)
self.assertEqual(d2.definition_formal, r'X2\X2\X3') self.assertEqual(d2.definition_formal, r'X2\X2\X3')

View File

@ -367,7 +367,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
@action(detail=False, methods=['post'], url_path='get-predecessor') @action(detail=False, methods=['post'], url_path='get-predecessor')
def get_predecessor(self, request: Request) -> HttpResponse: def get_predecessor(self, request: Request) -> HttpResponse:
''' Get predecessor. ''' ''' Get predecessor. '''
# TODO: add tests for this method
serializer = CstTargetSerializer(data=request.data) serializer = CstTargetSerializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
cst = cast(Constituenta, serializer.validated_data['target']) cst = cast(Constituenta, serializer.validated_data['target'])

View File

@ -7,7 +7,8 @@ from . import models
class ConstituentaAdmin(admin.ModelAdmin): class ConstituentaAdmin(admin.ModelAdmin):
''' Admin model: Constituenta. ''' ''' Admin model: Constituenta. '''
ordering = ['schema', 'order'] ordering = ['schema', 'order']
list_display = ['schema', 'alias', 'term_resolved', 'definition_resolved'] list_display = ['schema', 'order', 'alias', 'term_resolved', 'definition_resolved']
search_fields = ['term_resolved', 'definition_resolved'] search_fields = ['term_resolved', 'definition_resolved']
admin.site.register(models.Constituenta, ConstituentaAdmin) admin.site.register(models.Constituenta, ConstituentaAdmin)

View File

@ -0,0 +1,26 @@
# Generated by Django 5.1 on 2024-09-11 16:37
from django.db import migrations, models
def migrate_order_from_1_to_0(apps, schema_editor):
Constituenta = apps.get_model('rsform', 'Constituenta')
for cst in Constituenta.objects.all():
cst.order = max(0, cst.order - 1)
cst.save()
class Migration(migrations.Migration):
dependencies = [
('rsform', '0002_alter_constituenta_order'),
]
operations = [
migrations.AlterField(
model_name='constituenta',
name='order',
field=models.PositiveIntegerField(default=0, verbose_name='Позиция'),
),
migrations.RunPython(migrate_order_from_1_to_0),
]

View File

@ -2,7 +2,6 @@
import re import re
from cctext import extract_entities from cctext import extract_entities
from django.core.validators import MinValueValidator
from django.db.models import ( from django.db.models import (
CASCADE, CASCADE,
CharField, CharField,
@ -57,8 +56,7 @@ class Constituenta(Model):
) )
order: PositiveIntegerField = PositiveIntegerField( order: PositiveIntegerField = PositiveIntegerField(
verbose_name='Позиция', verbose_name='Позиция',
validators=[MinValueValidator(1)], default=0,
default=1,
) )
alias: CharField = CharField( alias: CharField = CharField(
verbose_name='Имя', verbose_name='Имя',

View File

@ -140,7 +140,8 @@ class RSForm:
if insert_after is None: if insert_after is None:
position = INSERT_LAST position = INSERT_LAST
else: else:
position = insert_after.order + 1 self.cache.ensure_loaded()
position = self.cache.constituents.index(self.cache.by_id[insert_after.pk]) + 1
result = self.insert_new(data['alias'], data['cst_type'], position) result = self.insert_new(data['alias'], data['cst_type'], position)
result.convention = data.get('convention', '') result.convention = data.get('convention', '')
result.definition_formal = data.get('definition_formal', '') result.definition_formal = data.get('definition_formal', '')
@ -170,8 +171,7 @@ class RSForm:
position: int = INSERT_LAST, position: int = INSERT_LAST,
**kwargs **kwargs
) -> Constituenta: ) -> Constituenta:
''' Insert new constituenta at given position. ''' Insert new constituenta at given position. '''
All following constituents order is shifted by 1 position. '''
if self.constituents().filter(alias=alias).exists(): if self.constituents().filter(alias=alias).exists():
raise ValidationError(msg.aliasTaken(alias)) raise ValidationError(msg.aliasTaken(alias))
position = self._get_insert_position(position) position = self._get_insert_position(position)
@ -298,8 +298,8 @@ class RSForm:
if cst in target: if cst in target:
cst.order = destination + count_moved cst.order = destination + count_moved
count_moved += 1 count_moved += 1
elif count_top + 1 < destination: elif count_top < destination:
cst.order = count_top + 1 cst.order = count_top
count_top += 1 count_top += 1
else: else:
cst.order = destination + size + count_bot cst.order = destination + size + count_bot
@ -417,8 +417,8 @@ class RSForm:
if count_new == 0: if count_new == 0:
return [] return []
position = target.order + 1
self.cache.ensure_loaded() self.cache.ensure_loaded()
position = self.cache.constituents.index(self.cache.by_id[target.id]) + 1
self._shift_positions(position, count_new) self._shift_positions(position, count_new)
result = [] result = []
cst_type = CstType.TERM if len(parse['args']) == 0 else CstType.FUNCTION cst_type = CstType.TERM if len(parse['args']) == 0 else CstType.FUNCTION
@ -456,29 +456,23 @@ class RSForm:
def _shift_positions(self, start: int, shift: int) -> None: def _shift_positions(self, start: int, shift: int) -> None:
if shift == 0: if shift == 0:
return return
update_list: Iterable[Constituenta] = [] self.cache.ensure_loaded()
if not self.cache.is_loaded: update_list = self.cache.constituents[start:]
update_list = Constituenta.objects \
.only('order') \
.filter(schema=self.model, order__gte=start)
else:
update_list = [cst for cst in self.cache.constituents if cst.order >= start]
for cst in update_list: for cst in update_list:
cst.order += shift cst.order += shift
Constituenta.objects.bulk_update(update_list, ['order']) Constituenta.objects.bulk_update(update_list, ['order'])
def _get_insert_position(self, position: int) -> int: def _get_insert_position(self, position: int) -> int:
if position <= 0 and position != INSERT_LAST: if position < 0 and position != INSERT_LAST:
raise ValidationError(msg.invalidPosition()) raise ValidationError(msg.invalidPosition())
lastPosition = self.constituents().count() lastPosition = self.constituents().count()
if position == INSERT_LAST: if position == INSERT_LAST:
position = lastPosition + 1 return lastPosition
else: else:
position = max(1, min(position, lastPosition + 1)) return max(0, min(position, lastPosition))
return position
def _reset_order(self) -> None: def _reset_order(self) -> None:
order = 1 order = 0
changed: list[Constituenta] = [] changed: list[Constituenta] = []
cst_list: Iterable[Constituenta] = [] cst_list: Iterable[Constituenta] = []
if not self.cache.is_loaded: if not self.cache.is_loaded:
@ -569,14 +563,14 @@ class RSFormCache:
def insert(self, cst: Constituenta) -> None: def insert(self, cst: Constituenta) -> None:
if self.is_loaded: if self.is_loaded:
self.constituents.insert(cst.order - 1, cst) self.constituents.insert(cst.order, cst)
self.by_id[cst.pk] = cst self.by_id[cst.pk] = cst
self.by_alias[cst.alias] = cst self.by_alias[cst.alias] = cst
def insert_multi(self, items: Iterable[Constituenta]) -> None: def insert_multi(self, items: Iterable[Constituenta]) -> None:
if self.is_loaded: if self.is_loaded:
for cst in items: for cst in items:
self.constituents.insert(cst.order - 1, cst) self.constituents.insert(cst.order, cst)
self.by_id[cst.pk] = cst self.by_id[cst.pk] = cst
self.by_alias[cst.alias] = cst self.by_alias[cst.alias] = cst
@ -770,7 +764,7 @@ class _OrderManager:
self._items = result self._items = result
def _save_order(self) -> None: def _save_order(self) -> None:
order = 1 order = 0
for cst in self._items: for cst in self._items:
cst.order = order cst.order = order
order += 1 order += 1

View File

@ -26,7 +26,7 @@ class CstBaseSerializer(serializers.ModelSerializer):
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = Constituenta model = Constituenta
fields = '__all__' exclude = ('order',)
read_only_fields = ('id',) read_only_fields = ('id',)
@ -35,8 +35,8 @@ class CstSerializer(serializers.ModelSerializer):
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = Constituenta model = Constituenta
fields = '__all__' exclude = ('order',)
read_only_fields = ('id', 'schema', 'order', 'alias', 'cst_type', 'definition_resolved', 'term_resolved') read_only_fields = ('id', 'schema', 'alias', 'cst_type', 'definition_resolved', 'term_resolved')
class CstUpdateSerializer(serializers.Serializer): class CstUpdateSerializer(serializers.Serializer):
@ -71,7 +71,7 @@ class CstDetailsSerializer(serializers.ModelSerializer):
class Meta: class Meta:
''' serializer metadata. ''' ''' serializer metadata. '''
model = Constituenta model = Constituenta
fields = '__all__' exclude = ('order',)
class CstCreateSerializer(serializers.ModelSerializer): class CstCreateSerializer(serializers.ModelSerializer):
@ -126,7 +126,7 @@ class RSFormSerializer(serializers.ModelSerializer):
result['items'] = [] result['items'] = []
result['oss'] = [] result['oss'] = []
result['inheritance'] = [] result['inheritance'] = []
for cst in RSForm(instance).constituents().order_by('order'): for cst in RSForm(instance).constituents().defer('order').order_by('order'):
result['items'].append(CstSerializer(cst).data) result['items'].append(CstSerializer(cst).data)
for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'): for oss in LibraryItem.objects.filter(operations__result=instance).only('alias'):
result['oss'].append({ result['oss'].append({
@ -171,6 +171,7 @@ class RSFormSerializer(serializers.ModelSerializer):
cst_data = next(x for x in items if x['id'] == cst.pk) cst_data = next(x for x in items if x['id'] == cst.pk)
new_cst = CstBaseSerializer(data=cst_data) new_cst = CstBaseSerializer(data=cst_data)
new_cst.is_valid(raise_exception=True) new_cst.is_valid(raise_exception=True)
new_cst.validated_data['order'] = ids.index(cst.pk)
new_cst.update( new_cst.update(
instance=cst, instance=cst,
validated_data=new_cst.validated_data validated_data=new_cst.validated_data
@ -180,9 +181,11 @@ class RSFormSerializer(serializers.ModelSerializer):
for cst_data in items: for cst_data in items:
if cst_data['id'] not in processed: if cst_data['id'] not in processed:
cst = schema.insert_new(cst_data['alias']) cst = schema.insert_new(cst_data['alias'])
old_id = cst_data['id']
cst_data['id'] = cst.pk cst_data['id'] = cst.pk
new_cst = CstBaseSerializer(data=cst_data) new_cst = CstBaseSerializer(data=cst_data)
new_cst.is_valid(raise_exception=True) new_cst.is_valid(raise_exception=True)
new_cst.validated_data['order'] = ids.index(old_id)
new_cst.update( new_cst.update(
instance=cst, instance=cst,
validated_data=new_cst.validated_data validated_data=new_cst.validated_data

View File

@ -151,7 +151,7 @@ class RSFormTRSSerializer(serializers.Serializer):
location=validated_data['location'] location=validated_data['location']
) )
self.instance.save() self.instance.save()
order = 1 order = 0
for cst_data in validated_data['items']: for cst_data in validated_data['items']:
cst = Constituenta( cst = Constituenta(
alias=cst_data['alias'], alias=cst_data['alias'],
@ -174,7 +174,7 @@ class RSFormTRSSerializer(serializers.Serializer):
if 'comment' in validated_data: if 'comment' in validated_data:
instance.model.comment = validated_data['comment'] instance.model.comment = validated_data['comment']
order = 1 order = 0
prev_constituents = instance.constituents() prev_constituents = instance.constituents()
loaded_ids = set() loaded_ids = set()
for cst_data in validated_data['items']: for cst_data in validated_data['items']:

View File

@ -16,7 +16,7 @@ class TestConstituenta(TestCase):
def test_str(self): def test_str(self):
testStr = 'X1' testStr = 'X1'
cst = Constituenta.objects.create(alias=testStr, schema=self.schema1.model, order=1, convention='Test') cst = Constituenta.objects.create(alias=testStr, schema=self.schema1.model, order=0, convention='Test')
self.assertEqual(str(cst), testStr) self.assertEqual(str(cst), testStr)
@ -25,25 +25,18 @@ class TestConstituenta(TestCase):
Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=-1) Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=-1)
def test_order_min_value(self):
with self.assertRaises(ValidationError):
cst = Constituenta.objects.create(alias='X1', schema=self.schema1.model, order=0)
cst.full_clean()
def test_schema_not_null(self): def test_schema_not_null(self):
with self.assertRaises(IntegrityError): with self.assertRaises(IntegrityError):
Constituenta.objects.create(alias='X1', order=1) Constituenta.objects.create(alias='X1', order=0)
def test_create_default(self): def test_create_default(self):
cst = Constituenta.objects.create( cst = Constituenta.objects.create(
alias='X1', alias='X1',
schema=self.schema1.model, schema=self.schema1.model
order=1
) )
self.assertEqual(cst.schema, self.schema1.model) self.assertEqual(cst.schema, self.schema1.model)
self.assertEqual(cst.order, 1) self.assertEqual(cst.order, 0)
self.assertEqual(cst.alias, 'X1') self.assertEqual(cst.alias, 'X1')
self.assertEqual(cst.cst_type, CstType.BASE) self.assertEqual(cst.cst_type, CstType.BASE)
self.assertEqual(cst.convention, '') self.assertEqual(cst.convention, '')
@ -57,7 +50,6 @@ class TestConstituenta(TestCase):
def test_extract_references(self): def test_extract_references(self):
cst = Constituenta.objects.create( cst = Constituenta.objects.create(
alias='X1', alias='X1',
order=1,
schema=self.schema1.model, schema=self.schema1.model,
definition_formal='X1 X2', definition_formal='X1 X2',
term_raw='@{X3|sing} is a @{X4|sing}', term_raw='@{X3|sing} is a @{X4|sing}',
@ -68,7 +60,6 @@ class TestConstituenta(TestCase):
def text_apply_mapping(self): def text_apply_mapping(self):
cst = Constituenta.objects.create( cst = Constituenta.objects.create(
alias='X1', alias='X1',
order=1,
schema=self.schema1.model, schema=self.schema1.model,
definition_formal='X1 = X2', definition_formal='X1 = X2',
term_raw='@{X1|sing}', term_raw='@{X1|sing}',

View File

@ -23,8 +23,8 @@ class TestRSForm(DBTester):
self.assertFalse(schema1.constituents().exists()) self.assertFalse(schema1.constituents().exists())
self.assertFalse(schema2.constituents().exists()) self.assertFalse(schema2.constituents().exists())
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1) Constituenta.objects.create(alias='X1', schema=schema1.model, order=0)
Constituenta.objects.create(alias='X2', schema=schema1.model, order=2) Constituenta.objects.create(alias='X2', schema=schema1.model, order=1)
self.assertTrue(schema1.constituents().exists()) self.assertTrue(schema1.constituents().exists())
self.assertFalse(schema2.constituents().exists()) self.assertFalse(schema2.constituents().exists())
self.assertEqual(schema1.constituents().count(), 2) self.assertEqual(schema1.constituents().count(), 2)
@ -32,8 +32,8 @@ class TestRSForm(DBTester):
def test_get_max_index(self): def test_get_max_index(self):
schema1 = RSForm.create(title='Test1') schema1 = RSForm.create(title='Test1')
Constituenta.objects.create(alias='X1', schema=schema1.model, order=1) Constituenta.objects.create(alias='X1', schema=schema1.model, order=0)
Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1.model, order=2) Constituenta.objects.create(alias='D2', cst_type=CstType.TERM, schema=schema1.model, order=1)
self.assertEqual(schema1.get_max_index(CstType.BASE), 1) self.assertEqual(schema1.get_max_index(CstType.BASE), 1)
self.assertEqual(schema1.get_max_index(CstType.TERM), 2) self.assertEqual(schema1.get_max_index(CstType.TERM), 2)
self.assertEqual(schema1.get_max_index(CstType.AXIOM), 0) self.assertEqual(schema1.get_max_index(CstType.AXIOM), 0)
@ -42,37 +42,37 @@ class TestRSForm(DBTester):
def test_insert_at(self): def test_insert_at(self):
schema = RSForm.create(title='Test') schema = RSForm.create(title='Test')
x1 = schema.insert_new('X1') x1 = schema.insert_new('X1')
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
self.assertEqual(x1.schema, schema.model) self.assertEqual(x1.schema, schema.model)
x2 = schema.insert_new('X2', position=1) x2 = schema.insert_new('X2', position=0)
x1.refresh_from_db() x1.refresh_from_db()
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
self.assertEqual(x2.schema, schema.model) self.assertEqual(x2.schema, schema.model)
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 1)
x3 = schema.insert_new('X3', position=4) x3 = schema.insert_new('X3', position=3)
x2.refresh_from_db() x2.refresh_from_db()
x1.refresh_from_db() x1.refresh_from_db()
self.assertEqual(x3.order, 3) self.assertEqual(x3.order, 2)
self.assertEqual(x3.schema, schema.model) self.assertEqual(x3.schema, schema.model)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 1)
x4 = schema.insert_new('X4', position=3) x4 = schema.insert_new('X4', position=2)
x3.refresh_from_db() x3.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
x1.refresh_from_db() x1.refresh_from_db()
self.assertEqual(x4.order, 3) self.assertEqual(x4.order, 2)
self.assertEqual(x4.schema, schema.model) self.assertEqual(x4.schema, schema.model)
self.assertEqual(x3.order, 4) self.assertEqual(x3.order, 3)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 1)
def test_insert_at_invalid_position(self): def test_insert_at_invalid_position(self):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
self.schema.insert_new('X5', position=0) self.schema.insert_new('X5', position=-2)
def test_insert_at_invalid_alias(self): def test_insert_at_invalid_alias(self):
@ -84,24 +84,24 @@ class TestRSForm(DBTester):
def test_insert_at_reorder(self): def test_insert_at_reorder(self):
self.schema.insert_new('X1') self.schema.insert_new('X1')
d1 = self.schema.insert_new('D1') d1 = self.schema.insert_new('D1')
d2 = self.schema.insert_new('D2', position=1) d2 = self.schema.insert_new('D2', position=0)
d1.refresh_from_db() d1.refresh_from_db()
self.assertEqual(d1.order, 3) self.assertEqual(d1.order, 2)
self.assertEqual(d2.order, 1) self.assertEqual(d2.order, 0)
x2 = self.schema.insert_new('X2', position=4) x2 = self.schema.insert_new('X2', position=3)
self.assertEqual(x2.order, 4) self.assertEqual(x2.order, 3)
def test_insert_last(self): def test_insert_last(self):
x1 = self.schema.insert_new('X1') x1 = self.schema.insert_new('X1')
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
self.assertEqual(x1.schema, self.schema.model) self.assertEqual(x1.schema, self.schema.model)
x2 = self.schema.insert_new('X2') x2 = self.schema.insert_new('X2')
self.assertEqual(x2.order, 2) self.assertEqual(x2.order, 1)
self.assertEqual(x2.schema, self.schema.model) self.assertEqual(x2.schema, self.schema.model)
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
def test_create_cst(self): def test_create_cst(self):
data = { data = {
@ -120,8 +120,8 @@ class TestRSForm(DBTester):
self.assertEqual(x3.alias, data['alias']) self.assertEqual(x3.alias, data['alias'])
self.assertEqual(x3.term_raw, data['term_raw']) self.assertEqual(x3.term_raw, data['term_raw'])
self.assertEqual(x3.definition_raw, data['definition_raw']) self.assertEqual(x3.definition_raw, data['definition_raw'])
self.assertEqual(x2.order, 3) self.assertEqual(x2.order, 2)
self.assertEqual(x3.order, 2) self.assertEqual(x3.order, 1)
def test_create_cst_resolve(self): def test_create_cst_resolve(self):
@ -154,20 +154,20 @@ class TestRSForm(DBTester):
definition_raw='@{X10|plur}' definition_raw='@{X10|plur}'
) )
result = self.schema.insert_copy([s1, x1], 2) result = self.schema.insert_copy([s1, x1], 1)
self.assertEqual(len(result), 2) self.assertEqual(len(result), 2)
s1.refresh_from_db() s1.refresh_from_db()
self.assertEqual(s1.order, 4) self.assertEqual(s1.order, 3)
x2 = result[1] x2 = result[1]
self.assertEqual(x2.order, 3) self.assertEqual(x2.order, 2)
self.assertEqual(x2.alias, 'X11') self.assertEqual(x2.alias, 'X11')
self.assertEqual(x2.cst_type, CstType.BASE) self.assertEqual(x2.cst_type, CstType.BASE)
self.assertEqual(x2.convention, x1.convention) self.assertEqual(x2.convention, x1.convention)
s2 = result[0] s2 = result[0]
self.assertEqual(s2.order, 2) self.assertEqual(s2.order, 1)
self.assertEqual(s2.alias, 'S12') self.assertEqual(s2.alias, 'S12')
self.assertEqual(s2.cst_type, CstType.STRUCTURED) self.assertEqual(s2.cst_type, CstType.STRUCTURED)
self.assertEqual(s2.definition_formal, x2.alias) self.assertEqual(s2.definition_formal, x2.alias)
@ -188,8 +188,8 @@ class TestRSForm(DBTester):
x2.refresh_from_db() x2.refresh_from_db()
d1.refresh_from_db() d1.refresh_from_db()
self.assertEqual(self.schema.constituents().count(), 2) self.assertEqual(self.schema.constituents().count(), 2)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
self.assertEqual(d1.order, 2) self.assertEqual(d1.order, 1)
self.assertEqual(d1.definition_formal, 'DEL = X2') self.assertEqual(d1.definition_formal, 'DEL = X2')
self.assertEqual(d1.definition_raw, '@{DEL|sing}') self.assertEqual(d1.definition_raw, '@{DEL|sing}')
self.assertEqual(d1.term_raw, '@{X2|plur}') self.assertEqual(d1.term_raw, '@{X2|plur}')
@ -240,25 +240,25 @@ class TestRSForm(DBTester):
x2 = self.schema.insert_new('X2') x2 = self.schema.insert_new('X2')
d1 = self.schema.insert_new('D1') d1 = self.schema.insert_new('D1')
d2 = self.schema.insert_new('D2') d2 = self.schema.insert_new('D2')
self.schema.move_cst([x2, d2], 1) self.schema.move_cst([x2, d2], 0)
x1.refresh_from_db() x1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
d1.refresh_from_db() d1.refresh_from_db()
d2.refresh_from_db() d2.refresh_from_db()
self.assertEqual(x1.order, 3) self.assertEqual(x1.order, 2)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
self.assertEqual(d1.order, 4) self.assertEqual(d1.order, 3)
self.assertEqual(d2.order, 2) self.assertEqual(d2.order, 1)
def test_move_cst_down(self): def test_move_cst_down(self):
x1 = self.schema.insert_new('X1') x1 = self.schema.insert_new('X1')
x2 = self.schema.insert_new('X2') x2 = self.schema.insert_new('X2')
self.schema.move_cst([x1], 2) self.schema.move_cst([x1], 1)
x1.refresh_from_db() x1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 1)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
def test_restore_order(self): def test_restore_order(self):
@ -316,18 +316,18 @@ class TestRSForm(DBTester):
f2.refresh_from_db() f2.refresh_from_db()
a1.refresh_from_db() a1.refresh_from_db()
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
self.assertEqual(x2.order, 2) self.assertEqual(x2.order, 1)
self.assertEqual(c1.order, 3) self.assertEqual(c1.order, 2)
self.assertEqual(s1.order, 4) self.assertEqual(s1.order, 3)
self.assertEqual(d1.order, 5) self.assertEqual(d1.order, 4)
self.assertEqual(s2.order, 6) self.assertEqual(s2.order, 5)
self.assertEqual(d3.order, 7) self.assertEqual(d3.order, 6)
self.assertEqual(a1.order, 8) self.assertEqual(a1.order, 7)
self.assertEqual(d4.order, 9) self.assertEqual(d4.order, 8)
self.assertEqual(d2.order, 10) self.assertEqual(d2.order, 9)
self.assertEqual(f1.order, 11) self.assertEqual(f1.order, 10)
self.assertEqual(f2.order, 12) self.assertEqual(f2.order, 11)
def test_reset_aliases(self): def test_reset_aliases(self):

View File

@ -198,7 +198,7 @@ class TestRSFormViewset(EndpointTester):
response = self.executeCreated(data=data, item=self.owned_id) response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], 'X3') self.assertEqual(response.data['new_cst']['alias'], 'X3')
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias']) x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x3.order, 3) self.assertEqual(x3.order, 2)
data = { data = {
'alias': 'X4', 'alias': 'X4',
@ -210,7 +210,7 @@ class TestRSFormViewset(EndpointTester):
response = self.executeCreated(data=data, item=self.owned_id) response = self.executeCreated(data=data, item=self.owned_id)
self.assertEqual(response.data['new_cst']['alias'], data['alias']) self.assertEqual(response.data['new_cst']['alias'], data['alias'])
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias']) x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
self.assertEqual(x4.order, 3) self.assertEqual(x4.order, 2)
self.assertEqual(x4.term_raw, data['term_raw']) self.assertEqual(x4.term_raw, data['term_raw'])
self.assertEqual(x4.term_forms, data['term_forms']) self.assertEqual(x4.term_forms, data['term_forms'])
@ -257,7 +257,7 @@ class TestRSFormViewset(EndpointTester):
term_raw='@{X1|plur}', term_raw='@{X1|plur}',
definition_formal='X1' definition_formal='X1'
) )
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
self.assertEqual(x1.alias, 'X1') self.assertEqual(x1.alias, 'X1')
self.assertEqual(x1.cst_type, CstType.BASE) self.assertEqual(x1.cst_type, CstType.BASE)
@ -269,7 +269,7 @@ class TestRSFormViewset(EndpointTester):
x1.refresh_from_db() x1.refresh_from_db()
self.assertEqual(d1.term_resolved, '') self.assertEqual(d1.term_resolved, '')
self.assertEqual(d1.term_raw, '@{D2|plur}') self.assertEqual(d1.term_raw, '@{D2|plur}')
self.assertEqual(x1.order, 1) self.assertEqual(x1.order, 0)
self.assertEqual(x1.alias, 'D2') self.assertEqual(x1.alias, 'D2')
self.assertEqual(x1.cst_type, CstType.TERM) self.assertEqual(x1.cst_type, CstType.TERM)
@ -354,7 +354,7 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(len(response.data['items']), 1) self.assertEqual(len(response.data['items']), 1)
self.assertEqual(self.owned.constituents().count(), 1) self.assertEqual(self.owned.constituents().count(), 1)
self.assertEqual(x2.alias, 'X2') self.assertEqual(x2.alias, 'X2')
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
x3 = self.unowned.insert_new('X1') x3 = self.unowned.insert_new('X1')
data = {'items': [x3.pk]} data = {'items': [x3.pk]}
@ -365,22 +365,22 @@ class TestRSFormViewset(EndpointTester):
def test_move_constituenta(self): def test_move_constituenta(self):
self.set_params(item=self.owned_id) self.set_params(item=self.owned_id)
data = {'items': [1337], 'move_to': 1} data = {'items': [1337], 'move_to': 0}
self.executeBadData(data=data) self.executeBadData(data=data)
x1 = self.owned.insert_new('X1') x1 = self.owned.insert_new('X1')
x2 = self.owned.insert_new('X2') x2 = self.owned.insert_new('X2')
data = {'items': [x2.pk], 'move_to': 1} data = {'items': [x2.pk], 'move_to': 0}
response = self.executeOK(data=data) response = self.executeOK(data=data)
x1.refresh_from_db() x1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
self.assertEqual(response.data['id'], self.owned_id) self.assertEqual(response.data['id'], self.owned_id)
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 1)
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
x3 = self.unowned.insert_new('X1') x3 = self.unowned.insert_new('X1')
data = {'items': [x3.pk], 'move_to': 1} data = {'items': [x3.pk], 'move_to': 0}
self.executeBadData(data=data) self.executeBadData(data=data)
@ -399,11 +399,11 @@ class TestRSFormViewset(EndpointTester):
x1.refresh_from_db() x1.refresh_from_db()
x2.refresh_from_db() x2.refresh_from_db()
d11.refresh_from_db() d11.refresh_from_db()
self.assertEqual(x2.order, 1) self.assertEqual(x2.order, 0)
self.assertEqual(x2.alias, 'X1') self.assertEqual(x2.alias, 'X1')
self.assertEqual(x1.order, 2) self.assertEqual(x1.order, 1)
self.assertEqual(x1.alias, 'X2') self.assertEqual(x1.alias, 'X2')
self.assertEqual(d11.order, 3) self.assertEqual(d11.order, 2)
self.assertEqual(d11.alias, 'D1') self.assertEqual(d11.alias, 'D1')
self.executeOK() self.executeOK()
@ -462,9 +462,7 @@ class TestRSFormViewset(EndpointTester):
result = response.data['schema'] result = response.data['schema']
items = [item for item in result['items'] if item['id'] in response.data['cst_list']] items = [item for item in result['items'] if item['id'] in response.data['cst_list']]
self.assertEqual(len(items), 2) self.assertEqual(len(items), 2)
self.assertEqual(items[0]['order'], s1.order + 1)
self.assertEqual(items[0]['definition_formal'], 'Pr1(S1)') self.assertEqual(items[0]['definition_formal'], 'Pr1(S1)')
self.assertEqual(items[1]['order'], s1.order + 2)
self.assertEqual(items[1]['definition_formal'], 'Pr2(S1)') self.assertEqual(items[1]['definition_formal'], 'Pr2(S1)')
# Testing complex structure # Testing complex structure
@ -473,7 +471,6 @@ class TestRSFormViewset(EndpointTester):
result = response.data['schema'] result = response.data['schema']
items = [item for item in result['items'] if item['id'] in response.data['cst_list']] items = [item for item in result['items'] if item['id'] in response.data['cst_list']]
self.assertEqual(len(items), 8) self.assertEqual(len(items), 8)
self.assertEqual(items[0]['order'], s3.order + 1)
self.assertEqual(items[0]['definition_formal'], 'pr1(S3)') self.assertEqual(items[0]['definition_formal'], 'pr1(S3)')
# Testing function # Testing function
@ -482,7 +479,6 @@ class TestRSFormViewset(EndpointTester):
result = response.data['schema'] result = response.data['schema']
items = [item for item in result['items'] if item['id'] in response.data['cst_list']] items = [item for item in result['items'] if item['id'] in response.data['cst_list']]
self.assertEqual(len(items), 2) self.assertEqual(len(items), 2)
self.assertEqual(items[0]['order'], f1.order + 1)
self.assertEqual(items[0]['definition_formal'], '[α∈X1, β∈X1] Pr1(F10[α,β])') self.assertEqual(items[0]['definition_formal'], '[α∈X1, β∈X1] Pr1(F10[α,β])')
@ -497,7 +493,7 @@ class TestConstituentaAPI(EndpointTester):
alias='X1', alias='X1',
cst_type=CstType.BASE, cst_type=CstType.BASE,
schema=self.rsform_owned.model, schema=self.rsform_owned.model,
order=1, order=0,
convention='Test', convention='Test',
term_raw='Test1', term_raw='Test1',
term_resolved='Test1R', term_resolved='Test1R',
@ -506,7 +502,7 @@ class TestConstituentaAPI(EndpointTester):
alias='X2', alias='X2',
cst_type=CstType.BASE, cst_type=CstType.BASE,
schema=self.rsform_unowned.model, schema=self.rsform_unowned.model,
order=1, order=0,
convention='Test1', convention='Test1',
term_raw='Test2', term_raw='Test2',
term_resolved='Test2R' term_resolved='Test2R'
@ -514,7 +510,7 @@ class TestConstituentaAPI(EndpointTester):
self.cst3 = Constituenta.objects.create( self.cst3 = Constituenta.objects.create(
alias='X3', alias='X3',
schema=self.rsform_owned.model, schema=self.rsform_owned.model,
order=2, order=1,
term_raw='Test3', term_raw='Test3',
term_resolved='Test3', term_resolved='Test3',
definition_raw='Test1', definition_raw='Test1',
@ -594,14 +590,12 @@ class TestConstituentaAPI(EndpointTester):
data = { data = {
'target': self.cst1.pk, 'target': self.cst1.pk,
'item_data': { 'item_data': {
'alias': 'X33', 'alias': 'X33'
'order': 10
} }
} }
response = self.executeOK(data=data, schema=self.rsform_owned.model.pk) response = self.executeOK(data=data, schema=self.rsform_owned.model.pk)
self.assertEqual(response.data['alias'], 'X1') self.assertEqual(response.data['alias'], 'X1')
self.assertEqual(response.data['alias'], self.cst1.alias) self.assertEqual(response.data['alias'], self.cst1.alias)
self.assertEqual(response.data['order'], self.cst1.order)
class TestInlineSynthesis(EndpointTester): class TestInlineSynthesis(EndpointTester):
@ -669,8 +663,6 @@ class TestInlineSynthesis(EndpointTester):
response = self.executeOK(data=data) response = self.executeOK(data=data)
result = {item['alias']: item for item in response.data['items']} result = {item['alias']: item for item in response.data['items']}
self.assertEqual(len(result), 6) self.assertEqual(len(result), 6)
self.assertEqual(result['X2']['order'], 1)
self.assertEqual(result['X4']['order'], 2)
self.assertEqual(result['S1']['definition_formal'], 'X2') self.assertEqual(result['S1']['definition_formal'], 'X2')
self.assertEqual(result['S2']['definition_formal'], 'X4×X4') self.assertEqual(result['S2']['definition_formal'], 'X4×X4')
self.assertEqual(result['D1']['definition_formal'], r'S1\S2\X2') self.assertEqual(result['D1']['definition_formal'], r'S1\S2\X2')

File diff suppressed because it is too large Load Diff

View File

@ -10,11 +10,11 @@
"dependencies": { "dependencies": {
"@lezer/lr": "^1.4.2", "@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@uiw/codemirror-themes": "^4.23.0", "@uiw/codemirror-themes": "^4.23.2",
"@uiw/react-codemirror": "^4.23.0", "@uiw/react-codemirror": "^4.23.2",
"axios": "^1.7.7", "axios": "^1.7.7",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^11.5.1", "framer-motion": "^11.5.4",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"react": "^18.3.1", "react": "^18.3.1",
@ -23,7 +23,7 @@
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-intl": "^6.6.8", "react-intl": "^6.6.8",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.26.1", "react-router-dom": "^6.26.2",
"react-select": "^5.8.0", "react-select": "^5.8.0",
"react-tabs": "^6.0.2", "react-tabs": "^6.0.2",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
@ -36,14 +36,14 @@
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^22.5.3", "@types/node": "^22.5.4",
"@types/react": "^18.3.5", "@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.9.1", "eslint": "^9.10.0",
"eslint-plugin-react": "^7.35.2", "eslint-plugin-react": "^7.35.2",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.9.0", "globals": "^15.9.0",
@ -51,9 +51,9 @@
"postcss": "^8.4.45", "postcss": "^8.4.45",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.5.4", "typescript": "^5.6.2",
"typescript-eslint": "^8.4.0", "typescript-eslint": "^8.5.0",
"vite": "^5.4.3" "vite": "^5.4.4"
} }
}, },
"node_modules/@alloc/quick-lru": { "node_modules/@alloc/quick-lru": {
@ -1431,9 +1431,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.9.1", "version": "9.10.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
"integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -1450,6 +1450,19 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/plugin-kit": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"levn": "^0.4.1"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@floating-ui/core": { "node_modules/@floating-ui/core": {
"version": "1.6.7", "version": "1.6.7",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.7.tgz",
@ -1616,9 +1629,9 @@
} }
}, },
"node_modules/@isaacs/cliui/node_modules/ansi-regex": { "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.0.1", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -2875,9 +2888,9 @@
} }
}, },
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.19.1", "version": "1.19.2",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz",
"integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==", "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@ -3549,9 +3562,9 @@
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.5.3", "version": "22.5.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
"integrity": "sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==", "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -3672,17 +3685,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz",
"integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==", "integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.4.0", "@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/type-utils": "8.4.0", "@typescript-eslint/type-utils": "8.5.0",
"@typescript-eslint/utils": "8.4.0", "@typescript-eslint/utils": "8.5.0",
"@typescript-eslint/visitor-keys": "8.4.0", "@typescript-eslint/visitor-keys": "8.5.0",
"graphemer": "^1.4.0", "graphemer": "^1.4.0",
"ignore": "^5.3.1", "ignore": "^5.3.1",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -3706,16 +3719,16 @@
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz",
"integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==", "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.4.0", "@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/types": "8.4.0", "@typescript-eslint/types": "8.5.0",
"@typescript-eslint/typescript-estree": "8.4.0", "@typescript-eslint/typescript-estree": "8.5.0",
"@typescript-eslint/visitor-keys": "8.4.0", "@typescript-eslint/visitor-keys": "8.5.0",
"debug": "^4.3.4" "debug": "^4.3.4"
}, },
"engines": { "engines": {
@ -3735,14 +3748,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz",
"integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.4.0", "@typescript-eslint/types": "8.5.0",
"@typescript-eslint/visitor-keys": "8.4.0" "@typescript-eslint/visitor-keys": "8.5.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -3753,14 +3766,14 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz",
"integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==", "integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/typescript-estree": "8.4.0", "@typescript-eslint/typescript-estree": "8.5.0",
"@typescript-eslint/utils": "8.4.0", "@typescript-eslint/utils": "8.5.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"ts-api-utils": "^1.3.0" "ts-api-utils": "^1.3.0"
}, },
@ -3778,9 +3791,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz",
"integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -3792,14 +3805,14 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz",
"integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.4.0", "@typescript-eslint/types": "8.5.0",
"@typescript-eslint/visitor-keys": "8.4.0", "@typescript-eslint/visitor-keys": "8.5.0",
"debug": "^4.3.4", "debug": "^4.3.4",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
@ -3821,16 +3834,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz",
"integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.4.0", "@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "8.4.0", "@typescript-eslint/scope-manager": "8.5.0",
"@typescript-eslint/types": "8.4.0", "@typescript-eslint/types": "8.5.0",
"@typescript-eslint/typescript-estree": "8.4.0" "@typescript-eslint/typescript-estree": "8.5.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -3844,13 +3857,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz",
"integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.4.0", "@typescript-eslint/types": "8.5.0",
"eslint-visitor-keys": "^3.4.3" "eslint-visitor-keys": "^3.4.3"
}, },
"engines": { "engines": {
@ -3862,9 +3875,9 @@
} }
}, },
"node_modules/@uiw/codemirror-extensions-basic-setup": { "node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.23.0", "version": "4.23.2",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.0.tgz", "resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.23.2.tgz",
"integrity": "sha512-+k5nkRpUWGaHr1JWT8jcKsVewlXw5qBgSopm9LW8fZ6KnSNZBycz8kHxh0+WSvckmXEESGptkIsb7dlkmJT/hQ==", "integrity": "sha512-eacivkj7wzskl2HBYs4rfN0CbYlsSQh5ADtOYWTpc8Txm4ONw8RTi4/rxF6Ks2vdaovizewU5QaHximbxoNTrw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.0.0", "@codemirror/autocomplete": "^6.0.0",
@ -3889,9 +3902,9 @@
} }
}, },
"node_modules/@uiw/codemirror-themes": { "node_modules/@uiw/codemirror-themes": {
"version": "4.23.0", "version": "4.23.2",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.0.tgz", "resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.23.2.tgz",
"integrity": "sha512-9fiji9xooZyBQozR1i6iTr56YP7j/Dr/VgsNWbqf5Szv+g+4WM1iZuiDGwNXmFMWX8gbkDzp6ASE21VCPSofWw==", "integrity": "sha512-g8x+oPqgbzxXSkHhRf7e1AM1mI9/Nl3URReS89pHitRKv8MZNrE+ey+HE8ycfNXRUatrb6zTSRV3M75uoZwNYw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@codemirror/language": "^6.0.0", "@codemirror/language": "^6.0.0",
@ -3908,16 +3921,16 @@
} }
}, },
"node_modules/@uiw/react-codemirror": { "node_modules/@uiw/react-codemirror": {
"version": "4.23.0", "version": "4.23.2",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.0.tgz", "resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.23.2.tgz",
"integrity": "sha512-MnqTXfgeLA3fsUUQjqjJgemEuNyoGALgsExVm0NQAllAAi1wfj+IoKFeK+h3XXMlTFRCFYOUh4AHDv0YXJLsOg==", "integrity": "sha512-MmFL6P5V1Mr81JLkJyWNedfxENKdRhsvyU7Izji9wp337m8dqRAz7rCF5XWarGKx+iQ7q2H5ryl07nLqKLSvtQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.6", "@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0", "@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1", "@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0", "@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.23.0", "@uiw/codemirror-extensions-basic-setup": "4.23.2",
"codemirror": "^6.0.0" "codemirror": "^6.0.0"
}, },
"funding": { "funding": {
@ -4729,9 +4742,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001655", "version": "1.0.30001660",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001655.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001660.tgz",
"integrity": "sha512-jRGVy3iSGO5Uutn2owlb5gR6qsGngTw9ZTb4ali9f3glshcNmJ2noam4Mo9zia5P9Dk3jNNydy7vQjuE5dQmfg==", "integrity": "sha512-GacvNTTuATm26qC74pt+ad1fW15mlQ/zuTzzY1ZoIzECTP8HURDfF43kNxPgf7H1jmelCBQTTbBNxdSXOA7Bqg==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -4828,9 +4841,9 @@
} }
}, },
"node_modules/cjs-module-lexer": { "node_modules/cjs-module-lexer": {
"version": "1.4.0", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz", "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz",
"integrity": "sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==", "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@ -5427,12 +5440,12 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.6", "version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "^2.1.3"
}, },
"engines": { "engines": {
"node": ">=6.0" "node": ">=6.0"
@ -5521,9 +5534,9 @@
} }
}, },
"node_modules/detect-gpu": { "node_modules/detect-gpu": {
"version": "5.0.46", "version": "5.0.47",
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.46.tgz", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.47.tgz",
"integrity": "sha512-aulQlEJDVAADo2j4ZkcEu/mtuX9dz104w7uIDa52/ntcKdOEM8aI+k91Wv4x0o+Gds4Nbd2Sds0Uaqp1ZuLLJw==", "integrity": "sha512-hxOjFbFN6/ToNzDs0SIt/P/Y1WxoxAEUXXlrw/HT2IPtDtIxSi57zP/TC6kTvWDWmwSnvfVHsPoDZihscx8OJQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"webgl-constants": "^1.1.1" "webgl-constants": "^1.1.1"
@ -5616,9 +5629,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.5.13", "version": "1.5.19",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.19.tgz",
"integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", "integrity": "sha512-kpLJJi3zxTR1U828P+LIUDZ5ohixyo68/IcYOHLqnbTPr/wdgn4i1ECvmALN9E16JPA6cvCG5UG79gVwVdEK5w==",
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
@ -5882,9 +5895,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "9.9.1", "version": "9.10.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
"integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==", "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -5892,7 +5905,8 @@
"@eslint-community/regexpp": "^4.11.0", "@eslint-community/regexpp": "^4.11.0",
"@eslint/config-array": "^0.18.0", "@eslint/config-array": "^0.18.0",
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "9.9.1", "@eslint/js": "9.10.0",
"@eslint/plugin-kit": "^0.1.0",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@humanwhocodes/retry": "^0.3.0", "@humanwhocodes/retry": "^0.3.0",
"@nodelib/fs.walk": "^1.2.8", "@nodelib/fs.walk": "^1.2.8",
@ -5915,7 +5929,6 @@
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"is-path-inside": "^3.0.3", "is-path-inside": "^3.0.3",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
@ -6495,9 +6508,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.8", "version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==", "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -6583,9 +6596,9 @@
} }
}, },
"node_modules/framer-motion": { "node_modules/framer-motion": {
"version": "11.5.1", "version": "11.5.4",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.1.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.4.tgz",
"integrity": "sha512-8QI17IxYiqWo+lIXmAopG+6NEi10CgTL1AlQRpcNcSxoyrrcRgXB/tc6pzmeloZ3y4xTdoFCN/s4mJAdTz6Clw==", "integrity": "sha512-E+tb3/G6SO69POkdJT+3EpdMuhmtCh9EWuK4I1DnIC23L7tFPrl8vxP+LSovwaw6uUr73rUbpb4FgK011wbRJQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "^2.4.0" "tslib": "^2.4.0"
@ -10033,9 +10046,9 @@
} }
}, },
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mz": { "node_modules/mz": {
@ -11035,12 +11048,12 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.26.1", "version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz",
"integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==", "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@remix-run/router": "1.19.1" "@remix-run/router": "1.19.2"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@ -11050,13 +11063,13 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "6.26.1", "version": "6.26.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz",
"integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==", "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@remix-run/router": "1.19.1", "@remix-run/router": "1.19.2",
"react-router": "6.26.1" "react-router": "6.26.2"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@ -11662,9 +11675,9 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@ -12229,9 +12242,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/three-mesh-bvh": { "node_modules/three-mesh-bvh": {
"version": "0.7.6", "version": "0.7.8",
"resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.6.tgz", "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz",
"integrity": "sha512-rCjsnxEqR9r1/C/lCqzGLS67NDty/S/eT6rAJfDvsanrIctTWdNoR4ZOGWewCB13h1QkVo2BpmC0wakj1+0m8A==", "integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
"three": ">= 0.151.0" "three": ">= 0.151.0"
@ -12517,9 +12530,9 @@
} }
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.5.4", "version": "5.6.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz",
"integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==",
"devOptional": true, "devOptional": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"bin": { "bin": {
@ -12531,15 +12544,15 @@
} }
}, },
"node_modules/typescript-eslint": { "node_modules/typescript-eslint": {
"version": "8.4.0", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.4.0.tgz", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.5.0.tgz",
"integrity": "sha512-67qoc3zQZe3CAkO0ua17+7aCLI0dU+sSQd1eKPGq06QE4rfQjstVXR6woHO5qQvGUa550NfGckT4tzh3b3c8Pw==", "integrity": "sha512-uD+XxEoSIvqtm4KE97etm32Tn5MfaZWgWfMMREStLxR6JzvHkc2Tkj7zhTEK5XmtpTmKHNnG8Sot6qDfhHtR1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/eslint-plugin": "8.4.0", "@typescript-eslint/eslint-plugin": "8.5.0",
"@typescript-eslint/parser": "8.4.0", "@typescript-eslint/parser": "8.5.0",
"@typescript-eslint/utils": "8.4.0" "@typescript-eslint/utils": "8.5.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -12698,9 +12711,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.3", "version": "5.4.4",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.4.tgz",
"integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", "integrity": "sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {

View File

@ -14,11 +14,11 @@
"dependencies": { "dependencies": {
"@lezer/lr": "^1.4.2", "@lezer/lr": "^1.4.2",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",
"@uiw/codemirror-themes": "^4.23.0", "@uiw/codemirror-themes": "^4.23.2",
"@uiw/react-codemirror": "^4.23.0", "@uiw/react-codemirror": "^4.23.2",
"axios": "^1.7.7", "axios": "^1.7.7",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"framer-motion": "^11.5.1", "framer-motion": "^11.5.4",
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"js-file-download": "^0.4.12", "js-file-download": "^0.4.12",
"react": "^18.3.1", "react": "^18.3.1",
@ -27,7 +27,7 @@
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
"react-intl": "^6.6.8", "react-intl": "^6.6.8",
"react-loader-spinner": "^6.1.6", "react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.26.1", "react-router-dom": "^6.26.2",
"react-select": "^5.8.0", "react-select": "^5.8.0",
"react-tabs": "^6.0.2", "react-tabs": "^6.0.2",
"react-toastify": "^10.0.5", "react-toastify": "^10.0.5",
@ -40,14 +40,14 @@
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "^22.5.3", "@types/node": "^22.5.4",
"@types/react": "^18.3.5", "@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.0.1", "@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1", "@typescript-eslint/parser": "^8.0.1",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"eslint": "^9.9.1", "eslint": "^9.10.0",
"eslint-plugin-react": "^7.35.2", "eslint-plugin-react": "^7.35.2",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"globals": "^15.9.0", "globals": "^15.9.0",
@ -55,9 +55,9 @@
"postcss": "^8.4.45", "postcss": "^8.4.45",
"tailwindcss": "^3.4.10", "tailwindcss": "^3.4.10",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"typescript": "^5.5.4", "typescript": "^5.6.2",
"typescript-eslint": "^8.4.0", "typescript-eslint": "^8.5.0",
"vite": "^5.4.3" "vite": "^5.4.4"
}, },
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",

View File

@ -116,6 +116,22 @@ const RSInput = forwardRef<ReactCodeMirrorRef, RSInputProps>(
if (!selection.empty || !schema) { if (!selection.empty || !schema) {
return; return;
} }
const wordRange = text.getWord(selection.from);
if (wordRange) {
const word = text.getText(wordRange.from, wordRange.to);
if (word.length > 2 && (word.startsWith('Pr') || word.startsWith('pr'))) {
text.setSelection(wordRange.from, wordRange.from + 2);
if (word.startsWith('Pr')) {
text.replaceWith('pr');
} else {
text.replaceWith('Pr');
}
event.preventDefault();
event.stopPropagation();
return;
}
}
const hint = text.getText(selection.from - 1, selection.from); const hint = text.getText(selection.from - 1, selection.from);
const type = guessCstType(hint); const type = guessCstType(hint);
if (hint === getCstTypePrefix(type)) { if (hint === getCstTypePrefix(type)) {

View File

@ -70,7 +70,9 @@ function PickConstituenta({
cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} /> cell: props => <BadgeConstituenta theme={colors} value={props.row.original} prefixID={prefixID} />
}), }),
columnHelper.accessor(cst => describeFunc(cst), { columnHelper.accessor(cst => describeFunc(cst), {
id: 'description' id: 'description',
size: 1000,
minSize: 1000
}) })
], ],
[colors, prefixID, describeFunc] [colors, prefixID, describeFunc]

View File

@ -127,7 +127,6 @@ function DataTable<TData extends RowData>({
const isEmpty = tableImpl.getRowModel().rows.length === 0; const isEmpty = tableImpl.getRowModel().rows.length === 0;
// TODO: refactor formula for different font sizes and pagination tools
const fixedSize = useMemo(() => { const fixedSize = useMemo(() => {
if (!rows) { if (!rows) {
return undefined; return undefined;

View File

@ -46,7 +46,7 @@ function FileInput({ id, label, acceptType, title, className, style, onChange, .
{...restProps} {...restProps}
/> />
<Button text={label} icon={<IconUpload size='1.25rem' />} onClick={handleUploadClick} title={title} /> <Button text={label} icon={<IconUpload size='1.25rem' />} onClick={handleUploadClick} title={title} />
<Label text={fileName} htmlFor={id} /> <Label className='text-wrap' text={fileName} htmlFor={id} />
</div> </div>
); );
} }

View File

@ -103,23 +103,19 @@ function Modal({
{children} {children}
</div> </div>
<div className={clsx('z-modalControls', 'px-6 py-3 flex gap-12 justify-center')}> <div className='z-modalControls my-2 flex gap-12 justify-center text-sm'>
{!readonly ? ( {!readonly ? (
<Button <Button
autoFocus autoFocus
text={submitText} text={submitText}
title={!canSubmit ? submitInvalidTooltip : ''} title={!canSubmit ? submitInvalidTooltip : ''}
className='min-w-[8rem] min-h-[2.6rem]' className='min-w-[7rem]'
colors='clr-btn-primary' colors='clr-btn-primary'
disabled={!canSubmit} disabled={!canSubmit}
onClick={handleSubmit} onClick={handleSubmit}
/> />
) : null} ) : null}
<Button <Button text={readonly ? 'Закрыть' : 'Отмена'} className='min-w-[7rem]' onClick={handleCancel} />
text={readonly ? 'Закрыть' : 'Отмена'}
className='min-w-[8rem] min-h-[2.6rem]'
onClick={handleCancel}
/>
</div> </div>
</motion.div> </motion.div>
</div> </div>

View File

@ -110,8 +110,14 @@ export const OptionsState = ({ children }: OptionsStateProps) => {
}, [setDarkMode]); }, [setDarkMode]);
const mainHeight = useMemo(() => { const mainHeight = useMemo(() => {
return !noNavigation ? 'calc(100dvh - 6.75rem)' : '100dvh'; if (noNavigation) {
}, [noNavigation]); return '100dvh';
} else if (noFooter) {
return 'calc(100dvh - 3rem)';
} else {
return 'calc(100dvh - 6.75rem)';
}
}, [noNavigation, noFooter]);
const viewportHeight = useMemo(() => { const viewportHeight = useMemo(() => {
return !noNavigation ? 'calc(100dvh - 3rem)' : '100dvh'; return !noNavigation ? 'calc(100dvh - 3rem)' : '100dvh';

View File

@ -160,13 +160,13 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
<Modal <Modal
header='Создание конституенты из шаблона' header='Создание конституенты из шаблона'
submitText='Создать' submitText='Создать'
className='w-[43rem] h-[36.5rem] px-6' className='w-[43rem] h-[35rem] px-6'
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={validated} canSubmit={validated}
beforeSubmit={handlePrompt} beforeSubmit={handlePrompt}
onSubmit={handleSubmit} onSubmit={handleSubmit}
> >
<Overlay position='top-0 right-[6rem]'> <Overlay position='top-0 right-[5.9rem]'>
<BadgeHelp <BadgeHelp
topic={HelpTopic.RSL_TEMPLATES} topic={HelpTopic.RSL_TEMPLATES}
className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')}

View File

@ -118,7 +118,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
data={filteredData} data={filteredData}
onSelectValue={cst => partialUpdate({ prototype: cst })} onSelectValue={cst => partialUpdate({ prototype: cst })}
prefixID={prefixes.cst_template_ist} prefixID={prefixes.cst_template_ist}
rows={9} rows={8}
/> />
<TextArea <TextArea
id='dlg_template_term' id='dlg_template_term'

View File

@ -147,7 +147,7 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={isValid} canSubmit={isValid}
onSubmit={handleSubmit} onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]' className='w-[40rem] px-6 h-[32rem]'
> >
<Overlay position='top-0 right-0'> <Overlay position='top-0 right-0'>
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} /> <BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />
@ -155,11 +155,11 @@ function DlgCreateOperation({ hideWindow, oss, onCreate, initialInputs }: DlgCre
<Tabs <Tabs
selectedTabClassName='clr-selected' selectedTabClassName='clr-selected'
className='flex flex-col' className='flex flex-col pt-2'
selectedIndex={activeTab} selectedIndex={activeTab}
onSelect={handleSelectTab} onSelect={handleSelectTab}
> >
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}> <TabList className={clsx('self-center absolute top-[2.4rem]', 'flex', 'border divide-x rounded-none')}>
<TabLabel <TabLabel
title={describeOperationType(OperationType.INPUT)} title={describeOperationType(OperationType.INPUT)}
label={labelOperationType(OperationType.INPUT)} label={labelOperationType(OperationType.INPUT)}

View File

@ -169,7 +169,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={canSubmit} canSubmit={canSubmit}
onSubmit={handleSubmit} onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]' className='w-[40rem] px-6 h-[32rem]'
> >
<Overlay position='top-0 right-0'> <Overlay position='top-0 right-0'>
<BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} /> <BadgeHelp topic={HelpTopic.CC_OSS} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} offset={14} />

View File

@ -35,7 +35,7 @@ function TabSynthesis({
<PickSubstitutions <PickSubstitutions
schemas={schemas} schemas={schemas}
prefixID={prefixes.dlg_cst_substitutes_list} prefixID={prefixes.dlg_cst_substitutes_list}
rows={10} rows={8}
substitutions={substitutions} substitutions={substitutions}
setSubstitutions={setSubstitutions} setSubstitutions={setSubstitutions}
suggestions={suggestions} suggestions={suggestions}

View File

@ -70,7 +70,7 @@ function DlgEditReference({ hideWindow, schema, initial, onSave }: DlgEditRefere
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={isValid} canSubmit={isValid}
onSubmit={handleSubmit} onSubmit={handleSubmit}
className='w-[40rem] px-6 min-h-[35rem]' className='w-[40rem] px-6 h-[32rem]'
> >
<Overlay position='top-0 right-0'> <Overlay position='top-0 right-0'>
<BadgeHelp <BadgeHelp

View File

@ -70,7 +70,7 @@ function TabEntityReference({ initial, schema, setIsValid, setReference }: TabEn
describeFunc={cst => cst.term_resolved} describeFunc={cst => cst.term_resolved}
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)} matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
onBeginFilter={cst => cst.term_resolved !== ''} onBeginFilter={cst => cst.term_resolved !== ''}
rows={8} rows={7}
/> />
<div className='flex gap-3'> <div className='flex gap-3'>

View File

@ -96,7 +96,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
<Modal <Modal
header='Импорт концептуальной схем' header='Импорт концептуальной схем'
submitText='Добавить конституенты' submitText='Добавить конституенты'
className='w-[40rem] h-[36rem] px-6' className='w-[40rem] h-[33rem] px-6'
hideWindow={hideWindow} hideWindow={hideWindow}
canSubmit={validated} canSubmit={validated}
onSubmit={handleSubmit} onSubmit={handleSubmit}

View File

@ -19,7 +19,7 @@ function TabConstituents({ schema, error, loading, selected, setSelected }: TabC
<DataLoader id='dlg-constituents-tab' isLoading={loading} error={error} hasNoData={!schema}> <DataLoader id='dlg-constituents-tab' isLoading={loading} error={error} hasNoData={!schema}>
<PickMultiConstituenta <PickMultiConstituenta
schema={schema} schema={schema}
rows={14} rows={13}
prefixID={prefixes.cst_inline_synth_list} prefixID={prefixes.cst_inline_synth_list}
selected={selected} selected={selected}
setSelected={setSelected} setSelected={setSelected}

View File

@ -35,7 +35,7 @@ function TabSchema({ selected, setSelected }: TabSchemaProps) {
id='dlg_schema_picker' // prettier: split lines id='dlg_schema_picker' // prettier: split lines
items={library.items} items={library.items}
itemType={LibraryItemType.RSFORM} itemType={LibraryItemType.RSFORM}
rows={15} rows={14}
value={selected} value={selected}
onSelectValue={setSelected} onSelectValue={setSelected}
/> />

View File

@ -25,11 +25,6 @@ export enum CstType {
// CstType constant for category dividers in TemplateSchemas // CstType constant for category dividers in TemplateSchemas
export const CATEGORY_CST_TYPE = CstType.THEOREM; export const CATEGORY_CST_TYPE = CstType.THEOREM;
/**
* Represents position in linear order.
*/
export type Position = number;
/** /**
* Represents {@link IConstituenta} identifier type. * Represents {@link IConstituenta} identifier type.
*/ */
@ -71,7 +66,6 @@ export interface TermForm {
export interface IConstituentaMeta { export interface IConstituentaMeta {
id: ConstituentaID; id: ConstituentaID;
schema: LibraryItemID; schema: LibraryItemID;
order: Position;
alias: string; alias: string;
convention: string; convention: string;
cst_type: CstType; cst_type: CstType;
@ -146,7 +140,7 @@ export interface ICstCreateData
* Represents data, used in ordering a list of {@link IConstituenta}. * Represents data, used in ordering a list of {@link IConstituenta}.
*/ */
export interface ICstMovetoData extends IConstituentaList { export interface ICstMovetoData extends IConstituentaList {
move_to: Position; move_to: number; // Note: 0-base index
} }
/** /**

View File

@ -120,7 +120,6 @@ export function createMockConstituenta(id: ConstituentaID, alias: string, commen
children: [], children: [],
children_alias: [], children_alias: [],
is_simple_expression: false, is_simple_expression: false,
order: -1,
schema: -1, schema: -1,
alias: alias, alias: alias,
convention: comment, convention: comment,
@ -157,8 +156,17 @@ export function isMockCst(cst: IConstituenta) {
* Apply filter based on start {@link IConstituenta} type. * Apply filter based on start {@link IConstituenta} type.
*/ */
export function applyFilterCategory(start: IConstituenta, schema: IRSForm): IConstituenta[] { export function applyFilterCategory(start: IConstituenta, schema: IRSForm): IConstituenta[] {
const nextCategory = schema.items.find(cst => cst.order > start.order && cst.cst_type === CATEGORY_CST_TYPE); const startIndex = schema.items.indexOf(start);
return schema.items.filter(cst => cst.order >= start.order && (!nextCategory || cst.order < nextCategory.order)); if (startIndex === -1) {
return [];
}
const nextCategoryIndex = schema.items.findIndex(
(cst, index) => index > startIndex && cst.cst_type === CATEGORY_CST_TYPE
);
return schema.items.filter(
(_, index) => index >= startIndex && (nextCategoryIndex === -1 || index < nextCategoryIndex)
);
} }
/** /**

View File

@ -123,7 +123,7 @@ function FormCreateItem() {
return ( return (
<form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}> <form className={clsx('cc-column', 'min-w-[30rem] max-w-[30rem] mx-auto', 'px-6 py-3')} onSubmit={handleSubmit}>
<h1> <h1 className='select-none'>
{itemType == LibraryItemType.RSFORM ? ( {itemType == LibraryItemType.RSFORM ? (
<Overlay position='top-0 right-[0.5rem]'> <Overlay position='top-0 right-[0.5rem]'>
<input <input
@ -144,7 +144,7 @@ function FormCreateItem() {
Создание схемы Создание схемы
</h1> </h1>
{fileName ? <Label text={`Загружен файл: ${fileName}`} /> : null} {fileName ? <Label className='text-wrap' text={`Загружен файл: ${fileName}`} /> : null}
<TextInput <TextInput
id='schema_title' id='schema_title'

View File

@ -94,7 +94,7 @@ function HelpRSEditor() {
<IconTree className='inline-icon' /> отображение{' '} <IconTree className='inline-icon' /> отображение{' '}
<LinkTopic text='дерева разбора' topic={HelpTopic.UI_FORMULA_TREE} /> <LinkTopic text='дерева разбора' topic={HelpTopic.UI_FORMULA_TREE} />
</li> </li>
<li>Ctrl + Пробел дополняет до незанятого имени</li> <li>Ctrl + Пробел вставка незанятого имени / замена проекции</li>
<h2>Термин и Текстовое определение</h2> <h2>Термин и Текстовое определение</h2>
<li> <li>

View File

@ -49,7 +49,7 @@ function EditorOssCard({ isModified, onDestroy, setIsModified }: EditorOssCardPr
/> />
<AnimateFade <AnimateFade
onKeyDown={handleInput} onKeyDown={handleInput}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem]', 'mx-auto ', 'flex flex-col md:flex-row px-6')} className={clsx('md:w-fit md:max-w-fit max-w-[32rem]', 'mx-auto pt-[1.9rem]', 'flex flex-col md:flex-row px-6')}
> >
<FlexColumn className='px-3'> <FlexColumn className='px-3'>
<FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} /> <FormOSS id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />

View File

@ -40,7 +40,7 @@ interface OssFlowProps {
} }
function OssFlow({ isModified, setIsModified }: OssFlowProps) { function OssFlow({ isModified, setIsModified }: OssFlowProps) {
const { calculateHeight, colors } = useConceptOptions(); const { mainHeight, colors } = useConceptOptions();
const model = useOSS(); const model = useOSS();
const controller = useOssEdit(); const controller = useOssEdit();
const flow = useReactFlow(); const flow = useReactFlow();
@ -342,8 +342,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
} }
} }
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
const OssNodeTypes: NodeTypes = useMemo( const OssNodeTypes: NodeTypes = useMemo(
() => ({ () => ({
synthesis: OperationNode, synthesis: OperationNode,
@ -352,6 +350,8 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
[] []
); );
// proOptions={{ hideAttribution: true }}
const graph = useMemo( const graph = useMemo(
() => ( () => (
<ReactFlow <ReactFlow
@ -360,7 +360,6 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
onNodesChange={handleNodesChange} onNodesChange={handleNodesChange}
onEdgesChange={onEdgesChange} onEdgesChange={onEdgesChange}
onNodeDoubleClick={handleNodeDoubleClick} onNodeDoubleClick={handleNodeDoubleClick}
proOptions={{ hideAttribution: true }}
fitView fitView
nodeTypes={OssNodeTypes} nodeTypes={OssNodeTypes}
maxZoom={2} maxZoom={2}
@ -389,7 +388,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
return ( return (
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}> <AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
<Overlay position='top-0 pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'> <Overlay position='top-[1.9rem] pt-1 right-1/2 translate-x-1/2' className='rounded-b-2xl cc-blur'>
<ToolbarOssGraph <ToolbarOssGraph
isModified={isModified} isModified={isModified}
showGrid={showGrid} showGrid={showGrid}
@ -419,7 +418,7 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
{...menuProps} {...menuProps}
/> />
) : null} ) : null}
<div className='relative w-[100vw]' style={{ height: canvasHeight }}> <div className='relative w-[100vw]' style={{ height: mainHeight }}>
{graph} {graph}
</div> </div>
</AnimateFade> </AnimateFade>

View File

@ -9,6 +9,7 @@ import { toast } from 'react-toastify';
import { urls } from '@/app/urls'; import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError'; import InfoError, { ErrorData } from '@/components/info/InfoError';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
@ -37,7 +38,7 @@ function OssTabs() {
const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH; const activeTab = query.get('tab') ? (Number(query.get('tab')) as OssTabID) : OssTabID.GRAPH;
const { user } = useAuth(); const { user } = useAuth();
const { calculateHeight, setNoFooter } = useConceptOptions(); const { setNoFooter } = useConceptOptions();
const { schema, loading, loadingError: errorLoading } = useOSS(); const { schema, loading, loadingError: errorLoading } = useOSS();
const { destroyItem } = useLibrary(); const { destroyItem } = useLibrary();
@ -107,8 +108,6 @@ function OssTabs() {
}); });
}, [schema, destroyItem, router]); }, [schema, destroyItem, router]);
const panelHeight = useMemo(() => calculateHeight('1.625rem + 2px'), [calculateHeight]);
const cardPanel = useMemo( const cardPanel = useMemo(
() => ( () => (
<TabPanel> <TabPanel>
@ -143,14 +142,16 @@ function OssTabs() {
selectedTabClassName='clr-selected' selectedTabClassName='clr-selected'
className='flex flex-col mx-auto min-w-fit' className='flex flex-col mx-auto min-w-fit'
> >
<TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}> <Overlay position='top-0 right-1/2 translate-x-1/2' layer='z-sticky'>
<MenuOssTabs onDestroy={onDestroySchema} /> <TabList className={clsx('w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}>
<MenuOssTabs onDestroy={onDestroySchema} />
<TabLabel label='Карточка' title={schema.title ?? ''} /> <TabLabel label='Карточка' title={schema.title ?? ''} />
<TabLabel label='Граф' /> <TabLabel label='Граф' />
</TabList> </TabList>
</Overlay>
<AnimateFade className='overflow-y-auto' style={{ maxHeight: panelHeight }}> <AnimateFade>
{cardPanel} {cardPanel}
{graphPanel} {graphPanel}
</AnimateFade> </AnimateFade>

View File

@ -28,7 +28,7 @@ interface EditorConstituentaProps {
function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) { function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }: EditorConstituentaProps) {
const controller = useRSEdit(); const controller = useRSEdit();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
const { calculateHeight } = useConceptOptions(); const { mainHeight } = useConceptOptions();
const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true); const [showList, setShowList] = useLocalStorage(storage.rseditShowList, true);
const [toggleReset, setToggleReset] = useState(false); const [toggleReset, setToggleReset] = useState(false);
@ -39,7 +39,6 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
); );
const isNarrow = useMemo(() => !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD, [windowSize]); const isNarrow = useMemo(() => !!windowSize.width && windowSize.width <= SIDELIST_LAYOUT_THRESHOLD, [windowSize]);
const panelHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
function handleInput(event: React.KeyboardEvent<HTMLDivElement>) { function handleInput(event: React.KeyboardEvent<HTMLDivElement>) {
if (disabled) { if (disabled) {
@ -79,7 +78,7 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
} }
return ( return (
<div className='overflow-y-auto min-h-[20rem]' style={{ maxHeight: panelHeight }}> <>
<ToolbarConstituenta <ToolbarConstituenta
activeCst={activeCst} activeCst={activeCst}
disabled={disabled} disabled={disabled}
@ -89,39 +88,41 @@ function EditorConstituenta({ activeCst, isModified, setIsModified, onOpenEdit }
onReset={() => setToggleReset(prev => !prev)} onReset={() => setToggleReset(prev => !prev)}
onToggleList={() => setShowList(prev => !prev)} onToggleList={() => setShowList(prev => !prev)}
/> />
<div <div className='pt-[1.9rem] overflow-y-auto min-h-[20rem]' style={{ maxHeight: mainHeight }}>
tabIndex={-1} <div
className={clsx( tabIndex={-1}
'max-w-[95rem] mx-auto', // prettier: split lines className={clsx(
'flex', 'max-w-[95rem] mx-auto', // prettier: split lines
{ 'flex-col md:items-center': isNarrow } 'flex',
)} { 'flex-col md:items-center': isNarrow }
onKeyDown={handleInput} )}
> onKeyDown={handleInput}
<FormConstituenta >
disabled={disabled} <FormConstituenta
id={globals.constituenta_editor} disabled={disabled}
state={activeCst} id={globals.constituenta_editor}
isModified={isModified} state={activeCst}
toggleReset={toggleReset} isModified={isModified}
setIsModified={setIsModified} toggleReset={toggleReset}
onEditTerm={controller.editTermForms} setIsModified={setIsModified}
onRename={controller.renameCst} onEditTerm={controller.editTermForms}
onOpenEdit={onOpenEdit} onRename={controller.renameCst}
/> onOpenEdit={onOpenEdit}
<AnimatePresence initial={false}> />
{showList ? ( <AnimatePresence initial={false}>
<ViewConstituents {showList ? (
schema={controller.schema} <ViewConstituents
expression={activeCst?.definition_formal ?? ''} schema={controller.schema}
isBottom={isNarrow} expression={activeCst?.definition_formal ?? ''}
activeCst={activeCst} isBottom={isNarrow}
onOpenEdit={onOpenEdit} activeCst={activeCst}
/> onOpenEdit={onOpenEdit}
) : null} />
</AnimatePresence> ) : null}
</AnimatePresence>
</div>
</div> </div>
</div> </>
); );
} }

View File

@ -50,7 +50,7 @@ function ToolbarConstituenta({
return ( return (
<Overlay <Overlay
position='top-1 right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2' position='cc-tab-tools right-1/2 translate-x-1/2 xs:right-4 xs:translate-x-0 md:right-1/2 md:translate-x-1/2'
className='cc-icons outline-none transition-all duration-500' className='cc-icons outline-none transition-all duration-500'
> >
{controller.schema && controller.schema?.oss.length > 0 ? ( {controller.schema && controller.schema?.oss.length > 0 ? (

View File

@ -32,7 +32,7 @@ function ParsingResult({ isOpen, data, disabled, onShowError }: ParsingResultPro
<p <p
tabIndex={-1} tabIndex={-1}
key={`error-${index}`} key={`error-${index}`}
className={`clr-text-red ${disabled ? '' : 'cursor-pointer'}`} className={`clr-text-red break-all ${disabled ? '' : 'cursor-pointer'}`}
onClick={disabled ? undefined : () => onShowError(error)} onClick={disabled ? undefined : () => onShowError(error)}
> >
<span className='mr-1 font-semibold underline'> <span className='mr-1 font-semibold underline'>

View File

@ -49,7 +49,7 @@ function EditorRSFormCard({ isModified, onDestroy, setIsModified }: EditorRSForm
/> />
<AnimateFade <AnimateFade
onKeyDown={handleInput} onKeyDown={handleInput}
className={clsx('md:w-fit md:max-w-fit max-w-[32rem] mx-auto', 'flex flex-col md:flex-row px-6')} className={clsx('md:w-fit md:max-w-fit max-w-[32rem] mx-auto', 'flex flex-col md:flex-row px-6 pt-[1.9rem]')}
> >
<FlexColumn className='flex-shrink'> <FlexColumn className='flex-shrink'>
<FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} /> <FormRSForm id={globals.library_item_editor} isModified={isModified} setIsModified={setIsModified} />

View File

@ -45,7 +45,7 @@ function ToolbarRSFormCard({ modified, controller, onSubmit, onDestroy }: Toolba
}, [controller]); }, [controller]);
return ( return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='cc-icons'> <Overlay position='cc-tab-tools' className='cc-icons'>
{ossSelector} {ossSelector}
{controller.isMutable || modified ? ( {controller.isMutable || modified ? (
<MiniButton <MiniButton

View File

@ -142,7 +142,7 @@ function EditorRSList({ onOpenEdit }: EditorRSListProps) {
return ( return (
<> <>
{controller.isContentEditable ? <ToolbarRSList /> : null} {controller.isContentEditable ? <ToolbarRSList /> : null}
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}> <AnimateFade tabIndex={-1} onKeyDown={handleKeyDown} className='pt-[1.9rem]'>
{controller.isContentEditable ? ( {controller.isContentEditable ? (
<div className='flex items-center border-b'> <div className='flex items-center border-b'>
<div className='px-2'> <div className='px-2'>

View File

@ -28,7 +28,7 @@ function ToolbarRSList() {
return ( return (
<Overlay <Overlay
position='top-1 right-4 translate-x-0 md:right-1/2 md:translate-x-1/2' position='cc-tab-tools right-4 translate-x-0 md:right-1/2 md:translate-x-1/2'
className='cc-icons items-start outline-none transition-all duration-500' className='cc-icons items-start outline-none transition-all duration-500'
> >
{controller.schema && controller.schema?.oss.length > 0 ? ( {controller.schema && controller.schema?.oss.length > 0 ? (

View File

@ -287,7 +287,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
); );
return ( return (
<AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}> <>
<AnimatePresence> <AnimatePresence>
{showParamsDialog ? ( {showParamsDialog ? (
<DlgGraphParams <DlgGraphParams
@ -298,10 +298,7 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
) : null} ) : null}
</AnimatePresence> </AnimatePresence>
<Overlay <Overlay position='cc-tab-tools' className='flex flex-col items-center rounded-b-2xl cc-blur'>
position='top-0 pt-1 right-1/2 translate-x-1/2'
className='flex flex-col items-center rounded-b-2xl cc-blur'
>
<ToolbarTermGraph <ToolbarTermGraph
is3D={is3D} is3D={is3D}
orbit={orbit} orbit={orbit}
@ -352,39 +349,41 @@ function EditorTermGraph({ onOpenEdit }: EditorTermGraphProps) {
) : null} ) : null}
</Overlay> </Overlay>
<SelectedCounter <AnimateFade tabIndex={-1} onKeyDown={handleKeyDown}>
hideZero <SelectedCounter
totalCount={controller.schema?.stats?.count_all ?? 0} hideZero
selectedCount={controller.selected.length} totalCount={controller.schema?.stats?.count_all ?? 0}
position='top-[4.3rem] sm:top-[2rem] left-0' selectedCount={controller.selected.length}
/> position='top-[4.3rem] sm:top-[2rem] left-0'
/>
{hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? ( {hoverCst && hoverCstDebounced && hoverCst === hoverCstDebounced ? (
<Overlay <Overlay
layer='z-tooltip' layer='z-tooltip'
position={clsx('top-[1.6rem]', { 'left-[2.6rem]': hoverLeft, 'right-[2.6rem]': !hoverLeft })} position={clsx('top-[3.5rem]', { 'left-[2.6rem]': hoverLeft, 'right-[2.6rem]': !hoverLeft })}
className={clsx( className={clsx(
'w-[25rem] max-h-[calc(100dvh-15rem)]', 'w-[25rem] max-h-[calc(100dvh-15rem)]',
'px-3', 'px-3',
'cc-scroll-y', 'cc-scroll-y',
'border shadow-md', 'border shadow-md',
'clr-input', 'clr-input',
'text-sm' 'text-sm'
)} )}
> >
<InfoConstituenta className='pt-1 pb-2' data={hoverCstDebounced} /> <InfoConstituenta className='pt-1 pb-2' data={hoverCstDebounced} />
</Overlay>
) : null}
<Overlay position='top-[8.15rem] sm:top-[5.9rem] left-0' className='flex gap-1'>
<div className='flex flex-col ml-2 w-[13.5rem]'>
{selectors}
{viewHidden}
</div>
</Overlay> </Overlay>
) : null}
<Overlay position='top-[6.25rem] sm:top-[4rem] left-0' className='flex gap-1'> {graph}
<div className='flex flex-col ml-2 w-[13.5rem]'> </AnimateFade>
{selectors} </>
{viewHidden}
</div>
</Overlay>
{graph}
</AnimateFade>
); );
} }

View File

@ -53,7 +53,7 @@ function TermGraph({
onSelect, onSelect,
onDeselect onDeselect
}: TermGraphProps) { }: TermGraphProps) {
const { calculateHeight, darkMode } = useConceptOptions(); const { mainHeight, darkMode } = useConceptOptions();
const { selections, setSelections } = useSelection({ const { selections, setSelections } = useSelection({
ref: graphRef, ref: graphRef,
@ -111,10 +111,8 @@ function TermGraph({
return 'calc(100vw - 1rem)'; return 'calc(100vw - 1rem)';
}, []); }, []);
const canvasHeight = useMemo(() => calculateHeight('1.75rem + 4px'), [calculateHeight]);
return ( return (
<div className='relative outline-none' style={{ width: canvasWidth, height: canvasHeight }}> <div className='relative outline-none' style={{ width: canvasWidth, height: mainHeight }}>
<GraphUI <GraphUI
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}

View File

@ -406,8 +406,8 @@ export const RSEditState = ({
} }
return Math.min(prev, index); return Math.min(prev, index);
}, -1); }, -1);
const target = Math.max(0, currentIndex - 1) + 1; const target = Math.max(0, currentIndex - 1);
const data = { const data: ICstMovetoData = {
items: selected, items: selected,
move_to: target move_to: target
}; };
@ -430,7 +430,7 @@ export const RSEditState = ({
return Math.max(prev, index); return Math.max(prev, index);
} }
}, -1); }, -1);
const target = Math.min(model.schema.items.length - 1, currentIndex - count + 2) + 1; const target = Math.min(model.schema.items.length - 1, currentIndex - count + 2);
const data: ICstMovetoData = { const data: ICstMovetoData = {
items: selected, items: selected,
move_to: target move_to: target

View File

@ -10,6 +10,7 @@ import { urls } from '@/app/urls';
import InfoError, { ErrorData } from '@/components/info/InfoError'; import InfoError, { ErrorData } from '@/components/info/InfoError';
import Divider from '@/components/ui/Divider'; import Divider from '@/components/ui/Divider';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import Overlay from '@/components/ui/Overlay';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import TextURL from '@/components/ui/TextURL'; import TextURL from '@/components/ui/TextURL';
import AnimateFade from '@/components/wrap/AnimateFade'; import AnimateFade from '@/components/wrap/AnimateFade';
@ -45,7 +46,7 @@ function RSTabs() {
const version = query.get('v') ? Number(query.get('v')) : undefined; const version = query.get('v') ? Number(query.get('v')) : undefined;
const cstQuery = query.get('active'); const cstQuery = query.get('active');
const { setNoFooter, calculateHeight } = useConceptOptions(); const { setNoFooter } = useConceptOptions();
const { schema, loading, errorLoading, isArchive, itemID } = useRSForm(); const { schema, loading, errorLoading, isArchive, itemID } = useRSForm();
const library = useLibrary(); const library = useLibrary();
const oss = useGlobalOss(); const oss = useGlobalOss();
@ -73,7 +74,7 @@ function RSTabs() {
}, [schema, schema?.title]); }, [schema, schema?.title]);
useLayoutEffect(() => { useLayoutEffect(() => {
setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST); setNoFooter(activeTab !== RSTabID.CARD);
setIsModified(false); setIsModified(false);
if (activeTab === RSTabID.CST_EDIT) { if (activeTab === RSTabID.CST_EDIT) {
const cstID = Number(cstQuery); const cstID = Number(cstQuery);
@ -189,8 +190,6 @@ function RSTabs() {
}); });
}, [schema, library, oss, router]); }, [schema, library, oss, router]);
const panelHeight = useMemo(() => calculateHeight('1.625rem + 2px'), [calculateHeight]);
const cardPanel = useMemo( const cardPanel = useMemo(
() => ( () => (
<TabPanel> <TabPanel>
@ -255,19 +254,23 @@ function RSTabs() {
selectedTabClassName='clr-selected' selectedTabClassName='clr-selected'
className='flex flex-col mx-auto min-w-fit' className='flex flex-col mx-auto min-w-fit'
> >
<TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}> <Overlay position='top-0 right-1/2 translate-x-1/2' layer='z-sticky'>
<MenuRSTabs onDestroy={onDestroySchema} /> <TabList className={clsx('mx-auto w-fit', 'flex items-stretch', 'border-b-2 border-x-2 divide-x-2')}>
<MenuRSTabs onDestroy={onDestroySchema} />
<TabLabel label='Карточка' titleHtml={`${schema.title ?? ''}<br />Версия: ${labelVersion(schema)}`} /> <TabLabel label='Карточка' titleHtml={`${schema.title ?? ''}<br />Версия: ${labelVersion(schema)}`} />
<TabLabel <TabLabel
label='Содержание' label='Содержание'
titleHtml={`Конституент: ${schema.stats?.count_all ?? 0}<br />Ошибок: ${schema.stats?.count_errors ?? 0}`} titleHtml={`Конституент: ${schema.stats?.count_all ?? 0}<br />Ошибок: ${
/> schema.stats?.count_errors ?? 0
<TabLabel label='Редактор' /> }`}
<TabLabel label='Граф термов' /> />
</TabList> <TabLabel label='Редактор' />
<TabLabel label='Граф термов' />
</TabList>
</Overlay>
<AnimateFade className='overflow-y-auto overflow-x-hidden' style={{ maxHeight: panelHeight }}> <AnimateFade className='overflow-x-hidden'>
{cardPanel} {cardPanel}
{listPanel} {listPanel}
{editorPanel} {editorPanel}

View File

@ -64,6 +64,14 @@
cursor: default; cursor: default;
} }
.react-flow__attribution {
background-color: transparent;
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
:is(.react-flow__node-input, .react-flow__node-synthesis) { :is(.react-flow__node-input, .react-flow__node-synthesis) {
cursor: pointer; cursor: pointer;

View File

@ -49,17 +49,40 @@
} }
} }
:is(.clr-app, .clr-footer, .cc-modal-backdrop, .clr-btn-nav, .clr-input:disabled) { :is(.clr-app, .clr-btn-nav) {
background-color: var(--cl-bg-100); background-color: var(--cl-bg-100);
.dark & { .dark & {
background-color: var(--cd-bg-100); background-color: var(--cd-bg-100);
} }
} }
:is(.clr-input) { .clr-footer {
color: var(--cl-fg-60);
background-color: var(--cl-bg-100);
.dark & {
color: var(--cd-fg-60);
background-color: var(--cd-bg-100);
}
}
.cc-modal-backdrop {
opacity: 0.5;
background-color: var(--cl-bg-100);
.dark & {
background-color: var(--cd-bg-100);
}
}
.clr-input {
background-color: var(--cl-bg-120); background-color: var(--cl-bg-120);
&:disabled {
background-color: var(--cl-bg-100);
}
.dark & { .dark & {
background-color: var(--cd-bg-120); background-color: var(--cd-bg-120);
&:disabled {
background-color: var(--cd-bg-100);
}
} }
} }
@ -126,13 +149,6 @@
} }
} }
.clr-footer {
color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
}
:is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) { :is(.clr-text-controls, .clr-btn-nav, .clr-btn-clear) {
color: var(--cl-fg-80); color: var(--cl-fg-80);
&:disabled { &:disabled {
@ -153,7 +169,7 @@
} }
} }
:is(.clr-text-default, input:disabled, textarea:disabled) { :is(.clr-text-default, input:disabled:not(::placeholder), textarea:disabled:not(::placeholder)) {
opacity: 1; opacity: 1;
-webkit-text-fill-color: var(--cl-fg-100); -webkit-text-fill-color: var(--cl-fg-100);
color: var(--cl-fg-100); color: var(--cl-fg-100);
@ -205,15 +221,15 @@
@apply clr-text-primary; @apply clr-text-primary;
} }
.cc-tab-tools {
@apply top-[1.9rem] pt-1 right-1/2 translate-x-1/2;
}
.cc-modal-blur { .cc-modal-blur {
opacity: 0.3; opacity: 0.3;
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
} }
.cc-modal-backdrop {
opacity: 0.5;
}
.cc-label { .cc-label {
@apply text-sm font-medium cursor-default select-text whitespace-nowrap; @apply text-sm font-medium cursor-default select-text whitespace-nowrap;
} }

View File

@ -357,6 +357,10 @@ export class CodeMirrorWrapper {
return this.ref.view.state.doc.sliceString(from, to); return this.ref.view.state.doc.sliceString(from, to);
} }
getWord(position: number): SelectionRange | null {
return this.ref.view.state.wordAt(position);
}
getSelection(): SelectionRange { getSelection(): SelectionRange {
return this.ref.view.state.selection.main; return this.ref.view.state.selection.main;
} }