mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
UI fixes and synthesis table unification
This commit is contained in:
parent
54da2f4871
commit
102f8c2baf
|
@ -1,12 +1,12 @@
|
|||
''' Utility: Graph implementation. '''
|
||||
from typing import Dict, Iterable, Optional, cast
|
||||
from typing import Iterable, Optional, cast
|
||||
|
||||
|
||||
class Graph:
|
||||
''' Directed graph. '''
|
||||
def __init__(self, graph: Optional[Dict[str, list[str]]]=None):
|
||||
def __init__(self, graph: Optional[dict[str, list[str]]]=None):
|
||||
if graph is None:
|
||||
self._graph = cast(Dict[str, list[str]], {})
|
||||
self._graph = cast(dict[str, list[str]], {})
|
||||
else:
|
||||
self._graph = graph
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@ def renameTrivial(name: str):
|
|||
def substituteTrivial(name: str):
|
||||
return f'Отождествление конституенты с собой не корректно: {name}'
|
||||
|
||||
def substituteDouble(name: str):
|
||||
return f'Повторное отождествление: {name}'
|
||||
|
||||
def aliasTaken(name: str):
|
||||
return f'Имя уже используется: {name}'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
''' Models: RSForm API. '''
|
||||
from typing import Dict, Iterable, Optional, Union, cast
|
||||
from typing import Iterable, Optional, Union, cast
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import QuerySet
|
||||
|
@ -53,6 +53,7 @@ class RSForm:
|
|||
''' Trigger cascade resolutions when term changes. '''
|
||||
graph_terms = self._term_graph()
|
||||
expansion = graph_terms.expand_outputs(changed)
|
||||
expanded_change = list(changed) + expansion
|
||||
resolver = self.resolver()
|
||||
if len(expansion) > 0:
|
||||
for alias in graph_terms.topological_order():
|
||||
|
@ -67,7 +68,7 @@ class RSForm:
|
|||
resolver.context[cst.alias] = Entity(cst.alias, resolved)
|
||||
|
||||
graph_defs = self._definition_graph()
|
||||
update_defs = set(expansion + graph_defs.expand_outputs(expansion + changed)).union(changed)
|
||||
update_defs = set(expansion + graph_defs.expand_outputs(expanded_change)).union(changed)
|
||||
if len(update_defs) == 0:
|
||||
return
|
||||
for alias in update_defs:
|
||||
|
@ -126,11 +127,11 @@ class RSForm:
|
|||
position = self._get_insert_position(position)
|
||||
self._shift_positions(position, count)
|
||||
|
||||
indices: Dict[str, int] = {}
|
||||
indices: dict[str, int] = {}
|
||||
for (value, _) in CstType.choices:
|
||||
indices[value] = self.get_max_index(cast(CstType, value))
|
||||
|
||||
mapping: Dict[str, str] = {}
|
||||
mapping: dict[str, str] = {}
|
||||
for cst in items:
|
||||
indices[cst.cst_type] = indices[cst.cst_type] + 1
|
||||
newAlias = f'{get_type_prefix(cst.cst_type)}{indices[cst.cst_type]}'
|
||||
|
|
|
@ -194,34 +194,6 @@ class RSFormParseSerializer(serializers.ModelSerializer):
|
|||
return data
|
||||
|
||||
|
||||
class CstSubstituteSerializerBase(serializers.Serializer):
|
||||
''' Serializer: Basic substitution. '''
|
||||
original = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
substitution = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
transfer_term = serializers.BooleanField(required=False, default=False)
|
||||
|
||||
|
||||
class CstSubstituteSerializer(CstSubstituteSerializerBase):
|
||||
''' Serializer: Constituenta substitution. '''
|
||||
def validate(self, attrs):
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
original_cst = cast(Constituenta, attrs['original'])
|
||||
substitution_cst = cast(Constituenta, attrs['substitution'])
|
||||
if original_cst.alias == substitution_cst.alias:
|
||||
raise serializers.ValidationError({
|
||||
'alias': msg.substituteTrivial(original_cst.alias)
|
||||
})
|
||||
if original_cst.schema != schema:
|
||||
raise serializers.ValidationError({
|
||||
'original': msg.constituentaNotOwned(schema.title)
|
||||
})
|
||||
if substitution_cst.schema != schema:
|
||||
raise serializers.ValidationError({
|
||||
'substitution': msg.constituentaNotOwned(schema.title)
|
||||
})
|
||||
return attrs
|
||||
|
||||
|
||||
class CstTargetSerializer(serializers.Serializer):
|
||||
''' Serializer: Target single Constituenta. '''
|
||||
target = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
|
@ -289,6 +261,46 @@ class CstMoveSerializer(CstListSerializer):
|
|||
move_to = serializers.IntegerField()
|
||||
|
||||
|
||||
class CstSubstituteSerializerBase(serializers.Serializer):
|
||||
''' Serializer: Basic substitution. '''
|
||||
original = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
substitution = PKField(many=False, queryset=Constituenta.objects.all())
|
||||
transfer_term = serializers.BooleanField(required=False, default=False)
|
||||
|
||||
|
||||
class CstSubstituteSerializer(serializers.Serializer):
|
||||
''' Serializer: Constituenta substitution. '''
|
||||
substitutions = serializers.ListField(
|
||||
child=CstSubstituteSerializerBase(),
|
||||
min_length=1
|
||||
)
|
||||
|
||||
def validate(self, attrs):
|
||||
schema = cast(LibraryItem, self.context['schema'])
|
||||
deleted = set()
|
||||
for item in attrs['substitutions']:
|
||||
original_cst = cast(Constituenta, item['original'])
|
||||
substitution_cst = cast(Constituenta, item['substitution'])
|
||||
if original_cst.pk in deleted:
|
||||
raise serializers.ValidationError({
|
||||
f'{original_cst.id}': msg.substituteDouble(original_cst.alias)
|
||||
})
|
||||
if original_cst.alias == substitution_cst.alias:
|
||||
raise serializers.ValidationError({
|
||||
'alias': msg.substituteTrivial(original_cst.alias)
|
||||
})
|
||||
if original_cst.schema != schema:
|
||||
raise serializers.ValidationError({
|
||||
'original': msg.constituentaNotOwned(schema.title)
|
||||
})
|
||||
if substitution_cst.schema != schema:
|
||||
raise serializers.ValidationError({
|
||||
'substitution': msg.constituentaNotOwned(schema.title)
|
||||
})
|
||||
deleted.add(original_cst.pk)
|
||||
return attrs
|
||||
|
||||
|
||||
class InlineSynthesisSerializer(serializers.Serializer):
|
||||
''' Serializer: Inline synthesis operation input. '''
|
||||
receiver = PKField(many=False, queryset=LibraryItem.objects.all())
|
||||
|
@ -313,6 +325,7 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError({
|
||||
f'{cst.id}': msg.constituentaNotOwned(schema_in.title)
|
||||
})
|
||||
deleted = set()
|
||||
for item in attrs['substitutions']:
|
||||
original_cst = cast(Constituenta, item['original'])
|
||||
substitution_cst = cast(Constituenta, item['substitution'])
|
||||
|
@ -334,4 +347,9 @@ class InlineSynthesisSerializer(serializers.Serializer):
|
|||
raise serializers.ValidationError({
|
||||
f'{original_cst.id}': msg.constituentaNotOwned(schema_out.title)
|
||||
})
|
||||
if original_cst.pk in deleted:
|
||||
raise serializers.ValidationError({
|
||||
f'{original_cst.id}': msg.substituteDouble(original_cst.alias)
|
||||
})
|
||||
deleted.add(original_cst.pk)
|
||||
return attrs
|
||||
|
|
|
@ -260,7 +260,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch')
|
||||
def test_substitute_constituenta(self):
|
||||
def test_substitute_single(self):
|
||||
x1 = self.schema.insert_new(
|
||||
alias='X1',
|
||||
term_raw='Test1',
|
||||
|
@ -273,14 +273,14 @@ class TestRSFormViewset(EndpointTester):
|
|||
)
|
||||
unowned = self.unowned.insert_new('X2')
|
||||
|
||||
data = {'original': x1.pk, 'substitution': unowned.pk, 'transfer_term': True}
|
||||
data = {'substitutions': [{'original': x1.pk, 'substitution': unowned.pk, 'transfer_term': True}]}
|
||||
self.assertForbidden(data, item=self.unowned_id)
|
||||
self.assertBadData(data, item=self.schema_id)
|
||||
|
||||
data = {'original': unowned.pk, 'substitution': x1.pk, 'transfer_term': True}
|
||||
data = {'substitutions': [{'original': unowned.pk, 'substitution': x1.pk, 'transfer_term': True}]}
|
||||
self.assertBadData(data, item=self.schema_id)
|
||||
|
||||
data = {'original': x1.pk, 'substitution': x1.pk, 'transfer_term': True}
|
||||
data = {'substitutions': [{'original': x1.pk, 'substitution': x1.pk, 'transfer_term': True}]}
|
||||
self.assertBadData(data, item=self.schema_id)
|
||||
|
||||
d1 = self.schema.insert_new(
|
||||
|
@ -288,7 +288,7 @@ class TestRSFormViewset(EndpointTester):
|
|||
term_raw='@{X2|sing,datv}',
|
||||
definition_formal='X1'
|
||||
)
|
||||
data = {'original': x1.pk, 'substitution': x2.pk, 'transfer_term': True}
|
||||
data = {'substitutions': [{'original': x1.pk, 'substitution': x2.pk, 'transfer_term': True}]}
|
||||
response = self.execute(data, item=self.schema_id)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
@ -298,6 +298,53 @@ class TestRSFormViewset(EndpointTester):
|
|||
self.assertEqual(d1.term_resolved, 'form1')
|
||||
self.assertEqual(d1.definition_formal, 'X2')
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/cst-substitute', method='patch')
|
||||
def test_substitute_multiple(self):
|
||||
self.set_params(item=self.schema_id)
|
||||
x1 = self.schema.insert_new('X1')
|
||||
x2 = self.schema.insert_new('X2')
|
||||
d1 = self.schema.insert_new('D1')
|
||||
d2 = self.schema.insert_new('D2')
|
||||
d3 = self.schema.insert_new(
|
||||
alias='D3',
|
||||
definition_formal='X1 \ X2'
|
||||
)
|
||||
|
||||
data = {'substitutions': []}
|
||||
self.assertBadData(data)
|
||||
|
||||
data = {'substitutions': [
|
||||
{
|
||||
'original': x1.pk,
|
||||
'substitution': d1.pk,
|
||||
'transfer_term': True
|
||||
},
|
||||
{
|
||||
'original': x1.pk,
|
||||
'substitution': d2.pk,
|
||||
'transfer_term': True
|
||||
}
|
||||
]}
|
||||
self.assertBadData(data)
|
||||
|
||||
data = {'substitutions': [
|
||||
{
|
||||
'original': x1.pk,
|
||||
'substitution': d1.pk,
|
||||
'transfer_term': True
|
||||
},
|
||||
{
|
||||
'original': x2.pk,
|
||||
'substitution': d2.pk,
|
||||
'transfer_term': True
|
||||
}
|
||||
]}
|
||||
response = self.execute(data, item=self.schema_id)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
d3.refresh_from_db()
|
||||
self.assertEqual(d3.definition_formal, 'D1 \ D2')
|
||||
|
||||
|
||||
@decl_endpoint('/api/rsforms/{item}/cst-create', method='post')
|
||||
def test_create_constituenta_data(self):
|
||||
|
|
|
@ -147,11 +147,10 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
|
|||
context={'schema': schema.item}
|
||||
)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
schema.substitute(
|
||||
original=serializer.validated_data['original'],
|
||||
substitution=serializer.validated_data['substitution'],
|
||||
transfer_term=serializer.validated_data['transfer_term']
|
||||
)
|
||||
for substitution in serializer.validated_data['substitutions']:
|
||||
original = cast(m.Constituenta, substitution['original'])
|
||||
replacement = cast(m.Constituenta, substitution['substitution'])
|
||||
schema.substitute(original, replacement, substitution['transfer_term'])
|
||||
schema.item.refresh_from_db()
|
||||
return Response(
|
||||
status=c.HTTP_200_OK,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
''' Term context for reference resolution. '''
|
||||
from typing import Iterable, Dict, Optional, TypedDict
|
||||
from typing import Iterable, Optional, TypedDict
|
||||
|
||||
from .ruparser import PhraseParser
|
||||
from .rumodel import WordTag
|
||||
|
@ -81,4 +81,4 @@ class Entity:
|
|||
|
||||
|
||||
# Represents term context for resolving entity references.
|
||||
TermContext = Dict[str, Entity]
|
||||
TermContext = dict[str, Entity]
|
||||
|
|
|
@ -2,7 +2,7 @@ function HelpRSTemplates() {
|
|||
// prettier-ignore
|
||||
return (
|
||||
<div>
|
||||
<h1>Банк выражений</h1>
|
||||
<h1>Шаблоны</h1>
|
||||
<p>Портал предоставляет быстрый доступ к часто используемым выражениям с помощью функции создания конституенты из шаблона</p>
|
||||
<p>Источником шаблонов является <b>Банк выражений</b>, содержащий параметризованные понятия и утверждения, сгруппированные по разделам</p>
|
||||
<p>Сначала выбирается шаблон выражения (вкладка Шаблон)</p>
|
||||
|
|
|
@ -100,15 +100,15 @@ function ConstituentaMultiPicker({ id, schema, prefixID, rows, selected, setSele
|
|||
</span>
|
||||
<div className='flex w-full gap-6 text-sm'>
|
||||
<Button
|
||||
text='Поставщики'
|
||||
text='Влияющие'
|
||||
title='Добавить все конституенты, от которых зависят выбранные'
|
||||
className='w-[7rem]'
|
||||
className='w-[7rem] text-sm'
|
||||
onClick={selectBasis}
|
||||
/>
|
||||
<Button
|
||||
text='Потребители'
|
||||
text='Зависимые'
|
||||
title='Добавить все конституенты, которые зависят от выбранных'
|
||||
className='w-[7rem]'
|
||||
className='w-[7rem] text-sm'
|
||||
onClick={selectDependant}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -154,11 +154,11 @@ function SubstitutionsPicker({
|
|||
);
|
||||
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className='flex flex-col w-full'>
|
||||
<div className='flex items-end gap-3 justify-stretch'>
|
||||
<div className='flex-grow basis-1/2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Label text={schema1?.alias ?? 'Схема 1'} />
|
||||
<Label text={schema1 !== schema2 ? schema1?.alias ?? 'Схема 1' : ''} />
|
||||
<div>
|
||||
<MiniButton
|
||||
title='Сохранить конституенту'
|
||||
|
@ -204,7 +204,7 @@ function SubstitutionsPicker({
|
|||
|
||||
<div className='flex-grow basis-1/2'>
|
||||
<div className='flex items-center justify-between'>
|
||||
<Label text={schema2?.alias ?? 'Схема 2'} />
|
||||
<Label text={schema1 !== schema2 ? schema2?.alias ?? 'Схема 2' : ''} />
|
||||
<div>
|
||||
<MiniButton
|
||||
title='Сохранить конституенту'
|
||||
|
@ -242,8 +242,9 @@ function SubstitutionsPicker({
|
|||
|
||||
<DataTable
|
||||
dense
|
||||
noHeader
|
||||
noFooter
|
||||
className='overflow-y-auto border select-none'
|
||||
className='w-full overflow-y-auto text-sm border select-none'
|
||||
rows={rows}
|
||||
contentHeight='1.3rem'
|
||||
data={items}
|
||||
|
|
|
@ -7,11 +7,13 @@ import { CProps } from '../props';
|
|||
interface MiniButtonProps extends CProps.Button {
|
||||
icon: React.ReactNode;
|
||||
noHover?: boolean;
|
||||
noPadding?: boolean;
|
||||
}
|
||||
|
||||
function MiniButton({
|
||||
icon,
|
||||
noHover,
|
||||
noPadding,
|
||||
tabIndex,
|
||||
title,
|
||||
titleHtml,
|
||||
|
@ -24,11 +26,11 @@ function MiniButton({
|
|||
type='button'
|
||||
tabIndex={tabIndex ?? -1}
|
||||
className={clsx(
|
||||
'px-1 py-1',
|
||||
'rounded-full',
|
||||
'clr-btn-clear',
|
||||
'cursor-pointer disabled:cursor-not-allowed',
|
||||
{
|
||||
'px-1 py-1': !noPadding,
|
||||
'outline-none': noHover,
|
||||
'clr-hover': !noHover
|
||||
},
|
||||
|
|
|
@ -71,8 +71,9 @@ function Modal({
|
|||
exit={{ ...animateModal.exit }}
|
||||
{...restProps}
|
||||
>
|
||||
<Overlay position='right-[0.3rem] top-2'>
|
||||
<Overlay position='right-2 top-2'>
|
||||
<MiniButton
|
||||
noPadding
|
||||
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
|
||||
icon={<BiX size='1.25rem' />}
|
||||
onClick={handleCancel}
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
patchProduceStructure,
|
||||
patchRenameConstituenta,
|
||||
patchResetAliases,
|
||||
patchSubstituteConstituenta,
|
||||
patchSubstituteConstituents,
|
||||
patchUploadTRS,
|
||||
patchVersion,
|
||||
postClaimLibraryItem,
|
||||
|
@ -374,7 +374,7 @@ export const RSFormState = ({ schemaID, versionID, children }: RSFormStateProps)
|
|||
const cstSubstitute = useCallback(
|
||||
(data: ICstSubstituteData, callback?: () => void) => {
|
||||
setError(undefined);
|
||||
patchSubstituteConstituenta(schemaID, {
|
||||
patchSubstituteConstituents(schemaID, {
|
||||
data: data,
|
||||
showError: true,
|
||||
setLoading: setProcessing,
|
||||
|
|
|
@ -35,10 +35,6 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
|
||||
const [argumentValue, setArgumentValue] = useState('');
|
||||
|
||||
const selectedClearable = useMemo(() => {
|
||||
return argumentValue && !!selectedArgument && !!selectedArgument.value;
|
||||
}, [argumentValue, selectedArgument]);
|
||||
|
||||
const isModified = useMemo(
|
||||
() => selectedArgument && argumentValue !== selectedArgument.value,
|
||||
[selectedArgument, argumentValue]
|
||||
|
@ -92,7 +88,6 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
() => [
|
||||
argumentsHelper.accessor('alias', {
|
||||
id: 'alias',
|
||||
header: 'Имя',
|
||||
size: 40,
|
||||
minSize: 40,
|
||||
maxSize: 40,
|
||||
|
@ -100,14 +95,12 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
}),
|
||||
argumentsHelper.accessor(arg => arg.value || 'свободный аргумент', {
|
||||
id: 'value',
|
||||
header: 'Значение',
|
||||
size: 200,
|
||||
minSize: 200,
|
||||
maxSize: 200
|
||||
}),
|
||||
argumentsHelper.accessor(arg => arg.typification, {
|
||||
id: 'type',
|
||||
header: 'Типизация',
|
||||
enableHiding: true,
|
||||
cell: props => (
|
||||
<div
|
||||
|
@ -122,16 +115,14 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
}),
|
||||
argumentsHelper.display({
|
||||
id: 'actions',
|
||||
size: 50,
|
||||
minSize: 50,
|
||||
maxSize: 50,
|
||||
cell: props => (
|
||||
<div className='max-h-[1.2rem]'>
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
{props.row.original.value ? (
|
||||
<MiniButton
|
||||
title='Очистить значение'
|
||||
icon={<BiX size='0.75rem' className='icon-red' />}
|
||||
noPadding
|
||||
noHover
|
||||
icon={<BiX size='1.25rem' className='icon-red' />}
|
||||
onClick={() => handleClearArgument(props.row.original)}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -157,6 +148,7 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
<DataTable
|
||||
dense
|
||||
noFooter
|
||||
noHeader
|
||||
className={clsx(
|
||||
'max-h-[5.8rem] min-h-[5.8rem]', // prettier: split lines
|
||||
'overflow-y-auto',
|
||||
|
@ -194,21 +186,19 @@ function ArgumentsTab({ state, schema, partialUpdate }: ArgumentsTabProps) {
|
|||
<div className='flex'>
|
||||
<MiniButton
|
||||
title='Подставить значение аргумента'
|
||||
icon={<BiCheck size='1.25rem' className='icon-green' />}
|
||||
noHover
|
||||
className='py-0'
|
||||
icon={<BiCheck size='2rem' className='icon-green' />}
|
||||
disabled={!argumentValue || !selectedArgument}
|
||||
onClick={() => handleAssignArgument(selectedArgument!, argumentValue)}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Откатить значение'
|
||||
title='Очистить поле'
|
||||
noHover
|
||||
className='py-0'
|
||||
disabled={!isModified}
|
||||
onClick={handleReset}
|
||||
icon={<BiRefresh size='1.25rem' className='icon-primary' />}
|
||||
/>
|
||||
<MiniButton
|
||||
title='Очистить значение аргумента'
|
||||
disabled={!selectedClearable}
|
||||
icon={<BiX size='1.25rem' className='icon-red' />}
|
||||
onClick={() => (selectedArgument ? handleClearArgument(selectedArgument) : undefined)}
|
||||
icon={<BiRefresh size='2rem' className='icon-primary' />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -114,7 +114,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
|||
onSubmit={handleSubmit}
|
||||
>
|
||||
<Overlay position='top-0 right-[6rem]'>
|
||||
<HelpButton topic={HelpTopic.RSTEMPLATES} className='max-w-[35rem]' />
|
||||
<HelpButton topic={HelpTopic.RSTEMPLATES} className='max-w-[40rem]' offset={12} />
|
||||
</Overlay>
|
||||
<Tabs
|
||||
forceRenderTabPanel
|
||||
|
|
|
@ -60,13 +60,16 @@ function VersionsTable({ processing, items, onDelete, selected, onSelect }: Vers
|
|||
minSize: 50,
|
||||
maxSize: 50,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Удалить версию'
|
||||
noHover
|
||||
noPadding
|
||||
disabled={processing}
|
||||
icon={<BiX size='1rem' className='icon-red' />}
|
||||
icon={<BiX size='1.25rem' className='icon-red' />}
|
||||
onClick={() => onDelete(props.row.original.id)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
|
|
|
@ -129,7 +129,7 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
onSubmit={handleSubmit}
|
||||
className='flex flex-col w-[40rem] px-6'
|
||||
>
|
||||
<Overlay position='top-[-0.2rem] left-[7.5rem]'>
|
||||
<Overlay position='top-[-0.2rem] left-[8rem]'>
|
||||
<HelpButton topic={HelpTopic.TERM_CONTROL} className='max-w-[38rem]' offset={3} />
|
||||
</Overlay>
|
||||
|
||||
|
@ -182,14 +182,14 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
<MiniButton
|
||||
noHover
|
||||
title='Внести словоформу'
|
||||
icon={<BiCheck size='1.25rem' className='icon-green' />}
|
||||
icon={<BiCheck size='1.5rem' className='icon-green' />}
|
||||
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
|
||||
onClick={handleAddForm}
|
||||
/>
|
||||
<MiniButton
|
||||
noHover
|
||||
title='Генерировать стандартные словоформы'
|
||||
icon={<BiChevronsDown size='1.25rem' className='icon-primary' />}
|
||||
icon={<BiChevronsDown size='1.5rem' className='icon-primary' />}
|
||||
disabled={textProcessor.loading || !inputText}
|
||||
onClick={handleGenerateLexeme}
|
||||
/>
|
||||
|
@ -200,7 +200,8 @@ function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps)
|
|||
<MiniButton
|
||||
noHover
|
||||
title='Сбросить все словоформы'
|
||||
icon={<BiX size='1rem' className='icon-red' />}
|
||||
className='py-0'
|
||||
icon={<BiX size='1.5rem' className='icon-red' />}
|
||||
disabled={textProcessor.loading || forms.length === 0}
|
||||
onClick={handleResetAll}
|
||||
/>
|
||||
|
|
|
@ -39,16 +39,14 @@ function WordFormsTable({ forms, setForms, onFormSelect }: WordFormsTableProps)
|
|||
id: 'text',
|
||||
header: 'Текст',
|
||||
size: 350,
|
||||
minSize: 350,
|
||||
maxSize: 350,
|
||||
minSize: 500,
|
||||
maxSize: 500,
|
||||
cell: props => <div className='min-w-[20rem]'>{props.getValue()}</div>
|
||||
}),
|
||||
columnHelper.accessor('grams', {
|
||||
id: 'grams',
|
||||
header: 'Граммемы',
|
||||
size: 250,
|
||||
minSize: 250,
|
||||
maxSize: 250,
|
||||
maxSize: 150,
|
||||
cell: props => <WordFormBadge keyPrefix={props.cell.id} form={props.row.original} />
|
||||
}),
|
||||
columnHelper.display({
|
||||
|
@ -57,12 +55,15 @@ function WordFormsTable({ forms, setForms, onFormSelect }: WordFormsTableProps)
|
|||
minSize: 50,
|
||||
maxSize: 50,
|
||||
cell: props => (
|
||||
<div className='h-[1.25rem] w-[1.25rem]'>
|
||||
<MiniButton
|
||||
noHover
|
||||
noPadding
|
||||
title='Удалить словоформу'
|
||||
icon={<BiX size='1rem' className='icon-red' />}
|
||||
icon={<BiX size='1.25rem' className='icon-red' />}
|
||||
onClick={() => handleDeleteRow(props.row.index)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
],
|
||||
|
@ -73,7 +74,7 @@ function WordFormsTable({ forms, setForms, onFormSelect }: WordFormsTableProps)
|
|||
<DataTable
|
||||
dense
|
||||
noFooter
|
||||
className={clsx('mb-2', 'max-h-[17.4rem] min-h-[17.4rem]', 'border', 'overflow-y-auto')}
|
||||
className={clsx('mb-2', 'max-h-[17.4rem] min-h-[17.4rem]', 'border', 'text-sm', 'overflow-y-auto')}
|
||||
data={forms}
|
||||
columns={columns}
|
||||
headPosition='0'
|
||||
|
|
|
@ -2,15 +2,12 @@
|
|||
|
||||
import clsx from 'clsx';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { LuReplace } from 'react-icons/lu';
|
||||
|
||||
import ConstituentaSelector from '@/components/select/ConstituentaSelector';
|
||||
import Checkbox from '@/components/ui/Checkbox';
|
||||
import FlexColumn from '@/components/ui/FlexColumn';
|
||||
import Label from '@/components/ui/Label';
|
||||
import SubstitutionsPicker from '@/components/select/SubstitutionsPicker';
|
||||
import Modal, { ModalProps } from '@/components/ui/Modal';
|
||||
import { useRSForm } from '@/context/RSFormContext';
|
||||
import { IConstituenta, ICstSubstituteData } from '@/models/rsform';
|
||||
import { ICstSubstituteData, ISubstitution } from '@/models/rsform';
|
||||
import { prefixes } from '@/utils/constants';
|
||||
|
||||
interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
||||
onSubstitute: (data: ICstSubstituteData) => void;
|
||||
|
@ -19,59 +16,38 @@ interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
|
|||
function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
|
||||
const { schema } = useRSForm();
|
||||
|
||||
const [original, setOriginal] = useState<IConstituenta | undefined>(undefined);
|
||||
const [substitution, setSubstitution] = useState<IConstituenta | undefined>(undefined);
|
||||
const [transferTerm, setTransferTerm] = useState(false);
|
||||
const [substitutions, setSubstitutions] = useState<ISubstitution[]>([]);
|
||||
|
||||
const canSubmit = useMemo(() => {
|
||||
return !!original && !!substitution && substitution.id !== original.id;
|
||||
}, [original, substitution]);
|
||||
const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]);
|
||||
|
||||
function handleSubmit() {
|
||||
const data: ICstSubstituteData = {
|
||||
original: original!.id,
|
||||
substitution: substitution!.id,
|
||||
transfer_term: transferTerm
|
||||
substitutions: substitutions.map(item => ({
|
||||
original: item.deleteRight ? item.rightCst.id : item.leftCst.id,
|
||||
substitution: item.deleteRight ? item.leftCst.id : item.rightCst.id,
|
||||
transfer_term: !item.deleteRight && item.takeLeftTerm
|
||||
}))
|
||||
};
|
||||
onSubstitute(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
header='Отождествление конституенты'
|
||||
header='Отождествление'
|
||||
submitText='Отождествить'
|
||||
submitInvalidTooltip={'Выберите две различные конституенты'}
|
||||
hideWindow={hideWindow}
|
||||
canSubmit={canSubmit}
|
||||
onSubmit={handleSubmit}
|
||||
className={clsx('w-[25rem]', 'px-6 py-3 flex flex-col gap-3 justify-center items-center')}
|
||||
className={clsx('w-[40rem]', 'px-6 pb-3')}
|
||||
>
|
||||
<FlexColumn>
|
||||
<Label text='Удаляемая конституента' />
|
||||
<ConstituentaSelector
|
||||
className='w-[20rem]'
|
||||
items={schema?.items}
|
||||
value={original}
|
||||
onSelectValue={setOriginal}
|
||||
/>
|
||||
</FlexColumn>
|
||||
|
||||
<LuReplace size='3rem' className='icon-primary' />
|
||||
|
||||
<FlexColumn>
|
||||
<Label text='Подставляемая конституента' />
|
||||
<ConstituentaSelector
|
||||
className='w-[20rem]'
|
||||
items={schema?.items}
|
||||
value={substitution}
|
||||
onSelectValue={setSubstitution}
|
||||
/>
|
||||
</FlexColumn>
|
||||
<Checkbox
|
||||
className='mt-3'
|
||||
label='Сохранить термин удаляемой конституенты'
|
||||
value={transferTerm}
|
||||
setValue={setTransferTerm}
|
||||
<SubstitutionsPicker
|
||||
items={substitutions}
|
||||
setItems={setSubstitutions}
|
||||
rows={6}
|
||||
prefixID={prefixes.dlg_cst_substitutes_list}
|
||||
schema1={schema}
|
||||
schema2={schema}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
|
|
|
@ -148,14 +148,21 @@ export interface ICstUpdateData
|
|||
export interface ICstRenameData extends ICstTarget, Pick<IConstituentaMeta, 'alias' | 'cst_type'> {}
|
||||
|
||||
/**
|
||||
* Represents data, used in merging {@link IConstituenta}.
|
||||
* Represents data, used in merging single {@link IConstituenta}.
|
||||
*/
|
||||
export interface ICstSubstituteData {
|
||||
export interface ICstSubstitute {
|
||||
original: ConstituentaID;
|
||||
substitution: ConstituentaID;
|
||||
transfer_term: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents data, used in merging multiple {@link IConstituenta}.
|
||||
*/
|
||||
export interface ICstSubstituteData {
|
||||
substitutions: ICstSubstitute[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents single substitution for synthesis table.
|
||||
*/
|
||||
|
@ -251,5 +258,5 @@ export interface IInlineSynthesisData {
|
|||
receiver: LibraryItemID;
|
||||
source: LibraryItemID;
|
||||
items: ConstituentaID[];
|
||||
substitutions: ICstSubstituteData[];
|
||||
substitutions: ICstSubstitute[];
|
||||
}
|
||||
|
|
|
@ -209,7 +209,7 @@ function RSTabsMenu({ onDestroy }: RSTabsMenuProps) {
|
|||
<Dropdown isOpen={editMenu.isOpen}>
|
||||
<DropdownButton
|
||||
disabled={!controller.isContentEditable}
|
||||
text='Банк выражений'
|
||||
text='Шаблоны'
|
||||
title='Создать конституенту из шаблона'
|
||||
icon={<BiDiamond size='1rem' className='icon-green' />}
|
||||
onClick={handleTemplates}
|
||||
|
|
|
@ -334,9 +334,9 @@ export function patchProduceStructure(schema: string, request: FrontExchange<ICs
|
|||
});
|
||||
}
|
||||
|
||||
export function patchSubstituteConstituenta(schema: string, request: FrontExchange<ICstSubstituteData, IRSFormData>) {
|
||||
export function patchSubstituteConstituents(schema: string, request: FrontExchange<ICstSubstituteData, IRSFormData>) {
|
||||
AxiosPatch({
|
||||
title: `Substitution for constituenta id=${request.data.original} for schema id=${schema}`,
|
||||
title: `Substitution for constituents schema id=${schema}`,
|
||||
endpoint: `/api/rsforms/${schema}/cst-substitute`,
|
||||
request: request
|
||||
});
|
||||
|
|
|
@ -133,5 +133,6 @@ export const prefixes = {
|
|||
topic_list: 'topic_list_',
|
||||
library_list: 'library_list_',
|
||||
wordform_list: 'wordform_list_',
|
||||
rsedit_btn: 'rsedit_btn_'
|
||||
rsedit_btn: 'rsedit_btn_',
|
||||
dlg_cst_substitutes_list: 'dlg_cst_substitutes_list_'
|
||||
};
|
||||
|
|
|
@ -345,11 +345,11 @@ export function labelHelpTopic(topic: HelpTopic): string {
|
|||
switch (topic) {
|
||||
case HelpTopic.MAIN: return 'Портал';
|
||||
case HelpTopic.LIBRARY: return 'Библиотека';
|
||||
case HelpTopic.RSFORM: return '- паспорт схемы';
|
||||
case HelpTopic.RSFORM: return '- карточка схемы';
|
||||
case HelpTopic.CSTLIST: return '- список конституент';
|
||||
case HelpTopic.CONSTITUENTA: return '- конституента';
|
||||
case HelpTopic.GRAPH_TERM: return '- граф термов';
|
||||
case HelpTopic.RSTEMPLATES: return '- Банк выражений';
|
||||
case HelpTopic.RSTEMPLATES: return '- шаблоны выражений';
|
||||
case HelpTopic.RSLANG: return 'Экспликация';
|
||||
case HelpTopic.TERM_CONTROL: return 'Терминологизация';
|
||||
case HelpTopic.VERSIONS: return 'Версионирование';
|
||||
|
|
Loading…
Reference in New Issue
Block a user