mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 04:50:36 +03:00
F: Rework reference editor dialog
This commit is contained in:
parent
5afe8ac86e
commit
00fd2beea3
|
@ -43,7 +43,10 @@ interface ModalFormProps extends ModalProps {
|
|||
beforeSubmit?: () => boolean;
|
||||
|
||||
/** Callback to be called after submit. */
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
|
||||
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void | Promise<void>;
|
||||
|
||||
/** Callback to be called when modal is canceled. */
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -61,25 +64,30 @@ export function ModalForm({
|
|||
submitInvalidTooltip,
|
||||
beforeSubmit,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
|
||||
helpTopic,
|
||||
hideHelpWhen,
|
||||
...restProps
|
||||
}: React.PropsWithChildren<ModalFormProps>) {
|
||||
const hideDialog = useDialogsStore(state => state.hideDialog);
|
||||
useEscapeKey(hideDialog);
|
||||
|
||||
function handleCancel() {
|
||||
onCancel?.();
|
||||
hideDialog();
|
||||
}
|
||||
useEscapeKey(handleCancel);
|
||||
|
||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
||||
if (beforeSubmit && !beforeSubmit()) {
|
||||
return;
|
||||
}
|
||||
onSubmit(event);
|
||||
hideDialog();
|
||||
void Promise.resolve(onSubmit(event)).then(hideDialog);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
|
||||
<ModalBackdrop onHide={hideDialog} />
|
||||
<ModalBackdrop onHide={handleCancel} />
|
||||
<form
|
||||
className={clsx(
|
||||
'cc-animate-modal',
|
||||
|
@ -99,7 +107,7 @@ export function ModalForm({
|
|||
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
|
||||
icon={<IconClose size='1.25rem' />}
|
||||
className='float-right mt-2 mr-2'
|
||||
onClick={hideDialog}
|
||||
onClick={handleCancel}
|
||||
/>
|
||||
|
||||
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}
|
||||
|
@ -126,7 +134,7 @@ export function ModalForm({
|
|||
className='min-w-[7rem]'
|
||||
disabled={!canSubmit}
|
||||
/>
|
||||
<Button text='Отмена' className='min-w-[7rem]' onClick={hideDialog} />
|
||||
<Button text='Отмена' className='min-w-[7rem]' onClick={handleCancel} />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -9,12 +9,15 @@ import clsx from 'clsx';
|
|||
import { EditorView } from 'codemirror';
|
||||
|
||||
import { Label } from '@/components/Input';
|
||||
import { DialogType, useDialogsStore } from '@/stores/dialogs';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
import { usePreferencesStore } from '@/stores/preferences';
|
||||
import { APP_COLORS } from '@/styling/colors';
|
||||
import { CodeMirrorWrapper } from '@/utils/codemirror';
|
||||
import { PARAMETER } from '@/utils/constants';
|
||||
|
||||
import { IReferenceInputState } from '../../dialogs/DlgEditReference/DlgEditReference';
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { referenceToString } from '../../models/languageAPI';
|
||||
import { IRSForm } from '../../models/rsform';
|
||||
|
||||
import { RefEntity } from './parse/parser.terms';
|
||||
|
@ -63,9 +66,9 @@ interface RefsInputInputProps
|
|||
| 'onBlur'
|
||||
| 'placeholder'
|
||||
> {
|
||||
value?: string;
|
||||
resolved?: string;
|
||||
onChange?: (newValue: string) => void;
|
||||
value: string;
|
||||
resolved: string;
|
||||
onChange: (newValue: string) => void;
|
||||
|
||||
schema: IRSForm;
|
||||
onOpenEdit?: (cstID: number) => void;
|
||||
|
@ -75,7 +78,7 @@ interface RefsInputInputProps
|
|||
initialValue?: string;
|
||||
}
|
||||
|
||||
const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
||||
export const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
||||
(
|
||||
{
|
||||
id, // prettier: split-lines
|
||||
|
@ -98,14 +101,7 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
const [isFocused, setIsFocused] = useState(false);
|
||||
|
||||
const showEditReference = useDialogsStore(state => state.showEditReference);
|
||||
const activeDialog = useDialogsStore(state => state.active);
|
||||
const isActive = activeDialog === DialogType.EDIT_REFERENCE; // TODO: reconsider this dependency
|
||||
|
||||
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 [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||
const thisRef = !ref || typeof ref === 'function' ? internalRef : ref;
|
||||
|
@ -135,11 +131,8 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
refsHoverTooltip(schema, onOpenEdit !== undefined)
|
||||
];
|
||||
|
||||
function handleChange(newValue: string) {
|
||||
if (onChange) onChange(newValue);
|
||||
}
|
||||
|
||||
function handleFocusIn(event: React.FocusEvent<HTMLDivElement>) {
|
||||
setIsEditing(false);
|
||||
setIsFocused(true);
|
||||
if (onFocus) onFocus(event);
|
||||
}
|
||||
|
@ -162,45 +155,50 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.fixSelection(ReferenceTokens);
|
||||
const nodes = wrap.getEnvelopingNodes(ReferenceTokens);
|
||||
|
||||
const data: IReferenceInputState = {
|
||||
type: ReferenceType.ENTITY,
|
||||
refRaw: '',
|
||||
text: '',
|
||||
mainRefs: [],
|
||||
basePosition: 0
|
||||
};
|
||||
|
||||
if (nodes.length !== 1) {
|
||||
setCurrentType(ReferenceType.ENTITY);
|
||||
setRefText('');
|
||||
setHintText(wrap.getSelectionText());
|
||||
data.text = wrap.getSelectionText();
|
||||
} else {
|
||||
setCurrentType(nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
||||
setRefText(wrap.getSelectionText());
|
||||
data.type = nodes[0].type.id === RefEntity ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC;
|
||||
data.refRaw = 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);
|
||||
data.mainRefs = mainNodes.map(node => wrap.getText(node.from, node.to));
|
||||
data.basePosition = mainNodes.filter(node => node.to <= selection.from).length;
|
||||
|
||||
setIsEditing(true);
|
||||
showEditReference({
|
||||
schema: schema,
|
||||
initial: {
|
||||
type: currentType,
|
||||
refRaw: refText,
|
||||
text: hintText,
|
||||
basePosition: basePosition,
|
||||
mainRefs: mainRefs
|
||||
initial: data,
|
||||
onCancel: () => {
|
||||
setIsEditing(false);
|
||||
setTimeout(() => {
|
||||
thisRef.current?.view?.focus();
|
||||
}, PARAMETER.minimalTimeout);
|
||||
},
|
||||
onSave: handleInputReference
|
||||
onSave: ref => {
|
||||
wrap.replaceWith(referenceToString(ref));
|
||||
setIsEditing(false);
|
||||
setTimeout(() => {
|
||||
thisRef.current?.view?.focus();
|
||||
}, PARAMETER.minimalTimeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleInputReference(referenceText: string) {
|
||||
if (!thisRef.current?.view) {
|
||||
return;
|
||||
}
|
||||
thisRef.current.view.focus();
|
||||
const wrap = new CodeMirrorWrapper(thisRef.current as Required<ReactCodeMirrorRef>);
|
||||
wrap.replaceWith(referenceText);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={clsx('flex flex-col gap-2', cursor)}>
|
||||
<Label text={label} />
|
||||
|
@ -210,10 +208,10 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
basicSetup={editorSetup}
|
||||
theme={customTheme}
|
||||
extensions={editorExtensions}
|
||||
value={isFocused ? value : value !== initialValue || isActive ? value : resolved}
|
||||
value={isFocused ? value : value !== initialValue || isEditing ? value : resolved}
|
||||
indentWithTab={false}
|
||||
onChange={handleChange}
|
||||
editable={!disabled && !isActive}
|
||||
onChange={onChange}
|
||||
editable={!disabled && !isEditing}
|
||||
onKeyDown={handleInput}
|
||||
onFocus={handleFocusIn}
|
||||
onBlur={handleFocusOut}
|
||||
|
@ -223,5 +221,3 @@ const RefsInput = forwardRef<ReactCodeMirrorRef, RefsInputInputProps>(
|
|||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default RefsInput;
|
||||
|
|
|
@ -1 +1 @@
|
|||
export { default } from './RefsInput';
|
||||
export { RefsInput } from './RefsInput';
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import clsx from 'clsx';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { HelpTopic } from '@/features/help';
|
||||
|
||||
|
@ -10,11 +13,17 @@ import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
|
|||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { labelReferenceType } from '../../labels';
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { IReference, ReferenceType } from '../../models/language';
|
||||
import {
|
||||
parseEntityReference,
|
||||
parseGrammemes,
|
||||
parseSyntacticReference,
|
||||
supportedGrammeOptions
|
||||
} from '../../models/languageAPI';
|
||||
import { IRSForm } from '../../models/rsform';
|
||||
|
||||
import TabEntityReference from './TabEntityReference';
|
||||
import TabSyntacticReference from './TabSyntacticReference';
|
||||
import { TabEntityReference } from './TabEntityReference';
|
||||
import { TabSyntacticReference } from './TabSyntacticReference';
|
||||
|
||||
export interface IReferenceInputState {
|
||||
type: ReferenceType;
|
||||
|
@ -24,10 +33,25 @@ export interface IReferenceInputState {
|
|||
basePosition: number;
|
||||
}
|
||||
|
||||
const schemaEditReferenceState = z
|
||||
.object({
|
||||
type: z.nativeEnum(ReferenceType),
|
||||
entity: z.object({ entity: z.string(), grams: z.array(z.object({ value: z.string(), label: z.string() })) }),
|
||||
syntactic: z.object({ offset: z.coerce.number(), nominal: z.string() })
|
||||
})
|
||||
.refine(
|
||||
data =>
|
||||
(data.type !== ReferenceType.SYNTACTIC || (data.syntactic.offset !== 0 && data.syntactic.nominal !== '')) &&
|
||||
(data.type !== ReferenceType.ENTITY || (data.entity.entity !== '' && data.entity.grams.length > 0))
|
||||
);
|
||||
|
||||
export type IEditReferenceState = z.infer<typeof schemaEditReferenceState>;
|
||||
|
||||
export interface DlgEditReferenceProps {
|
||||
schema: IRSForm;
|
||||
initial: IReferenceInputState;
|
||||
onSave: (newRef: string) => void;
|
||||
onSave: (newRef: IReference) => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
export enum TabID {
|
||||
|
@ -36,22 +60,45 @@ export enum TabID {
|
|||
}
|
||||
|
||||
function DlgEditReference() {
|
||||
const { initial, onSave } = useDialogsStore(state => state.props as DlgEditReferenceProps);
|
||||
const { initial, onSave, onCancel } = useDialogsStore(state => state.props as DlgEditReferenceProps);
|
||||
const [activeTab, setActiveTab] = useState(initial.type === ReferenceType.ENTITY ? TabID.ENTITY : TabID.SYNTACTIC);
|
||||
const [reference, setReference] = useState('');
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
function handleSubmit() {
|
||||
onSave(reference);
|
||||
return true;
|
||||
const methods = useForm<IEditReferenceState>({
|
||||
resolver: zodResolver(schemaEditReferenceState),
|
||||
defaultValues: {
|
||||
type: initial.type,
|
||||
entity: initEntityReference(initial),
|
||||
syntactic: initSyntacticReference(initial)
|
||||
},
|
||||
mode: 'onChange'
|
||||
});
|
||||
|
||||
function onSubmit(data: IEditReferenceState) {
|
||||
if (data.type === ReferenceType.ENTITY) {
|
||||
onSave({
|
||||
type: data.type,
|
||||
data: {
|
||||
entity: data.entity.entity,
|
||||
form: data.entity.grams.map(gram => gram.value).join(',')
|
||||
}
|
||||
});
|
||||
} else {
|
||||
onSave({ type: data.type, data: data.syntactic });
|
||||
}
|
||||
}
|
||||
|
||||
function handleChangeTab(tab: TabID) {
|
||||
methods.setValue('type', tab === TabID.ENTITY ? ReferenceType.ENTITY : ReferenceType.SYNTACTIC);
|
||||
setActiveTab(tab);
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalForm
|
||||
header='Редактирование ссылки'
|
||||
submitText='Сохранить ссылку'
|
||||
canSubmit={isValid}
|
||||
onSubmit={handleSubmit}
|
||||
canSubmit={methods.formState.isValid}
|
||||
onCancel={onCancel}
|
||||
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
|
||||
className='w-[40rem] px-6 h-[32rem]'
|
||||
helpTopic={HelpTopic.TERM_CONTROL}
|
||||
>
|
||||
|
@ -59,7 +106,7 @@ function DlgEditReference() {
|
|||
selectedTabClassName='clr-selected'
|
||||
className='flex flex-col'
|
||||
selectedIndex={activeTab}
|
||||
onSelect={setActiveTab}
|
||||
onSelect={handleChangeTab}
|
||||
>
|
||||
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none', 'bg-prim-200')}>
|
||||
<TabLabel title='Отсылка на термин в заданной словоформе' label={labelReferenceType(ReferenceType.ENTITY)} />
|
||||
|
@ -69,16 +116,47 @@ function DlgEditReference() {
|
|||
/>
|
||||
</TabList>
|
||||
|
||||
<FormProvider {...methods}>
|
||||
<TabPanel>
|
||||
<TabEntityReference onChangeReference={setReference} onChangeValid={setIsValid} />
|
||||
<TabEntityReference />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel>
|
||||
<TabSyntacticReference onChangeReference={setReference} onChangeValid={setIsValid} />
|
||||
<TabSyntacticReference />
|
||||
</TabPanel>
|
||||
</FormProvider>
|
||||
</Tabs>
|
||||
</ModalForm>
|
||||
);
|
||||
}
|
||||
|
||||
export default DlgEditReference;
|
||||
|
||||
// ====== Internals =========
|
||||
function initEntityReference(initial: IReferenceInputState) {
|
||||
if (!initial.refRaw || initial.type === ReferenceType.SYNTACTIC) {
|
||||
return {
|
||||
entity: '',
|
||||
grams: []
|
||||
};
|
||||
} else {
|
||||
const ref = parseEntityReference(initial.refRaw);
|
||||
const grams = parseGrammemes(ref.form);
|
||||
const supported = supportedGrammeOptions.filter(data => grams.includes(data.value));
|
||||
return {
|
||||
entity: ref.entity,
|
||||
grams: supported
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function initSyntacticReference(initial: IReferenceInputState) {
|
||||
if (!initial.refRaw || initial.type === ReferenceType.ENTITY) {
|
||||
return {
|
||||
offset: 1,
|
||||
nominal: initial.text ?? ''
|
||||
};
|
||||
} else {
|
||||
return parseSyntacticReference(initial.refRaw);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { Label, TextInput } from '@/components/Input';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
@ -8,51 +8,22 @@ import { useDialogsStore } from '@/stores/dialogs';
|
|||
import { PickConstituenta } from '../../components/PickConstituenta';
|
||||
import SelectMultiGrammeme from '../../components/SelectMultiGrammeme';
|
||||
import SelectWordForm from '../../components/SelectWordForm';
|
||||
import { IGrammemeOption, ReferenceType } from '../../models/language';
|
||||
import { parseEntityReference, parseGrammemes, supportedGrammeOptions } from '../../models/languageAPI';
|
||||
import { IConstituenta } from '../../models/rsform';
|
||||
import { matchConstituenta } from '../../models/rsformAPI';
|
||||
import { CstMatchMode } from '../../stores/cstSearch';
|
||||
|
||||
import { DlgEditReferenceProps } from './DlgEditReference';
|
||||
import { DlgEditReferenceProps, IEditReferenceState } from './DlgEditReference';
|
||||
|
||||
interface TabEntityReferenceProps {
|
||||
onChangeValid: (newValue: boolean) => void;
|
||||
onChangeReference: (newValue: string) => void;
|
||||
}
|
||||
|
||||
function TabEntityReference({ onChangeValid, onChangeReference }: TabEntityReferenceProps) {
|
||||
export function TabEntityReference() {
|
||||
const { schema, initial } = useDialogsStore(state => state.props as DlgEditReferenceProps);
|
||||
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
|
||||
const [alias, setAlias] = useState('');
|
||||
const [term, setTerm] = useState('');
|
||||
const [selectedGrams, setSelectedGrams] = useState<IGrammemeOption[]>([]);
|
||||
const { setValue, control, register } = useFormContext<IEditReferenceState>();
|
||||
const alias = useWatch({ control, name: 'entity.entity' });
|
||||
|
||||
// Initialization
|
||||
useEffect(() => {
|
||||
if (!!initial.refRaw && initial.type === ReferenceType.ENTITY) {
|
||||
const ref = parseEntityReference(initial.refRaw);
|
||||
setAlias(ref.entity);
|
||||
const grams = parseGrammemes(ref.form);
|
||||
setSelectedGrams(supportedGrammeOptions.filter(data => grams.includes(data.value)));
|
||||
}
|
||||
}, [initial, schema.items]);
|
||||
|
||||
// Produce result
|
||||
useEffect(() => {
|
||||
onChangeValid(alias !== '' && selectedGrams.length > 0);
|
||||
onChangeReference(`@{${alias}|${selectedGrams.map(gram => gram.value).join(',')}}`);
|
||||
}, [alias, selectedGrams, onChangeValid, onChangeReference]);
|
||||
|
||||
// Update term when alias changes
|
||||
useEffect(() => {
|
||||
const cst = schema.cstByAlias.get(alias);
|
||||
setTerm(cst?.term_resolved ?? '');
|
||||
}, [alias, term, schema]);
|
||||
const selectedCst = schema.cstByAlias.get(alias);
|
||||
const term = selectedCst?.term_resolved ?? '';
|
||||
|
||||
function handleSelectConstituenta(cst: IConstituenta) {
|
||||
setAlias(cst.alias);
|
||||
setSelectedCst(cst);
|
||||
setValue('entity.entity', cst.alias);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -76,8 +47,7 @@ function TabEntityReference({ onChangeValid, onChangeReference }: TabEntityRefer
|
|||
label='Конституента'
|
||||
placeholder='Имя'
|
||||
className='w-[11rem]'
|
||||
value={alias}
|
||||
onChange={event => setAlias(event.target.value)}
|
||||
{...register('entity.entity')}
|
||||
/>
|
||||
<TextInput
|
||||
id='dlg_reference_term'
|
||||
|
@ -91,21 +61,29 @@ function TabEntityReference({ onChangeValid, onChangeReference }: TabEntityRefer
|
|||
/>
|
||||
</div>
|
||||
|
||||
<SelectWordForm value={selectedGrams} onChange={setSelectedGrams} />
|
||||
<Controller
|
||||
control={control}
|
||||
name='entity.grams'
|
||||
render={({ field }) => <SelectWordForm value={field.value} onChange={field.onChange} />}
|
||||
/>
|
||||
|
||||
<div className='flex items-center gap-4'>
|
||||
<Label text='Словоформа' />
|
||||
<Controller
|
||||
control={control}
|
||||
name='entity.grams'
|
||||
render={({ field }) => (
|
||||
<SelectMultiGrammeme
|
||||
id='dlg_reference_grammemes'
|
||||
placeholder='Выберите граммемы'
|
||||
className='flex-grow'
|
||||
menuPlacement='top'
|
||||
value={selectedGrams}
|
||||
onChange={setSelectedGrams}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TabEntityReference;
|
||||
|
|
|
@ -1,24 +1,16 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { TextInput } from '@/components/Input';
|
||||
import { useDialogsStore } from '@/stores/dialogs';
|
||||
|
||||
import { ReferenceType } from '../../models/language';
|
||||
import { parseSyntacticReference } from '../../models/languageAPI';
|
||||
import { DlgEditReferenceProps, IEditReferenceState } from './DlgEditReference';
|
||||
|
||||
import { DlgEditReferenceProps } from './DlgEditReference';
|
||||
|
||||
interface TabSyntacticReferenceProps {
|
||||
onChangeValid: (newValue: boolean) => void;
|
||||
onChangeReference: (newValue: string) => void;
|
||||
}
|
||||
|
||||
function TabSyntacticReference({ onChangeValid, onChangeReference }: TabSyntacticReferenceProps) {
|
||||
export function TabSyntacticReference() {
|
||||
const { initial } = useDialogsStore(state => state.props as DlgEditReferenceProps);
|
||||
const [nominal, setNominal] = useState('');
|
||||
const [offset, setOffset] = useState(1);
|
||||
const { control, register } = useFormContext<IEditReferenceState>();
|
||||
const offset = useWatch({ control, name: 'syntactic.offset' });
|
||||
|
||||
const mainLink = (() => {
|
||||
const position = offset > 0 ? initial.basePosition + (offset - 1) : initial.basePosition + offset;
|
||||
|
@ -29,21 +21,6 @@ function TabSyntacticReference({ onChangeValid, onChangeReference }: TabSyntacti
|
|||
}
|
||||
})();
|
||||
|
||||
useEffect(() => {
|
||||
if (initial.refRaw && initial.type === ReferenceType.SYNTACTIC) {
|
||||
const ref = parseSyntacticReference(initial.refRaw);
|
||||
setOffset(ref.offset);
|
||||
setNominal(ref.nominal);
|
||||
} else {
|
||||
setNominal(initial.text ?? '');
|
||||
}
|
||||
}, [initial]);
|
||||
|
||||
useEffect(() => {
|
||||
onChangeValid(nominal !== '' && offset !== 0);
|
||||
onChangeReference(`@{${offset}|${nominal}}`);
|
||||
}, [nominal, offset, onChangeValid, onChangeReference]);
|
||||
|
||||
return (
|
||||
<div className='cc-fade-in flex flex-col gap-2'>
|
||||
<TextInput
|
||||
|
@ -52,8 +29,7 @@ function TabSyntacticReference({ onChangeValid, onChangeReference }: TabSyntacti
|
|||
dense
|
||||
label='Смещение'
|
||||
className='max-w-[10rem]'
|
||||
value={offset}
|
||||
onChange={event => setOffset(event.target.valueAsNumber)}
|
||||
{...register('syntactic.offset')}
|
||||
/>
|
||||
<TextInput
|
||||
id='dlg_main_ref'
|
||||
|
@ -68,11 +44,8 @@ function TabSyntacticReference({ onChangeValid, onChangeReference }: TabSyntacti
|
|||
spellCheck
|
||||
label='Начальная форма'
|
||||
placeholder='зависимое слово в начальной форме'
|
||||
value={nominal}
|
||||
onChange={event => setNominal(event.target.value)}
|
||||
{...register('syntactic.nominal')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TabSyntacticReference;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
* Module: Natural language model declarations.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Represents single unit of language Morphology.
|
||||
*/
|
||||
|
@ -266,10 +268,15 @@ export interface ITextPosition {
|
|||
finish: number;
|
||||
}
|
||||
|
||||
export const schemaReference = z.object({
|
||||
type: z.nativeEnum(ReferenceType),
|
||||
data: z.union([
|
||||
z.object({ entity: z.string(), form: z.string() }),
|
||||
z.object({ offset: z.number(), nominal: z.string() })
|
||||
])
|
||||
});
|
||||
|
||||
/**
|
||||
* Represents abstract reference data.
|
||||
*/
|
||||
export interface IReference {
|
||||
type: ReferenceType;
|
||||
data: IEntityReference | ISyntacticReference;
|
||||
}
|
||||
export type IReference = z.infer<typeof schemaReference>;
|
||||
|
|
|
@ -10,9 +10,11 @@ import {
|
|||
GrammemeGroups,
|
||||
IEntityReference,
|
||||
IGrammemeOption,
|
||||
IReference,
|
||||
ISyntacticReference,
|
||||
IWordForm,
|
||||
NounGrams,
|
||||
ReferenceType,
|
||||
supportedGrammemes,
|
||||
VerbGrams
|
||||
} from './language';
|
||||
|
@ -128,3 +130,19 @@ export const supportedGrammeOptions: IGrammemeOption[] = supportedGrammemes.map(
|
|||
value: gram,
|
||||
label: labelGrammeme(gram)
|
||||
}));
|
||||
|
||||
/**
|
||||
* Transforms {@link IReference} to string representation.
|
||||
*/
|
||||
export function referenceToString(ref: IReference): string {
|
||||
switch (ref.type) {
|
||||
case ReferenceType.ENTITY: {
|
||||
const entity = ref.data as IEntityReference;
|
||||
return `@{${entity.entity}|${entity.form}}`;
|
||||
}
|
||||
case ReferenceType.SYNTACTIC: {
|
||||
const syntactic = ref.data as ISyntacticReference;
|
||||
return `@{${syntactic.offset}|${syntactic.nominal}}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import { errorMsg } from '@/utils/labels';
|
|||
import { ICstUpdateDTO, schemaCstUpdate } from '../../../backend/types';
|
||||
import { useCstUpdate } from '../../../backend/useCstUpdate';
|
||||
import { useMutatingRSForm } from '../../../backend/useMutatingRSForm';
|
||||
import RefsInput from '../../../components/RefsInput';
|
||||
import { RefsInput } from '../../../components/RefsInput';
|
||||
import { labelCstTypification, labelTypification } from '../../../labels';
|
||||
import { CstType, IConstituenta, IRSForm } from '../../../models/rsform';
|
||||
import { isBaseSet, isBasicConcept, isFunctional } from '../../../models/rsformAPI';
|
||||
|
@ -125,7 +125,7 @@ function FormConstituenta({ disabled, id, toggleReset, schema, activeCst, onOpen
|
|||
placeholder='Обозначение для текстовых определений'
|
||||
schema={schema}
|
||||
onOpenEdit={onOpenEdit}
|
||||
value={field.value}
|
||||
value={field.value ?? ''}
|
||||
initialValue={activeCst.term_raw}
|
||||
resolved={activeCst.term_resolved}
|
||||
disabled={disabled}
|
||||
|
@ -189,7 +189,7 @@ function FormConstituenta({ disabled, id, toggleReset, schema, activeCst, onOpen
|
|||
maxHeight='8rem'
|
||||
schema={schema}
|
||||
onOpenEdit={onOpenEdit}
|
||||
value={field.value}
|
||||
value={field.value ?? ''}
|
||||
initialValue={activeCst.definition_raw}
|
||||
resolved={activeCst.definition_resolved}
|
||||
disabled={disabled}
|
||||
|
|
|
@ -52,6 +52,10 @@ export enum DialogType {
|
|||
UPLOAD_RSFORM
|
||||
}
|
||||
|
||||
export interface GenericDialogProps {
|
||||
onHide?: () => void;
|
||||
}
|
||||
|
||||
interface DialogsStore {
|
||||
active: DialogType | undefined;
|
||||
props: unknown;
|
||||
|
@ -85,7 +89,12 @@ interface DialogsStore {
|
|||
export const useDialogsStore = create<DialogsStore>()(set => ({
|
||||
active: undefined,
|
||||
props: undefined,
|
||||
hideDialog: () => set({ active: undefined, props: undefined }),
|
||||
hideDialog: () => {
|
||||
set(state => {
|
||||
(state.props as GenericDialogProps | undefined)?.onHide?.();
|
||||
return { active: undefined, props: undefined };
|
||||
});
|
||||
},
|
||||
|
||||
showCstTemplate: props => set({ active: DialogType.CONSTITUENTA_TEMPLATE, props: props }),
|
||||
showCreateCst: props => set({ active: DialogType.CREATE_CONSTITUENTA, props: props }),
|
||||
|
|
Loading…
Reference in New Issue
Block a user