Improve reference editing

This commit is contained in:
IRBorisov 2023-09-30 17:16:20 +03:00
parent 41bb83b784
commit f54254bb56
6 changed files with 122 additions and 50 deletions

View File

@ -4,7 +4,7 @@ function HelpTerminologyControl() {
<div>
<h1>Терминологизация: Контроль терминологии</h1>
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>синтаксическая связь.</i></p>
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p>
<p>При отсылке к термину указывается параметры словоформы так, обеспечивающие корректное согласование слов.</p>
<p><b>Граммема</b> - минимальная единица грамматической информаци, например род, число, падеж.</p>
<p><b>Словоформа</b> - грамматическая форма словосочетания, которая может меняться в зависимости от его грамматических характеристик.</p>

View File

@ -8,33 +8,36 @@ 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 { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
import ConceptTooltip from '../Common/ConceptTooltip';
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 HelpTerminologyControl from '../Help/HelpTerminologyControl';
import { HelpIcon } from '../Icons';
import TermformButton from './TermformButton';
import ReferenceTypeButton from './ReferenceTypeButton';
import WordformButton from './WordformButton';
export interface IReferenceInputState {
type: ReferenceType
refRaw?: string
text?: string
mainRefs: string[]
basePosition: number
}
interface DlgEditReferenceProps {
hideWindow: () => void
items: IConstituenta[]
initialType: ReferenceType
initialRef?: string
initialText?: string
initial: IReferenceInputState
onSave: (newRef: string) => void
}
const constituentaHelper = createColumnHelper<IConstituenta>();
function DlgEditReference({ hideWindow, items, initialRef, initialText, initialType, onSave }: DlgEditReferenceProps) {
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
const { colors } = useConceptTheme();
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
@ -49,6 +52,16 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
const [gramOptions, setGramOptions] = useState<IGrammemeOption[]>([]);
const mainLink = useMemo(
() => {
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
if (offset === 0 || position < 0 || position >= initial.mainRefs.length) {
return 'Некорректное значение смещения';
} else {
return initial.mainRefs[position];
}
}, [initial, offset]);
const isValid = useMemo(
() => {
if (type === ReferenceType.ENTITY) {
@ -73,23 +86,23 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
// Initialization
useLayoutEffect(
() => {
setType(initialType);
if (initialRef) {
if (initialType === ReferenceType.ENTITY) {
const ref = parseEntityReference(initialRef);
setType(initial.type);
if (initial.refRaw) {
if (initial.type === ReferenceType.ENTITY) {
const ref = parseEntityReference(initial.refRaw);
setAlias(ref.entity);
const grams = parseGrammemes(ref.form);
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
} else if (initialType === ReferenceType.SYNTACTIC) {
const ref = parseSyntacticReference(initialRef);
} else if (initial.type === ReferenceType.SYNTACTIC) {
const ref = parseSyntacticReference(initial.refRaw);
setOffset(ref.offset);
setNominal(ref.nominal);
}
} else if (initialText) {
setNominal(initialText ?? '');
setFilter(initialText);
} else if (initial.text) {
setNominal(initial.text ?? '');
setFilter(initial.text);
}
}, [initialRef, initialText, initialType, items]);
}, [initial, items]);
// Filter constituents
useEffect(
@ -140,7 +153,7 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
<div className='flex flex-start'>
{PremadeWordForms.slice(0, 6).map(
(data, index) =>
<TermformButton id={`${prefixes.wordform_list}${index}`}
<WordformButton 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}
@ -151,7 +164,7 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
<div className='flex flex-start'>
{PremadeWordForms.slice(6, 12).map(
(data, index) =>
<TermformButton id={`${prefixes.wordform_list}${index}`}
<WordformButton 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}
@ -216,15 +229,17 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
canSubmit={isValid}
onSubmit={handleSubmit}
>
<div className='min-w-[40rem] flex flex-col gap-4 mb-4 mt-2'>
<div className='min-w-[40rem] flex flex-col gap-4 mb-4 mt-2 min-h-[34rem]'>
<div className='flex items-center self-center flex-start'>
<SelectSingle
className='z-modal-top min-w-[20rem] w-fit'
options={SelectorReferenceType}
isSearchable={false}
placeholder='Тип ссылки'
value={{ value: type, label: labelReferenceType(type) }}
onChange={data => setType(data?.value ?? ReferenceType.ENTITY)}
<ReferenceTypeButton
type={ReferenceType.ENTITY}
onSelect={setType}
isSelected={type === ReferenceType.ENTITY}
/>
<ReferenceTypeButton
type={ReferenceType.SYNTACTIC}
onSelect={setType}
isSelected={type === ReferenceType.SYNTACTIC}
/>
<div id='terminology-help' className='px-1 py-1'>
<HelpIcon color='text-primary' size={5} />
@ -238,7 +253,8 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
</ConceptTooltip>
</div>
{type === ReferenceType.SYNTACTIC &&
<div className='flex gap-4 flex-start'>
<div className='flex flex-col gap-2'>
<div className='flex flex-start'>
<TextInput id='offset' type='number'
label='Смещение'
dimensions='max-w-[10rem]'
@ -246,12 +262,22 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
value={offset}
onChange={event => setOffset(event.target.valueAsNumber)}
/>
<div className='self-center text-sm font-semibold whitespace-nowrap ml-2'>
Основная ссылка:
</div>
<TextInput
singleRow
disabled
noBorder
value={mainLink}
dimensions='w-full text-sm'
/>
</div>
<TextInput id='nominal' type='text'
dimensions='w-full'
label='Начальная форма'
placeholder='зависимое слово в начальной форме'
spellCheck
singleRow
value={nominal}
onChange={event => setNominal(event.target.value)}
/>

View File

@ -0,0 +1,23 @@
import { ReferenceType } from '../../models/language';
import { labelReferenceType } from '../../utils/labels';
interface ReferenceTypeButtonProps {
id?: string
type: ReferenceType
isSelected?: boolean
onSelect: (type: ReferenceType) => void
}
function ReferenceTypeButton({ type, isSelected, onSelect, ...props }: ReferenceTypeButtonProps) {
return (
<button type='button' tabIndex={-1}
onClick={() => onSelect(type)}
className={`min-w-[12rem] px-2 py-1 border font-semibold rounded-none cursor-pointer clr-btn-clear clr-hover ${isSelected ? 'clr-selected': ''}`}
{...props}
>
{labelReferenceType(type)}
</button>
);
}
export default ReferenceTypeButton;

View File

@ -1,6 +1,6 @@
import { Grammeme } from '../../models/language';
interface TermformButtonProps {
interface WordformButtonProps {
id?: string
text: string
example: string
@ -9,7 +9,7 @@ interface TermformButtonProps {
onSelectGrams: (grams: Grammeme[]) => void
}
function TermformButton({ text, example, grams, onSelectGrams, isSelected, ...props }: TermformButtonProps) {
function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...props }: WordformButtonProps) {
return (
<button
type='button'
@ -24,4 +24,4 @@ function TermformButton({ text, example, grams, onSelectGrams, isSelected, ...pr
);
}
export default TermformButton;
export default WordformButton;

View File

@ -63,9 +63,9 @@ extends Pick<ReactCodeMirrorProps,
}
function RefsInput({
id, label, innerref, onChange, editable, items,
id, label, innerref, editable, items,
initialValue, value, resolved,
onFocus, onBlur,
onFocus, onBlur, onChange,
...props
}: RefsInputInputProps) {
const { darkMode, colors } = useConceptTheme();
@ -80,6 +80,8 @@ function RefsInput({
const [currentType, setCurrentType] = useState<ReferenceType>(ReferenceType.ENTITY);
const [refText, setRefText] = useState('');
const [hintText, setHintText] = useState('');
const [basePosition, setBasePosition] = useState(0);
const [mainRefs, setMainRefs] = useState<string[]>([]);
const internalRef = useRef<ReactCodeMirrorRef>(null);
const thisRef = useMemo(
@ -152,6 +154,12 @@ function RefsInput({
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
setRefText(wrap.getSelectionText());
}
const selection = wrap.getSelection();
const mainNodes = wrap.getAllNodes([RefEntity]).filter(node => node.from >= selection.to || node.to <= selection.from);
setMainRefs(mainNodes.map(node => wrap.getText(node.from, node.to)));
setBasePosition(mainNodes.filter(node => node.to <= selection.from).length);
setShowEditor(true);
}
}, [thisRef, resolveText, value]);
@ -172,9 +180,13 @@ function RefsInput({
<DlgEditReference
hideWindow={() => setShowEditor(false)}
items={items ?? []}
initialType={currentType}
initialRef={refText}
initialText={hintText}
initial={{
type: currentType,
refRaw: refText,
text: hintText,
basePosition: basePosition,
mainRefs: mainRefs
}}
onSave={handleInputReference}
/>
}

View File

@ -240,6 +240,10 @@ export class CodeMirrorWrapper {
this.ref = object;
}
getText(from: number, to: number): string {
return this.ref.view.state.doc.sliceString(from, to);
}
getSelection(): SelectionRange {
return this.ref.view.state.selection.main;
}
@ -284,7 +288,7 @@ export class CodeMirrorWrapper {
}
/**
* Access list of SyntaxNodes contained in current selection
* Access list of SyntaxNodes contained in current selection.
*/
getContainedNodes(tokenFilter?: number[]): SyntaxNode[] {
const selection = this.getSelection();
@ -292,13 +296,20 @@ export class CodeMirrorWrapper {
}
/**
* Access list of SyntaxNodes enveloping current selection
* 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);
}
/**
* Access list of SyntaxNodes contained in documents.
*/
getAllNodes(tokenFilter?: number[]): SyntaxNode[] {
return findContainedNodes(0, this.ref.view.state.doc.length, syntaxTree(this.ref.view.state), tokenFilter);
}
/**
* Enlarges selection to nearest spaces.
*