mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-13 20:30:36 +03:00
F: Implementing Nominal cst_type
This commit is contained in:
parent
876a98ea8c
commit
c63144e78b
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -216,6 +216,8 @@
|
|||
"неинтерпретируемый",
|
||||
"неитерируемого",
|
||||
"Никанорова",
|
||||
"Номеноид",
|
||||
"Номеноиды",
|
||||
"операционализации",
|
||||
"операционализированных",
|
||||
"Оргтеор",
|
||||
|
|
|
@ -27,7 +27,7 @@ class AssociationSerializer(StrictModelSerializer):
|
|||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
model = Association
|
||||
fields = ('argument', 'operation')
|
||||
fields = ('container', 'associate')
|
||||
|
||||
|
||||
class CstBaseSerializer(StrictModelSerializer):
|
||||
|
@ -121,11 +121,6 @@ class CstCreateSerializer(StrictModelSerializer):
|
|||
)
|
||||
alias = serializers.CharField(max_length=8)
|
||||
cst_type = serializers.ChoiceField(CstType.choices)
|
||||
associations = PKField(
|
||||
many=True,
|
||||
required=False,
|
||||
queryset=Constituenta.objects.all().only('schema_id', 'pk')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
''' serializer metadata. '''
|
||||
|
@ -133,7 +128,7 @@ class CstCreateSerializer(StrictModelSerializer):
|
|||
fields = \
|
||||
'alias', 'cst_type', 'convention', 'crucial', \
|
||||
'term_raw', 'definition_raw', 'definition_formal', \
|
||||
'insert_after', 'term_forms', 'associations'
|
||||
'insert_after', 'term_forms'
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
|
@ -142,12 +137,6 @@ class CstCreateSerializer(StrictModelSerializer):
|
|||
raise serializers.ValidationError({
|
||||
'insert_after': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
associations = attrs.get('associations', [])
|
||||
for assoc in associations:
|
||||
if assoc.schema_id != schema.pk:
|
||||
raise serializers.ValidationError({
|
||||
'associations': msg.constituentaNotInRSform(schema.title)
|
||||
})
|
||||
return attrs
|
||||
|
||||
|
||||
|
@ -322,6 +311,8 @@ class RSFormParseSerializer(StrictModelSerializer):
|
|||
def _parse_data(self, data: dict) -> dict:
|
||||
parse = PyConceptAdapter(data).parse()
|
||||
for cst_data in data['items']:
|
||||
if cst_data['cst_type'] == CstType.NOMINAL:
|
||||
continue
|
||||
cst_data['parse'] = next(
|
||||
cst['parse'] for cst in parse['items']
|
||||
if cst['id'] == cst_data['id']
|
||||
|
|
|
@ -6,7 +6,7 @@ import pyconcept
|
|||
|
||||
from shared import messages as msg
|
||||
|
||||
from ..models import Constituenta
|
||||
from ..models import Constituenta, CstType
|
||||
|
||||
|
||||
class PyConceptAdapter:
|
||||
|
@ -34,7 +34,7 @@ class PyConceptAdapter:
|
|||
result: dict = {
|
||||
'items': []
|
||||
}
|
||||
items = Constituenta.objects.filter(schema_id=schemaID).order_by('order')
|
||||
items = Constituenta.objects.filter(schema_id=schemaID).exclude(cst_type=CstType.NOMINAL).order_by('order')
|
||||
for cst in items:
|
||||
result['items'].append({
|
||||
'entityUID': cst.pk,
|
||||
|
@ -51,6 +51,8 @@ class PyConceptAdapter:
|
|||
'items': []
|
||||
}
|
||||
for cst in data['items']:
|
||||
if cst['cst_type'] == CstType.NOMINAL:
|
||||
continue
|
||||
result['items'].append({
|
||||
'entityUID': cst['id'],
|
||||
'cstType': cst['cst_type'],
|
||||
|
|
|
@ -12,7 +12,7 @@ class TestConstituentaAPI(EndpointTester):
|
|||
self.owned_id = self.owned.model.pk
|
||||
self.unowned = RSForm.create(title='Test2', alias='T2')
|
||||
self.unowned_id = self.unowned.model.pk
|
||||
self.cst1 = Constituenta.objects.create(
|
||||
self.x1 = Constituenta.objects.create(
|
||||
alias='X1',
|
||||
cst_type=CstType.BASE,
|
||||
schema=self.owned.model,
|
||||
|
@ -21,119 +21,175 @@ class TestConstituentaAPI(EndpointTester):
|
|||
term_raw='Test1',
|
||||
term_resolved='Test1R',
|
||||
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}])
|
||||
self.cst2 = Constituenta.objects.create(
|
||||
self.x2 = Constituenta.objects.create(
|
||||
alias='X2',
|
||||
cst_type=CstType.BASE,
|
||||
schema=self.unowned.model,
|
||||
order=0,
|
||||
schema=self.owned.model,
|
||||
order=1,
|
||||
convention='Test1',
|
||||
term_raw='Test2',
|
||||
term_resolved='Test2R'
|
||||
)
|
||||
self.cst3 = Constituenta.objects.create(
|
||||
self.x3 = Constituenta.objects.create(
|
||||
alias='X3',
|
||||
schema=self.owned.model,
|
||||
order=1,
|
||||
order=2,
|
||||
term_raw='Test3',
|
||||
term_resolved='Test3',
|
||||
definition_raw='Test1',
|
||||
definition_resolved='Test2'
|
||||
)
|
||||
self.invalid_cst = self.cst3.pk + 1337
|
||||
self.unowned_cst = self.unowned.insert_last(alias='X1', cst_type=CstType.BASE)
|
||||
self.invalid_cst = self.x3.pk + 1337
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
|
||||
@decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
|
||||
def test_partial_update(self):
|
||||
data = {'target': self.cst1.pk, 'item_data': {'convention': 'tt'}}
|
||||
self.executeForbidden(data, schema=self.unowned_id)
|
||||
data = {'target': self.x1.pk, 'item_data': {'convention': 'tt'}}
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
|
||||
self.logout()
|
||||
self.executeForbidden(data, schema=self.owned_id)
|
||||
self.executeForbidden(data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
self.executeOK(data)
|
||||
self.cst1.refresh_from_db()
|
||||
self.assertEqual(self.cst1.convention, 'tt')
|
||||
self.x1.refresh_from_db()
|
||||
self.assertEqual(self.x1.convention, 'tt')
|
||||
|
||||
self.executeOK(data)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
|
||||
@decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
|
||||
def test_partial_update_rename(self):
|
||||
data = {'target': self.cst1.pk, 'item_data': {'alias': self.cst3.alias}}
|
||||
self.executeBadData(data, schema=self.owned_id)
|
||||
data = {'target': self.x1.pk, 'item_data': {'alias': self.x3.alias}}
|
||||
self.executeBadData(data, item=self.owned_id)
|
||||
|
||||
d1 = self.owned.insert_last(
|
||||
alias='D1',
|
||||
term_raw='@{X1|plur}',
|
||||
definition_formal='X1'
|
||||
)
|
||||
self.assertEqual(self.cst1.order, 0)
|
||||
self.assertEqual(self.cst1.alias, 'X1')
|
||||
self.assertEqual(self.cst1.cst_type, CstType.BASE)
|
||||
self.assertEqual(self.x1.order, 0)
|
||||
self.assertEqual(self.x1.alias, 'X1')
|
||||
self.assertEqual(self.x1.cst_type, CstType.BASE)
|
||||
|
||||
data = {'target': self.cst1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}}
|
||||
self.executeOK(data, schema=self.owned_id)
|
||||
data = {'target': self.x1.pk, 'item_data': {'alias': 'D2', 'cst_type': CstType.TERM}}
|
||||
self.executeOK(data, item=self.owned_id)
|
||||
d1.refresh_from_db()
|
||||
self.cst1.refresh_from_db()
|
||||
self.x1.refresh_from_db()
|
||||
self.assertEqual(d1.term_resolved, '')
|
||||
self.assertEqual(d1.term_raw, '@{D2|plur}')
|
||||
self.assertEqual(self.cst1.order, 0)
|
||||
self.assertEqual(self.cst1.alias, 'D2')
|
||||
self.assertEqual(self.cst1.cst_type, CstType.TERM)
|
||||
self.assertEqual(self.x1.order, 0)
|
||||
self.assertEqual(self.x1.alias, 'D2')
|
||||
self.assertEqual(self.x1.cst_type, CstType.TERM)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
|
||||
@decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
|
||||
def test_update_resolved_no_refs(self):
|
||||
data = {
|
||||
'target': self.cst3.pk,
|
||||
'target': self.x3.pk,
|
||||
'item_data': {
|
||||
'term_raw': 'New term',
|
||||
'definition_raw': 'New def'
|
||||
}
|
||||
}
|
||||
self.executeOK(data, schema=self.owned_id)
|
||||
self.cst3.refresh_from_db()
|
||||
self.assertEqual(self.cst3.term_resolved, 'New term')
|
||||
self.assertEqual(self.cst3.definition_resolved, 'New def')
|
||||
self.executeOK(data, item=self.owned_id)
|
||||
self.x3.refresh_from_db()
|
||||
self.assertEqual(self.x3.term_resolved, 'New term')
|
||||
self.assertEqual(self.x3.definition_resolved, 'New def')
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
|
||||
@decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
|
||||
def test_update_resolved_refs(self):
|
||||
data = {
|
||||
'target': self.cst3.pk,
|
||||
'target': self.x3.pk,
|
||||
'item_data': {
|
||||
'term_raw': '@{X1|nomn,sing}',
|
||||
'definition_raw': '@{X1|nomn,sing} @{X1|sing,datv}'
|
||||
}
|
||||
}
|
||||
self.executeOK(data, schema=self.owned_id)
|
||||
self.cst3.refresh_from_db()
|
||||
self.assertEqual(self.cst3.term_resolved, self.cst1.term_resolved)
|
||||
self.assertEqual(self.cst3.definition_resolved, f'{self.cst1.term_resolved} form1')
|
||||
self.executeOK(data, item=self.owned_id)
|
||||
self.x3.refresh_from_db()
|
||||
self.assertEqual(self.x3.term_resolved, self.x1.term_resolved)
|
||||
self.assertEqual(self.x3.definition_resolved, f'{self.x1.term_resolved} form1')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/update-cst', method='patch')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/update-cst', method='patch')
|
||||
def test_update_term_forms(self):
|
||||
data = {
|
||||
'target': self.cst3.pk,
|
||||
'target': self.x3.pk,
|
||||
'item_data': {
|
||||
'definition_raw': '@{X3|sing,datv}',
|
||||
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}]
|
||||
}
|
||||
}
|
||||
self.executeOK(data, schema=self.owned_id)
|
||||
self.cst3.refresh_from_db()
|
||||
self.assertEqual(self.cst3.definition_resolved, 'form1')
|
||||
self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms'])
|
||||
self.executeOK(data, item=self.owned_id)
|
||||
self.x3.refresh_from_db()
|
||||
self.assertEqual(self.x3.definition_resolved, 'form1')
|
||||
self.assertEqual(self.x3.term_forms, data['item_data']['term_forms'])
|
||||
|
||||
@decl_endpoint('/api/rsforms/{schema}/update-crucial', method='patch')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/update-crucial', method='patch')
|
||||
def test_update_crucial(self):
|
||||
data = {'target': [self.cst1.pk], 'value': True}
|
||||
self.executeForbidden(data, schema=self.unowned_id)
|
||||
data = {'target': [self.x1.pk], 'value': True}
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
|
||||
self.logout()
|
||||
self.executeForbidden(data, schema=self.owned_id)
|
||||
self.executeForbidden(data, item=self.owned_id)
|
||||
|
||||
self.login()
|
||||
self.executeOK(data, schema=self.owned_id)
|
||||
self.cst1.refresh_from_db()
|
||||
self.assertEqual(self.cst1.crucial, True)
|
||||
self.executeOK(data, item=self.owned_id)
|
||||
self.x1.refresh_from_db()
|
||||
self.assertEqual(self.x1.crucial, True)
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
|
||||
def test_create_constituenta(self):
|
||||
data = {'alias': 'X4', 'cst_type': CstType.BASE}
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
|
||||
data = {'alias': 'X4'}
|
||||
self.executeBadData(item=self.owned_id)
|
||||
self.executeBadData(data)
|
||||
|
||||
data['cst_type'] = 'invalid'
|
||||
self.executeBadData(data)
|
||||
|
||||
data = {
|
||||
'alias': 'X4',
|
||||
'cst_type': CstType.BASE,
|
||||
'term_raw': 'test',
|
||||
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}],
|
||||
'definition_formal': 'invalid',
|
||||
'crucial': True
|
||||
}
|
||||
response = self.executeCreated(data)
|
||||
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
||||
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||
self.assertEqual(x4.order, 3)
|
||||
self.assertEqual(x4.term_raw, data['term_raw'])
|
||||
self.assertEqual(x4.term_forms, data['term_forms'])
|
||||
self.assertEqual(x4.definition_formal, data['definition_formal'])
|
||||
self.assertEqual(x4.crucial, data['crucial'])
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
|
||||
def test_create_constituenta_after(self):
|
||||
self.set_params(item=self.owned_id)
|
||||
|
||||
data = {'alias': 'X4', 'cst_type': CstType.BASE, 'insert_after': self.invalid_cst}
|
||||
self.executeBadData(data)
|
||||
|
||||
data['insert_after'] = self.unowned_cst.pk
|
||||
self.executeBadData(data)
|
||||
|
||||
data = {
|
||||
'alias': 'X4',
|
||||
'cst_type': CstType.BASE,
|
||||
'insert_after': self.x2.pk,
|
||||
}
|
||||
response = self.executeCreated(data)
|
||||
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
||||
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||
self.x3.refresh_from_db()
|
||||
self.assertEqual(x4.order, 2)
|
||||
self.assertEqual(self.x3.order, 3)
|
||||
|
|
|
@ -200,54 +200,6 @@ class TestRSFormViewset(EndpointTester):
|
|||
self.assertIn('document.json', zipped_file.namelist())
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/create-cst', method='post')
|
||||
def test_create_constituenta(self):
|
||||
data = {'alias': 'X3', 'cst_type': CstType.BASE}
|
||||
self.executeForbidden(data, item=self.unowned_id)
|
||||
|
||||
data = {'alias': 'X3'}
|
||||
self.owned.insert_last('X1')
|
||||
x2 = self.owned.insert_last('X2')
|
||||
self.executeBadData(item=self.owned_id)
|
||||
self.executeBadData(data)
|
||||
|
||||
data['cst_type'] = 'invalid'
|
||||
self.executeBadData(data)
|
||||
|
||||
data['cst_type'] = CstType.BASE
|
||||
response = self.executeCreated(data)
|
||||
self.assertEqual(response.data['new_cst']['alias'], 'X3')
|
||||
x3 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||
self.assertEqual(x3.order, 2)
|
||||
|
||||
data = {
|
||||
'alias': 'X4',
|
||||
'cst_type': CstType.BASE,
|
||||
'insert_after': x2.pk,
|
||||
'term_raw': 'test',
|
||||
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}],
|
||||
'definition_formal': 'invalid',
|
||||
'crucial': True
|
||||
}
|
||||
response = self.executeCreated(data)
|
||||
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
||||
x4 = Constituenta.objects.get(alias=response.data['new_cst']['alias'])
|
||||
self.assertEqual(x4.order, 2)
|
||||
self.assertEqual(x4.term_raw, data['term_raw'])
|
||||
self.assertEqual(x4.term_forms, data['term_forms'])
|
||||
self.assertEqual(x4.definition_formal, data['definition_formal'])
|
||||
self.assertEqual(x4.crucial, data['crucial'])
|
||||
|
||||
data = {
|
||||
'alias': 'X5',
|
||||
'cst_type': CstType.BASE,
|
||||
'insert_after': None,
|
||||
'term_raw': 'test5'
|
||||
}
|
||||
response = self.executeCreated(data)
|
||||
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
|
||||
def test_substitute_multiple(self):
|
||||
self.set_params(item=self.owned_id)
|
||||
|
|
|
@ -12,7 +12,7 @@ export { BiX as IconRemove } from 'react-icons/bi';
|
|||
export { BiTrash as IconDestroy } from 'react-icons/bi';
|
||||
export { BiReset as IconReset } from 'react-icons/bi';
|
||||
export { TbArrowsDiagonal2 as IconResize } from 'react-icons/tb';
|
||||
export { FiEdit as IconEdit } from 'react-icons/fi';
|
||||
export { FiEdit as IconEdit } from 'react-icons/fi';
|
||||
export { AiOutlineEdit as IconEdit2 } from 'react-icons/ai';
|
||||
export { BiSearchAlt2 as IconSearch } from 'react-icons/bi';
|
||||
export { BiDownload as IconDownload } from 'react-icons/bi';
|
||||
|
@ -87,6 +87,7 @@ export { MdOutlineSelectAll as IconConceptBlock } from 'react-icons/md';
|
|||
export { TbHexagon as IconRSForm } from 'react-icons/tb';
|
||||
export { TbAssembly as IconRSFormOwned } from 'react-icons/tb';
|
||||
export { TbBallFootball as IconRSFormImported } from 'react-icons/tb';
|
||||
export { TbHexagonLetterN as IconCstNominal } from 'react-icons/tb';
|
||||
export { TbHexagonLetterX as IconCstBaseSet } from 'react-icons/tb';
|
||||
export { TbHexagonLetterC as IconCstConstSet } from 'react-icons/tb';
|
||||
export { TbHexagonLetterS as IconCstStructured } from 'react-icons/tb';
|
||||
|
@ -125,7 +126,7 @@ export { RiOpenSourceLine as IconPublic } from 'react-icons/ri';
|
|||
export { RiShieldLine as IconProtected } from 'react-icons/ri';
|
||||
export { RiShieldKeyholeLine as IconPrivate } from 'react-icons/ri';
|
||||
export { BiBug as IconStatusError } from 'react-icons/bi';
|
||||
export { BiCheckCircle as IconStatusOK } from 'react-icons/bi';
|
||||
export { LuThumbsUp as IconStatusOK } from 'react-icons/lu';
|
||||
export { BiHelpCircle as IconStatusUnknown } from 'react-icons/bi';
|
||||
export { BiStopCircle as IconStatusIncalculable } from 'react-icons/bi';
|
||||
export { BiPauseCircle as IconStatusProperty } from 'react-icons/bi';
|
||||
|
|
|
@ -89,7 +89,9 @@ export function varSchemaGraph(schema: IRSForm): string {
|
|||
/** Generates a prompt for a schema type graph variable. */
|
||||
export function varSchemaTypeGraph(schema: IRSForm): string {
|
||||
const graph = new TypificationGraph();
|
||||
schema.items.forEach(item => graph.addConstituenta(item.alias, item.parse.typification, item.parse.args));
|
||||
schema.items.forEach(item => {
|
||||
if (item.parse) graph.addConstituenta(item.alias, item.parse.typification, item.parse.args);
|
||||
});
|
||||
|
||||
let result = `Название концептуальной схемы: ${schema.title}\n`;
|
||||
result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
|
||||
|
@ -154,6 +156,6 @@ export function varSyntaxTree(cst: IConstituenta): string {
|
|||
let result = `Конституента: ${cst.alias}\n`;
|
||||
result += `Формальное выражение: ${cst.definition_formal}\n`;
|
||||
result += `Дерево синтаксического разбора:\n`;
|
||||
result += JSON.stringify(cst.parse.syntaxTree, null, PARAMETER.indentJSON);
|
||||
result += cst.parse ? JSON.stringify(cst.parse.syntaxTree, null, PARAMETER.indentJSON) : 'не определено';
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
IconCstBaseSet,
|
||||
IconCstConstSet,
|
||||
IconCstFunction,
|
||||
IconCstNominal,
|
||||
IconCstPredicate,
|
||||
IconCstStructured,
|
||||
IconCstTerm,
|
||||
|
@ -118,6 +119,11 @@ export function HelpThesaurus() {
|
|||
|
||||
<ul>
|
||||
<b>Типы конституент</b>
|
||||
<li>
|
||||
<IconCstNominal size='1rem' className='inline-icon' />
|
||||
{'\u2009'}Номеноид (N#) – предметная сущность, не имеющая четкого определения, используемая для ассоциативной
|
||||
группировки конституент и предварительной фиксации содержательных отношений.
|
||||
</li>
|
||||
<li>
|
||||
<IconCstBaseSet size='1rem' className='inline-icon' />
|
||||
{'\u2009'}Базисное множество (X#) – неопределяемое понятие, представленное множеством различимых элементов.
|
||||
|
|
|
@ -60,7 +60,7 @@ export const schemaLibraryItemType = z.enum(Object.values(LibraryItemType) as [L
|
|||
export const schemaAccessPolicy = z.enum(Object.values(AccessPolicy) as [AccessPolicy, ...AccessPolicy[]]);
|
||||
|
||||
export const schemaLibraryItem = z.strictObject({
|
||||
id: z.coerce.number(),
|
||||
id: z.number(),
|
||||
item_type: schemaLibraryItemType,
|
||||
|
||||
alias: z.string().nonempty(),
|
||||
|
@ -72,9 +72,9 @@ export const schemaLibraryItem = z.strictObject({
|
|||
location: z.string(),
|
||||
access_policy: schemaAccessPolicy,
|
||||
|
||||
time_create: z.string().datetime({ offset: true }),
|
||||
time_update: z.string().datetime({ offset: true }),
|
||||
owner: z.coerce.number().nullable()
|
||||
time_create: z.iso.datetime({ offset: true }),
|
||||
time_update: z.iso.datetime({ offset: true }),
|
||||
owner: z.number().nullable()
|
||||
});
|
||||
|
||||
export const schemaLibraryItemArray = z.array(schemaLibraryItem);
|
||||
|
@ -126,10 +126,10 @@ export const schemaUpdateLibraryItem = schemaInputLibraryItem
|
|||
});
|
||||
|
||||
export const schemaVersionInfo = z.strictObject({
|
||||
id: z.coerce.number(),
|
||||
id: z.number(),
|
||||
version: z.string(),
|
||||
description: z.string(),
|
||||
time_create: z.string().datetime({ offset: true })
|
||||
time_create: z.iso.datetime({ offset: true })
|
||||
});
|
||||
|
||||
const schemaVersionInput = z.strictObject({
|
||||
|
|
|
@ -175,7 +175,11 @@ export class SubstitutionValidator {
|
|||
if (!original || !substitution) {
|
||||
return this.reportError(SubstitutionErrorType.invalidIDs, []);
|
||||
}
|
||||
if (original.parse.status === ParsingStatus.INCORRECT || substitution.parse.status === ParsingStatus.INCORRECT) {
|
||||
if (
|
||||
substitution.parse &&
|
||||
original.parse &&
|
||||
(original.parse.status === ParsingStatus.INCORRECT || substitution.parse.status === ParsingStatus.INCORRECT)
|
||||
) {
|
||||
return this.reportError(SubstitutionErrorType.incorrectCst, [substitution.alias, original.alias]);
|
||||
}
|
||||
switch (substitution.cst_type) {
|
||||
|
@ -247,6 +251,9 @@ export class SubstitutionValidator {
|
|||
continue;
|
||||
}
|
||||
graph.addNode(cst.id);
|
||||
if (!cst.parse) {
|
||||
continue;
|
||||
}
|
||||
const parents = extractGlobals(cst.parse.typification);
|
||||
for (const arg of cst.parse.args) {
|
||||
for (const alias of extractGlobals(arg.typification)) {
|
||||
|
@ -299,6 +306,13 @@ export class SubstitutionValidator {
|
|||
}
|
||||
}
|
||||
|
||||
if (!!original.parse !== !!substitution.parse) {
|
||||
return this.reportError(SubstitutionErrorType.unequalTypification, [substitution.alias, original.alias]);
|
||||
}
|
||||
if (!original.parse || !substitution.parse) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const originalType = applyTypificationMapping(
|
||||
applyAliasMapping(original.parse.typification, baseMappings.get(original.schema)!),
|
||||
typeMappings
|
||||
|
@ -365,7 +379,7 @@ export class SubstitutionValidator {
|
|||
|
||||
const substitution = this.cstByID.get(item.substitution)!;
|
||||
let substitutionText = '';
|
||||
if (substitution.cst_type === original.cst_type) {
|
||||
if (substitution.cst_type === original.cst_type || !substitution.parse) {
|
||||
substitutionText = baseMappings.get(substitution.schema)![substitution.alias];
|
||||
} else {
|
||||
substitutionText = applyAliasMapping(substitution.parse.typification, baseMappings.get(substitution.schema)!);
|
||||
|
|
|
@ -181,11 +181,13 @@ export function ToolbarSchema({
|
|||
}
|
||||
|
||||
function handleShowTypeGraph() {
|
||||
const typeInfo = schema.items.map(item => ({
|
||||
alias: item.alias,
|
||||
result: item.parse.typification,
|
||||
args: item.parse.args
|
||||
}));
|
||||
const typeInfo = schema.items
|
||||
.filter(item => !!item.parse)
|
||||
.map(item => ({
|
||||
alias: item.alias,
|
||||
result: item.parse!.typification,
|
||||
args: item.parse!.args
|
||||
}));
|
||||
showTypeGraph({ items: typeInfo });
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
import { Graph } from '@/models/graph';
|
||||
import { type RO } from '@/utils/meta';
|
||||
|
||||
import { type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform';
|
||||
import { ExpressionStatus, type IConstituenta, type IRSForm, type IRSFormStats } from '../models/rsform';
|
||||
import { inferClass, inferStatus, inferTemplate, isBaseSet, isFunctional } from '../models/rsform-api';
|
||||
import { extractGlobals, isSimpleExpression, splitTemplateDefinition } from '../models/rslang-api';
|
||||
|
||||
|
@ -25,7 +25,7 @@ export class RSFormLoader {
|
|||
private cstByID = new Map<number, IConstituenta>();
|
||||
|
||||
constructor(input: RO<IRSFormDTO>) {
|
||||
this.schema = structuredClone(input) as IRSForm;
|
||||
this.schema = structuredClone(input) as unknown as IRSForm;
|
||||
this.schema.version = input.version ?? 'latest';
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ export class RSFormLoader {
|
|||
order.forEach(cstID => {
|
||||
const cst = this.cstByID.get(cstID)!;
|
||||
cst.schema = this.schema.id;
|
||||
cst.status = inferStatus(cst.parse.status, cst.parse.valueClass);
|
||||
cst.status = cst.parse ? inferStatus(cst.parse.status, cst.parse.valueClass) : ExpressionStatus.UNKNOWN;
|
||||
cst.is_template = inferTemplate(cst.definition_formal);
|
||||
cst.cst_class = inferClass(cst.cst_type, cst.is_template);
|
||||
cst.spawn = [];
|
||||
|
@ -184,11 +184,20 @@ export class RSFormLoader {
|
|||
return {
|
||||
count_all: items.length,
|
||||
count_crucial: items.reduce((sum, cst) => sum + (cst.crucial ? 1 : 0), 0),
|
||||
count_errors: items.reduce((sum, cst) => sum + (cst.parse.status === ParsingStatus.INCORRECT ? 1 : 0), 0),
|
||||
count_property: items.reduce((sum, cst) => sum + (cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0), 0),
|
||||
count_errors: items.reduce(
|
||||
(sum, cst) => sum + (cst.parse && cst.parse.status === ParsingStatus.INCORRECT ? 1 : 0),
|
||||
0
|
||||
),
|
||||
count_property: items.reduce(
|
||||
(sum, cst) => sum + (cst.parse && cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0),
|
||||
0
|
||||
),
|
||||
count_incalculable: items.reduce(
|
||||
(sum, cst) =>
|
||||
sum + (cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID ? 1 : 0),
|
||||
sum +
|
||||
(cst.parse && cst.parse.status === ParsingStatus.VERIFIED && cst.parse.valueClass === ValueClass.INVALID
|
||||
? 1
|
||||
: 0),
|
||||
0
|
||||
),
|
||||
count_inherited: items.reduce((sum, cst) => sum + (cst.is_inherited ? 1 : 0), 0),
|
||||
|
@ -204,7 +213,8 @@ export class RSFormLoader {
|
|||
count_term: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.TERM ? 1 : 0), 0),
|
||||
count_function: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.FUNCTION ? 1 : 0), 0),
|
||||
count_predicate: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.PREDICATE ? 1 : 0), 0),
|
||||
count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0)
|
||||
count_theorem: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.THEOREM ? 1 : 0), 0),
|
||||
count_nominal: items.reduce((sum, cst) => sum + (cst.cst_type === CstType.NOMINAL ? 1 : 0), 0)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import { errorMsg } from '@/utils/labels';
|
|||
|
||||
/** Represents {@link IConstituenta} type. */
|
||||
export const CstType = {
|
||||
NOMINAL: 'nominal',
|
||||
BASE: 'basic',
|
||||
STRUCTURED: 'structure',
|
||||
TERM: 'term',
|
||||
|
@ -276,7 +277,7 @@ export const schemaTokenID = z.enum(TokenID);
|
|||
export const schemaRSErrorType = z.enum(RSErrorType);
|
||||
|
||||
export const schemaConstituentaBasics = z.strictObject({
|
||||
id: z.coerce.number(),
|
||||
id: z.number(),
|
||||
alias: z.string().nonempty(errorMsg.requiredField),
|
||||
convention: z.string(),
|
||||
crucial: z.boolean(),
|
||||
|
@ -290,31 +291,34 @@ export const schemaConstituentaBasics = z.strictObject({
|
|||
});
|
||||
|
||||
export const schemaConstituenta = schemaConstituentaBasics.extend({
|
||||
parse: z.strictObject({
|
||||
status: schemaParsingStatus,
|
||||
valueClass: schemaValueClass,
|
||||
typification: z.string(),
|
||||
syntaxTree: z.string(),
|
||||
args: z.array(z.strictObject({ alias: z.string(), typification: z.string() }))
|
||||
})
|
||||
parse: z
|
||||
.strictObject({
|
||||
status: schemaParsingStatus,
|
||||
valueClass: schemaValueClass,
|
||||
typification: z.string(),
|
||||
syntaxTree: z.string(),
|
||||
args: z.array(z.strictObject({ alias: z.string(), typification: z.string() }))
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
export const schemaRSForm = schemaLibraryItem.extend({
|
||||
editors: z.array(z.coerce.number()),
|
||||
editors: z.array(z.number()),
|
||||
|
||||
version: z.coerce.number().optional(),
|
||||
version: z.number().optional(),
|
||||
versions: z.array(schemaVersionInfo),
|
||||
|
||||
items: z.array(schemaConstituenta),
|
||||
association: z.array(z.strictObject({ container: z.number(), associate: z.number() })),
|
||||
inheritance: z.array(
|
||||
z.strictObject({
|
||||
child: z.coerce.number(),
|
||||
child_source: z.coerce.number(),
|
||||
parent: z.coerce.number(),
|
||||
parent_source: z.coerce.number()
|
||||
child: z.number(),
|
||||
child_source: z.number(),
|
||||
parent: z.number(),
|
||||
parent_source: z.number()
|
||||
})
|
||||
),
|
||||
oss: z.array(z.strictObject({ id: z.coerce.number(), alias: z.string() }))
|
||||
oss: z.array(z.strictObject({ id: z.number(), alias: z.string() }))
|
||||
});
|
||||
|
||||
export const schemaVersionCreatedResponse = z.strictObject({
|
||||
|
|
|
@ -196,6 +196,7 @@ export function colorFgCstStatus(status: ExpressionStatus): string {
|
|||
export function colorBgCstClass(cstClass: CstClass): string {
|
||||
// prettier-ignore
|
||||
switch (cstClass) {
|
||||
case CstClass.NOMINAL: return APP_COLORS.bgOrange;
|
||||
case CstClass.BASIC: return APP_COLORS.bgGreen;
|
||||
case CstClass.DERIVED: return APP_COLORS.bgBlue;
|
||||
case CstClass.STATEMENT: return APP_COLORS.bgRed;
|
||||
|
@ -203,6 +204,15 @@ export function colorBgCstClass(cstClass: CstClass): string {
|
|||
}
|
||||
}
|
||||
|
||||
/** Determines background color for {@link IConstituenta} badge. */
|
||||
export function colorBgBadge(item: IConstituenta) {
|
||||
switch (item.cst_class) {
|
||||
case CstClass.BASIC:
|
||||
return 'bg-accent-green25';
|
||||
}
|
||||
return 'bg-input';
|
||||
}
|
||||
|
||||
/** Determines background color for {@link IConstituenta} depending on its parent schema index. */
|
||||
export function colorBgSchemas(schema_index: number): string {
|
||||
if (schema_index === 0) {
|
||||
|
|
|
@ -4,8 +4,8 @@ import clsx from 'clsx';
|
|||
|
||||
import { globalIDs } from '@/utils/constants';
|
||||
|
||||
import { colorFgCstStatus } from '../colors';
|
||||
import { CstClass, type IConstituenta } from '../models/rsform';
|
||||
import { colorBgBadge, colorFgCstStatus } from '../colors';
|
||||
import { type IConstituenta } from '../models/rsform';
|
||||
import { useCstTooltipStore } from '../stores/cst-tooltip';
|
||||
|
||||
interface BadgeConstituentaProps {
|
||||
|
@ -29,7 +29,7 @@ export function BadgeConstituenta({ value, prefixID }: BadgeConstituentaProps) {
|
|||
'cc-badge-constituenta',
|
||||
value.is_inherited && 'border-dashed',
|
||||
value.crucial && 'cc-badge-inner-shadow',
|
||||
value.cst_class === CstClass.BASIC ? 'bg-accent-green25' : 'bg-input'
|
||||
colorBgBadge(value)
|
||||
)}
|
||||
style={{
|
||||
borderColor: colorFgCstStatus(value.status),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { type DomIconProps } from '@/components/icons';
|
||||
import { type DomIconProps, IconCstNominal } from '@/components/icons';
|
||||
import {
|
||||
IconCstAxiom,
|
||||
IconCstBaseSet,
|
||||
|
@ -15,6 +15,8 @@ import { CstType } from '../backend/types';
|
|||
/** Icon for constituenta type. */
|
||||
export function IconCstType({ value, size = '1.25rem', className }: DomIconProps<CstType>) {
|
||||
switch (value) {
|
||||
case CstType.NOMINAL:
|
||||
return <IconCstNominal size={size} className={className ?? 'text-primary'} />;
|
||||
case CstType.BASE:
|
||||
return <IconCstBaseSet size={size} className={className ?? 'text-constructive'} />;
|
||||
case CstType.CONSTANT:
|
||||
|
|
|
@ -23,10 +23,12 @@ export function InfoConstituenta({ data, className, ...restProps }: InfoConstitu
|
|||
{data.term_resolved || data.term_raw}
|
||||
</p>
|
||||
) : null}
|
||||
<p className='break-all'>
|
||||
<b>Типизация: </b>
|
||||
<span className='font-math'>{labelCstTypification(data)}</span>
|
||||
</p>
|
||||
{data.parse ? (
|
||||
<p className='break-all'>
|
||||
<b>Типизация: </b>
|
||||
<span className='font-math'>{labelCstTypification(data)}</span>
|
||||
</p>
|
||||
) : null}
|
||||
{data.definition_formal ? (
|
||||
<p className='break-all'>
|
||||
<b>Выражение: </b>
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IconCstBaseSet,
|
||||
IconCstConstSet,
|
||||
IconCstFunction,
|
||||
IconCstNominal,
|
||||
IconCstPredicate,
|
||||
IconCstStructured,
|
||||
IconCstTerm,
|
||||
|
@ -14,7 +15,6 @@ import {
|
|||
IconPredecessor,
|
||||
IconStatusError,
|
||||
IconStatusIncalculable,
|
||||
IconStatusOK,
|
||||
IconStatusProperty,
|
||||
IconTerminology
|
||||
} from '@/components/icons';
|
||||
|
@ -48,18 +48,11 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
|
|||
titleHtml='Наследованные'
|
||||
/>
|
||||
|
||||
<ValueStats
|
||||
id='count_ok'
|
||||
title='Корректные'
|
||||
className='col-start-1'
|
||||
icon={<IconStatusOK size='1.25rem' />}
|
||||
value={stats.count_all - stats.count_errors - stats.count_property - stats.count_incalculable}
|
||||
/>
|
||||
<ValueStats
|
||||
id='count_property'
|
||||
title='Неразмерные'
|
||||
icon={<IconStatusProperty size='1.25rem' />}
|
||||
value={stats.count_errors}
|
||||
value={stats.count_property}
|
||||
/>
|
||||
<ValueStats
|
||||
id='count_incalculable'
|
||||
|
@ -73,6 +66,12 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
|
|||
icon={<IconStatusError size='1.25rem' className={stats.count_errors > 0 ? 'text-destructive' : undefined} />}
|
||||
value={stats.count_errors}
|
||||
/>
|
||||
<ValueStats
|
||||
id='count_nominal'
|
||||
title='Номеноиды'
|
||||
icon={<IconCstNominal size='1.25rem' className={stats.count_nominal > 0 ? 'text-destructive' : undefined} />}
|
||||
value={stats.count_nominal}
|
||||
/>
|
||||
|
||||
<ValueStats
|
||||
id='count_base'
|
||||
|
|
|
@ -75,7 +75,7 @@ function describeCstNode(cst: IConstituenta) {
|
|||
? cst.convention
|
||||
: cst.definition_resolved || cst.definition_formal || cst.convention;
|
||||
const typification = labelCstTypification(cst);
|
||||
return `${cst.alias}: ${cst.term_resolved}</br><b>Типизация:</b> ${typification}</br><b>Содержание:</b> ${
|
||||
contents ? contents : 'отсутствует'
|
||||
}`;
|
||||
return `${cst.alias}: ${cst.term_resolved}</br>${
|
||||
cst.parse ? `<b>Типизация:</b> ${typification}</br>` : ''
|
||||
}<b>Содержание:</b> ${contents ? contents : 'отсутствует'}`;
|
||||
}
|
||||
|
|
|
@ -13,9 +13,9 @@ import { CstType, type ICreateConstituentaDTO } from '../../backend/types';
|
|||
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||
import { RSInput } from '../../components/rs-input';
|
||||
import { SelectCstType } from '../../components/select-cst-type';
|
||||
import { getRSDefinitionPlaceholder } from '../../labels';
|
||||
import { getRSDefinitionPlaceholder, labelRSExpression } from '../../labels';
|
||||
import { type IRSForm } from '../../models/rsform';
|
||||
import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api';
|
||||
import { generateAlias, isBaseSet, isBasicConcept } from '../../models/rsform-api';
|
||||
|
||||
interface FormCreateCstProps {
|
||||
schema: IRSForm;
|
||||
|
@ -33,9 +33,8 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
|
|||
const cst_type = useWatch({ control, name: 'cst_type' });
|
||||
const convention = useWatch({ control, name: 'convention' });
|
||||
const crucial = useWatch({ control, name: 'crucial' });
|
||||
const isBasic = isBasicConcept(cst_type);
|
||||
const isBasic = isBasicConcept(cst_type) || cst_type === CstType.NOMINAL;
|
||||
const isElementary = isBaseSet(cst_type);
|
||||
const isFunction = isFunctional(cst_type);
|
||||
const showConvention = !!convention || forceComment || isBasic;
|
||||
|
||||
function handleTypeChange(target: CstType) {
|
||||
|
@ -91,13 +90,7 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
|
|||
<RSInput
|
||||
id='dlg_cst_expression'
|
||||
noTooltip
|
||||
label={
|
||||
cst_type === CstType.STRUCTURED
|
||||
? 'Область определения'
|
||||
: isFunction
|
||||
? 'Определение функции'
|
||||
: 'Формальное определение'
|
||||
}
|
||||
label={labelRSExpression(cst_type)}
|
||||
placeholder={getRSDefinitionPlaceholder(cst_type)}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
|
|
|
@ -37,7 +37,7 @@ export const TemplateState = ({ children }: React.PropsWithChildren) => {
|
|||
function onChangePrototype(newPrototype: IConstituenta) {
|
||||
setPrototype(newPrototype);
|
||||
setArguments(
|
||||
newPrototype.parse.args.map(arg => ({
|
||||
newPrototype.parse!.args.map(arg => ({
|
||||
alias: arg.alias,
|
||||
typification: arg.typification,
|
||||
value: ''
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { useState } from 'react';
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { HelpTopic } from '@/features/help';
|
||||
import { BadgeHelp } from '@/features/help/components/badge-help';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import { TextArea, TextInput } from '@/components/input';
|
||||
|
||||
|
@ -8,9 +11,9 @@ import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
|
|||
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||
import { RSInput } from '../../components/rs-input';
|
||||
import { SelectCstType } from '../../components/select-cst-type';
|
||||
import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels';
|
||||
import { getRSDefinitionPlaceholder, labelCstTypification, labelRSExpression } from '../../labels';
|
||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||
import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsform-api';
|
||||
import { generateAlias, isBaseSet, isBasicConcept } from '../../models/rsform-api';
|
||||
|
||||
interface FormEditCstProps {
|
||||
schema: IRSForm;
|
||||
|
@ -30,9 +33,8 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
const cst_type = useWatch({ control, name: 'item_data.cst_type' }) ?? CstType.BASE;
|
||||
const convention = useWatch({ control, name: 'item_data.convention' });
|
||||
const crucial = useWatch({ control, name: 'item_data.crucial' }) ?? false;
|
||||
const isBasic = isBasicConcept(cst_type);
|
||||
const isBasic = isBasicConcept(cst_type) || cst_type === CstType.NOMINAL;
|
||||
const isElementary = isBaseSet(cst_type);
|
||||
const isFunction = isFunctional(cst_type);
|
||||
const showConvention = !!convention || forceComment || isBasic;
|
||||
|
||||
function handleTypeChange(newValue: CstType) {
|
||||
|
@ -67,6 +69,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
{...register('item_data.alias')}
|
||||
error={errors.item_data?.alias}
|
||||
/>
|
||||
<BadgeHelp topic={HelpTopic.CC_CONSTITUENTA} offset={16} contentClass='sm:max-w-160' />
|
||||
</div>
|
||||
|
||||
<TextArea
|
||||
|
@ -75,23 +78,26 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
spellCheck
|
||||
label='Термин'
|
||||
className='max-h-15 disabled:min-h-9'
|
||||
placeholder='Обозначение для текстовых определений'
|
||||
{...register('item_data.term_raw')}
|
||||
error={errors.item_data?.term_raw}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
fitContent
|
||||
dense
|
||||
noResize
|
||||
noBorder
|
||||
noOutline
|
||||
transparent
|
||||
readOnly
|
||||
label='Типизация'
|
||||
value={labelCstTypification(target)}
|
||||
className='cursor-default'
|
||||
/>
|
||||
{cst_type !== CstType.NOMINAL ? (
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
fitContent
|
||||
dense
|
||||
noResize
|
||||
noBorder
|
||||
noOutline
|
||||
transparent
|
||||
readOnly
|
||||
label='Типизация'
|
||||
value={labelCstTypification(target)}
|
||||
className='cursor-default'
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
|
@ -101,13 +107,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
<RSInput
|
||||
id='dlg_cst_expression'
|
||||
noTooltip
|
||||
label={
|
||||
cst_type === CstType.STRUCTURED
|
||||
? 'Область определения'
|
||||
: isFunction
|
||||
? 'Определение функции'
|
||||
: 'Формальное определение'
|
||||
}
|
||||
label={labelRSExpression(cst_type)}
|
||||
placeholder={getRSDefinitionPlaceholder(cst_type)}
|
||||
className='max-h-15'
|
||||
schema={schema}
|
||||
|
@ -131,6 +131,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
fitContent
|
||||
spellCheck
|
||||
label='Текстовое определение'
|
||||
placeholder='Текстовая интерпретация формального выражения'
|
||||
className='max-h-15'
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
|
@ -158,6 +159,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
|||
fitContent
|
||||
spellCheck
|
||||
label={isBasic ? 'Конвенция' : 'Комментарий'}
|
||||
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
|
||||
className='max-h-20 disabled:min-h-9'
|
||||
{...register('item_data.convention')}
|
||||
error={errors.item_data?.convention}
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
|
||||
import { MiniButton } from '@/components/control';
|
||||
import { Checkbox } from '@/components/input';
|
||||
import { ModalForm } from '@/components/modal';
|
||||
|
||||
import { CstType } from '../backend/types';
|
||||
import { IconCstType } from '../components/icon-cst-type';
|
||||
import { labelCstType } from '../labels';
|
||||
import { type GraphFilterParams, useTermGraphStore } from '../stores/term-graph';
|
||||
import { cstTypeToFilterKey, type GraphFilterParams, useTermGraphStore } from '../stores/term-graph';
|
||||
|
||||
export function DlgGraphParams() {
|
||||
const params = useTermGraphStore(state => state.filter);
|
||||
|
@ -71,47 +73,32 @@ export function DlgGraphParams() {
|
|||
/>
|
||||
</div>
|
||||
<div className='flex flex-col gap-1'>
|
||||
<h1 className='mb-2'>Типы конституент</h1>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowBase'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.BASE)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowStruct'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.STRUCTURED)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowTerm'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.TERM)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowAxiom'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.AXIOM)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowFunction'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.FUNCTION)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowPredicate'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.PREDICATE)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowConstant'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.CONSTANT)} />}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name='allowTheorem'
|
||||
render={({ field }) => <Checkbox {...field} label={labelCstType(CstType.THEOREM)} />}
|
||||
/>
|
||||
<h1 className='mb-1'>Типы конституент</h1>
|
||||
<div>
|
||||
{Object.values(CstType).map(cstType => {
|
||||
const fieldName = cstTypeToFilterKey[cstType];
|
||||
return (
|
||||
<Controller
|
||||
key={fieldName}
|
||||
control={control}
|
||||
name={fieldName}
|
||||
render={({ field }) => (
|
||||
<MiniButton
|
||||
onClick={() => field.onChange(!field.value)}
|
||||
title={labelCstType(cstType)}
|
||||
icon={
|
||||
<IconCstType
|
||||
size='2rem'
|
||||
value={cstType}
|
||||
className={field.value ? 'text-constructive' : 'text-destructive'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</ModalForm>
|
||||
);
|
||||
|
|
|
@ -18,6 +18,7 @@ import { type GraphColoring } from './stores/term-graph';
|
|||
|
||||
// --- Records for label/describe functions ---
|
||||
const labelCstTypeRecord: Record<CstType, string> = {
|
||||
[CstType.NOMINAL]: 'Номеноид',
|
||||
[CstType.BASE]: 'Базисное множество',
|
||||
[CstType.CONSTANT]: 'Константное множество',
|
||||
[CstType.STRUCTURED]: 'Родовая структура',
|
||||
|
@ -34,6 +35,7 @@ const labelReferenceTypeRecord: Record<ReferenceType, string> = {
|
|||
};
|
||||
|
||||
const labelCstClassRecord: Record<CstClass, string> = {
|
||||
[CstClass.NOMINAL]: 'номинальный',
|
||||
[CstClass.BASIC]: 'базовый',
|
||||
[CstClass.DERIVED]: 'производный',
|
||||
[CstClass.STATEMENT]: 'утверждение',
|
||||
|
@ -41,6 +43,7 @@ const labelCstClassRecord: Record<CstClass, string> = {
|
|||
};
|
||||
|
||||
const describeCstClassRecord: Record<CstClass, string> = {
|
||||
[CstClass.NOMINAL]: 'номинальная сущность',
|
||||
[CstClass.BASIC]: 'неопределяемое понятие',
|
||||
[CstClass.DERIVED]: 'определяемое понятие',
|
||||
[CstClass.STATEMENT]: 'логическое утверждение',
|
||||
|
@ -158,15 +161,28 @@ const labelGrammemeRecord: Partial<Record<Grammeme, string>> = {
|
|||
[Grammeme.Litr]: 'Стиль: литературный'
|
||||
};
|
||||
|
||||
const labelRSExpressionsRecord: Record<CstType, string> = {
|
||||
[CstType.NOMINAL]: 'Определяющие конституенты',
|
||||
[CstType.BASE]: 'Формальное определение',
|
||||
[CstType.CONSTANT]: 'Формальное определение',
|
||||
[CstType.STRUCTURED]: 'Область определения',
|
||||
[CstType.TERM]: 'Формальное определение',
|
||||
[CstType.THEOREM]: 'Формальное определение',
|
||||
[CstType.AXIOM]: 'Формальное определение',
|
||||
[CstType.FUNCTION]: 'Определение функции',
|
||||
[CstType.PREDICATE]: 'Определение функции'
|
||||
};
|
||||
|
||||
const rsDefinitionPlaceholderRecord: Record<CstType, string> = {
|
||||
[CstType.NOMINAL]: 'Например, X1 D1 N1',
|
||||
[CstType.BASE]: 'Не предусмотрено',
|
||||
[CstType.CONSTANT]: 'Не предусмотрено',
|
||||
[CstType.STRUCTURED]: 'Пример: ℬ(X1×D2)',
|
||||
[CstType.TERM]: 'Пример: D{ξ∈S1 | Pr1(ξ)∩Pr2(ξ)=∅}',
|
||||
[CstType.THEOREM]: 'Пример: D11=∅',
|
||||
[CstType.AXIOM]: 'Пример: D11=∅',
|
||||
[CstType.FUNCTION]: 'Пример: [α∈X1, β∈ℬ(X1×X2)] Pr2(Fi1[{α}](β))',
|
||||
[CstType.PREDICATE]: 'Пример: [α∈X1, β∈ℬ(X1)] α∈β & card(β)>1',
|
||||
[CstType.CONSTANT]: 'Формальное выражение',
|
||||
[CstType.BASE]: 'Формальное выражение'
|
||||
[CstType.PREDICATE]: 'Пример: [α∈X1, β∈ℬ(X1)] α∈β & card(β)>1'
|
||||
};
|
||||
|
||||
const cstTypeShortcutKeyRecord: Record<CstType, string> = {
|
||||
|
@ -177,7 +193,8 @@ const cstTypeShortcutKeyRecord: Record<CstType, string> = {
|
|||
[CstType.FUNCTION]: 'Q',
|
||||
[CstType.PREDICATE]: 'W',
|
||||
[CstType.CONSTANT]: '5',
|
||||
[CstType.THEOREM]: '6'
|
||||
[CstType.THEOREM]: '6',
|
||||
[CstType.NOMINAL]: '7'
|
||||
};
|
||||
|
||||
const labelTokenRecord: Partial<Record<TokenID, string>> = {
|
||||
|
@ -331,6 +348,11 @@ export function getCstTypeShortcut(type: CstType) {
|
|||
return key ? `${labelCstType(type)} [Alt + ${key}]` : labelCstType(type);
|
||||
}
|
||||
|
||||
/** Generates label for RS expression based on {@link CstType}. */
|
||||
export function labelRSExpression(type: CstType): string {
|
||||
return labelRSExpressionsRecord[type] ?? 'Формальное выражение';
|
||||
}
|
||||
|
||||
/** Generates placeholder for RS definition based on {@link CstType}. */
|
||||
export function getRSDefinitionPlaceholder(type: CstType): string {
|
||||
return rsDefinitionPlaceholderRecord[type] ?? 'Формальное выражение';
|
||||
|
@ -445,6 +467,9 @@ export function labelTypification({
|
|||
* Generates label for {@link IConstituenta} typification.
|
||||
*/
|
||||
export function labelCstTypification(cst: RO<IConstituenta>): string {
|
||||
if (!cst.parse) {
|
||||
return 'N/A';
|
||||
}
|
||||
return labelTypification({
|
||||
isValid: cst.parse.status === ParsingStatus.VERIFIED,
|
||||
resultType: cst.parse.typification,
|
||||
|
|
|
@ -69,6 +69,7 @@ export function produceFilteredGraph(schema: IRSForm, params: GraphFilterParams,
|
|||
if (params.allowPredicate) result.push(CstType.PREDICATE);
|
||||
if (params.allowConstant) result.push(CstType.CONSTANT);
|
||||
if (params.allowTheorem) result.push(CstType.THEOREM);
|
||||
if (params.allowNominal) result.push(CstType.NOMINAL);
|
||||
return result;
|
||||
})();
|
||||
|
||||
|
|
|
@ -95,6 +95,7 @@ export function inferClass(type: CstType, isTemplate: boolean = false): CstClass
|
|||
}
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.NOMINAL: return CstClass.NOMINAL;
|
||||
case CstType.BASE: return CstClass.BASIC;
|
||||
case CstType.CONSTANT: return CstClass.BASIC;
|
||||
case CstType.STRUCTURED: return CstClass.BASIC;
|
||||
|
@ -124,6 +125,7 @@ export function applyFilterCategory(start: IConstituenta, schema: IRSForm): ICon
|
|||
}
|
||||
|
||||
const cstTypePrefixRecord: Record<CstType, string> = {
|
||||
[CstType.NOMINAL]: 'N',
|
||||
[CstType.BASE]: 'X',
|
||||
[CstType.CONSTANT]: 'C',
|
||||
[CstType.STRUCTURED]: 'S',
|
||||
|
@ -148,6 +150,7 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM):
|
|||
}
|
||||
// prettier-ignore
|
||||
switch (hint) {
|
||||
case 'N': return CstType.NOMINAL;
|
||||
case 'X': return CstType.BASE;
|
||||
case 'C': return CstType.CONSTANT;
|
||||
case 'S': return CstType.STRUCTURED;
|
||||
|
@ -166,6 +169,7 @@ export function guessCstType(hint: string, defaultType: CstType = CstType.TERM):
|
|||
export function isBasicConcept(type: CstType): boolean {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.NOMINAL: return true;
|
||||
case CstType.BASE: return true;
|
||||
case CstType.CONSTANT: return true;
|
||||
case CstType.STRUCTURED: return true;
|
||||
|
@ -183,6 +187,7 @@ export function isBasicConcept(type: CstType): boolean {
|
|||
export function isBaseSet(type: CstType): boolean {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.NOMINAL: return false;
|
||||
case CstType.BASE: return true;
|
||||
case CstType.CONSTANT: return true;
|
||||
case CstType.STRUCTURED: return false;
|
||||
|
@ -200,6 +205,7 @@ export function isBaseSet(type: CstType): boolean {
|
|||
export function isFunctional(type: CstType): boolean {
|
||||
// prettier-ignore
|
||||
switch (type) {
|
||||
case CstType.NOMINAL: return false;
|
||||
case CstType.BASE: return false;
|
||||
case CstType.CONSTANT: return false;
|
||||
case CstType.STRUCTURED: return false;
|
||||
|
@ -215,7 +221,13 @@ export function isFunctional(type: CstType): boolean {
|
|||
* Evaluate if {@link IConstituenta} can be used produce structure.
|
||||
*/
|
||||
export function canProduceStructure(cst: RO<IConstituenta>): boolean {
|
||||
return !!cst.parse.typification && cst.cst_type !== CstType.BASE && cst.cst_type !== CstType.CONSTANT;
|
||||
return (
|
||||
!!cst.parse &&
|
||||
!!cst.parse.typification &&
|
||||
cst.cst_type !== CstType.BASE &&
|
||||
cst.cst_type !== CstType.CONSTANT &&
|
||||
cst.cst_type !== CstType.NOMINAL
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,7 @@ export const CATEGORY_CST_TYPE = CstType.THEOREM;
|
|||
|
||||
/** Represents Constituenta classification in terms of system of concepts. */
|
||||
export const CstClass = {
|
||||
NOMINAL: 'nominal',
|
||||
BASIC: 'basic',
|
||||
DERIVED: 'derived',
|
||||
STATEMENT: 'statement',
|
||||
|
@ -58,7 +59,7 @@ export interface IConstituenta {
|
|||
term_resolved: string;
|
||||
term_forms: TermForm[];
|
||||
|
||||
parse: {
|
||||
parse?: {
|
||||
status: ParsingStatus;
|
||||
valueClass: ValueClass;
|
||||
typification: string;
|
||||
|
@ -122,6 +123,7 @@ export interface IRSFormStats {
|
|||
count_function: number;
|
||||
count_predicate: number;
|
||||
count_theorem: number;
|
||||
count_nominal: number;
|
||||
}
|
||||
|
||||
/** Represents inheritance data for {@link IRSForm}. */
|
||||
|
|
|
@ -30,9 +30,14 @@ import {
|
|||
import { useMutatingRSForm } from '../../../backend/use-mutating-rsform';
|
||||
import { useUpdateConstituenta } from '../../../backend/use-update-constituenta';
|
||||
import { RefsInput } from '../../../components/refs-input';
|
||||
import { getRSDefinitionPlaceholder, labelCstTypification, labelTypification } from '../../../labels';
|
||||
import {
|
||||
getRSDefinitionPlaceholder,
|
||||
labelCstTypification,
|
||||
labelRSExpression,
|
||||
labelTypification
|
||||
} from '../../../labels';
|
||||
import { type IConstituenta, type IRSForm } from '../../../models/rsform';
|
||||
import { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsform-api';
|
||||
import { isBaseSet, isBasicConcept } from '../../../models/rsform-api';
|
||||
import { EditorRSExpression } from '../editor-rsexpression';
|
||||
|
||||
interface FormConstituentaProps {
|
||||
|
@ -90,15 +95,18 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
);
|
||||
|
||||
const typeInfo = useMemo(
|
||||
() => ({
|
||||
alias: activeCst.alias,
|
||||
result: localParse ? localParse.typification : activeCst.parse.typification,
|
||||
args: localParse ? localParse.args : activeCst.parse.args
|
||||
}),
|
||||
() =>
|
||||
activeCst.parse
|
||||
? {
|
||||
alias: activeCst.alias,
|
||||
result: localParse ? localParse.typification : activeCst.parse.typification,
|
||||
args: localParse ? localParse.args : activeCst.parse.args
|
||||
}
|
||||
: null,
|
||||
[activeCst, localParse]
|
||||
);
|
||||
|
||||
const isBasic = isBasicConcept(activeCst.cst_type);
|
||||
const isBasic = isBasicConcept(activeCst.cst_type) || activeCst.cst_type === CstType.NOMINAL;
|
||||
const isElementary = isBaseSet(activeCst.cst_type);
|
||||
const showConvention = !!activeCst.convention || forceComment || isBasic;
|
||||
|
||||
|
@ -140,7 +148,11 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
}
|
||||
|
||||
function handleTypeGraph(event: React.MouseEvent<Element>) {
|
||||
if ((localParse && !localParse.parseResult) || activeCst.parse.status !== ParsingStatus.VERIFIED) {
|
||||
if (
|
||||
(localParse && !localParse.parseResult) ||
|
||||
!activeCst.parse ||
|
||||
activeCst.parse.status !== ParsingStatus.VERIFIED
|
||||
) {
|
||||
toast.error(errorMsg.typeStructureFailed);
|
||||
return;
|
||||
}
|
||||
|
@ -227,19 +239,21 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
)}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
fitContent
|
||||
dense
|
||||
noResize
|
||||
noBorder
|
||||
noOutline
|
||||
transparent
|
||||
readOnly
|
||||
label='Типизация'
|
||||
value={typification}
|
||||
className='cursor-default'
|
||||
/>
|
||||
{activeCst.cst_type !== CstType.NOMINAL ? (
|
||||
<TextArea
|
||||
id='cst_typification'
|
||||
fitContent
|
||||
dense
|
||||
noResize
|
||||
noBorder
|
||||
noOutline
|
||||
transparent
|
||||
readOnly
|
||||
label='Типизация'
|
||||
value={typification}
|
||||
className='cursor-default'
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!!activeCst.definition_formal || !isElementary ? (
|
||||
<Controller
|
||||
|
@ -248,13 +262,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
|||
render={({ field }) => (
|
||||
<EditorRSExpression
|
||||
id='cst_expression'
|
||||
label={
|
||||
activeCst.cst_type === CstType.STRUCTURED
|
||||
? 'Область определения'
|
||||
: isFunctional(activeCst.cst_type)
|
||||
? 'Определение функции'
|
||||
: 'Формальное определение'
|
||||
}
|
||||
label={labelRSExpression(activeCst.cst_type)}
|
||||
placeholder={disabled ? '' : getRSDefinitionPlaceholder(activeCst.cst_type)}
|
||||
value={field.value ?? ''}
|
||||
activeCst={activeCst}
|
||||
|
|
|
@ -155,6 +155,21 @@ export function EditorRSExpression({
|
|||
}
|
||||
}
|
||||
|
||||
if (!activeCst.parse) {
|
||||
return (
|
||||
<RSInput
|
||||
value={value}
|
||||
schema={schema}
|
||||
minHeight='3.75rem'
|
||||
maxHeight='8rem'
|
||||
onChange={handleChange}
|
||||
onOpenEdit={onOpenEdit}
|
||||
disabled={disabled}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative'>
|
||||
<ToolbarRSExpression
|
||||
|
|
|
@ -30,7 +30,7 @@ interface StatusBarProps {
|
|||
|
||||
export function StatusBar({ className, isModified, processing, activeCst, parseData, onAnalyze }: StatusBarProps) {
|
||||
const status = (() => {
|
||||
if (isModified) {
|
||||
if (isModified || !activeCst.parse) {
|
||||
return ExpressionStatus.UNKNOWN;
|
||||
}
|
||||
if (parseData) {
|
||||
|
|
|
@ -96,6 +96,7 @@ export function EditorRSList() {
|
|||
case 'KeyW': createCst(CstType.PREDICATE, true); return true;
|
||||
case 'Digit5': createCst(CstType.CONSTANT, true); return true;
|
||||
case 'Digit6': createCst(CstType.THEOREM, true); return true;
|
||||
case 'Digit7': createCst(CstType.NOMINAL, true); return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -63,11 +63,13 @@ export function ToolbarTermGraph({ className }: ToolbarTermGraphProps) {
|
|||
const { addSelectedNodes } = store.getState();
|
||||
|
||||
function handleShowTypeGraph() {
|
||||
const typeInfo = schema.items.map(item => ({
|
||||
alias: item.alias,
|
||||
result: item.parse.typification,
|
||||
args: item.parse.args
|
||||
}));
|
||||
const typeInfo = schema.items
|
||||
.filter(item => !!item.parse)
|
||||
.map(item => ({
|
||||
alias: item.alias,
|
||||
result: item.parse!.typification,
|
||||
args: item.parse!.args
|
||||
}));
|
||||
showTypeGraph({ items: typeInfo });
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { CstType } from '../backend/types';
|
||||
|
||||
export const graphColorings = ['none', 'status', 'type', 'schemas'] as const;
|
||||
|
||||
/**
|
||||
|
@ -29,8 +31,21 @@ export interface GraphFilterParams {
|
|||
allowPredicate: boolean;
|
||||
allowConstant: boolean;
|
||||
allowTheorem: boolean;
|
||||
allowNominal: boolean;
|
||||
}
|
||||
|
||||
export const cstTypeToFilterKey: Record<CstType, keyof GraphFilterParams> = {
|
||||
[CstType.BASE]: 'allowBase',
|
||||
[CstType.STRUCTURED]: 'allowStruct',
|
||||
[CstType.TERM]: 'allowTerm',
|
||||
[CstType.AXIOM]: 'allowAxiom',
|
||||
[CstType.FUNCTION]: 'allowFunction',
|
||||
[CstType.PREDICATE]: 'allowPredicate',
|
||||
[CstType.CONSTANT]: 'allowConstant',
|
||||
[CstType.THEOREM]: 'allowTheorem',
|
||||
[CstType.NOMINAL]: 'allowNominal'
|
||||
};
|
||||
|
||||
interface TermGraphStore {
|
||||
filter: GraphFilterParams;
|
||||
setFilter: (value: GraphFilterParams) => void;
|
||||
|
@ -66,7 +81,8 @@ export const useTermGraphStore = create<TermGraphStore>()(
|
|||
allowFunction: true,
|
||||
allowPredicate: true,
|
||||
allowConstant: true,
|
||||
allowTheorem: true
|
||||
allowTheorem: true,
|
||||
allowNominal: true
|
||||
},
|
||||
setFilter: value => set({ filter: value }),
|
||||
toggleFocusInputs: () =>
|
||||
|
|
|
@ -17,7 +17,7 @@ export type IUpdateProfileDTO = z.infer<typeof schemaUpdateProfile>;
|
|||
|
||||
// ========= SCHEMAS ========
|
||||
export const schemaUser = z.strictObject({
|
||||
id: z.coerce.number(),
|
||||
id: z.number(),
|
||||
username: z.string().nonempty(errorMsg.requiredField),
|
||||
is_staff: z.boolean(),
|
||||
email: z.email(errorMsg.emailField),
|
||||
|
|
Loading…
Reference in New Issue
Block a user