Implement reference dialog

This commit is contained in:
IRBorisov 2023-09-29 15:33:32 +03:00
parent 4eef460be1
commit edff9b640a
15 changed files with 504 additions and 50 deletions

View File

@ -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}
/> />

View File

@ -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,

View 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;

View File

@ -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;

View File

@ -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}

View File

@ -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
} }

View File

@ -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'> {

View File

@ -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;

View File

@ -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 ?? ''}

View File

@ -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!}

View File

@ -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);
} }
/** /**

View File

@ -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%)'
}; };

View File

@ -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'
} }

View File

@ -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 'базовый';

View File

@ -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] }
];