mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +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
|
||||
colorClass?: string
|
||||
singleRow?: boolean
|
||||
noBorder?: boolean
|
||||
}
|
||||
|
||||
function TextInput({
|
||||
id, required, label, singleRow, tooltip,
|
||||
id, required, label, singleRow, tooltip, noBorder,
|
||||
dimensions = 'w-full',
|
||||
colorClass = 'clr-input',
|
||||
...props
|
||||
}: TextInputProps) {
|
||||
const borderClass = noBorder ? '': 'border';
|
||||
return (
|
||||
<div className={`flex ${singleRow ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
|
||||
{label && <Label
|
||||
{label &&
|
||||
<Label
|
||||
text={label}
|
||||
required={!props.disabled && required}
|
||||
htmlFor={id}
|
||||
/>}
|
||||
<input id={id}
|
||||
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}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
@ -26,7 +26,7 @@ const editorSetup: BasicSetupOptions = {
|
|||
lineNumbers: false,
|
||||
highlightActiveLineGutter: false,
|
||||
foldGutter: false,
|
||||
dropCursor: false,
|
||||
dropCursor: true,
|
||||
allowMultipleSelections: false,
|
||||
indentOnInput: 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 { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useResolveText from '../../hooks/useResolveText';
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { CodeMirrorWrapper } from '../../utils/codemirror';
|
||||
import Label from '../Common/Label';
|
||||
import Modal from '../Common/Modal';
|
||||
import PrettyJson from '../Common/PrettyJSON';
|
||||
import DlgEditReference from './DlgEditReference';
|
||||
import { NaturalLanguage, ReferenceTokens } from './parse';
|
||||
import { RefEntity } from './parse/parser.terms';
|
||||
import { refsHoverTooltip } from './tooltip';
|
||||
|
||||
const editorSetup: BasicSetupOptions = {
|
||||
|
@ -51,6 +55,7 @@ extends Pick<ReactCodeMirrorProps,
|
|||
label?: string
|
||||
innerref?: RefObject<ReactCodeMirrorRef> | undefined
|
||||
onChange?: (newValue: string) => void
|
||||
items?: IConstituenta[]
|
||||
|
||||
initialValue?: string
|
||||
value?: string
|
||||
|
@ -58,7 +63,7 @@ extends Pick<ReactCodeMirrorProps,
|
|||
}
|
||||
|
||||
function RefsInput({
|
||||
id, label, innerref, onChange, editable,
|
||||
id, label, innerref, onChange, editable, items,
|
||||
initialValue, value, resolved,
|
||||
onFocus, onBlur,
|
||||
...props
|
||||
|
@ -71,6 +76,11 @@ function RefsInput({
|
|||
const [showResolve, setShowResolve] = 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 thisRef = useMemo(
|
||||
() => {
|
||||
|
@ -133,11 +143,41 @@ function RefsInput({
|
|||
if (event.ctrlKey && event.code === 'Space') {
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
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]);
|
||||
|
||||
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 (
|
||||
<>
|
||||
{ showEditor &&
|
||||
<DlgEditReference
|
||||
hideWindow={() => setShowEditor(false)}
|
||||
items={items ?? []}
|
||||
initialType={currentType}
|
||||
initialRef={refText}
|
||||
initialText={hintText}
|
||||
onSave={handleInputReference}
|
||||
/>
|
||||
}
|
||||
{ showResolve &&
|
||||
<Modal
|
||||
readonly
|
||||
|
@ -161,7 +201,7 @@ function RefsInput({
|
|||
theme={customTheme}
|
||||
extensions={editorExtensions}
|
||||
|
||||
value={isFocused ? value : (value !== initialValue ? value : resolved)}
|
||||
value={isFocused ? value : (value !== initialValue || showEditor ? value : resolved)}
|
||||
|
||||
indentWithTab={false}
|
||||
onChange={handleChange}
|
||||
|
|
|
@ -283,6 +283,39 @@ export function parseGrammemes(termForm: string): GramData[] {
|
|||
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 =====
|
||||
/**
|
||||
* 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
|
||||
data: IEntityReference | ISyntacticReference
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents single resolved reference data.
|
||||
*/
|
||||
export interface IResolvedReference extends IReference {
|
||||
pos_input: ITextPosition
|
||||
pos_output: ITextPosition
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import SelectSingle from '../../components/Common/SelectSingle';
|
|||
import TextArea from '../../components/Common/TextArea';
|
||||
import RSInput from '../../components/RSInput';
|
||||
import { CstType,ICstCreateData } from '../../models/rsform';
|
||||
import { SelectorCstType } from '../../utils/selectors';
|
||||
import { labelCstType } from '../../utils/labels';
|
||||
import { SelectorCstType } from '../../utils/selectors';
|
||||
|
||||
interface DlgCreateCstProps
|
||||
extends Pick<ModalProps, 'hideWindow'> {
|
||||
|
|
|
@ -10,15 +10,15 @@ import { ArrowLeftIcon, ArrowRightIcon, CheckIcon, ChevronDoubleDownIcon, CrossI
|
|||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useConceptText from '../../hooks/useConceptText';
|
||||
import {
|
||||
GramData, Grammeme, GrammemeGroups, ITextRequest, IWordForm,
|
||||
IWordFormPlain, matchWordForm, NounGrams, parseGrammemes, VerbGrams
|
||||
getCompatibleGrams, Grammeme, ITextRequest, IWordForm,
|
||||
IWordFormPlain, matchWordForm, parseGrammemes
|
||||
} 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';
|
||||
|
||||
interface DlgEditTermProps {
|
||||
interface DlgEditWordFormsProps {
|
||||
hideWindow: () => void
|
||||
target: IConstituenta
|
||||
onSave: (data: TermForm[]) => void
|
||||
|
@ -26,7 +26,7 @@ interface DlgEditTermProps {
|
|||
|
||||
const columnHelper = createColumnHelper<IWordForm>();
|
||||
|
||||
function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
||||
function DlgEditWordForms({ hideWindow, target, onSave }: DlgEditWordFormsProps) {
|
||||
const textProcessor = useConceptText();
|
||||
const { colors } = useConceptTheme();
|
||||
const [term, setTerm] = useState('');
|
||||
|
@ -65,32 +65,12 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
// Filter grammemes when input changes
|
||||
useEffect(
|
||||
() => {
|
||||
let newFilter: GramData[] = [];
|
||||
inputGrams.forEach(({value: gram}) => {
|
||||
if (!newFilter.includes(gram)) {
|
||||
if (NounGrams.includes(gram as Grammeme)) {
|
||||
newFilter.push(...NounGrams);
|
||||
}
|
||||
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)));
|
||||
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)));
|
||||
}, [inputGrams]);
|
||||
|
||||
const handleSubmit = () => onSave(getData());
|
||||
|
@ -329,4 +309,4 @@ function DlgEditTerm({ hideWindow, target, onSave }: DlgEditTermProps) {
|
|||
</Modal>);
|
||||
}
|
||||
|
||||
export default DlgEditTerm;
|
||||
export default DlgEditWordForms;
|
|
@ -194,6 +194,7 @@ function EditorConstituenta({
|
|||
<RefsInput id='term' label='Термин'
|
||||
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
|
||||
height='3.5rem'
|
||||
items={schema?.items}
|
||||
value={term}
|
||||
initialValue={activeCst?.term_raw ?? ''}
|
||||
resolved={activeCst?.term_resolved ?? ''}
|
||||
|
@ -221,6 +222,7 @@ function EditorConstituenta({
|
|||
<RefsInput id='definition' label='Текстовое определение'
|
||||
placeholder='Лингвистическая интерпретация формального выражения'
|
||||
height='6.3rem'
|
||||
items={schema?.items}
|
||||
value={textDefinition}
|
||||
initialValue={activeCst?.definition_raw ?? ''}
|
||||
resolved={activeCst?.definition_resolved ?? ''}
|
||||
|
|
|
@ -21,7 +21,7 @@ import { createAliasFor } from '../../utils/misc';
|
|||
import DlgCloneRSForm from './DlgCloneRSForm';
|
||||
import DlgCreateCst from './DlgCreateCst';
|
||||
import DlgDeleteCst from './DlgDeleteCst';
|
||||
import DlgEditTerm from './DlgEditTerm';
|
||||
import DlgEditWordForms from './DlgEditWordForms';
|
||||
import DlgRenameCst from './DlgRenameCst';
|
||||
import DlgShowAST from './DlgShowAST';
|
||||
import DlgUploadRSForm from './DlgUploadRSForm';
|
||||
|
@ -354,7 +354,7 @@ function RSTabs() {
|
|||
selected={toBeDeleted}
|
||||
/>}
|
||||
{showEditTerm &&
|
||||
<DlgEditTerm
|
||||
<DlgEditWordForms
|
||||
hideWindow={() => setShowEditTerm(false)}
|
||||
onSave={handleSaveWordforms}
|
||||
target={activeCst!}
|
||||
|
|
|
@ -244,6 +244,11 @@ export class CodeMirrorWrapper {
|
|||
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) {
|
||||
this.ref.view.dispatch({
|
||||
selection: {
|
||||
|
@ -253,6 +258,10 @@ export class CodeMirrorWrapper {
|
|||
});
|
||||
}
|
||||
|
||||
insertChar(key: string) {
|
||||
this.replaceWith(key);
|
||||
}
|
||||
|
||||
replaceWith(data: string) {
|
||||
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%)',
|
||||
fgBlue: 'hsl(235, 100%, 080%)',
|
||||
fgPurple: 'hsl(270, 100%, 080%)',
|
||||
fgTeal: 'hsl(192, 100%, 030%)',
|
||||
fgTeal: 'hsl(192, 100%, 045%)',
|
||||
fgOrange: 'hsl(035, 100%, 050%)'
|
||||
};
|
||||
|
||||
|
|
|
@ -36,5 +36,6 @@ export const prefixes = {
|
|||
cst_list: 'cst-list-',
|
||||
cst_status_list: 'cst-status-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 ==========
|
||||
|
||||
import { GramData,Grammeme } from '../models/language';
|
||||
import { GramData,Grammeme, ReferenceType } from '../models/language';
|
||||
import { CstMatchMode, DependencyMode, HelpTopic } from '../models/miscelanious';
|
||||
import { CstClass, CstType, ExpressionStatus, IConstituenta } from '../models/rsform';
|
||||
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) {
|
||||
case CstType.BASE: 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 {
|
||||
switch (cclass) {
|
||||
case CstClass.BASIC: return 'базовый';
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Module: Selector maps
|
||||
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 { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
||||
import { labelGrammeme } from './labels';
|
||||
import { labelGrammeme, labelReferenceType } from './labels';
|
||||
import { labelCstType } from './labels';
|
||||
|
||||
/**
|
||||
|
@ -79,3 +79,33 @@ gram => ({
|
|||
value: 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