UI improvements

This commit is contained in:
IRBorisov 2023-08-08 23:04:21 +03:00
parent f6c51a3c5f
commit 0fe2360886
14 changed files with 59 additions and 46 deletions

View File

@ -6,14 +6,15 @@ import Button from './Button';
interface ModalProps { interface ModalProps {
title?: string title?: string
submitText?: string submitText?: string
readonly?: boolean
canSubmit?: boolean canSubmit?: boolean
hideWindow: () => void hideWindow: () => void
onSubmit: () => void onSubmit?: () => void
onCancel?: () => void onCancel?: () => void
children: React.ReactNode children: React.ReactNode
} }
function Modal({ title, hideWindow, onSubmit, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) { function Modal({ title, hideWindow, onSubmit, readonly, onCancel, canSubmit, children, submitText = 'Продолжить' }: ModalProps) {
const ref = useRef(null); const ref = useRef(null);
useEscapeKey(hideWindow); useEscapeKey(hideWindow);
@ -24,29 +25,32 @@ function Modal({ title, hideWindow, onSubmit, onCancel, canSubmit, children, sub
const handleSubmit = () => { const handleSubmit = () => {
hideWindow(); hideWindow();
onSubmit(); if (onSubmit) onSubmit();
}; };
return ( return (
<> <>
<div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal'> <div className='fixed top-0 left-0 z-50 w-full h-full opacity-50 clr-modal'>
</div> </div>
<div ref={ref} className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit h-fit z-[60] clr-card border shadow-md mb-[5rem]'> <div
ref={ref}
className='fixed bottom-1/2 left-1/2 translate-y-1/2 -translate-x-1/2 px-6 py-4 flex flex-col w-fit max-w-[95vw] h-fit z-[60] clr-card border shadow-md mb-[5rem]'
>
{ title && <h1 className='mb-2 text-xl font-bold text-center'>{title}</h1> } { title && <h1 className='mb-2 text-xl font-bold text-center'>{title}</h1> }
<div> <div className='max-h-[calc(95vh-15rem)] overflow-auto'>
{children} {children}
</div> </div>
<div className='flex justify-between w-full pt-4 mt-2 border-t-4'> <div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-4'>
<Button {!readonly && <Button
text={submitText} text={submitText}
widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit' widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
colorClass='clr-btn-primary' colorClass='clr-btn-primary'
disabled={!canSubmit} disabled={!canSubmit}
onClick={handleSubmit} onClick={handleSubmit}
autoFocus autoFocus
/> />}
<Button <Button
text='Отмена' text={readonly ? 'Закрыть' : 'Отмена'}
widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit' widthClass='min-w-[6rem] min-h-[2.6rem] w-fit h-fit'
onClick={handleCancel} onClick={handleCancel}
/> />

View File

@ -8,7 +8,7 @@ interface SubmitButtonProps {
function SubmitButton({ text = 'ОК', icon, disabled, loading = false }: SubmitButtonProps) { function SubmitButton({ text = 'ОК', icon, disabled, loading = false }: SubmitButtonProps) {
return ( return (
<button type='submit' <button type='submit'
className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`} className={`px-4 py-2 inline-flex items-center gap-2 align-middle justify-center font-bold disabled:cursor-not-allowed border rounded clr-btn-primary ${loading ? ' cursor-progress' : ''}`}
disabled={disabled ?? loading} disabled={disabled ?? loading}
> >
{icon && <span>{icon}</span>} {icon && <span>{icon}</span>}

View File

@ -28,7 +28,7 @@ function TextArea({
htmlFor={id} htmlFor={id}
/> />
<textarea id={id} <textarea id={id}
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 ' + widthClass} className={`px-3 py-2 mt-2 leading-tight border shadow clr-input ${widthClass}`}
rows={rows} rows={rows}
placeholder={placeholder} placeholder={placeholder}
required={required} required={required}

View File

@ -21,7 +21,7 @@ function TextInput({
htmlFor={id} htmlFor={id}
/> />
<input id={id} <input id={id}
className={'px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800 truncate hover:text-clip ' + widthClass} className={`px-3 py-2 mt-2 leading-tight border shadow truncate hover:text-clip clr-input ${widthClass}`}
required={required} required={required}
{...props} {...props}
/> />

View File

@ -1,11 +1,13 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { useAuth } from '../../context/AuthContext';
import { BellIcon, PlusIcon, SquaresIcon } from '../Icons'; import { BellIcon, PlusIcon, SquaresIcon } from '../Icons';
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
function UserTools() { function UserTools() {
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useAuth();
const navigateCreateRSForm = () => { navigate('/rsform-create'); }; const navigateCreateRSForm = () => { navigate('/rsform-create'); };
const navigateMyWork = () => { navigate('/library?filter=personal'); }; const navigateMyWork = () => { navigate('/library?filter=personal'); };
@ -24,7 +26,7 @@ function UserTools() {
/> />
</span> </span>
<NavigationButton icon={<SquaresIcon />} description='Мои схемы' onClick={navigateMyWork} /> <NavigationButton icon={<SquaresIcon />} description='Мои схемы' onClick={navigateMyWork} />
<NavigationButton icon={<BellIcon />} description='Уведомления' onClick={handleNotifications} /> { user && user.is_staff && <NavigationButton icon={<BellIcon />} description='Уведомления' onClick={handleNotifications} />}
</div> </div>
); );
} }

View File

@ -87,7 +87,7 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
const toggleTracking = useCallback( const toggleTracking = useCallback(
() => { () => {
toast('not implemented yet') toast.info('Отслеживание в разработке...')
}, []) }, [])
const update = useCallback( const update = useCallback(

View File

@ -51,6 +51,10 @@
@apply border-gray-400 dark:border-gray-300 bg-white dark:bg-gray-700 @apply border-gray-400 dark:border-gray-300 bg-white dark:bg-gray-700
} }
.clr-input {
@apply dark:bg-black bg-white disabled:bg-[#f0f2f7] dark:disabled:bg-gray-700
}
.clr-footer { .clr-footer {
@apply text-gray-600 bg-white border-gray-400 dark:bg-gray-700 dark:border-gray-300 dark:text-gray-300 @apply text-gray-600 bg-white border-gray-400 dark:bg-gray-700 dark:border-gray-300 dark:text-gray-300
} }
@ -68,11 +72,11 @@
} }
.clr-btn-primary { .clr-btn-primary {
@apply text-white bg-blue-400 hover:bg-blue-600 dark:bg-orange-600 dark:hover:bg-orange-400 disabled:bg-gray-400 dark:disabled:bg-gray-400 @apply text-white bg-blue-400 hover:bg-blue-600 dark:bg-orange-600 dark:hover:bg-orange-400 disabled:bg-gray-400 dark:disabled:bg-gray-600
} }
.clr-btn-default { .clr-btn-default {
@apply text-gray-500 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400 bg-gray-100 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400 @apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400 bg-[#f0f2f7] hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400
} }
/* Transparent button */ /* Transparent button */

View File

@ -1,3 +1,4 @@
import { useLayoutEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
@ -5,11 +6,14 @@ import { useAuth } from '../context/AuthContext';
function HomePage() { function HomePage() {
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useAuth(); const { user } = useAuth();
if (!user) {
navigate('/library?filter=common'); useLayoutEffect(() => {
} else if(!user.is_staff) { if (!user) {
navigate('/library?filter=personal'); navigate('/library?filter=common');
} } else if(!user.is_staff) {
navigate('/library?filter=personal');
}
}, [navigate, user])
return ( return (
<div className='flex flex-col items-center justify-center w-full py-2'> <div className='flex flex-col items-center justify-center w-full py-2'>

View File

@ -16,10 +16,6 @@ interface DlgShowASTProps {
function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) { function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
const { darkMode } = useConceptTheme(); const { darkMode } = useConceptTheme();
function handleSubmit() {
// Do nothing
}
const nodes: GraphNode[] = useMemo( const nodes: GraphNode[] = useMemo(
() => syntaxTree.map(node => { () => syntaxTree.map(node => {
return { return {
@ -46,14 +42,12 @@ function DlgShowAST({ hideWindow, syntaxTree, expression }: DlgShowASTProps) {
return ( return (
<Modal <Modal
hideWindow={hideWindow} hideWindow={hideWindow}
onSubmit={handleSubmit} readonly
submitText='Закрыть'
canSubmit={true}
> >
<div className='flex flex-col items-start gap-2'> <div className='flex flex-col items-start gap-2'>
<div className='w-full text-lg text-center'>{expression}</div> <div className='w-full text-lg text-center'>{expression}</div>
<div className='flex-wrap w-full h-full overflow-auto'> <div className='flex-wrap w-full h-full overflow-auto'>
<div className='relative w-[1040px] h-[600px] 2xl:w-[1680px] 2xl:h-[600px]'> <div className='relative w-[1040px] h-[600px] 2xl:w-[1680px] 2xl:h-[600px] max-h-full max-w-full'>
<GraphCanvas <GraphCanvas
nodes={nodes} nodes={nodes}
edges={edges} edges={edges}

View File

@ -114,7 +114,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
<div className='flex items-start justify-between'> <div className='flex items-start justify-between'>
<button type='submit' <button type='submit'
title='Сохранить изменения' title='Сохранить изменения'
className='px-1 py-1 font-bold rounded whitespace-nowrap disabled:cursor-not-allowed clr-btn-primary' className='px-1 py-1 font-bold border rounded whitespace-nowrap disabled:cursor-not-allowed clr-btn-primary'
disabled={!isModified || !isEnabled} disabled={!isModified || !isEnabled}
> >
<SaveIcon size={5} /> <SaveIcon size={5} />
@ -166,7 +166,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onOpenEdit, onDe
<p>- слева от ввода текста настраивается набор атрибутов конституенты</p> <p>- слева от ввода текста настраивается набор атрибутов конституенты</p>
<p>- справа от ввода текста настраивается список конституент, которые фильтруются</p> <p>- справа от ввода текста настраивается список конституент, которые фильтруются</p>
<p>- текущая конституента выделена цветом строки</p> <p>- текущая конституента выделена цветом строки</p>
<p>- двойнок клин / Alt + клик - выбор редактируемой конституенты</p> <p>- двойной клик / Alt + клик - выбор редактируемой конституенты</p>
<p>- при наведении на ID конституенты отображаются ее атрибуты</p> <p>- при наведении на ID конституенты отображаются ее атрибуты</p>
<p>- столбец "Описание" содержит один из непустых текстовых атрибутов</p> <p>- столбец "Описание" содержит один из непустых текстовых атрибутов</p>
<Divider margins='mt-2' /> <Divider margins='mt-2' />

View File

@ -210,13 +210,22 @@ function EditorRSExpression({
return ( return (
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'> <div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full'>
<div className='relative w-full'>
<div className='absolute top-[-0.3rem] right-0'>
<StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/>
</div>
</div>
<Label <Label
text={label} text={label}
required={false} required={false}
htmlFor={id} htmlFor={id}
/> />
<textarea id={id} ref={expressionCtrl} <textarea id={id} ref={expressionCtrl}
className='w-full px-3 py-2 mt-2 leading-tight border shadow dark:bg-gray-800' className='w-full px-3 py-2 mt-2 leading-tight border shadow clr-input'
rows={6} rows={6}
placeholder={placeholder} placeholder={placeholder}
value={value} value={value}
@ -228,23 +237,15 @@ function EditorRSExpression({
/> />
<div className='flex w-full gap-4 py-1 mt-1 justify-stretch'> <div className='flex w-full gap-4 py-1 mt-1 justify-stretch'>
<div className='flex flex-col gap-2'> <div className='flex flex-col gap-2'>
{isActive && <StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/>}
<Button <Button
tooltip='Проверить формальное выражение' tooltip='Проверить формальное выражение'
text='Проверить' text='Проверить'
widthClass='h-full w-fit'
colorClass='clr-btn-default'
onClick={handleCheckExpression} onClick={handleCheckExpression}
/> />
</div> </div>
{isActive && EditButtons} {isActive && EditButtons}
{!isActive && <StatusBar
isModified={isModified}
constituenta={activeCst}
parseData={parseData}
/>}
</div> </div>
{ (loading || parseData) && { (loading || parseData) &&
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'> <div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[7rem]'>

View File

@ -35,7 +35,7 @@ function MatchModePicker({ value, onChange }: MatchModePickerProps) {
<p><b>везде:</b> искать во всех атрибутах</p> <p><b>везде:</b> искать во всех атрибутах</p>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={() => handleChange(CstMatchMode.EXPR)}> <DropdownButton onClick={() => handleChange(CstMatchMode.EXPR)}>
<p><b>ФВ:</b> искать в формальных выражениях</p> <p><b>выраж:</b> искать в формальных выражениях</p>
</DropdownButton> </DropdownButton>
<DropdownButton onClick={() => handleChange(CstMatchMode.TERM)}> <DropdownButton onClick={() => handleChange(CstMatchMode.TERM)}>
<p><b>термин:</b> искать в терминах</p> <p><b>термин:</b> искать в терминах</p>

View File

@ -24,8 +24,8 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
const data = mapStatusInfo.get(status)!; const data = mapStatusInfo.get(status)!;
return ( return (
<div title={data.tooltip} <div title={data.tooltip}
className={'min-h-[2rem] min-w-[6rem] font-semibold inline-flex border rounded-lg items-center justify-center align-middle ' + data.color}> className={`text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center justify-center align-middle ${data.color}`}>
{data.text} Статус: [ {data.text} ]
</div> </div>
) )
} }

View File

@ -5,6 +5,10 @@ import { defineConfig } from 'vite'
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: { server: {
port: 3000 port: 3000,
// https: {
// key: fs.readFileSync('cert/portal-key.pem'),
// cert: fs.readFileSync('cert/portal-cert.pem'),
// }
} }
}) })