mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-08-14 21:00:37 +03:00
Improve reference editing
This commit is contained in:
parent
41bb83b784
commit
f54254bb56
|
@ -4,7 +4,7 @@ function HelpTerminologyControl() {
|
||||||
<div>
|
<div>
|
||||||
<h1>Терминологизация: Контроль терминологии</h1>
|
<h1>Терминологизация: Контроль терминологии</h1>
|
||||||
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
|
<p>Портал позволяет контролировать употребление терминов, привязанных к сущностям в концептуальных схемах.</p>
|
||||||
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>синтаксическая связь.</i></p>
|
<p>Для этого используется механизм текстовых отсылок: <i>использование термина</i> и <i>связывание слов.</i></p>
|
||||||
<p>При отсылке к термину указывается параметры словоформы так, обеспечивающие корректное согласование слов.</p>
|
<p>При отсылке к термину указывается параметры словоформы так, обеспечивающие корректное согласование слов.</p>
|
||||||
<p><b>Граммема</b> - минимальная единица грамматической информаци, например род, число, падеж.</p>
|
<p><b>Граммема</b> - минимальная единица грамматической информаци, например род, число, падеж.</p>
|
||||||
<p><b>Словоформа</b> - грамматическая форма словосочетания, которая может меняться в зависимости от его грамматических характеристик.</p>
|
<p><b>Словоформа</b> - грамматическая форма словосочетания, которая может меняться в зависимости от его грамматических характеристик.</p>
|
||||||
|
|
|
@ -8,33 +8,36 @@ import { IConstituenta, matchConstituenta } from '../../models/rsform';
|
||||||
import ConstituentaTooltip from '../../pages/RSFormPage/elements/ConstituentaTooltip';
|
import ConstituentaTooltip from '../../pages/RSFormPage/elements/ConstituentaTooltip';
|
||||||
import { colorfgCstStatus } from '../../utils/color';
|
import { colorfgCstStatus } from '../../utils/color';
|
||||||
import { prefixes } from '../../utils/constants';
|
import { prefixes } from '../../utils/constants';
|
||||||
import { labelReferenceType } from '../../utils/labels';
|
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems } from '../../utils/selectors';
|
||||||
import { compareGrammemeOptions, IGrammemeOption, PremadeWordForms, SelectorGrammems, SelectorReferenceType } from '../../utils/selectors';
|
|
||||||
import ConceptTooltip from '../Common/ConceptTooltip';
|
import ConceptTooltip from '../Common/ConceptTooltip';
|
||||||
import Label from '../Common/Label';
|
import Label from '../Common/Label';
|
||||||
import Modal from '../Common/Modal';
|
import Modal from '../Common/Modal';
|
||||||
import SelectMulti from '../Common/SelectMulti';
|
import SelectMulti from '../Common/SelectMulti';
|
||||||
import SelectSingle from '../Common/SelectSingle';
|
|
||||||
import TextInput from '../Common/TextInput';
|
import TextInput from '../Common/TextInput';
|
||||||
import DataTable, { IConditionalStyle } from '../DataTable';
|
import DataTable, { IConditionalStyle } from '../DataTable';
|
||||||
import HelpTerminologyControl from '../Help/HelpTerminologyControl';
|
import HelpTerminologyControl from '../Help/HelpTerminologyControl';
|
||||||
import { HelpIcon } from '../Icons';
|
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 {
|
interface DlgEditReferenceProps {
|
||||||
hideWindow: () => void
|
hideWindow: () => void
|
||||||
items: IConstituenta[]
|
items: IConstituenta[]
|
||||||
|
initial: IReferenceInputState
|
||||||
initialType: ReferenceType
|
|
||||||
initialRef?: string
|
|
||||||
initialText?: string
|
|
||||||
|
|
||||||
onSave: (newRef: string) => void
|
onSave: (newRef: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const constituentaHelper = createColumnHelper<IConstituenta>();
|
const constituentaHelper = createColumnHelper<IConstituenta>();
|
||||||
|
|
||||||
function DlgEditReference({ hideWindow, items, initialRef, initialText, initialType, onSave }: DlgEditReferenceProps) {
|
function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferenceProps) {
|
||||||
const { colors } = useConceptTheme();
|
const { colors } = useConceptTheme();
|
||||||
|
|
||||||
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
const [type, setType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||||
|
@ -49,6 +52,16 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
|
||||||
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||||
const [gramOptions, setGramOptions] = 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(
|
const isValid = useMemo(
|
||||||
() => {
|
() => {
|
||||||
if (type === ReferenceType.ENTITY) {
|
if (type === ReferenceType.ENTITY) {
|
||||||
|
@ -73,23 +86,23 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
|
||||||
// Initialization
|
// Initialization
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
setType(initialType);
|
setType(initial.type);
|
||||||
if (initialRef) {
|
if (initial.refRaw) {
|
||||||
if (initialType === ReferenceType.ENTITY) {
|
if (initial.type === ReferenceType.ENTITY) {
|
||||||
const ref = parseEntityReference(initialRef);
|
const ref = parseEntityReference(initial.refRaw);
|
||||||
setAlias(ref.entity);
|
setAlias(ref.entity);
|
||||||
const grams = parseGrammemes(ref.form);
|
const grams = parseGrammemes(ref.form);
|
||||||
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
setSelectedGrams(SelectorGrammems.filter(data => grams.includes(data.value)));
|
||||||
} else if (initialType === ReferenceType.SYNTACTIC) {
|
} else if (initial.type === ReferenceType.SYNTACTIC) {
|
||||||
const ref = parseSyntacticReference(initialRef);
|
const ref = parseSyntacticReference(initial.refRaw);
|
||||||
setOffset(ref.offset);
|
setOffset(ref.offset);
|
||||||
setNominal(ref.nominal);
|
setNominal(ref.nominal);
|
||||||
}
|
}
|
||||||
} else if (initialText) {
|
} else if (initial.text) {
|
||||||
setNominal(initialText ?? '');
|
setNominal(initial.text ?? '');
|
||||||
setFilter(initialText);
|
setFilter(initial.text);
|
||||||
}
|
}
|
||||||
}, [initialRef, initialText, initialType, items]);
|
}, [initial, items]);
|
||||||
|
|
||||||
// Filter constituents
|
// Filter constituents
|
||||||
useEffect(
|
useEffect(
|
||||||
|
@ -140,7 +153,7 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
|
||||||
<div className='flex flex-start'>
|
<div className='flex flex-start'>
|
||||||
{PremadeWordForms.slice(0, 6).map(
|
{PremadeWordForms.slice(0, 6).map(
|
||||||
(data, index) =>
|
(data, index) =>
|
||||||
<TermformButton id={`${prefixes.wordform_list}${index}`}
|
<WordformButton id={`${prefixes.wordform_list}${index}`}
|
||||||
text={data.text} example={data.example} grams={data.grams}
|
text={data.text} example={data.example} grams={data.grams}
|
||||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
||||||
onSelectGrams={handleSelectGrams}
|
onSelectGrams={handleSelectGrams}
|
||||||
|
@ -151,7 +164,7 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
|
||||||
<div className='flex flex-start'>
|
<div className='flex flex-start'>
|
||||||
{PremadeWordForms.slice(6, 12).map(
|
{PremadeWordForms.slice(6, 12).map(
|
||||||
(data, index) =>
|
(data, index) =>
|
||||||
<TermformButton id={`${prefixes.wordform_list}${index}`}
|
<WordformButton id={`${prefixes.wordform_list}${index}`}
|
||||||
text={data.text} example={data.example} grams={data.grams}
|
text={data.text} example={data.example} grams={data.grams}
|
||||||
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
isSelected={data.grams.every(gram => selectedGrams.find(item => item.value as Grammeme === gram))}
|
||||||
onSelectGrams={handleSelectGrams}
|
onSelectGrams={handleSelectGrams}
|
||||||
|
@ -216,15 +229,17 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
|
||||||
canSubmit={isValid}
|
canSubmit={isValid}
|
||||||
onSubmit={handleSubmit}
|
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'>
|
<div className='flex items-center self-center flex-start'>
|
||||||
<SelectSingle
|
<ReferenceTypeButton
|
||||||
className='z-modal-top min-w-[20rem] w-fit'
|
type={ReferenceType.ENTITY}
|
||||||
options={SelectorReferenceType}
|
onSelect={setType}
|
||||||
isSearchable={false}
|
isSelected={type === ReferenceType.ENTITY}
|
||||||
placeholder='Тип ссылки'
|
/>
|
||||||
value={{ value: type, label: labelReferenceType(type) }}
|
<ReferenceTypeButton
|
||||||
onChange={data => setType(data?.value ?? ReferenceType.ENTITY)}
|
type={ReferenceType.SYNTACTIC}
|
||||||
|
onSelect={setType}
|
||||||
|
isSelected={type === ReferenceType.SYNTACTIC}
|
||||||
/>
|
/>
|
||||||
<div id='terminology-help' className='px-1 py-1'>
|
<div id='terminology-help' className='px-1 py-1'>
|
||||||
<HelpIcon color='text-primary' size={5} />
|
<HelpIcon color='text-primary' size={5} />
|
||||||
|
@ -238,20 +253,31 @@ function DlgEditReference({ hideWindow, items, initialRef, initialText, initialT
|
||||||
</ConceptTooltip>
|
</ConceptTooltip>
|
||||||
</div>
|
</div>
|
||||||
{type === ReferenceType.SYNTACTIC &&
|
{type === ReferenceType.SYNTACTIC &&
|
||||||
<div className='flex gap-4 flex-start'>
|
<div className='flex flex-col gap-2'>
|
||||||
<TextInput id='offset' type='number'
|
<div className='flex flex-start'>
|
||||||
label='Смещение'
|
<TextInput id='offset' type='number'
|
||||||
dimensions='max-w-[10rem]'
|
label='Смещение'
|
||||||
singleRow
|
dimensions='max-w-[10rem]'
|
||||||
value={offset}
|
singleRow
|
||||||
onChange={event => setOffset(event.target.valueAsNumber)}
|
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'
|
<TextInput id='nominal' type='text'
|
||||||
dimensions='w-full'
|
dimensions='w-full'
|
||||||
label='Начальная форма'
|
label='Начальная форма'
|
||||||
placeholder='зависимое слово в начальной форме'
|
placeholder='зависимое слово в начальной форме'
|
||||||
spellCheck
|
spellCheck
|
||||||
singleRow
|
|
||||||
value={nominal}
|
value={nominal}
|
||||||
onChange={event => setNominal(event.target.value)}
|
onChange={event => setNominal(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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;
|
|
@ -1,6 +1,6 @@
|
||||||
import { Grammeme } from '../../models/language';
|
import { Grammeme } from '../../models/language';
|
||||||
|
|
||||||
interface TermformButtonProps {
|
interface WordformButtonProps {
|
||||||
id?: string
|
id?: string
|
||||||
text: string
|
text: string
|
||||||
example: string
|
example: string
|
||||||
|
@ -9,7 +9,7 @@ interface TermformButtonProps {
|
||||||
onSelectGrams: (grams: Grammeme[]) => void
|
onSelectGrams: (grams: Grammeme[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
function TermformButton({ text, example, grams, onSelectGrams, isSelected, ...props }: TermformButtonProps) {
|
function WordformButton({ text, example, grams, onSelectGrams, isSelected, ...props }: WordformButtonProps) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
|
@ -24,4 +24,4 @@ function TermformButton({ text, example, grams, onSelectGrams, isSelected, ...pr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TermformButton;
|
export default WordformButton;
|
|
@ -63,9 +63,9 @@ extends Pick<ReactCodeMirrorProps,
|
||||||
}
|
}
|
||||||
|
|
||||||
function RefsInput({
|
function RefsInput({
|
||||||
id, label, innerref, onChange, editable, items,
|
id, label, innerref, editable, items,
|
||||||
initialValue, value, resolved,
|
initialValue, value, resolved,
|
||||||
onFocus, onBlur,
|
onFocus, onBlur, onChange,
|
||||||
...props
|
...props
|
||||||
}: RefsInputInputProps) {
|
}: RefsInputInputProps) {
|
||||||
const { darkMode, colors } = useConceptTheme();
|
const { darkMode, colors } = useConceptTheme();
|
||||||
|
@ -80,6 +80,8 @@ function RefsInput({
|
||||||
const [currentType, setCurrentType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
const [currentType, setCurrentType] = useState<ReferenceType>(ReferenceType.ENTITY);
|
||||||
const [refText, setRefText] = useState('');
|
const [refText, setRefText] = useState('');
|
||||||
const [hintText, setHintText] = useState('');
|
const [hintText, setHintText] = useState('');
|
||||||
|
const [basePosition, setBasePosition] = useState(0);
|
||||||
|
const [mainRefs, setMainRefs] = useState<string[]>([]);
|
||||||
|
|
||||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||||
const thisRef = useMemo(
|
const thisRef = useMemo(
|
||||||
|
@ -152,6 +154,12 @@ function RefsInput({
|
||||||
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
||||||
setRefText(wrap.getSelectionText());
|
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);
|
setShowEditor(true);
|
||||||
}
|
}
|
||||||
}, [thisRef, resolveText, value]);
|
}, [thisRef, resolveText, value]);
|
||||||
|
@ -172,9 +180,13 @@ function RefsInput({
|
||||||
<DlgEditReference
|
<DlgEditReference
|
||||||
hideWindow={() => setShowEditor(false)}
|
hideWindow={() => setShowEditor(false)}
|
||||||
items={items ?? []}
|
items={items ?? []}
|
||||||
initialType={currentType}
|
initial={{
|
||||||
initialRef={refText}
|
type: currentType,
|
||||||
initialText={hintText}
|
refRaw: refText,
|
||||||
|
text: hintText,
|
||||||
|
basePosition: basePosition,
|
||||||
|
mainRefs: mainRefs
|
||||||
|
}}
|
||||||
onSave={handleInputReference}
|
onSave={handleInputReference}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,6 +240,10 @@ export class CodeMirrorWrapper {
|
||||||
this.ref = object;
|
this.ref = object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getText(from: number, to: number): string {
|
||||||
|
return this.ref.view.state.doc.sliceString(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
getSelection(): SelectionRange {
|
getSelection(): SelectionRange {
|
||||||
return this.ref.view.state.selection.main;
|
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[] {
|
getContainedNodes(tokenFilter?: number[]): SyntaxNode[] {
|
||||||
const selection = this.getSelection();
|
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[] {
|
getEnvelopingNodes(tokenFilter?: number[]): SyntaxNode[] {
|
||||||
const selection = this.getSelection();
|
const selection = this.getSelection();
|
||||||
return findEnvelopingNodes(selection.from, selection.to, syntaxTree(this.ref.view.state), tokenFilter);
|
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.
|
* Enlarges selection to nearest spaces.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue
Block a user