mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 04:40:36 +03:00
F: Implement crucial constituents feature
This commit is contained in:
parent
e4480b158d
commit
ba0416c37d
|
@ -84,15 +84,18 @@ class TestVersionViews(EndpointTester):
|
||||||
alias='A1',
|
alias='A1',
|
||||||
cst_type='axiom',
|
cst_type='axiom',
|
||||||
definition_formal='X1=X1',
|
definition_formal='X1=X1',
|
||||||
order=1
|
order=1,
|
||||||
|
crucial=True
|
||||||
)
|
)
|
||||||
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
version_id = self._create_version({'version': '1.0.0', 'description': 'test'})
|
||||||
a1.definition_formal = 'X1=X2'
|
a1.definition_formal = 'X1=X2'
|
||||||
|
a1.crucial = False
|
||||||
a1.save()
|
a1.save()
|
||||||
|
|
||||||
response = self.executeOK(schema=self.owned_id, version=version_id)
|
response = self.executeOK(schema=self.owned_id, version=version_id)
|
||||||
loaded_a1 = response.data['items'][1]
|
loaded_a1 = response.data['items'][1]
|
||||||
self.assertEqual(loaded_a1['definition_formal'], 'X1=X1')
|
self.assertEqual(loaded_a1['definition_formal'], 'X1=X1')
|
||||||
|
self.assertEqual(loaded_a1['crucial'], True)
|
||||||
self.assertEqual(loaded_a1['parse']['status'], 'verified')
|
self.assertEqual(loaded_a1['parse']['status'], 'verified')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -121,7 +121,8 @@ class TestChangeConstituents(EndpointTester):
|
||||||
'term_raw': 'Test1',
|
'term_raw': 'Test1',
|
||||||
'definition_formal': r'X4\X4',
|
'definition_formal': r'X4\X4',
|
||||||
'definition_raw': '@{X5|sing,datv}',
|
'definition_raw': '@{X5|sing,datv}',
|
||||||
'convention': 'test'
|
'convention': 'test',
|
||||||
|
'crucial': True,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response = self.executeOK(data=data, schema=self.ks1.model.pk)
|
response = self.executeOK(data=data, schema=self.ks1.model.pk)
|
||||||
|
@ -132,9 +133,11 @@ class TestChangeConstituents(EndpointTester):
|
||||||
self.assertEqual(self.ks1X1.definition_formal, data['item_data']['definition_formal'])
|
self.assertEqual(self.ks1X1.definition_formal, data['item_data']['definition_formal'])
|
||||||
self.assertEqual(self.ks1X1.definition_raw, data['item_data']['definition_raw'])
|
self.assertEqual(self.ks1X1.definition_raw, data['item_data']['definition_raw'])
|
||||||
self.assertEqual(self.ks1X1.convention, data['item_data']['convention'])
|
self.assertEqual(self.ks1X1.convention, data['item_data']['convention'])
|
||||||
|
self.assertEqual(self.ks1X1.crucial, data['item_data']['crucial'])
|
||||||
self.assertEqual(d2.definition_resolved, data['item_data']['term_raw'])
|
self.assertEqual(d2.definition_resolved, data['item_data']['term_raw'])
|
||||||
self.assertEqual(inherited_cst.term_raw, data['item_data']['term_raw'])
|
self.assertEqual(inherited_cst.term_raw, data['item_data']['term_raw'])
|
||||||
self.assertEqual(inherited_cst.convention, data['item_data']['convention'])
|
self.assertEqual(inherited_cst.convention, data['item_data']['convention'])
|
||||||
|
self.assertEqual(inherited_cst.crucial, False)
|
||||||
self.assertEqual(inherited_cst.definition_formal, r'X1\X1')
|
self.assertEqual(inherited_cst.definition_formal, r'X1\X1')
|
||||||
self.assertEqual(inherited_cst.definition_raw, r'@{X2|sing,datv}')
|
self.assertEqual(inherited_cst.definition_raw, r'@{X2|sing,datv}')
|
||||||
|
|
||||||
|
|
|
@ -8,5 +8,5 @@ from . import models
|
||||||
class ConstituentaAdmin(admin.ModelAdmin):
|
class ConstituentaAdmin(admin.ModelAdmin):
|
||||||
''' Admin model: Constituenta. '''
|
''' Admin model: Constituenta. '''
|
||||||
ordering = ['schema', 'order']
|
ordering = ['schema', 'order']
|
||||||
list_display = ['schema', 'order', 'alias', 'term_resolved', 'definition_resolved']
|
list_display = ['schema', 'order', 'alias', 'term_resolved', 'definition_resolved', 'crucial']
|
||||||
search_fields = ['term_resolved', 'definition_resolved']
|
search_fields = ['term_resolved', 'definition_resolved']
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 5.2.4 on 2025-07-29 09:01
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('rsform', '0003_alter_constituenta_order'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='constituenta',
|
||||||
|
name='crucial',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='Ключевая'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,7 @@ import re
|
||||||
from cctext import extract_entities
|
from cctext import extract_entities
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
CASCADE,
|
CASCADE,
|
||||||
|
BooleanField,
|
||||||
CharField,
|
CharField,
|
||||||
ForeignKey,
|
ForeignKey,
|
||||||
JSONField,
|
JSONField,
|
||||||
|
@ -103,6 +104,10 @@ class Constituenta(Model):
|
||||||
default='',
|
default='',
|
||||||
blank=True
|
blank=True
|
||||||
)
|
)
|
||||||
|
crucial = BooleanField(
|
||||||
|
verbose_name='Ключевая',
|
||||||
|
default=False
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
''' Model metadata. '''
|
''' Model metadata. '''
|
||||||
|
|
|
@ -144,6 +144,7 @@ class RSForm:
|
||||||
self.cache.ensure_loaded()
|
self.cache.ensure_loaded()
|
||||||
position = self.cache.constituents.index(self.cache.by_id[insert_after.pk]) + 1
|
position = self.cache.constituents.index(self.cache.by_id[insert_after.pk]) + 1
|
||||||
result = self.insert_new(data['alias'], data['cst_type'], position)
|
result = self.insert_new(data['alias'], data['cst_type'], position)
|
||||||
|
result.crucial = data.get('crucial', False)
|
||||||
result.convention = data.get('convention', '')
|
result.convention = data.get('convention', '')
|
||||||
result.definition_formal = data.get('definition_formal', '')
|
result.definition_formal = data.get('definition_formal', '')
|
||||||
result.term_forms = data.get('term_forms', [])
|
result.term_forms = data.get('term_forms', [])
|
||||||
|
@ -247,6 +248,9 @@ class RSForm:
|
||||||
else:
|
else:
|
||||||
old_data['convention'] = cst.convention
|
old_data['convention'] = cst.convention
|
||||||
cst.convention = data['convention']
|
cst.convention = data['convention']
|
||||||
|
if 'crucial' in data:
|
||||||
|
cst.crucial = data['crucial']
|
||||||
|
del data['crucial']
|
||||||
if 'definition_formal' in data:
|
if 'definition_formal' in data:
|
||||||
if cst.definition_formal == data['definition_formal']:
|
if cst.definition_formal == data['definition_formal']:
|
||||||
del data['definition_formal']
|
del data['definition_formal']
|
||||||
|
|
|
@ -12,6 +12,7 @@ from .basics import (
|
||||||
WordFormSerializer
|
WordFormSerializer
|
||||||
)
|
)
|
||||||
from .data_access import (
|
from .data_access import (
|
||||||
|
CrucialUpdateSerializer,
|
||||||
CstCreateSerializer,
|
CstCreateSerializer,
|
||||||
CstInfoSerializer,
|
CstInfoSerializer,
|
||||||
CstListSerializer,
|
CstListSerializer,
|
||||||
|
|
|
@ -46,12 +46,13 @@ class CstUpdateSerializer(StrictSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = Constituenta
|
model = Constituenta
|
||||||
fields = 'alias', 'cst_type', 'convention', 'definition_formal', 'definition_raw', 'term_raw', 'term_forms'
|
fields = 'alias', 'cst_type', 'convention', 'crucial', 'definition_formal', \
|
||||||
|
'definition_raw', 'term_raw', 'term_forms'
|
||||||
|
|
||||||
target = PKField(
|
target = PKField(
|
||||||
many=False,
|
many=False,
|
||||||
queryset=Constituenta.objects.all().only(
|
queryset=Constituenta.objects.all().only(
|
||||||
'alias', 'cst_type', 'convention', 'definition_formal', 'definition_raw', 'term_raw')
|
'alias', 'cst_type', 'convention', 'crucial', 'definition_formal', 'definition_raw', 'term_raw')
|
||||||
)
|
)
|
||||||
item_data = ConstituentaUpdateData()
|
item_data = ConstituentaUpdateData()
|
||||||
|
|
||||||
|
@ -71,6 +72,24 @@ class CstUpdateSerializer(StrictSerializer):
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
class CrucialUpdateSerializer(StrictSerializer):
|
||||||
|
''' Serializer: update crucial status. '''
|
||||||
|
target = PKField(
|
||||||
|
many=True,
|
||||||
|
queryset=Constituenta.objects.all().only('crucial', 'schema_id')
|
||||||
|
)
|
||||||
|
value = serializers.BooleanField()
|
||||||
|
|
||||||
|
def validate(self, attrs):
|
||||||
|
schema = cast(LibraryItem, self.context['schema'])
|
||||||
|
for cst in attrs['target']:
|
||||||
|
if schema and cst.schema_id != schema.pk:
|
||||||
|
raise serializers.ValidationError({
|
||||||
|
f'{cst.pk}': msg.constituentaNotInRSform(schema.title)
|
||||||
|
})
|
||||||
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
class CstDetailsSerializer(StrictModelSerializer):
|
class CstDetailsSerializer(StrictModelSerializer):
|
||||||
''' Serializer: Constituenta data including parse. '''
|
''' Serializer: Constituenta data including parse. '''
|
||||||
parse = CstParseSerializer()
|
parse = CstParseSerializer()
|
||||||
|
@ -96,7 +115,7 @@ class CstCreateSerializer(StrictModelSerializer):
|
||||||
''' serializer metadata. '''
|
''' serializer metadata. '''
|
||||||
model = Constituenta
|
model = Constituenta
|
||||||
fields = \
|
fields = \
|
||||||
'alias', 'cst_type', 'convention', \
|
'alias', 'cst_type', 'convention', 'crucial', \
|
||||||
'term_raw', 'definition_raw', 'definition_formal', \
|
'term_raw', 'definition_raw', 'definition_formal', \
|
||||||
'insert_after', 'term_forms'
|
'insert_after', 'term_forms'
|
||||||
|
|
||||||
|
|
|
@ -225,7 +225,9 @@ class TestRSFormViewset(EndpointTester):
|
||||||
'cst_type': CstType.BASE,
|
'cst_type': CstType.BASE,
|
||||||
'insert_after': x2.pk,
|
'insert_after': x2.pk,
|
||||||
'term_raw': 'test',
|
'term_raw': 'test',
|
||||||
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}]
|
'term_forms': [{'text': 'form1', 'tags': 'sing,datv'}],
|
||||||
|
'definition_formal': 'invalid',
|
||||||
|
'crucial': True
|
||||||
}
|
}
|
||||||
response = self.executeCreated(data=data, item=self.owned_id)
|
response = self.executeCreated(data=data, item=self.owned_id)
|
||||||
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
self.assertEqual(response.data['new_cst']['alias'], data['alias'])
|
||||||
|
@ -233,6 +235,8 @@ class TestRSFormViewset(EndpointTester):
|
||||||
self.assertEqual(x4.order, 2)
|
self.assertEqual(x4.order, 2)
|
||||||
self.assertEqual(x4.term_raw, data['term_raw'])
|
self.assertEqual(x4.term_raw, data['term_raw'])
|
||||||
self.assertEqual(x4.term_forms, data['term_forms'])
|
self.assertEqual(x4.term_forms, data['term_forms'])
|
||||||
|
self.assertEqual(x4.definition_formal, data['definition_formal'])
|
||||||
|
self.assertEqual(x4.crucial, data['crucial'])
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'alias': 'X5',
|
'alias': 'X5',
|
||||||
|
@ -574,6 +578,19 @@ class TestConstituentaAPI(EndpointTester):
|
||||||
self.assertEqual(self.cst3.definition_resolved, 'form1')
|
self.assertEqual(self.cst3.definition_resolved, 'form1')
|
||||||
self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms'])
|
self.assertEqual(self.cst3.term_forms, data['item_data']['term_forms'])
|
||||||
|
|
||||||
|
@decl_endpoint('/api/rsforms/{schema}/update-crucial', method='patch')
|
||||||
|
def test_update_crucial(self):
|
||||||
|
data = {'target': [self.cst1.pk], 'value': True}
|
||||||
|
self.executeForbidden(data=data, schema=self.unowned_id)
|
||||||
|
|
||||||
|
self.logout()
|
||||||
|
self.executeForbidden(data=data, schema=self.owned_id)
|
||||||
|
|
||||||
|
self.login()
|
||||||
|
self.executeOK(data=data, schema=self.owned_id)
|
||||||
|
self.cst1.refresh_from_db()
|
||||||
|
self.assertEqual(self.cst1.crucial, True)
|
||||||
|
|
||||||
|
|
||||||
class TestInlineSynthesis(EndpointTester):
|
class TestInlineSynthesis(EndpointTester):
|
||||||
''' Testing Operations endpoints. '''
|
''' Testing Operations endpoints. '''
|
||||||
|
|
|
@ -42,6 +42,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
'load_trs',
|
'load_trs',
|
||||||
'create_cst',
|
'create_cst',
|
||||||
'update_cst',
|
'update_cst',
|
||||||
|
'update_crucial',
|
||||||
'move_cst',
|
'move_cst',
|
||||||
'delete_multiple_cst',
|
'delete_multiple_cst',
|
||||||
'substitute',
|
'substitute',
|
||||||
|
@ -137,6 +138,36 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
||||||
data=s.RSFormParseSerializer(schema.model).data
|
data=s.RSFormParseSerializer(schema.model).data
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@extend_schema(
|
||||||
|
summary='update crucial attributes of a given list of constituents',
|
||||||
|
tags=['RSForm'],
|
||||||
|
request=s.CrucialUpdateSerializer,
|
||||||
|
responses={
|
||||||
|
c.HTTP_200_OK: s.RSFormParseSerializer,
|
||||||
|
c.HTTP_400_BAD_REQUEST: None,
|
||||||
|
c.HTTP_403_FORBIDDEN: None,
|
||||||
|
c.HTTP_404_NOT_FOUND: None
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@action(detail=True, methods=['patch'], url_path='update-crucial')
|
||||||
|
def update_crucial(self, request: Request, pk) -> HttpResponse:
|
||||||
|
''' Update crucial attributes of a given list of constituents. '''
|
||||||
|
model = self._get_item()
|
||||||
|
serializer = s.CrucialUpdateSerializer(data=request.data, partial=True, context={'schema': model})
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
value: bool = serializer.validated_data['value']
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for cst in serializer.validated_data['target']:
|
||||||
|
cst.crucial = value
|
||||||
|
cst.save(update_fields=['crucial'])
|
||||||
|
model.save(update_fields=['time_update'])
|
||||||
|
|
||||||
|
return Response(
|
||||||
|
status=c.HTTP_200_OK,
|
||||||
|
data=s.RSFormParseSerializer(model).data
|
||||||
|
)
|
||||||
|
|
||||||
@extend_schema(
|
@extend_schema(
|
||||||
summary='produce the structure of a given constituenta',
|
summary='produce the structure of a given constituenta',
|
||||||
tags=['RSForm'],
|
tags=['RSForm'],
|
||||||
|
|
37
rsconcept/frontend/src/components/control/text-button.tsx
Normal file
37
rsconcept/frontend/src/components/control/text-button.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { globalIDs } from '@/utils/constants';
|
||||||
|
|
||||||
|
import { type Button as ButtonStyle } from '../props';
|
||||||
|
import { cn } from '../utils';
|
||||||
|
|
||||||
|
interface TextButtonProps extends ButtonStyle {
|
||||||
|
/** Text to display second. */
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customizable `button` with text, transparent background and no additional styling.
|
||||||
|
*/
|
||||||
|
export function TextButton({ text, title, titleHtml, hideTitle, className, ...restProps }: TextButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
tabIndex={-1}
|
||||||
|
type='button'
|
||||||
|
className={cn(
|
||||||
|
'self-start cc-label cc-hover-underline',
|
||||||
|
'font-medium text-primary select-none disabled:text-foreground',
|
||||||
|
'cursor-pointer disabled:cursor-default',
|
||||||
|
'outline-hidden',
|
||||||
|
'select-text',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
data-tooltip-id={!!title || !!titleHtml ? globalIDs.tooltip : undefined}
|
||||||
|
data-tooltip-html={titleHtml}
|
||||||
|
data-tooltip-content={title}
|
||||||
|
data-tooltip-hidden={hideTitle}
|
||||||
|
aria-label={!text ? title : undefined}
|
||||||
|
{...restProps}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ export function TextURL({ text, href, title, color = 'text-primary', onClick }:
|
||||||
);
|
);
|
||||||
} else if (onClick) {
|
} else if (onClick) {
|
||||||
return (
|
return (
|
||||||
<button type='button' tabIndex={-1} className={design} onClick={onClick}>
|
<button type='button' tabIndex={-1} className={design} title={title} onClick={onClick}>
|
||||||
{text}
|
{text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
|
@ -106,9 +106,9 @@ export { LuDatabase as IconDatabase } from 'react-icons/lu';
|
||||||
export { LuView as IconDBStructure } from 'react-icons/lu';
|
export { LuView as IconDBStructure } from 'react-icons/lu';
|
||||||
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
|
export { LuPlaneTakeoff as IconRESTapi } from 'react-icons/lu';
|
||||||
export { LuImage as IconImage } from 'react-icons/lu';
|
export { LuImage as IconImage } from 'react-icons/lu';
|
||||||
export { PiFediverseLogo as IconGraphSelection } from 'react-icons/pi';
|
|
||||||
export { GoVersions as IconVersions } from 'react-icons/go';
|
export { GoVersions as IconVersions } from 'react-icons/go';
|
||||||
export { LuAtSign as IconTerm } from 'react-icons/lu';
|
export { LuAtSign as IconTerm } from 'react-icons/lu';
|
||||||
|
export { MdTaskAlt as IconCrucial } from 'react-icons/md';
|
||||||
export { LuSubscript as IconAlias } from 'react-icons/lu';
|
export { LuSubscript as IconAlias } from 'react-icons/lu';
|
||||||
export { TbMathFunction as IconFormula } from 'react-icons/tb';
|
export { TbMathFunction as IconFormula } from 'react-icons/tb';
|
||||||
export { BiFontFamily as IconText } from 'react-icons/bi';
|
export { BiFontFamily as IconText } from 'react-icons/bi';
|
||||||
|
@ -150,9 +150,11 @@ export { GrConnect as IconConnect } from 'react-icons/gr';
|
||||||
export { BiPlayCircle as IconExecute } from 'react-icons/bi';
|
export { BiPlayCircle as IconExecute } from 'react-icons/bi';
|
||||||
|
|
||||||
// ======== Graph UI =======
|
// ======== Graph UI =======
|
||||||
|
export { PiFediverseLogo as IconContextSelection } from 'react-icons/pi';
|
||||||
|
export { ImMakeGroup as IconGroupSelection } from 'react-icons/im';
|
||||||
export { BiCollapse as IconGraphCollapse } from 'react-icons/bi';
|
export { BiCollapse as IconGraphCollapse } from 'react-icons/bi';
|
||||||
export { BiExpand as IconGraphExpand } from 'react-icons/bi';
|
export { BiExpand as IconGraphExpand } from 'react-icons/bi';
|
||||||
export { LuMaximize as IconGraphMaximize } from 'react-icons/lu';
|
export { TiArrowMaximise as IconGraphMaximize } from 'react-icons/ti';
|
||||||
export { BiGitBranch as IconGraphInputs } from 'react-icons/bi';
|
export { BiGitBranch as IconGraphInputs } from 'react-icons/bi';
|
||||||
export { TbEarScan as IconGraphInverse } from 'react-icons/tb';
|
export { TbEarScan as IconGraphInverse } from 'react-icons/tb';
|
||||||
export { BiGitMerge as IconGraphOutputs } from 'react-icons/bi';
|
export { BiGitMerge as IconGraphOutputs } from 'react-icons/bi';
|
||||||
|
|
|
@ -40,12 +40,20 @@ export function generateSample(target: string): string {
|
||||||
export function varSchema(schema: IRSForm): string {
|
export function varSchema(schema: IRSForm): string {
|
||||||
let result = `Название концептуальной схемы: ${schema.title}\n`;
|
let result = `Название концептуальной схемы: ${schema.title}\n`;
|
||||||
result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
|
result += `[${schema.alias}] Описание: "${schema.description}"\n\n`;
|
||||||
result += 'Понятия:\n';
|
result += 'Конституенты:\n';
|
||||||
schema.items.forEach(item => {
|
schema.items.forEach(item => {
|
||||||
result += `\n${item.alias} - "${labelCstTypification(item)}" - "${item.term_resolved}" - "${
|
result += `\n${item.alias} - "${labelCstTypification(item)}" - "${item.term_resolved}" - "${
|
||||||
item.definition_formal
|
item.definition_formal
|
||||||
}" - "${item.definition_resolved}" - "${item.convention}"`;
|
}" - "${item.definition_resolved}" - "${item.convention}"`;
|
||||||
});
|
});
|
||||||
|
if (schema.stats.count_crucial > 0) {
|
||||||
|
result +=
|
||||||
|
'\nКлючевые конституенты: ' +
|
||||||
|
schema.items
|
||||||
|
.filter(cst => cst.crucial)
|
||||||
|
.map(cst => cst.alias)
|
||||||
|
.join(', ');
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
IconChild,
|
IconChild,
|
||||||
IconConsolidation,
|
IconConsolidation,
|
||||||
|
IconCrucial,
|
||||||
IconCstAxiom,
|
IconCstAxiom,
|
||||||
IconCstBaseSet,
|
IconCstBaseSet,
|
||||||
IconCstConstSet,
|
IconCstConstSet,
|
||||||
|
@ -91,6 +92,11 @@ export function HelpThesaurus() {
|
||||||
родоструктурной экспликации являются Термин, Конвенция, Типизация (Структура), Формальное определение, Текстовое
|
родоструктурной экспликации являются Термин, Конвенция, Типизация (Структура), Формальное определение, Текстовое
|
||||||
определение, Комментарий.
|
определение, Комментарий.
|
||||||
</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
<IconCrucial size='1rem' className='inline-icon' /> Ключевая конституента используется как маркер для
|
||||||
|
обозначения содержательно значимых конституент. Ключевые конституенты выделяются визуально и используются при
|
||||||
|
фильтрации.
|
||||||
|
</p>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
IconChild,
|
IconChild,
|
||||||
IconClone,
|
IconClone,
|
||||||
|
IconContextSelection,
|
||||||
|
IconCrucial,
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconEdit,
|
|
||||||
IconFilter,
|
IconFilter,
|
||||||
IconGraphSelection,
|
|
||||||
IconKeyboard,
|
IconKeyboard,
|
||||||
IconLeftOpen,
|
IconLeftOpen,
|
||||||
IconMoveDown,
|
IconMoveDown,
|
||||||
|
@ -27,6 +27,13 @@ export function HelpRSEditor() {
|
||||||
return (
|
return (
|
||||||
<div className='dense'>
|
<div className='dense'>
|
||||||
<h1>Редактор конституенты</h1>
|
<h1>Редактор конституенты</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<IconCrucial className='inline-icon' /> статус ключевой конституенты
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div className='flex flex-col sm:flex-row sm:gap-3'>
|
<div className='flex flex-col sm:flex-row sm:gap-3'>
|
||||||
<div>
|
<div>
|
||||||
<h2>Команды</h2>
|
<h2>Команды</h2>
|
||||||
|
@ -69,7 +76,7 @@ export function HelpRSEditor() {
|
||||||
<IconFilter className='inline-icon' /> фильтрация по атрибутам
|
<IconFilter className='inline-icon' /> фильтрация по атрибутам
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconGraphSelection className='inline-icon' /> фильтрация по графу термов
|
<IconContextSelection className='inline-icon' /> фильтрация по графу термов
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconChild className='inline-icon' /> отображение наследованных
|
<IconChild className='inline-icon' /> отображение наследованных
|
||||||
|
@ -114,8 +121,7 @@ export function HelpRSEditor() {
|
||||||
<h2>Термин и Текстовое определение</h2>
|
<h2>Термин и Текстовое определение</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<IconEdit className='inline-icon' /> редактирование{' '}
|
<kbd>Клик</kbd> редактирование <LinkTopic text='Имени' topic={HelpTopic.CC_CONSTITUENTA} /> /{' '}
|
||||||
<LinkTopic text='Имени' topic={HelpTopic.CC_CONSTITUENTA} /> /{' '}
|
|
||||||
<LinkTopic text='Термина' topic={HelpTopic.CC_CONSTITUENTA} />
|
<LinkTopic text='Термина' topic={HelpTopic.CC_CONSTITUENTA} />
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { Divider } from '@/components/container';
|
import { Divider } from '@/components/container';
|
||||||
import {
|
import {
|
||||||
|
IconChild,
|
||||||
IconClustering,
|
IconClustering,
|
||||||
|
IconContextSelection,
|
||||||
|
IconCrucial,
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconEdit,
|
IconEdit,
|
||||||
IconFilter,
|
IconFilter,
|
||||||
|
@ -12,7 +15,7 @@ import {
|
||||||
IconGraphInputs,
|
IconGraphInputs,
|
||||||
IconGraphMaximize,
|
IconGraphMaximize,
|
||||||
IconGraphOutputs,
|
IconGraphOutputs,
|
||||||
IconGraphSelection,
|
IconGroupSelection,
|
||||||
IconNewItem,
|
IconNewItem,
|
||||||
IconOSS,
|
IconOSS,
|
||||||
IconPredecessor,
|
IconPredecessor,
|
||||||
|
@ -103,7 +106,7 @@ export function HelpRSGraphTerm() {
|
||||||
<h2>Выделение</h2>
|
<h2>Выделение</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<IconGraphSelection className='inline-icon' /> выделить связанные...
|
<IconContextSelection className='inline-icon' /> выделить связанные...
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconGraphCollapse className='inline-icon' /> все влияющие
|
<IconGraphCollapse className='inline-icon' /> все влияющие
|
||||||
|
@ -120,13 +123,23 @@ export function HelpRSGraphTerm() {
|
||||||
<li>
|
<li>
|
||||||
<IconGraphOutputs className='inline-icon' /> исходящие напрямую
|
<IconGraphOutputs className='inline-icon' /> исходящие напрямую
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconGroupSelection className='inline-icon' /> выделить группы...
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconGraphCore className='inline-icon' /> выделить <LinkTopic text='Ядро' topic={HelpTopic.CC_SYSTEM} />
|
<IconGraphCore className='inline-icon' /> выделить <LinkTopic text='Ядро' topic={HelpTopic.CC_SYSTEM} />
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconCrucial className='inline-icon' /> выделить ключевые
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<IconPredecessor className='inline-icon' /> выделить{' '}
|
<IconPredecessor className='inline-icon' /> выделить{' '}
|
||||||
<LinkTopic text='собственные' topic={HelpTopic.CC_PROPAGATION} />
|
<LinkTopic text='собственные' topic={HelpTopic.CC_PROPAGATION} />
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<IconChild className='inline-icon' /> выделить{' '}
|
||||||
|
<LinkTopic text='наследники' topic={HelpTopic.CC_PROPAGATION} />
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -77,6 +77,7 @@ export function ToolbarSchema({
|
||||||
const targetType = activeCst?.cst_type ?? CstType.BASE;
|
const targetType = activeCst?.cst_type ?? CstType.BASE;
|
||||||
const data: ICreateConstituentaDTO = {
|
const data: ICreateConstituentaDTO = {
|
||||||
insert_after: activeCst?.id ?? null,
|
insert_after: activeCst?.id ?? null,
|
||||||
|
crucial: false,
|
||||||
cst_type: targetType,
|
cst_type: targetType,
|
||||||
alias: generateAlias(targetType, schema),
|
alias: generateAlias(targetType, schema),
|
||||||
term_raw: '',
|
term_raw: '',
|
||||||
|
@ -96,6 +97,7 @@ export function ToolbarSchema({
|
||||||
itemID: schema.id,
|
itemID: schema.id,
|
||||||
data: {
|
data: {
|
||||||
insert_after: activeCst.id,
|
insert_after: activeCst.id,
|
||||||
|
crucial: activeCst.crucial,
|
||||||
cst_type: activeCst.cst_type,
|
cst_type: activeCst.cst_type,
|
||||||
alias: generateAlias(activeCst.cst_type, schema),
|
alias: generateAlias(activeCst.cst_type, schema),
|
||||||
term_raw: activeCst.term_raw,
|
term_raw: activeCst.term_raw,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
type IRSFormUploadDTO,
|
type IRSFormUploadDTO,
|
||||||
type ISubstitutionsDTO,
|
type ISubstitutionsDTO,
|
||||||
type IUpdateConstituentaDTO,
|
type IUpdateConstituentaDTO,
|
||||||
|
type IUpdateCrucialDTO,
|
||||||
schemaConstituentaCreatedResponse,
|
schemaConstituentaCreatedResponse,
|
||||||
schemaExpressionParse,
|
schemaExpressionParse,
|
||||||
schemaProduceStructureResponse,
|
schemaProduceStructureResponse,
|
||||||
|
@ -79,6 +80,15 @@ export const rsformsApi = {
|
||||||
successMessage: infoMsg.changesSaved
|
successMessage: infoMsg.changesSaved
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
updateCrucial: ({ itemID, data }: { itemID: number; data: IUpdateCrucialDTO }) =>
|
||||||
|
axiosPatch<IUpdateCrucialDTO, IRSFormDTO>({
|
||||||
|
schema: schemaRSForm,
|
||||||
|
endpoint: `/api/rsforms/${itemID}/update-crucial`,
|
||||||
|
request: {
|
||||||
|
data: data,
|
||||||
|
successMessage: infoMsg.changesSaved
|
||||||
|
}
|
||||||
|
}),
|
||||||
deleteConstituents: ({ itemID, data }: { itemID: number; data: IConstituentaList }) =>
|
deleteConstituents: ({ itemID, data }: { itemID: number; data: IConstituentaList }) =>
|
||||||
axiosPatch<IConstituentaList, IRSFormDTO>({
|
axiosPatch<IConstituentaList, IRSFormDTO>({
|
||||||
schema: schemaRSForm,
|
schema: schemaRSForm,
|
||||||
|
|
|
@ -183,6 +183,7 @@ export class RSFormLoader {
|
||||||
const items = this.schema.items;
|
const items = this.schema.items;
|
||||||
return {
|
return {
|
||||||
count_all: items.length,
|
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_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_property: items.reduce((sum, cst) => sum + (cst.parse.valueClass === ValueClass.PROPERTY ? 1 : 0), 0),
|
||||||
count_incalculable: items.reduce(
|
count_incalculable: items.reduce(
|
||||||
|
|
|
@ -65,6 +65,9 @@ export type IConstituentaCreatedResponse = z.infer<typeof schemaConstituentaCrea
|
||||||
/** Represents data, used in updating persistent attributes in {@link IConstituenta}. */
|
/** Represents data, used in updating persistent attributes in {@link IConstituenta}. */
|
||||||
export type IUpdateConstituentaDTO = z.infer<typeof schemaUpdateConstituenta>;
|
export type IUpdateConstituentaDTO = z.infer<typeof schemaUpdateConstituenta>;
|
||||||
|
|
||||||
|
/** Represents data, used in batch updating crucial attributes in {@link IConstituenta}. */
|
||||||
|
export type IUpdateCrucialDTO = z.infer<typeof schemaUpdateCrucial>;
|
||||||
|
|
||||||
/** Represents data, used in ordering a list of {@link IConstituenta}. */
|
/** Represents data, used in ordering a list of {@link IConstituenta}. */
|
||||||
export interface IMoveConstituentsDTO {
|
export interface IMoveConstituentsDTO {
|
||||||
items: number[];
|
items: number[];
|
||||||
|
@ -276,6 +279,7 @@ export const schemaConstituentaBasics = z.strictObject({
|
||||||
id: z.coerce.number(),
|
id: z.coerce.number(),
|
||||||
alias: z.string().nonempty(errorMsg.requiredField),
|
alias: z.string().nonempty(errorMsg.requiredField),
|
||||||
convention: z.string(),
|
convention: z.string(),
|
||||||
|
crucial: z.boolean(),
|
||||||
cst_type: schemaCstType,
|
cst_type: schemaCstType,
|
||||||
definition_formal: z.string(),
|
definition_formal: z.string(),
|
||||||
definition_raw: z.string(),
|
definition_raw: z.string(),
|
||||||
|
@ -321,7 +325,8 @@ export const schemaVersionCreatedResponse = z.strictObject({
|
||||||
export const schemaCreateConstituenta = schemaConstituentaBasics
|
export const schemaCreateConstituenta = schemaConstituentaBasics
|
||||||
.pick({
|
.pick({
|
||||||
cst_type: true,
|
cst_type: true,
|
||||||
term_forms: true
|
term_forms: true,
|
||||||
|
crucial: true
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField),
|
||||||
|
@ -342,6 +347,7 @@ export const schemaUpdateConstituenta = z.strictObject({
|
||||||
item_data: z.strictObject({
|
item_data: z.strictObject({
|
||||||
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField).optional(),
|
alias: z.string().max(limits.len_alias, errorMsg.aliasLength).nonempty(errorMsg.requiredField).optional(),
|
||||||
cst_type: schemaCstType.optional(),
|
cst_type: schemaCstType.optional(),
|
||||||
|
crucial: z.boolean().optional(),
|
||||||
convention: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
convention: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||||
definition_formal: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
definition_formal: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||||
definition_raw: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
definition_raw: z.string().max(limits.len_description, errorMsg.descriptionLength).optional(),
|
||||||
|
@ -357,6 +363,11 @@ export const schemaUpdateConstituenta = z.strictObject({
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const schemaUpdateCrucial = z.strictObject({
|
||||||
|
target: z.array(z.number()),
|
||||||
|
value: z.boolean()
|
||||||
|
});
|
||||||
|
|
||||||
export const schemaProduceStructureResponse = z.strictObject({
|
export const schemaProduceStructureResponse = z.strictObject({
|
||||||
cst_list: z.array(z.number()),
|
cst_list: z.array(z.number()),
|
||||||
schema: schemaRSForm
|
schema: schemaRSForm
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
import { useUpdateTimestamp } from '@/features/library/backend/use-update-timestamp';
|
||||||
|
|
||||||
|
import { KEYS } from '@/backend/configuration';
|
||||||
|
|
||||||
|
import { rsformsApi } from './api';
|
||||||
|
import { type IUpdateCrucialDTO } from './types';
|
||||||
|
|
||||||
|
export const useUpdateCrucial = () => {
|
||||||
|
const client = useQueryClient();
|
||||||
|
const { updateTimestamp } = useUpdateTimestamp();
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationKey: [KEYS.global_mutation, rsformsApi.baseKey, 'update-crucial'],
|
||||||
|
mutationFn: rsformsApi.updateCrucial,
|
||||||
|
onSuccess: data => {
|
||||||
|
updateTimestamp(data.id, data.time_update);
|
||||||
|
client.setQueryData(rsformsApi.getRSFormQueryOptions({ itemID: data.id }).queryKey, data);
|
||||||
|
},
|
||||||
|
onError: () => client.invalidateQueries()
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
updateCrucial: (data: { itemID: number; data: IUpdateCrucialDTO }) => mutation.mutateAsync(data)
|
||||||
|
};
|
||||||
|
};
|
|
@ -26,6 +26,7 @@ export function BadgeConstituenta({ value, prefixID }: BadgeConstituentaProps) {
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'cc-badge-constituenta',
|
'cc-badge-constituenta',
|
||||||
value.is_inherited && 'border-dashed',
|
value.is_inherited && 'border-dashed',
|
||||||
|
value.crucial && 'cc-badge-inner-shadow',
|
||||||
value.cst_class === CstClass.BASIC ? 'bg-accent-green25' : 'bg-input'
|
value.cst_class === CstClass.BASIC ? 'bg-accent-green25' : 'bg-input'
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { type DomIconProps, IconCrucial } from '@/components/icons';
|
||||||
|
import { cn } from '@/components/utils';
|
||||||
|
|
||||||
|
export function IconCrucialValue({ value, size = '1.25rem', className }: DomIconProps<boolean>) {
|
||||||
|
if (value) {
|
||||||
|
return <IconCrucial size={size} className={cn('text-primary', className)} />;
|
||||||
|
} else {
|
||||||
|
return <IconCrucial size={size} className={cn('text-muted-foreground', className)} />;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
import {
|
import {
|
||||||
type DomIconProps,
|
type DomIconProps,
|
||||||
|
IconContextSelection,
|
||||||
IconGraphCollapse,
|
IconGraphCollapse,
|
||||||
IconGraphExpand,
|
IconGraphExpand,
|
||||||
IconGraphInputs,
|
IconGraphInputs,
|
||||||
IconGraphOutputs,
|
IconGraphOutputs
|
||||||
IconGraphSelection
|
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
|
|
||||||
import { DependencyMode } from '../stores/cst-search';
|
import { DependencyMode } from '../stores/cst-search';
|
||||||
|
@ -13,7 +13,7 @@ import { DependencyMode } from '../stores/cst-search';
|
||||||
export function IconDependencyMode({ value, size = '1.25rem', className }: DomIconProps<DependencyMode>) {
|
export function IconDependencyMode({ value, size = '1.25rem', className }: DomIconProps<DependencyMode>) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case DependencyMode.ALL:
|
case DependencyMode.ALL:
|
||||||
return <IconGraphSelection size={size} className={className} />;
|
return <IconContextSelection size={size} className={className} />;
|
||||||
case DependencyMode.OUTPUTS:
|
case DependencyMode.OUTPUTS:
|
||||||
return <IconGraphOutputs size={size} className={className ?? 'text-primary'} />;
|
return <IconGraphOutputs size={size} className={className ?? 'text-primary'} />;
|
||||||
case DependencyMode.INPUTS:
|
case DependencyMode.INPUTS:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { IconChild } from '@/components/icons';
|
import { IconChild, IconCrucial } from '@/components/icons';
|
||||||
import { cn } from '@/components/utils';
|
import { cn } from '@/components/utils';
|
||||||
|
|
||||||
import { labelCstTypification } from '../labels';
|
import { labelCstTypification } from '../labels';
|
||||||
|
@ -15,6 +15,7 @@ export function InfoConstituenta({ data, className, ...restProps }: InfoConstitu
|
||||||
<h2 className='cursor-default' title={data.is_inherited ? ' наследник' : undefined}>
|
<h2 className='cursor-default' title={data.is_inherited ? ' наследник' : undefined}>
|
||||||
{data.alias}
|
{data.alias}
|
||||||
{data.is_inherited ? <IconChild size='1rem' className='inline-icon align-middle ml-1 mt-1' /> : null}
|
{data.is_inherited ? <IconChild size='1rem' className='inline-icon align-middle ml-1 mt-1' /> : null}
|
||||||
|
{data.crucial ? <IconCrucial size='1rem' className='inline-icon align-middle mt-1' /> : null}
|
||||||
</h2>
|
</h2>
|
||||||
{data.term_resolved ? (
|
{data.term_resolved ? (
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -116,7 +116,8 @@ export function PickMultiConstituenta({
|
||||||
const cst = schema.cstByID.get(cstID);
|
const cst = schema.cstByID.get(cstID);
|
||||||
return !!cst && isBasicConcept(cst.cst_type);
|
return !!cst && isBasicConcept(cst.cst_type);
|
||||||
}}
|
}}
|
||||||
isOwned={cstID => !schema.cstByID.get(cstID)?.is_inherited}
|
isCrucial={cstID => schema.cstByID.get(cstID)?.crucial ?? false}
|
||||||
|
isInherited={cstID => schema.cstByID.get(cstID)?.is_inherited ?? false}
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className='w-fit'
|
className='w-fit'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
IconChild,
|
IconChild,
|
||||||
IconConvention,
|
IconConvention,
|
||||||
|
IconCrucial,
|
||||||
IconCstAxiom,
|
IconCstAxiom,
|
||||||
IconCstBaseSet,
|
IconCstBaseSet,
|
||||||
IconCstConstSet,
|
IconCstConstSet,
|
||||||
|
@ -113,6 +114,12 @@ export function RSFormStats({ className, stats }: RSFormStatsProps) {
|
||||||
value={stats.count_theorem}
|
value={stats.count_theorem}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ValueStats
|
||||||
|
id='count_crucial'
|
||||||
|
title='Ключевые'
|
||||||
|
icon={<IconCrucial size='1.25rem' />}
|
||||||
|
value={stats.count_crucial}
|
||||||
|
/>
|
||||||
<ValueStats
|
<ValueStats
|
||||||
id='count_text_term'
|
id='count_text_term'
|
||||||
title='Термины'
|
title='Термины'
|
||||||
|
|
|
@ -30,6 +30,7 @@ export function TGNode(node: TGNodeInternal) {
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-full h-full cursor-default flex items-center justify-center rounded-full',
|
'w-full h-full cursor-default flex items-center justify-center rounded-full',
|
||||||
|
node.data.cst.crucial && 'text-primary',
|
||||||
node.data.focused && 'border-[2px] border-selected',
|
node.data.focused && 'border-[2px] border-selected',
|
||||||
label.length > LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]'
|
label.length > LABEL_THRESHOLD ? 'text-[12px]/[16px]' : 'text-[14px]/[20px]'
|
||||||
)}
|
)}
|
||||||
|
@ -50,6 +51,7 @@ export function TGNode(node: TGNodeInternal) {
|
||||||
{description ? (
|
{description ? (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
node.data.cst.crucial && 'text-primary',
|
||||||
'mt-[4px] w-[150px] px-[4px] text-center translate-x-[calc(-50%+20px)]',
|
'mt-[4px] w-[150px] px-[4px] text-center translate-x-[calc(-50%+20px)]',
|
||||||
'pointer-events-none',
|
'pointer-events-none',
|
||||||
description.length > DESCRIPTION_THRESHOLD ? 'text-[10px]/[12px]' : 'text-[12px]/[16px]'
|
description.length > DESCRIPTION_THRESHOLD ? 'text-[10px]/[12px]' : 'text-[12px]/[16px]'
|
||||||
|
@ -69,9 +71,11 @@ export function TGNode(node: TGNodeInternal) {
|
||||||
|
|
||||||
// ====== INTERNAL ======
|
// ====== INTERNAL ======
|
||||||
function describeCstNode(cst: IConstituenta) {
|
function describeCstNode(cst: IConstituenta) {
|
||||||
return `${cst.alias}: ${cst.term_resolved}</br><b>Типизация:</b> ${labelCstTypification(
|
const contents = isBasicConcept(cst.cst_type)
|
||||||
cst
|
? cst.convention
|
||||||
)}</br><b>Содержание:</b> ${
|
: cst.definition_resolved || cst.definition_formal || cst.convention;
|
||||||
isBasicConcept(cst.cst_type) ? 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 : 'отсутствует'
|
||||||
}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import { MiniButton } from '@/components/control';
|
import { MiniButton } from '@/components/control';
|
||||||
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
|
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
|
||||||
import {
|
import {
|
||||||
|
IconChild,
|
||||||
|
IconContextSelection,
|
||||||
|
IconCrucial,
|
||||||
IconGraphCollapse,
|
IconGraphCollapse,
|
||||||
IconGraphCore,
|
IconGraphCore,
|
||||||
IconGraphExpand,
|
IconGraphExpand,
|
||||||
|
@ -8,7 +11,7 @@ import {
|
||||||
IconGraphInverse,
|
IconGraphInverse,
|
||||||
IconGraphMaximize,
|
IconGraphMaximize,
|
||||||
IconGraphOutputs,
|
IconGraphOutputs,
|
||||||
IconGraphSelection,
|
IconGroupSelection,
|
||||||
IconPredecessor,
|
IconPredecessor,
|
||||||
IconReset
|
IconReset
|
||||||
} from '@/components/icons';
|
} from '@/components/icons';
|
||||||
|
@ -21,7 +24,8 @@ interface ToolbarGraphSelectionProps extends Styling {
|
||||||
onChange: (newSelection: number[]) => void;
|
onChange: (newSelection: number[]) => void;
|
||||||
graph: Graph;
|
graph: Graph;
|
||||||
isCore: (item: number) => boolean;
|
isCore: (item: number) => boolean;
|
||||||
isOwned?: (item: number) => boolean;
|
isCrucial: (item: number) => boolean;
|
||||||
|
isInherited: (item: number) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ToolbarGraphSelection({
|
export function ToolbarGraphSelection({
|
||||||
|
@ -29,20 +33,66 @@ export function ToolbarGraphSelection({
|
||||||
graph,
|
graph,
|
||||||
value: selected,
|
value: selected,
|
||||||
isCore,
|
isCore,
|
||||||
isOwned,
|
isInherited,
|
||||||
|
isCrucial,
|
||||||
onChange,
|
onChange,
|
||||||
...restProps
|
...restProps
|
||||||
}: ToolbarGraphSelectionProps) {
|
}: ToolbarGraphSelectionProps) {
|
||||||
const menu = useDropdown();
|
const selectedMenu = useDropdown();
|
||||||
|
const groupMenu = useDropdown();
|
||||||
const emptySelection = selected.length === 0;
|
const emptySelection = selected.length === 0;
|
||||||
|
|
||||||
|
function handleSelectReset() {
|
||||||
|
onChange([]);
|
||||||
|
}
|
||||||
|
|
||||||
function handleSelectCore() {
|
function handleSelectCore() {
|
||||||
|
groupMenu.hide();
|
||||||
const core = [...graph.nodes.keys()].filter(isCore);
|
const core = [...graph.nodes.keys()].filter(isCore);
|
||||||
onChange([...core, ...graph.expandInputs(core)]);
|
onChange([...core, ...graph.expandInputs(core)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelectOwned() {
|
function handleSelectOwned() {
|
||||||
if (isOwned) onChange([...graph.nodes.keys()].filter(isOwned));
|
groupMenu.hide();
|
||||||
|
onChange([...graph.nodes.keys()].filter((item: number) => !isInherited(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectInherited() {
|
||||||
|
groupMenu.hide();
|
||||||
|
onChange([...graph.nodes.keys()].filter(isInherited));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectCrucial() {
|
||||||
|
groupMenu.hide();
|
||||||
|
onChange([...graph.nodes.keys()].filter(isCrucial));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleExpandOutputs() {
|
||||||
|
onChange([...selected, ...graph.expandOutputs(selected)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleExpandInputs() {
|
||||||
|
onChange([...selected, ...graph.expandInputs(selected)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectMaximize() {
|
||||||
|
selectedMenu.hide();
|
||||||
|
onChange(graph.maximizePart(selected));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectInvert() {
|
||||||
|
selectedMenu.hide();
|
||||||
|
onChange([...graph.nodes.keys()].filter(item => !selected.includes(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectAllInputs() {
|
||||||
|
selectedMenu.hide();
|
||||||
|
onChange([...graph.expandInputs(selected)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSelectAllOutputs() {
|
||||||
|
selectedMenu.hide();
|
||||||
|
onChange([...graph.expandOutputs(selected)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -50,73 +100,99 @@ export function ToolbarGraphSelection({
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Сбросить выделение'
|
title='Сбросить выделение'
|
||||||
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
icon={<IconReset size='1.25rem' className='icon-primary' />}
|
||||||
onClick={() => onChange([])}
|
onClick={handleSelectReset}
|
||||||
disabled={emptySelection}
|
disabled={emptySelection}
|
||||||
/>
|
/>
|
||||||
<div ref={menu.ref} onBlur={menu.handleBlur} className='flex items-center relative'>
|
|
||||||
|
<div ref={selectedMenu.ref} onBlur={selectedMenu.handleBlur} className='flex items-center relative'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Выделить...'
|
title='Выделить на основе выбранных...'
|
||||||
hideTitle={menu.isOpen}
|
hideTitle={selectedMenu.isOpen}
|
||||||
icon={<IconGraphSelection size='1.25rem' className='icon-primary' />}
|
icon={<IconContextSelection size='1.25rem' className='icon-primary' />}
|
||||||
onClick={menu.toggle}
|
onClick={selectedMenu.toggle}
|
||||||
disabled={emptySelection}
|
disabled={emptySelection}
|
||||||
/>
|
/>
|
||||||
<Dropdown isOpen={menu.isOpen} className='-translate-x-1/2'>
|
<Dropdown isOpen={selectedMenu.isOpen} className='-translate-x-1/2'>
|
||||||
<DropdownButton
|
|
||||||
text='Влияющие'
|
|
||||||
title='Выделить все влияющие'
|
|
||||||
icon={<IconGraphCollapse size='1.25rem' className='icon-primary' />}
|
|
||||||
onClick={() => onChange([...selected, ...graph.expandAllInputs(selected)])}
|
|
||||||
disabled={emptySelection}
|
|
||||||
/>
|
|
||||||
<DropdownButton
|
|
||||||
text='Зависимые'
|
|
||||||
title='Выделить все зависимые'
|
|
||||||
icon={<IconGraphExpand size='1.25rem' className='icon-primary' />}
|
|
||||||
onClick={() => onChange([...selected, ...graph.expandAllOutputs(selected)])}
|
|
||||||
disabled={emptySelection}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Поставщики'
|
text='Поставщики'
|
||||||
title='Выделить поставщиков'
|
title='Выделить поставщиков'
|
||||||
icon={<IconGraphInputs size='1.25rem' className='icon-primary' />}
|
icon={<IconGraphInputs size='1.25rem' className='icon-primary' />}
|
||||||
onClick={() => onChange([...selected, ...graph.expandInputs(selected)])}
|
onClick={handleExpandInputs}
|
||||||
disabled={emptySelection}
|
disabled={emptySelection}
|
||||||
/>
|
/>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Потребители'
|
text='Потребители'
|
||||||
title='Выделить потребителей'
|
title='Выделить потребителей'
|
||||||
icon={<IconGraphOutputs size='1.25rem' className='icon-primary' />}
|
icon={<IconGraphOutputs size='1.25rem' className='icon-primary' />}
|
||||||
onClick={() => onChange([...selected, ...graph.expandOutputs(selected)])}
|
onClick={handleExpandOutputs}
|
||||||
disabled={emptySelection}
|
disabled={emptySelection}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<DropdownButton
|
||||||
|
text='Влияющие'
|
||||||
|
title='Выделить все влияющие'
|
||||||
|
icon={<IconGraphCollapse size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleSelectAllInputs}
|
||||||
|
disabled={emptySelection}
|
||||||
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='Зависимые'
|
||||||
|
title='Выделить все зависимые'
|
||||||
|
icon={<IconGraphExpand size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleSelectAllOutputs}
|
||||||
|
disabled={emptySelection}
|
||||||
|
/>
|
||||||
|
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
text='Максимизация'
|
text='Максимизация'
|
||||||
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
|
titleHtml='<b>Максимизация</b> <br/>дополнение выделения конституентами, <br/>зависимыми только от выделенных'
|
||||||
aria-label='Максимизация - дополнение выделения конституентами, зависимыми только от выделенных'
|
aria-label='Максимизация - дополнение выделения конституентами, зависимыми только от выделенных'
|
||||||
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
|
icon={<IconGraphMaximize size='1.25rem' className='icon-primary' />}
|
||||||
onClick={() => onChange(graph.maximizePart(selected))}
|
onClick={handleSelectMaximize}
|
||||||
disabled={emptySelection}
|
disabled={emptySelection}
|
||||||
/>
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='Инвертировать'
|
||||||
|
icon={<IconGraphInverse size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleSelectInvert}
|
||||||
|
/>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MiniButton
|
<div ref={groupMenu.ref} onBlur={groupMenu.handleBlur} className='flex items-center relative'>
|
||||||
title='Выделить ядро'
|
<MiniButton
|
||||||
icon={<IconGraphCore size='1.25rem' className='icon-primary' />}
|
title='Выделить группу...'
|
||||||
onClick={handleSelectCore}
|
hideTitle={groupMenu.isOpen}
|
||||||
/>
|
icon={<IconGroupSelection size='1.25rem' className='icon-primary' />}
|
||||||
<MiniButton
|
onClick={groupMenu.toggle}
|
||||||
title='Выделить собственные'
|
/>
|
||||||
icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
|
<Dropdown isOpen={groupMenu.isOpen} className='-translate-x-1/2'>
|
||||||
onClick={handleSelectOwned}
|
<DropdownButton
|
||||||
/>
|
text='ядро'
|
||||||
<MiniButton
|
title='Выделить ядро'
|
||||||
title='Инвертировать'
|
icon={<IconGraphCore size='1.25rem' className='icon-primary' />}
|
||||||
icon={<IconGraphInverse size='1.25rem' className='icon-primary' />}
|
onClick={handleSelectCore}
|
||||||
onClick={() => onChange([...graph.nodes.keys()].filter(item => !selected.includes(item)))}
|
/>
|
||||||
/>
|
<DropdownButton
|
||||||
|
text='ключевые'
|
||||||
|
title='Выделить ключевые'
|
||||||
|
icon={<IconCrucial size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleSelectCrucial}
|
||||||
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='собственные'
|
||||||
|
title='Выделить собственные'
|
||||||
|
icon={<IconPredecessor size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleSelectOwned}
|
||||||
|
/>
|
||||||
|
<DropdownButton
|
||||||
|
text='наследники'
|
||||||
|
title='Выделить наследников'
|
||||||
|
icon={<IconChild size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleSelectInherited}
|
||||||
|
/>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,11 @@ import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||||
import { HelpTopic } from '@/features/help';
|
import { HelpTopic } from '@/features/help';
|
||||||
import { BadgeHelp } from '@/features/help/components/badge-help';
|
import { BadgeHelp } from '@/features/help/components/badge-help';
|
||||||
|
|
||||||
|
import { MiniButton } from '@/components/control';
|
||||||
import { TextArea, TextInput } from '@/components/input';
|
import { TextArea, TextInput } from '@/components/input';
|
||||||
|
|
||||||
import { CstType, type ICreateConstituentaDTO } from '../../backend/types';
|
import { CstType, type ICreateConstituentaDTO } from '../../backend/types';
|
||||||
|
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||||
import { RSInput } from '../../components/rs-input';
|
import { RSInput } from '../../components/rs-input';
|
||||||
import { SelectCstType } from '../../components/select-cst-type';
|
import { SelectCstType } from '../../components/select-cst-type';
|
||||||
import { getRSDefinitionPlaceholder } from '../../labels';
|
import { getRSDefinitionPlaceholder } from '../../labels';
|
||||||
|
@ -30,6 +32,7 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
|
||||||
|
|
||||||
const cst_type = useWatch({ control, name: 'cst_type' });
|
const cst_type = useWatch({ control, name: 'cst_type' });
|
||||||
const convention = useWatch({ control, name: 'convention' });
|
const convention = useWatch({ control, name: 'convention' });
|
||||||
|
const crucial = useWatch({ control, name: 'crucial' });
|
||||||
const isBasic = isBasicConcept(cst_type);
|
const isBasic = isBasicConcept(cst_type);
|
||||||
const isElementary = isBaseSet(cst_type);
|
const isElementary = isBaseSet(cst_type);
|
||||||
const isFunction = isFunctional(cst_type);
|
const isFunction = isFunctional(cst_type);
|
||||||
|
@ -41,9 +44,18 @@ export function FormCreateCst({ schema }: FormCreateCstProps) {
|
||||||
setForceComment(false);
|
setForceComment(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleToggleCrucial() {
|
||||||
|
setValue('crucial', !crucial);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex items-center self-center gap-3'>
|
<div className='flex items-center self-center gap-3'>
|
||||||
|
<MiniButton
|
||||||
|
title='Ключевая конституента'
|
||||||
|
icon={<IconCrucialValue size='1.25rem' value={crucial} />}
|
||||||
|
onClick={handleToggleCrucial}
|
||||||
|
/>
|
||||||
<SelectCstType
|
<SelectCstType
|
||||||
id='dlg_cst_type' //
|
id='dlg_cst_type' //
|
||||||
value={cst_type}
|
value={cst_type}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
|
import { MiniButton } from '@/components/control';
|
||||||
import { TextArea, TextInput } from '@/components/input';
|
import { TextArea, TextInput } from '@/components/input';
|
||||||
|
|
||||||
import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
|
import { CstType, type IUpdateConstituentaDTO } from '../../backend/types';
|
||||||
|
import { IconCrucialValue } from '../../components/icon-crucial-value';
|
||||||
import { SelectCstType } from '../../components/select-cst-type';
|
import { SelectCstType } from '../../components/select-cst-type';
|
||||||
import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels';
|
import { getRSDefinitionPlaceholder, labelCstTypification } from '../../labels';
|
||||||
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
import { type IConstituenta, type IRSForm } from '../../models/rsform';
|
||||||
|
@ -26,6 +28,7 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
||||||
|
|
||||||
const cst_type = useWatch({ control, name: 'item_data.cst_type' }) ?? CstType.BASE;
|
const cst_type = useWatch({ control, name: 'item_data.cst_type' }) ?? CstType.BASE;
|
||||||
const convention = useWatch({ control, name: 'item_data.convention' });
|
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);
|
||||||
const isElementary = isBaseSet(cst_type);
|
const isElementary = isBaseSet(cst_type);
|
||||||
const isFunction = isFunctional(cst_type);
|
const isFunction = isFunctional(cst_type);
|
||||||
|
@ -37,9 +40,18 @@ export function FormEditCst({ target, schema }: FormEditCstProps) {
|
||||||
setForceComment(false);
|
setForceComment(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleToggleCrucial() {
|
||||||
|
setValue('item_data.crucial', !crucial);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='flex items-center self-center gap-3'>
|
<div className='flex items-center self-center gap-3'>
|
||||||
|
<MiniButton
|
||||||
|
title='Ключевая конституента'
|
||||||
|
icon={<IconCrucialValue size='1.25rem' value={crucial} />}
|
||||||
|
onClick={handleToggleCrucial}
|
||||||
|
/>
|
||||||
<SelectCstType
|
<SelectCstType
|
||||||
id='dlg_cst_type' //
|
id='dlg_cst_type' //
|
||||||
value={cst_type}
|
value={cst_type}
|
||||||
|
|
|
@ -47,6 +47,7 @@ export interface TermForm {
|
||||||
/** Represents Constituenta. */
|
/** Represents Constituenta. */
|
||||||
export interface IConstituenta {
|
export interface IConstituenta {
|
||||||
id: number;
|
id: number;
|
||||||
|
crucial: boolean;
|
||||||
alias: string;
|
alias: string;
|
||||||
convention: string;
|
convention: string;
|
||||||
cst_type: CstType;
|
cst_type: CstType;
|
||||||
|
@ -102,6 +103,8 @@ export interface IConstituenta {
|
||||||
/** Represents {@link IRSForm} statistics. */
|
/** Represents {@link IRSForm} statistics. */
|
||||||
export interface IRSFormStats {
|
export interface IRSFormStats {
|
||||||
count_all: number;
|
count_all: number;
|
||||||
|
count_crucial: number;
|
||||||
|
|
||||||
count_errors: number;
|
count_errors: number;
|
||||||
count_property: number;
|
count_property: number;
|
||||||
count_incalculable: number;
|
count_incalculable: number;
|
||||||
|
|
|
@ -107,7 +107,7 @@ export function EditorConstituenta() {
|
||||||
isNarrow={isNarrow}
|
isNarrow={isNarrow}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='mx-0 min-w-140 md:mx-auto pt-8 md:w-195 shrink-0 xs:pt-0'>
|
<div className='mx-0 min-w-120 md:mx-auto pt-8 md:w-195 shrink-0 xs:pt-0'>
|
||||||
{activeCst ? (
|
{activeCst ? (
|
||||||
<FormConstituenta
|
<FormConstituenta
|
||||||
key={activeCst.id}
|
key={activeCst.id}
|
||||||
|
|
|
@ -6,8 +6,12 @@ import { Controller, useForm } from 'react-hook-form';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
|
||||||
|
import { useUpdateCrucial } from '@/features/rsform/backend/use-update-crucial';
|
||||||
|
import { IconCrucialValue } from '@/features/rsform/components/icon-crucial-value';
|
||||||
|
|
||||||
import { MiniButton, SubmitButton } from '@/components/control';
|
import { MiniButton, SubmitButton } from '@/components/control';
|
||||||
import { IconChild, IconEdit, IconPredecessor, IconSave } from '@/components/icons';
|
import { TextButton } from '@/components/control/text-button';
|
||||||
|
import { IconChild, IconPredecessor, IconSave } from '@/components/icons';
|
||||||
import { TextArea } from '@/components/input';
|
import { TextArea } from '@/components/input';
|
||||||
import { Indicator } from '@/components/view';
|
import { Indicator } from '@/components/view';
|
||||||
import { useDialogsStore } from '@/stores/dialogs';
|
import { useDialogsStore } from '@/stores/dialogs';
|
||||||
|
@ -46,7 +50,8 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
||||||
const setIsModified = useModificationStore(state => state.setIsModified);
|
const setIsModified = useModificationStore(state => state.setIsModified);
|
||||||
const isProcessing = useMutatingRSForm();
|
const isProcessing = useMutatingRSForm();
|
||||||
|
|
||||||
const { updateConstituenta: cstUpdate } = useUpdateConstituenta();
|
const { updateConstituenta } = useUpdateConstituenta();
|
||||||
|
const { updateCrucial } = useUpdateCrucial();
|
||||||
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
|
const showTypification = useDialogsStore(state => state.showShowTypeGraph);
|
||||||
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
const showEditTerm = useDialogsStore(state => state.showEditWordForms);
|
||||||
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
const showRenameCst = useDialogsStore(state => state.showRenameCst);
|
||||||
|
@ -128,7 +133,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSubmit(data: IUpdateConstituentaDTO) {
|
function onSubmit(data: IUpdateConstituentaDTO) {
|
||||||
void cstUpdate({ itemID: schema.id, data }).then(() => {
|
void updateConstituenta({ itemID: schema.id, data }).then(() => {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
reset({ ...data });
|
reset({ ...data });
|
||||||
});
|
});
|
||||||
|
@ -158,33 +163,48 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
||||||
showRenameCst({ schema: schema, target: activeCst });
|
showRenameCst({ schema: schema, target: activeCst });
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
function handleToggleCrucial() {
|
||||||
<form id={id} className='relative cc-column mt-1 px-6 py-1' onSubmit={event => void handleSubmit(onSubmit)(event)}>
|
void updateCrucial({
|
||||||
{!disabled || isProcessing ? (
|
itemID: schema.id,
|
||||||
<MiniButton
|
data: {
|
||||||
title={isModified ? tooltipText.unsaved : 'Редактировать словоформы термина'}
|
target: [activeCst.id],
|
||||||
aria-label='Редактировать словоформы термина'
|
value: !activeCst.crucial
|
||||||
onClick={handleEditTermForms}
|
}
|
||||||
className='absolute z-pop top-0 left-[calc(7ch+4px)]'
|
});
|
||||||
icon={<IconEdit size='1rem' className='icon-primary' />}
|
}
|
||||||
disabled={isModified}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className='absolute z-pop top-0 left-[calc(7ch+4px+3rem)] flex select-none'>
|
return (
|
||||||
<div className='pt-1 text-sm font-medium min-w-16 whitespace-nowrap select-text cursor-default'>
|
<form
|
||||||
<span>Имя </span>
|
id={id}
|
||||||
<span className='ml-1'>{activeCst?.alias ?? ''}</span>
|
className='relative cc-column mt-1 px-6 pb-1 pt-8'
|
||||||
|
onSubmit={event => void handleSubmit(onSubmit)(event)}
|
||||||
|
>
|
||||||
|
<div className='absolute z-pop top-0 left-6 flex select-text font-medium whitespace-nowrap pt-1'>
|
||||||
|
<TextButton
|
||||||
|
text='Термин' //
|
||||||
|
title={disabled ? undefined : isModified ? tooltipText.unsaved : 'Редактировать словоформы термина'}
|
||||||
|
onClick={handleEditTermForms}
|
||||||
|
disabled={isModified || disabled}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<MiniButton
|
||||||
|
title={activeCst.crucial ? 'Ключевая: да' : 'Ключевая: нет'}
|
||||||
|
className='ml-6 mr-1 -mt-0.75'
|
||||||
|
aria-label='Переключатель статуса ключевой конституенты'
|
||||||
|
icon={<IconCrucialValue size='1rem' value={activeCst.crucial} />}
|
||||||
|
onClick={handleToggleCrucial}
|
||||||
|
disabled={disabled || isProcessing || isModified}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextButton
|
||||||
|
text='Имя' //
|
||||||
|
title={disabled ? undefined : isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
|
||||||
|
onClick={handleRenameCst}
|
||||||
|
disabled={isModified || disabled}
|
||||||
|
/>
|
||||||
|
<div className='ml-2 text-sm font-medium whitespace-nowrap select-text cursor-default'>
|
||||||
|
{activeCst?.alias ?? ''}
|
||||||
</div>
|
</div>
|
||||||
{!disabled || isProcessing ? (
|
|
||||||
<MiniButton
|
|
||||||
title={isModified ? tooltipText.unsaved : 'Переименовать конституенту'}
|
|
||||||
aria-label='Переименовать конституенту'
|
|
||||||
onClick={handleRenameCst}
|
|
||||||
icon={<IconEdit size='1rem' className='icon-primary' />}
|
|
||||||
disabled={isModified}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
|
@ -193,7 +213,7 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<RefsInput
|
<RefsInput
|
||||||
id='cst_term'
|
id='cst_term'
|
||||||
label='Термин'
|
aria-label='Термин'
|
||||||
maxHeight='8rem'
|
maxHeight='8rem'
|
||||||
placeholder={disabled ? '' : 'Обозначение для текстовых определений'}
|
placeholder={disabled ? '' : 'Обозначение для текстовых определений'}
|
||||||
schema={schema}
|
schema={schema}
|
||||||
|
@ -285,14 +305,10 @@ export function FormConstituenta({ disabled, id, toggleReset, schema, activeCst,
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!showConvention && (!disabled || isProcessing) ? (
|
{!showConvention && (!disabled || isProcessing) ? (
|
||||||
<button
|
<TextButton
|
||||||
type='button'
|
text='Добавить комментарий' //
|
||||||
tabIndex={-1}
|
|
||||||
className='self-start cc-label text-primary hover:underline select-none'
|
|
||||||
onClick={() => setForceComment(true)}
|
onClick={() => setForceComment(true)}
|
||||||
>
|
/>
|
||||||
Добавить комментарий
|
|
||||||
</button>
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!disabled || isProcessing ? (
|
{!disabled || isProcessing ? (
|
||||||
|
|
|
@ -41,11 +41,11 @@ export function StatusBar({ className, isModified, processing, activeCst, parseD
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('pl-34 xs:pl-8 flex gap-1', className)}>
|
<div className={cn('pl-22 xs:pl-8 flex gap-1', className)}>
|
||||||
<div
|
<div
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'w-40 h-7',
|
'w-32 h-7',
|
||||||
'px-2 flex items-center justify-center',
|
'px-2 flex items-center justify-center',
|
||||||
'border',
|
'border',
|
||||||
'select-none',
|
'select-none',
|
||||||
|
@ -64,9 +64,9 @@ export function StatusBar({ className, isModified, processing, activeCst, parseD
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{!processing ? (
|
{!processing ? (
|
||||||
<div className='cc-fade-in flex items-center gap-2'>
|
<div className='cc-fade-in flex items-center gap-1'>
|
||||||
<IconExpressionStatus size='1rem' value={status} />
|
<IconExpressionStatus size='1rem' value={status} />
|
||||||
<span className='pb-0.5 font-controls pr-2'>{labelExpressionStatus(status)}</span>
|
<span className='font-controls pr-1 text-sm'>{labelExpressionStatus(status)}</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
import { HelpTopic } from '@/features/help';
|
import { HelpTopic } from '@/features/help';
|
||||||
import { BadgeHelp } from '@/features/help/components/badge-help';
|
import { BadgeHelp } from '@/features/help/components/badge-help';
|
||||||
import { MiniSelectorOSS } from '@/features/library/components/mini-selector-oss';
|
import { MiniSelectorOSS } from '@/features/library/components/mini-selector-oss';
|
||||||
|
import { useUpdateCrucial } from '@/features/rsform/backend/use-update-crucial';
|
||||||
|
|
||||||
import { MiniButton } from '@/components/control';
|
import { MiniButton } from '@/components/control';
|
||||||
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
|
import { Dropdown, DropdownButton, useDropdown } from '@/components/dropdown';
|
||||||
import {
|
import {
|
||||||
IconClone,
|
IconClone,
|
||||||
|
IconCrucial,
|
||||||
IconDestroy,
|
IconDestroy,
|
||||||
IconMoveDown,
|
IconMoveDown,
|
||||||
IconMoveUp,
|
IconMoveUp,
|
||||||
|
@ -31,10 +33,12 @@ interface ToolbarRSListProps {
|
||||||
|
|
||||||
export function ToolbarRSList({ className }: ToolbarRSListProps) {
|
export function ToolbarRSList({ className }: ToolbarRSListProps) {
|
||||||
const isProcessing = useMutatingRSForm();
|
const isProcessing = useMutatingRSForm();
|
||||||
|
const { updateCrucial } = useUpdateCrucial();
|
||||||
const menu = useDropdown();
|
const menu = useDropdown();
|
||||||
const {
|
const {
|
||||||
schema,
|
schema,
|
||||||
selected,
|
selected,
|
||||||
|
activeCst,
|
||||||
navigateOss,
|
navigateOss,
|
||||||
deselectAll,
|
deselectAll,
|
||||||
createCst,
|
createCst,
|
||||||
|
@ -46,6 +50,19 @@ export function ToolbarRSList({ className }: ToolbarRSListProps) {
|
||||||
moveDown
|
moveDown
|
||||||
} = useRSEdit();
|
} = useRSEdit();
|
||||||
|
|
||||||
|
function handleToggleCrucial() {
|
||||||
|
if (!activeCst) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void updateCrucial({
|
||||||
|
itemID: schema.id,
|
||||||
|
data: {
|
||||||
|
target: selected,
|
||||||
|
value: !activeCst.crucial
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('cc-icons items-start outline-hidden', className)}>
|
<div className={cn('cc-icons items-start outline-hidden', className)}>
|
||||||
{schema.oss.length > 0 ? (
|
{schema.oss.length > 0 ? (
|
||||||
|
@ -75,6 +92,13 @@ export function ToolbarRSList({ className }: ToolbarRSListProps) {
|
||||||
onClick={moveDown}
|
onClick={moveDown}
|
||||||
disabled={isProcessing || selected.length === 0 || selected.length === schema.items.length}
|
disabled={isProcessing || selected.length === 0 || selected.length === schema.items.length}
|
||||||
/>
|
/>
|
||||||
|
<MiniButton
|
||||||
|
title='Ключевая конституента'
|
||||||
|
aria-label='Переключатель статуса ключевой конституенты'
|
||||||
|
icon={<IconCrucial size='1.25rem' className='icon-primary' />}
|
||||||
|
onClick={handleToggleCrucial}
|
||||||
|
disabled={isProcessing || selected.length === 0}
|
||||||
|
/>
|
||||||
<div ref={menu.ref} onBlur={menu.handleBlur} className='relative'>
|
<div ref={menu.ref} onBlur={menu.handleBlur} className='relative'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
title='Добавить пустую конституенту'
|
title='Добавить пустую конституенту'
|
||||||
|
|
|
@ -172,7 +172,8 @@ export function ToolbarTermGraph({ className }: ToolbarTermGraphProps) {
|
||||||
const cst = schema.cstByID.get(cstID);
|
const cst = schema.cstByID.get(cstID);
|
||||||
return !!cst && isBasicConcept(cst.cst_type);
|
return !!cst && isBasicConcept(cst.cst_type);
|
||||||
}}
|
}}
|
||||||
isOwned={schema.inheritance.length > 0 ? cstID => !schema.cstByID.get(cstID)?.is_inherited : undefined}
|
isCrucial={cstID => schema.cstByID.get(cstID)?.crucial ?? false}
|
||||||
|
isInherited={cstID => schema.cstByID.get(cstID)?.is_inherited ?? false}
|
||||||
value={selected}
|
value={selected}
|
||||||
onChange={handleSetSelected}
|
onChange={handleSetSelected}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -80,6 +80,7 @@ export function ViewHidden({ items }: ViewHiddenProps) {
|
||||||
type='button'
|
type='button'
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'cc-view-hidden-item w-12 rounded-md text-center select-none',
|
'cc-view-hidden-item w-12 rounded-md text-center select-none',
|
||||||
|
cst.crucial && 'text-primary',
|
||||||
localSelected.includes(cstID) && 'selected',
|
localSelected.includes(cstID) && 'selected',
|
||||||
cst.is_inherited && 'inherited'
|
cst.is_inherited && 'inherited'
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -225,6 +225,7 @@ export const RSEditState = ({
|
||||||
definition_formal: definition ?? '',
|
definition_formal: definition ?? '',
|
||||||
definition_raw: '',
|
definition_raw: '',
|
||||||
convention: '',
|
convention: '',
|
||||||
|
crucial: false,
|
||||||
term_forms: []
|
term_forms: []
|
||||||
};
|
};
|
||||||
if (skipDialog) {
|
if (skipDialog) {
|
||||||
|
@ -248,6 +249,7 @@ export const RSEditState = ({
|
||||||
definition_formal: activeCst.definition_formal,
|
definition_formal: activeCst.definition_formal,
|
||||||
definition_raw: activeCst.definition_raw,
|
definition_raw: activeCst.definition_raw,
|
||||||
convention: activeCst.convention,
|
convention: activeCst.convention,
|
||||||
|
crucial: activeCst.crucial,
|
||||||
term_forms: activeCst.term_forms
|
term_forms: activeCst.term_forms
|
||||||
}
|
}
|
||||||
}).then(onCreateCst);
|
}).then(onCreateCst);
|
||||||
|
|
|
@ -40,6 +40,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility cc-hover-underline {
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@utility focus-outline {
|
@utility focus-outline {
|
||||||
--focus-color: var(--color-ring);
|
--focus-color: var(--color-ring);
|
||||||
|
|
||||||
|
@ -233,3 +239,7 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@utility cc-badge-inner-shadow {
|
||||||
|
box-shadow: inset 0 1px 3px 0, inset 0 -1px 3px 0;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user