diff --git a/rsconcept/backend/apps/rsform/models.py b/rsconcept/backend/apps/rsform/models.py index c631a04c..ff6ff3f4 100644 --- a/rsconcept/backend/apps/rsform/models.py +++ b/rsconcept/backend/apps/rsform/models.py @@ -1,6 +1,4 @@ ''' Models: RSForms for conceptual schemas. ''' -import json -from copy import deepcopy import re from typing import Iterable, Optional, cast @@ -13,7 +11,6 @@ from django.core.validators import MinValueValidator from django.core.exceptions import ValidationError from django.urls import reverse -import pyconcept from apps.users.models import User from cctext import Resolver, Entity, extract_entities from .graph import Graph @@ -315,6 +312,8 @@ class RSForm: ''' Insert new constituenta at given position. All following constituents order is shifted by 1 position ''' if position <= 0: raise ValidationError('Invalid position: should be positive integer') + currentSize = self.constituents().count() + position = max(1, min(position, currentSize + 1)) update_list = Constituenta.objects.only('id', 'order', 'schema').filter(schema=self.item, order__gte=position) for cst in update_list: cst.order += 1 @@ -326,7 +325,6 @@ class RSForm: alias=alias, cst_type=insert_type ) - self.update_order() self.item.save() result.refresh_from_db() return result @@ -343,7 +341,6 @@ class RSForm: alias=alias, cst_type=insert_type ) - self.update_order() self.item.save() result.refresh_from_db() return result @@ -369,7 +366,6 @@ class RSForm: count_moved += 1 update_list.append(cst) Constituenta.objects.bulk_update(update_list, ['order']) - self.update_order() self.item.save() @transaction.atomic @@ -377,7 +373,7 @@ class RSForm: ''' Delete multiple constituents. Do not check if listCst are from this schema ''' for cst in listCst: cst.delete() - self.update_order() + self._reset_order() self.resolve_all_text() self.item.save() @@ -447,23 +443,6 @@ class RSForm: if modified: cst.save() - @transaction.atomic - def update_order(self): - ''' Update constituents order. ''' - checked = PyConceptAdapter(self).basic() - update_list = self.constituents().only('id', 'order') - if len(checked['items']) != update_list.count(): - raise ValidationError('Invalid constituents count') - order = 1 - for cst in checked['items']: - cst_id = cst['id'] - for oldCst in update_list: - if oldCst.pk == cst_id: - oldCst.order = order - order += 1 - break - Constituenta.objects.bulk_update(update_list, ['order']) - @transaction.atomic def resolve_all_text(self): ''' Trigger reference resolution for all texts. ''' @@ -482,6 +461,15 @@ class RSForm: cst.definition_resolved = resolved cst.save() + @transaction.atomic + def _reset_order(self): + order = 1 + for cst in self.constituents().only('id', 'order').order_by('order'): + if cst.order != order: + cst.order = order + cst.save() + order += 1 + def _insert_new(self, data: dict, insert_after: Optional[str]=None) -> 'Constituenta': if insert_after is not None: cstafter = Constituenta.objects.get(pk=insert_after) @@ -510,87 +498,3 @@ class RSForm: if result.contains(alias): result.add_edge(id_from=alias, id_to=cst.alias) return result - - -class PyConceptAdapter: - ''' RSForm adapter for interacting with pyconcept module. ''' - def __init__(self, instance: RSForm): - self.schema = instance - self.data = self._prepare_request() - self._checked_data: Optional[dict] = None - - def basic(self) -> dict: - ''' Check RSForm and return check results. - Warning! Does not include texts. ''' - self._produce_response() - if self._checked_data is None: - raise ValueError('Invalid data response from pyconcept') - return self._checked_data - - def full(self) -> dict: - ''' Check RSForm and return check results including initial texts. ''' - self._produce_response() - if self._checked_data is None: - raise ValueError('Invalid data response from pyconcept') - return self._complete_rsform_details(self._checked_data) - - def _complete_rsform_details(self, data: dict) -> dict: - result = deepcopy(data) - result['id'] = self.schema.item.pk - result['alias'] = self.schema.item.alias - result['title'] = self.schema.item.title - result['comment'] = self.schema.item.comment - result['time_update'] = self.schema.item.time_update - result['time_create'] = self.schema.item.time_create - result['is_common'] = self.schema.item.is_common - result['is_canonical'] = self.schema.item.is_canonical - result['owner'] = (self.schema.item.owner.pk if self.schema.item.owner is not None else None) - for cst_data in result['items']: - cst = Constituenta.objects.get(pk=cst_data['id']) - cst_data['convention'] = cst.convention - cst_data['term'] = { - 'raw': cst.term_raw, - 'resolved': cst.term_resolved, - 'forms': cst.term_forms - } - cst_data['definition']['text'] = { - 'raw': cst.definition_raw, - 'resolved': cst.definition_resolved, - } - result['subscribers'] = [item.pk for item in self.schema.item.subscribers()] - return result - - def _prepare_request(self) -> dict: - result: dict = { - 'items': [] - } - items = self.schema.constituents().order_by('order') - for cst in items: - result['items'].append({ - 'entityUID': cst.pk, - 'cstType': cst.cst_type, - 'alias': cst.alias, - 'definition': { - 'formal': cst.definition_formal - } - }) - return result - - def _produce_response(self): - if self._checked_data is not None: - return - response = pyconcept.check_schema(json.dumps(self.data)) - data = json.loads(response) - self._checked_data = { - 'items': [] - } - for cst in data['items']: - self._checked_data['items'].append({ - 'id': cst['entityUID'], - 'cstType': cst['cstType'], - 'alias': cst['alias'], - 'definition': { - 'formal': cst['definition']['formal'] - }, - 'parse': cst['parse'] - }) diff --git a/rsconcept/backend/apps/rsform/serializers.py b/rsconcept/backend/apps/rsform/serializers.py index e18a9873..9c8e5ba8 100644 --- a/rsconcept/backend/apps/rsform/serializers.py +++ b/rsconcept/backend/apps/rsform/serializers.py @@ -1,8 +1,10 @@ ''' Serializers for conceptual schema API. ''' +import json from typing import Optional, cast from rest_framework import serializers from django.db import transaction +import pyconcept from cctext import Resolver, Reference, ReferenceType, EntityReference, SyntacticReference from .utils import fix_old_references @@ -31,7 +33,7 @@ class TextSerializer(serializers.Serializer): class LibraryItemSerializer(serializers.ModelSerializer): - ''' Serializer: Library item data. ''' + ''' Serializer: LibraryItem entry. ''' class Meta: ''' serializer metadata. ''' model = LibraryItem @@ -39,6 +41,71 @@ class LibraryItemSerializer(serializers.ModelSerializer): read_only_fields = ('owner', 'id', 'item_type') +class LibraryItemDetailsSerializer(serializers.ModelSerializer): + ''' Serializer: LibraryItem detailed data. ''' + class Meta: + ''' serializer metadata. ''' + model = LibraryItem + fields = '__all__' + read_only_fields = ('owner', 'id', 'item_type') + + def to_representation(self, instance: LibraryItem): + result = super().to_representation(instance) + result['subscribers'] = [item.pk for item in instance.subscribers()] + return result + + +class PyConceptAdapter: + ''' RSForm adapter for interacting with pyconcept module. ''' + def __init__(self, instance: RSForm): + self.schema = instance + self.data = self._prepare_request() + self._checked_data: Optional[dict] = None + + def parse(self) -> dict: + ''' Check RSForm and return check results. + Warning! Does not include texts. ''' + self._produce_response() + if self._checked_data is None: + raise ValueError('Invalid data response from pyconcept') + return self._checked_data + + def _prepare_request(self) -> dict: + result: dict = { + 'items': [] + } + items = self.schema.constituents().order_by('order') + for cst in items: + result['items'].append({ + 'entityUID': cst.pk, + 'cstType': cst.cst_type, + 'alias': cst.alias, + 'definition': { + 'formal': cst.definition_formal + } + }) + return result + + def _produce_response(self): + if self._checked_data is not None: + return + response = pyconcept.check_schema(json.dumps(self.data)) + data = json.loads(response) + self._checked_data = { + 'items': [] + } + for cst in data['items']: + self._checked_data['items'].append({ + 'id': cst['entityUID'], + 'cstType': cst['cstType'], + 'alias': cst['alias'], + 'definition': { + 'formal': cst['definition']['formal'] + }, + 'parse': cst['parse'] + }) + + class RSFormSerializer(serializers.ModelSerializer): ''' Serializer: Detailed data for RSForm. ''' class Meta: @@ -46,13 +113,30 @@ class RSFormSerializer(serializers.ModelSerializer): model = RSForm def to_representation(self, instance: RSForm): - result = LibraryItemSerializer(instance.item).data + result = LibraryItemDetailsSerializer(instance.item).data result['items'] = [] for cst in instance.constituents().order_by('order'): result['items'].append(ConstituentaSerializer(cst).data) return result +class RSFormParseSerializer(serializers.ModelSerializer): + ''' Serializer: Detailed data for RSForm including parse. ''' + class Meta: + ''' serializer metadata. ''' + model = RSForm + + def to_representation(self, instance: RSForm): + result = RSFormSerializer(instance).data + parse = PyConceptAdapter(instance).parse() + for cst_data in result['items']: + cst_data['parse'] = next( + cst['parse'] for cst in parse['items'] + if cst['id'] == cst_data['id'] + ) + return result + + class RSFormUploadSerializer(serializers.Serializer): ''' Upload data for RSForm serializer. ''' file = serializers.FileField() @@ -193,7 +277,6 @@ class RSFormTRSSerializer(serializers.Serializer): if prev_cst.pk not in loaded_ids: prev_cst.delete() - instance.update_order() instance.resolve_all_text() instance.item.save() return instance diff --git a/rsconcept/backend/apps/rsform/tests/t_models.py b/rsconcept/backend/apps/rsform/tests/t_models.py index 41970602..0f3575f1 100644 --- a/rsconcept/backend/apps/rsform/tests/t_models.py +++ b/rsconcept/backend/apps/rsform/tests/t_models.py @@ -200,10 +200,10 @@ class TestRSForm(TestCase): d2 = schema.insert_at(1, 'D2', CstType.TERM) d1.refresh_from_db() self.assertEqual(d1.order, 3) - self.assertEqual(d2.order, 2) + self.assertEqual(d2.order, 1) x2 = schema.insert_at(4, 'X2', CstType.BASE) - self.assertEqual(x2.order, 2) + self.assertEqual(x2.order, 4) def test_insert_last(self): schema = RSForm.create(title='Test') @@ -259,10 +259,10 @@ class TestRSForm(TestCase): x2.refresh_from_db() d1.refresh_from_db() d2.refresh_from_db() - self.assertEqual(x1.order, 2) + self.assertEqual(x1.order, 3) self.assertEqual(x2.order, 1) self.assertEqual(d1.order, 4) - self.assertEqual(d2.order, 3) + self.assertEqual(d2.order, 2) def test_move_cst_down(self): schema = RSForm.create(title='Test') diff --git a/rsconcept/backend/apps/rsform/tests/t_views.py b/rsconcept/backend/apps/rsform/tests/t_views.py index b3d290f3..3d7b1fe6 100644 --- a/rsconcept/backend/apps/rsform/tests/t_views.py +++ b/rsconcept/backend/apps/rsform/tests/t_views.py @@ -287,11 +287,11 @@ class TestRSFormViewset(APITestCase): self.assertEqual(len(response.data['items']), 2) self.assertEqual(response.data['items'][0]['id'], x1.id) self.assertEqual(response.data['items'][0]['parse']['status'], 'verified') - self.assertEqual(response.data['items'][0]['term']['raw'], x1.term_raw) - self.assertEqual(response.data['items'][0]['term']['resolved'], x1.term_resolved) + self.assertEqual(response.data['items'][0]['term_raw'], x1.term_raw) + self.assertEqual(response.data['items'][0]['term_resolved'], x1.term_resolved) self.assertEqual(response.data['items'][1]['id'], x2.id) - self.assertEqual(response.data['items'][1]['term']['raw'], x2.term_raw) - self.assertEqual(response.data['items'][1]['term']['resolved'], x2.term_resolved) + self.assertEqual(response.data['items'][1]['term_raw'], x2.term_raw) + self.assertEqual(response.data['items'][1]['term_resolved'], x2.term_resolved) self.assertEqual(response.data['subscribers'], [self.user.pk]) def test_check(self): @@ -412,6 +412,7 @@ class TestRSFormViewset(APITestCase): d1.definition_formal = 'X1' d1.save() + self.assertEqual(d1.order, 4) self.assertEqual(self.cst1.order, 1) self.assertEqual(self.cst1.alias, 'X1') self.assertEqual(self.cst1.cst_type, CstType.BASE) @@ -422,9 +423,10 @@ class TestRSFormViewset(APITestCase): self.assertEqual(response.data['new_cst']['cst_type'], 'term') d1.refresh_from_db() self.cst1.refresh_from_db() + self.assertEqual(d1.order, 4) self.assertEqual(d1.term_resolved, '') self.assertEqual(d1.term_raw, '@{D2|plur}') - self.assertEqual(self.cst1.order, 2) + self.assertEqual(self.cst1.order, 1) self.assertEqual(self.cst1.alias, 'D2') self.assertEqual(self.cst1.cst_type, CstType.TERM) @@ -560,10 +562,10 @@ class TestRSFormViewset(APITestCase): self.assertEqual(response.status_code, 201) self.assertEqual(response.data['title'], 'Title') self.assertEqual(response.data['items'][0]['alias'], x1.alias) - self.assertEqual(response.data['items'][0]['term']['raw'], x1.term_raw) - self.assertEqual(response.data['items'][0]['term']['resolved'], x1.term_resolved) - self.assertEqual(response.data['items'][1]['term']['raw'], d1.term_raw) - self.assertEqual(response.data['items'][1]['term']['resolved'], d1.term_resolved) + self.assertEqual(response.data['items'][0]['term_raw'], x1.term_raw) + self.assertEqual(response.data['items'][0]['term_resolved'], x1.term_resolved) + self.assertEqual(response.data['items'][1]['term_raw'], d1.term_raw) + self.assertEqual(response.data['items'][1]['term_resolved'], d1.term_resolved) class TestFunctionalViews(APITestCase): diff --git a/rsconcept/backend/apps/rsform/views.py b/rsconcept/backend/apps/rsform/views.py index 74ad2239..3966e556 100644 --- a/rsconcept/backend/apps/rsform/views.py +++ b/rsconcept/backend/apps/rsform/views.py @@ -94,7 +94,7 @@ class LibraryViewSet(viewsets.ModelViewSet): clone = s.RSFormTRSSerializer(data=clone_data, context={'load_meta': True}) clone.is_valid(raise_exception=True) new_schema = clone.save() - return Response(status=201, data=m.PyConceptAdapter(new_schema).full()) + return Response(status=201, data=s.RSFormParseSerializer(new_schema).data) return Response(status=404) @transaction.atomic @@ -153,7 +153,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr schema.item.refresh_from_db() response = Response(status=201, data={ 'new_cst': s.ConstituentaSerializer(new_cst).data, - 'schema': m.PyConceptAdapter(schema).full() + 'schema': s.RSFormParseSerializer(schema).data }) response['Location'] = new_cst.get_absolute_url() return response @@ -169,12 +169,11 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer.save() mapping = { old_alias: serializer.validated_data['alias'] } schema.apply_mapping(mapping, change_aliases=False) - schema.update_order() schema.item.refresh_from_db() cst = m.Constituenta.objects.get(pk=serializer.validated_data['id']) return Response(status=200, data={ 'new_cst': s.ConstituentaSerializer(cst).data, - 'schema': m.PyConceptAdapter(schema).full() + 'schema': s.RSFormParseSerializer(schema).data }) @action(detail=True, methods=['patch'], url_path='cst-multidelete') @@ -185,7 +184,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer.is_valid(raise_exception=True) schema.delete_cst(serializer.validated_data['constituents']) schema.item.refresh_from_db() - return Response(status=202, data=m.PyConceptAdapter(schema).full()) + return Response(status=202, data=s.RSFormParseSerializer(schema).data) @action(detail=True, methods=['patch'], url_path='cst-moveto') def cst_moveto(self, request, pk): @@ -195,14 +194,14 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer.is_valid(raise_exception=True) schema.move_cst(serializer.validated_data['constituents'], serializer.validated_data['move_to']) schema.item.refresh_from_db() - return Response(status=200, data=m.PyConceptAdapter(schema).full()) + return Response(status=200, data=s.RSFormParseSerializer(schema).data) @action(detail=True, methods=['patch'], url_path='reset-aliases') def reset_aliases(self, request, pk): ''' Endpoint: Recreate all aliases based on order. ''' schema = self._get_schema() schema.reset_aliases() - return Response(status=200, data=m.PyConceptAdapter(schema).full()) + return Response(status=200, data=s.RSFormParseSerializer(schema).data) @action(detail=True, methods=['patch'], url_path='load-trs') def load_trs(self, request, pk): @@ -217,7 +216,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr serializer = s.RSFormTRSSerializer(data=data, context={'load_meta': load_metadata}) serializer.is_valid(raise_exception=True) schema = serializer.save() - return Response(status=200, data=m.PyConceptAdapter(schema).full()) + return Response(status=200, data=s.RSFormParseSerializer(schema).data) @action(detail=True, methods=['get']) def contents(self, request, pk): @@ -229,13 +228,13 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr def details(self, request, pk): ''' Endpoint: Detailed schema view including statuses and parse. ''' schema = self._get_schema() - serializer = m.PyConceptAdapter(schema) - return Response(serializer.full()) + serializer = s.RSFormParseSerializer(schema) + return Response(serializer.data) @action(detail=True, methods=['post']) def check(self, request, pk): ''' Endpoint: Check RSLang expression against schema context. ''' - schema = m.PyConceptAdapter(self._get_schema()) + schema = s.PyConceptAdapter(self._get_schema()) serializer = s.ExpressionSerializer(data=request.data) serializer.is_valid(raise_exception=True) expression = serializer.validated_data['expression'] diff --git a/rsconcept/frontend/src/components/Common/Checkbox.tsx b/rsconcept/frontend/src/components/Common/Checkbox.tsx index c29fdef7..25a1e709 100644 --- a/rsconcept/frontend/src/components/Common/Checkbox.tsx +++ b/rsconcept/frontend/src/components/Common/Checkbox.tsx @@ -18,7 +18,7 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full const cursor = disabled ? 'cursor-not-allowed' : 'cursor-pointer'; - function handleLabelClick(event: React.MouseEvent): void { + function handleClick(event: React.MouseEvent): void { event.preventDefault(); if (!disabled) { inputRef.current?.click(); @@ -26,7 +26,12 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full } return ( -
+
+ ); } diff --git a/rsconcept/frontend/src/components/Help/InfoConstituenta.tsx b/rsconcept/frontend/src/components/Help/InfoConstituenta.tsx index 4caa1876..6dc228b5 100644 --- a/rsconcept/frontend/src/components/Help/InfoConstituenta.tsx +++ b/rsconcept/frontend/src/components/Help/InfoConstituenta.tsx @@ -11,9 +11,9 @@ function InfoConstituenta({ data, ...props }: InfoConstituentaProps) {

Конституента {data.alias}

Типизация: {getCstTypificationLabel(data)}

-

Термин: {data.term.resolved || data.term.raw}

- {data.definition.formal &&

Выражение: {data.definition.formal}

} - {data.definition.text.resolved &&

Определение: {data.definition.text.resolved}

} +

Термин: {data.term_resolved || data.term_raw}

+ {data.definition_formal &&

Выражение: {data.definition_formal}

} + {data.definition_resolved &&

Определение: {data.definition_resolved}

} {data.convention &&

Конвенция: {data.convention}

}
); diff --git a/rsconcept/frontend/src/components/RSInput/tooltip.ts b/rsconcept/frontend/src/components/RSInput/tooltip.ts index abe1e069..de5762a8 100644 --- a/rsconcept/frontend/src/components/RSInput/tooltip.ts +++ b/rsconcept/frontend/src/components/RSInput/tooltip.ts @@ -11,19 +11,19 @@ function createTooltipFor(cst: IConstituenta) { alias.className = 'text-sm text-left'; alias.textContent = `${cst.alias}: ${getCstTypificationLabel(cst)}`; dom.appendChild(alias); - if (cst.term.resolved) { + if (cst.term_resolved) { const term = document.createElement('p'); - term.innerHTML = `Термин: ${cst.term.resolved}`; + term.innerHTML = `Термин: ${cst.term_resolved}`; dom.appendChild(term); } - if (cst.definition.formal) { + if (cst.definition_formal) { const expression = document.createElement('p'); - expression.innerHTML = `Выражение: ${cst.definition.formal}`; + expression.innerHTML = `Выражение: ${cst.definition_formal}`; dom.appendChild(expression); } - if (cst.definition.text.resolved) { + if (cst.definition_resolved) { const definition = document.createElement('p'); - definition.innerHTML = `Определение: ${cst.definition.text.resolved}`; + definition.innerHTML = `Определение: ${cst.definition_resolved}`; dom.appendChild(definition); } if (cst.convention) { diff --git a/rsconcept/frontend/src/hooks/useCheckExpression.ts b/rsconcept/frontend/src/hooks/useCheckExpression.ts index fc0f1fc9..aa2646ea 100644 --- a/rsconcept/frontend/src/hooks/useCheckExpression.ts +++ b/rsconcept/frontend/src/hooks/useCheckExpression.ts @@ -70,7 +70,7 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) { onError: error => setError(error), onSuccess: parse => { if (activeCst) { - adjustResults(parse, expression === getCstExpressionPrefix(activeCst), activeCst.cstType); + adjustResults(parse, expression === getCstExpressionPrefix(activeCst), activeCst.cst_type); } setParseData(parse); if (onSuccess) onSuccess(parse); diff --git a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx index f0de720d..3eac2f97 100644 --- a/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx +++ b/rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx @@ -5,6 +5,7 @@ import Checkbox from '../../components/Common/Checkbox'; import Dropdown from '../../components/Common/Dropdown'; import DropdownButton from '../../components/Common/DropdownButton'; import { FilterCogIcon } from '../../components/Icons'; +import { useAuth } from '../../context/AuthContext'; import useDropdown from '../../hooks/useDropdown'; import { LibraryFilterStrategy } from '../../utils/models'; @@ -15,6 +16,7 @@ interface PickerStrategyProps { function PickerStrategy({ value, onChange }: PickerStrategyProps) { const pickerMenu = useDropdown(); + const { user } = useAuth(); const handleChange = useCallback( (newValue: LibraryFilterStrategy) => { @@ -61,6 +63,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { @@ -69,6 +72,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { @@ -76,6 +80,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) { handleChange(LibraryFilterStrategy.OWNED)}> { if (activeCst) { setAlias(activeCst.alias); - setConvention(activeCst.convention ?? ''); - setTerm(activeCst.term?.raw ?? ''); - setTextDefinition(activeCst.definition?.text?.raw ?? ''); - setExpression(activeCst.definition?.formal ?? ''); + setConvention(activeCst.convention || ''); + setTerm(activeCst.term_raw || ''); + setTextDefinition(activeCst.definition_raw || ''); + setExpression(activeCst.definition_formal || ''); setTypification(activeCst ? getCstTypificationLabel(activeCst) : 'N/A'); } }, [activeCst, onOpenEdit, schema]); @@ -106,7 +106,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO } const data: ICstCreateData = { insert_after: activeID, - cst_type: activeCst?.cstType ?? CstType.BASE, + cst_type: activeCst?.cst_type ?? CstType.BASE, alias: '', term_raw: '', definition_formal: '', @@ -123,7 +123,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO const data: ICstRenameData = { id: activeID, alias: activeCst?.alias, - cst_type: activeCst.cstType + cst_type: activeCst.cst_type }; onRenameCst(data); } @@ -181,8 +181,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO placeholder='Схемный или предметный термин, обозначающий данное понятие или утверждение' rows={2} value={term} - initialValue={activeCst?.term.raw ?? ''} - resolved={activeCst?.term.resolved ?? ''} + initialValue={activeCst?.term_raw ?? ''} + resolved={activeCst?.term_resolved ?? ''} disabled={!isEnabled} spellCheck onChange={event => setTerm(event.target.value)} @@ -209,8 +209,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO placeholder='Лингвистическая интерпретация формального выражения' rows={4} value={textDefinition} - initialValue={activeCst?.definition.text.raw ?? ''} - resolved={activeCst?.definition.text.resolved ?? ''} + initialValue={activeCst?.definition_raw ?? ''} + resolved={activeCst?.definition_resolved ?? ''} disabled={!isEnabled} spellCheck onChange={event => setTextDefinition(event.target.value)} diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx index 4c9732e1..a9863f2e 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorItems.tsx @@ -215,7 +215,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) { name: 'Термин', id: 'term', - selector: (cst: IConstituenta) => cst.term?.resolved ?? cst.term?.raw ?? '', + selector: (cst: IConstituenta) => cst.term_resolved || cst.term_raw || '', width: '350px', minWidth: '150px', maxWidth: '350px', @@ -225,7 +225,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) { name: 'Формальное определение', id: 'expression', - selector: (cst: IConstituenta) => cst.definition?.formal ?? '', + selector: (cst: IConstituenta) => cst.definition_formal || '', minWidth: '300px', maxWidth: '500px', grow: 2, @@ -237,7 +237,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) id: 'definition', cell: (cst: IConstituenta) => (
- {cst.definition?.text.resolved ?? cst.definition?.text.raw ?? ''} + {cst.definition_resolved || cst.definition_raw || ''}
), minWidth: '200px', diff --git a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx index faa5784c..dd6314c0 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/EditorTermGraph.tsx @@ -31,7 +31,7 @@ const TREE_SIZE_MILESTONE = 50; function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, colors: IColorTheme): string { if (coloringScheme === 'type') { - return getCstClassColor(cst.cstClass, colors); + return getCstClassColor(cst.cst_class, colors); } if (coloringScheme === 'status') { return getCstStatusColor(cst.status, colors); @@ -125,14 +125,14 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra } if (noTemplates) { schema.items.forEach(cst => { - if (cst.isTemplate) { + if (cst.is_template) { graph.foldNode(cst.id); } }); } if (allowedTypes.length < Object.values(CstType).length) { schema.items.forEach(cst => { - if (!allowedTypes.includes(cst.cstType)) { + if (!allowedTypes.includes(cst.cst_type)) { graph.foldNode(cst.id); } }); @@ -173,7 +173,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra result.push({ id: String(node.id), fill: getCstNodeColor(cst, coloringScheme, colors), - label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias + label: cst.term_resolved && !noTerms ? `${cst.alias}: ${cst.term_resolved}` : cst.alias }); } }); @@ -360,7 +360,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
} diff --git a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx index 372552ce..24483384 100644 --- a/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx +++ b/rsconcept/frontend/src/pages/RSFormPage/elements/ViewSideConstituents.tsx @@ -49,7 +49,16 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: const diff = Array.from(aliases).filter(name => !names.includes(name)); if (diff.length > 0) { diff.forEach( - (alias, index) => filtered.push(getMockConstituenta(-index, alias, CstType.BASE, 'Конституента отсутствует'))); + (alias, index) => filtered.push( + getMockConstituenta( + schema.id, + -index, + alias, + CstType.BASE, + 'Конституента отсутствует' + ) + ) + ); } } else if (!activeID) { filtered = schema.items @@ -133,7 +142,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: { name: 'Выражение', id: 'expression', - selector: (cst: IConstituenta) => cst.definition?.formal ?? '', + selector: (cst: IConstituenta) => cst.definition_formal || '', minWidth: '200px', hide: 1600, grow: 2, diff --git a/rsconcept/frontend/src/utils/models.ts b/rsconcept/frontend/src/utils/models.ts index eabd3df4..332cef82 100644 --- a/rsconcept/frontend/src/utils/models.ts +++ b/rsconcept/frontend/src/utils/models.ts @@ -148,33 +148,9 @@ export enum CstClass { TEMPLATE = 'template' } -export interface IConstituenta { - id: number - alias: string - cstType: CstType - convention: string - term: { - raw: string - resolved: string - forms: string[] - } - definition: { - formal: string - text: { - raw: string - resolved: string - } - } - cstClass: CstClass - status: ExpressionStatus - isTemplate: boolean - parse: { - status: ParsingStatus - valueClass: ValueClass - typification: string - syntaxTree: string - args: IFunctionArg[] - } +export interface TermForm { + text: string + tags: string } export interface IConstituentaMeta { @@ -189,6 +165,21 @@ export interface IConstituentaMeta { definition_resolved: string term_raw: string term_resolved: string + term_forms: TermForm[] +} + +export interface IConstituenta +extends IConstituentaMeta { + cst_class: CstClass + status: ExpressionStatus + is_template: boolean + parse: { + status: ParsingStatus + valueClass: ValueClass + typification: string + syntaxTree: string + args: IFunctionArg[] + } } export interface IConstituentaID extends Pick{} @@ -431,35 +422,35 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm { ((cst.parse?.status === ParsingStatus.VERIFIED && cst.parse?.valueClass === ValueClass.INVALID) ? 1 : 0) || 0, 0), count_termin: result.items.reduce( - (sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0), + (sum, cst) => (sum + (cst.term_raw ? 1 : 0) || 0), 0), count_definition: result.items.reduce( - (sum, cst) => (sum + (cst.definition?.text.raw ? 1 : 0) || 0), 0), + (sum, cst) => (sum + (cst.definition_raw ? 1 : 0) || 0), 0), count_convention: result.items.reduce( (sum, cst) => (sum + (cst.convention ? 1 : 0) || 0), 0), count_base: result.items.reduce( - (sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.BASE ? 1 : 0), 0), count_constant: result.items?.reduce( - (sum, cst) => sum + (cst.cstType === CstType.CONSTANT ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.CONSTANT ? 1 : 0), 0), count_structured: result.items?.reduce( - (sum, cst) => sum + (cst.cstType === CstType.STRUCTURED ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.STRUCTURED ? 1 : 0), 0), count_axiom: result.items?.reduce( - (sum, cst) => sum + (cst.cstType === CstType.AXIOM ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.AXIOM ? 1 : 0), 0), count_term: result.items.reduce( - (sum, cst) => sum + (cst.cstType === CstType.TERM ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0), count_function: result.items.reduce( - (sum, cst) => sum + (cst.cstType === CstType.FUNCTION ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0), count_predicate: result.items.reduce( - (sum, cst) => sum + (cst.cstType === CstType.PREDICATE ? 1 : 0), 0), + (sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0), count_theorem: result.items.reduce( - (sum, cst) => sum + (cst.cstType === CstType.THEOREM ? 1 : 0), 0) + (sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0) } result.items.forEach(cst => { cst.status = inferStatus(cst.parse.status, cst.parse.valueClass); - cst.isTemplate = inferTemplate(cst.definition.formal); - cst.cstClass = inferClass(cst.cstType, cst.isTemplate); + cst.is_template = inferTemplate(cst.definition_formal); + cst.cst_class = inferClass(cst.cst_type, cst.is_template); result.graph.addNode(cst.id); - const dependencies = extractGlobals(cst.definition.formal); + const dependencies = extractGlobals(cst.definition_formal); dependencies.forEach(value => { const source = schema.items.find(cst => cst.alias === value) if (source) { @@ -476,15 +467,15 @@ export function matchConstituenta(query: string, target: IConstituenta, mode: Cs return true; } if ((mode === CstMatchMode.ALL || mode === CstMatchMode.TERM) && - target.term.resolved.match(query)) { + target.term_resolved.match(query)) { return true; } if ((mode === CstMatchMode.ALL || mode === CstMatchMode.EXPR) && - target.definition.formal.match(query)) { + target.definition_formal.match(query)) { return true; } if ((mode === CstMatchMode.ALL || mode === CstMatchMode.TEXT)) { - return (target.definition.text.resolved.match(query) || target.convention.match(query)); + return (target.definition_resolved.match(query) || target.convention.match(query)); } return false; } diff --git a/rsconcept/frontend/src/utils/staticUI.ts b/rsconcept/frontend/src/utils/staticUI.ts index 9678e00b..cf50fce1 100644 --- a/rsconcept/frontend/src/utils/staticUI.ts +++ b/rsconcept/frontend/src/utils/staticUI.ts @@ -16,18 +16,18 @@ export interface IDescriptor { } export function getCstDescription(cst: IConstituenta): string { - if (cst.cstType === CstType.STRUCTURED) { + if (cst.cst_type === CstType.STRUCTURED) { return ( - cst.term.resolved || cst.term.raw || - cst.definition.text.resolved || cst.definition.text.raw || + cst.term_resolved || cst.term_raw || + cst.definition_resolved || cst.definition_raw || cst.convention || - cst.definition.formal + cst.definition_formal ); } else { return ( - cst.term.resolved || cst.term.raw || - cst.definition.text.resolved || cst.definition.text.raw || - cst.definition.formal || + cst.term_resolved || cst.term_raw || + cst.definition_resolved || cst.definition_raw || + cst.definition_formal || cst.convention ); } @@ -51,7 +51,7 @@ export function getCstTypePrefix(type: CstType) { } export function getCstExpressionPrefix(cst: IConstituenta): string { - return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':=='); + return cst.alias + (cst.cst_type === CstType.STRUCTURED ? '::=' : ':=='); } export function getRSButtonData(id: TokenID): IDescriptor { @@ -424,7 +424,7 @@ export function createAliasFor(type: CstType, schema: IRSForm): string { return `${prefix}1`; } const index = schema.items.reduce((prev, cst, index) => { - if (cst.cstType !== type) { + if (cst.cst_type !== type) { return prev; } index = Number(cst.alias.slice(1 - cst.alias.length)) + 1; @@ -433,27 +433,23 @@ export function createAliasFor(type: CstType, schema: IRSForm): string { return `${prefix}${index}`; } -export function getMockConstituenta(id: number, alias: string, type: CstType, comment: string): IConstituenta { +export function getMockConstituenta(schema: number, id: number, alias: string, type: CstType, comment: string): IConstituenta { return { id: id, + order: -1, + schema: schema, alias: alias, convention: comment, - cstType: type, - term: { - raw: '', - resolved: '', - forms: [] - }, - definition: { - formal: '', - text: { - raw: '', - resolved: '' - } - }, + cst_type: type, + term_raw: '', + term_resolved: '', + term_forms: [], + definition_formal: '', + definition_raw: '', + definition_resolved: '', status: ExpressionStatus.INCORRECT, - isTemplate: false, - cstClass: CstClass.DERIVED, + is_template: false, + cst_class: CstClass.DERIVED, parse: { status: ParsingStatus.INCORRECT, valueClass: ValueClass.INVALID,