mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
Implement reference dialog
This commit is contained in:
parent
4eef460be1
commit
edff9b640a
|
@ -8,24 +8,27 @@ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'>
|
||||||
dimensions?: string
|
dimensions?: string
|
||||||
colorClass?: string
|
colorClass?: string
|
||||||
singleRow?: boolean
|
singleRow?: boolean
|
||||||
|
noBorder?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
function TextInput({
|
function TextInput({
|
||||||
id, required, label, singleRow, tooltip,
|
id, required, label, singleRow, tooltip, noBorder,
|
||||||
dimensions = 'w-full',
|
dimensions = 'w-full',
|
||||||
colorClass = 'clr-input',
|
colorClass = 'clr-input',
|
||||||
...props
|
...props
|
||||||
}: TextInputProps) {
|
}: TextInputProps) {
|
||||||
|
const borderClass = noBorder ? '': 'border';
|
||||||
return (
|
return (
|
||||||
<div className={`flex ${singleRow ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
|
<div className={`flex ${singleRow ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
|
||||||
{label && <Label
|
{label &&
|
||||||
|
<Label
|
||||||
text={label}
|
text={label}
|
||||||
required={!props.disabled && required}
|
required={!props.disabled && required}
|
||||||
htmlFor={id}
|
htmlFor={id}
|
||||||
/>}
|
/>}
|
||||||
<input id={id}
|
<input id={id}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? '' : dimensions}`}
|
className={`px-3 py-2 leading-tight ${borderClass} shadow truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? 'w-full' : dimensions}`}
|
||||||
required={required}
|
required={required}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -26,7 +26,7 @@ const editorSetup: BasicSetupOptions = {
|
||||||
lineNumbers: false,
|
lineNumbers: false,
|
||||||
highlightActiveLineGutter: false,
|
highlightActiveLineGutter: false,
|
||||||
foldGutter: false,
|
foldGutter: false,
|
||||||
dropCursor: false,
|
dropCursor: true,
|
||||||
allowMultipleSelections: false,
|
allowMultipleSelections: false,
|
||||||
indentOnInput: false,
|
indentOnInput: false,
|
||||||
bracketMatching: false,
|
bracketMatching: false,
|
||||||
|
|
304
rsconcept/frontend/src/components/RefsInput/DlgEditReference.tsx
Normal file
304
rsconcept/frontend/src/components/RefsInput/DlgEditReference.tsx
Normal file
|
@ -0,0 +1,304 @@
|
||||||
|
import { createColumnHelper } from '@tanstack/react-table';
|
||||||
|
import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import { getCompatibleGrams, Grammeme, parseEntityReference,parseGrammemes,parseSyntacticReference,ReferenceType } from '../../models/language';
|
||||||
|
import { CstMatchMode } from '../../models/miscelanious';
|
||||||
|
import { IConstituenta, matchConstituenta } from '../../models/rsform';
|
||||||
|
import ConstituentaTooltip from '../../pages/RSFormPage/elements/ConstituentaTooltip';
|
||||||
|
import { colorfgCstStatus } from '../../utils/color';
|
||||||
|
import { prefixes } from '../../utils/constants';
|
||||||
|
import { labelReferenceType } from '../../utils/labels';
|
||||||
|
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems, SelectorReferenceType } from '../../utils/selectors';
|
||||||
|
import Label from '../Common/Label';
|
||||||
|
import Modal from '../Common/Modal';
|
||||||
|
import SelectMulti from '../Common/SelectMulti';
|
||||||
|
import SelectSingle from '../Common/SelectSingle';
|
||||||
|
import TextInput from '../Common/TextInput';
|
||||||
|
import DataTable, { IConditionalStyle } from '../DataTable';
|
||||||
|
import TermformButton from './TermformButton';
|
||||||
|
|
||||||
|
interface DlgEditReferenceProps {
|
||||||
|
hideWindow: () => void
|
||||||
|
items: IConstituenta[]
|
||||||
|
|
||||||
|
initialType: ReferenceType
|
||||||
|
initialRef?: string
|
||||||
|
initialText?: string
|
||||||
|
|
||||||
|
onSave: (newRef: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const constituentaHelper = createColumnHelper<IConstituenta>();
|
||||||
|
|
||||||
|
function DlgEditReference({ hideWindow, items, initialRef, initialText, initialType, onSave }: DlgEditReferenceProps) {
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
|
|
||||||
|
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||||
|
|
||||||
|
const [nominal, setNominal] = useState('');
|
||||||
|
const [offset, setOffset] = useState(1);
|
||||||
|
|
||||||
|
const [alias, setAlias] = useState('');
|
||||||
|
const [term, setTerm] = useState('');
|
||||||
|
const [filter, setFilter] = useState('');
|
||||||
|
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
|
||||||
|
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||||
|
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
|
||||||
|
|
||||||
|
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(initialType);
|
||||||
|
if (initialRef) {
|
||||||
|
if (initialType === ReferenceType.ENTITY) {
|
||||||
|
const ref = parseEntityReference(initialRef);
|
||||||
|
setAlias(ref.entity);
|
||||||
|
const grams = parseGrammemes(ref.form);
|
||||||
|
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
||||||
|
} else if (initialType === ReferenceType.SYNTACTIC) {
|
||||||
|
const ref = parseSyntacticReference(initialRef);
|
||||||
|
setOffset(ref.offset);
|
||||||
|
setNominal(ref.nominal);
|
||||||
|
}
|
||||||
|
} else if (initialText) {
|
||||||
|
setNominal(initialText ?? '');
|
||||||
|
setFilter(initialText);
|
||||||
|
}
|
||||||
|
}, [initialRef, initialText, initialType, items]);
|
||||||
|
|
||||||
|
// Filter constituents
|
||||||
|
useEffect(
|
||||||
|
() => {
|
||||||
|
if (filter === '') {
|
||||||
|
setFilteredData(items.filter(
|
||||||
|
(cst) => cst.term_resolved !== '')
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
setFilteredData(items.filter(
|
||||||
|
(cst) => matchConstituenta(filter, cst, CstMatchMode.TERM))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [filter, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) =>
|
||||||
|
<TermformButton id={`${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) =>
|
||||||
|
<TermformButton id={`${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]);
|
||||||
|
|
||||||
|
|
||||||
|
const columnsConstituenta = useMemo(
|
||||||
|
() => [
|
||||||
|
constituentaHelper.accessor('alias', {
|
||||||
|
id: 'alias',
|
||||||
|
header: 'Имя',
|
||||||
|
size: 65,
|
||||||
|
minSize: 65,
|
||||||
|
cell: props => {
|
||||||
|
const cst = props.row.original;
|
||||||
|
return (<>
|
||||||
|
<div
|
||||||
|
id={`${prefixes.cst_list}${cst.alias}`}
|
||||||
|
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
|
||||||
|
style={{
|
||||||
|
borderWidth: '1px',
|
||||||
|
borderColor: colorfgCstStatus(cst.status, colors),
|
||||||
|
color: colorfgCstStatus(cst.status, colors),
|
||||||
|
fontWeight: 600
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cst.alias}
|
||||||
|
</div>
|
||||||
|
<ConstituentaTooltip data={cst} anchor={`#${prefixes.cst_list}${cst.alias}`} />
|
||||||
|
</>);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
constituentaHelper.accessor('term_resolved', {
|
||||||
|
id: 'term',
|
||||||
|
header: 'Термин',
|
||||||
|
size: 600,
|
||||||
|
minSize: 350,
|
||||||
|
maxSize: 600
|
||||||
|
})
|
||||||
|
], [colors]);
|
||||||
|
|
||||||
|
const conditionalRowStyles = useMemo(
|
||||||
|
(): IConditionalStyle<IConstituenta>[] => [
|
||||||
|
{
|
||||||
|
when: (cst: IConstituenta) => cst.alias === alias,
|
||||||
|
style: {
|
||||||
|
backgroundColor: colors.bgSelected
|
||||||
|
},
|
||||||
|
}
|
||||||
|
], [alias, colors]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title='Редактирование ссылки'
|
||||||
|
hideWindow={hideWindow}
|
||||||
|
submitText='Сохранить ссылку'
|
||||||
|
canSubmit={isValid}
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<div className='min-w-[40rem] flex flex-col gap-4 mb-4 mt-2'>
|
||||||
|
<SelectSingle
|
||||||
|
className='z-modal-top min-w-[20rem] w-fit self-center'
|
||||||
|
options={SelectorReferenceType}
|
||||||
|
isSearchable={false}
|
||||||
|
placeholder='Тип ссылки'
|
||||||
|
value={{ value: type, label: labelReferenceType(type) }}
|
||||||
|
onChange={data => setType(data?.value ?? ReferenceType.ENTITY)}
|
||||||
|
/>
|
||||||
|
{type === ReferenceType.SYNTACTIC &&
|
||||||
|
<div className='flex gap-4 flex-start'>
|
||||||
|
<TextInput id='offset' type='number'
|
||||||
|
label='Смещение'
|
||||||
|
dimensions='max-w-[10rem]'
|
||||||
|
singleRow
|
||||||
|
value={offset}
|
||||||
|
onChange={event => setOffset(event.target.valueAsNumber)}
|
||||||
|
/>
|
||||||
|
<TextInput id='nominal' type='text'
|
||||||
|
dimensions='w-full'
|
||||||
|
label='Начальная форма'
|
||||||
|
placeholder='зависимое слово в начальной форме'
|
||||||
|
spellCheck
|
||||||
|
singleRow
|
||||||
|
value={nominal}
|
||||||
|
onChange={event => setNominal(event.target.value)}
|
||||||
|
/>
|
||||||
|
</div>}
|
||||||
|
{type === ReferenceType.ENTITY &&
|
||||||
|
<div className='flex flex-col gap-2'>
|
||||||
|
<TextInput
|
||||||
|
dimensions='w-full'
|
||||||
|
placeholder='текст фильтра'
|
||||||
|
value={filter}
|
||||||
|
onChange={event => setFilter(event.target.value)}
|
||||||
|
/>
|
||||||
|
<div className='border min-h-[15.5rem] max-h-[15.5rem] text-sm overflow-y-auto'>
|
||||||
|
<DataTable
|
||||||
|
data={filteredData}
|
||||||
|
columns={columnsConstituenta}
|
||||||
|
conditionalRowStyles={conditionalRowStyles}
|
||||||
|
dense
|
||||||
|
|
||||||
|
noDataComponent={
|
||||||
|
<span className='flex flex-col justify-center p-2 text-center min-h-[5rem]'>
|
||||||
|
<p>Список конституент пуст</p>
|
||||||
|
<p>Измените параметры фильтра</p>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
onRowClicked={handleSelectConstituenta}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-4 flex-start'>
|
||||||
|
<TextInput
|
||||||
|
label='Отсылаемый идентификатор'
|
||||||
|
dimensions='max-w-[18rem] min-w-[18rem] whitespace-nowrap'
|
||||||
|
singleRow
|
||||||
|
value={alias}
|
||||||
|
onChange={event => setAlias(event.target.value)}
|
||||||
|
/>
|
||||||
|
<TextInput
|
||||||
|
label='Термин'
|
||||||
|
singleRow
|
||||||
|
disabled
|
||||||
|
noBorder
|
||||||
|
value={term}
|
||||||
|
tooltip={term}
|
||||||
|
dimensions='w-full'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{FormButtons}
|
||||||
|
<div className='flex items-center gap-10 flex-start'>
|
||||||
|
<Label text='Отсылаемая словоформа'/>
|
||||||
|
<SelectMulti
|
||||||
|
className='flex-grow h-full z-modal-top'
|
||||||
|
options={gramOptions}
|
||||||
|
placeholder='Выберите граммемы'
|
||||||
|
|
||||||
|
value={selectedGrams}
|
||||||
|
onChange={newValue => setSelectedGrams([...newValue].sort(compareGrammemeOptions))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>}
|
||||||
|
</div>
|
||||||
|
</Modal>);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DlgEditReference;
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Grammeme } from '../../models/language';
|
||||||
|
|
||||||
|
interface TermformButtonProps {
|
||||||
|
id?: string
|
||||||
|
text: string
|
||||||
|
example: string
|
||||||
|
grams: Grammeme[]
|
||||||
|
isSelected?: boolean
|
||||||
|
onSelectGrams: (grams: Grammeme[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function TermformButton({ text, example, grams, onSelectGrams, isSelected, ...props }: TermformButtonProps) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
onClick={() => onSelectGrams(grams)}
|
||||||
|
tabIndex={-1}
|
||||||
|
className={`min-w-[6rem] p-1 border rounded-none cursor-pointer clr-btn-clear clr-hover ${isSelected ? 'clr-selected': ''}`}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<p className='font-semibold'>{text}</p>
|
||||||
|
<p>{example}</p>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TermformButton;
|
|
@ -9,11 +9,15 @@ import { RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import useResolveText from '../../hooks/useResolveText';
|
import useResolveText from '../../hooks/useResolveText';
|
||||||
|
import { ReferenceType } from '../../models/language';
|
||||||
|
import { IConstituenta } from '../../models/rsform';
|
||||||
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
||||||
import Label from '../Common/Label';
|
import Label from '../Common/Label';
|
||||||
import Modal from '../Common/Modal';
|
import Modal from '../Common/Modal';
|
||||||
import PrettyJson from '../Common/PrettyJSON';
|
import PrettyJson from '../Common/PrettyJSON';
|
||||||
|
import DlgEditReference from './DlgEditReference';
|
||||||
import { NaturalLanguage, ReferenceTokens } from './parse';
|
import { NaturalLanguage, ReferenceTokens } from './parse';
|
||||||
|
import { RefEntity } from './parse/parser.terms';
|
||||||
import { refsHoverTooltip } from './tooltip';
|
import { refsHoverTooltip } from './tooltip';
|
||||||
|
|
||||||
const editorSetup: BasicSetupOptions = {
|
const editorSetup: BasicSetupOptions = {
|
||||||
|
@ -51,6 +55,7 @@ extends Pick<ReactCodeMirrorProps,
|
||||||
label?: string
|
label?: string
|
||||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||||
onChange?: (newValue: string) => void
|
onChange?: (newValue: string) => void
|
||||||
|
items?: IConstituenta[]
|
||||||
|
|
||||||
initialValue?: string
|
initialValue?: string
|
||||||
value?: string
|
value?: string
|
||||||
|
@ -58,7 +63,7 @@ extends Pick<ReactCodeMirrorProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
function RefsInput({
|
function RefsInput({
|
||||||
id, label, innerref, onChange, editable,
|
id, label, innerref, onChange, editable, items,
|
||||||
initialValue, value, resolved,
|
initialValue, value, resolved,
|
||||||
onFocus, onBlur,
|
onFocus, onBlur,
|
||||||
...props
|
...props
|
||||||
|
@ -71,6 +76,11 @@ function RefsInput({
|
||||||
const [showResolve, setShowResolve] = useState(false);
|
const [showResolve, setShowResolve] = useState(false);
|
||||||
const [isFocused, setIsFocused] = useState(false);
|
const [isFocused, setIsFocused] = useState(false);
|
||||||
|
|
||||||
|
const [showEditor, setShowEditor] = useState(false);
|
||||||
|
const [currentType, setCurrentType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||||
|
const [refText, setRefText] = useState('');
|
||||||
|
const [hintText, setHintText] = useState('');
|
||||||
|
|
||||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||||
const thisRef = useMemo(
|
const thisRef = useMemo(
|
||||||
() => {
|
() => {
|
||||||
|
@ -133,11 +143,41 @@ function RefsInput({
|
||||||
if (event.ctrlKey && event.code === 'Space') {
|
if (event.ctrlKey && event.code === 'Space') {
|
||||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||||
wrap.fixSelection(ReferenceTokens);
|
wrap.fixSelection(ReferenceTokens);
|
||||||
|
const nodes = wrap.getEnvelopingNodes(ReferenceTokens);
|
||||||
|
if (nodes.length !== 1) {
|
||||||
|
setCurrentType(ReferenceType.ENTITY);
|
||||||
|
setRefText('');
|
||||||
|
setHintText(wrap.getSelectionText());
|
||||||
|
} else {
|
||||||
|
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
||||||
|
setRefText(wrap.getSelectionText());
|
||||||
|
}
|
||||||
|
setShowEditor(true);
|
||||||
}
|
}
|
||||||
}, [thisRef, resolveText, value]);
|
}, [thisRef, resolveText, value]);
|
||||||
|
|
||||||
|
const handleInputReference = useCallback(
|
||||||
|
(referenceText: string) => {
|
||||||
|
if (!thisRef.current?.view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thisRef.current.view.focus();
|
||||||
|
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||||
|
wrap.replaceWith(referenceText);
|
||||||
|
}, [thisRef]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{ showEditor &&
|
||||||
|
<DlgEditReference
|
||||||
|
hideWindow={() => setShowEditor(false)}
|
||||||
|
items={items ?? []}
|
||||||
|
initialType={currentType}
|
||||||
|
initialRef={refText}
|
||||||
|
initialText={hintText}
|
||||||
|
onSave={handleInputReference}
|
||||||
|
/>
|
||||||
|
}
|
||||||
{ showResolve &&
|
{ showResolve &&
|
||||||
<Modal
|
<Modal
|
||||||
readonly
|
readonly
|
||||||
|
@ -161,7 +201,7 @@ function RefsInput({
|
||||||
theme={customTheme}
|
theme={customTheme}
|
||||||
extensions={editorExtensions}
|
extensions={editorExtensions}
|
||||||
|
|
||||||
value={isFocused ? value : (value !== initialValue ? value : resolved)}
|
value={isFocused ? value : (value !== initialValue || showEditor ? value : resolved)}
|
||||||
|
|
||||||
indentWithTab={false}
|
indentWithTab={false}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|
|
@ -283,6 +283,39 @@ export function parseGrammemes(termForm: string): GramData[] {
|
||||||
return result.sort(compareGrammemes);
|
return result.sort(compareGrammemes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a list of compatible {@link Grammeme}s.
|
||||||
|
*/
|
||||||
|
export function getCompatibleGrams(input: Grammeme[]): Grammeme[] {
|
||||||
|
let result: Grammeme[] = [];
|
||||||
|
input.forEach(
|
||||||
|
(gram) => {
|
||||||
|
if (!result.includes(gram)) {
|
||||||
|
if (NounGrams.includes(gram)) {
|
||||||
|
result.push(...NounGrams);
|
||||||
|
}
|
||||||
|
if (VerbGrams.includes(gram)) {
|
||||||
|
result.push(...VerbGrams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
input.forEach(
|
||||||
|
(gram) => GrammemeGroups.forEach(
|
||||||
|
(group) => {
|
||||||
|
if (group.includes(gram)) {
|
||||||
|
result = result.filter(item => !group.includes(item));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
return [... new Set<Grammeme>([...VerbGrams, ...NounGrams])];
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ====== Reference resolution =====
|
// ====== Reference resolution =====
|
||||||
/**
|
/**
|
||||||
* Represents text request.
|
* Represents text request.
|
||||||
|
@ -324,11 +357,17 @@ export interface ITextPosition {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents single resolved reference data.
|
* Represents abstract reference data.
|
||||||
*/
|
*/
|
||||||
export interface IResolvedReference {
|
export interface IReference {
|
||||||
type: ReferenceType
|
type: ReferenceType
|
||||||
data: IEntityReference | ISyntacticReference
|
data: IEntityReference | ISyntacticReference
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents single resolved reference data.
|
||||||
|
*/
|
||||||
|
export interface IResolvedReference extends IReference {
|
||||||
pos_input: ITextPosition
|
pos_input: ITextPosition
|
||||||
pos_output: ITextPosition
|
pos_output: ITextPosition
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import SelectSingle from '../../components/Common/SelectSingle';
|
||||||
import TextArea from '../../components/Common/TextArea';
|
import TextArea from '../../components/Common/TextArea';
|
||||||
import RSInput from '../../components/RSInput';
|
import RSInput from '../../components/RSInput';
|
||||||
import { CstType,ICstCreateData } from '../../models/rsform';
|
import { CstType,ICstCreateData } from '../../models/rsform';
|
||||||
import { SelectorCstType } from '../../utils/selectors';
|
|
||||||
import { labelCstType } from '../../utils/labels';
|
import { labelCstType } from '../../utils/labels';
|
||||||
|
import { SelectorCstType } from '../../utils/selectors';
|
||||||
|
|
||||||
interface DlgCreateCstProps
|
interface DlgCreateCstProps
|
||||||
extends Pick<ModalProps, 'hideWindow'> {
|
extends Pick<ModalProps, 'hideWindow'> {
|
||||||
|
|
|
@ -10,15 +10,15 @@ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossI
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import useConceptText from '../../hooks/useConceptText';
|
import useConceptText from '../../hooks/useConceptText';
|
||||||
import {
|
import {
|
||||||
GramData, Grammeme, GrammemeGroups, ITextRequest, IWordForm,
|
getCompatibleGrams, Grammeme, ITextRequest, IWordForm,
|
||||||
IWordFormPlain, matchWordForm, NounGrams, parseGrammemes, VerbGrams
|
IWordFormPlain, matchWordForm, parseGrammemes
|
||||||
} from '../../models/language';
|
} from '../../models/language';
|
||||||
import { IConstituenta, TermForm } from '../../models/rsform';
|
import { IConstituenta, TermForm } from '../../models/rsform';
|
||||||
import { colorfgGrammeme } from '../../utils/color';
|
import { colorfgGrammeme } from '../../utils/color';
|
||||||
import { labelGrammeme } from '../../utils/labels';
|
import { labelGrammeme } from '../../utils/labels';
|
||||||
import { compareGrammemeOptions,IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors';
|
import { compareGrammemeOptions,IGrammemeOption, SelectorGrammemesList, SelectorGrammems } from '../../utils/selectors';
|
||||||
|
|
||||||
interface DlgEditTermProps {
|
interface DlgEditWordFormsProps {
|
||||||
hideWindow: () => void
|
hideWindow: () => void
|
||||||
target: IConstituenta
|
target: IConstituenta
|
||||||
onSave: (data: TermForm[]) => void
|
onSave: (data: TermForm[]) => void
|
||||||
|
@ -26,7 +26,7 @@ interface DlgEditTermProps {
|
||||||
|
|
||||||
const columnHelper = createColumnHelper<IWordForm>();
|
const columnHelper = createColumnHelper<IWordForm>();
|
||||||
|
|
||||||
function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps) {
|
||||||
const textProcessor = useConceptText();
|
const textProcessor = useConceptText();
|
||||||
const { colors } = useConceptTheme();
|
const { colors } = useConceptTheme();
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
|
@ -65,32 +65,12 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
// Filter grammemes when input changes
|
// Filter grammemes when input changes
|
||||||
useEffect(
|
useEffect(
|
||||||
() => {
|
() => {
|
||||||
let newFilter: GramData[] = [];
|
const compatible = getCompatibleGrams(
|
||||||
inputGrams.forEach(({value: gram}) => {
|
inputGrams
|
||||||
if (!newFilter.includes(gram)) {
|
.filter(data => Object.values(Grammeme).includes(data.value as Grammeme))
|
||||||
if (NounGrams.includes(gram as Grammeme)) {
|
.map(data => data.value as Grammeme)
|
||||||
newFilter.push(...NounGrams);
|
);
|
||||||
}
|
setOptions(SelectorGrammems.filter(({value}) => compatible.includes(value as Grammeme)));
|
||||||
if (VerbGrams.includes(gram as Grammeme)) {
|
|
||||||
newFilter.push(...VerbGrams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
inputGrams.forEach(({value: gram}) =>
|
|
||||||
GrammemeGroups.forEach(group => {
|
|
||||||
if (group.includes(gram as Grammeme)) {
|
|
||||||
newFilter = newFilter.filter(item => !group.includes(item as Grammeme) || item === gram);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
newFilter.push(...inputGrams.map(({value}) => value));
|
|
||||||
if (newFilter.length === 0) {
|
|
||||||
newFilter = [...VerbGrams, ...NounGrams];
|
|
||||||
}
|
|
||||||
|
|
||||||
newFilter = [... new Set(newFilter)];
|
|
||||||
setOptions(SelectorGrammems.filter(({value}) => newFilter.includes(value)));
|
|
||||||
}, [inputGrams]);
|
}, [inputGrams]);
|
||||||
|
|
||||||
const handleSubmit = () => onSave(getData());
|
const handleSubmit = () => onSave(getData());
|
||||||
|
@ -329,4 +309,4 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||||
</Modal>);
|
</Modal>);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default DlgEditTerm;
|
export default DlgEditWordForms;
|
|
@ -194,6 +194,7 @@ function EditorConstituenta({
|
||||||
<RefsInput id='term' label='Термин'
|
<RefsInput id='term' label='Термин'
|
||||||
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
|
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
|
||||||
height='3.5rem'
|
height='3.5rem'
|
||||||
|
items={schema?.items}
|
||||||
value={term}
|
value={term}
|
||||||
initialValue={activeCst?.term_raw ?? ''}
|
initialValue={activeCst?.term_raw ?? ''}
|
||||||
resolved={activeCst?.term_resolved ?? ''}
|
resolved={activeCst?.term_resolved ?? ''}
|
||||||
|
@ -221,6 +222,7 @@ function EditorConstituenta({
|
||||||
<RefsInput id='definition' label='Текстовое определение'
|
<RefsInput id='definition' label='Текстовое определение'
|
||||||
placeholder='Лингвистическая интерпретация формального выражения'
|
placeholder='Лингвистическая интерпретация формального выражения'
|
||||||
height='6.3rem'
|
height='6.3rem'
|
||||||
|
items={schema?.items}
|
||||||
value={textDefinition}
|
value={textDefinition}
|
||||||
initialValue={activeCst?.definition_raw ?? ''}
|
initialValue={activeCst?.definition_raw ?? ''}
|
||||||
resolved={activeCst?.definition_resolved ?? ''}
|
resolved={activeCst?.definition_resolved ?? ''}
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { createAliasFor } from '../../utils/misc';
|
||||||
import DlgCloneRSForm from './DlgCloneRSForm';
|
import DlgCloneRSForm from './DlgCloneRSForm';
|
||||||
import DlgCreateCst from './DlgCreateCst';
|
import DlgCreateCst from './DlgCreateCst';
|
||||||
import DlgDeleteCst from './DlgDeleteCst';
|
import DlgDeleteCst from './DlgDeleteCst';
|
||||||
import DlgEditTerm from './DlgEditTerm';
|
import DlgEditWordForms from './DlgEditWordForms';
|
||||||
import DlgRenameCst from './DlgRenameCst';
|
import DlgRenameCst from './DlgRenameCst';
|
||||||
import DlgShowAST from './DlgShowAST';
|
import DlgShowAST from './DlgShowAST';
|
||||||
import DlgUploadRSForm from './DlgUploadRSForm';
|
import DlgUploadRSForm from './DlgUploadRSForm';
|
||||||
|
@ -354,7 +354,7 @@ function RSTabs() {
|
||||||
selected={toBeDeleted}
|
selected={toBeDeleted}
|
||||||
/>}
|
/>}
|
||||||
{showEditTerm &&
|
{showEditTerm &&
|
||||||
<DlgEditTerm
|
<DlgEditWordForms
|
||||||
hideWindow={() => setShowEditTerm(false)}
|
hideWindow={() => setShowEditTerm(false)}
|
||||||
onSave={handleSaveWordforms}
|
onSave={handleSaveWordforms}
|
||||||
target={activeCst!}
|
target={activeCst!}
|
||||||
|
|
|
@ -244,6 +244,11 @@ export class CodeMirrorWrapper {
|
||||||
return this.ref.view.state.selection.main;
|
return this.ref.view.state.selection.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelectionText(): string {
|
||||||
|
const selection = this.getSelection();
|
||||||
|
return this.ref.view.state.doc.sliceString(selection.from, selection.to);
|
||||||
|
}
|
||||||
|
|
||||||
setSelection(from: number, to: number) {
|
setSelection(from: number, to: number) {
|
||||||
this.ref.view.dispatch({
|
this.ref.view.dispatch({
|
||||||
selection: {
|
selection: {
|
||||||
|
@ -253,6 +258,10 @@ export class CodeMirrorWrapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insertChar(key: string) {
|
||||||
|
this.replaceWith(key);
|
||||||
|
}
|
||||||
|
|
||||||
replaceWith(data: string) {
|
replaceWith(data: string) {
|
||||||
this.ref.view.dispatch(this.ref.view.state.replaceSelection(data));
|
this.ref.view.dispatch(this.ref.view.state.replaceSelection(data));
|
||||||
}
|
}
|
||||||
|
@ -274,8 +283,20 @@ export class CodeMirrorWrapper {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
insertChar(key: string) {
|
/**
|
||||||
this.replaceWith(key);
|
* Access list of SyntaxNodes contained in current selection
|
||||||
|
*/
|
||||||
|
getContainedNodes(tokenFilter?: number[]): SyntaxNode[] {
|
||||||
|
const selection = this.getSelection();
|
||||||
|
return findContainedNodes(selection.from, selection.to, syntaxTree(this.ref.view.state), tokenFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access list of SyntaxNodes enveloping current selection
|
||||||
|
*/
|
||||||
|
getEnvelopingNodes(tokenFilter?: number[]): SyntaxNode[] {
|
||||||
|
const selection = this.getSelection();
|
||||||
|
return findEnvelopingNodes(selection.from, selection.to, syntaxTree(this.ref.view.state), tokenFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -103,7 +103,7 @@ export const darkT: IColorTheme = {
|
||||||
fgGreen: 'hsl(100, 080%, 035%)',
|
fgGreen: 'hsl(100, 080%, 035%)',
|
||||||
fgBlue: 'hsl(235, 100%, 080%)',
|
fgBlue: 'hsl(235, 100%, 080%)',
|
||||||
fgPurple: 'hsl(270, 100%, 080%)',
|
fgPurple: 'hsl(270, 100%, 080%)',
|
||||||
fgTeal: 'hsl(192, 100%, 030%)',
|
fgTeal: 'hsl(192, 100%, 045%)',
|
||||||
fgOrange: 'hsl(035, 100%, 050%)'
|
fgOrange: 'hsl(035, 100%, 050%)'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -36,5 +36,6 @@ export const prefixes = {
|
||||||
cst_list: 'cst-list-',
|
cst_list: 'cst-list-',
|
||||||
cst_status_list: 'cst-status-list-',
|
cst_status_list: 'cst-status-list-',
|
||||||
topic_list: 'topic-list-',
|
topic_list: 'topic-list-',
|
||||||
library_list: 'library-list-'
|
library_list: 'library-list-',
|
||||||
|
wordform_list: 'wordform-list'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// =========== Modules contains all text descriptors ==========
|
// =========== Modules contains all text descriptors ==========
|
||||||
|
|
||||||
import { GramData,Grammeme } from '../models/language';
|
import { GramData,Grammeme, ReferenceType } from '../models/language';
|
||||||
import { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
|
import { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
|
||||||
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
||||||
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
import { IFunctionArg, IRSErrorDescription, ISyntaxTreeNode, ParsingStatus, RSErrorType, TokenID } from '../models/rslang';
|
||||||
|
@ -221,7 +221,7 @@ export function describeHelpTopic(topic: HelpTopic): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function labelCstType(type: CstType) {
|
export function labelCstType(type: CstType): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CstType.BASE: return 'Базисное множество';
|
case CstType.BASE: return 'Базисное множество';
|
||||||
case CstType.CONSTANT: return 'Константное множество';
|
case CstType.CONSTANT: return 'Константное множество';
|
||||||
|
@ -234,6 +234,13 @@ export function labelCstType(type: CstType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function labelReferenceType(type: ReferenceType): string {
|
||||||
|
switch(type) {
|
||||||
|
case ReferenceType.ENTITY: return 'Использование термина';
|
||||||
|
case ReferenceType.SYNTACTIC: return 'Синтаксическая зависимость';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function labelCstClass(cclass: CstClass): string {
|
export function labelCstClass(cclass: CstClass): string {
|
||||||
switch (cclass) {
|
switch (cclass) {
|
||||||
case CstClass.BASIC: return 'базовый';
|
case CstClass.BASIC: return 'базовый';
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Module: Selector maps
|
// Module: Selector maps
|
||||||
import { LayoutTypes } from 'reagraph';
|
import { LayoutTypes } from 'reagraph';
|
||||||
|
|
||||||
import { compareGrammemes,type GramData, Grammeme } from '../models/language';
|
import { compareGrammemes,type GramData, Grammeme, ReferenceType } from '../models/language';
|
||||||
import { CstType } from '../models/rsform';
|
import { CstType } from '../models/rsform';
|
||||||
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
||||||
import { labelGrammeme } from './labels';
|
import { labelGrammeme, labelReferenceType } from './labels';
|
||||||
import { labelCstType } from './labels';
|
import { labelCstType } from './labels';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,3 +79,33 @@ gram => ({
|
||||||
value: gram,
|
value: gram,
|
||||||
label: labelGrammeme(gram)
|
label: labelGrammeme(gram)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents options for {@link ReferenceType} selector.
|
||||||
|
*/
|
||||||
|
export const SelectorReferenceType = (
|
||||||
|
Object.values(ReferenceType)).map(
|
||||||
|
typeStr => ({
|
||||||
|
value: typeStr as ReferenceType,
|
||||||
|
label: labelReferenceType(typeStr as ReferenceType)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents recommended wordforms data.
|
||||||
|
*/
|
||||||
|
export const PremadeWordForms = [
|
||||||
|
{ text: 'ед им', example: 'ручка', grams: [Grammeme.sing, Grammeme.nomn] },
|
||||||
|
{ text: 'ед род', example: 'ручки', grams: [Grammeme.sing, Grammeme.gent] },
|
||||||
|
{ text: 'ед дат', example: 'ручке', grams: [Grammeme.sing, Grammeme.datv] },
|
||||||
|
{ text: 'ед вин', example: 'ручку', grams: [Grammeme.sing, Grammeme.accs] },
|
||||||
|
{ text: 'ед твор', example: 'ручкой', grams: [Grammeme.sing, Grammeme.ablt] },
|
||||||
|
{ text: 'ед пред', example: 'ручке', grams: [Grammeme.sing, Grammeme.loct] },
|
||||||
|
|
||||||
|
{ text: 'мн им', example: 'ручки', grams: [Grammeme.plur, Grammeme.nomn] },
|
||||||
|
{ text: 'мн род', example: 'ручек', grams: [Grammeme.plur, Grammeme.gent] },
|
||||||
|
{ text: 'мн дат', example: 'ручкам', grams: [Grammeme.plur, Grammeme.datv] },
|
||||||
|
{ text: 'мн вин', example: 'ручки', grams: [Grammeme.plur, Grammeme.accs] },
|
||||||
|
{ text: 'мн твор', example: 'ручками', grams: [Grammeme.plur, Grammeme.ablt] },
|
||||||
|
{ text: 'мн пред', example: 'ручках', grams: [Grammeme.plur, Grammeme.loct] }
|
||||||
|
];
|
||||||
|
|
Loading…
Reference in New Issue
Block a user