Major UI improvement and colors refactoring

This commit is contained in:
IRBorisov 2023-09-03 18:26:50 +03:00
parent 8a596ccccf
commit c18e34d2eb
56 changed files with 569 additions and 356 deletions

View File

@ -1,5 +1,7 @@
export default { export default {
plugins: { plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {}, tailwindcss: {},
autoprefixer: {}, autoprefixer: {},
}, },

View File

@ -1,8 +1,8 @@
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom'; import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import ConceptToaster from './components/ConceptToaster';
import Footer from './components/Footer'; import Footer from './components/Footer';
import Navigation from './components/Navigation/Navigation'; import Navigation from './components/Navigation/Navigation';
import ToasterThemed from './components/ToasterThemed';
import { useConceptTheme } from './context/ThemeContext'; import { useConceptTheme } from './context/ThemeContext';
import CreateRSFormPage from './pages/CreateRSFormPage'; import CreateRSFormPage from './pages/CreateRSFormPage';
import HomePage from './pages/HomePage'; import HomePage from './pages/HomePage';
@ -20,7 +20,7 @@ function Root() {
return ( return (
<div className='w-screen antialiased clr-app'> <div className='w-screen antialiased clr-app'>
<Navigation /> <Navigation />
<ToasterThemed <ConceptToaster
className='mt-[4rem] text-sm' className='mt-[4rem] text-sm'
autoClose={3000} autoClose={3000}
draggable={false} draggable={false}

View File

@ -59,7 +59,7 @@ function DescribeError(error: ErrorInfo) {
function BackendError({ error }: BackendErrorProps) { function BackendError({ error }: BackendErrorProps) {
return ( return (
<div className='py-2 text-sm font-semibold text-red-600 dark:text-red-400'> <div className='py-2 text-sm font-semibold select-text text-warning'>
{DescribeError(error)} {DescribeError(error)}
</div> </div>
); );

View File

@ -6,7 +6,7 @@ interface CardProps {
function Card({ title, widthClass = 'min-w-fit', children }: CardProps) { function Card({ title, widthClass = 'min-w-fit', children }: CardProps) {
return ( return (
<div className={`border shadow-md py-2 clr-card px-6 ${widthClass}`}> <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> } { title && <h1 className='mb-2 text-xl font-bold whitespace-nowrap'>{title}</h1> }
{children} {children}
</div> </div>

View File

@ -1,14 +1,18 @@
import { ThreeDots } from 'react-loader-spinner'; import { ThreeDots } from 'react-loader-spinner';
interface LoaderProps { import { useConceptTheme } from '../../context/ThemeContext';
interface ConceptLoaderProps {
size?: number size?: number
} }
export function Loader({size=10}: LoaderProps) { export function ConceptLoader({size=10}: ConceptLoaderProps) {
const {colors} = useConceptTheme()
return ( return (
<div className='flex justify-center w-full h-full'> <div className='flex justify-center w-full h-full'>
<ThreeDots <ThreeDots
color='rgb(96 165 250)' color={colors.bgSelected}
height={size*10} height={size*10}
width={size*10} width={size*10}
radius={size} radius={size}

View File

@ -9,7 +9,7 @@ extends Omit<PropsWithRef<SelectProps<T>>, 'noDataLabel'> {
function ConceptSelect<T extends object | string>({ className, ...props }: ConceptSelectProps<T>) { function ConceptSelect<T extends object | string>({ className, ...props }: ConceptSelectProps<T>) {
return ( return (
<Select <Select
className={`overflow-x-ellipsis whitespace-nowrap ${className}`} className={`overflow-x-ellipsis whitespace-nowrap clr-border clr-input outline-none ${className}`}
{...props} {...props}
noDataLabel='Список пуст' noDataLabel='Список пуст'
/> />

View File

@ -9,7 +9,7 @@ extends Omit<TabProps, 'className'> {
function ConceptTab({ children, className, ...otherProps }: ConceptTabProps) { function ConceptTab({ children, className, ...otherProps }: ConceptTabProps) {
return ( return (
<Tab <Tab
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab whitespace-nowrap ${className}`} className={`px-2 py-1 h-full text-sm hover:cursor-pointer clr-tab whitespace-nowrap ${className}`}
{...otherProps} {...otherProps}
> >
{children} {children}

View File

@ -6,8 +6,8 @@ interface DividerProps {
function Divider({ vertical, margins = 'mx-2' }: DividerProps) { function Divider({ vertical, margins = 'mx-2' }: DividerProps) {
return ( return (
<> <>
{vertical && <div className={`${margins} border-x-2 clr-border`} />} {vertical && <div className={`${margins} border-x-2`} />}
{!vertical && <div className={`${margins} border-y-2 clr-border`} />} {!vertical && <div className={`${margins} border-y-2`} />}
</> </>
); );
} }

View File

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

View File

@ -13,10 +13,10 @@ function DropdownCheckbox({ tooltip, onChange, disabled, ...props }: DropdownChe
return ( return (
<div <div
title={tooltip} title={tooltip}
className={`px-4 py-1 text-left overflow-ellipsis ${behavior} whitespace-nowrap`} className={`px-4 py-1 text-left overflow-ellipsis ${behavior} w-full whitespace-nowrap`}
> >
<Checkbox <Checkbox
widthClass='w-fit' widthClass='w-full'
disabled={disabled} disabled={disabled}
onChange={onChange} onChange={onChange}
{...props} {...props}

View File

@ -14,7 +14,7 @@ function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) {
style={{height: 0, paddingBottom: `${pxHeight}px`, paddingLeft: `${pxWidth}px`}} style={{height: 0, paddingBottom: `${pxHeight}px`, paddingLeft: `${pxWidth}px`}}
> >
<iframe <iframe
className='absolute top-0 left-0 clr-border' className='absolute top-0 left-0 border'
style={{minHeight: `${pxHeight}px`, minWidth: `${pxWidth}px`}} style={{minHeight: `${pxHeight}px`, minWidth: `${pxWidth}px`}}
width={`${pxWidth}px`} width={`${pxWidth}px`}
height={`${pxHeight}px`} height={`${pxHeight}px`}

View File

@ -9,7 +9,7 @@ extends Omit<React.DetailedHTMLProps<LabelHTMLAttributes<HTMLLabelElement>, HTML
function Label({ text, required, title, className, ...props }: LabelProps) { function Label({ text, required, title, className, ...props }: LabelProps) {
return ( return (
<label <label
className={`${className} text-sm font-semibold`} className={`text-sm font-semibold ${className}`}
title={ (required && !title) ? 'обязательное поле' : title } title={ (required && !title) ? 'обязательное поле' : title }
{...props} {...props}
> >

View File

@ -37,16 +37,16 @@ function Modal({
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 clr-modal-backdrop' />
<div <div
ref={ref} 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]' 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-app 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'>{title}</h1> }
<div className='max-h-[calc(95vh-15rem)]'> <div className='max-h-[calc(95vh-15rem)]'>
{children} {children}
</div> </div>
<div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-4 clr-border'> <div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-2'>
{!readonly && {!readonly &&
<Button <Button
text={submitText} text={submitText}

View File

@ -26,7 +26,7 @@ function TextArea({
/> />
<textarea id={id} <textarea id={id}
title={tooltip} title={tooltip}
className={`px-3 py-2 mt-2 leading-tight border shadow ${colorClass} ${widthClass}`} className={`px-3 py-2 mt-2 leading-tight border shadow clr-outline ${colorClass} ${widthClass}`}
rows={rows} rows={rows}
required={required} required={required}
{...props} {...props}

View File

@ -27,7 +27,7 @@ function TextInput({
/> />
<input id={id} <input id={id}
title={tooltip} title={tooltip}
className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip ${colorClass} ${singleRow ? '' : 'mt-2 ' + widthClass}`} className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip clr-outline ${colorClass} ${singleRow ? '' : 'mt-2 ' + widthClass}`}
required={required} required={required}
{...props} {...props}
/> />

View File

@ -6,9 +6,9 @@ function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
reportError(error); reportError(error);
return ( return (
<div className='flex flex-col items-center antialiased clr-app' role='alert'> <div className='flex flex-col items-center antialiased clr-app' role='alert'>
<h1 className='text-lg font-semibold'>Something went wrong!</h1> <h1 className='text-lg font-semibold'>Что-то пошло не так!</h1>
{ error } { error }
<Button onClick={resetErrorBoundary} text='Try again' /> <Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
</div> </div>
); );
} }

View File

@ -4,7 +4,7 @@ import { urls } from '../utils/constants';
function Footer() { function Footer() {
return ( return (
<footer className='z-50 px-4 pt-2 pb-4 text-sm select-none whitespace-nowrap clr-footer'> <footer className='z-50 px-4 py-2 text-sm select-none whitespace-nowrap clr-footer'>
<div className='justify-center w-full mx-auto'> <div className='justify-center w-full mx-auto'>
<div className='mb-2 text-center'> <div className='mb-2 text-center'>
<Link className='mx-2 hover:underline' to='/library' tabIndex={-1}>Библиотека</Link> <Link className='mx-2 hover:underline' to='/library' tabIndex={-1}>Библиотека</Link>

View File

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

View File

@ -18,7 +18,7 @@ function HelpLibrary() {
</div> </div>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<EducationIcon size={4}/> <EducationIcon size={4}/>
<p>Аттрибут <b>неизменяемая</b> выделяет неизменяемые стандартные схемы.</p> <p>Аттрибут <b>неизменная</b> выделяет стандартные схемы.</p>
</div> </div>
</div> </div>
); );

View File

@ -4,19 +4,19 @@ import TextURL from '../Common/TextURL';
function HelpMain() { function HelpMain() {
return ( return (
<div className='w-full'> <div className='flex flex-col w-full'>
<h1>Портал</h1> <h1>Портал</h1>
<p className='lg:text-left indent-10'>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p> <p className='lg:text-justify'>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p>
<p className='lg:text-left indent-10'>Навигация по порталу осуществляется верхнюю панель или ссылки в "подвале" страницы. Их можно скрыть с помощью кнопки в правом верхнем углу</p> <p className='mt-2 lg:text-justify'>Навигация по порталу осуществляется верхнюю панель или ссылки в "подвале" страницы. Их можно скрыть с помощью кнопки в правом верхнем углу.</p>
<p className='lg:text-left indent-10'>В меню пользователя (правый верхний угол) редактирование данных пользователя и изменение цветовой темы.</p> <p className='mt-2 lg:text-justify'>В меню пользователя (правый верхний угол) доступно редактирование данных пользователя и изменение цветовой темы.</p>
<p className='mt-4 mb-1 text-center'><b>Основные разделы</b></p> <p className='mt-4 mb-1 text-center'><b>Основные разделы</b></p>
<li className='lg:text-left indent-5'><TextURL text='Библиотека' href='/library' /> - все схемы доступные пользователю</li> <li className='text-left'><TextURL text='Библиотека' href='/library' /> - все схемы доступные пользователю</li>
<li className='lg:text-left indent-5'><TextURL text='Общие схемы' href={`/library?filter=${LibraryFilterStrategy.COMMON}`} /> - общедоступные схемы и инструменты поиска и навигации по ним</li> <li className='text-left'><TextURL text='Общие схемы' href={`/library?filter=${LibraryFilterStrategy.COMMON}`} /> - общедоступные схемы и инструменты поиска и навигации по ним</li>
<li className='lg:text-left indent-5'><TextURL text='Мои схемы' href={`/library?filter=${LibraryFilterStrategy.PERSONAL}`} /> - отслеживаемые и редактируемые схемы. Основной рабочий раздел</li> <li className='text-left'><TextURL text='Мои схемы' href={`/library?filter=${LibraryFilterStrategy.PERSONAL}`} /> - отслеживаемые и редактируемые схемы. Основной рабочий раздел</li>
<li className='lg:text-left indent-5'><TextURL text='Профиль' href='/profile' /> - данные пользователя и смена пароля</li> <li className='text-left'><TextURL text='Профиль' href='/profile' /> - данные пользователя и смена пароля</li>
<p className='mt-4 mb-1 text-center'><b>Поддержка</b></p> <p className='mt-4 mb-1 text-center'><b>Поддержка</b></p>
<p className='lg:text-left indent-10'>Портал разрабатывается <TextURL text='Центром Концепт' href={urls.concept}/> и является проектом с открытым исходным кодом, доступным на <TextURL text='Github' href={urls.gitrepo}/>.</p> <p className='lg:text-justify'>Портал разрабатывается <TextURL text='Центром Концепт' href={urls.concept}/> и является проектом с открытым исходным кодом, доступным на <TextURL text='Github' href={urls.gitrepo}/>.</p>
<p className='lg:text-left indent-10'>Ждём Ваши пожелания по доработке, найденные ошибки и иные предложения по адресу <TextURL href={urls.mailportal} text='portal@acconcept.ru'/></p> <p className='mt-2 lg:text-justify'>Ждём Ваши пожелания по доработке, найденные ошибки и иные предложения по адресу <TextURL href={urls.mailportal} text='portal@acconcept.ru'/></p>
<p></p> <p></p>
</div> </div>
); );

View File

@ -300,3 +300,20 @@ export function GithubIcon(props: IconProps) {
</IconSVG> </IconSVG>
); );
} }
export function MeshIcon(props: IconProps) {
return (
<IconSVG viewbox='0 0 1024 1024' {...props}>
<path d='M872 394c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H400V152c0-4.4-3.6-8-8-8h-64c-4.4 0-8 3.6-8 8v166H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v236H152c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h168v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h228v166c0 4.4 3.6 8 8 8h64c4.4 0 8-3.6 8-8V706h164c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8H708V394h164zM628 630H400V394h228v236z' />
</IconSVG>
);
}
export function InDoor(props: IconProps) {
return (
<IconSVG viewbox='0 0 24 24' {...props}>
<path fill='none' d='M0 0h24v24H0z' />
<path d='M10 11H4V3a1 1 0 011-1h14a1 1 0 011 1v18a1 1 0 01-1 1H5a1 1 0 01-1-1v-8h6v3l5-4-5-4v3z' />
</IconSVG>
);
}

View File

@ -1,7 +1,7 @@
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { EducationIcon, LibraryIcon } from '../Icons'; import { EducationIcon, LibraryIcon, PlusIcon } from '../Icons';
import Logo from './Logo' import Logo from './Logo'
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
import UserMenu from './UserMenu'; import UserMenu from './UserMenu';
@ -12,13 +12,14 @@ function Navigation () {
const navigateLibrary = () => navigate('/library'); const navigateLibrary = () => navigate('/library');
const navigateHelp = () => navigate('/manuals'); const navigateHelp = () => navigate('/manuals');
const navigateCreateNew = () => navigate('/rsform-create');
return ( return (
<nav className='sticky top-0 left-0 right-0 z-50 select-none h-fit'> <nav className='sticky top-0 left-0 right-0 z-50 select-none h-fit'>
{!noNavigation && {!noNavigation &&
<button <button
title='Скрыть навигацию' title='Скрыть навигацию'
className='absolute top-0 right-0 z-[60] w-[1.2rem] border-b-2 border-l-2 clr-nav rounded-none' className='absolute top-0 right-0 z-[60] w-[1.2rem] h-[3rem] border-b-2 border-l-2 clr-btn-nav rounded-none'
onClick={toggleNoNavigation} onClick={toggleNoNavigation}
> >
<p>{'>'}</p><p>{'>'}</p> <p>{'>'}</p><p>{'>'}</p>
@ -26,18 +27,23 @@ function Navigation () {
{noNavigation && {noNavigation &&
<button <button
title='Показать навигацию' title='Показать навигацию'
className='absolute top-0 right-0 z-[60] px-1 h-[1.6rem] border-b-2 border-l-2 clr-nav rounded-none' className='absolute top-0 right-0 z-[60] px-1 h-[1.6rem] border-b-2 border-l-2 clr-btn-nav rounded-none'
onClick={toggleNoNavigation} onClick={toggleNoNavigation}
> >
{''} {''}
</button>} </button>}
{!noNavigation && {!noNavigation &&
<div className='flex items-center justify-between py-1 pl-2 pr-6 border-b-2 rounded-none clr-nav'> <div className='flex items-stretch justify-between pl-2 pr-[0.8rem] border-b-2 rounded-none h-[3rem]'>
<div className='flex items-center justify-start'> <div className='flex items-center justify-start'>
<Logo title='КонцептПортал' /> <Logo title='КонцептПортал' />
</div> </div>
<div className='flex items-center'> <div className='flex items-center h-full'>
<div className='flex items-center pl-2'> <NavigationButton
text='Новая схема'
description='Создать новую схему'
icon={<PlusIcon />}
onClick={navigateCreateNew}
/>
<NavigationButton <NavigationButton
text='Библиотека' text='Библиотека'
description='Библиотека концептуальных схем' description='Библиотека концептуальных схем'
@ -52,7 +58,6 @@ function Navigation () {
/> />
<UserMenu /> <UserMenu />
</div> </div>
</div>
</div>} </div>}
</nav> </nav>
); );

View File

@ -12,7 +12,7 @@ function NavigationButton({ id, icon, description, onClick, text }: NavigationBu
title={description} title={description}
type='button' type='button'
onClick={onClick} onClick={onClick}
className='flex gap-1 p-2 mr-1 rounded-lg min-w-fit whitespace-nowrap clr-btn-nav' className={`flex items-center h-full gap-1 ${text ? 'px-2' : 'px-4'} mr-1 min-w-fit whitespace-nowrap clr-btn-nav`}
> >
{icon && <span>{icon}</span>} {icon && <span>{icon}</span>}
{text && <span className='font-semibold'>{text}</span>} {text && <span className='font-semibold'>{text}</span>}

View File

@ -1,26 +1,27 @@
import { Link } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import useDropdown from '../../hooks/useDropdown'; import useDropdown from '../../hooks/useDropdown';
import { UserIcon } from '../Icons'; import { InDoor, UserIcon } from '../Icons';
import NavigationButton from './NavigationButton'; import NavigationButton from './NavigationButton';
import UserDropdown from './UserDropdown'; import UserDropdown from './UserDropdown';
function LoginRef() {
return (
<Link to='login' className='inline-block h-full px-1 py-2 font-semibold rounded-lg hover:underline clr-btn-nav text-primary'>
Войти...
</Link>
);
}
function UserMenu() { function UserMenu() {
const navigate = useNavigate();
const { user } = useAuth(); const { user } = useAuth();
const menu = useDropdown(); const menu = useDropdown();
const navigateLogin = () => navigate('/login');
return ( return (
<div ref={menu.ref}> <div ref={menu.ref} className='h-full'>
<div className='w-[4.2rem] flex justify-end'> <div className='flex items-center justify-end h-full w-fit'>
{ !user && <LoginRef />} { !user &&
<NavigationButton
text='Войти...'
description='Перейти на страницу логина'
icon={<InDoor />}
onClick={navigateLogin}
/>}
{ user && { user &&
<NavigationButton <NavigationButton
icon={<UserIcon />} icon={<UserIcon />}

View File

@ -3,8 +3,8 @@ import { Decoration, EditorView } from '@codemirror/view';
import { bracketsDarkT, bracketsLightT } from '../../utils/color'; import { bracketsDarkT, bracketsLightT } from '../../utils/color';
const matchingMark = Decoration.mark({class: "cc-matchingBracket"}); const matchingMark = Decoration.mark({class: 'cc-matchingBracket'});
const nonmatchingMark = Decoration.mark({class: "cc-nonmatchingBracket"}); const nonmatchingMark = Decoration.mark({class: 'cc-nonmatchingBracket'});
function bracketRender(match: MatchResult) { function bracketRender(match: MatchResult) {
const decorations = []; const decorations = [];

View File

@ -69,10 +69,9 @@ function RSInput({
theme: 'light', theme: 'light',
settings: { settings: {
fontFamily: 'inherit', fontFamily: 'inherit',
background: editable ? colors.input : colors.inputDisabled, background: editable ? colors.bgInput : colors.bgDisabled,
foreground: colors.text, foreground: colors.fgDefault,
selection: colors.selection, selection: colors.bgHover
caret: '#5d00ff',
}, },
styles: [ styles: [
{ tag: t.name, class: 'text-[#b266ff] cursor-default' }, // GlobalID { tag: t.name, class: 'text-[#b266ff] cursor-default' }, // GlobalID
@ -90,10 +89,9 @@ function RSInput({
theme: 'dark', theme: 'dark',
settings: { settings: {
fontFamily: 'inherit', fontFamily: 'inherit',
background: editable ? colors.input : colors.inputDisabled, background: editable ? colors.bgInput : colors.bgDisabled,
foreground: colors.text, foreground: colors.fgDefault,
selection: colors.selection, selection: colors.bgHover
caret: '#ffaa00'
}, },
styles: [ styles: [
{ tag: t.name, class: 'text-[#dfbfff] cursor-default' }, // GlobalID { tag: t.name, class: 'text-[#dfbfff] cursor-default' }, // GlobalID
@ -139,12 +137,13 @@ function RSInput({
}, [thisRef]); }, [thisRef]);
return ( return (
<div className={`w-full ${cursor}`}> <div className={`flex flex-col w-full ${cursor}`}>
{label && {label &&
<Label <Label
text={label} text={label}
required={false} required={false}
htmlFor={id} htmlFor={id}
className='mb-2'
/>} />}
<CodeMirror id={id} <CodeMirror id={id}
ref={thisRef} ref={thisRef}

View File

@ -1,4 +1,4 @@
import {LRLanguage} from "@codemirror/language" import {LRLanguage} from '@codemirror/language'
import { parser } from './parser'; import { parser } from './parser';

View File

@ -1,12 +1,12 @@
import { Extension } from "@codemirror/state"; import { Extension } from '@codemirror/state';
import { hoverTooltip } from "@codemirror/view"; import { hoverTooltip } from '@codemirror/view';
import { IConstituenta } from '../../utils/models'; import { IConstituenta } from '../../utils/models';
import { getCstTypificationLabel } from '../../utils/staticUI'; import { getCstTypificationLabel } from '../../utils/staticUI';
function createTooltipFor(cst: IConstituenta) { function createTooltipFor(cst: IConstituenta) {
const dom = document.createElement('div'); const dom = document.createElement('div');
dom.className = 'overflow-y-auto border shadow-md max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-20 text-sm clr-border px-2 py-2'; dom.className = 'overflow-y-auto border shadow-md max-h-[25rem] max-w-[25rem] min-w-[10rem] w-fit z-20 text-sm px-2 py-2';
const alias = document.createElement('p'); const alias = document.createElement('p');
alias.innerHTML = `<b>${cst.alias}:</b> ${getCstTypificationLabel(cst)}`; alias.innerHTML = `<b>${cst.alias}:</b> ${getCstTypificationLabel(cst)}`;
dom.appendChild(alias); dom.appendChild(alias);

View File

@ -12,10 +12,10 @@ function RequireAuth({ children }: RequireAuthProps) {
<> <>
{user && children} {user && children}
{!user && {!user &&
<div className='flex flex-col items-center mt-2 gap-1'> <div className='flex flex-col items-center gap-1 mt-2'>
<p><b>Данная страница доступна только зарегистрированным пользователям</b></p> <p><b>Данная страница доступна только зарегистрированным пользователям</b></p>
<p className='mb-2'>Пожалуйста войдите в систему</p> <p className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в систему' href='/login'/> <TextURL text='Войти в Портал' href='/login'/>
<TextURL text='Зарегистрироваться' href='/signup'/> <TextURL text='Зарегистрироваться' href='/signup'/>
<TextURL text='Начальная страница' href='/'/> <TextURL text='Начальная страница' href='/'/>
</div> </div>

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
function useWindowSize() { function useWindowSize() {
const isClient = typeof window === "object"; const isClient = typeof window === 'object';
function getSize() { function getSize() {
return { return {
@ -20,8 +20,8 @@ function useWindowSize() {
function handleResize() { function handleResize() {
setWindowSize(getSize()); setWindowSize(getSize());
} }
window.addEventListener("resize", handleResize); window.addEventListener('resize', handleResize);
return () => window.removeEventListener("resize", handleResize); return () => window.removeEventListener('resize', handleResize);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);

View File

@ -2,8 +2,75 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.rdt_TableCell{ :root {
font-size: 0.875rem; /* Light Theme */
--cl-bg-120: #ffffff;
--cl-bg-100: #f9fafb;
--cl-bg-80: #f3f4f6;
--cl-bg-60: #e5e7eb;
--cl-bg-40: #d1d5db;
--cl-fg-40: #8c8c8c;
--cl-fg-60: #777777;
--cl-fg-80: #333333;
--cl-fg-100: #000000;
--cl-prim-bg-100: #3377ff;
--cl-prim-bg-80: #ccddff;
--cl-prim-bg-60: #e0ebff;
--cl-prim-fg-60: #1a63ff;
--cl-prim-fg-80: #0051ff;
--cl-prim-fg-100: #ffffff;
--cl-red-bg-100: #ffe5e5;
--cl-red-fg-100: #dc2626;
--cl-green-fg-100: #4ade80;
/* Dark Theme */
--cd-bg-120: #0d0d0d;
--cd-bg-100: #181818;
--cd-bg-80: #272727;
--cd-bg-60: #383838;
--cd-bg-40: #595959;
--cd-fg-40: #878792;
--cd-fg-60: #bcbcc2;
--cd-fg-80: #d4d4d8;
--cd-fg-100: #e4e4e7;
--cd-prim-bg-100: #e66000;
--cd-prim-bg-80: #b36800;
--cd-prim-bg-60: #663c00;
--cd-prim-fg-60: #ffa666;
--cd-prim-fg-80: #ff6a00;
--cd-prim-fg-100: #ffffff;
--cd-red-bg-100: #4d0000;
--cd-red-fg-100: #ff334b;
--cd-green-fg-100: #22c55e;
}
:root {
color: var(--cl-fg-100);
border-color: var(--cl-bg-40);
background-color: var(--cl-bg-100);
&.dark {
color: var(--cd-fg-100);
border-color: var(--cd-bg-40);
background-color: var(--cd-bg-100);
}
}
:focus {
outline-width: 2px;
outline-style: solid;
outline-color: transparent;
.dark & {
outline-color: transparent;
}
} }
[data-color-scheme="dark"] { [data-color-scheme="dark"] {
@ -14,127 +81,213 @@
color-scheme: light; color-scheme: light;
} }
.react-dropdown-select-item {
@apply bg-gray-100 dark:bg-gray-600 dark:text-zinc-200 hover:bg-gray-50 hover:text-gray-700 dark:hover:text-white dark:hover:bg-gray-500
}
.cm-editor {
@apply border shadow rounded clr-border px-1
}
.cm-editor.cm-focused {
@apply border shadow rounded outline-2 outline
}
@layer components { @layer components {
:root {
@apply bg-gray-50;
}
.dark {
@apply text-zinc-200 bg-[#181818]
}
h1 { h1 {
@apply text-lg font-bold text-center @apply text-lg font-bold text-center
} }
.border { .border {
@apply clr-border rounded @apply rounded
} }
.text-btn { .clr-modal-backdrop {
@apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400 opacity: 0.75;
} }
.clr-border { :is(.clr-border,
@apply border-gray-300 dark:border-[#434343] .border, .border-x, .border-y, .border-b, .border-t, .border-l, .border-r,
.border-2, .border-x-2, .border-y-2, .border-b-2, .border-t-2, .border-l-2, .border-r-2,
.divide-x, .divide-y, .divide-x-2, .divide-y-2
) {
border-color: var(--cl-bg-40);
.dark & {
border-color: var(--cd-bg-40);
}
} }
.clr-border-nav { :is(.clr-app,
@apply border-gray-400 dark:border-[#434343] .clr-footer,
.clr-modal-backdrop,
.clr-btn-nav,
.clr-checkbox
) {
background-color: var(--cl-bg-100);
.dark & {
background-color: var(--cd-bg-100);
}
} }
.clr-app { :is(.clr-input
@apply bg-gray-50 dark:bg-[#181818] ) {
background-color: var(--cl-bg-120);
.dark & {
background-color: var(--cd-bg-120);
}
} }
.clr-bg-pop { :is(.clr-controls,
@apply bg-gray-100 dark:bg-[#272727] dark:border-[#434343] .clr-btn-default
) {
background-color: var(--cl-bg-80);
.dark & {
background-color: var(--cd-bg-80);
}
} }
.clr-modal { :is(.clr-primary,
@apply bg-gray-300 dark:bg-[#272727] .clr-btn-primary,
.clr-checkbox:checked
) {
color: var(--cl-prim-fg-100);
background-color: var(--cl-prim-bg-100);
.dark & {
color: var(--cd-prim-fg-100);
background-color: var(--cd-prim-bg-100);
}
} }
.clr-nav { :is(.clr-selected
@apply border-gray-400 dark:border-[#434343] bg-white dark:bg-[#181818] ) {
color: var(--cl-fg-100);
background-color: var(--cl-prim-bg-80);
.dark & {
color: var(--cd-fg-100);
background-color: var(--cd-prim-bg-80);
}
} }
.clr-input { :is(.clr-disabled,
@apply dark:bg-[#181818] bg-white disabled:bg-[#c6c6c6] dark:disabled:bg-[#181818] .clr-input,
.clr-btn-default,
.clr-btn-primary
):disabled {
color: var(--cl-fg-60);
background-color: var(--cl-bg-60);
.dark & {
color: var(--cd-fg-60);
background-color: var(--cd-bg-60);
}
}
:is(.clr-hover,
.clr-tab,
.clr-btn-nav,
.clr-btn-default,
.clr-btn-primary,
.clr-btn-clear
):hover:not(:disabled) {
color: var(--cl-fg-100);
background-color: var(--cl-prim-bg-60);
.dark & {
color: var(--cd-fg-100);
background-color: var(--cd-prim-bg-60);
}
}
:is(.clr-outline
):focus {
outline-width: 2px;
outline-style: solid;
outline-color: var(--cl-bg-40);
.dark & {
outline-color: var(--cd-bg-40);
}
}
:is(.text-primary,
.text-url
) {
color: var(--cl-prim-fg-80);
.dark & {
color: var(--cd-prim-fg-80);
}
} }
.clr-footer { .clr-footer {
@apply clr-app text-gray-600 border-gray-400 dark:border-[#434343] dark:text-[#aaaaaa] color: var(--cl-fg-40);
.dark & {
color: var(--cd-fg-40);
} }
.clr-card {
@apply bg-gray-50 dark:bg-[#272727]
}
.clr-tab {
@apply clr-border text-gray-700 dark:text-zinc-200 hover:bg-blue-200 dark:hover:bg-[#EA580C]
}
.clr-hover {
@apply hover:bg-gray-200 hover:text-gray-700 dark:hover:text-white dark:hover:bg-gray-500
} }
.clr-btn-nav { .clr-btn-nav {
@apply text-gray-500 hover:text-gray-900 dark:text-gray-200 dark:hover:text-white focus:ring-gray-300 dark:focus:ring-gray-400 hover:bg-gray-100 dark:hover:bg-gray-600 color: var(--cl-fg-60);
.dark & {
color: var(--cd-fg-60);
}
} }
.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-600
}
.clr-btn-green {
@apply text-white bg-green-400 hover:bg-green-600 dark:bg-green-600 dark:hover:bg-green-400 dark:text-black disabled:bg-gray-400 dark:disabled:bg-gray-600
}
.clr-btn-blue {
@apply text-white bg-blue-400 hover:bg-blue-600 dark:bg-blue-600 dark:hover:bg-blue-400 dark:text-black disabled:bg-gray-400 dark:disabled:bg-gray-600
}
.clr-btn-default {
@apply bg-[#f0f2f7] hover:bg-gray-300 dark:bg-[#434343] dark:hover:bg-[#606060] text-btn
}
/* Transparent button */
.clr-btn-clear { .clr-btn-clear {
@apply dark:disabled:text-zinc-400 disabled:text-gray-400 text-gray-500 dark:text-zinc-200 color: var(--cl-fg-60);
&:disabled {
color: var(--cl-fg-40);
}
.dark & {
color: var(--cd-fg-60);
&:disabled {
color: var(--cd-fg-40);
}
}
} }
.clr-checkbox { .clr-warning {
@apply bg-white dark:bg-[#181818] checked:bg-blue-700 dark:checked:bg-orange-500 background-color: var(--cl-red-bg-100);
.dark & {
background-color: var(--cd-red-bg-100);
}
} }
.clr-input-red { .text-warning {
@apply bg-red-300 dark:bg-red-700 color: var(--cl-red-fg-100);
.dark & {
color: var(--cd-red-fg-100);
}
} }
.text-url { .text-success {
@apply hover:text-blue-600 text-blue-400 dark:text-orange-500 dark:hover:text-orange-300 color: var(--cl-green-fg-100);
.dark & {
color: var(--cd-green-fg-100);
}
}
}
.cm-editor {
border-color: var(--cl-bg-40);
.dark & {
border-color: var(--cd-bg-40);
}
@apply border shadow rounded px-1
}
.cm-editor.cm-focused {
border-color: var(--cl-bg-40);
outline-color: var(--cl-bg-40);
.dark & {
border-color: var(--cd-bg-40);
outline-color: var(--cd-bg-40);
}
@apply border shadow rounded outline-2 outline
}
.rdt_TableCell{
font-size: 0.875rem;
}
.react-dropdown-select-item {
color: var(--cl-fg-100);
border-color: var(--cl-bg-40);
background-color: var(--cl-bg-100);
&:hover {
background-color: var(--cl-prim-bg-60);
}
.dark & {
color: var(--cd-fg-100);
border-color: var(--cd-bg-40);
background-color: var(--cd-bg-100);
&:hover {
background-color: var(--cd-prim-bg-60);
} }
.text-red {
@apply text-red-600 dark:text-red-400
}
.text-green {
@apply text-green-400 dark:text-green-500
}
.text-primary {
@apply text-blue-600 dark:text-orange-400
} }
} }

View File

@ -38,7 +38,7 @@ function CreateRSFormPage() {
} }
function handleCancel() { function handleCancel() {
if (location.key !== "default") { if (location.key !== 'default') {
navigate(-1); navigate(-1);
} else { } else {
navigate('/library'); navigate('/library');

View File

@ -50,7 +50,7 @@ function PickerStrategy({ value, onChange }: PickerStrategyProps) {
onChange={() => handleChange(LibraryFilterStrategy.CANONICAL)} onChange={() => handleChange(LibraryFilterStrategy.CANONICAL)}
value={value === LibraryFilterStrategy.CANONICAL} value={value === LibraryFilterStrategy.CANONICAL}
label='Неизменные' label='Неизменные'
tooltip='Отображать только неизменные схемы' tooltip='Отображать только стандартные схемы'
/> />
<DropdownCheckbox <DropdownCheckbox
onChange={() => handleChange(LibraryFilterStrategy.PERSONAL)} onChange={() => handleChange(LibraryFilterStrategy.PERSONAL)}

View File

@ -4,10 +4,9 @@ import { useNavigate } from 'react-router-dom';
import ConceptDataTable from '../../components/Common/ConceptDataTable'; import ConceptDataTable from '../../components/Common/ConceptDataTable';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import MiniButton from '../../components/Common/MiniButton';
import TextURL from '../../components/Common/TextURL'; import TextURL from '../../components/Common/TextURL';
import HelpLibrary from '../../components/Help/HelpLibrary'; import HelpLibrary from '../../components/Help/HelpLibrary';
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon, PlusIcon } from '../../components/Icons'; import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
import { useUsers } from '../../context/UsersContext'; import { useUsers } from '../../context/UsersContext';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
@ -26,10 +25,6 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
const openRSForm = (item: ILibraryItem) => navigate(`/rsforms/${item.id}`); const openRSForm = (item: ILibraryItem) => navigate(`/rsforms/${item.id}`);
function handleCreateNew() {
navigate('/rsform-create');
}
const columns = useMemo( const columns = useMemo(
() => [ () => [
{ {
@ -43,9 +38,9 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
className='flex items-center justify-start gap-1' className='flex items-center justify-start gap-1'
id={`${prefixes.library_list}${item.id}`} id={`${prefixes.library_list}${item.id}`}
> >
{user && user.subscriptions.includes(item.id) && <p title="Отслеживаемая"><EyeIcon size={3}/></p>} {user && user.subscriptions.includes(item.id) && <p title='Отслеживаемая'><EyeIcon size={3}/></p>}
{item.is_common && <p title="Общедоступная"><GroupIcon size={3}/></p>} {item.is_common && <p title='Общедоступная'><GroupIcon size={3}/></p>}
{item.is_canonical && <p title="Неизменяемая"><EducationIcon size={3}/></p>} {item.is_canonical && <p title='Неизменная'><EducationIcon size={3}/></p>}
</div> </div>
</>); </>);
}, },
@ -91,16 +86,9 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
return ( return (
<div> <div>
<div className='relative w-full'> <div className='relative w-full'>
<div className='absolute top-0 left-0 z-20 flex gap-1 mt-2 ml-1'> <div className='absolute top-0 left-0 z-20 flex gap-1 mt-1 ml-5'>
<MiniButton
onClick={handleCreateNew}
tooltip='Создать схему'
noHover
disabled={!user || !user.id}
icon={<PlusIcon color={!user || !user.id ? '' : 'text-primary'} size={5} />}
/>
<div id='library-help' className='py-2'> <div id='library-help' className='py-2'>
<HelpIcon color='text-primary' size={5} /> <HelpIcon color='text-primary' size={6} />
</div> </div>
<ConceptTooltip anchorSelect='#library-help'> <ConceptTooltip anchorSelect='#library-help'>
<div className='max-w-[35rem]'> <div className='max-w-[35rem]'>

View File

@ -1,7 +1,7 @@
import { useLayoutEffect, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import BackendError from '../../components/BackendError' import BackendError from '../../components/BackendError'
import { Loader } from '../../components/Common/Loader' import { ConceptLoader } from '../../components/Common/ConceptLoader'
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { ILibraryFilter, ILibraryItem } from '../../utils/models'; import { ILibraryFilter, ILibraryItem } from '../../utils/models';
import SearchPanel from './SearchPanel'; import SearchPanel from './SearchPanel';
@ -20,7 +20,7 @@ function LibraryPage() {
return ( return (
<div className='w-full'> <div className='w-full'>
{ library.loading && <Loader /> } { library.loading && <ConceptLoader /> }
{ library.error && <BackendError error={library.error} />} { library.error && <BackendError error={library.error} />}
{ !library.loading && library.items && { !library.loading && library.items &&
<div className='flex flex-col w-full'> <div className='flex flex-col w-full'>

View File

@ -1,15 +1,30 @@
import axios from 'axios';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import BackendError from '../components/BackendError'; import BackendError, { ErrorInfo } from '../components/BackendError';
import Form from '../components/Common/Form'; import Form from '../components/Common/Form';
import SubmitButton from '../components/Common/SubmitButton'; import SubmitButton from '../components/Common/SubmitButton';
import TextInput from '../components/Common/TextInput'; import TextInput from '../components/Common/TextInput';
import TextURL from '../components/Common/TextURL'; import TextURL from '../components/Common/TextURL';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { useConceptTheme } from '../context/ThemeContext';
import { IUserLoginData } from '../utils/models'; import { IUserLoginData } from '../utils/models';
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>
);
} else {
return ( <BackendError error={error} />);
}
}
function LoginPage() { function LoginPage() {
const {mainHeight} = useConceptTheme();
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const search = useLocation().search; const search = useLocation().search;
@ -36,7 +51,7 @@ function LoginPage() {
password: password password: password
}; };
login(data, () => { login(data, () => {
if (location.key !== "default") { if (location.key !== 'default') {
navigate(-1); navigate(-1);
} else { } else {
navigate('/library'); navigate('/library');
@ -46,10 +61,12 @@ function LoginPage() {
} }
return ( return (
<div className='flex justify-center w-full'> <div className='flex items-center justify-center w-full select-none' style={{minHeight: mainHeight}}>
<div className='py-2'> { user { user &&
? <b>{`Вы вошли в систему как ${user.username}`}</b> <div className='py-2 font-semibold'>
: {`Вы вошли в систему как ${user.username}`}
</div>}
{ !user &&
<Form <Form
title='Вход в Портал' title='Вход в Портал'
onSubmit={handleSubmit} onSubmit={handleSubmit}
@ -82,9 +99,9 @@ function LoginPage() {
<TextURL text='Восстановить пароль...' href='/restore-password' /> <TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' /> <TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div> </div>
{ error && <BackendError error={error} />} { error && <ProcessError error={error} />}
</Form> </Form>
}</div> }
</div> </div>
); );
} }

View File

@ -9,13 +9,13 @@ interface TopicsListProps {
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) { function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
return ( return (
<div className='sticky top-0 left-0 border-r border-b min-w-[13rem] pt-2 select-none flex flex-col clr-bg-pop clr-border'> <div className='sticky top-0 left-0 border-r border-b min-w-[13rem] pt-2 select-none flex flex-col clr-controls'>
<div className='mb-2 font-bold text-center'>Справка</div> <div className='mb-2 font-semibold text-center'>Справка</div>
{ [... mapTopicInfo.entries()].map( { [... mapTopicInfo.entries()].map(
([topic, info], index) => { ([topic, info], index) => {
return ( return (
<div key={`${prefixes.topic_list}${index}`} <div key={`${prefixes.topic_list}${index}`}
className={`px-3 py-1 border-y cursor-pointer hover:bg-blue-200 dark:hover:bg-[#EA580C] clr-border ${activeTopic === topic ? 'font-bold bg-blue-200 dark:bg-[#EA580C] ' : ''}`} className={`px-3 py-1 border-y cursor-pointer clr-hover ${activeTopic === topic ? 'font-semibold clr-selected ' : ''}`}
title={info.tooltip} title={info.tooltip}
onClick={() => onChangeTopic(topic)} onClick={() => onChangeTopic(topic)}
> >

View File

@ -74,7 +74,6 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
/> />
<RSInput id='expression' label='Формальное выражение' <RSInput id='expression' label='Формальное выражение'
editable editable
className='mt-2'
height='5.5rem' height='5.5rem'
value={expression} value={expression}
onChange={value => setExpression(value)} onChange={value => setExpression(value)}

View File

@ -133,7 +133,7 @@ function EditorConstituenta({
return ( return (
<div className='flex items-stretch w-full gap-2 mb-2 justify-stretch'> <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 clr-border'> <form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r'>
<div className='relative'> <div className='relative'>
<div className='absolute top-0 left-0'> <div className='absolute top-0 left-0'>
<MiniButton <MiniButton
@ -151,13 +151,13 @@ function EditorConstituenta({
tooltip='Удалить редактируемую конституенту' tooltip='Удалить редактируемую конституенту'
disabled={!isEnabled} disabled={!isEnabled}
onClick={handleDelete} onClick={handleDelete}
icon={<DumpBinIcon size={5} color={isEnabled ? 'text-red' : ''} />} icon={<DumpBinIcon size={5} color={isEnabled ? 'text-warning' : ''} />}
/> />
<MiniButton <MiniButton
tooltip='Создать конституенты после данной' tooltip='Создать конституенты после данной'
disabled={!isEnabled} disabled={!isEnabled}
onClick={handleCreateCst} onClick={handleCreateCst}
icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-green' : ''} />} icon={<SmallPlusIcon size={5} color={isEnabled ? 'text-success' : ''} />}
/> />
<div id='cst-help' className='flex items-center ml-[6px]'> <div id='cst-help' className='flex items-center ml-[6px]'>
<HelpIcon color='text-primary' size={5} /> <HelpIcon color='text-primary' size={5} />
@ -167,7 +167,7 @@ function EditorConstituenta({
</ConceptTooltip> </ConceptTooltip>
</div> </div>
</div> </div>
<div className='flex items-center justify-center w-full pr-10'> <div className='flex items-center justify-center w-full gap-1 pr-10'>
<div className='font-semibold w-fit'> <div className='font-semibold w-fit'>
<span className=''>Конституента </span> <span className=''>Конституента </span>
<span className='ml-4'>{alias}</span> <span className='ml-4'>{alias}</span>
@ -194,6 +194,7 @@ function EditorConstituenta({
<TextArea id='typification' label='Типизация' <TextArea id='typification' label='Типизация'
rows={1} rows={1}
value={typification} value={typification}
colorClass='clr-app'
disabled disabled
/> />
<EditorRSExpression id='expression' label='Формальное выражение' <EditorRSExpression id='expression' label='Формальное выражение'

View File

@ -6,7 +6,7 @@ import ConceptDataTable from '../../components/Common/ConceptDataTable';
import ConceptTooltip from '../../components/Common/ConceptTooltip'; import ConceptTooltip from '../../components/Common/ConceptTooltip';
import Divider from '../../components/Common/Divider'; import Divider from '../../components/Common/Divider';
import HelpRSFormItems from '../../components/Help/HelpRSFormItems'; import HelpRSFormItems from '../../components/Help/HelpRSFormItems';
import { ArrowDownIcon, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons'; import { ArrowDownIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, MeshIcon, SmallPlusIcon } from '../../components/Icons';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
import { prefixes } from '../../utils/constants'; import { prefixes } from '../../utils/constants';
@ -90,7 +90,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
// Generate new names for all constituents // Generate new names for all constituents
function handleReindex() { function handleReindex() {
resetAliases(() => toast.success('Переиндексация конституент успешна')); resetAliases(() => toast.success('Имена конституент обновлены'));
} }
function handleCreateCst(type?: CstType) { function handleCreateCst(type?: CstType) {
@ -185,7 +185,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
return (<> return (<>
<div <div
id={`${prefixes.cst_list}${cst.alias}`} id={`${prefixes.cst_list}${cst.alias}`}
className='w-full text-center rounded-md' className='w-full px-1 text-center rounded-md whitespace-nowrap'
style={{backgroundColor: getCstStatusColor(cst.status, colors)}} style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
> >
{cst.alias} {cst.alias}
@ -284,22 +284,22 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
/> />
<Button <Button
tooltip='Удалить выбранные' tooltip='Удалить выбранные'
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={6}/>} icon={<DumpBinIcon color={isEditable && !nothingSelected ? 'text-warning' : ''} size={6}/>}
disabled={!isEditable || nothingSelected} disabled={!isEditable || nothingSelected}
dense dense
onClick={handleDelete} onClick={handleDelete}
/> />
<Divider vertical margins='my-1' /> <Divider vertical margins='my-1' />
<Button <Button
tooltip='Переиндексировать имена' tooltip='Сбросить имена'
icon={<ArrowsRotateIcon color='text-primary' size={6}/>} icon={<MeshIcon color={isEditable ? 'text-primary': ''} size={6}/>}
dense dense
disabled={!isEditable} disabled={!isEditable}
onClick={handleReindex} onClick={handleReindex}
/> />
<Button <Button
tooltip='Новая конституента' tooltip='Новая конституента'
icon={<SmallPlusIcon color='text-green' size={6}/>} icon={<SmallPlusIcon color={isEditable ? 'text-success': ''} size={6}/>}
dense dense
disabled={!isEditable} disabled={!isEditable}
onClick={() => handleCreateCst()} onClick={() => handleCreateCst()}
@ -320,7 +320,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
<div id='items-table-help'> <div id='items-table-help'>
<HelpIcon color='text-primary' size={6} /> <HelpIcon color='text-primary' size={6} />
</div> </div>
<ConceptTooltip anchorSelect='#items-table-help'> <ConceptTooltip anchorSelect='#items-table-help' offset={30}>
<HelpRSFormItems /> <HelpRSFormItems />
</ConceptTooltip> </ConceptTooltip>
</div> </div>

View File

@ -2,7 +2,7 @@ import { ReactCodeMirrorRef } from '@uiw/react-codemirror';
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Button from '../../components/Common/Button'; import Button from '../../components/Common/Button';
import { Loader } from '../../components/Common/Loader'; import { ConceptLoader } from '../../components/Common/ConceptLoader';
import RSInput from '../../components/RSInput'; import RSInput from '../../components/RSInput';
import { TextWrapper } from '../../components/RSInput/textEditing'; import { TextWrapper } from '../../components/RSInput/textEditing';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
@ -187,7 +187,7 @@ function EditorRSExpression({
return ( return (
<div className='flex flex-col items-start [&:not(:first-child)]:mt-3 w-full min-h-[15.75rem]'> <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='relative w-full'>
<div className='absolute top-[-0.1rem] right-0'> <div className='absolute top-[-0.3rem] right-0'>
<StatusBar <StatusBar
isModified={isModified} isModified={isModified}
constituenta={activeCst} constituenta={activeCst}
@ -196,7 +196,7 @@ function EditorRSExpression({
</div> </div>
</div> </div>
<RSInput innerref={rsInput} <RSInput innerref={rsInput}
className='mt-2 text-lg' className='text-lg'
height='10.1rem' height='10.1rem'
value={value} value={value}
editable={!disabled} editable={!disabled}
@ -218,7 +218,7 @@ function EditorRSExpression({
</div> </div>
{ (isActive || loading || parseData) && { (isActive || loading || parseData) &&
<div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[4.2rem]'> <div className='w-full overflow-y-auto border mt-2 max-h-[14rem] min-h-[4.2rem]'>
{ loading && <Loader size={6} />} { loading && <ConceptLoader size={6} />}
{ !loading && parseData && { !loading && parseData &&
<ParsingResult <ParsingResult
data={parseData} data={parseData}

View File

@ -80,7 +80,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
}; };
return ( return (
<form onSubmit={handleSubmit} className='flex-grow max-w-[35.3rem] px-4 py-2 border-y border-r clr-border min-w-fit'> <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='relative w-full'>
<div className='absolute top-0 right-0 flex'> <div className='absolute top-0 right-0 flex'>
<MiniButton <MiniButton
@ -95,7 +95,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
/> />
<MiniButton <MiniButton
tooltip={isClaimable ? 'Стать владельцем' : 'Невозможно стать владельцем' } tooltip={isClaimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
icon={<CrownIcon size={5} color={!isClaimable ? '' : 'text-green'}/>} icon={<CrownIcon size={5} color={!isClaimable ? '' : 'text-success'}/>}
disabled={!isClaimable || !user} disabled={!isClaimable || !user}
onClick={onClaim} onClick={onClaim}
/> />
@ -103,7 +103,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, isModified, setIsModified,
tooltip='Удалить схему' tooltip='Удалить схему'
disabled={!isEditable} disabled={!isEditable}
onClick={onDestroy} onClick={onDestroy}
icon={<DumpBinIcon size={5} color={isEditable ? 'text-red' : ''} />} icon={<DumpBinIcon size={5} color={isEditable ? 'text-warning' : ''} />}
/> />
<div id='rsform-help' className='py-1 ml-1'> <div id='rsform-help' className='py-1 ml-1'>
<HelpIcon color='text-primary' size={5} /> <HelpIcon color='text-primary' size={5} />

View File

@ -66,7 +66,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const { darkMode, colors, noNavigation } = useConceptTheme(); const { darkMode, colors, noNavigation } = useConceptTheme();
const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d'); const [ layout, setLayout ] = useLocalStorage<LayoutTypes>('graph_layout', 'treeTd2d');
const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'none'); const [ coloringScheme, setColoringScheme ] = useLocalStorage<ColoringScheme>('graph_coloring', 'type');
const [ orbit, setOrbit ] = useState(false); const [ orbit, setOrbit ] = useState(false);
const [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true); const [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true);
@ -355,7 +355,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
initial={getOptions()} initial={getOptions()}
onConfirm={handleChangeOptions} onConfirm={handleChangeOptions}
/>} />}
<div className='flex flex-col border-r border-b min-w-[13.5rem] max-w-min px-2 pb-2 text-sm select-none clr-border' style={{height: canvasHeight}}> <div className='flex flex-col border-r border-b min-w-[13.5rem] max-w-min px-2 pb-2 text-sm select-none' style={{height: canvasHeight}}>
{hoverCst && {hoverCst &&
<div className='relative'> <div className='relative'>
<InfoConstituenta <InfoConstituenta
@ -374,13 +374,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
<div> <div>
<MiniButton <MiniButton
tooltip='Удалить выбранные' tooltip='Удалить выбранные'
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={5}/>} icon={<DumpBinIcon color={!nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isEditable || nothingSelected} disabled={!isEditable || nothingSelected}
onClick={handleDeleteCst} onClick={handleDeleteCst}
/> />
<MiniButton <MiniButton
tooltip='Новая конституента' tooltip='Новая конституента'
icon={<SmallPlusIcon color='text-green' size={5}/>} icon={<SmallPlusIcon color='text-success' size={5}/>}
disabled={!isEditable} disabled={!isEditable}
onClick={handleCreateCst} onClick={handleCreateCst}
/> />
@ -460,7 +460,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div> </div>
</div> </div>
</div> </div>
<div className='w-full h-full overflow-auto border'> <div className='w-full h-full overflow-auto'>
<div <div
className='relative' className='relative'
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}} style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}

View File

@ -6,7 +6,7 @@ import { toast } from 'react-toastify';
import BackendError from '../../components/BackendError'; import BackendError from '../../components/BackendError';
import ConceptTab from '../../components/Common/ConceptTab'; import ConceptTab from '../../components/Common/ConceptTab';
import { Loader } from '../../components/Common/Loader'; import { ConceptLoader } from '../../components/Common/ConceptLoader';
import { useLibrary } from '../../context/LibraryContext'; import { useLibrary } from '../../context/LibraryContext';
import { useRSForm } from '../../context/RSFormContext'; import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext'; import { useConceptTheme } from '../../context/ThemeContext';
@ -91,12 +91,18 @@ function RSTabs() {
const navigateTo = useCallback( const navigateTo = useCallback(
(tab: RSTabID, activeID?: number) => { (tab: RSTabID, activeID?: number) => {
if (!schema) {
return;
}
if (activeID) { if (activeID) {
navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, { navigate(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`, {
replace: tab === activeTab && tab !== RSTabID.CST_EDIT replace: tab === activeTab && tab !== RSTabID.CST_EDIT
}); });
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
activeID = schema.items[0].id;
navigate(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`, { replace: true });
} else { } else {
navigate(`/rsforms/${schema!.id}?tab=${tab}`); navigate(`/rsforms/${schema.id}?tab=${tab}`);
} }
}, [navigate, schema, activeTab]); }, [navigate, schema, activeTab]);
@ -272,7 +278,7 @@ function RSTabs() {
return ( return (
<div className='w-full'> <div className='w-full'>
{ loading && <Loader /> } { loading && <ConceptLoader /> }
{ error && <BackendError error={error} />} { error && <BackendError error={error} />}
{ schema && !loading && <> { schema && !loading && <>
{showUpload && {showUpload &&
@ -311,9 +317,9 @@ function RSTabs() {
selectedIndex={activeTab} selectedIndex={activeTab}
onSelect={onSelectTab} onSelect={onSelectTab}
defaultFocus={true} defaultFocus={true}
selectedTabClassName='font-bold bg-blue-200 dark:bg-[#EA580C]' selectedTabClassName='font-semibold clr-selected'
> >
<TabList className='flex items-start pl-2 border-b border-r-2 select-none w-fit clr-bg-pop clr-border'> <TabList className='flex items-start pl-2 border-b border-r-2 select-none justify-stretch w-fit clr-controls h-[1.9rem]'>
<RSTabsMenu <RSTabsMenu
onDownload={onDownloadSchema} onDownload={onDownloadSchema}
onDestroy={onDestroySchema} onDestroy={onDestroySchema}

View File

@ -67,7 +67,7 @@ function RSTabsMenu({
} }
return ( return (
<div className='flex items-stretch w-fit'> <div className='flex items-stretch h-full w-fit'>
<div ref={schemaMenu.ref}> <div ref={schemaMenu.ref}>
<Button <Button
tooltip='Действия' tooltip='Действия'
@ -99,13 +99,13 @@ function RSTabsMenu({
</DropdownButton> </DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleUpload}> <DropdownButton disabled={!isEditable} onClick={handleUpload}>
<div className='inline-flex items-center justify-start gap-2'> <div className='inline-flex items-center justify-start gap-2'>
<UploadIcon color={isEditable ? 'text-red' : ''} size={4}/> <UploadIcon color={isEditable ? 'text-warning' : ''} size={4}/>
<p>Загрузить из Экстеора</p> <p>Загрузить из Экстеора</p>
</div> </div>
</DropdownButton> </DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleDelete}> <DropdownButton disabled={!isEditable} onClick={handleDelete}>
<span className='inline-flex items-center justify-start gap-2'> <span className='inline-flex items-center justify-start gap-2'>
<DumpBinIcon color={isEditable ? 'text-red' : ''} size={4} /> <DumpBinIcon color={isEditable ? 'text-warning' : ''} size={4} />
<p>Удалить схему</p> <p>Удалить схему</p>
</span> </span>
</DropdownButton> </DropdownButton>
@ -122,7 +122,7 @@ function RSTabsMenu({
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')} tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
borderClass='' borderClass=''
widthClass='h-full w-fit' widthClass='h-full w-fit'
icon={<PenIcon size={5} color={isEditable ? 'text-green' : 'text-red'}/>} icon={<PenIcon size={5} color={isEditable ? 'text-success' : 'text-warning'}/>}
dense dense
onClick={editMenu.toggle} onClick={editMenu.toggle}
/> />
@ -134,7 +134,7 @@ function RSTabsMenu({
tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''} tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''}
> >
<div className='inline-flex items-center gap-1 justify-normal'> <div className='inline-flex items-center gap-1 justify-normal'>
<span className={isOwned ? 'text-green' : ''}><CrownIcon size={4} /></span> <span className={isOwned ? 'text-success' : ''}><CrownIcon size={4} /></span>
<p> <p>
{ isOwned && <b>Владелец схемы</b> } { isOwned && <b>Владелец схемы</b> }
{ !isOwned && <b>Стать владельцем</b> } { !isOwned && <b>Стать владельцем</b> }

View File

@ -20,7 +20,7 @@ function ParsingResult({ data, onShowAST, onShowError }: ParsingResultProps) {
<p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p> <p>Ошибок: <b>{errorCount}</b> | Предупреждений: <b>{warningsCount}</b></p>
{data.errors.map((error, index) => { {data.errors.map((error, index) => {
return ( return (
<p key={`error-${index}`} className='cursor-pointer text-red' onClick={() => onShowError(error)}> <p key={`error-${index}`} className='cursor-pointer text-warning' onClick={() => onShowError(error)}>
<span className='mr-1 font-semibold underline'>{error.isCritical ? 'Ошибка' : 'Предупреждение'} {getRSErrorPrefix(error)}:</span> <span className='mr-1 font-semibold underline'>{error.isCritical ? 'Ошибка' : 'Предупреждение'} {getRSErrorPrefix(error)}:</span>
<span> {getRSErrorMessage(error)}</span> <span> {getRSErrorMessage(error)}</span>
</p> </p>

View File

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

View File

@ -90,7 +90,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
{ {
when: (cst: IConstituenta) => cst.id === activeID, when: (cst: IConstituenta) => cst.id === activeID,
style: { style: {
backgroundColor: colors.selection, backgroundColor: colors.bgSelected,
}, },
} }
], [activeID, colors]); ], [activeID, colors]);
@ -109,7 +109,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
return (<> return (<>
<div <div
id={`${prefixes.cst_list}${cst.alias}`} id={`${prefixes.cst_list}${cst.alias}`}
className='w-full text-center rounded-md' className='w-full px-1 text-center rounded-md min-w-fit whitespace-nowrap'
style={{backgroundColor: getCstStatusColor(cst.status, colors)}} style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
> >
{cst.alias} {cst.alias}
@ -122,7 +122,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
conditionalCellStyles: [ conditionalCellStyles: [
{ {
when: (cst: IConstituenta) => isMockCst(cst), when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.selectionError} style: {backgroundColor: colors.bgWarning}
} }
] ]
}, },
@ -135,7 +135,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
conditionalCellStyles: [ conditionalCellStyles: [
{ {
when: (cst: IConstituenta) => isMockCst(cst), when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.selectionError} style: {backgroundColor: colors.bgWarning}
} }
] ]
}, },
@ -150,7 +150,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
conditionalCellStyles: [ conditionalCellStyles: [
{ {
when: (cst: IConstituenta) => isMockCst(cst), when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.selectionError} style: {backgroundColor: colors.fgWarning}
} }
] ]
} }
@ -165,7 +165,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
}, [noNavigation, baseHeight]); }, [noNavigation, baseHeight]);
return (<> return (<>
<div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 border-b rounded clr-input clr-border'> <div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 border-b rounded clr-input'>
<MatchModePicker <MatchModePicker
value={filterMatch} value={filterMatch}
onChange={setFilterMatch} onChange={setFilterMatch}

View File

@ -27,7 +27,7 @@ function RegisterPage() {
}, [username, email, password, password2, setError]); }, [username, email, password, password2, setError]);
function handleCancel() { function handleCancel() {
if (location.key !== "default") { if (location.key !== 'default') {
navigate(-1); navigate(-1);
} else { } else {
navigate('/library'); navigate('/library');
@ -80,7 +80,7 @@ function RegisterPage() {
<div className='text-sm'> <div className='text-sm'>
<p>- используйте уникальный пароль</p> <p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p> <p>- портал функционирует в тестовом режиме</p>
<p className='font-semibold text-red'>- безопасность информации не гарантируется</p> <p className='font-semibold text-warning'>- безопасность информации не гарантируется</p>
{/* <p>- минимум 8 символов</p> {/* <p>- минимум 8 символов</p>
<p>- большие, маленькие буквы, цифры</p> <p>- большие, маленькие буквы, цифры</p>
<p>- минимум 1 спец. символ</p> */} <p>- минимум 1 спец. символ</p> */}

View File

@ -19,7 +19,7 @@ function EditorPassword() {
const passwordColor = useMemo( const passwordColor = useMemo(
() => { () => {
return !!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-input-red' : 'clr-input'; return !!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-warning' : 'clr-input';
}, [newPassword, newPasswordRepeat]); }, [newPassword, newPasswordRepeat]);
const canSubmit = useMemo( const canSubmit = useMemo(
@ -48,7 +48,7 @@ function EditorPassword() {
}, [newPassword, oldPassword, newPasswordRepeat, setError]); }, [newPassword, oldPassword, newPasswordRepeat, setError]);
return ( return (
<div className='flex py-2 border-l-2 clr-border max-w-[14rem]'> <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'> <form onSubmit={handleSubmit} className='flex flex-col justify-between px-6 min-w-fit'>
<div> <div>
<TextInput id='old_password' <TextInput id='old_password'
@ -59,7 +59,7 @@ function EditorPassword() {
/> />
<TextInput id='new_password' type='password' <TextInput id='new_password' type='password'
colorClass={passwordColor} colorClass={passwordColor}
label="Новый пароль" label='Новый пароль'
value={newPassword} value={newPassword}
onChange={event => { onChange={event => {
setNewPassword(event.target.value); setNewPassword(event.target.value);
@ -67,7 +67,7 @@ function EditorPassword() {
/> />
<TextInput id='new_password_repeat' type='password' <TextInput id='new_password_repeat' type='password'
colorClass={passwordColor} colorClass={passwordColor}
label="Повторите новый" label='Повторите новый'
value={newPasswordRepeat} value={newPasswordRepeat}
onChange={event => { onChange={event => {
setNewPasswordRepeat(event.target.value); setNewPasswordRepeat(event.target.value);

View File

@ -61,12 +61,12 @@ function EditorProfile() {
onChange={event => setUsername(event.target.value)} onChange={event => setUsername(event.target.value)}
/> />
<TextInput id='first_name' <TextInput id='first_name'
label="Имя" label='Имя'
value={first_name} value={first_name}
onChange={event => setFirstName(event.target.value)} onChange={event => setFirstName(event.target.value)}
/> />
<TextInput id='last_name' label="Фамилия" value={last_name} onChange={event => setLastName(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)}/> <TextInput id='email' label='Электронная почта' value={email} onChange={event => setEmail(event.target.value)}/>
</div> </div>
<div className='flex justify-center w-full mt-10'> <div className='flex justify-center w-full mt-10'>
<SubmitButton <SubmitButton

View File

@ -1,7 +1,7 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import BackendError from '../../components/BackendError'; import BackendError from '../../components/BackendError';
import { Loader } from '../../components/Common/Loader'; import { ConceptLoader } from '../../components/Common/ConceptLoader';
import MiniButton from '../../components/Common/MiniButton'; import MiniButton from '../../components/Common/MiniButton';
import { EyeIcon, EyeOffIcon } from '../../components/Icons'; import { EyeIcon, EyeOffIcon } from '../../components/Icons';
import { useAuth } from '../../context/AuthContext'; import { useAuth } from '../../context/AuthContext';
@ -25,7 +25,7 @@ function UserTabs() {
return ( return (
<div className='w-full'> <div className='w-full'>
{ loading && <Loader /> } { loading && <ConceptLoader /> }
{ error && <BackendError error={error} />} { error && <BackendError error={error} />}
{ user && { user &&
<div className='flex justify-center gap-2 py-2'> <div className='flex justify-center gap-2 py-2'>

View File

@ -45,7 +45,7 @@ function ViewSubscriptions({items}: ViewSubscriptionsProps) {
return ( return (
<ConceptDataTable <ConceptDataTable
className='h-full overflow-auto border clr-border' className='h-full overflow-auto border'
columns={columns} columns={columns}
data={items} data={items}
defaultSortFieldId='time_update' defaultSortFieldId='time_update'

View File

@ -5,23 +5,19 @@ export interface IColorTheme {
teal: string teal: string
orange: string orange: string
text: string bgDefault: string
bgInput: string
bgControls: string
bgDisabled: string
bgHover: string
bgSelected: string
bgWarning: string
input: string border: string
inputDisabled: string
selection: string
selectionError: string
// bg100: string fgDefault: string
// bg70: string fgDisabled: string
// bg50: string fgWarning: string
// fg100: string
// fg70: string
// fg50: string
// primary: string
// secondary: string
} }
// =========== GENERAL THEMES ========= // =========== GENERAL THEMES =========
@ -32,63 +28,94 @@ export const lightT: IColorTheme = {
teal: '#a5e9fa', teal: '#a5e9fa',
orange: '#ffbb80', orange: '#ffbb80',
text: '#000000', bgDefault: 'var(--cl-bg-100)',
bgInput: 'var(--cl-bg-120)',
bgControls: 'var(--cl-bg-80)',
bgDisabled: 'var(--cl-bg-60)',
bgHover: 'var(--cl-prim-bg-60)',
bgSelected: 'var(--cl-prim-bg-80)',
bgWarning: 'var(--cl-red-bg-100)',
input: '#ffffff', border: 'var(--cl-bg-40)',
inputDisabled: '#f0f2f7',
selection: '#def1ff', fgDefault: 'var(--cl-fg-100)',
selectionError: '#ffc9c9' fgDisabled: 'var(--cl-fg-60)',
fgWarning: 'var(--cl-red-fg-100)'
}; };
export const darkT: IColorTheme = { export const darkT: IColorTheme = {
red: '#bf0d00', red: '#bf0d00',
green: '#2b8000', green: '#2b8000',
blue: '#bf0d00', blue: '#394bbf',
teal: '#0099bf', teal: '#007a99',
orange: '#964600', orange: '#964600',
text: '#e4e4e7', bgDefault: 'var(--cd-bg-100)',
bgInput: 'var(--cd-bg-120)',
bgControls: 'var(--cd-bg-80)',
bgDisabled: 'var(--cd-bg-60)',
bgHover: 'var(--cd-prim-bg-60)',
bgSelected: 'var(--cd-prim-bg-80)',
bgWarning: 'var(--cd-red-bg-100)',
input: '#181818', border: 'var(--cd-bg-40)',
inputDisabled: '#272727', // bg-gray-700
selection: '#394bbf', fgDefault: 'var(--cd-fg-100)',
selectionError: '#592b2b' fgDisabled: 'var(--cd-fg-60)',
fgWarning: 'var(--cd-red-fg-100)'
}; };
// ========= DATA TABLE THEMES ======== // ========= DATA TABLE THEMES ========
export const dataTableLightT = { export const dataTableLightT = {
text: {
primary: lightT.fgDefault,
secondary: lightT.fgDefault,
disabled: lightT.fgDisabled
},
background: {
default: lightT.bgDefault
},
highlightOnHover: {
default: lightT.bgHover,
text: lightT.fgDefault
},
divider: { divider: {
default: '#d1d5db' default: lightT.border
}, },
striped: { striped: {
default: '#f0f2f7' default: lightT.bgControls,
text: lightT.fgDefault
}, },
selected: {
default: lightT.bgSelected,
text: lightT.fgDefault
}
} }
export const dataTableDarkT = { export const dataTableDarkT = {
text: { text: {
primary: 'rgba(228, 228, 231, 1)', primary: darkT.fgDefault,
secondary: 'rgba(228, 228, 231, 0.87)', secondary: darkT.fgDefault,
disabled: 'rgba(228, 228, 231, 0.54)' disabled: darkT.fgDisabled
}, },
background: { background: {
default: '#181818' default: darkT.bgDefault
}, },
highlightOnHover: { highlightOnHover: {
default: '#606060', default: darkT.bgHover,
text: 'rgba(228, 228, 231, 1)' text: darkT.fgDefault
}, },
divider: { divider: {
default: '#6b6b6b' default: darkT.border
}, },
striped: { striped: {
default: '#272727', default: darkT.bgControls,
text: 'rgba(228, 228, 228, 1)' text: darkT.fgDefault
}, },
selected: { selected: {
default: '#181818', default: darkT.bgSelected,
text: 'rgba(228, 228, 231, 1)' text: darkT.fgDefault
} }
}; };
@ -144,7 +171,7 @@ export const graphLightT = {
export const graphDarkT = { export const graphDarkT = {
canvas: { canvas: {
background: '#1f2937' background: '#181818' // var(--cd-bg-100)
}, },
node: { node: {
fill: '#7a8c9e', fill: '#7a8c9e',
@ -193,27 +220,21 @@ export const graphDarkT = {
// ======== Bracket Matching Themes =========== // ======== Bracket Matching Themes ===========
export const bracketsLightT = { export const bracketsLightT = {
'.cc-matchingBracket': {
fontWeight: 600,
},
'.cc-nonmatchingBracket': { '.cc-nonmatchingBracket': {
color: '#ef4444', color: lightT.fgWarning,
fontWeight: 700, fontWeight: 700,
}, },
'&.cm-focused .cc-matchingBracket': { '&.cm-focused .cc-matchingBracket': {
backgroundColor: '#dae6f2', backgroundColor: lightT.bgSelected,
}, },
}; };
export const bracketsDarkT = { export const bracketsDarkT = {
'.cc-matchingBracket': {
fontWeight: 600,
},
'.cc-nonmatchingBracket': { '.cc-nonmatchingBracket': {
color: '#ef4444', color: darkT.fgWarning,
fontWeight: 700, fontWeight: 700,
}, },
'&.cm-focused .cc-matchingBracket': { '&.cm-focused .cc-matchingBracket': {
backgroundColor: '#734f00', backgroundColor: darkT.bgSelected,
}, },
}; };

View File

@ -1,4 +1,4 @@
import { NodeType, Tree, TreeCursor } from "@lezer/common" import { NodeType, Tree, TreeCursor } from '@lezer/common'
export type CursorNode = { export type CursorNode = {
type: NodeType type: NodeType
@ -44,16 +44,16 @@ export function traverseTree(tree: Tree, { beforeEnter, onEnter, onLeave, }: Tre
export function printTree(tree: Tree): string { export function printTree(tree: Tree): string {
const state = { const state = {
output: "", output: '',
prefixes: [] as string[] prefixes: [] as string[]
} }
traverseTree(tree, { traverseTree(tree, {
onEnter: node => { onEnter: node => {
state.output += "["; state.output += '[';
state.output += node.type.name; state.output += node.type.name;
}, },
onLeave: () => { onLeave: () => {
state.output += "]"; state.output += ']';
}, },
}) })
return state.output; return state.output;