F: Rework cst create and template dialogs

This commit is contained in:
Ivan 2025-02-14 02:41:31 +03:00
parent 3c92e07d0f
commit d5854366a9
9 changed files with 351 additions and 320 deletions

View File

@ -4,7 +4,7 @@ import { ILibraryItemReference, ILibraryItemVersioned } from '@/features/library
import { errorMsg } from '@/utils/labels';
import { CstType, IConstituentaMeta, IInheritanceInfo, TermForm } from '../models/rsform';
import { CstType, IConstituentaMeta, IInheritanceInfo } from '../models/rsform';
import { IArgumentInfo, ParsingStatus, ValueClass } from '../models/rslang';
/**
@ -42,17 +42,21 @@ export interface IRSFormUploadDTO {
/**
* Represents {@link IConstituenta} data, used in creation process.
*/
export interface ICstCreateDTO {
alias: string;
cst_type: CstType;
definition_raw: string;
term_raw: string;
convention: string;
definition_formal: string;
term_forms: TermForm[];
export const schemaCstCreate = z.object({
cst_type: z.nativeEnum(CstType),
alias: z.string().nonempty(errorMsg.requiredField),
convention: z.string(),
definition_formal: z.string(),
definition_raw: z.string(),
term_raw: z.string(),
term_forms: z.array(z.object({ text: z.string(), tags: z.string() })),
insert_after: z.number().nullable()
});
insert_after: number | null;
}
/**
* Represents {@link IConstituenta} data, used in creation process.
*/
export type ICstCreateDTO = z.infer<typeof schemaCstCreate>;
/**
* Represents data response when creating {@link IConstituenta}.

View File

@ -1,54 +1,53 @@
'use client';
import { useState } from 'react';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { ModalForm } from '@/components/Modal';
import usePartialUpdate from '@/hooks/usePartialUpdate';
import { useDialogsStore } from '@/stores/dialogs';
import { errorMsg } from '@/utils/labels';
import { ICstCreateDTO } from '../../backend/types';
import { CstType, IRSForm } from '../../models/rsform';
import { generateAlias } from '../../models/rsformAPI';
import { ICstCreateDTO, schemaCstCreate } from '../../backend/types';
import { useCstCreate } from '../../backend/useCstCreate';
import { IConstituentaMeta, IRSForm } from '../../models/rsform';
import { validateNewAlias } from '../../models/rsformAPI';
import FormCreateCst from './FormCreateCst';
export interface DlgCreateCstProps {
initial?: ICstCreateDTO;
initial: ICstCreateDTO;
schema: IRSForm;
onCreate: (data: ICstCreateDTO) => void;
onCreate: (data: IConstituentaMeta) => void;
}
function DlgCreateCst() {
const { initial, schema, onCreate } = useDialogsStore(state => state.props as DlgCreateCstProps);
const { cstCreate } = useCstCreate();
const [validated, setValidated] = useState(false);
const [cstData, updateCstData] = usePartialUpdate(
initial || {
cst_type: CstType.BASE,
insert_after: null,
alias: generateAlias(CstType.BASE, schema),
convention: '',
definition_formal: '',
definition_raw: '',
term_raw: '',
term_forms: []
}
);
const methods = useForm<ICstCreateDTO>({
resolver: zodResolver(schemaCstCreate),
defaultValues: { ...initial }
});
const alias = useWatch({ control: methods.control, name: 'alias' });
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
const isValid = alias !== initial.alias && validateNewAlias(alias, cst_type, schema);
const handleSubmit = () => {
onCreate(cstData);
return true;
};
function onSubmit(data: ICstCreateDTO) {
return cstCreate({ itemID: schema.id, data }).then(onCreate);
}
return (
<ModalForm
header='Создание конституенты'
canSubmit={validated}
onSubmit={handleSubmit}
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
submitInvalidTooltip={errorMsg.aliasInvalid}
submitText='Создать'
className='cc-column w-[35rem] max-h-[30rem] py-2 px-6'
>
<FormCreateCst schema={schema} state={cstData} partialUpdate={updateCstData} setValidated={setValidated} />
<FormProvider {...methods}>
<FormCreateCst schema={schema} />
</FormProvider>
</ModalForm>
);
}

View File

@ -1,6 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { Controller, useFormContext, useWatch } from 'react-hook-form';
import clsx from 'clsx';
import { BadgeHelp, HelpTopic } from '@/features/help';
@ -12,48 +13,45 @@ import { ICstCreateDTO } from '../../backend/types';
import RSInput from '../../components/RSInput';
import { SelectCstType } from '../../components/SelectCstType';
import { CstType, IRSForm } from '../../models/rsform';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional, validateNewAlias } from '../../models/rsformAPI';
import { generateAlias, isBaseSet, isBasicConcept, isFunctional } from '../../models/rsformAPI';
interface FormCreateCstProps {
schema: IRSForm;
state: ICstCreateDTO;
partialUpdate: React.Dispatch<Partial<ICstCreateDTO>>;
setValidated?: React.Dispatch<React.SetStateAction<boolean>>;
}
function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreateCstProps) {
function FormCreateCst({ schema }: FormCreateCstProps) {
const {
setValue,
register,
control,
formState: { errors }
} = useFormContext<ICstCreateDTO>();
const [forceComment, setForceComment] = useState(false);
const isBasic = isBasicConcept(state.cst_type);
const isElementary = isBaseSet(state.cst_type);
const showConvention = !!state.convention || forceComment || isBasic;
useEffect(() => {
setForceComment(false);
}, [state.cst_type, partialUpdate, schema]);
useEffect(() => {
if (setValidated) {
setValidated(validateNewAlias(state.alias, state.cst_type, schema));
}
}, [state.alias, state.cst_type, schema, setValidated]);
const cst_type = useWatch({ control, name: 'cst_type' });
const convention = useWatch({ control, name: 'convention' });
const isBasic = isBasicConcept(cst_type);
const isElementary = isBaseSet(cst_type);
const isFunction = isFunctional(cst_type);
const showConvention = !!convention || forceComment || isBasic;
function handleTypeChange(target: CstType) {
return partialUpdate({ cst_type: target, alias: generateAlias(target, schema) });
setValue('cst_type', target);
setValue('alias', generateAlias(target, schema));
setForceComment(false);
}
return (
<>
<div className='flex items-center self-center gap-3'>
<SelectCstType id='dlg_cst_type' className='w-[16rem]' value={state.cst_type} onChange={handleTypeChange} />
<SelectCstType id='dlg_cst_type' className='w-[16rem]' value={cst_type} onChange={handleTypeChange} />
<TextInput
id='dlg_cst_alias'
dense
label='Имя'
className='w-[7rem]'
value={state.alias}
onChange={event => partialUpdate({ alias: event.target.value })}
{...register('alias')}
error={errors.alias}
/>
<BadgeHelp
topic={HelpTopic.CC_CONSTITUENTA}
@ -69,42 +67,58 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
label='Термин'
placeholder='Обозначение для текстовых определений'
className='max-h-[3.6rem]'
value={state.term_raw}
onChange={event => partialUpdate({ term_raw: event.target.value })}
{...register('term_raw')}
error={errors.term_raw}
/>
{!!state.definition_formal || !isElementary ? (
<RSInput
id='dlg_cst_expression'
noTooltip
label={
state.cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunctional(state.cst_type)
? 'Определение функции'
: 'Формальное определение'
}
placeholder={
state.cst_type !== CstType.STRUCTURED ? 'Родоструктурное выражение' : 'Типизация родовой структуры'
}
value={state.definition_formal}
onChange={value => partialUpdate({ definition_formal: value })}
schema={schema}
/>
) : null}
<Controller
control={control}
name='definition_formal'
render={({ field }) =>
!!field.value || !isElementary ? (
<RSInput
id='dlg_cst_expression'
noTooltip
label={
cst_type === CstType.STRUCTURED
? 'Область определения'
: isFunction
? 'Определение функции'
: 'Формальное определение'
}
placeholder={
cst_type !== CstType.STRUCTURED ? 'Родоструктурное выражение' : 'Типизация родовой структуры'
}
value={field.value}
onChange={field.onChange}
schema={schema}
/>
) : (
<></>
)
}
/>
{!!state.definition_raw || !isElementary ? (
<TextArea
id='dlg_cst_definition'
spellCheck
fitContent
label='Текстовое определение'
placeholder='Текстовая интерпретация формального выражения'
className='max-h-[3.6rem]'
value={state.definition_raw}
onChange={event => partialUpdate({ definition_raw: event.target.value })}
/>
) : null}
<Controller
control={control}
name='definition_raw'
render={({ field }) =>
!!field.value || !isElementary ? (
<TextArea
id='dlg_cst_definition'
spellCheck
fitContent
label='Текстовое определение'
placeholder='Текстовая интерпретация формального выражения'
className='max-h-[3.6rem]'
value={field.value}
onChange={field.onChange}
/>
) : (
<></>
)
}
/>
{!showConvention ? (
<button
@ -124,8 +138,7 @@ function FormCreateCst({ schema, state, partialUpdate, setValidated }: FormCreat
label={isBasic ? 'Конвенция' : 'Комментарий'}
placeholder={isBasic ? 'Договоренность об интерпретации' : 'Пояснение разработчика'}
className='max-h-[5.4rem]'
value={state.convention}
onChange={event => partialUpdate({ convention: event.target.value })}
{...register('convention')}
/>
)}
</>

View File

@ -1,6 +1,8 @@
'use client';
import { Suspense, useEffect, useState } from 'react';
import { Suspense, useState } from 'react';
import { FormProvider, useForm, useWatch } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import clsx from 'clsx';
import { HelpTopic } from '@/features/help';
@ -8,23 +10,21 @@ import { HelpTopic } from '@/features/help';
import { Loader } from '@/components/Loader';
import { ModalForm } from '@/components/Modal';
import { TabLabel, TabList, TabPanel, Tabs } from '@/components/Tabs';
import usePartialUpdate from '@/hooks/usePartialUpdate';
import { useDialogsStore } from '@/stores/dialogs';
import { promptText } from '@/utils/labels';
import { ICstCreateDTO } from '../../backend/types';
import { useRSForm } from '../../backend/useRSForm';
import { CstType, IRSForm } from '../../models/rsform';
import { ICstCreateDTO, schemaCstCreate } from '../../backend/types';
import { useCstCreate } from '../../backend/useCstCreate';
import { CstType, IConstituentaMeta, IRSForm } from '../../models/rsform';
import { generateAlias, validateNewAlias } from '../../models/rsformAPI';
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslangAPI';
import FormCreateCst from '../DlgCreateCst/FormCreateCst';
import TabArguments, { IArgumentsState } from './TabArguments';
import TabTemplate, { ITemplateState } from './TabTemplate';
import TabArguments from './TabArguments';
import TabTemplate from './TabTemplate';
import { TemplateState } from './TemplateContext';
export interface DlgCstTemplateProps {
schema: IRSForm;
onCreate: (data: ICstCreateDTO) => void;
onCreate: (data: IConstituentaMeta) => void;
insertAfter?: number;
}
@ -36,98 +36,38 @@ export enum TabID {
function DlgCstTemplate() {
const { schema, onCreate, insertAfter } = useDialogsStore(state => state.props as DlgCstTemplateProps);
const { cstCreate } = useCstCreate();
const methods = useForm<ICstCreateDTO>({
resolver: zodResolver(schemaCstCreate),
defaultValues: {
cst_type: CstType.TERM,
insert_after: insertAfter ?? null,
alias: generateAlias(CstType.TERM, schema),
convention: '',
definition_formal: '',
definition_raw: '',
term_raw: '',
term_forms: []
}
});
const alias = useWatch({ control: methods.control, name: 'alias' });
const cst_type = useWatch({ control: methods.control, name: 'cst_type' });
const isValid = validateNewAlias(alias, cst_type, schema);
const [activeTab, setActiveTab] = useState(TabID.TEMPLATE);
const [template, updateTemplate] = usePartialUpdate<ITemplateState>({});
const { schema: templateSchema } = useRSForm({ itemID: template.templateID });
const [substitutes, updateSubstitutes] = usePartialUpdate<IArgumentsState>({
definition: '',
arguments: []
});
const [constituenta, updateConstituenta] = usePartialUpdate<ICstCreateDTO>({
cst_type: CstType.TERM,
insert_after: insertAfter ?? null,
alias: generateAlias(CstType.TERM, schema),
convention: '',
definition_formal: '',
definition_raw: '',
term_raw: '',
term_forms: []
});
const [validated, setValidated] = useState(false);
function handleSubmit() {
onCreate(constituenta);
return true;
function onSubmit(data: ICstCreateDTO) {
return cstCreate({ itemID: schema.id, data }).then(onCreate);
}
function handlePrompt(): boolean {
const definedSomeArgs = substitutes.arguments.some(arg => !!arg.value);
if (!definedSomeArgs && !window.confirm(promptText.templateUndefined)) {
return false;
}
return true;
}
useEffect(() => {
if (!template.prototype) {
updateConstituenta({
definition_raw: '',
definition_formal: '',
term_raw: ''
});
updateSubstitutes({
definition: '',
arguments: []
});
} else {
updateConstituenta({
cst_type: template.prototype.cst_type,
alias: generateAlias(template.prototype.cst_type, schema),
definition_raw: template.prototype.definition_raw,
definition_formal: template.prototype.definition_formal,
term_raw: template.prototype.term_raw
});
updateSubstitutes({
definition: template.prototype.definition_formal,
arguments: template.prototype.parse.args.map(arg => ({
alias: arg.alias,
typification: arg.typification,
value: ''
}))
});
}
}, [template.prototype, updateConstituenta, updateSubstitutes, schema]);
useEffect(() => {
if (substitutes.arguments.length === 0 || !template.prototype) {
return;
}
const definition = substituteTemplateArgs(template.prototype.definition_formal, substitutes.arguments);
const type = inferTemplatedType(template.prototype.cst_type, substitutes.arguments);
updateConstituenta({
cst_type: type,
alias: generateAlias(type, schema),
definition_formal: definition
});
updateSubstitutes({
definition: definition
});
}, [substitutes.arguments, template.prototype, updateConstituenta, updateSubstitutes, schema]);
useEffect(() => {
setValidated(!!template.prototype && validateNewAlias(constituenta.alias, constituenta.cst_type, schema));
}, [constituenta.alias, constituenta.cst_type, schema, template.prototype]);
return (
<ModalForm
header='Создание конституенты из шаблона'
submitText='Создать'
className='w-[43rem] h-[35rem] px-6'
canSubmit={validated}
beforeSubmit={handlePrompt}
onSubmit={handleSubmit}
canSubmit={isValid}
onSubmit={event => void methods.handleSubmit(onSubmit)(event)}
helpTopic={HelpTopic.RSL_TEMPLATES}
>
<Tabs
@ -142,21 +82,25 @@ function DlgCstTemplate() {
<TabLabel label='Конституента' title='Редактирование конституенты' className='w-[8rem]' />
</TabList>
<TabPanel>
<Suspense fallback={<Loader />}>
<TabTemplate state={template} partialUpdate={updateTemplate} templateSchema={templateSchema} />
</Suspense>
</TabPanel>
<FormProvider {...methods}>
<TemplateState>
<TabPanel>
<Suspense fallback={<Loader />}>
<TabTemplate />
</Suspense>
</TabPanel>
<TabPanel>
<TabArguments schema={schema} state={substitutes} partialUpdate={updateSubstitutes} />
</TabPanel>
<TabPanel>
<TabArguments />
</TabPanel>
<TabPanel>
<div className='cc-fade-in cc-column'>
<FormCreateCst state={constituenta} partialUpdate={updateConstituenta} schema={schema} />
</div>
</TabPanel>
<TabPanel>
<div className='cc-fade-in cc-column'>
<FormCreateCst schema={schema} />
</div>
</TabPanel>
</TemplateState>
</FormProvider>
</Tabs>
</ModalForm>
);

View File

@ -1,6 +1,7 @@
'use client';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import { createColumnHelper } from '@tanstack/react-table';
import clsx from 'clsx';
@ -8,37 +9,32 @@ import { MiniButton } from '@/components/Control';
import DataTable, { IConditionalStyle } from '@/components/DataTable';
import { IconAccept, IconRemove, IconReset } from '@/components/Icons';
import { NoData } from '@/components/View';
import { useDialogsStore } from '@/stores/dialogs';
import { APP_COLORS } from '@/styling/colors';
import { ICstCreateDTO } from '../../backend/types';
import { PickConstituenta } from '../../components/PickConstituenta';
import RSInput from '../../components/RSInput';
import { IConstituenta, IRSForm } from '../../models/rsform';
import { IConstituenta } from '../../models/rsform';
import { IArgumentValue } from '../../models/rslang';
interface TabArgumentsProps {
state: IArgumentsState;
schema: IRSForm;
partialUpdate: React.Dispatch<Partial<IArgumentsState>>;
}
export interface IArgumentsState {
arguments: IArgumentValue[];
definition: string;
}
import { DlgCstTemplateProps } from './DlgCstTemplate';
import { useTemplateContext } from './TemplateContext';
const argumentsHelper = createColumnHelper<IArgumentValue>();
function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(undefined);
const [argumentValue, setArgumentValue] = useState('');
const isModified = selectedArgument && argumentValue !== selectedArgument.value;
function TabArguments() {
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
const { control } = useFormContext<ICstCreateDTO>();
const { args, onChangeArguments } = useTemplateContext();
const definition = useWatch({ control, name: 'definition_formal' });
useEffect(() => {
if (!selectedArgument && state.arguments.length > 0) {
setSelectedArgument(state.arguments[0]);
}
}, [state.arguments, selectedArgument]);
const [selectedCst, setSelectedCst] = useState<IConstituenta | undefined>(undefined);
const [selectedArgument, setSelectedArgument] = useState<IArgumentValue | undefined>(
args.length > 0 ? args[0] : undefined
);
const [argumentValue, setArgumentValue] = useState('');
function handleSelectArgument(arg: IArgumentValue) {
setSelectedArgument(arg);
@ -54,9 +50,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
function handleClearArgument(target: IArgumentValue) {
const newArg = { ...target, value: '' };
partialUpdate({
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
});
onChangeArguments(args.map(arg => (arg.alias !== target.alias ? arg : newArg)));
setSelectedArgument(newArg);
}
@ -64,11 +58,9 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
setArgumentValue(selectedArgument?.value ?? '');
}
function handleAssignArgument(target: IArgumentValue, value: string) {
const newArg = { ...target, value: value };
partialUpdate({
arguments: state.arguments.map(arg => (arg.alias !== target.alias ? arg : newArg))
});
function handleAssignArgument(target: IArgumentValue, argValue: string) {
const newArg = { ...target, value: argValue };
onChangeArguments(args.map(arg => (arg.alias !== target.alias ? arg : newArg)));
setSelectedArgument(newArg);
}
@ -139,7 +131,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
'border',
'select-none'
)}
data={state.arguments}
data={args}
columns={columns}
conditionalRowStyles={conditionalRowStyles}
noDataComponent={<NoData className='min-h-[3.6rem]'>Аргументы отсутствуют</NoData>}
@ -179,7 +171,6 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
title='Очистить поле'
noHover
className='py-0'
disabled={!isModified}
onClick={handleReset}
icon={<IconReset size='1.5rem' className='icon-primary' />}
/>
@ -201,7 +192,7 @@ function TabArguments({ state, schema, partialUpdate }: TabArgumentsProps) {
placeholder='Итоговое определение'
className='mt-[1.2rem]'
height='5.1rem'
value={state.definition}
value={definition}
/>
</div>
);

View File

@ -1,36 +1,44 @@
'use client';
import { Dispatch, useEffect, useState } from 'react';
import { useTemplatesSuspense } from '@/features/library';
import { SelectSingle, TextArea } from '@/components/Input';
import { useRSForm } from '../../backend/useRSForm';
import { PickConstituenta } from '../../components/PickConstituenta';
import RSInput from '../../components/RSInput';
import { CATEGORY_CST_TYPE, IConstituenta, IRSForm } from '../../models/rsform';
import { CATEGORY_CST_TYPE } from '../../models/rsform';
import { applyFilterCategory } from '../../models/rsformAPI';
export interface ITemplateState {
templateID?: number;
prototype?: IConstituenta;
filterCategory?: IConstituenta;
}
import { useTemplateContext } from './TemplateContext';
interface TabTemplateProps {
state: ITemplateState;
partialUpdate: Dispatch<Partial<ITemplateState>>;
templateSchema?: IRSForm;
}
function TabTemplate() {
const {
templateID, //
filterCategory,
prototype,
onChangePrototype,
onChangeTemplateID,
onChangeFilterCategory
} = useTemplateContext();
function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps) {
const { templates } = useTemplatesSuspense();
const { schema: templateSchema } = useRSForm({ itemID: templateID });
const [filteredData, setFilteredData] = useState<IConstituenta[]>([]);
if (!templateID) {
onChangeTemplateID(templates[0].id);
return null;
}
const prototypeInfo = !state.prototype
const filteredData = !templateSchema
? []
: !filterCategory
? templateSchema.items
: applyFilterCategory(filterCategory, templateSchema);
const prototypeInfo = !prototype
? ''
: `${state.prototype?.term_raw}${state.prototype?.definition_raw ? `${state.prototype?.definition_raw}` : ''}`;
: `${prototype?.term_raw}${prototype?.definition_raw ? `${prototype?.definition_raw}` : ''}`;
const templateSelector = templates.map(template => ({
value: template.id,
@ -46,23 +54,6 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
label: cst.term_raw
}));
useEffect(() => {
if (templates.length > 0 && !state.templateID) {
partialUpdate({ templateID: templates[0].id });
}
}, [templates, state.templateID, partialUpdate]);
useEffect(() => {
if (!templateSchema) {
return;
}
let data = templateSchema.items;
if (state.filterCategory) {
data = applyFilterCategory(state.filterCategory, templateSchema);
}
setFilteredData(data);
}, [state.filterCategory, templateSchema]);
return (
<div className='cc-fade-in'>
<div className='flex border-t border-x rounded-t-md clr-input'>
@ -71,12 +62,8 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
placeholder='Источник'
className='w-[12rem]'
options={templateSelector}
value={
state.templateID
? { value: state.templateID, label: templates.find(item => item.id == state.templateID)!.title }
: null
}
onChange={data => partialUpdate({ templateID: data ? data.value : undefined })}
value={templateID ? { value: templateID, label: templates.find(item => item.id == templateID)!.title } : null}
onChange={data => onChangeTemplateID(data ? data.value : undefined)}
/>
<SelectSingle
noBorder
@ -85,24 +72,22 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
className='flex-grow ml-1 border-none'
options={categorySelector}
value={
state.filterCategory && templateSchema
filterCategory && templateSchema
? {
value: state.filterCategory.id,
label: state.filterCategory.term_raw
value: filterCategory.id,
label: filterCategory.term_raw
}
: null
}
onChange={data =>
partialUpdate({ filterCategory: data ? templateSchema?.cstByID.get(data?.value) : undefined })
}
onChange={data => onChangeFilterCategory(data ? templateSchema?.cstByID.get(data?.value) : undefined)}
isClearable
/>
</div>
<PickConstituenta
id='dlg_template_picker'
value={state.prototype}
value={prototype}
items={filteredData}
onChange={cst => partialUpdate({ prototype: cst })}
onChange={onChangePrototype}
className='rounded-t-none'
rows={8}
/>
@ -121,7 +106,7 @@ function TabTemplate({ state, partialUpdate, templateSchema }: TabTemplateProps)
disabled
placeholder='Выберите шаблон из списка'
height='5.1rem'
value={state.prototype?.definition_formal}
value={prototype?.definition_formal}
/>
</div>
);

View File

@ -0,0 +1,95 @@
'use client';
import { createContext, useContext, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useDialogsStore } from '@/stores/dialogs';
import { ICstCreateDTO } from '../../backend/types';
import { IConstituenta } from '../../models/rsform';
import { generateAlias } from '../../models/rsformAPI';
import { IArgumentValue } from '../../models/rslang';
import { inferTemplatedType, substituteTemplateArgs } from '../../models/rslangAPI';
import { DlgCstTemplateProps } from './DlgCstTemplate';
export interface ITemplateContext {
args: IArgumentValue[];
prototype?: IConstituenta;
templateID?: number;
filterCategory?: IConstituenta;
onChangeArguments: (newArgs: IArgumentValue[]) => void;
onChangePrototype: (newPrototype: IConstituenta) => void;
onChangeTemplateID: (newTemplateID: number | undefined) => void;
onChangeFilterCategory: (newFilterCategory: IConstituenta | undefined) => void;
}
const TemplateContext = createContext<ITemplateContext | null>(null);
export const useTemplateContext = () => {
const context = useContext(TemplateContext);
if (context === null) {
throw new Error('useTemplateContext has to be used within <TemplateState>');
}
return context;
};
export const TemplateState = ({ children }: React.PropsWithChildren) => {
const { schema } = useDialogsStore(state => state.props as DlgCstTemplateProps);
const { setValue } = useFormContext<ICstCreateDTO>();
const [templateID, setTemplateID] = useState<number | undefined>(undefined);
const [args, setArguments] = useState<IArgumentValue[]>([]);
const [prototype, setPrototype] = useState<IConstituenta | undefined>(undefined);
const [filterCategory, setFilterCategory] = useState<IConstituenta | undefined>(undefined);
function onChangeArguments(newArgs: IArgumentValue[]) {
setArguments(newArgs);
if (newArgs.length === 0 || !prototype) {
return;
}
const newType = inferTemplatedType(prototype.cst_type, newArgs);
setValue('definition_formal', substituteTemplateArgs(prototype.definition_formal, newArgs));
setValue('cst_type', newType);
setValue('alias', generateAlias(newType, schema));
}
function onChangePrototype(newPrototype: IConstituenta) {
setPrototype(newPrototype);
setArguments(
newPrototype.parse.args.map(arg => ({
alias: arg.alias,
typification: arg.typification,
value: ''
}))
);
setValue('cst_type', newPrototype.cst_type);
setValue('alias', generateAlias(newPrototype.cst_type, schema));
setValue('definition_formal', newPrototype.definition_formal);
setValue('term_raw', newPrototype.term_raw);
setValue('definition_raw', newPrototype.definition_raw);
}
function onChangeTemplateID(newTemplateID: number | undefined) {
setTemplateID(newTemplateID);
setPrototype(undefined);
setArguments([]);
}
return (
<TemplateContext
value={{
templateID,
prototype,
filterCategory,
args,
onChangeArguments,
onChangePrototype,
onChangeFilterCategory: setFilterCategory,
onChangeTemplateID
}}
>
{children}
</TemplateContext>
);
};

View File

@ -18,7 +18,7 @@ import { ICstCreateDTO } from '../../backend/types';
import { useCstCreate } from '../../backend/useCstCreate';
import { useCstMove } from '../../backend/useCstMove';
import { useRSFormSuspense } from '../../backend/useRSForm';
import { CstType, IConstituenta, IRSForm } from '../../models/rsform';
import { CstType, IConstituenta, IConstituentaMeta, IRSForm } from '../../models/rsform';
import { generateAlias } from '../../models/rsformAPI';
export enum RSTabID {
@ -177,24 +177,21 @@ export const RSEditState = ({
});
}
function handleCreateCst(data: ICstCreateDTO) {
data.alias = data.alias || generateAlias(data.cst_type, schema);
void cstCreate({ itemID: itemID, data }).then(newCst => {
setSelected([newCst.id]);
navigateRSForm({ tab: activeTab, activeID: newCst.id });
if (activeTab === RSTabID.CST_LIST) {
setTimeout(() => {
const element = document.getElementById(`${prefixes.cst_list}${newCst.id}`);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'end'
});
}
}, PARAMETER.refreshTimeout);
}
});
function onCreateCst(newCst: IConstituentaMeta) {
setSelected([newCst.id]);
navigateRSForm({ tab: activeTab, activeID: newCst.id });
if (activeTab === RSTabID.CST_LIST) {
setTimeout(() => {
const element = document.getElementById(`${prefixes.cst_list}${newCst.id}`);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'end'
});
}
}, PARAMETER.refreshTimeout);
}
}
function moveUp() {
@ -258,9 +255,9 @@ export const RSEditState = ({
term_forms: []
};
if (skipDialog) {
handleCreateCst(data);
void cstCreate({ itemID: schema.id, data }).then(onCreateCst);
} else {
showCreateCst({ schema: schema, onCreate: handleCreateCst, initial: data });
showCreateCst({ schema: schema, onCreate: onCreateCst, initial: data });
}
}
@ -268,17 +265,19 @@ export const RSEditState = ({
if (!activeCst) {
return;
}
const data: ICstCreateDTO = {
insert_after: activeCst.id,
cst_type: activeCst.cst_type,
alias: generateAlias(activeCst.cst_type, schema),
term_raw: activeCst.term_raw,
definition_formal: activeCst.definition_formal,
definition_raw: activeCst.definition_raw,
convention: activeCst.convention,
term_forms: activeCst.term_forms
};
handleCreateCst(data);
void cstCreate({
itemID: schema.id,
data: {
insert_after: activeCst.id,
cst_type: activeCst.cst_type,
alias: generateAlias(activeCst.cst_type, schema),
term_raw: activeCst.term_raw,
definition_formal: activeCst.definition_formal,
definition_raw: activeCst.definition_raw,
convention: activeCst.convention,
term_forms: activeCst.term_forms
}
}).then(onCreateCst);
}
function promptDeleteCst() {
@ -299,11 +298,12 @@ export const RSEditState = ({
}
});
}
function promptTemplate() {
if (isModified && !promptUnsaved()) {
return;
}
showCstTemplate({ schema: schema, onCreate: handleCreateCst, insertAfter: activeCst?.id });
showCstTemplate({ schema: schema, onCreate: onCreateCst, insertAfter: activeCst?.id });
}
return (

View File

@ -140,7 +140,8 @@ export const errorMsg = {
loginFormat: 'Имя пользователя должно содержать только буквы и цифры',
invalidLocation: 'Некорректный формат пути',
versionTaken: 'Версия с таким шифром уже существует',
emptySubstitutions: 'Выберите хотя бы одно отождествление'
emptySubstitutions: 'Выберите хотя бы одно отождествление',
aliasInvalid: 'Введите незанятое имя, соответствующее типу'
};
/**
@ -162,7 +163,6 @@ export const promptText = {
'Внимание!!\nУдаление операционной схемы приведет к удалению всех операций и собственных концептуальных схем.\nДанное действие нельзя отменить.\nВы уверены, что хотите удалить данную ОСС?',
generateWordforms: 'Данное действие приведет к перезаписи словоформ при совпадении граммем. Продолжить?',
restoreArchive: 'При восстановлении архивной версии актуальная схему будет заменена. Продолжить?',
templateUndefined: 'Вы уверены, что хотите создать шаблонную конституенту не фиксируя аргументы?',
ownerChange:
'Вы уверены, что хотите изменить владельца? Вы потеряете право управления данной схемой. Данное действие отменить нельзя'
};