R: Simplify sync_text and transfer_term

This commit is contained in:
Ivan 2024-07-30 15:59:37 +03:00
parent afd3f5f7e4
commit 2e19c6fa69
40 changed files with 297 additions and 707 deletions

View File

@ -140,7 +140,7 @@ class LibraryItem(Model):
def _update_connected_operations(self):
# using method level import to prevent circular dependency
from apps.oss.models import Operation # pylint: disable=import-outside-toplevel
operations = Operation.objects.filter(result__pk=self.pk, sync_text=True)
operations = Operation.objects.filter(result__pk=self.pk)
if not operations.exists():
return
for operation in operations:

View File

@ -21,7 +21,7 @@ class ArgumentAdmin(admin.ModelAdmin):
class SynthesisSubstitutionAdmin(admin.ModelAdmin):
''' Admin model: Substitutions as part of Synthesis operation. '''
ordering = ['operation']
list_display = ['id', 'operation', 'original', 'substitution', 'transfer_term']
list_display = ['id', 'operation', 'original', 'substitution']
search_fields = ['id', 'operation', 'original', 'substitution']

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-07-30 07:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('oss', '0002_inheritance'),
]
operations = [
migrations.RemoveField(
model_name='operation',
name='sync_text',
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-07-30 07:36
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('oss', '0003_remove_operation_sync_text'),
]
operations = [
migrations.RemoveField(
model_name='substitution',
name='transfer_term',
),
]

View File

@ -2,7 +2,6 @@
from django.db.models import (
CASCADE,
SET_NULL,
BooleanField,
CharField,
FloatField,
ForeignKey,
@ -43,10 +42,6 @@ class Operation(Model):
on_delete=SET_NULL,
related_name='producer'
)
sync_text: BooleanField = BooleanField(
verbose_name='Синхронизация',
default=True
)
alias: CharField = CharField(
verbose_name='Шифр',

View File

@ -134,11 +134,7 @@ class OperationSchema:
if len(subs) == 0:
changed = True
current.delete()
continue
if current.transfer_term != subs[0]['transfer_term']:
current.transfer_term = subs[0]['transfer_term']
current.save()
continue
else:
processed.append(subs[0])
for sub in substitutes:
@ -147,8 +143,7 @@ class OperationSchema:
Substitution.objects.create(
operation=target,
original=sub['original'],
substitution=sub['substitution'],
transfer_term=sub['transfer_term']
substitution=sub['substitution']
)
if not changed:

View File

@ -1,5 +1,5 @@
''' Models: Synthesis Substitution. '''
from django.db.models import CASCADE, BooleanField, ForeignKey, Model
from django.db.models import CASCADE, ForeignKey, Model
class Substitution(Model):
@ -22,10 +22,6 @@ class Substitution(Model):
on_delete=CASCADE,
related_name='as_substitute'
)
transfer_term: BooleanField = BooleanField(
verbose_name='Перенос термина',
default=False
)
class Meta:
''' Model metadata. '''

View File

@ -21,7 +21,6 @@ class SubstitutionExSerializer(serializers.Serializer):
operation = serializers.IntegerField()
original = serializers.IntegerField()
substitution = serializers.IntegerField()
transfer_term = serializers.BooleanField()
original_alias = serializers.CharField()
original_term = serializers.CharField()
substitution_alias = serializers.CharField()

View File

@ -34,7 +34,7 @@ class ArgumentSerializer(serializers.ModelSerializer):
class OperationCreateSerializer(serializers.Serializer):
''' Serializer: Operation creation. '''
class OperationData(serializers.ModelSerializer):
class OperationCreateData(serializers.ModelSerializer):
''' Serializer: Operation creation data. '''
alias = serializers.CharField()
operation_type = serializers.ChoiceField(OperationType.choices)
@ -43,11 +43,11 @@ class OperationCreateSerializer(serializers.Serializer):
''' serializer metadata. '''
model = Operation
fields = \
'alias', 'operation_type', 'title', 'sync_text', \
'alias', 'operation_type', 'title', \
'comment', 'result', 'position_x', 'position_y'
create_schema = serializers.BooleanField(default=False, required=False)
item_data = OperationData()
item_data = OperationCreateData()
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
positions = serializers.ListField(
@ -58,15 +58,15 @@ class OperationCreateSerializer(serializers.Serializer):
class OperationUpdateSerializer(serializers.Serializer):
''' Serializer: Operation creation. '''
class OperationData(serializers.ModelSerializer):
class OperationUpdateData(serializers.ModelSerializer):
''' Serializer: Operation creation data. '''
class Meta:
''' serializer metadata. '''
model = Operation
fields = 'alias', 'title', 'sync_text', 'comment'
fields = 'alias', 'title', 'comment'
target = PKField(many=False, queryset=Operation.objects.all())
item_data = OperationData()
item_data = OperationUpdateData()
arguments = PKField(many=True, queryset=Operation.objects.all(), required=False)
substitutions = serializers.ListField(
child=SubstitutionSerializerBase(),
@ -145,7 +145,6 @@ class SetOperationInputSerializer(serializers.Serializer):
allow_null=True,
default=None
)
sync_text = serializers.BooleanField(default=False, required=False)
positions = serializers.ListField(
child=OperationPositionSerializer(),
default=[]
@ -196,7 +195,6 @@ class OperationSchemaSerializer(serializers.ModelSerializer):
'operation',
'original',
'substitution',
'transfer_term',
original_alias=F('original__alias'),
original_term=F('original__term_resolved'),
substitution_alias=F('substitution__alias'),

View File

@ -29,7 +29,6 @@ class TestOperation(TestCase):
self.assertEqual(self.operation.alias, 'KS1')
self.assertEqual(self.operation.title, '')
self.assertEqual(self.operation.comment, '')
self.assertEqual(self.operation.sync_text, True)
self.assertEqual(self.operation.position_x, 0)
self.assertEqual(self.operation.position_y, 0)
@ -50,15 +49,6 @@ class TestOperation(TestCase):
self.assertEqual(self.operation.title, schema.model.title)
self.assertEqual(self.operation.comment, schema.model.comment)
self.operation.sync_text = False
self.operation.save()
schema.model.alias = 'KS3'
schema.save()
self.operation.refresh_from_db()
self.assertEqual(self.operation.result, schema.model)
self.assertNotEqual(self.operation.alias, schema.model.alias)
def test_sync_from_library_item(self):
schema = LibraryItem.objects.create(alias=self.operation.alias, item_type=LibraryItemType.RSFORM)
self.operation.result = schema

View File

@ -47,8 +47,7 @@ class TestSynthesisSubstitution(TestCase):
self.substitution = Substitution.objects.create(
operation=self.operation3,
original=self.ks1x1,
substitution=self.ks2x1,
transfer_term=False
substitution=self.ks2x1
)

View File

@ -44,8 +44,7 @@ class TestOssViewset(EndpointTester):
self.owned.set_arguments(self.operation3, [self.operation1, self.operation2])
self.owned.set_substitutions(self.operation3, [{
'original': self.ks1x1,
'substitution': self.ks2x1,
'transfer_term': False
'substitution': self.ks2x1
}])
@decl_endpoint('/api/oss/{item}/details', method='get')
@ -71,7 +70,6 @@ class TestOssViewset(EndpointTester):
self.assertEqual(sub['operation'], self.operation3.pk)
self.assertEqual(sub['original'], self.ks1x1.pk)
self.assertEqual(sub['substitution'], self.ks2x1.pk)
self.assertEqual(sub['transfer_term'], False)
self.assertEqual(sub['original_alias'], self.ks1x1.alias)
self.assertEqual(sub['original_term'], self.ks1x1.term_resolved)
self.assertEqual(sub['substitution_alias'], self.ks2x1.alias)
@ -134,7 +132,6 @@ class TestOssViewset(EndpointTester):
'alias': 'Test3',
'title': 'Test title',
'comment': 'Тест кириллицы',
'sync_text': False,
'position_x': 1,
'position_y': 1,
},
@ -159,7 +156,6 @@ class TestOssViewset(EndpointTester):
self.assertEqual(new_operation['comment'], data['item_data']['comment'])
self.assertEqual(new_operation['position_x'], data['item_data']['position_x'])
self.assertEqual(new_operation['position_y'], data['item_data']['position_y'])
self.assertEqual(new_operation['sync_text'], data['item_data']['sync_text'])
self.assertEqual(new_operation['result'], None)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.position_x, data['positions'][0]['position_x'])
@ -274,13 +270,11 @@ class TestOssViewset(EndpointTester):
self.operation1.result = None
self.operation1.comment = 'TestComment'
self.operation1.title = 'TestTitle'
self.operation1.sync_text = False
self.operation1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
new_schema = response.data['new_schema']
self.assertEqual(self.operation1.sync_text, True)
self.assertEqual(new_schema['id'], self.operation1.result.pk)
self.assertEqual(new_schema['alias'], self.operation1.alias)
self.assertEqual(new_schema['title'], self.operation1.title)
@ -295,7 +289,6 @@ class TestOssViewset(EndpointTester):
self.executeBadData(item=self.owned_id)
data = {
'sync_text': True,
'positions': []
}
self.executeBadData(data=data)
@ -312,7 +305,6 @@ class TestOssViewset(EndpointTester):
self.login()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.sync_text, True)
self.assertEqual(self.operation1.result, None)
data['input'] = self.ks1.model.pk
@ -322,7 +314,6 @@ class TestOssViewset(EndpointTester):
self.ks1.save()
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.sync_text, True)
self.assertEqual(self.operation1.result, self.ks1.model)
self.assertEqual(self.operation1.alias, self.ks1.model.alias)
self.assertEqual(self.operation1.title, self.ks1.model.title)
@ -334,14 +325,12 @@ class TestOssViewset(EndpointTester):
self.operation2.result = None
data = {
'sync_text': True,
'positions': [],
'target': self.operation1.pk,
'input': self.ks2.model.pk
}
response = self.executeOK(data=data, item=self.owned_id)
self.operation2.refresh_from_db()
self.assertEqual(self.operation2.sync_text, True)
self.assertEqual(self.operation2.result, self.ks2.model)
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
@ -357,16 +346,14 @@ class TestOssViewset(EndpointTester):
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'comment': 'Comment mod',
'sync_text': True
'comment': 'Comment mod'
},
'positions': [],
'arguments': [self.operation1.pk, self.operation2.pk],
'substitutions': [
{
'original': self.ks1x1.pk,
'substitution': ks3x1.pk,
'transfer_term': False
'substitution': ks3x1.pk
}
]
}
@ -381,7 +368,6 @@ class TestOssViewset(EndpointTester):
self.login()
response = self.executeOK(data=data)
self.operation3.refresh_from_db()
self.assertEqual(self.operation3.sync_text, data['item_data']['sync_text'])
self.assertEqual(self.operation3.alias, data['item_data']['alias'])
self.assertEqual(self.operation3.title, data['item_data']['title'])
self.assertEqual(self.operation3.comment, data['item_data']['comment'])
@ -389,7 +375,6 @@ class TestOssViewset(EndpointTester):
sub = self.operation3.getSubstitutions()[0]
self.assertEqual(sub.original.pk, data['substitutions'][0]['original'])
self.assertEqual(sub.substitution.pk, data['substitutions'][0]['substitution'])
self.assertEqual(sub.transfer_term, data['substitutions'][0]['transfer_term'])
@decl_endpoint('/api/oss/{item}/update-operation', method='patch')
def test_update_operation_sync(self):
@ -401,15 +386,13 @@ class TestOssViewset(EndpointTester):
'item_data': {
'alias': 'Test3 mod',
'title': 'Test title mod',
'comment': 'Comment mod',
'sync_text': True
'comment': 'Comment mod'
},
'positions': [],
}
response = self.executeOK(data=data)
self.operation1.refresh_from_db()
self.assertEqual(self.operation1.sync_text, data['item_data']['sync_text'])
self.assertEqual(self.operation1.alias, data['item_data']['alias'])
self.assertEqual(self.operation1.title, data['item_data']['title'])
self.assertEqual(self.operation1.comment, data['item_data']['comment'])

View File

@ -209,7 +209,6 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
)
Editor.set(schema, oss.model.editors())
operation.result = schema
operation.sync_text = True
operation.save()
oss.refresh_from_db()
@ -247,8 +246,7 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
with transaction.atomic():
oss.update_positions(serializer.validated_data['positions'])
operation.result = result
operation.sync_text = serializer.validated_data['sync_text']
if result is not None and operation.sync_text:
if result is not None:
operation.title = result.title
operation.comment = result.comment
operation.alias = result.alias
@ -289,10 +287,9 @@ class OssViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retriev
operation.alias = serializer.validated_data['item_data']['alias']
operation.title = serializer.validated_data['item_data']['title']
operation.comment = serializer.validated_data['item_data']['comment']
operation.sync_text = serializer.validated_data['item_data']['sync_text']
operation.save()
if operation.sync_text and operation.result is not None:
if operation.result is not None:
can_edit = permissions.can_edit_item(request.user, operation.result)
if can_edit:
operation.result.alias = operation.alias

View File

@ -241,18 +241,12 @@ class RSForm:
def substitute(
self,
original: Constituenta,
substitution: Constituenta,
transfer_term: bool
substitution: Constituenta
):
''' Execute constituenta substitution. '''
assert original.pk != substitution.pk
mapping = {original.alias: substitution.alias}
self.apply_mapping(mapping)
if transfer_term:
substitution.term_raw = original.term_raw
substitution.term_forms = original.term_forms
substitution.term_resolved = original.term_resolved
substitution.save()
original.delete()
self.on_term_change([substitution.id])

View File

@ -274,7 +274,6 @@ class SubstitutionSerializerBase(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):

View File

@ -57,13 +57,11 @@ class TestInlineSynthesis(EndpointTester):
'substitutions': [
{
'original': ks1_x1.pk,
'substitution': ks2_s1.pk,
'transfer_term': False
'substitution': ks2_s1.pk
},
{
'original': ks2_x1.pk,
'substitution': ks1_s1.pk,
'transfer_term': True
'substitution': ks1_s1.pk
}
]
}

View File

@ -272,43 +272,6 @@ class TestRSFormViewset(EndpointTester):
self.assertEqual(x1.cst_type, CstType.TERM)
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
def test_substitute_single(self):
x1 = self.owned.insert_new(
alias='X1',
term_raw='Test1',
term_resolved='Test1',
term_forms=[{'text': 'form1', 'tags': 'sing,datv'}]
)
x2 = self.owned.insert_new(
alias='X2',
term_raw='Test2'
)
unowned = self.unowned.insert_new('X2')
data = {'substitutions': [{'original': x1.pk, 'substitution': unowned.pk, 'transfer_term': True}]}
self.executeForbidden(data=data, item=self.unowned_id)
self.executeBadData(data=data, item=self.owned_id)
data = {'substitutions': [{'original': unowned.pk, 'substitution': x1.pk, 'transfer_term': True}]}
self.executeBadData(data=data, item=self.owned_id)
data = {'substitutions': [{'original': x1.pk, 'substitution': x1.pk, 'transfer_term': True}]}
self.executeBadData(data=data, item=self.owned_id)
d1 = self.owned.insert_new(
alias='D1',
term_raw='@{X2|sing,datv}',
definition_formal='X1'
)
data = {'substitutions': [{'original': x1.pk, 'substitution': x2.pk, 'transfer_term': True}]}
response = self.executeOK(data=data, item=self.owned_id)
d1.refresh_from_db()
x2.refresh_from_db()
self.assertEqual(x2.term_raw, 'Test1')
self.assertEqual(d1.term_resolved, 'form1')
self.assertEqual(d1.definition_formal, 'X2')
@decl_endpoint('/api/rsforms/{item}/substitute', method='patch')
def test_substitute_multiple(self):
self.set_params(item=self.owned_id)
@ -327,13 +290,11 @@ class TestRSFormViewset(EndpointTester):
data = {'substitutions': [
{
'original': x1.pk,
'substitution': d1.pk,
'transfer_term': True
'substitution': d1.pk
},
{
'original': x1.pk,
'substitution': d2.pk,
'transfer_term': True
'substitution': d2.pk
}
]}
self.executeBadData(data=data)
@ -341,13 +302,11 @@ class TestRSFormViewset(EndpointTester):
data = {'substitutions': [
{
'original': x1.pk,
'substitution': d1.pk,
'transfer_term': True
'substitution': d1.pk
},
{
'original': x2.pk,
'substitution': d2.pk,
'transfer_term': True
'substitution': d2.pk
}
]}
response = self.executeOK(data=data, item=self.owned_id)

View File

@ -41,7 +41,7 @@ def inline_synthesis(request: Request):
else:
index = next(i for (i, cst) in enumerate(items) if cst == replacement)
replacement = new_items[index]
receiver.substitute(original, replacement, substitution['transfer_term'])
receiver.substitute(original, replacement)
receiver.restore_order()
return Response(

View File

@ -174,7 +174,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
)
@extend_schema(
summary='substitute constituenta',
summary='execute substitutions',
tags=['RSForm'],
request=s.CstSubstituteSerializer,
responses={
@ -198,7 +198,7 @@ class RSFormViewSet(viewsets.GenericViewSet, generics.ListAPIView, generics.Retr
for substitution in serializer.validated_data['substitutions']:
original = cast(m.Constituenta, substitution['original'])
replacement = cast(m.Constituenta, substitution['substitution'])
m.RSForm(schema).substitute(original, replacement, substitution['transfer_term'])
m.RSForm(schema).substitute(original, replacement)
schema.refresh_from_db()
return Response(

View File

@ -89,8 +89,6 @@ export { BiHelpCircle as IconStatusUnknown } from 'react-icons/bi';
export { BiPauseCircle as IconStatusIncalculable } from 'react-icons/bi';
export { LuPower as IconKeepAliasOn } from 'react-icons/lu';
export { LuPowerOff as IconKeepAliasOff } from 'react-icons/lu';
export { LuFlag as IconKeepTermOn } from 'react-icons/lu';
export { LuFlagOff as IconKeepTermOff } from 'react-icons/lu';
// ===== Domain actions =====
export { BiUpvote as IconMoveUp } from 'react-icons/bi';

View File

@ -1,273 +0,0 @@
'use client';
import { useCallback, useMemo, useState } from 'react';
import BadgeConstituenta from '@/components/info/BadgeConstituenta';
import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { IBinarySubstitution, IConstituenta, IRSForm } from '@/models/rsform';
import { describeConstituenta } from '@/utils/labels';
import {
IconKeepAliasOff,
IconKeepAliasOn,
IconKeepTermOff,
IconKeepTermOn,
IconPageFirst,
IconPageLast,
IconPageLeft,
IconPageRight,
IconRemove,
IconReplace
} from '../Icons';
import NoData from '../ui/NoData';
interface PickInlineSubstitutionsProps {
prefixID: string;
rows?: number;
schema1?: IRSForm;
schema2?: IRSForm;
filter1?: (cst: IConstituenta) => boolean;
filter2?: (cst: IConstituenta) => boolean;
items: IBinarySubstitution[];
setItems: React.Dispatch<React.SetStateAction<IBinarySubstitution[]>>;
}
function SubstitutionIcon({ item }: { item: IBinarySubstitution }) {
if (item.deleteRight) {
if (item.takeLeftTerm) {
return <IconPageRight size='1.2rem' />;
} else {
return <IconPageLast size='1.2rem' />;
}
} else {
if (item.takeLeftTerm) {
return <IconPageFirst size='1.2rem' />;
} else {
return <IconPageLeft size='1.2rem' />;
}
}
}
const columnHelper = createColumnHelper<IBinarySubstitution>();
function PickInlineSubstitutions({
items,
schema1,
schema2,
filter1,
filter2,
rows,
setItems,
prefixID
}: PickInlineSubstitutionsProps) {
const { colors } = useConceptOptions();
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
const [deleteRight, setDeleteRight] = useState(true);
const [takeLeftTerm, setTakeLeftTerm] = useState(true);
const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev);
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
const newSubstitution: IBinarySubstitution = {
leftCst: leftCst,
rightCst: rightCst,
deleteRight: deleteRight,
takeLeftTerm: takeLeftTerm
};
setItems([
newSubstitution,
...items.filter(
item =>
(!item.deleteRight && item.leftCst.id !== leftCst.id) ||
(item.deleteRight && item.rightCst.id !== rightCst.id)
)
]);
}
const handleDeleteRow = useCallback(
(row: number) => {
setItems(prev => {
const newItems: IBinarySubstitution[] = [];
prev.forEach((item, index) => {
if (index !== row) {
newItems.push(item);
}
});
return newItems;
});
},
[setItems]
);
const columns = useMemo(
() => [
columnHelper.accessor(item => describeConstituenta(item.leftCst), {
id: 'left_text',
header: 'Описание',
size: 1000,
cell: props => <div className='text-xs text-ellipsis'>{props.getValue()}</div>
}),
columnHelper.accessor(item => item.leftCst.alias, {
id: 'left_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
cell: props => (
<BadgeConstituenta theme={colors} value={props.row.original.leftCst} prefixID={`${prefixID}_1_`} />
)
}),
columnHelper.display({
id: 'status',
header: '',
size: 40,
cell: props => <SubstitutionIcon item={props.row.original} />
}),
columnHelper.accessor(item => item.rightCst.alias, {
id: 'right_alias',
header: () => <span className='pl-3'>Имя</span>,
size: 65,
cell: props => (
<BadgeConstituenta theme={colors} value={props.row.original.rightCst} prefixID={`${prefixID}_2_`} />
)
}),
columnHelper.accessor(item => describeConstituenta(item.rightCst), {
id: 'right_text',
header: 'Описание',
minSize: 1000,
cell: props => <div className='text-xs text-ellipsis text-pretty'>{props.getValue()}</div>
}),
columnHelper.display({
id: 'actions',
cell: props => (
<MiniButton
noHover
title='Удалить'
icon={<IconRemove size='1rem' className='icon-red' />}
onClick={() => handleDeleteRow(props.row.index)}
/>
)
})
],
[handleDeleteRow, colors, prefixID]
);
return (
<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 !== schema2 ? schema1?.alias ?? 'Схема 1' : ''} />
<div className='cc-icons'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
</div>
<SelectConstituenta
items={schema1?.items.filter(cst => !filter1 || filter1(cst))}
value={leftCst}
onSelectValue={setLeftCst}
/>
</div>
<MiniButton
noHover
title='Добавить в таблицу отождествлений'
className='mb-[0.375rem] grow-0'
icon={<IconReplace size='1.5rem' className='icon-primary' />}
disabled={!leftCst || !rightCst || leftCst === rightCst}
onClick={addSubstitution}
/>
<div className='flex-grow basis-1/2'>
<div className='flex items-center justify-between'>
<Label text={schema1 !== schema2 ? schema2?.alias ?? 'Схема 2' : ''} />
<div className='cc-icons'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
!deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
!takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
</div>
<SelectConstituenta
items={schema2?.items.filter(cst => !filter2 || filter2(cst))}
value={rightCst}
onSelectValue={setRightCst}
/>
</div>
</div>
<DataTable
dense
noHeader
noFooter
className='w-full text-sm border select-none cc-scroll-y'
rows={rows}
contentHeight='1.3rem'
data={items}
columns={columns}
headPosition='0'
noDataComponent={
<NoData className='min-h-[2rem]'>
<p>Список пуст</p>
<p>Добавьте отождествление</p>
</NoData>
}
/>
</div>
);
}
export default PickInlineSubstitutions;

View File

@ -7,109 +7,96 @@ import SelectConstituenta from '@/components/select/SelectConstituenta';
import DataTable, { createColumnHelper } from '@/components/ui/DataTable';
import MiniButton from '@/components/ui/MiniButton';
import { useConceptOptions } from '@/context/ConceptOptionsContext';
import { LibraryItemID } from '@/models/library';
import { ICstSubstitute, IMultiSubstitution, IOperation } from '@/models/oss';
import { ILibraryItem } from '@/models/library';
import { ICstSubstitute, IMultiSubstitution } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import {
IconKeepAliasOff,
IconKeepAliasOn,
IconKeepTermOff,
IconKeepTermOn,
IconPageLast,
IconPageRight,
IconRemove,
IconReplace
} from '../Icons';
import { IconPageLeft, IconPageRight, IconRemove, IconReplace } from '../Icons';
import NoData from '../ui/NoData';
import SelectOperation from './SelectOperation';
function SubstitutionIcon({ item, className }: { item: IMultiSubstitution; className?: string }) {
if (!item.transfer_term) {
return <IconPageRight size='1.2rem' className={className} />;
} else {
return <IconPageLast size='1.2rem' className={className} />;
}
}
import SelectLibraryItem from './SelectLibraryItem';
interface PickSubstitutionsProps {
prefixID: string;
rows?: number;
operations: IOperation[];
getSchema: (id: LibraryItemID) => IRSForm | undefined;
getConstituenta: (id: ConstituentaID) => IConstituenta | undefined;
getSchemaByCst: (id: ConstituentaID) => IRSForm | undefined;
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
prefixID: string;
rows?: number;
allowSelfSubstitution?: boolean;
schemas: IRSForm[];
filter?: (cst: IConstituenta) => boolean;
}
const columnHelper = createColumnHelper<IMultiSubstitution>();
function PickSubstitutions({
substitutions,
setSubstitutions,
prefixID,
rows,
operations,
getSchema,
getConstituenta,
getSchemaByCst,
substitutions,
setSubstitutions
schemas,
filter,
allowSelfSubstitution
}: PickSubstitutionsProps) {
const { colors } = useConceptOptions();
const [leftArgument, setLeftArgument] = useState<IOperation | undefined>(undefined);
const [rightArgument, setRightArgument] = useState<IOperation | undefined>(undefined);
const leftSchema = useMemo(
() => (leftArgument?.result ? getSchema(leftArgument.result) : undefined),
[leftArgument, getSchema]
const [leftArgument, setLeftArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 ? schemas[0] : undefined
);
const [rightArgument, setRightArgument] = useState<ILibraryItem | undefined>(
schemas.length === 1 && allowSelfSubstitution ? schemas[0] : undefined
);
const rightSchema = useMemo(
() => (rightArgument?.result ? getSchema(rightArgument.result) : undefined),
[rightArgument, getSchema]
);
const [leftCst, setLeftCst] = useState<IConstituenta | undefined>(undefined);
const [rightCst, setRightCst] = useState<IConstituenta | undefined>(undefined);
const [deleteRight, setDeleteRight] = useState(true);
const [takeLeftTerm, setTakeLeftTerm] = useState(true);
const toggleDelete = () => setDeleteRight(prev => !prev);
const operationByConstituenta = useCallback(
(cst: ConstituentaID): IOperation | undefined => {
const schema = getSchemaByCst(cst);
if (!schema) {
return undefined;
const getSchemaByCst = useCallback(
(id: ConstituentaID): IRSForm | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return schema;
}
const cstOperations = operations.filter(item => item.result === schema.id);
return cstOperations.length === 1 ? cstOperations[0] : undefined;
}
return undefined;
},
[getSchemaByCst, operations]
[schemas]
);
const getConstituenta = useCallback(
(id: ConstituentaID): IConstituenta | undefined => {
for (const schema of schemas) {
const cst = schema.cstByID.get(id);
if (cst) {
return cst;
}
}
return undefined;
},
[schemas]
);
const substitutionData: IMultiSubstitution[] = useMemo(
() =>
substitutions.map(item => ({
original_operation: operationByConstituenta(item.original),
original_source: getSchemaByCst(item.original),
original: getConstituenta(item.original),
substitution: getConstituenta(item.substitution),
substitution_operation: operationByConstituenta(item.substitution),
transfer_term: item.transfer_term
substitution_source: getSchemaByCst(item.substitution)
})),
[getConstituenta, operationByConstituenta, substitutions]
[getConstituenta, getSchemaByCst, substitutions]
);
const toggleDelete = () => setDeleteRight(prev => !prev);
const toggleTerm = () => setTakeLeftTerm(prev => !prev);
function addSubstitution() {
if (!leftCst || !rightCst) {
return;
}
const newSubstitution: ICstSubstitute = {
original: deleteRight ? rightCst.id : leftCst.id,
substitution: deleteRight ? leftCst.id : rightCst.id,
transfer_term: deleteRight != takeLeftTerm
substitution: deleteRight ? leftCst.id : rightCst.id
};
setSubstitutions(prev => [...prev, newSubstitution]);
setLeftCst(undefined);
@ -133,11 +120,11 @@ function PickSubstitutions({
const columns = useMemo(
() => [
columnHelper.accessor(item => item.substitution_operation?.alias ?? 'N/A', {
columnHelper.accessor(item => item.substitution_source?.alias ?? 'N/A', {
id: 'left_schema',
header: 'Операция',
size: 100,
cell: props => <div className='min-w-[10rem] text-ellipsis text-right'>{props.getValue()}</div>
cell: props => <div className='min-w-[10.5rem] text-ellipsis text-right'>{props.getValue()}</div>
}),
columnHelper.accessor(item => item.substitution?.alias ?? 'N/A', {
id: 'left_alias',
@ -154,7 +141,7 @@ function PickSubstitutions({
id: 'status',
header: '',
size: 40,
cell: props => <SubstitutionIcon item={props.row.original} />
cell: () => <IconPageRight size='1.2rem' />
}),
columnHelper.accessor(item => item.original?.alias ?? 'N/A', {
id: 'right_alias',
@ -167,7 +154,7 @@ function PickSubstitutions({
'N/A'
)
}),
columnHelper.accessor(item => item.original_operation?.alias ?? 'N/A', {
columnHelper.accessor(item => item.original_source?.alias ?? 'N/A', {
id: 'right_schema',
header: 'Операция',
size: 100,
@ -194,96 +181,60 @@ function PickSubstitutions({
<div className='flex flex-col w-full'>
<div className='flex items-end gap-3 justify-stretch'>
<div className='flex-grow flex flex-col basis-1/2'>
<div className='cc-icons mb-1 w-fit mx-auto'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectOperation
<SelectLibraryItem
noBorder
placeholder='Выберите аргумент'
items={operations.filter(item => item.id !== rightArgument?.id)}
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== rightArgument?.id)}
value={leftArgument}
onSelectValue={setLeftArgument}
/>
<SelectConstituenta
noBorder
items={leftSchema?.items.filter(cst => !substitutions.find(item => item.original === cst.id))}
items={(leftArgument as IRSForm)?.items.filter(
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
)}
value={leftCst}
onSelectValue={setLeftCst}
/>
</div>
</div>
<div className='flex flex-col gap-1'>
<MiniButton
title={deleteRight ? 'Заменить правую' : 'Заменить левую'}
onClick={toggleDelete}
icon={
deleteRight ? (
<IconPageRight size='1.5rem' className='clr-text-primary' />
) : (
<IconPageLeft size='1.5rem' className='clr-text-primary' />
)
}
/>
<MiniButton
noHover
title='Добавить в таблицу отождествлений'
className='mb-[0.375rem] grow-0'
icon={<IconReplace size='1.5rem' className='icon-primary' />}
disabled={!leftCst || !rightCst || leftCst === rightCst}
onClick={addSubstitution}
/>
</div>
<div className='flex-grow basis-1/2'>
<div className='cc-icons mb-1 w-fit mx-auto'>
<MiniButton
title='Сохранить конституенту'
noHover
onClick={toggleDelete}
icon={
!deleteRight ? (
<IconKeepAliasOn size='1rem' className='clr-text-green' />
) : (
<IconKeepAliasOff size='1rem' className='clr-text-red' />
)
}
/>
<MiniButton
title='Сохранить термин'
noHover
onClick={toggleTerm}
icon={
!takeLeftTerm ? (
<IconKeepTermOn size='1rem' className='clr-text-green' />
) : (
<IconKeepTermOff size='1rem' className='clr-text-red' />
)
}
/>
</div>
<div className='flex flex-col gap-[0.125rem] border-x border-t clr-input'>
<SelectOperation
<SelectLibraryItem
noBorder
placeholder='Выберите аргумент'
items={operations.filter(item => item.id !== leftArgument?.id)}
items={allowSelfSubstitution ? schemas : schemas.filter(item => item.id !== leftArgument?.id)}
value={rightArgument}
onSelectValue={setRightArgument}
/>
<SelectConstituenta
noBorder
items={rightSchema?.items.filter(cst => !substitutions.find(item => item.original === cst.id))}
items={(rightArgument as IRSForm)?.items.filter(
cst => !substitutions.find(item => item.original === cst.id) && (!filter || filter(cst))
)}
value={rightCst}
onSelectValue={setRightCst}
/>

View File

@ -0,0 +1,60 @@
'use client';
import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { ILibraryItem, LibraryItemID } from '@/models/library';
import { matchLibraryItem } from '@/models/libraryAPI';
import { CProps } from '../props';
import SelectSingle from '../ui/SelectSingle';
interface SelectLibraryItemProps extends CProps.Styling {
items?: ILibraryItem[];
value?: ILibraryItem;
onSelectValue: (newValue?: ILibraryItem) => void;
placeholder?: string;
noBorder?: boolean;
}
function SelectLibraryItem({
className,
items,
value,
onSelectValue,
placeholder = 'Выберите схему',
...restProps
}: SelectLibraryItemProps) {
const options = useMemo(() => {
return (
items?.map(cst => ({
value: cst.id,
label: `${cst.alias}: ${cst.title}`
})) ?? []
);
}, [items]);
const filter = useCallback(
(option: { value: LibraryItemID | undefined; label: string }, inputValue: string) => {
const item = items?.find(item => item.id === option.value);
return !item ? false : matchLibraryItem(item, inputValue);
},
[items]
);
return (
<SelectSingle
className={clsx('text-ellipsis', className)}
options={options}
value={value ? { value: value.id, label: `${value.alias}: ${value.title}` } : null}
onChange={data => onSelectValue(items?.find(cst => cst.id === data?.value))}
// @ts-expect-error: TODO: use type definitions from react-select in filter object
filterOption={filter}
placeholder={placeholder}
{...restProps}
/>
);
}
export default SelectLibraryItem;

View File

@ -5,7 +5,6 @@ import { useCallback, useMemo, useState } from 'react';
import { IconReset } from '@/components/Icons';
import PickSchema from '@/components/select/PickSchema';
import Checkbox from '@/components/ui/Checkbox';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import Modal, { ModalProps } from '@/components/ui/Modal';
@ -15,12 +14,11 @@ import { IOperation, IOperationSchema } from '@/models/oss';
interface DlgChangeInputSchemaProps extends Pick<ModalProps, 'hideWindow'> {
oss: IOperationSchema;
target: IOperation;
onSubmit: (newSchema: LibraryItemID | undefined, syncText: boolean) => void;
onSubmit: (newSchema: LibraryItemID | undefined) => void;
}
function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeInputSchemaProps) {
const [selected, setSelected] = useState<LibraryItemID | undefined>(target.result ?? undefined);
const [syncText, setSyncText] = useState(target.sync_text);
const baseFilter = useCallback(
(item: ILibraryItem) => !oss.schemas.includes(item.id) || item.id === selected || item.id === target.result,
@ -34,7 +32,7 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
}, []);
function handleSubmit() {
onSubmit(selected, syncText);
onSubmit(selected);
}
return (
@ -66,12 +64,6 @@ function DlgChangeInputSchema({ oss, hideWindow, target, onSubmit }: DlgChangeIn
rows={8}
baseFilter={baseFilter}
/>
<Checkbox
value={syncText}
setValue={setSyncText}
label='Синхронизировать текст'
titleHtml='Загрузить текстовые поля<br/> из концептуальной схемы'
/>
</Modal>
);
}

View File

@ -38,7 +38,6 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
const [comment, setComment] = useState('');
const [inputs, setInputs] = useState<OperationID[]>([]);
const [attachedID, setAttachedID] = useState<LibraryItemID | undefined>(undefined);
const [syncText, setSyncText] = useState(true);
const [createSchema, setCreateSchema] = useState(false);
const isValid = useMemo(
@ -65,7 +64,6 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
alias: alias,
title: title,
comment: comment,
sync_text: activeTab === TabID.INPUT ? syncText : true,
operation_type: activeTab === TabID.INPUT ? OperationType.INPUT : OperationType.SYNTHESIS,
result: activeTab === TabID.INPUT ? attachedID ?? null : null
},
@ -89,14 +87,12 @@ function DlgCreateOperation({ hideWindow, oss, onCreate }: DlgCreateOperationPro
setTitle={setTitle}
attachedID={attachedID}
setAttachedID={setAttachedID}
syncText={syncText}
setSyncText={setSyncText}
createSchema={createSchema}
setCreateSchema={setCreateSchema}
/>
</TabPanel>
),
[alias, comment, title, attachedID, syncText, oss, createSchema]
[alias, comment, title, attachedID, oss, createSchema]
);
const synthesisPanel = useMemo(

View File

@ -5,7 +5,6 @@ import { useCallback, useEffect } from 'react';
import { IconReset } from '@/components/Icons';
import PickSchema from '@/components/select/PickSchema';
import Checkbox from '@/components/ui/Checkbox';
import FlexColumn from '@/components/ui/FlexColumn';
import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton';
import TextArea from '@/components/ui/TextArea';
@ -25,8 +24,6 @@ interface TabInputOperationProps {
setComment: React.Dispatch<React.SetStateAction<string>>;
attachedID: LibraryItemID | undefined;
setAttachedID: React.Dispatch<React.SetStateAction<LibraryItemID | undefined>>;
syncText: boolean;
setSyncText: React.Dispatch<React.SetStateAction<boolean>>;
createSchema: boolean;
setCreateSchema: React.Dispatch<React.SetStateAction<boolean>>;
}
@ -41,8 +38,6 @@ function TabInputOperation({
setComment,
attachedID,
setAttachedID,
syncText,
setSyncText,
createSchema,
setCreateSchema
}: TabInputOperationProps) {
@ -51,9 +46,8 @@ function TabInputOperation({
useEffect(() => {
if (createSchema) {
setAttachedID(undefined);
setSyncText(true);
}
}, [createSchema, setAttachedID, setSyncText]);
}, [createSchema, setAttachedID]);
return (
<AnimateFade className='cc-column'>
@ -62,10 +56,9 @@ function TabInputOperation({
label='Полное название'
value={title}
onChange={event => setTitle(event.target.value)}
disabled={syncText && attachedID !== undefined}
disabled={attachedID !== undefined}
/>
<div className='flex gap-6'>
<FlexColumn>
<TextInput
id='operation_alias'
label='Сокращение'
@ -74,15 +67,8 @@ function TabInputOperation({
title={`не более ${limits.library_alias_len} символов`}
value={alias}
onChange={event => setAlias(event.target.value)}
disabled={syncText && attachedID !== undefined}
disabled={attachedID !== undefined}
/>
<Checkbox
value={syncText}
setValue={setSyncText}
label='Синхронизировать текст'
titleHtml='Загрузить текстовые поля<br/> из концептуальной схемы'
/>
</FlexColumn>
<TextArea
id='operation_comment'
@ -91,7 +77,7 @@ function TabInputOperation({
rows={3}
value={comment}
onChange={event => setComment(event.target.value)}
disabled={syncText && attachedID !== undefined}
disabled={attachedID !== undefined}
/>
</div>

View File

@ -11,9 +11,9 @@ import { prefixes } from '@/utils/constants';
import ListConstituents from './ListConstituents';
interface DlgDeleteCstProps extends Pick<ModalProps, 'hideWindow'> {
schema: IRSForm;
selected: ConstituentaID[];
onDelete: (items: ConstituentaID[]) => void;
schema: IRSForm;
}
function DlgDeleteCst({ hideWindow, selected, schema, onDelete }: DlgDeleteCstProps) {

View File

@ -43,7 +43,6 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
const [alias, setAlias] = useState(target.alias);
const [title, setTitle] = useState(target.title);
const [comment, setComment] = useState(target.comment);
const [syncText, setSyncText] = useState(true);
const [inputs, setInputs] = useState<OperationID[]>(oss.graph.expandInputs([target.id]));
const inputOperations = useMemo(() => inputs.map(id => oss.operationByID.get(id)!), [inputs, oss.operationByID]);
@ -53,6 +52,10 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>(oss.substitutions);
const cache = useRSFormCache();
const schemas = useMemo(
() => schemasIDs.map(id => cache.getSchema(id)).filter(item => item !== undefined),
[schemasIDs, cache]
);
const isValid = useMemo(() => alias !== '', [alias]);
@ -67,8 +70,7 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
item_data: {
alias: alias,
title: title,
comment: comment,
sync_text: syncText
comment: comment
},
positions: [],
arguments: target.operation_type !== OperationType.SYNTHESIS ? undefined : inputs,
@ -87,12 +89,10 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
setComment={setComment}
title={title}
setTitle={setTitle}
syncText={syncText}
setSyncText={setSyncText}
/>
</TabPanel>
),
[alias, comment, title, syncText]
[alias, comment, title]
);
const argumentsPanel = useMemo(
@ -113,26 +113,15 @@ function DlgEditOperation({ hideWindow, oss, target, onSubmit }: DlgEditOperatio
() => (
<TabPanel>
<TabSynthesis
operations={inputOperations}
schemas={schemas}
loading={cache.loading}
error={cache.error}
getSchema={cache.getSchema}
getConstituenta={cache.getConstituenta}
getSchemaByCst={cache.getSchemaByCst}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>
</TabPanel>
),
[
inputOperations,
cache.loading,
cache.error,
cache.getSchema,
cache.getConstituenta,
substitutions,
cache.getSchemaByCst
]
[cache.loading, cache.error, substitutions, schemas]
);
return (

View File

@ -1,5 +1,3 @@
import Checkbox from '@/components/ui/Checkbox';
import FlexColumn from '@/components/ui/FlexColumn';
import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput';
import AnimateFade from '@/components/wrap/AnimateFade';
@ -12,20 +10,9 @@ interface TabOperationProps {
setTitle: React.Dispatch<React.SetStateAction<string>>;
comment: string;
setComment: React.Dispatch<React.SetStateAction<string>>;
syncText: boolean;
setSyncText: React.Dispatch<React.SetStateAction<boolean>>;
}
function TabOperation({
alias,
setAlias,
title,
setTitle,
comment,
setComment,
syncText,
setSyncText
}: TabOperationProps) {
function TabOperation({ alias, setAlias, title, setTitle, comment, setComment }: TabOperationProps) {
return (
<AnimateFade className='cc-column'>
<TextInput
@ -35,7 +22,6 @@ function TabOperation({
onChange={event => setTitle(event.target.value)}
/>
<div className='flex gap-6'>
<FlexColumn>
<TextInput
id='operation_alias'
label='Сокращение'
@ -45,13 +31,6 @@ function TabOperation({
value={alias}
onChange={event => setAlias(event.target.value)}
/>
<Checkbox
value={syncText}
setValue={setSyncText}
label='Синхронизировать текст'
titleHtml='Загрузить текстовые поля<br/> из концептуальной схемы'
/>
</FlexColumn>
<TextArea
id='operation_comment'

View File

@ -1,42 +1,26 @@
import { ErrorData } from '@/components/info/InfoError';
import PickSubstitutions from '@/components/select/PickSubstitutions';
import DataLoader from '@/components/wrap/DataLoader';
import { LibraryItemID } from '@/models/library';
import { ICstSubstitute, IOperation } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { ICstSubstitute } from '@/models/oss';
import { IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
interface TabSynthesisProps {
loading: boolean;
error: ErrorData;
operations: IOperation[];
getSchema: (id: LibraryItemID) => IRSForm | undefined;
getConstituenta: (id: ConstituentaID) => IConstituenta | undefined;
getSchemaByCst: (id: ConstituentaID) => IRSForm | undefined;
schemas: IRSForm[];
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
}
function TabSynthesis({
operations,
loading,
error,
getSchema,
getConstituenta,
getSchemaByCst,
substitutions,
setSubstitutions
}: TabSynthesisProps) {
function TabSynthesis({ schemas, loading, error, substitutions, setSubstitutions }: TabSynthesisProps) {
return (
<DataLoader id='dlg-synthesis-tab' className='cc-column' isLoading={loading} error={error}>
<DataLoader id='dlg-synthesis-tab' className='cc-column mt-3' isLoading={loading} error={error}>
<PickSubstitutions
schemas={schemas}
prefixID={prefixes.dlg_cst_substitutes_list}
rows={8}
operations={operations}
getSchema={getSchema}
getConstituenta={getConstituenta}
getSchemaByCst={getSchemaByCst}
substitutions={substitutions}
setSubstitutions={setSubstitutions}
/>

View File

@ -8,7 +8,8 @@ import Modal, { ModalProps } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel';
import useRSFormDetails from '@/hooks/useRSFormDetails';
import { LibraryItemID } from '@/models/library';
import { IBinarySubstitution, IInlineSynthesisData, IRSForm } from '@/models/rsform';
import { ICstSubstitute } from '@/models/oss';
import { IInlineSynthesisData, IRSForm } from '@/models/rsform';
import TabConstituents from './TabConstituents';
import TabSchema from './TabSchema';
@ -30,7 +31,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
const [donorID, setDonorID] = useState<LibraryItemID | undefined>(undefined);
const [selected, setSelected] = useState<LibraryItemID[]>([]);
const [substitutions, setSubstitutions] = useState<IBinarySubstitution[]>([]);
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]);
const source = useRSFormDetails({ target: donorID ? String(donorID) : undefined });
@ -44,11 +45,7 @@ function DlgInlineSynthesis({ hideWindow, receiver, onInlineSynthesis }: DlgInli
source: source.schema?.id,
receiver: receiver.id,
items: selected,
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
}))
substitutions: substitutions
};
onInlineSynthesis(data);
}

View File

@ -1,11 +1,13 @@
'use client';
import { ErrorData } from '@/components/info/InfoError';
import DataLoader from '@/components/wrap/DataLoader';
import { ConstituentaID, IBinarySubstitution, IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
import { useCallback, useMemo } from 'react';
import PickInlineSubstitutions from '../../components/select/PickInlineSubstitutions';
import { ErrorData } from '@/components/info/InfoError';
import PickSubstitutions from '@/components/select/PickSubstitutions';
import DataLoader from '@/components/wrap/DataLoader';
import { ICstSubstitute } from '@/models/oss';
import { ConstituentaID, IConstituenta, IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
interface TabSubstitutionsProps {
receiver?: IRSForm;
@ -15,8 +17,8 @@ interface TabSubstitutionsProps {
loading?: boolean;
error?: ErrorData;
substitutions: IBinarySubstitution[];
setSubstitutions: React.Dispatch<React.SetStateAction<IBinarySubstitution[]>>;
substitutions: ICstSubstitute[];
setSubstitutions: React.Dispatch<React.SetStateAction<ICstSubstitute[]>>;
}
function TabSubstitutions({
@ -30,16 +32,22 @@ function TabSubstitutions({
substitutions,
setSubstitutions
}: TabSubstitutionsProps) {
const filter = useCallback(
(cst: IConstituenta) => cst.id !== source?.id || selected.includes(cst.id),
[selected, source]
);
const schemas = useMemo(() => [...(source ? [source] : []), ...(receiver ? [receiver] : [])], [source, receiver]);
return (
<DataLoader id='dlg-substitutions-tab' className='cc-column' isLoading={loading} error={error} hasNoData={!source}>
<PickInlineSubstitutions
items={substitutions}
setItems={setSubstitutions}
<PickSubstitutions
substitutions={substitutions}
setSubstitutions={setSubstitutions}
rows={10}
prefixID={prefixes.cst_inline_synth_substitutes}
schema1={receiver}
schema2={source}
filter2={cst => selected.includes(cst.id)}
schemas={schemas}
filter={filter}
/>
</DataLoader>
);

View File

@ -3,31 +3,25 @@
import clsx from 'clsx';
import { useMemo, useState } from 'react';
import PickInlineSubstitutions from '@/components/select/PickInlineSubstitutions';
import PickSubstitutions from '@/components/select/PickSubstitutions';
import Modal, { ModalProps } from '@/components/ui/Modal';
import { useRSForm } from '@/context/RSFormContext';
import { ICstSubstituteData } from '@/models/oss';
import { IBinarySubstitution } from '@/models/rsform';
import { ICstSubstitute, ICstSubstituteData } from '@/models/oss';
import { IRSForm } from '@/models/rsform';
import { prefixes } from '@/utils/constants';
interface DlgSubstituteCstProps extends Pick<ModalProps, 'hideWindow'> {
schema: IRSForm;
onSubstitute: (data: ICstSubstituteData) => void;
}
function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
const { schema } = useRSForm();
const [substitutions, setSubstitutions] = useState<IBinarySubstitution[]>([]);
function DlgSubstituteCst({ hideWindow, onSubstitute, schema }: DlgSubstituteCstProps) {
const [substitutions, setSubstitutions] = useState<ICstSubstitute[]>([]);
const canSubmit = useMemo(() => substitutions.length > 0, [substitutions]);
function handleSubmit() {
const data: ICstSubstituteData = {
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
}))
substitutions: substitutions
};
onSubstitute(data);
}
@ -42,13 +36,13 @@ function DlgSubstituteCst({ hideWindow, onSubstitute }: DlgSubstituteCstProps) {
onSubmit={handleSubmit}
className={clsx('w-[40rem]', 'px-6 pb-3')}
>
<PickInlineSubstitutions
items={substitutions}
setItems={setSubstitutions}
<PickSubstitutions
allowSelfSubstitution
substitutions={substitutions}
setSubstitutions={setSubstitutions}
rows={6}
prefixID={prefixes.dlg_cst_substitutes_list}
schema1={schema}
schema2={schema}
schemas={[schema]}
/>
</Modal>
);

View File

@ -30,7 +30,6 @@ export interface IOperation {
alias: string;
title: string;
comment: string;
sync_text: boolean;
position_x: number;
position_y: number;
@ -63,7 +62,7 @@ export interface ITargetOperation extends IPositionsData {
export interface IOperationCreateData extends IPositionsData {
item_data: Pick<
IOperation,
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result' | 'sync_text'
'alias' | 'operation_type' | 'title' | 'comment' | 'position_x' | 'position_y' | 'result'
>;
arguments: OperationID[] | undefined;
create_schema: boolean;
@ -73,7 +72,7 @@ export interface IOperationCreateData extends IPositionsData {
* Represents {@link IOperation} data, used in update process.
*/
export interface IOperationUpdateData extends ITargetOperation {
item_data: Pick<IOperation, 'alias' | 'title' | 'comment' | 'sync_text'>;
item_data: Pick<IOperation, 'alias' | 'title' | 'comment'>;
arguments: OperationID[] | undefined;
substitutions: ICstSubstitute[] | undefined;
}
@ -82,7 +81,6 @@ export interface IOperationUpdateData extends ITargetOperation {
* Represents {@link IOperation} data, used in setInput process.
*/
export interface IOperationSetInputData extends ITargetOperation {
sync_text: boolean;
input: LibraryItemID | null;
}
@ -100,7 +98,6 @@ export interface IArgument {
export interface ICstSubstitute {
original: ConstituentaID;
substitution: ConstituentaID;
transfer_term: boolean;
}
/**
@ -114,11 +111,10 @@ export interface ICstSubstituteData {
* Represents substitution for multi synthesis table.
*/
export interface IMultiSubstitution {
original_operation: IOperation | undefined;
original_source: ILibraryItem | undefined;
original: IConstituenta | undefined;
substitution: IConstituenta | undefined;
substitution_operation: IOperation | undefined;
transfer_term: boolean;
substitution_source: ILibraryItem | undefined;
}
/**

View File

@ -247,7 +247,6 @@ export interface IBinarySubstitution {
leftCst: IConstituenta;
rightCst: IConstituenta;
deleteRight: boolean;
takeLeftTerm: boolean;
}
/**

View File

@ -4,8 +4,6 @@ import {
IconGenerateNames,
IconGenerateStructure,
IconInlineSynthesis,
IconKeepAliasOn,
IconKeepTermOn,
IconReplace,
IconSortList,
IconTemplates
@ -60,13 +58,7 @@ function HelpRSLangOperations() {
</h2>
<p>
Формирование таблицы отождествлений и ее применение к текущей схеме. В результате будет удален ряд конституент и
их вхождения заменены на другие. Возможна настройка какой термин использовать для оставшихся конституент
<li>
<IconKeepAliasOn size='1.25rem' className='inline-icon' /> выбор сохраняемой конституенты
</li>
<li>
<IconKeepTermOn size='1.25rem' className='inline-icon' /> выбор сохраняемого термина
</li>
их вхождения заменены на другие.
</p>
<h2>

View File

@ -266,6 +266,12 @@ function OssFlow({ isModified, setIsModified }: OssFlowProps) {
handleSavePositions();
return;
}
if ((event.ctrlKey || event.metaKey) && event.key === 'q') {
event.preventDefault();
event.stopPropagation();
handleCreateOperation();
return;
}
if (event.key === 'Delete') {
event.preventDefault();
event.stopPropagation();

View File

@ -128,7 +128,7 @@ function ToolbarOssGraph({
onClick={onSavePositions}
/>
<MiniButton
title={prepareTooltip('Новая операция', 'Ctrl + Q')}
titleHtml={prepareTooltip('Новая операция', 'Ctrl + Q')}
icon={<IconNewItem size='1.25rem' className='icon-green' />}
disabled={controller.isProcessing}
onClick={onCreate}

View File

@ -265,14 +265,13 @@ export const OssEditState = ({ selected, setSelected, children }: OssEditStatePr
}, []);
const setTargetInput = useCallback(
(newInput: LibraryItemID | undefined, syncText: boolean) => {
(newInput: LibraryItemID | undefined) => {
if (!targetOperationID) {
return;
}
const data: IOperationSetInputData = {
target: targetOperationID,
positions: positions,
sync_text: syncText,
input: newInput ?? null
};
model.setInput(data, () => toast.success(information.changesSaved));

View File

@ -646,6 +646,7 @@ export const RSEditState = ({
) : null}
{showSubstitute ? (
<DlgSubstituteCst
schema={model.schema}
hideWindow={() => setShowSubstitute(false)} // prettier: split lines
onSubstitute={handleSubstituteCst}
/>