ConceptPortal-public/rsconcept/frontend/src/dialogs/DlgEditWordForms.tsx

341 lines
10 KiB
TypeScript
Raw Normal View History

2023-09-21 14:58:01 +03:00
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
2023-09-11 20:31:54 +03:00
2023-11-01 13:47:49 +03:00
import ConceptTooltip from '../components/Common/ConceptTooltip';
import MiniButton from '../components/Common/MiniButton';
import Modal from '../components/Common/Modal';
import SelectMulti from '../components/Common/SelectMulti';
import TextArea from '../components/Common/TextArea';
import DataTable, { createColumnHelper } from '../components/DataTable';
import HelpTerminologyControl from '../components/Help/HelpTerminologyControl';
import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossIcon, HelpIcon } from '../components/Icons';
import { useConceptTheme } from '../context/ThemeContext';
import useConceptText from '../hooks/useConceptText';
2023-09-21 23:09:51 +03:00
import {
2023-09-29 15:33:32 +03:00
getCompatibleGrams, Grammeme, ITextRequest, IWordForm,
IWordFormPlain, matchWordForm, parseGrammemes
2023-11-01 13:47:49 +03:00
} from '../models/language';
import { IConstituenta, TermForm } from '../models/rsform';
import { colorfgGrammeme } from '../utils/color';
import { labelGrammeme } from '../utils/labels';
import { compareGrammemeOptions,IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../utils/selectors';
2023-09-11 20:31:54 +03:00
2023-09-29 15:33:32 +03:00
interface DlgEditWordFormsProps {
2023-09-11 20:31:54 +03:00
hideWindow: () => void
target: IConstituenta
2023-09-21 14:58:01 +03:00
onSave: (data: TermForm[]) => void
2023-09-11 20:31:54 +03:00
}
2023-09-21 14:58:01 +03:00
const columnHelper = createColumnHelper<IWordForm>();
2023-09-29 15:33:32 +03:00
function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps) {
2023-09-25 14:17:52 +03:00
const textProcessor = useConceptText();
2023-09-21 23:09:51 +03:00
const { colors } = useConceptTheme();
2023-09-11 20:31:54 +03:00
const [term, setTerm] = useState('');
2023-09-19 17:55:17 +03:00
const [inputText, setInputText] = useState('');
2023-09-21 14:58:01 +03:00
const [inputGrams, setInputGrams] = useState<IGrammemeOption[]>([]);
const [options, setOptions] = useState<IGrammemeOption[]>([]);
2023-09-19 17:55:17 +03:00
2023-09-21 14:58:01 +03:00
const [forms, setForms] = useState<IWordForm[]>([]);
2023-09-19 17:55:17 +03:00
2023-09-21 14:58:01 +03:00
function getData(): TermForm[] {
const result: TermForm[] = [];
forms.forEach(
({text, grams}) => result.push({
text: text,
2023-09-27 23:36:51 +03:00
tags: grams.join(',')
2023-09-21 14:58:01 +03:00
}));
return result;
}
2023-09-11 20:31:54 +03:00
2023-09-21 14:58:01 +03:00
// Initialization
useLayoutEffect(
() => {
const initForms: IWordForm[] = [];
target.term_forms.forEach(
term => initForms.push({
text: term.text,
grams: parseGrammemes(term.tags),
}));
setForms(initForms);
setTerm(target.term_resolved);
2023-09-21 23:09:51 +03:00
setInputText(target.term_resolved);
2023-09-25 14:17:52 +03:00
setInputGrams([]);
2023-09-21 14:58:01 +03:00
}, [target]);
2023-09-11 20:31:54 +03:00
2023-09-21 14:58:01 +03:00
// Filter grammemes when input changes
useEffect(
() => {
2023-09-29 15:33:32 +03:00
const compatible = getCompatibleGrams(
inputGrams
.filter(data => Object.values(Grammeme).includes(data.value as Grammeme))
.map(data => data.value as Grammeme)
);
setOptions(SelectorGrammems.filter(({value}) => compatible.includes(value as Grammeme)));
2023-09-21 14:58:01 +03:00
}, [inputGrams]);
const handleSubmit = () => onSave(getData());
2023-09-11 20:31:54 +03:00
2023-09-19 17:55:17 +03:00
function handleAddForm() {
2023-09-21 23:09:51 +03:00
const newForm: IWordForm = {
text: inputText,
2023-09-27 23:36:51 +03:00
grams: inputGrams.map(item => item.value)
2023-09-21 23:09:51 +03:00
};
2023-09-21 14:58:01 +03:00
setForms(forms => [
2023-09-25 14:17:52 +03:00
newForm,
...forms.filter(value => !matchWordForm(value, newForm))
2023-09-21 14:58:01 +03:00
]);
2023-09-19 17:55:17 +03:00
}
2023-09-21 23:09:51 +03:00
function handleDeleteRow(row: number) {
setForms(
(prev) => {
const newForms: IWordForm[] = [];
prev.forEach(
(form, index) => {
if (index !== row) {
newForms.push(form);
}
});
return newForms;
});
}
2023-09-25 14:17:52 +03:00
function handleRowClicked(form: IWordForm) {
setInputText(form.text);
2023-09-27 23:36:51 +03:00
setInputGrams(SelectorGrammems.filter(gram => form.grams.find(test => test === gram.value)));
2023-09-25 14:17:52 +03:00
}
2023-09-30 12:47:27 +03:00
function handleResetAll() {
setForms([]);
2023-09-19 17:55:17 +03:00
}
2023-09-25 14:17:52 +03:00
function handleInflect() {
const data: IWordFormPlain = {
text: term,
2023-09-27 23:36:51 +03:00
grams: inputGrams.map(gram => gram.value).join(',')
2023-09-25 14:17:52 +03:00
}
textProcessor.inflect(data, response => setInputText(response.result));
}
function handleParse() {
const data: ITextRequest = {
text: inputText
}
textProcessor.parse(data, response => {
const grams = parseGrammemes(response.result);
2023-09-27 23:36:51 +03:00
setInputGrams(SelectorGrammems.filter(gram => grams.find(test => test === gram.value)));
2023-09-25 14:17:52 +03:00
});
2023-09-19 17:55:17 +03:00
}
2023-09-25 14:17:52 +03:00
function handleGenerateLexeme() {
2023-09-21 23:09:51 +03:00
if (forms.length > 0) {
if (!window.confirm('Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?')) {
return;
}
}
2023-09-25 14:17:52 +03:00
const data: ITextRequest = {
text: inputText
}
textProcessor.generateLexeme(data, response => {
const lexeme: IWordForm[] = [];
response.items.forEach(
form => {
const newForm: IWordForm = {
text: form.text,
grams: parseGrammemes(form.grams).filter(gram => SelectorGrammemesList.find(item => item === gram as Grammeme))
}
if (newForm.grams.length === 2 && !lexeme.some(test => matchWordForm(test, newForm))) {
lexeme.push(newForm);
}
});
setForms(lexeme);
2023-09-25 14:17:52 +03:00
});
2023-09-19 17:55:17 +03:00
}
2023-09-21 14:58:01 +03:00
const columns = useMemo(
() => [
columnHelper.accessor('text', {
id: 'text',
header: 'Текст',
size: 350,
minSize: 350,
2023-09-21 23:09:51 +03:00
maxSize: 350,
cell: props => <div className='min-w-[20rem]'>{props.getValue()}</div>
2023-09-21 14:58:01 +03:00
}),
columnHelper.accessor('grams', {
id: 'grams',
header: 'Граммемы',
size: 250,
minSize: 250,
maxSize: 250,
2023-09-21 23:09:51 +03:00
cell: props =>
2023-09-25 14:17:52 +03:00
<div className='flex flex-wrap justify-start gap-1 select-none'>
2023-09-21 14:58:01 +03:00
{ props.getValue().map(
2023-09-21 23:09:51 +03:00
gram =>
2023-09-21 14:58:01 +03:00
<div
2023-09-27 23:36:51 +03:00
key={`${props.cell.id}-${gram}`}
2023-09-21 23:09:51 +03:00
className='min-w-[3rem] px-1 text-sm text-center rounded-md whitespace-nowrap'
2023-09-21 14:58:01 +03:00
title=''
2023-09-21 23:09:51 +03:00
style={{
borderWidth: '1px',
2023-09-27 23:36:51 +03:00
borderColor: colorfgGrammeme(gram, colors),
color: colorfgGrammeme(gram, colors),
2023-09-21 23:09:51 +03:00
fontWeight: 600,
backgroundColor: colors.bgInput
}}
2023-09-21 14:58:01 +03:00
>
2023-09-21 23:09:51 +03:00
{labelGrammeme(gram)}
2023-09-21 14:58:01 +03:00
</div>
2023-09-21 23:09:51 +03:00
)}
</div>
2023-09-21 14:58:01 +03:00
}),
2023-09-21 23:09:51 +03:00
columnHelper.display({
id: 'actions',
size: 50,
minSize: 50,
maxSize: 50,
cell: props =>
<div>
2023-09-30 12:47:27 +03:00
<MiniButton
tooltip='Удалить словоформу'
icon={<CrossIcon size={4} color='text-warning'/>}
noHover
onClick={() => handleDeleteRow(props.row.index)}
/>
2023-09-21 23:09:51 +03:00
</div>
})
], [colors]);
2023-09-11 20:31:54 +03:00
return (
2023-09-19 17:55:17 +03:00
<Modal
title='Редактирование словоформ'
hideWindow={hideWindow}
submitText='Сохранить данные'
canSubmit
onSubmit={handleSubmit}
>
2023-09-29 16:22:49 +03:00
<div className='relative w-full'>
<div className='absolute top-0 right-0'>
<div id='terminology-help' className='px-1 py-1'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip
anchorSelect='#terminology-help'
className='max-w-[30rem] z-modal-tooltip'
offset={4}
>
<HelpTerminologyControl />
</ConceptTooltip>
</div>
</div>
2023-09-30 12:47:27 +03:00
<div className='min-w-[40rem] max-w-[40rem]'>
2023-09-19 17:55:17 +03:00
<TextArea id='nominal' label='Начальная форма'
placeholder='Начальная форма'
rows={1}
value={term}
disabled={true}
spellCheck
/>
2023-10-04 18:46:52 +03:00
<div className='mt-4 mb-2 text-sm font-semibold'>
2023-09-30 12:47:27 +03:00
Параметры словоформы
</div>
2023-09-19 17:55:17 +03:00
2023-09-30 12:47:27 +03:00
<div className='flex items-start justify-between w-full'>
<TextArea
placeholder='Введите текст'
rows={2}
dimensions='min-w-[18rem] w-full min-h-[4.2rem]'
2023-09-25 14:17:52 +03:00
2023-09-30 12:47:27 +03:00
value={inputText}
onChange={event => setInputText(event.target.value)}
/>
<div className='max-w-min'>
<MiniButton
tooltip='Генерировать словоформу'
icon={<ArrowLeftIcon
size={6}
color={inputGrams.length == 0 ? 'text-disabled' : 'text-primary'}
/>}
disabled={textProcessor.loading || inputGrams.length == 0}
onClick={handleInflect}
/>
<MiniButton
tooltip='Определить граммемы'
icon={<ArrowRightIcon
size={6}
color={!inputText ? 'text-disabled' : 'text-primary'}
/>}
disabled={textProcessor.loading || !inputText}
onClick={handleParse}
2023-09-14 16:53:38 +03:00
/>
</div>
2023-09-19 17:55:17 +03:00
<SelectMulti
className='z-modal-top min-w-[20rem] max-w-[20rem] h-full flex-grow'
2023-09-21 14:58:01 +03:00
options={options}
2023-09-19 17:55:17 +03:00
placeholder='Выберите граммемы'
2023-09-21 14:58:01 +03:00
value={inputGrams}
2023-09-27 23:36:51 +03:00
onChange={newValue => setInputGrams([...newValue].sort(compareGrammemeOptions))}
2023-09-19 17:55:17 +03:00
/>
</div>
2023-09-30 12:47:27 +03:00
2023-10-04 18:46:52 +03:00
<div className='flex justify-between flex-start'>
2023-09-30 12:47:27 +03:00
<div className='flex items-center justify-start'>
<MiniButton
tooltip='Внести словоформу'
icon={<CheckIcon
size={6}
color={!inputText || inputGrams.length == 0 ? 'text-disabled' : 'text-success'}
/>}
disabled={textProcessor.loading || !inputText || inputGrams.length == 0}
onClick={handleAddForm}
/>
<MiniButton
tooltip='Генерировать все словоформы'
icon={<ChevronDoubleDownIcon
size={6}
color={!inputText ? 'text-disabled' : 'text-primary'}
/>}
disabled={textProcessor.loading || !inputText}
onClick={handleGenerateLexeme}
/>
</div>
2023-10-04 18:46:52 +03:00
<div className='w-full mt-2 mb-1 text-sm font-semibold text-center'>
2023-09-30 12:47:27 +03:00
Заданные вручную словоформы: [{forms.length}]
</div>
<MiniButton
tooltip='Сбросить ВСЕ словоформы'
icon={<CrossIcon
size={6}
color={forms.length === 0 ? 'text-disabled' : 'text-warning'}
/>}
disabled={textProcessor.loading || forms.length === 0}
onClick={handleResetAll}
/>
</div>
2023-09-21 14:58:01 +03:00
2023-09-30 12:47:27 +03:00
<div className='border overflow-y-auto max-h-[17.4rem] min-h-[17.4rem] mb-2'>
2023-09-21 14:58:01 +03:00
<DataTable
data={forms}
columns={columns}
2023-09-30 12:47:27 +03:00
headPosition='0'
2023-09-21 14:58:01 +03:00
dense
noDataComponent={
<span className='flex flex-col justify-center p-2 text-center min-h-[2rem]'>
<p>Список пуст</p>
2023-09-21 23:09:51 +03:00
<p>Добавьте словоформу</p>
2023-09-21 14:58:01 +03:00
</span>
}
2023-09-30 12:47:27 +03:00
onRowClicked={handleRowClicked}
2023-09-21 14:58:01 +03:00
/>
2023-09-14 16:53:38 +03:00
</div>
2023-09-19 17:55:17 +03:00
</div>
</Modal>);
2023-09-11 20:31:54 +03:00
}
2023-09-29 15:33:32 +03:00
export default DlgEditWordForms;