R: Refactor modal dialogs API

This commit is contained in:
Ivan 2025-02-06 20:27:56 +03:00
parent 6eeb6ffd14
commit d143bf4c3a
31 changed files with 245 additions and 177 deletions

View File

@ -5,7 +5,7 @@ import ConceptToaster from '@/app/ConceptToaster';
import Footer from '@/app/Footer'; import Footer from '@/app/Footer';
import Navigation from '@/app/Navigation'; import Navigation from '@/app/Navigation';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import ModalLoader from '@/components/ui/ModalLoader'; import { ModalLoader } from '@/components/ui/Modal';
import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout'; import { useAppLayoutStore, useMainHeight, useViewportHeight } from '@/stores/appLayout';
import { globals } from '@/utils/constants'; import { globals } from '@/utils/constants';

View File

@ -0,0 +1,19 @@
'use client';
import clsx from 'clsx';
interface ModalBackdropProps {
onHide: () => void;
}
export function ModalBackdrop({ onHide }: ModalBackdropProps) {
return (
<>
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
<div
className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')}
onClick={onHide}
/>
</>
);
}

View File

@ -5,46 +5,24 @@ import clsx from 'clsx';
import { IconClose } from '@/components/Icons'; import { IconClose } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp'; import BadgeHelp from '@/components/info/BadgeHelp';
import { CProps } from '@/components/props'; import { CProps } from '@/components/props';
import Button from '@/components/ui/Button';
import MiniButton from '@/components/ui/MiniButton';
import useEscapeKey from '@/hooks/useEscapeKey'; import useEscapeKey from '@/hooks/useEscapeKey';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants'; import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/labels'; import { prepareTooltip } from '@/utils/labels';
import Button from './Button'; import SubmitButton from '../SubmitButton';
import MiniButton from './MiniButton'; import { ModalBackdrop } from './ModalBackdrop';
export interface ModalProps extends CProps.Styling { export interface ModalProps extends CProps.Styling {
/** Title of the modal window. */ /** Title of the modal window. */
header?: string; header?: string;
/** Text of the submit button. */
submitText?: string;
/** Tooltip for the submit button when the form is invalid. */
submitInvalidTooltip?: string;
/** Indicates that form is readonly. */
readonly?: boolean;
/** Indicates that submit button is enabled. */
canSubmit?: boolean;
/** Indicates that the modal window should be scrollable. */ /** Indicates that the modal window should be scrollable. */
overflowVisible?: boolean; overflowVisible?: boolean;
/** ID of the form to be submitted. */
formID?: string;
/** Callback to be called before submit. */
beforeSubmit?: () => boolean;
/** Callback to be called after submit. */
onSubmit?: () => boolean;
/** Callback to be called after cancel. */
onCancel?: () => void;
/** Help topic to be displayed in the modal window. */ /** Help topic to be displayed in the modal window. */
helpTopic?: HelpTopic; helpTopic?: HelpTopic;
@ -52,67 +30,64 @@ export interface ModalProps extends CProps.Styling {
hideHelpWhen?: () => boolean; hideHelpWhen?: () => boolean;
} }
interface ModalFormProps extends ModalProps {
/** Text of the submit button. */
submitText?: string;
/** Tooltip for the submit button when the form is invalid. */
submitInvalidTooltip?: string;
/** Indicates that submit button is enabled. */
canSubmit?: boolean;
/** Callback to be called before submit. */
beforeSubmit?: () => boolean;
/** Callback to be called after submit. */
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
}
/** /**
* Displays a customizable modal window. * Displays a customizable modal window with submit form.
*/ */
function Modal({ export function ModalForm({
children, children,
className,
header, header,
submitText = 'Продолжить',
submitInvalidTooltip,
readonly,
canSubmit,
overflowVisible, overflowVisible,
canSubmit,
submitText = 'Продолжить',
submitInvalidTooltip,
beforeSubmit, beforeSubmit,
formID,
onSubmit, onSubmit,
onCancel,
className,
helpTopic, helpTopic,
hideHelpWhen, hideHelpWhen,
...restProps ...restProps
}: React.PropsWithChildren<ModalProps>) { }: React.PropsWithChildren<ModalFormProps>) {
const hideDialog = useDialogsStore(state => state.hideDialog); const hideDialog = useDialogsStore(state => state.hideDialog);
useEscapeKey(hideDialog); useEscapeKey(hideDialog);
const handleCancel = () => { function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
hideDialog();
onCancel?.();
};
const handleSubmit = () => {
if (beforeSubmit && !beforeSubmit()) { if (beforeSubmit && !beforeSubmit()) {
return; return;
} }
if (onSubmit && !onSubmit()) { onSubmit(event);
return;
}
if (formID) {
const element = document.getElementById(formID) as HTMLFormElement;
if (element) {
element.requestSubmit();
}
}
hideDialog(); hideDialog();
}; }
return ( return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'> <div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} /> <ModalBackdrop onHide={hideDialog} />
<div <form
className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'bg-prim-0 opacity-25')}
onClick={hideDialog}
/>
<div
className={clsx( className={clsx(
'cc-animate-modal', 'cc-animate-modal',
'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2', 'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border rounded-xl bg-prim-100' 'border rounded-xl bg-prim-100'
)} )}
onSubmit={handleSubmit}
> >
{helpTopic && !hideHelpWhen?.() ? ( {helpTopic && !hideHelpWhen?.() ? (
<div className='float-left mt-2 ml-2'> <div className='float-left mt-2 ml-2'>
@ -125,7 +100,7 @@ function Modal({
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')} titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<IconClose size='1.25rem' />} icon={<IconClose size='1.25rem' />}
className='float-right mt-2 mr-2' className='float-right mt-2 mr-2'
onClick={handleCancel} onClick={hideDialog}
/> />
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null} {header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}
@ -145,22 +120,16 @@ function Modal({
</div> </div>
<div className='z-modalControls my-2 flex gap-12 justify-center text-sm'> <div className='z-modalControls my-2 flex gap-12 justify-center text-sm'>
{!readonly ? ( <SubmitButton
<Button
autoFocus autoFocus
text={submitText} text={submitText}
title={!canSubmit ? submitInvalidTooltip : ''} title={!canSubmit ? submitInvalidTooltip : ''}
className='min-w-[7rem]' className='min-w-[7rem]'
colors='clr-btn-primary'
disabled={!canSubmit} disabled={!canSubmit}
onClick={handleSubmit}
/> />
) : null} <Button text='Отмена' className='min-w-[7rem]' onClick={hideDialog} />
<Button text={readonly ? 'Закрыть' : 'Отмена'} className='min-w-[7rem]' onClick={handleCancel} />
</div>
</div> </div>
</form>
</div> </div>
); );
} }
export default Modal;

View File

@ -2,9 +2,9 @@
import clsx from 'clsx'; import clsx from 'clsx';
import Loader from './Loader'; import Loader from '@/components/ui/Loader';
function ModalLoader() { export function ModalLoader() {
return ( return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'> <div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} /> <div className={clsx('z-navigation', 'fixed top-0 left-0', 'w-full h-full', 'backdrop-blur-[3px] opacity-50')} />
@ -21,5 +21,3 @@ function ModalLoader() {
</div> </div>
); );
} }
export default ModalLoader;

View File

@ -0,0 +1,80 @@
'use client';
import clsx from 'clsx';
import { IconClose } from '@/components/Icons';
import BadgeHelp from '@/components/info/BadgeHelp';
import Button from '@/components/ui/Button';
import MiniButton from '@/components/ui/MiniButton';
import useEscapeKey from '@/hooks/useEscapeKey';
import { useDialogsStore } from '@/stores/dialogs';
import { PARAMETER } from '@/utils/constants';
import { prepareTooltip } from '@/utils/labels';
import { ModalBackdrop } from './ModalBackdrop';
import { ModalProps } from './ModalForm';
interface ModalViewProps extends ModalProps {}
/**
* Displays a customizable modal window with submit form.
*/
export function ModalView({
children,
className,
header,
overflowVisible,
helpTopic,
hideHelpWhen,
...restProps
}: React.PropsWithChildren<ModalViewProps>) {
const hideDialog = useDialogsStore(state => state.hideDialog);
useEscapeKey(hideDialog);
return (
<div className='fixed top-0 left-0 w-full h-full z-modal cursor-default'>
<ModalBackdrop onHide={hideDialog} />
<div
className={clsx(
'cc-animate-modal',
'z-modal absolute bottom-1/2 left-1/2 -translate-x-1/2 translate-y-1/2',
'border rounded-xl bg-prim-100'
)}
>
{helpTopic && !hideHelpWhen?.() ? (
<div className='float-left mt-2 ml-2'>
<BadgeHelp topic={helpTopic} className={clsx(PARAMETER.TOOLTIP_WIDTH, 'sm:max-w-[40rem]')} padding='p-0' />
</div>
) : null}
<MiniButton
noPadding
titleHtml={prepareTooltip('Закрыть диалоговое окно', 'ESC')}
icon={<IconClose size='1.25rem' />}
className='float-right mt-2 mr-2'
onClick={hideDialog}
/>
{header ? <h1 className='px-12 py-2 select-none'>{header}</h1> : null}
<div
className={clsx(
'overscroll-contain max-h-[calc(100svh-8rem)] max-w-[100svw] xs:max-w-[calc(100svw-2rem)] outline-none',
{
'overflow-auto': !overflowVisible,
'overflow-visible': overflowVisible
},
className
)}
{...restProps}
>
{children}
</div>
<div className='z-modalControls my-2 flex gap-12 justify-center text-sm'>
<Button text='Закрыть' className='min-w-[7rem]' onClick={hideDialog} />
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,3 @@
export { ModalForm } from './ModalForm';
export { ModalLoader } from './ModalLoader';
export { ModalView } from './ModalView';

View File

@ -8,7 +8,7 @@ import { IconReset } from '@/components/Icons';
import PickSchema from '@/components/select/PickSchema'; import PickSchema from '@/components/select/PickSchema';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library'; import { ILibraryItem, LibraryItemID, LibraryItemType } from '@/models/library';
import { IOperation, IOperationSchema, OperationID } from '@/models/oss'; import { IOperation, IOperationSchema, OperationID } from '@/models/oss';
import { sortItemsForOSS } from '@/models/ossAPI'; import { sortItemsForOSS } from '@/models/ossAPI';
@ -41,7 +41,7 @@ function DlgChangeInputSchema() {
} }
return ( return (
<Modal <ModalForm
overflowVisible overflowVisible
header='Выбор концептуальной схемы' header='Выбор концептуальной схемы'
submitText='Подтвердить выбор' submitText='Подтвердить выбор'
@ -70,7 +70,7 @@ function DlgChangeInputSchema() {
rows={14} rows={14}
baseFilter={baseFilter} baseFilter={baseFilter}
/> />
</Modal> </ModalForm>
); );
} }

View File

@ -7,7 +7,7 @@ import { useAuthSuspense } from '@/backend/auth/useAuth';
import SelectLocationContext from '@/components/select/SelectLocationContext'; import SelectLocationContext from '@/components/select/SelectLocationContext';
import SelectLocationHead from '@/components/select/SelectLocationHead'; import SelectLocationHead from '@/components/select/SelectLocationHead';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import { LocationHead } from '@/models/library'; import { LocationHead } from '@/models/library';
import { combineLocation, validateLocation } from '@/models/libraryAPI'; import { combineLocation, validateLocation } from '@/models/libraryAPI';
@ -39,7 +39,7 @@ function DlgChangeLocation() {
} }
return ( return (
<Modal <ModalForm
overflowVisible overflowVisible
header='Изменение расположения' header='Изменение расположения'
submitText='Переместить' submitText='Переместить'
@ -58,7 +58,7 @@ function DlgChangeLocation() {
</div> </div>
<SelectLocationContext value={location} onChange={handleSelectLocation} className='max-h-[9.2rem]' /> <SelectLocationContext value={location} onChange={handleSelectLocation} className='max-h-[9.2rem]' />
<TextArea id='dlg_cst_body' label='Путь' rows={3} value={body} onChange={event => setBody(event.target.value)} /> <TextArea id='dlg_cst_body' label='Путь' rows={3} value={body} onChange={event => setBody(event.target.value)} />
</Modal> </ModalForm>
); );
} }

View File

@ -15,7 +15,7 @@ import SelectLocationHead from '@/components/select/SelectLocationHead';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library'; import { AccessPolicy, ILibraryItem, LocationHead } from '@/models/library';
@ -78,7 +78,7 @@ function DlgCloneLibraryItem() {
} }
return ( return (
<Modal <ModalForm
header='Создание копии концептуальной схемы' header='Создание копии концептуальной схемы'
canSubmit={canSubmit} canSubmit={canSubmit}
submitText='Создать' submitText='Создать'
@ -140,7 +140,7 @@ function DlgCloneLibraryItem() {
value={onlySelected} value={onlySelected}
onChange={value => setOnlySelected(value)} onChange={value => setOnlySelected(value)}
/> />
</Modal> </ModalForm>
); );
} }

View File

@ -3,7 +3,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { ICstCreateDTO } from '@/backend/rsform/api'; import { ICstCreateDTO } from '@/backend/rsform/api';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import usePartialUpdate from '@/hooks/usePartialUpdate'; import usePartialUpdate from '@/hooks/usePartialUpdate';
import { CstType, IRSForm } from '@/models/rsform'; import { CstType, IRSForm } from '@/models/rsform';
import { generateAlias } from '@/models/rsformAPI'; import { generateAlias } from '@/models/rsformAPI';
@ -40,7 +40,7 @@ function DlgCreateCst() {
}; };
return ( return (
<Modal <ModalForm
header='Создание конституенты' header='Создание конституенты'
canSubmit={validated} canSubmit={validated}
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -48,7 +48,7 @@ function DlgCreateCst() {
className='cc-column w-[35rem] max-h-[30rem] py-2 px-6' className='cc-column w-[35rem] max-h-[30rem] py-2 px-6'
> >
<FormCreateCst schema={schema} state={cstData} partialUpdate={updateCstData} setValidated={setValidated} /> <FormCreateCst schema={schema} state={cstData} partialUpdate={updateCstData} setValidated={setValidated} />
</Modal> </ModalForm>
); );
} }

View File

@ -6,7 +6,7 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import { useLibrary } from '@/backend/library/useLibrary'; import { useLibrary } from '@/backend/library/useLibrary';
import { IOperationCreateDTO } from '@/backend/oss/api'; import { IOperationCreateDTO } from '@/backend/oss/api';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
@ -98,7 +98,7 @@ function DlgCreateOperation() {
} }
return ( return (
<Modal <ModalForm
header='Создание операции' header='Создание операции'
submitText='Создать' submitText='Создать'
canSubmit={isValid} canSubmit={isValid}
@ -155,7 +155,7 @@ function DlgCreateOperation() {
/> />
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Modal> </ModalForm>
); );
} }

View File

@ -7,14 +7,14 @@ import { Controller, useForm, useWatch } from 'react-hook-form';
import { CreateVersionSchema, IVersionCreateDTO } from '@/backend/library/api'; import { CreateVersionSchema, IVersionCreateDTO } from '@/backend/library/api';
import { useVersionCreate } from '@/backend/library/useVersionCreate'; import { useVersionCreate } from '@/backend/library/useVersionCreate';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { IVersionInfo, LibraryItemID, VersionID } from '@/models/library'; import { IVersionInfo, LibraryItemID, VersionID } from '@/models/library';
import { nextVersion } from '@/models/libraryAPI'; import { nextVersion } from '@/models/libraryAPI';
import { ConstituentaID } from '@/models/rsform'; import { ConstituentaID } from '@/models/rsform';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { globals } from '@/utils/constants'; import { errors } from '@/utils/labels';
export interface DlgCreateVersionProps { export interface DlgCreateVersionProps {
itemID: LibraryItemID; itemID: LibraryItemID;
@ -50,10 +50,12 @@ function DlgCreateVersion() {
} }
return ( return (
<Modal header='Создание версии' canSubmit={canSubmit} submitText='Создать' formID={globals.dlg_create_version}> <ModalForm
<form header='Создание версии'
id={globals.dlg_create_version}
className={clsx('cc-column', 'w-[30rem]', 'py-2 px-6')} className={clsx('cc-column', 'w-[30rem]', 'py-2 px-6')}
canSubmit={canSubmit}
submitInvalidTooltip={errors.versionTaken}
submitText='Создать'
onSubmit={event => void handleSubmit(onSubmit)(event)} onSubmit={event => void handleSubmit(onSubmit)(event)}
> >
<TextInput id='dlg_version' {...register('version')} dense label='Версия' className='w-[16rem]' /> <TextInput id='dlg_version' {...register('version')} dense label='Версия' className='w-[16rem]' />
@ -72,8 +74,7 @@ function DlgCreateVersion() {
)} )}
/> />
) : null} ) : null}
</form> </ModalForm>
</Modal>
); );
} }

View File

@ -7,7 +7,7 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import { ICstCreateDTO } from '@/backend/rsform/api'; import { ICstCreateDTO } from '@/backend/rsform/api';
import { useRSForm } from '@/backend/rsform/useRSForm'; import { useRSForm } from '@/backend/rsform/useRSForm';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import FormCreateCst from '@/dialogs/DlgCreateCst/FormCreateCst'; import FormCreateCst from '@/dialogs/DlgCreateCst/FormCreateCst';
import usePartialUpdate from '@/hooks/usePartialUpdate'; import usePartialUpdate from '@/hooks/usePartialUpdate';
@ -120,7 +120,7 @@ function DlgCstTemplate() {
}, [constituenta.alias, constituenta.cst_type, schema, template.prototype]); }, [constituenta.alias, constituenta.cst_type, schema, template.prototype]);
return ( return (
<Modal <ModalForm
header='Создание конституенты из шаблона' header='Создание конституенты из шаблона'
submitText='Создать' submitText='Создать'
className='w-[43rem] h-[35rem] px-6' className='w-[43rem] h-[35rem] px-6'
@ -157,7 +157,7 @@ function DlgCstTemplate() {
</div> </div>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Modal> </ModalForm>
); );
} }

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import { ConstituentaID, IRSForm } from '@/models/rsform'; import { ConstituentaID, IRSForm } from '@/models/rsform';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { prefixes } from '@/utils/constants'; import { prefixes } from '@/utils/constants';
@ -36,7 +36,7 @@ function DlgDeleteCst() {
} }
return ( return (
<Modal <ModalForm
canSubmit canSubmit
header='Удаление конституент' header='Удаление конституент'
submitText={expandOut ? 'Удалить с зависимыми' : 'Удалить'} submitText={expandOut ? 'Удалить с зависимыми' : 'Удалить'}
@ -59,7 +59,7 @@ function DlgDeleteCst() {
{hasInherited ? ( {hasInherited ? (
<p className='text-sm clr-text-red'>Внимание! Выбранные конституенты имеют наследников в ОСС</p> <p className='text-sm clr-text-red'>Внимание! Выбранные конституенты имеют наследников в ОСС</p>
) : null} ) : null}
</Modal> </ModalForm>
); );
} }

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IOperation, OperationID } from '@/models/oss'; import { IOperation, OperationID } from '@/models/oss';
@ -26,7 +26,7 @@ function DlgDeleteOperation() {
} }
return ( return (
<Modal <ModalForm
overflowVisible overflowVisible
header='Удаление операции' header='Удаление операции'
submitText='Подтвердить удаление' submitText='Подтвердить удаление'
@ -54,7 +54,7 @@ function DlgDeleteOperation() {
onChange={setDeleteSchema} onChange={setDeleteSchema}
disabled={!target.is_owned || target.result === null} disabled={!target.is_owned || target.result === null}
/> />
</Modal> </ModalForm>
); );
} }

View File

@ -8,7 +8,7 @@ import { IconRemove } from '@/components/Icons';
import SelectUser from '@/components/select/SelectUser'; import SelectUser from '@/components/select/SelectUser';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import { UserID } from '@/models/user'; import { UserID } from '@/models/user';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
@ -38,7 +38,7 @@ function DlgEditEditors() {
} }
return ( return (
<Modal <ModalForm
canSubmit canSubmit
header='Список редакторов' header='Список редакторов'
submitText='Сохранить список' submitText='Сохранить список'
@ -68,7 +68,7 @@ function DlgEditEditors() {
className='w-[25rem]' className='w-[25rem]'
/> />
</div> </div>
</Modal> </ModalForm>
); );
} }

View File

@ -6,7 +6,7 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import { IOperationUpdateDTO } from '@/backend/oss/api'; import { IOperationUpdateDTO } from '@/backend/oss/api';
import { useRSForms } from '@/backend/rsform/useRSForms'; import { useRSForms } from '@/backend/rsform/useRSForms';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
@ -123,7 +123,7 @@ function DlgEditOperation() {
} }
return ( return (
<Modal <ModalForm
header='Редактирование операции' header='Редактирование операции'
submitText='Сохранить' submitText='Сохранить'
canSubmit={canSubmit} canSubmit={canSubmit}
@ -186,7 +186,7 @@ function DlgEditOperation() {
</TabPanel> </TabPanel>
) : null} ) : null}
</Tabs> </Tabs>
</Modal> </ModalForm>
); );
} }

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import { ReferenceType } from '@/models/language'; import { ReferenceType } from '@/models/language';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
@ -46,7 +46,7 @@ function DlgEditReference() {
} }
return ( return (
<Modal <ModalForm
header='Редактирование ссылки' header='Редактирование ссылки'
submitText='Сохранить ссылку' submitText='Сохранить ссылку'
canSubmit={isValid} canSubmit={isValid}
@ -81,7 +81,7 @@ function DlgEditReference() {
<TabSyntacticReference initial={initial} onChangeReference={setReference} onChangeValid={setIsValid} /> <TabSyntacticReference initial={initial} onChangeReference={setReference} onChangeValid={setIsValid} />
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Modal> </ModalForm>
); );
} }

View File

@ -7,7 +7,7 @@ import { useVersionDelete } from '@/backend/library/useVersionDelete';
import { useVersionUpdate } from '@/backend/library/useVersionUpdate'; import { useVersionUpdate } from '@/backend/library/useVersionUpdate';
import { IconReset, IconSave } from '@/components/Icons'; import { IconReset, IconSave } from '@/components/Icons';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import { ModalView } from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import { ILibraryItemVersioned, IVersionInfo, VersionID } from '@/models/library'; import { ILibraryItemVersioned, IVersionInfo, VersionID } from '@/models/library';
@ -64,7 +64,7 @@ function DlgEditVersions() {
}, [selected]); }, [selected]);
return ( return (
<Modal readonly header='Редактирование версий' className='flex flex-col w-[40rem] px-6 gap-3 pb-6'> <ModalView header='Редактирование версий' className='flex flex-col w-[40rem] px-6 gap-3 pb-6'>
<TableVersions <TableVersions
processing={processing} processing={processing}
items={item.versions} items={item.versions}
@ -105,7 +105,7 @@ function DlgEditVersions() {
value={description} value={description}
onChange={event => setDescription(event.target.value)} onChange={event => setDescription(event.target.value)}
/> />
</Modal> </ModalView>
); );
} }

View File

@ -11,7 +11,7 @@ import { IconAccept, IconMoveDown, IconMoveLeft, IconMoveRight, IconRemove } fro
import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme'; import SelectMultiGrammeme from '@/components/select/SelectMultiGrammeme';
import Label from '@/components/ui/Label'; import Label from '@/components/ui/Label';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TextArea from '@/components/ui/TextArea'; import TextArea from '@/components/ui/TextArea';
import { Grammeme, IWordForm } from '@/models/language'; import { Grammeme, IWordForm } from '@/models/language';
import { parseGrammemes, wordFormEquals } from '@/models/languageAPI'; import { parseGrammemes, wordFormEquals } from '@/models/languageAPI';
@ -124,7 +124,7 @@ function DlgEditWordForms() {
} }
return ( return (
<Modal <ModalForm
canSubmit canSubmit
header='Редактирование словоформ' header='Редактирование словоформ'
submitText='Сохранить' submitText='Сохранить'
@ -208,7 +208,7 @@ function DlgEditWordForms() {
</div> </div>
<TableWordForms forms={forms} setForms={setForms} onFormSelect={handleSelectForm} /> <TableWordForms forms={forms} setForms={setForms} onFormSelect={handleSelectForm} />
</Modal> </ModalForm>
); );
} }

View File

@ -1,7 +1,7 @@
'use client'; 'use client';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import usePartialUpdate from '@/hooks/usePartialUpdate'; import usePartialUpdate from '@/hooks/usePartialUpdate';
import { GraphFilterParams } from '@/models/miscellaneous'; import { GraphFilterParams } from '@/models/miscellaneous';
import { CstType } from '@/models/rsform'; import { CstType } from '@/models/rsform';
@ -23,7 +23,7 @@ function DlgGraphParams() {
} }
return ( return (
<Modal <ModalForm
canSubmit canSubmit
header='Настройки графа термов' header='Настройки графа термов'
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -106,7 +106,7 @@ function DlgGraphParams() {
onChange={value => updateParams({ allowTheorem: value })} onChange={value => updateParams({ allowTheorem: value })}
/> />
</div> </div>
</Modal> </ModalForm>
); );
} }

View File

@ -7,7 +7,7 @@ import { TabList, TabPanel, Tabs } from 'react-tabs';
import { IInlineSynthesisDTO } from '@/backend/rsform/api'; import { IInlineSynthesisDTO } from '@/backend/rsform/api';
import { useRSForm } from '@/backend/rsform/useRSForm'; import { useRSForm } from '@/backend/rsform/useRSForm';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import TabLabel from '@/components/ui/TabLabel'; import TabLabel from '@/components/ui/TabLabel';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { ICstSubstitute } from '@/models/oss'; import { ICstSubstitute } from '@/models/oss';
@ -67,7 +67,7 @@ function DlgInlineSynthesis() {
} }
return ( return (
<Modal <ModalForm
header='Импорт концептуальной схем' header='Импорт концептуальной схем'
submitText='Добавить конституенты' submitText='Добавить конституенты'
className='w-[40rem] h-[33rem] px-6' className='w-[40rem] h-[33rem] px-6'
@ -122,7 +122,7 @@ function DlgInlineSynthesis() {
) : null} ) : null}
</TabPanel> </TabPanel>
</Tabs> </Tabs>
</Modal> </ModalForm>
); );
} }

View File

@ -11,7 +11,7 @@ import PickMultiConstituenta from '@/components/select/PickMultiConstituenta';
import SelectLibraryItem from '@/components/select/SelectLibraryItem'; import SelectLibraryItem from '@/components/select/SelectLibraryItem';
import Loader from '@/components/ui/Loader'; import Loader from '@/components/ui/Loader';
import MiniButton from '@/components/ui/MiniButton'; import MiniButton from '@/components/ui/MiniButton';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import { ILibraryItem, LibraryItemID } from '@/models/library'; import { ILibraryItem, LibraryItemID } from '@/models/library';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { IOperation, IOperationSchema } from '@/models/oss'; import { IOperation, IOperationSchema } from '@/models/oss';
@ -86,7 +86,7 @@ function DlgRelocateConstituents() {
} }
return ( return (
<Modal <ModalForm
header='Перенос конституент' header='Перенос конституент'
submitText='Переместить' submitText='Переместить'
canSubmit={isValid} canSubmit={isValid}
@ -130,7 +130,7 @@ function DlgRelocateConstituents() {
/> />
) : null} ) : null}
</div> </div>
</Modal> </ModalForm>
); );
} }

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { ICstRenameDTO } from '@/backend/rsform/api'; import { ICstRenameDTO } from '@/backend/rsform/api';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import SelectSingle from '@/components/ui/SelectSingle'; import SelectSingle from '@/components/ui/SelectSingle';
import TextInput from '@/components/ui/TextInput'; import TextInput from '@/components/ui/TextInput';
import usePartialUpdate from '@/hooks/usePartialUpdate'; import usePartialUpdate from '@/hooks/usePartialUpdate';
@ -43,7 +43,7 @@ function DlgRenameCst() {
} }
return ( return (
<Modal <ModalForm
header='Переименование конституенты' header='Переименование конституенты'
submitText='Переименовать' submitText='Переименовать'
submitInvalidTooltip='Введите незанятое имя, соответствующее типу' submitInvalidTooltip='Введите незанятое имя, соответствующее типу'
@ -73,7 +73,7 @@ function DlgRenameCst() {
value={cstData.alias} value={cstData.alias}
onChange={event => updateData({ alias: event.target.value })} onChange={event => updateData({ alias: event.target.value })}
/> />
</Modal> </ModalForm>
); );
} }

View File

@ -3,7 +3,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { ReactFlowProvider } from 'reactflow'; import { ReactFlowProvider } from 'reactflow';
import Modal from '@/components/ui/Modal'; import { ModalView } from '@/components/ui/Modal';
import Overlay from '@/components/ui/Overlay'; import Overlay from '@/components/ui/Overlay';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { SyntaxTree } from '@/models/rslang'; import { SyntaxTree } from '@/models/rslang';
@ -24,8 +24,7 @@ function DlgShowAST() {
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
return ( return (
<Modal <ModalView
readonly
className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]' className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]'
helpTopic={HelpTopic.UI_FORMULA_TREE} helpTopic={HelpTopic.UI_FORMULA_TREE}
> >
@ -50,7 +49,7 @@ function DlgShowAST() {
onChangeDragging={setIsDragging} onChangeDragging={setIsDragging}
/> />
</ReactFlowProvider> </ReactFlowProvider>
</Modal> </ModalView>
); );
} }

View File

@ -3,7 +3,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { QRCodeSVG } from 'qrcode.react'; import { QRCodeSVG } from 'qrcode.react';
import Modal from '@/components/ui/Modal'; import { ModalView } from '@/components/ui/Modal';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
export interface DlgShowQRProps { export interface DlgShowQRProps {
@ -13,11 +13,11 @@ export interface DlgShowQRProps {
function DlgShowQR() { function DlgShowQR() {
const { target } = useDialogsStore(state => state.props as DlgShowQRProps); const { target } = useDialogsStore(state => state.props as DlgShowQRProps);
return ( return (
<Modal readonly className={clsx('w-[30rem]', 'py-12 pr-3 pl-6 flex gap-3 justify-center items-center')}> <ModalView className={clsx('w-[30rem]', 'py-12 pr-3 pl-6 flex gap-3 justify-center items-center')}>
<div className='bg-[#ffffff] p-4 border'> <div className='bg-[#ffffff] p-4 border'>
<QRCodeSVG value={target} size={256} /> <QRCodeSVG value={target} size={256} />
</div> </div>
</Modal> </ModalView>
); );
} }

View File

@ -3,7 +3,7 @@
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { ReactFlowProvider } from 'reactflow'; import { ReactFlowProvider } from 'reactflow';
import Modal from '@/components/ui/Modal'; import { ModalView } from '@/components/ui/Modal';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { ITypeInfo } from '@/models/rslang'; import { ITypeInfo } from '@/models/rslang';
import { TMGraph } from '@/models/TMGraph'; import { TMGraph } from '@/models/TMGraph';
@ -32,16 +32,15 @@ function DlgShowTypeGraph() {
} }
return ( return (
<Modal <ModalView
header='Граф ступеней' header='Граф ступеней'
readonly
className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]' className='flex flex-col justify-stretch w-[calc(100dvw-3rem)] h-[calc(100dvh-6rem)]'
helpTopic={HelpTopic.UI_TYPE_GRAPH} helpTopic={HelpTopic.UI_TYPE_GRAPH}
> >
<ReactFlowProvider> <ReactFlowProvider>
<MGraphFlow data={graph} /> <MGraphFlow data={graph} />
</ReactFlowProvider> </ReactFlowProvider>
</Modal> </ModalView>
); );
} }

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import PickSubstitutions from '@/components/select/PickSubstitutions'; import PickSubstitutions from '@/components/select/PickSubstitutions';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { ICstSubstitute, ICstSubstitutions } from '@/models/oss'; import { ICstSubstitute, ICstSubstitutions } from '@/models/oss';
import { IRSForm } from '@/models/rsform'; import { IRSForm } from '@/models/rsform';
@ -26,7 +26,7 @@ function DlgSubstituteCst() {
} }
return ( return (
<Modal <ModalForm
header='Отождествление' header='Отождествление'
submitText='Отождествить' submitText='Отождествить'
submitInvalidTooltip='Выберите две различные конституенты' submitInvalidTooltip='Выберите две различные конституенты'
@ -42,7 +42,7 @@ function DlgSubstituteCst() {
rows={6} rows={6}
schemas={[schema]} schemas={[schema]}
/> />
</Modal> </ModalForm>
); );
} }

View File

@ -5,7 +5,7 @@ import { useState } from 'react';
import { useUploadTRS } from '@/backend/rsform/useUploadTRS'; import { useUploadTRS } from '@/backend/rsform/useUploadTRS';
import Checkbox from '@/components/ui/Checkbox'; import Checkbox from '@/components/ui/Checkbox';
import FileInput from '@/components/ui/FileInput'; import FileInput from '@/components/ui/FileInput';
import Modal from '@/components/ui/Modal'; import { ModalForm } from '@/components/ui/Modal';
import { LibraryItemID } from '@/models/library'; import { LibraryItemID } from '@/models/library';
import { useDialogsStore } from '@/stores/dialogs'; import { useDialogsStore } from '@/stores/dialogs';
import { EXTEOR_TRS_FILE } from '@/utils/constants'; import { EXTEOR_TRS_FILE } from '@/utils/constants';
@ -41,7 +41,7 @@ function DlgUploadRSForm() {
}; };
return ( return (
<Modal <ModalForm
header='Импорт схемы из Экстеора' header='Импорт схемы из Экстеора'
canSubmit={!!file} canSubmit={!!file}
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -55,7 +55,7 @@ function DlgUploadRSForm() {
value={loadMetadata} value={loadMetadata}
onChange={value => setLoadMetadata(value)} onChange={value => setLoadMetadata(value)}
/> />
</Modal> </ModalForm>
); );
} }

View File

@ -114,8 +114,7 @@ export const globals = {
main_scroll: 'main_scroll', main_scroll: 'main_scroll',
library_item_editor: 'library_item_editor', library_item_editor: 'library_item_editor',
constituenta_editor: 'constituenta_editor', constituenta_editor: 'constituenta_editor',
graph_schemas: 'graph_schemas_tooltip', graph_schemas: 'graph_schemas_tooltip'
dlg_create_version: 'dlg_create_version'
}; };
/** /**

View File

@ -992,7 +992,8 @@ export const errors = {
rulesNotAccepted: 'Примите условия пользования Порталом', rulesNotAccepted: 'Примите условия пользования Порталом',
privacyNotAccepted: 'Примите политику обработки персональных данных', privacyNotAccepted: 'Примите политику обработки персональных данных',
loginFormat: 'Имя пользователя должно содержать только буквы и цифры', loginFormat: 'Имя пользователя должно содержать только буквы и цифры',
invalidLocation: 'Некорректный формат пути' invalidLocation: 'Некорректный формат пути',
versionTaken: 'Версия с таким шифром уже существует'
}; };
/** /**