Refactor constituenta API and remove move restriction

This commit is contained in:
IRBorisov 2023-08-29 15:17:16 +03:00
parent 31cbb6f05a
commit 03fb9e3fd0
16 changed files with 235 additions and 242 deletions

View File

@ -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']
})

View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -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']

View File

@ -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<HTMLLabelElement, MouseEvent>): void {
function handleClick(event: React.MouseEvent<HTMLButtonElement, MouseEvent>): void {
event.preventDefault();
if (!disabled) {
inputRef.current?.click();
@ -26,7 +26,12 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full
}
return (
<div className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass} title={tooltip}>
<button
className={'flex gap-2 [&:not(:first-child)]:mt-3 ' + widthClass}
title={tooltip}
disabled={disabled}
onClick={handleClick}
>
<input id={id} type='checkbox' ref={inputRef}
className={`relative peer w-4 h-4 shrink-0 mt-0.5 border rounded-sm appearance-none clr-checkbox ${cursor}`}
required={required}
@ -40,7 +45,6 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full
text={label}
required={required}
htmlFor={id}
onClick={handleLabelClick}
/>}
<svg
className='absolute hidden w-3 h-3 mt-1 ml-0.5 text-white pointer-events-none peer-checked:block'
@ -49,7 +53,7 @@ function Checkbox({ id, required, disabled, tooltip, label, widthClass = 'w-full
>
<path d='M470.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 338.7l233.4-233.3c12.5-12.5 32.8-12.5 45.3 0z' />
</svg>
</div>
</button>
);
}

View File

@ -11,9 +11,9 @@ function InfoConstituenta({ data, ...props }: InfoConstituentaProps) {
<div {...props}>
<h1>Конституента {data.alias}</h1>
<p><b>Типизация: </b>{getCstTypificationLabel(data)}</p>
<p><b>Термин: </b>{data.term.resolved || data.term.raw}</p>
{data.definition.formal && <p><b>Выражение: </b>{data.definition.formal}</p>}
{data.definition.text.resolved && <p><b>Определение: </b>{data.definition.text.resolved}</p>}
<p><b>Термин: </b>{data.term_resolved || data.term_raw}</p>
{data.definition_formal && <p><b>Выражение: </b>{data.definition_formal}</p>}
{data.definition_resolved && <p><b>Определение: </b>{data.definition_resolved}</p>}
{data.convention && <p><b>Конвенция: </b>{data.convention}</p>}
</div>
);

View File

@ -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 = `<b>Термин:</b> ${cst.term.resolved}`;
term.innerHTML = `<b>Термин:</b> ${cst.term_resolved}`;
dom.appendChild(term);
}
if (cst.definition.formal) {
if (cst.definition_formal) {
const expression = document.createElement('p');
expression.innerHTML = `<b>Выражение:</b> ${cst.definition.formal}`;
expression.innerHTML = `<b>Выражение:</b> ${cst.definition_formal}`;
dom.appendChild(expression);
}
if (cst.definition.text.resolved) {
if (cst.definition_resolved) {
const definition = document.createElement('p');
definition.innerHTML = `<b>Определение:</b> ${cst.definition.text.resolved}`;
definition.innerHTML = `<b>Определение:</b> ${cst.definition_resolved}`;
dom.appendChild(definition);
}
if (cst.convention) {

View File

@ -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);

View File

@ -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) {
<Checkbox
value={value === LibraryFilterStrategy.PERSONAL}
label='Личные'
disabled={!user}
widthClass='w-fit px-2'
tooltip='Отображать только подписки и владеемые схемы'
/>
@ -69,6 +72,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
<Checkbox
value={value === LibraryFilterStrategy.SUBSCRIBE}
label='Подписки'
disabled={!user}
widthClass='w-fit px-2'
tooltip='Отображать только подписки'
/>
@ -76,6 +80,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.OWNED)}>
<Checkbox
value={value === LibraryFilterStrategy.OWNED}
disabled={!user}
label='Я - Владелец!'
widthClass='w-fit px-2'
tooltip='Отображать только владеемые схемы'

View File

@ -54,23 +54,23 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
return;
}
setIsModified(
activeCst.term.raw !== term ||
activeCst.definition.text.raw !== textDefinition ||
activeCst.term_raw !== term ||
activeCst.definition_raw !== textDefinition ||
activeCst.convention !== convention ||
activeCst.definition.formal !== expression
activeCst.definition_formal !== expression
);
}, [activeCst, activeCst?.term, activeCst?.definition.formal,
activeCst?.definition.text.raw, activeCst?.convention,
}, [activeCst, activeCst?.term_raw, activeCst?.definition_formal,
activeCst?.definition_raw, activeCst?.convention,
term, textDefinition, expression, convention, setIsModified]);
useLayoutEffect(
() => {
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)}

View File

@ -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) => (
<div style={{ fontSize: 12 }}>
{cst.definition?.text.resolved ?? cst.definition?.text.raw ?? ''}
{cst.definition_resolved || cst.definition_raw || ''}
</div>
),
minWidth: '200px',

View File

@ -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
<div className='relative'>
<InfoConstituenta
data={hoverCst}
className='absolute top-0 left-0 z-50 w-[25rem] min-h-[11rem] overflow-y-auto border h-fit clr-app px-3'
className='absolute top-[2.2rem] left-[2.6rem] z-50 w-[25rem] min-h-[11rem] overflow-y-auto border h-fit clr-app px-3'
/>
</div>}

View File

@ -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,

View File

@ -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<IConstituentaMeta, 'id'>{}
@ -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;
}

View File

@ -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,