Refactor UI elements positioning and minor UI fixes

This commit is contained in:
IRBorisov 2023-09-15 23:29:52 +03:00
parent 1dd007a3e6
commit 88a7181695
36 changed files with 348 additions and 355 deletions

View File

@ -5,7 +5,7 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
tooltip?: string
dense?: boolean
loading?: boolean
widthClass?: string
dimensions?: string
borderClass?: string
colorClass?: string
}
@ -15,7 +15,7 @@ function Button({
dense, disabled,
borderClass = 'border rounded',
colorClass = 'clr-btn-default',
widthClass = 'w-fit h-fit',
dimensions = 'w-fit h-fit',
loading, onClick,
...props
}: ButtonProps) {
@ -27,10 +27,10 @@ function Button({
disabled={disabled ?? loading}
onClick={onClick}
title={tooltip}
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colorClass} ${widthClass} ${borderClass} ${cursor}`}
className={`inline-flex items-center gap-2 align-middle justify-center select-none ${padding} ${colorClass} ${dimensions} ${borderClass} ${cursor}`}
{...props}
>
{icon && <span>{icon}</span>}
{icon && icon}
{text && <span className={'font-semibold'}>{text}</span>}
</button>
);

View File

@ -1,16 +0,0 @@
interface CardProps {
title?: string
widthClass?: string
children: React.ReactNode
}
function Card({ title, widthClass = 'min-w-fit', children }: CardProps) {
return (
<div className={`border shadow-md py-2 clr-app px-6 ${widthClass}`}>
{ title && <h1 className='mb-2 text-xl font-bold whitespace-nowrap'>{title}</h1> }
{children}
</div>
);
}
export default Card;

View File

@ -9,7 +9,7 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
label?: string
required?: boolean
disabled?: boolean
widthClass?: string
dimensions?: string
tooltip?: string
value: boolean
@ -18,7 +18,7 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
function Checkbox({
id, required, disabled, tooltip, label,
widthClass = 'w-fit', value, setValue, ...props
dimensions = 'w-fit', value, setValue, ...props
}: CheckboxProps) {
const cursor = useMemo(
() => {
@ -46,7 +46,7 @@ function Checkbox({
return (
<button
id={id}
className={`flex items-center [&:not(:first-child)]:mt-3 clr-outline focus:outline-dotted focus:outline-1 ${widthClass}`}
className={`flex items-center clr-outline focus:outline-dotted focus:outline-1 ${dimensions}`}
title={tooltip}
disabled={disabled}
onClick={handleClick}

View File

@ -1,13 +1,13 @@
interface DropdownProps {
children: React.ReactNode
stretchLeft?: boolean
widthClass?: string
dimensions?: string
}
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
function Dropdown({ children, dimensions = 'w-fit', stretchLeft }: DropdownProps) {
return (
<div className='relative text-sm'>
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} mt-2 py-1 z-tooltip flex flex-col items-stretch justify-start origin-top-right border divide-y divide-inherit rounded-md shadow-lg clr-input ${widthClass}`}>
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} mt-2 py-1 z-tooltip flex flex-col items-stretch justify-start origin-top-right border divide-y divide-inherit rounded-md shadow-lg clr-input ${dimensions}`}>
{children}
</div>
</div>

View File

@ -16,7 +16,7 @@ function DropdownCheckbox({ tooltip, setValue, disabled, ...props }: DropdownChe
className={`px-4 py-1 text-left overflow-ellipsis ${behavior} w-full whitespace-nowrap`}
>
<Checkbox
widthClass='w-full'
dimensions='w-full'
disabled={disabled}
setValue={setValue}
{...props}

View File

@ -9,13 +9,13 @@ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'
label: string
tooltip?: string
acceptType?: string
widthClass?: string
dimensions?: string
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
}
function FileInput({
label, acceptType, tooltip,
widthClass = 'w-fit', onChange,
dimensions = 'w-fit', onChange,
...props
}: FileInputProps) {
const inputRef = useRef<HTMLInputElement | null>(null);
@ -37,7 +37,7 @@ function FileInput({
};
return (
<div className={`flex flex-col gap-2 py-2 mt-3 items-start ${widthClass}`}>
<div className={`flex flex-col gap-2 py-2 items-start ${dimensions}`}>
<input type='file'
ref={inputRef}
style={{ display: 'none' }}

View File

@ -1,21 +1,19 @@
import Card from './Card';
interface FormProps {
title: string
widthClass?: string
dimensions?: string
onSubmit: (event: React.FormEvent<HTMLFormElement>) => void
children: React.ReactNode
}
function Form({ title, onSubmit, widthClass = 'max-w-xs', children }: FormProps) {
function Form({ title, onSubmit, dimensions = 'max-w-xs', children }: FormProps) {
return (
<div className='flex flex-col items-center w-full'>
<Card title={title} widthClass={widthClass}>
<form onSubmit={onSubmit}>
{children}
</form>
</Card>
</div>
<form
className={`border shadow-md py-2 clr-app px-6 flex flex-col gap-3 ${dimensions}`}
onSubmit={onSubmit}
>
{ title && <h1 className='text-xl font-bold whitespace-nowrap'>{title}</h1> }
{children}
</form>
);
}

View File

@ -1,20 +1,23 @@
interface MiniButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'title' > {
icon?: React.ReactNode
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'title' |'children' > {
icon: React.ReactNode
tooltip?: string
noHover?: boolean
dimensions?: string
}
function MiniButton({ icon, tooltip, children, noHover, tabIndex, ...props }: MiniButtonProps) {
function MiniButton({
icon, tooltip, noHover, tabIndex, dimensions,
...props
}: MiniButtonProps) {
return (
<button type='button'
title={tooltip}
tabIndex={tabIndex ?? -1}
className={`px-1 py-1 font-bold rounded-full cursor-pointer whitespace-nowrap disabled:cursor-not-allowed clr-btn-clear ${noHover ? 'outline-none' : 'clr-hover'}`}
className={`px-1 py-1 w-fit h-fit rounded-full cursor-pointer disabled:cursor-not-allowed clr-btn-clear ${noHover ? 'outline-none' : 'clr-hover'} ${dimensions}`}
{...props}
>
{icon && <span>{icon}</span>}
{children}
{icon}
</button>
);
}

View File

@ -51,7 +51,7 @@ function Modal({
<Button
text={submitText}
tooltip={!canSubmit ? submitInvalidTooltip: ''}
widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
dimensions='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
colorClass='clr-btn-primary'
disabled={!canSubmit}
onClick={handleSubmit}
@ -59,7 +59,7 @@ function Modal({
/>}
<Button
text={readonly ? 'Закрыть' : 'Отмена'}
widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
dimensions='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
onClick={handleCancel}
/>
</div>

View File

@ -4,17 +4,17 @@ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'child
tooltip?: string
loading?: boolean
icon?: React.ReactNode
widthClass?: string
dimensions?: string
}
function SubmitButton({
text = 'ОК', icon, disabled, tooltip, loading,
widthClass = 'w-fit h-fit'
dimensions = 'w-fit h-fit'
}: SubmitButtonProps) {
return (
<button type='submit'
title={tooltip}
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${widthClass} ${loading ? ' cursor-progress' : ''}`}
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-semibold select-none disabled:cursor-not-allowed border rounded clr-btn-primary ${dimensions} ${loading ? ' cursor-progress' : ''}`}
disabled={disabled ?? loading}
>
{icon && <span>{icon}</span>}

View File

@ -6,27 +6,27 @@ export interface TextAreaProps
extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'className' | 'title'> {
label: string
tooltip?: string
widthClass?: string
dimensions?: string
colorClass?: string
}
function TextArea({
id, label, required, tooltip,
widthClass = 'w-full',
dimensions = 'w-full',
colorClass = 'clr-input',
rows = 4,
...props
}: TextAreaProps) {
return (
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3'>
<Label
<div className='flex flex-col items-start gap-2'>
{label && <Label
text={label}
required={!props.disabled && required}
htmlFor={id}
/>
/>}
<textarea id={id}
title={tooltip}
className={`px-3 py-2 mt-2 leading-tight border shadow clr-outline ${colorClass} ${widthClass}`}
className={`px-3 py-2 leading-tight border shadow clr-outline ${colorClass} ${dimensions}`}
rows={rows}
required={required}
{...props}

View File

@ -5,19 +5,19 @@ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'className' | 'title'>
id?: string
label?: string
tooltip?: string
widthClass?: string
dimensions?: string
colorClass?: string
singleRow?: boolean
}
function TextInput({
id, required, label, singleRow, tooltip,
widthClass = 'w-full',
dimensions = 'w-full',
colorClass = 'clr-input',
...props
}: TextInputProps) {
return (
<div className={`flex [&:not(:first-child)]:mt-3 ${singleRow ? 'items-center gap-4 ' + widthClass : 'flex-col items-start'}`}>
<div className={`flex ${singleRow ? 'items-center gap-4 ' + dimensions : 'flex-col items-start gap-2'}`}>
{label && <Label
text={label}
required={!props.disabled && required}
@ -25,7 +25,7 @@ function TextInput({
/>}
<input id={id}
title={tooltip}
className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? '' : 'mt-2 ' + widthClass}`}
className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? '' : dimensions}`}
required={required}
{...props}
/>

View File

@ -13,7 +13,7 @@ extends Omit<CheckboxProps, 'value' | 'setValue'> {
function Tristate({
id, required, disabled, tooltip, label,
widthClass = 'w-fit', value, setValue, ...props
dimensions = 'w-fit', value, setValue, ...props
}: TristateProps) {
const cursor = useMemo(
() => {
@ -47,7 +47,7 @@ function Tristate({
return (
<button
id={id}
className={`flex items-center [&:not(:first-child)]:mt-3 clr-outline focus:outline-dotted focus:outline-1 ${widthClass}`}
className={`flex items-center clr-outline focus:outline-dotted focus:outline-1 ${dimensions}`}
title={tooltip}
disabled={disabled}
onClick={handleClick}

View File

@ -5,7 +5,7 @@ function HelpConstituenta() {
return (
<div className=''>
<h1>Подсказки</h1>
<p><b className='text-warning'>Изменения сохраняются ПОСЛЕ нажатия на кнопку снизу или слева вверху</b></p>
<p><b className='text-warning'>Изменения сохраняются ПОСЛЕ нажатия на кнопку снизу или справа вверху</b></p>
<p><b>Клик на формальное выражение</b> - обратите внимание на кнопки снизу.<br/>Для каждой есть горячая клавиша в подсказке</p>
<p><b>Список конституент справа</b> - обратите внимание на настройки фильтрации</p>
<p>- слева от ввода текста настраивается набор атрибутов конституенты</p>

View File

@ -31,7 +31,7 @@ function UserDropdown({ hideDropdown }: UserDropdownProps) {
};
return (
<Dropdown widthClass='w-36' stretchLeft>
<Dropdown dimensions='w-36' stretchLeft>
<DropdownButton
tooltip='Профиль пользователя'
onClick={navigateProfile}

View File

@ -77,27 +77,29 @@ function CreateRSFormPage() {
}
return (
<RequireAuth>
<Form title='Создание концептуальной схемы'
onSubmit={handleSubmit}
widthClass='max-w-lg w-full mt-4'
>
<div className='relative w-full'>
<div className='absolute top-[-2.4rem] right-[-1rem] flex'>
<input
type='file'
ref={inputRef}
style={{ display: 'none' }}
accept={EXTEOR_TRS_FILE}
onChange={handleFileChange}
/>
<MiniButton
tooltip='Загрузить из Экстеор'
icon={<UploadIcon size={5} color='text-primary'/>}
onClick={() => inputRef.current?.click()}
/>
</div>
</div>
<RequireAuth>
<div className='flex justify-center w-full'>
<Form title='Создание концептуальной схемы'
onSubmit={handleSubmit}
dimensions='max-w-lg w-full mt-4'
>
<div className='relative w-full'>
<div className='absolute top-[-2.4rem] right-[-1rem] flex'>
<input
type='file'
ref={inputRef}
style={{ display: 'none' }}
accept={EXTEOR_TRS_FILE}
onChange={handleFileChange}
/>
<MiniButton
tooltip='Загрузить из Экстеор'
icon={<UploadIcon size={5} color='text-primary'/>}
onClick={() => inputRef.current?.click()}
/>
</div>
</div>
<div className='flex flex-col gap-3'>
{ fileName && <Label text={`Загружен файл: ${fileName}`} />}
<TextInput id='title' label='Полное название' type='text'
required={!file}
@ -121,22 +123,23 @@ function CreateRSFormPage() {
value={common}
setValue={value => setCommon(value ?? false)}
/>
<div className='flex items-center justify-center gap-4 py-2 mt-4'>
<div className='flex items-center justify-center gap-4 py-2'>
<SubmitButton
text='Создать схему'
loading={processing}
widthClass='min-w-[10rem]'
dimensions='min-w-[10rem]'
/>
<Button
text='Отмена'
onClick={() => handleCancel()}
widthClass='min-w-[10rem]'
dimensions='min-w-[10rem]'
/>
</div>
{ error && <BackendError error={error} />}
</Form>
</RequireAuth>
);
</div>
</Form>
</div>
</RequireAuth>);
}
export default CreateRSFormPage;

View File

@ -30,7 +30,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
dense
tooltip='Фильтры'
colorClass='clr-input clr-hover text-btn'
widthClass='h-full py-1 px-2 border-none'
dimensions='h-full py-1 px-2 border-none'
onClick={pickerMenu.toggle}
/>
{ pickerMenu.isActive &&

View File

@ -15,7 +15,7 @@ import { IUserLoginData } from '../models/library';
function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return (
<div className='mt-2 text-sm select-text text-warning'>
<div className='text-sm select-text text-warning'>
На Портале отсутствует такое сочетание имени пользователя и пароля
</div>
);
@ -80,7 +80,7 @@ function LoginPage() {
<Form
title='Вход в Портал'
onSubmit={handleSubmit}
widthClass='w-[24rem]'
dimensions='w-[24rem]'
>
<TextInput id='username'
label='Имя пользователя'
@ -98,14 +98,14 @@ function LoginPage() {
onChange={event => setPassword(event.target.value)}
/>
<div className='flex justify-center w-full gap-2 py-2 mt-4'>
<div className='flex justify-center w-full gap-2 py-2'>
<SubmitButton
text='Вход'
widthClass='w-[12rem]'
dimensions='w-[12rem]'
loading={loading}
/>
</div>
<div className='flex flex-col mt-2 text-sm'>
<div className='flex flex-col text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>

View File

@ -61,6 +61,7 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
submitText='Создать'
onSubmit={handleSubmit}
>
<div className='flex flex-col gap-3'>
<TextInput id='title' label='Полное название' type='text'
required
value={title}
@ -69,7 +70,7 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
<TextInput id='alias' label='Сокращение' type='text'
required
value={alias}
widthClass='max-w-sm'
dimensions='max-w-sm'
onChange={event => setAlias(event.target.value)}
/>
<TextArea id='comment' label='Комментарий'
@ -80,6 +81,7 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
value={common}
setValue={value => setCommon(value)}
/>
</div>
</Modal>
);
}

View File

@ -56,7 +56,7 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
canSubmit={validated}
onSubmit={handleSubmit}
>
<div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch'>
<div className='h-fit w-[35rem] px-2 mb-2 flex flex-col justify-stretch gap-3'>
<div className='flex justify-center w-full'>
<SelectSingle
className='my-2 min-w-[15rem] self-center'
@ -73,14 +73,12 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
spellCheck
onChange={event => setTerm(event.target.value)}
/>
<div className='mt-3'>
<RSInput id='expression' label='Формальное выражение'
editable
height='5.5rem'
value={expression}
onChange={value => setExpression(value)}
/>
</div>
<RSInput id='expression' label='Формальное выражение'
editable
height='5.5rem'
value={expression}
onChange={value => setExpression(value)}
/>
<TextArea id='definition' label='Текстовое определение'
placeholder='Лингвистическая интерпретация формального выражения'
rows={2}

View File

@ -36,14 +36,14 @@ function DlgDeleteCst({ hideWindow, selected, onDelete }: DlgDeleteCstProps) {
>
<div className='max-w-[60vw] min-w-[20rem]'>
<p>Выбраны к удалению: <b>{selected.length}</b></p>
<div className='px-3 border h-[9rem] overflow-y-auto whitespace-nowrap'>
<div className='px-3 border h-[9rem] mt-1 overflow-y-auto whitespace-nowrap'>
{selected.map(id => {
const cst = schema!.items.find(cst => cst.id === id);
return (cst && <p>{getCstLabel(cst)}</p>);
})}
</div>
<p className='mt-4'>Зависимые конституенты: <b>{expansion.length}</b></p>
<div className='px-3 border h-[9rem] overflow-y-auto whitespace-nowrap'>
<div className='mt-1 mb-3 px-3 border h-[9rem] overflow-y-auto whitespace-nowrap'>
{expansion.map(id => {
const cst = schema!.items.find(cst => cst.id === id);
return (cst && <p>{getCstLabel(cst)}</p>);

View File

@ -75,7 +75,7 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps
submitText='Применить'
>
<div className='flex gap-2'>
<div className='flex flex-col'>
<div className='flex flex-col gap-1'>
<h1>Преобразования</h1>
<Checkbox
label='Скрыть текст'
@ -102,7 +102,7 @@ function DlgGraphOptions({ hideWindow, initial, onConfirm }:DlgGraphOptionsProps
setValue={ value => setNoTransitive(value) }
/>
</div>
<div className='flex flex-col'>
<div className='flex flex-col gap-1'>
<h1>Типы конституент</h1>
<Checkbox
label={getCstTypeLabel(CstType.BASE)}

View File

@ -5,8 +5,8 @@ import SelectSingle from '../../components/Common/SelectSingle';
import TextInput from '../../components/Common/TextInput';
import { useRSForm } from '../../context/RSFormContext';
import { CstType, ICstRenameData } from '../../models/rsform';
import { createAliasFor, getCstTypeLabel, getCstTypePrefix } from '../../utils/staticUI';
import { SelectorCstType } from '../../utils/selectors';
import { createAliasFor, getCstTypeLabel, getCstTypePrefix } from '../../utils/staticUI';
interface DlgRenameCstProps
extends Pick<ModalProps, 'hideWindow'> {
@ -78,7 +78,7 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
<div>
<TextInput id='alias' label='Имя'
singleRow
widthClass='w-[7rem]'
dimensions='w-[7rem]'
value={alias}
onChange={event => setAlias(event.target.value)}
/>

View File

@ -45,7 +45,7 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
onSubmit={handleSubmit}
submitText='Загрузить'
>
<div className='flex flex-col items-center'>
<div className='flex flex-col items-start min-w-[20rem] max-w-[20rem]'>
<FileInput
label='Выбрать файл'
acceptType={EXTEOR_TRS_FILE}
@ -55,7 +55,7 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
label='Загружать название и комментарий'
value={loadMetadata}
setValue={value => setLoadMetadata(value)}
widthClass='w-fit pb-2'
dimensions='w-fit pb-2'
/>
</div>
</Modal>

View File

@ -136,41 +136,19 @@ function EditorConstituenta({
}
return (
<div className='flex items-stretch w-full gap-2 mb-2 justify-stretch'>
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r'>
<div className='relative'>
<div className='absolute top-0 left-0'>
<MiniButton
tooltip='Сохранить изменения'
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} color={isModified && isEnabled ? 'text-primary' : ''}/>}
onClick={() => handleSubmit()}
/>
</div>
</div>
<div className='relative'>
<div className='absolute top-0 right-0 flex justify-end'>
<MiniButton
tooltip='Удалить редактируемую конституенту'
disabled={!isEnabled}
onClick={handleDelete}
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-warning' : ''} />}
/>
<MiniButton
tooltip='Создать конституенты после данной'
disabled={!isEnabled}
onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-success' : ''} />}
/>
<div id='cst-help' className='flex items-center ml-[6px]'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#cst-help'>
<HelpConstituenta />
</ConceptTooltip>
</div>
</div>
<div className='flex items-center justify-center w-full gap-1 pr-10'>
<div className='flex w-full gap-2 mb-2 justify-stretch'>
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r'>
<div className='relative w-full'>
<div className='absolute top-0 right-0 flex items-start justify-between w-full'>
<MiniButton
tooltip='Редактировать словоформы термина'
disabled={!isEnabled}
dimensions='w-fit pl-[3.2rem] pt-[0.4rem]'
noHover
onClick={onEditTerm}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
<div className='flex items-center justify-center w-full gap-1'>
<div className='font-semibold w-fit'>
<span className=''>Конституента </span>
<span className='ml-4'>{alias}</span>
@ -183,17 +161,35 @@ function EditorConstituenta({
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
<div className='relative'>
<div className='absolute left-[3.2rem] top-[0.5rem]'>
<MiniButton
tooltip='Редактировать словоформы термина'
disabled={!isEnabled}
noHover
onClick={onEditTerm}
icon={<PenIcon size={4} color={isEnabled ? 'text-primary' : ''} />}
/>
</div>
<div className='flex justify-end min-w-fit'>
<MiniButton
tooltip='Сохранить изменения'
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={5} color={isModified && isEnabled ? 'text-primary' : ''}/>}
onClick={() => handleSubmit()}
/>
<MiniButton
tooltip='Создать конституенты после данной'
disabled={!isEnabled}
onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-success' : ''} />}
/>
<MiniButton
tooltip='Удалить редактируемую конституенту'
disabled={!isEnabled}
onClick={handleDelete}
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-warning' : ''} />}
/>
<div id='cst-help' className='px-1 py-1'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#cst-help' offset={4}>
<HelpConstituenta />
</ConceptTooltip>
</div>
</div>
</div>
<div className='flex flex-col gap-2 mt-1'>
<ReferenceInput id='term' label='Термин'
placeholder='Обозначение, используемое в текстовых определениях данной схемы'
rows={2}
@ -242,25 +238,25 @@ function EditorConstituenta({
onChange={event => setConvention(event.target.value)}
onFocus={() => setEditMode(EditMode.TEXT)}
/>
<div className='flex justify-center w-full mt-4 mb-2'>
<div className='flex justify-center w-full mt-2'>
<SubmitButton
text='Сохранить изменения'
disabled={!isModified || !isEnabled}
icon={<SaveIcon size={6} />}
/>
</div>
</form>
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
<div className='self-stretch w-full pb-1 border'>
<ViewSideConstituents
expression={expression}
baseHeight={UNFOLDED_HEIGHT}
activeID={activeID}
onOpenEdit={onOpenEdit}
/>
</div>}
</div>
);
</div>
</form>
{(windowSize.width ?? 0) >= SIDELIST_HIDE_THRESHOLD &&
<div className='self-stretch w-full border'>
<ViewSideConstituents
expression={expression}
baseHeight={UNFOLDED_HEIGHT}
activeID={activeID}
onOpenEdit={onOpenEdit}
/>
</div>}
</div>);
}
export default EditorConstituenta;

View File

@ -224,7 +224,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
return (<>
<div
id={`${prefixes.cst_list}${cst.alias}`}
className='w-full min-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
className='w-full min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
style={{
borderWidth: "1px",
borderColor: getCstStatusFgColor(cst.status, colors),
@ -251,7 +251,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
minSize: 150,
maxSize: 150,
enableHiding: true,
cell: props => <div className='text-sm min-w-[8.4rem]'>{props.getValue()}</div>
cell: props => <div className='text-sm min-w-[9.3rem] max-w-[9.3rem] break-words'>{props.getValue()}</div>
}),
columnHelper.accessor(cst => cst.term_resolved || cst.term_raw || '', {
id: 'term',
@ -265,7 +265,8 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
header: 'Формальное определение',
size: 1000,
minSize: 300,
maxSize: 1000
maxSize: 1000,
cell: props => <div className='break-words'>{props.getValue()}</div>
}),
columnHelper.accessor(cst => cst.definition_resolved || cst.definition_raw || '', {
id: 'definition',
@ -283,19 +284,14 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
maxSize: 500,
enableHiding: true,
cell: props => <div className='text-xs'>{props.getValue()}</div>
}),
})
], [colors]);
return (
<div className='w-full'>
<div
className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-y items-center h-[2.2rem] select-none clr-app'
>
<div className='mr-3 whitespace-nowrap'>
Выбраны
<span className='ml-2'>
{selected.length} из {schema?.stats?.count_all ?? 0}
</span>
<div className='sticky top-0 flex justify-start w-full gap-1 px-2 py-1 border-b items-center h-[2.2rem] select-none clr-app'>
<div className='mr-3 min-w-[9rem] whitespace-nowrap'>
Выбор {selected.length} из {schema?.stats?.count_all ?? 0}
</div>
<div className='flex items-center justify-start w-full gap-1'>
<Button
@ -342,7 +338,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
text={getCstTypePrefix(type)}
tooltip={getCstTypeShortcut(type)}
dense
widthClass='w-[1.4rem]'
dimensions='w-[1.4rem]'
disabled={!isEditable}
tabIndex={-1}
onClick={() => handleCreateCst(type)}
@ -378,7 +374,8 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
<p>Список пуст</p>
<p
className='cursor-pointer text-primary hover:underline'
onClick={() => handleCreateCst()}>
onClick={() => handleCreateCst()}
>
Создать новую конституенту
</p>
</span>

View File

@ -107,7 +107,8 @@ function EditorRSExpression({
}, []);
const EditButtons = useMemo(() => {
return (<div className='flex items-center justify-between w-full'>
return (
<div className='flex items-center justify-between w-full'>
<div className='text-sm w-fit'>
<div className='flex justify-start'>
<RSTokenButton id={TokenID.NT_DECLARATIVE_EXPR} onInsert={handleEdit}/>
@ -185,55 +186,54 @@ function EditorRSExpression({
}, [handleEdit]);
return (
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full min-h-[15.75rem]'>
<div className='relative w-full'>
<div className='absolute top-[-0.3rem] right-0'>
<StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/>
</div>
</div>
<RSInput innerref={rsInput}
className='text-lg'
height='10.1rem'
value={value}
editable={!disabled}
onChange={handleChange}
onFocus={handleFocusIn}
{...props}
/>
<div className='flex w-full gap-4 py-1 mt-1 justify-stretch'>
<div className='flex flex-col gap-2'>
<Button
tooltip='Проверить формальное выражение'
text='Проверить'
widthClass='h-full w-fit'
colorClass='clr-btn-default'
onClick={handleCheckExpression}
/>
</div>
{isActive && !disabled && EditButtons}
</div>
{ (isActive || loading || parseData) &&
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[4.2rem]'>
{ loading && <ConceptLoader size={6} />}
{ !loading && parseData &&
<ParsingResult
data={parseData}
onShowAST={ast => onShowAST(getCstExpressionPrefix(activeCst!) + value, ast)}
onShowError={onShowError}
/>}
{ !loading && !parseData &&
<input
disabled={true}
className='w-full h-full px-2 align-middle select-none clr-app'
placeholder='Результаты проверки выражения'
/>}
</div>}
<div className='flex flex-col items-start w-full min-h-[15.75rem]'>
<div className='relative w-full'>
<div className='absolute top-[-0.3rem] right-0'>
<StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/>
</div>
);
</div>
<RSInput innerref={rsInput}
className='text-lg'
height='10.1rem'
value={value}
editable={!disabled}
onChange={handleChange}
onFocus={handleFocusIn}
{...props}
/>
<div className='flex items-stretch w-full gap-4 py-1 mt-1 justify-stretch'>
<div>
<Button
tooltip='Проверить формальное выражение'
text='Проверить'
dimensions='h-full w-fit'
colorClass='clr-btn-default'
onClick={handleCheckExpression}
/>
</div>
{isActive && !disabled && EditButtons}
</div>
{ (isActive || loading || parseData) &&
<div className='w-full overflow-y-auto border mt-1 max-h-[14rem] min-h-[4.2rem]'>
{ loading && <ConceptLoader size={6} />}
{ !loading && parseData &&
<ParsingResult
data={parseData}
onShowAST={ast => onShowAST(getCstExpressionPrefix(activeCst!) + value, ast)}
onShowError={onShowError}
/>}
{ !loading && !parseData &&
<input
disabled={true}
className='w-full h-full px-2 align-middle select-none clr-app'
placeholder='Результаты проверки выражения'
/>}
</div>}
</div>);
}
export default EditorRSExpression;

View File

@ -13,8 +13,8 @@ import { CrownIcon, DownloadIcon, DumpBinIcon, HelpIcon, SaveIcon, ShareIcon } f
import { useAuth } from '../../context/AuthContext';
import { useRSForm } from '../../context/RSFormContext';
import { useUsers } from '../../context/UsersContext';
import { IRSFormCreateData } from '../../models/rsform';
import { LibraryItemType } from '../../models/library';
import { IRSFormCreateData } from '../../models/rsform';
interface EditorRSFormProps {
onDestroy: () => void
@ -81,39 +81,40 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
};
return (
<form onSubmit={handleSubmit} className='flex-grow max-w-[35.3rem] px-4 py-2 border-y border-r min-w-fit'>
<div className='relative w-full'>
<div className='absolute top-0 right-0 flex'>
<MiniButton
tooltip='Поделиться схемой'
icon={<ShareIcon size={5} color='text-primary'/>}
onClick={onShare}
/>
<MiniButton
tooltip='Скачать TRS файл'
icon={<DownloadIcon size={5} color='text-primary'/>}
onClick={onDownload}
/>
<MiniButton
tooltip={isClaimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
icon={<CrownIcon size={5} color={!isClaimable ? '' : 'text-success'}/>}
disabled={!isClaimable || !user}
onClick={onClaim}
/>
<MiniButton
tooltip='Удалить схему'
disabled={!isEditable}
onClick={onDestroy}
icon={<DumpBinIcon size={5} color={isEditable ? 'text-warning' : ''} />}
/>
<div id='rsform-help' className='py-1 ml-1'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#rsform-help'>
<HelpRSFormMeta />
</ConceptTooltip>
</div>
<form onSubmit={handleSubmit} className='flex-grow max-w-[35.3rem] px-4 py-2 border-y border-r min-w-fit'>
<div className='relative w-full'>
<div className='absolute top-0 right-0 flex'>
<MiniButton
tooltip='Поделиться схемой'
icon={<ShareIcon size={5} color='text-primary'/>}
onClick={onShare}
/>
<MiniButton
tooltip='Скачать TRS файл'
icon={<DownloadIcon size={5} color='text-primary'/>}
onClick={onDownload}
/>
<MiniButton
tooltip={isClaimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
icon={<CrownIcon size={5} color={!isClaimable ? '' : 'text-success'}/>}
disabled={!isClaimable || !user}
onClick={onClaim}
/>
<MiniButton
tooltip='Удалить схему'
disabled={!isEditable}
onClick={onDestroy}
icon={<DumpBinIcon size={5} color={isEditable ? 'text-warning' : ''} />}
/>
<div id='rsform-help' className='py-1 ml-1'>
<HelpIcon color='text-primary' size={5} />
</div>
<ConceptTooltip anchorSelect='#rsform-help'>
<HelpRSFormMeta />
</ConceptTooltip>
</div>
</div>
<div className='flex flex-col gap-3 mt-2'>
<TextInput id='title' label='Полное название' type='text'
required
value={title}
@ -124,7 +125,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
required
value={alias}
disabled={!isEditable}
widthClass='max-w-sm'
dimensions='max-w-sm'
onChange={event => setAlias(event.target.value)}
/>
<TextArea id='comment' label='Комментарий'
@ -135,12 +136,12 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
<div className='flex justify-between whitespace-nowrap'>
<Checkbox id='common' label='Общедоступная схема'
value={common}
widthClass='w-fit mt-3'
dimensions='w-fit'
disabled={!isEditable}
setValue={value => setCommon(value)}
/>
<Checkbox id='canonical' label='Неизменная схема'
widthClass='w-fit'
dimensions='w-fit'
value={canonical}
tooltip='Только администраторы могут присваивать схемам неизменный статус'
disabled={!isEditable || !isForceAdmin}
@ -148,36 +149,38 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
/>
</div>
<div className='flex items-center justify-between gap-1 py-2 mt-2'>
<SubmitButton
text='Сохранить изменения'
loading={processing}
disabled={!isModified || !isEditable}
icon={<SaveIcon size={6} />}
/>
</div>
<SubmitButton
text='Сохранить изменения'
loading={processing}
disabled={!isModified || !isEditable}
icon={<SaveIcon size={6} />}
dimensions='my-2 w-fit'
/>
<div className='flex justify-start mt-2'>
<label className='font-semibold'>Владелец:</label>
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
{getUserLabel(schema?.owner ?? null)}
</span>
<div className='flex flex-col gap-1'>
<div className='flex justify-start'>
<label className='font-semibold'>Владелец:</label>
<span className='min-w-[200px] ml-2 overflow-ellipsis overflow-hidden whitespace-nowrap'>
{getUserLabel(schema?.owner ?? null)}
</span>
</div>
<div className='flex justify-start'>
<label className='font-semibold'>Отслеживают:</label>
<span id='subscriber-count' className='ml-2'>
{ schema?.subscribers.length ?? 0 }
</span>
</div>
<div className='flex justify-start'>
<label className='font-semibold'>Дата обновления:</label>
<span className='ml-2'>{schema && new Date(schema?.time_update).toLocaleString(intl.locale)}</span>
</div>
<div className='flex justify-start'>
<label className='font-semibold'>Дата создания:</label>
<span className='ml-8'>{schema && new Date(schema?.time_create).toLocaleString(intl.locale)}</span>
</div>
</div>
<div className='flex justify-start mt-2'>
<label className='font-semibold'>Отслеживают:</label>
<span id='subscriber-count' className='ml-2'>
{ schema?.subscribers.length ?? 0 }
</span>
</div>
<div className='flex justify-start mt-2'>
<label className='font-semibold'>Дата обновления:</label>
<span className='ml-2'>{schema && new Date(schema?.time_update).toLocaleString(intl.locale)}</span>
</div>
<div className='flex justify-start mt-2'>
<label className='font-semibold'>Дата создания:</label>
<span className='ml-8'>{schema && new Date(schema?.time_create).toLocaleString(intl.locale)}</span>
</div>
</form>
</div>
</form>
);
}

View File

@ -376,25 +376,22 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div>}
<div className='flex items-center justify-between py-1'>
<div className='mr-3 whitespace-nowrap text-base'>
Выбраны
<span className='ml-1'>
{allSelected.length} из {schema?.stats?.count_all ?? 0}
</span>
<div className='mr-3 text-base'>
Выбор {allSelected.length} из {schema?.stats?.count_all ?? 0}
</div>
<div>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={!nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={handleDeleteCst}
/>
<div className='min-w-fit'>
<MiniButton
tooltip='Новая конституента'
icon={<SmallPlusIcon color='text-success' size={5}/>}
icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={5}/>}
disabled={!isEditable}
onClick={handleCreateCst}
/>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={handleDeleteCst}
/>
</div>
</div>
<div className='flex items-center w-full gap-1'>
@ -402,7 +399,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
icon={<FilterCogIcon size={7} />}
dense
tooltip='Настройки фильтрации узлов и связей'
widthClass='h-full'
dimensions='h-full'
onClick={() => setShowOptions(true)}
/>
<SelectSingle
@ -423,6 +420,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
value={layout ? { value: layout, label: mapLayoutLabels.get(layout) } : null}
onChange={data => handleChangeLayout(data?.value ?? SelectorGraphLayout[0].value)}
/>
<div className='flex flex-col gap-1 mt-2'>
<Checkbox
label='Скрыть текст'
value={noTerms}
@ -439,6 +437,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
value={orbit}
setValue={ value => setOrbit(value) }
/>
</div>
<Divider margins='mt-3 mb-2' />

View File

@ -73,7 +73,7 @@ function RSTabsMenu({
tooltip='Действия'
icon={<MenuIcon color='text-controls' size={5}/>}
borderClass=''
widthClass='h-full w-fit'
dimensions='h-full w-fit'
style={{outlineColor: 'transparent'}}
dense
onClick={schemaMenu.toggle}
@ -123,7 +123,7 @@ function RSTabsMenu({
<Button
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
borderClass=''
widthClass='h-full w-fit'
dimensions='h-full w-fit'
style={{outlineColor: 'transparent'}}
icon={<PenIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense
@ -169,7 +169,7 @@ function RSTabsMenu({
? <EyeIcon color='text-primary' size={5}/>
: <EyeOffIcon color='text-controls' size={5}/>
}
widthClass='h-full w-fit'
dimensions='h-full w-fit'
borderClass=''
style={{outlineColor: 'transparent'}}
dense

View File

@ -17,7 +17,7 @@ function RSTokenButton({ id, disabled, onInsert }: RSTokenButtonProps) {
onClick={() => onInsert(id)}
title={data.tooltip}
tabIndex={-1}
className={`px-1 cursor-pointer border rounded-none h-7 ${width} clr-outline clr-btn-clear`}
className={`px-1 cursor-pointer border rounded-none h-7 ${width} clr-outline clr-hover clr-btn-clear`}
>
{data.text && <span className='whitespace-nowrap'>{data.text}</span>}
</button>

View File

@ -121,7 +121,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
return (<>
<div
id={`${prefixes.cst_list}${cst.alias}`}
className='w-full px-1 text-center rounded-md whitespace-nowrap'
className='min-w-[3.1rem] max-w-[3.1rem] px-1 text-center rounded-md whitespace-nowrap'
style={{
borderWidth: '1px',
borderColor: getCstStatusFgColor(cst.status, colors),

View File

@ -61,7 +61,7 @@ function RegisterPage() {
<Form
title='Регистрация'
onSubmit={handleSubmit}
widthClass='w-[24rem]'
dimensions='w-[24rem]'
>
<TextInput id='username' label='Имя пользователя' type='text'
required
@ -104,12 +104,12 @@ function RegisterPage() {
<SubmitButton
text='Регистрировать'
loading={loading}
widthClass='min-w-[10rem]'
dimensions='min-w-[10rem]'
/>
<Button
text='Отмена'
onClick={() => handleCancel()}
widthClass='min-w-[10rem]'
dimensions='min-w-[10rem]'
/>
</div>
{ error && <BackendError error={error} />}

View File

@ -1,13 +1,25 @@
import axios from 'axios';
import { useEffect, useMemo, useState } from 'react';
import { toast } from 'react-toastify';
import BackendError from '../../components/BackendError';
import BackendError, { ErrorInfo } from '../../components/BackendError';
import SubmitButton from '../../components/Common/SubmitButton';
import TextInput from '../../components/Common/TextInput';
import { useAuth } from '../../context/AuthContext';
import { useConceptNavigation } from '../../context/NagivationContext';
import { IUserUpdatePassword } from '../../models/library';
function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
if (axios.isAxiosError(error) && error.response && error.response.status === 400) {
return (
<div className='text-sm select-text text-warning'>
Неверно введен старый пароль
</div>
);
} else {
return (<BackendError error={error} />);
}
}
function EditorPassword() {
const { navigateTo } = useConceptNavigation();
@ -50,7 +62,7 @@ function EditorPassword() {
return (
<div className='flex py-2 border-l-2 max-w-[14rem]'>
<form onSubmit={handleSubmit} className='flex flex-col justify-between px-6 min-w-fit'>
<div>
<div className='flex flex-col gap-3'>
<TextInput id='old_password'
type='password'
label='Старый пароль'
@ -74,7 +86,7 @@ function EditorPassword() {
}}
/>
</div>
{ error && <BackendError error={error} />}
{ error && <ProcessError error={error} />}
<div className='flex justify-center w-full'>
<SubmitButton
disabled={!canSubmit}

View File

@ -50,33 +50,31 @@ function EditorProfile() {
}
return (
<div className='flex py-2'>
<form onSubmit={handleSubmit} className='px-6 min-w-[18rem]'>
<div>
<TextInput id='username'
label='Логин'
tooltip='Логин изменить нельзя'
disabled={true}
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='first_name'
label='Имя'
value={first_name}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name' label='Фамилия' value={last_name} onChange={event => setLastName(event.target.value)}/>
<TextInput id='email' label='Электронная почта' value={email} onChange={event => setEmail(event.target.value)}/>
</div>
<div className='flex justify-center w-full mt-10'>
<SubmitButton
text='Сохранить данные'
loading={processing}
disabled={!isModified}
/>
</div>
</form>
</div>
<form onSubmit={handleSubmit} className='px-6 py-2 flex flex-col gap-8 min-w-[18rem]'>
<div className='flex flex-col gap-3'>
<TextInput id='username'
label='Логин'
tooltip='Логин изменить нельзя'
disabled={true}
value={username}
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='first_name'
label='Имя'
value={first_name}
onChange={event => setFirstName(event.target.value)}
/>
<TextInput id='last_name' label='Фамилия' value={last_name} onChange={event => setLastName(event.target.value)}/>
<TextInput id='email' label='Электронная почта' value={email} onChange={event => setEmail(event.target.value)}/>
</div>
<div className='flex justify-center w-full'>
<SubmitButton
text='Сохранить данные'
loading={processing}
disabled={!isModified}
/>
</div>
</form>
)
}

View File

@ -458,7 +458,7 @@ export function getTypificationLabel({isValid, resultType, args}: {
return 'N/A';
}
if (resultType === '' || resultType === 'LOGIC') {
resultType = 'Логический';
resultType = 'Logical';
}
if (args.length === 0) {
return resultType;