mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
UI improvements
This commit is contained in:
parent
f6c51a3c5f
commit
0fe2360886
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
navigate('/library?filter=common');
|
navigate('/library?filter=common');
|
||||||
} else if(!user.is_staff) {
|
} else if(!user.is_staff) {
|
||||||
navigate('/library?filter=personal');
|
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'>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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' />
|
||||||
|
|
|
@ -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]'>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue
Block a user