mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor Tabs UI components
This commit is contained in:
parent
b118c64b9b
commit
6999e086d5
|
@ -11,7 +11,7 @@ extends Omit<TabProps, 'title' | 'children'> {
|
||||||
function ConceptTab({ label, tooltip, className, ...otherProps }: ConceptTabProps) {
|
function ConceptTab({ label, tooltip, className, ...otherProps }: ConceptTabProps) {
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
className={`px-2 py-1 h-full flex justify-center text-sm hover:cursor-pointer clr-tab whitespace-nowrap min-w-[6rem] ${className}`}
|
className={`px-2 py-1 h-full min-w-[6rem] flex justify-center text-sm hover:cursor-pointer clr-tab whitespace-nowrap small-caps select-none font-semibold ${className}`}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
|
|
|
@ -9,7 +9,7 @@ extends Omit<React.DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTML
|
||||||
function Label({ text, tooltip, className, ...restProps }: LabelProps) {
|
function Label({ text, tooltip, className, ...restProps }: LabelProps) {
|
||||||
return (
|
return (
|
||||||
<label
|
<label
|
||||||
className={`text-sm font-semibold ${className}`}
|
className={`text-sm font-semibold ${className} whitespace-nowrap`}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
|
|
|
@ -45,7 +45,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
term_forms: []
|
term_forms: []
|
||||||
});
|
});
|
||||||
|
|
||||||
const [ activeTab, setActiveTab ] = useState(TabID.TEMPLATE);
|
const [activeTab, setActiveTab] = useState(TabID.TEMPLATE);
|
||||||
|
|
||||||
const handleSubmit = () => onCreate(constituenta);
|
const handleSubmit = () => onCreate(constituenta);
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
onSelect={setActiveTab}
|
onSelect={setActiveTab}
|
||||||
>
|
>
|
||||||
<div className='flex gap-1 pl-6 mb-3'>
|
<div className='flex gap-1 pl-6 mb-3'>
|
||||||
<TabList className='flex items-start font-semibold text-center border select-none clr-controls small-caps'>
|
<TabList className='flex border'>
|
||||||
<ConceptTab
|
<ConceptTab
|
||||||
label='Шаблон'
|
label='Шаблон'
|
||||||
tooltip='Выбор шаблона выражения'
|
tooltip='Выбор шаблона выражения'
|
||||||
|
@ -147,7 +147,7 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
|
||||||
<ConceptTooltip
|
<ConceptTooltip
|
||||||
anchorSelect='#templates-help'
|
anchorSelect='#templates-help'
|
||||||
className='max-w-[30rem] z-modal-tooltip'
|
className='max-w-[30rem] z-modal-tooltip'
|
||||||
offset={4}
|
offset={10}
|
||||||
>
|
>
|
||||||
<HelpRSTemplates />
|
<HelpRSTemplates />
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
|
|
|
@ -1,22 +1,16 @@
|
||||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||||
|
|
||||||
|
import ConceptTab from '../../components/Common/ConceptTab';
|
||||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||||
import Label from '../../components/Common/Label';
|
|
||||||
import Modal from '../../components/Common/Modal';
|
import Modal from '../../components/Common/Modal';
|
||||||
import SelectMulti from '../../components/Common/SelectMulti';
|
|
||||||
import TextInput from '../../components/Common/TextInput';
|
|
||||||
import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl';
|
import HelpTerminologyControl from '../../components/Help/HelpTerminologyControl';
|
||||||
import { HelpIcon } from '../../components/Icons';
|
import { HelpIcon } from '../../components/Icons';
|
||||||
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
import { ReferenceType } from '../../models/language';
|
||||||
import { Grammeme, ReferenceType } from '../../models/language';
|
|
||||||
import { getCompatibleGrams, parseEntityReference, parseGrammemes, parseSyntacticReference } from '../../models/languageAPI';
|
|
||||||
import { CstMatchMode } from '../../models/miscelanious';
|
|
||||||
import { IConstituenta } from '../../models/rsform';
|
import { IConstituenta } from '../../models/rsform';
|
||||||
import { matchConstituenta } from '../../models/rsformAPI';
|
import { labelReferenceType } from '../../utils/labels';
|
||||||
import { prefixes } from '../../utils/constants';
|
import EntityTab from './EntityTab';
|
||||||
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
|
import SyntacticTab from './SyntacticTab';
|
||||||
import ReferenceTypeButton from './ReferenceTypeButton';
|
|
||||||
import WordformButton from './WordformButton';
|
|
||||||
|
|
||||||
export interface IReferenceInputState {
|
export interface IReferenceInputState {
|
||||||
type: ReferenceType
|
type: ReferenceType
|
||||||
|
@ -33,127 +27,18 @@ interface DlgEditReferenceProps {
|
||||||
onSave: (newRef: string) => void
|
onSave: (newRef: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum TabID {
|
||||||
|
ENTITY = 0,
|
||||||
|
SYNTACTIC = 1
|
||||||
|
}
|
||||||
|
|
||||||
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
||||||
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC);
|
||||||
|
|
||||||
const [nominal, setNominal] = useState('');
|
const [reference, setReference] = useState('');
|
||||||
const [offset, setOffset] = useState(1);
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
|
||||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
const handleSubmit = () => onSave(reference);
|
||||||
const [alias, setAlias] = useState('');
|
|
||||||
const [term, setTerm] = useState('');
|
|
||||||
|
|
||||||
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
|
||||||
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
|
|
||||||
|
|
||||||
const mainLink = useMemo(
|
|
||||||
() => {
|
|
||||||
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
|
|
||||||
if (offset === 0 || position < 0 || position >= initial.mainRefs.length) {
|
|
||||||
return 'Некорректное значение смещения';
|
|
||||||
} else {
|
|
||||||
return initial.mainRefs[position];
|
|
||||||
}
|
|
||||||
}, [initial, offset]);
|
|
||||||
|
|
||||||
const isValid = useMemo(
|
|
||||||
() => {
|
|
||||||
if (type === ReferenceType.ENTITY) {
|
|
||||||
return alias !== '' && selectedGrams.length > 0;
|
|
||||||
} else if (type === ReferenceType.SYNTACTIC) {
|
|
||||||
return nominal !== '' && offset !== 0;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}, [type, alias, selectedGrams, nominal, offset]);
|
|
||||||
|
|
||||||
function produceReference(): string {
|
|
||||||
if (type === ReferenceType.ENTITY) {
|
|
||||||
return `@{${alias}|${selectedGrams.map(gram => gram.value).join(',')}}`;
|
|
||||||
} else if (type === ReferenceType.SYNTACTIC) {
|
|
||||||
return `@{${offset}|${nominal}}`;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialization
|
|
||||||
useLayoutEffect(
|
|
||||||
() => {
|
|
||||||
setType(initial.type);
|
|
||||||
if (initial.refRaw) {
|
|
||||||
if (initial.type === ReferenceType.ENTITY) {
|
|
||||||
const ref = parseEntityReference(initial.refRaw);
|
|
||||||
setAlias(ref.entity);
|
|
||||||
const grams = parseGrammemes(ref.form);
|
|
||||||
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
|
||||||
} else if (initial.type === ReferenceType.SYNTACTIC) {
|
|
||||||
const ref = parseSyntacticReference(initial.refRaw);
|
|
||||||
setOffset(ref.offset);
|
|
||||||
setNominal(ref.nominal);
|
|
||||||
}
|
|
||||||
} else if (initial.text) {
|
|
||||||
setNominal(initial.text ?? '');
|
|
||||||
}
|
|
||||||
}, [initial, items]);
|
|
||||||
|
|
||||||
// Filter grammemes when input changes
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
const compatible = getCompatibleGrams(
|
|
||||||
selectedGrams
|
|
||||||
.filter(data => Object.values(Grammeme).includes(data.value as Grammeme))
|
|
||||||
.map(data => data.value as Grammeme)
|
|
||||||
);
|
|
||||||
setGramOptions(SelectorGrammems.filter(({value}) => compatible.includes(value as Grammeme)));
|
|
||||||
}, [selectedGrams]);
|
|
||||||
|
|
||||||
// Update term when alias changes
|
|
||||||
useEffect(
|
|
||||||
() => {
|
|
||||||
const cst = items.find(item => item.alias === alias)
|
|
||||||
setTerm(cst?.term_resolved ?? '')
|
|
||||||
}, [alias, term, items]);
|
|
||||||
|
|
||||||
const handleSubmit = () => onSave(produceReference());
|
|
||||||
|
|
||||||
function handleSelectConstituenta(cst: IConstituenta) {
|
|
||||||
setAlias(cst.alias);
|
|
||||||
setSelectedCst(cst);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSelectGrams = useCallback(
|
|
||||||
(grams: Grammeme[]) => {
|
|
||||||
setSelectedGrams(SelectorGrammems.filter(({value}) => grams.includes(value as Grammeme)));
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const FormButtons = useMemo(() => {
|
|
||||||
return (
|
|
||||||
<div className='flex flex-col items-center w-full text-sm'>
|
|
||||||
<div className='flex flex-start'>
|
|
||||||
{PremadeWordForms.slice(0, 6).map(
|
|
||||||
(data, index) =>
|
|
||||||
<WordformButton key={`${prefixes.wordform_list}${index}`}
|
|
||||||
text={data.text} example={data.example} grams={data.grams}
|
|
||||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
|
||||||
onSelectGrams={handleSelectGrams}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='flex flex-start'>
|
|
||||||
{PremadeWordForms.slice(6, 12).map(
|
|
||||||
(data, index) =>
|
|
||||||
<WordformButton key={`${prefixes.wordform_list}${index}`}
|
|
||||||
text={data.text} example={data.example} grams={data.grams}
|
|
||||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
|
||||||
onSelectGrams={handleSelectGrams}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>);
|
|
||||||
}, [handleSelectGrams, selectedGrams]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -164,99 +49,57 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
<div className='min-w-[40rem] max-w-[40rem] flex flex-col gap-3 mb-2 min-h-[34rem]'>
|
<div className='min-w-[40rem] max-w-[40rem] flex flex-col gap-3 mb-2 min-h-[34rem]'>
|
||||||
<div className='flex items-center self-center flex-start'>
|
<Tabs defaultFocus
|
||||||
<ReferenceTypeButton
|
className='flex flex-col items-center'
|
||||||
type={ReferenceType.ENTITY}
|
selectedTabClassName='clr-selected'
|
||||||
onSelect={setType}
|
selectedIndex={activeTab}
|
||||||
isSelected={type === ReferenceType.ENTITY}
|
onSelect={setActiveTab}
|
||||||
/>
|
>
|
||||||
<ReferenceTypeButton
|
<div className='flex gap-1 pl-6 mb-3'>
|
||||||
type={ReferenceType.SYNTACTIC}
|
<TabList className='flex border'>
|
||||||
onSelect={setType}
|
<ConceptTab
|
||||||
isSelected={type === ReferenceType.SYNTACTIC}
|
label={labelReferenceType(ReferenceType.ENTITY)}
|
||||||
/>
|
tooltip='Отсылка на термин в заданной словоформе'
|
||||||
|
className='w-[12rem] border-r-2'
|
||||||
|
/>
|
||||||
|
<ConceptTab
|
||||||
|
label={labelReferenceType(ReferenceType.SYNTACTIC)}
|
||||||
|
tooltip='Установление синтаксической связи с отсылкой на термин'
|
||||||
|
className='w-[12rem]'
|
||||||
|
/>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
<div id='terminology-help' className='px-1 py-1'>
|
<div id='terminology-help' className='px-1 py-1'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
</div>
|
</div>
|
||||||
<ConceptTooltip
|
<ConceptTooltip
|
||||||
anchorSelect='#terminology-help'
|
anchorSelect='#terminology-help'
|
||||||
className='max-w-[30rem] z-modal-tooltip'
|
className='max-w-[30rem] z-modal-tooltip'
|
||||||
offset={4}
|
offset={10}
|
||||||
>
|
>
|
||||||
<HelpTerminologyControl />
|
<HelpTerminologyControl />
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
</div>
|
</div>
|
||||||
{type === ReferenceType.SYNTACTIC ?
|
|
||||||
<div className='flex flex-col gap-2'>
|
<div className='w-full'>
|
||||||
<div className='flex flex-start'>
|
<TabPanel>
|
||||||
<TextInput type='number' dense
|
<EntityTab
|
||||||
label='Смещение'
|
initial={initial}
|
||||||
dimensions='max-w-[10rem]'
|
items={items}
|
||||||
value={offset}
|
setReference={setReference}
|
||||||
onChange={event => setOffset(event.target.valueAsNumber)}
|
setIsValid={setIsValid}
|
||||||
/>
|
|
||||||
<div className='self-center ml-2 text-sm font-semibold whitespace-nowrap'>
|
|
||||||
Основная ссылка:
|
|
||||||
</div>
|
|
||||||
<TextInput disabled dense noBorder
|
|
||||||
value={mainLink}
|
|
||||||
dimensions='w-full text-sm'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<TextInput spellCheck
|
|
||||||
label='Начальная форма'
|
|
||||||
placeholder='зависимое слово в начальной форме'
|
|
||||||
value={nominal}
|
|
||||||
onChange={event => setNominal(event.target.value)}
|
|
||||||
/>
|
/>
|
||||||
</div> : null}
|
</TabPanel>
|
||||||
{type === ReferenceType.ENTITY ?
|
|
||||||
<div className='flex flex-col gap-3'>
|
<TabPanel>
|
||||||
<ConstituentaPicker
|
<SyntacticTab
|
||||||
value={selectedCst}
|
initial={initial}
|
||||||
data={items}
|
setReference={setReference}
|
||||||
onSelectValue={handleSelectConstituenta}
|
setIsValid={setIsValid}
|
||||||
prefixID={prefixes.cst_modal_list}
|
|
||||||
describeFunc={cst => cst.term_resolved}
|
|
||||||
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
|
|
||||||
prefilterFunc={cst => cst.term_resolved !== ''}
|
|
||||||
rows={8}
|
|
||||||
/>
|
/>
|
||||||
|
</TabPanel>
|
||||||
<div className='flex gap-4 flex-start'>
|
</div>
|
||||||
<TextInput dense
|
</Tabs>
|
||||||
label='Отсылаемая конституента'
|
|
||||||
placeholder='Имя'
|
|
||||||
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
|
|
||||||
value={alias}
|
|
||||||
onChange={event => setAlias(event.target.value)}
|
|
||||||
/>
|
|
||||||
<div className='flex items-center w-full flex-start'>
|
|
||||||
<div className='self-center text-sm font-semibold'>
|
|
||||||
Термин:
|
|
||||||
</div>
|
|
||||||
<TextInput disabled dense noBorder
|
|
||||||
value={term}
|
|
||||||
tooltip={term}
|
|
||||||
dimensions='w-full text-sm'
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{FormButtons}
|
|
||||||
|
|
||||||
<div className='flex items-center gap-4 flex-start'>
|
|
||||||
<Label text='Отсылаемая словоформа'/>
|
|
||||||
<SelectMulti
|
|
||||||
placeholder='Выберите граммемы'
|
|
||||||
className='flex-grow h-full'
|
|
||||||
menuPlacement='top'
|
|
||||||
options={gramOptions}
|
|
||||||
value={selectedGrams}
|
|
||||||
onChange={newValue => setSelectedGrams([...newValue].sort(compareGrammemeOptions))}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div> : null}
|
|
||||||
</div>
|
</div>
|
||||||
</Modal>);
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
123
rsconcept/frontend/src/dialogs/DlgEditReference/EntityTab.tsx
Normal file
123
rsconcept/frontend/src/dialogs/DlgEditReference/EntityTab.tsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||||
|
|
||||||
|
import Label from '../../components/Common/Label';
|
||||||
|
import SelectMulti from '../../components/Common/SelectMulti';
|
||||||
|
import TextInput from '../../components/Common/TextInput';
|
||||||
|
import ConstituentaPicker from '../../components/Shared/ConstituentaPicker';
|
||||||
|
import { Grammeme, ReferenceType } from '../../models/language';
|
||||||
|
import { getCompatibleGrams, parseEntityReference, parseGrammemes } from '../../models/languageAPI';
|
||||||
|
import { CstMatchMode } from '../../models/miscelanious';
|
||||||
|
import { IConstituenta } from '../../models/rsform';
|
||||||
|
import { matchConstituenta } from '../../models/rsformAPI';
|
||||||
|
import { prefixes } from '../../utils/constants';
|
||||||
|
import { compareGrammemeOptions,IGrammemeOption, SelectorGrammems } from '../../utils/selectors';
|
||||||
|
import { IReferenceInputState } from './DlgEditReference';
|
||||||
|
import SelectTermform from './SelectTermform';
|
||||||
|
|
||||||
|
interface EntityTabProps {
|
||||||
|
initial: IReferenceInputState
|
||||||
|
items: IConstituenta[]
|
||||||
|
setIsValid: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
setReference: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
}
|
||||||
|
|
||||||
|
function EntityTab({ initial, items, setIsValid, setReference }: EntityTabProps) {
|
||||||
|
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||||
|
const [alias, setAlias] = useState('');
|
||||||
|
const [term, setTerm] = useState('');
|
||||||
|
|
||||||
|
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||||
|
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
|
||||||
|
|
||||||
|
// Initialization
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
if (!!initial.refRaw && initial.type === ReferenceType.ENTITY) {
|
||||||
|
const ref = parseEntityReference(initial.refRaw);
|
||||||
|
setAlias(ref.entity);
|
||||||
|
const grams = parseGrammemes(ref.form);
|
||||||
|
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
||||||
|
}
|
||||||
|
}, [initial, items]);
|
||||||
|
|
||||||
|
// Produce result
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
setIsValid(alias !== '' && selectedGrams.length > 0);
|
||||||
|
setReference(`@{${alias}|${selectedGrams.map(gram => gram.value).join(',')}}`);
|
||||||
|
}, [alias, selectedGrams, setIsValid, setReference]);
|
||||||
|
|
||||||
|
// Filter grammemes when input changes
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const compatible = getCompatibleGrams(
|
||||||
|
selectedGrams
|
||||||
|
.filter(data => Object.values(Grammeme).includes(data.value as Grammeme))
|
||||||
|
.map(data => data.value as Grammeme)
|
||||||
|
);
|
||||||
|
setGramOptions(SelectorGrammems.filter(({value}) => compatible.includes(value as Grammeme)));
|
||||||
|
}, [selectedGrams]);
|
||||||
|
|
||||||
|
// Update term when alias changes
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
const cst = items.find(item => item.alias === alias)
|
||||||
|
setTerm(cst?.term_resolved ?? '')
|
||||||
|
}, [alias, term, items]);
|
||||||
|
|
||||||
|
function handleSelectConstituenta(cst: IConstituenta) {
|
||||||
|
setAlias(cst.alias);
|
||||||
|
setSelectedCst(cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-3'>
|
||||||
|
<ConstituentaPicker
|
||||||
|
value={selectedCst}
|
||||||
|
data={items}
|
||||||
|
onSelectValue={handleSelectConstituenta}
|
||||||
|
prefixID={prefixes.cst_modal_list}
|
||||||
|
describeFunc={cst => cst.term_resolved}
|
||||||
|
matchFunc={(cst, filter) => matchConstituenta(cst, filter, CstMatchMode.TERM)}
|
||||||
|
prefilterFunc={cst => cst.term_resolved !== ''}
|
||||||
|
rows={8}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='flex gap-4 flex-start'>
|
||||||
|
<TextInput dense
|
||||||
|
label='Отсылаемая конституента'
|
||||||
|
placeholder='Имя'
|
||||||
|
dimensions='max-w-[16rem] min-w-[16rem] whitespace-nowrap'
|
||||||
|
value={alias}
|
||||||
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
/>
|
||||||
|
<div className='flex items-center w-full flex-start'>
|
||||||
|
<Label text='Термин' />
|
||||||
|
<TextInput disabled dense noBorder
|
||||||
|
value={term}
|
||||||
|
tooltip={term}
|
||||||
|
dimensions='w-full text-sm'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SelectTermform
|
||||||
|
selected={selectedGrams}
|
||||||
|
setSelected={setSelectedGrams}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-4 flex-start'>
|
||||||
|
<Label text='Отсылаемая словоформа'/>
|
||||||
|
<SelectMulti
|
||||||
|
placeholder='Выберите граммемы'
|
||||||
|
className='flex-grow h-full'
|
||||||
|
menuPlacement='top'
|
||||||
|
options={gramOptions}
|
||||||
|
value={selectedGrams}
|
||||||
|
onChange={newValue => setSelectedGrams([...newValue].sort(compareGrammemeOptions))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EntityTab;
|
|
@ -1,23 +0,0 @@
|
||||||
import SwitchButton from '../../components/Common/SwitchButton';
|
|
||||||
import { ReferenceType } from '../../models/language';
|
|
||||||
import { labelReferenceType } from '../../utils/labels';
|
|
||||||
|
|
||||||
interface ReferenceTypeButtonProps {
|
|
||||||
type: ReferenceType
|
|
||||||
isSelected?: boolean
|
|
||||||
onSelect: (type: ReferenceType) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
function ReferenceTypeButton({ type, isSelected, onSelect }: ReferenceTypeButtonProps) {
|
|
||||||
return (
|
|
||||||
<SwitchButton
|
|
||||||
value={type}
|
|
||||||
isSelected={isSelected}
|
|
||||||
onSelect={onSelect}
|
|
||||||
dimensions='min-w-[12rem] h-fit'
|
|
||||||
label={labelReferenceType(type)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ReferenceTypeButton;
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { Grammeme } from '../../models/language';
|
||||||
|
import { prefixes } from '../../utils/constants';
|
||||||
|
import { IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
|
||||||
|
import WordformButton from './WordformButton';
|
||||||
|
|
||||||
|
interface SelectTermformProps {
|
||||||
|
selected: IGrammemeOption[]
|
||||||
|
setSelected: React.Dispatch<React.SetStateAction<IGrammemeOption[]>>
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectTermform({ selected, setSelected }: SelectTermformProps) {
|
||||||
|
const handleSelect = useCallback(
|
||||||
|
(grams: Grammeme[]) => {
|
||||||
|
setSelected(SelectorGrammems.filter(({value}) => grams.includes(value as Grammeme)));
|
||||||
|
}, [setSelected]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col items-center w-full text-sm'>
|
||||||
|
<div className='flex flex-start'>
|
||||||
|
{PremadeWordForms.slice(0, 6).map(
|
||||||
|
(data, index) =>
|
||||||
|
<WordformButton key={`${prefixes.wordform_list}${index}`}
|
||||||
|
text={data.text} example={data.example} grams={data.grams}
|
||||||
|
isSelected={data.grams.every(gram => selected.find(item => item.value as Grammeme === gram))}
|
||||||
|
onSelectGrams={handleSelect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-start'>
|
||||||
|
{PremadeWordForms.slice(6, 12).map(
|
||||||
|
(data, index) =>
|
||||||
|
<WordformButton key={`${prefixes.wordform_list}${index}`}
|
||||||
|
text={data.text} example={data.example} grams={data.grams}
|
||||||
|
isSelected={data.grams.every(gram => selected.find(item => item.value as Grammeme === gram))}
|
||||||
|
onSelectGrams={handleSelect}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectTermform;
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import TextInput from '../../components/Common/TextInput';
|
||||||
|
import { ReferenceType } from '../../models/language';
|
||||||
|
import { parseSyntacticReference } from '../../models/languageAPI';
|
||||||
|
import { IReferenceInputState } from './DlgEditReference';
|
||||||
|
|
||||||
|
interface SyntacticTabProps {
|
||||||
|
initial: IReferenceInputState
|
||||||
|
setIsValid: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
|
setReference: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
}
|
||||||
|
|
||||||
|
function SyntacticTab({ initial, setIsValid, setReference }: SyntacticTabProps) {
|
||||||
|
const [nominal, setNominal] = useState('');
|
||||||
|
const [offset, setOffset] = useState(1);
|
||||||
|
|
||||||
|
const mainLink = useMemo(
|
||||||
|
() => {
|
||||||
|
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
|
||||||
|
if (offset === 0 || position < 0 || position >= initial.mainRefs.length) {
|
||||||
|
return 'Некорректное значение смещения';
|
||||||
|
} else {
|
||||||
|
return initial.mainRefs[position];
|
||||||
|
}
|
||||||
|
}, [initial, offset]);
|
||||||
|
|
||||||
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
|
if (initial.refRaw && initial.type === ReferenceType.SYNTACTIC) {
|
||||||
|
const ref = parseSyntacticReference(initial.refRaw);
|
||||||
|
setOffset(ref.offset);
|
||||||
|
setNominal(ref.nominal);
|
||||||
|
} else {
|
||||||
|
setNominal(initial.text ?? '');
|
||||||
|
}
|
||||||
|
}, [initial]);
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
setIsValid(nominal !== '' && offset !== 0);
|
||||||
|
setReference(`@{${offset}|${nominal}}`);
|
||||||
|
}, [nominal, offset, setIsValid, setReference]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<TextInput type='number' dense
|
||||||
|
label='Смещение'
|
||||||
|
dimensions='max-w-[10rem]'
|
||||||
|
value={offset}
|
||||||
|
onChange={event => setOffset(event.target.valueAsNumber)}
|
||||||
|
/>
|
||||||
|
<TextInput disabled dense noBorder
|
||||||
|
label='Основная ссылка'
|
||||||
|
value={mainLink}
|
||||||
|
/>
|
||||||
|
<TextInput spellCheck
|
||||||
|
label='Начальная форма'
|
||||||
|
placeholder='зависимое слово в начальной форме'
|
||||||
|
value={nominal}
|
||||||
|
onChange={event => setNominal(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SyntacticTab;
|
|
@ -193,6 +193,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(.clr-controls,
|
:is(.clr-controls,
|
||||||
|
.clr-tab,
|
||||||
.clr-btn-default
|
.clr-btn-default
|
||||||
) {
|
) {
|
||||||
background-color: var(--cl-bg-80);
|
background-color: var(--cl-bg-80);
|
||||||
|
|
|
@ -388,7 +388,7 @@ function RSTabs() {
|
||||||
className='flex flex-col w-full'
|
className='flex flex-col w-full'
|
||||||
>
|
>
|
||||||
<div className='flex justify-center w-[100vw]'>
|
<div className='flex justify-center w-[100vw]'>
|
||||||
<TabList className='flex items-start border-b-2 border-x-2 select-none justify-stretch w-fit clr-controls h-[1.9rem] small-caps font-semibold'>
|
<TabList className='flex border-b-2 border-x-2 justify-stretch w-fit h-[1.9rem]'>
|
||||||
<RSTabsMenu
|
<RSTabsMenu
|
||||||
onDownload={onDownloadSchema}
|
onDownload={onDownloadSchema}
|
||||||
onDestroy={onDestroySchema}
|
onDestroy={onDestroySchema}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user