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 {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {},
},

View File

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

View File

@ -59,7 +59,7 @@ function DescribeError(error: ErrorInfo) {
function BackendError({ error }: BackendErrorProps) {
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)}
</div>
);

View File

@ -6,7 +6,7 @@ interface CardProps {
function Card({ title, widthClass = 'min-w-fit', children }: CardProps) {
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> }
{children}
</div>

View File

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

View File

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

View File

@ -9,7 +9,7 @@ extends Omit<TabProps, 'className'> {
function ConceptTab({ children, className, ...otherProps }: ConceptTabProps) {
return (
<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}
>
{children}

View File

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

View File

@ -7,7 +7,7 @@ interface DropdownProps {
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
return (
<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}
</div>
</div>

View File

@ -13,10 +13,10 @@ function DropdownCheckbox({ tooltip, onChange, disabled, ...props }: DropdownChe
return (
<div
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
widthClass='w-fit'
widthClass='w-full'
disabled={disabled}
onChange={onChange}
{...props}

View File

@ -14,7 +14,7 @@ function EmbedYoutube({ videoID, pxHeight, pxWidth }: EmbedYoutubeProps) {
style={{height: 0, paddingBottom: `${pxHeight}px`, paddingLeft: `${pxWidth}px`}}
>
<iframe
className='absolute top-0 left-0 clr-border'
className='absolute top-0 left-0 border'
style={{minHeight: `${pxHeight}px`, minWidth: `${pxWidth}px`}}
width={`${pxWidth}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) {
return (
<label
className={`${className} text-sm font-semibold`}
className={`text-sm font-semibold ${className}`}
title={ (required && !title) ? 'обязательное поле' : title }
{...props}
>

View File

@ -37,16 +37,16 @@ function Modal({
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
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)]'>
{children}
</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 &&
<Button
text={submitText}

View File

@ -26,7 +26,7 @@ function TextArea({
/>
<textarea id={id}
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}
required={required}
{...props}

View File

@ -27,7 +27,7 @@ function TextInput({
/>
<input id={id}
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}
{...props}
/>

View File

@ -6,9 +6,9 @@ function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
reportError(error);
return (
<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 }
<Button onClick={resetErrorBoundary} text='Try again' />
<Button onClick={resetErrorBoundary} text='Попробовать еще раз' />
</div>
);
}

View File

@ -4,7 +4,7 @@ import { urls } from '../utils/constants';
function Footer() {
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='mb-2 text-center'>
<Link className='mx-2 hover:underline' to='/library' tabIndex={-1}>Библиотека</Link>

View File

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

View File

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

View File

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

View File

@ -300,3 +300,20 @@ export function GithubIcon(props: IconProps) {
</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 { useConceptTheme } from '../../context/ThemeContext';
import { EducationIcon, LibraryIcon } from '../Icons';
import { EducationIcon, LibraryIcon, PlusIcon } from '../Icons';
import Logo from './Logo'
import NavigationButton from './NavigationButton';
import UserMenu from './UserMenu';
@ -12,13 +12,14 @@ function Navigation () {
const navigateLibrary = () => navigate('/library');
const navigateHelp = () => navigate('/manuals');
const navigateCreateNew = () => navigate('/rsform-create');
return (
<nav className='sticky top-0 left-0 right-0 z-50 select-none h-fit'>
{!noNavigation &&
<button
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}
>
<p>{'>'}</p><p>{'>'}</p>
@ -26,32 +27,36 @@ function Navigation () {
{noNavigation &&
<button
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}
>
{''}
</button>}
{!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'>
<Logo title='КонцептПортал' />
</div>
<div className='flex items-center'>
<div className='flex items-center pl-2'>
<NavigationButton
text='Библиотека'
description='Библиотека концептуальных схем'
icon={<LibraryIcon />}
onClick={navigateLibrary}
/>
<NavigationButton
text='Справка'
description='Справочные материалы и обучение'
icon={<EducationIcon />}
onClick={navigateHelp}
/>
<UserMenu />
</div>
<div className='flex items-center h-full'>
<NavigationButton
text='Новая схема'
description='Создать новую схему'
icon={<PlusIcon />}
onClick={navigateCreateNew}
/>
<NavigationButton
text='Библиотека'
description='Библиотека концептуальных схем'
icon={<LibraryIcon />}
onClick={navigateLibrary}
/>
<NavigationButton
text='Справка'
description='Справочные материалы и обучение'
icon={<EducationIcon />}
onClick={navigateHelp}
/>
<UserMenu />
</div>
</div>}
</nav>

View File

@ -12,7 +12,7 @@ function NavigationButton({ id, icon, description, onClick, text }: NavigationBu
title={description}
type='button'
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>}
{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 useDropdown from '../../hooks/useDropdown';
import { UserIcon } from '../Icons';
import { InDoor, UserIcon } from '../Icons';
import NavigationButton from './NavigationButton';
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() {
const navigate = useNavigate();
const { user } = useAuth();
const menu = useDropdown();
const navigateLogin = () => navigate('/login');
return (
<div ref={menu.ref}>
<div className='w-[4.2rem] flex justify-end'>
{ !user && <LoginRef />}
<div ref={menu.ref} className='h-full'>
<div className='flex items-center justify-end h-full w-fit'>
{ !user &&
<NavigationButton
text='Войти...'
description='Перейти на страницу логина'
icon={<InDoor />}
onClick={navigateLogin}
/>}
{ user &&
<NavigationButton
icon={<UserIcon />}

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
import { Extension } from "@codemirror/state";
import { hoverTooltip } from "@codemirror/view";
import { Extension } from '@codemirror/state';
import { hoverTooltip } from '@codemirror/view';
import { IConstituenta } from '../../utils/models';
import { getCstTypificationLabel } from '../../utils/staticUI';
function createTooltipFor(cst: IConstituenta) {
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');
alias.innerHTML = `<b>${cst.alias}:</b> ${getCstTypificationLabel(cst)}`;
dom.appendChild(alias);

View File

@ -12,10 +12,10 @@ function RequireAuth({ children }: RequireAuthProps) {
<>
{user && children}
{!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 className='mb-2'>Пожалуйста войдите в систему</p>
<TextURL text='Войти в систему' href='/login'/>
<TextURL text='Войти в Портал' href='/login'/>
<TextURL text='Зарегистрироваться' href='/signup'/>
<TextURL text='Начальная страница' href='/'/>
</div>

View File

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

View File

@ -2,8 +2,75 @@
@tailwind components;
@tailwind utilities;
.rdt_TableCell{
font-size: 0.875rem;
:root {
/* 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"] {
@ -14,127 +81,213 @@
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 {
:root {
@apply bg-gray-50;
}
.dark {
@apply text-zinc-200 bg-[#181818]
}
h1 {
@apply text-lg font-bold text-center
}
.border {
@apply clr-border rounded
@apply rounded
}
.text-btn {
@apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400
.clr-modal-backdrop {
opacity: 0.75;
}
.clr-border {
@apply border-gray-300 dark:border-[#434343]
:is(.clr-border,
.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 {
@apply border-gray-400 dark:border-[#434343]
:is(.clr-app,
.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 {
@apply bg-gray-50 dark:bg-[#181818]
:is(.clr-input
) {
background-color: var(--cl-bg-120);
.dark & {
background-color: var(--cd-bg-120);
}
}
.clr-bg-pop {
@apply bg-gray-100 dark:bg-[#272727] dark:border-[#434343]
:is(.clr-controls,
.clr-btn-default
) {
background-color: var(--cl-bg-80);
.dark & {
background-color: var(--cd-bg-80);
}
}
.clr-modal {
@apply bg-gray-300 dark:bg-[#272727]
:is(.clr-primary,
.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 {
@apply border-gray-400 dark:border-[#434343] bg-white dark:bg-[#181818]
:is(.clr-selected
) {
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 {
@apply dark:bg-[#181818] bg-white disabled:bg-[#c6c6c6] dark:disabled:bg-[#181818]
:is(.clr-disabled,
.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 {
@apply clr-app text-gray-600 border-gray-400 dark:border-[#434343] dark:text-[#aaaaaa]
}
.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
color: var(--cl-fg-40);
.dark & {
color: var(--cd-fg-40);
}
}
.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 {
@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 {
@apply bg-white dark:bg-[#181818] checked:bg-blue-700 dark:checked:bg-orange-500
.clr-warning {
background-color: var(--cl-red-bg-100);
.dark & {
background-color: var(--cd-red-bg-100);
}
}
.clr-input-red {
@apply bg-red-300 dark:bg-red-700
.text-warning {
color: var(--cl-red-fg-100);
.dark & {
color: var(--cd-red-fg-100);
}
}
.text-url {
@apply hover:text-blue-600 text-blue-400 dark:text-orange-500 dark:hover:text-orange-300
}
.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
.text-success {
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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,15 +1,30 @@
import axios from 'axios';
import { useEffect, useState } from 'react';
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 SubmitButton from '../components/Common/SubmitButton';
import TextInput from '../components/Common/TextInput';
import TextURL from '../components/Common/TextURL';
import { useAuth } from '../context/AuthContext';
import { useConceptTheme } from '../context/ThemeContext';
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() {
const {mainHeight} = useConceptTheme();
const location = useLocation();
const navigate = useNavigate();
const search = useLocation().search;
@ -36,7 +51,7 @@ function LoginPage() {
password: password
};
login(data, () => {
if (location.key !== "default") {
if (location.key !== 'default') {
navigate(-1);
} else {
navigate('/library');
@ -46,45 +61,47 @@ function LoginPage() {
}
return (
<div className='flex justify-center w-full'>
<div className='py-2'> { user
? <b>{`Вы вошли в систему как ${user.username}`}</b>
:
<Form
title='Вход в Портал'
onSubmit={handleSubmit}
widthClass='w-[24rem]'
>
<TextInput id='username'
label='Имя пользователя'
required
type='text'
value={username}
autoFocus
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password'
label='Пароль'
required
type='password'
value={password}
onChange={event => setPassword(event.target.value)}
/>
<div className='flex items-center justify-center w-full select-none' style={{minHeight: mainHeight}}>
{ user &&
<div className='py-2 font-semibold'>
{`Вы вошли в систему как ${user.username}`}
</div>}
{ !user &&
<Form
title='Вход в Портал'
onSubmit={handleSubmit}
widthClass='w-[24rem]'
>
<TextInput id='username'
label='Имя пользователя'
required
type='text'
value={username}
autoFocus
onChange={event => setUsername(event.target.value)}
/>
<TextInput id='password'
label='Пароль'
required
type='password'
value={password}
onChange={event => setPassword(event.target.value)}
/>
<div className='flex justify-center w-full gap-2 py-2 mt-4'>
<SubmitButton
text='Вход'
widthClass='w-[12rem]'
loading={loading}
/>
</div>
<div className='flex flex-col mt-2 text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{ error && <BackendError error={error} />}
</Form>
}</div>
<div className='flex justify-center w-full gap-2 py-2 mt-4'>
<SubmitButton
text='Вход'
widthClass='w-[12rem]'
loading={loading}
/>
</div>
<div className='flex flex-col mt-2 text-sm'>
<TextURL text='Восстановить пароль...' href='/restore-password' />
<TextURL text='Нет аккаунта? Зарегистрируйтесь...' href='/signup' />
</div>
{ error && <ProcessError error={error} />}
</Form>
}
</div>
);
}

View File

@ -9,13 +9,13 @@ interface TopicsListProps {
function TopicsList({ activeTopic, onChangeTopic }: TopicsListProps) {
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='mb-2 font-bold text-center'>Справка</div>
<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-semibold text-center'>Справка</div>
{ [... mapTopicInfo.entries()].map(
([topic, info], index) => {
return (
<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}
onClick={() => onChangeTopic(topic)}
>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,7 +66,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
const { darkMode, colors, noNavigation } = useConceptTheme();
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 [ noHermits, setNoHermits ] = useLocalStorage('graph_no_hermits', true);
@ -355,7 +355,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
initial={getOptions()}
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 &&
<div className='relative'>
<InfoConstituenta
@ -374,13 +374,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
<div>
<MiniButton
tooltip='Удалить выбранные'
icon={<DumpBinIcon color={!nothingSelected ? 'text-red' : ''} size={5}/>}
icon={<DumpBinIcon color={!nothingSelected ? 'text-warning' : ''} size={5}/>}
disabled={!isEditable || nothingSelected}
onClick={handleDeleteCst}
/>
<MiniButton
tooltip='Новая конституента'
icon={<SmallPlusIcon color='text-green' size={5}/>}
icon={<SmallPlusIcon color='text-success' size={5}/>}
disabled={!isEditable}
onClick={handleCreateCst}
/>
@ -460,7 +460,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
</div>
</div>
</div>
<div className='w-full h-full overflow-auto border'>
<div className='w-full h-full overflow-auto'>
<div
className='relative'
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 ConceptTab from '../../components/Common/ConceptTab';
import { Loader } from '../../components/Common/Loader';
import { ConceptLoader } from '../../components/Common/ConceptLoader';
import { useLibrary } from '../../context/LibraryContext';
import { useRSForm } from '../../context/RSFormContext';
import { useConceptTheme } from '../../context/ThemeContext';
@ -91,12 +91,18 @@ function RSTabs() {
const navigateTo = useCallback(
(tab: RSTabID, activeID?: number) => {
if (!schema) {
return;
}
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
});
} 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 {
navigate(`/rsforms/${schema!.id}?tab=${tab}`);
navigate(`/rsforms/${schema.id}?tab=${tab}`);
}
}, [navigate, schema, activeTab]);
@ -272,7 +278,7 @@ function RSTabs() {
return (
<div className='w-full'>
{ loading && <Loader /> }
{ loading && <ConceptLoader /> }
{ error && <BackendError error={error} />}
{ schema && !loading && <>
{showUpload &&
@ -311,9 +317,9 @@ function RSTabs() {
selectedIndex={activeTab}
onSelect={onSelectTab}
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
onDownload={onDownloadSchema}
onDestroy={onDestroySchema}

View File

@ -67,7 +67,7 @@ function RSTabsMenu({
}
return (
<div className='flex items-stretch w-fit'>
<div className='flex items-stretch h-full w-fit'>
<div ref={schemaMenu.ref}>
<Button
tooltip='Действия'
@ -99,13 +99,13 @@ function RSTabsMenu({
</DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleUpload}>
<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>
</div>
</DropdownButton>
<DropdownButton disabled={!isEditable} onClick={handleDelete}>
<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>
</span>
</DropdownButton>
@ -122,7 +122,7 @@ function RSTabsMenu({
tooltip={'измнение: ' + (isEditable ? '[доступно]' : '[запрещено]')}
borderClass=''
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
onClick={editMenu.toggle}
/>
@ -134,7 +134,7 @@ function RSTabsMenu({
tooltip={!user || !isClaimable ? 'Стать владельцем можно только для общей изменяемой схемы' : ''}
>
<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>
{ 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>
{data.errors.map((error, index) => {
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> {getRSErrorMessage(error)}</span>
</p>

View File

@ -17,7 +17,7 @@ function RSTokenButton({ id, disabled, onInsert }: RSTokenButtonProps) {
onClick={() => onInsert(id)}
title={data.tooltip}
tabIndex={-1}
className={`px-1 cursor-pointer border rounded-none h-7 ${width} clr-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>}
</button>

View File

@ -90,7 +90,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
{
when: (cst: IConstituenta) => cst.id === activeID,
style: {
backgroundColor: colors.selection,
backgroundColor: colors.bgSelected,
},
}
], [activeID, colors]);
@ -109,7 +109,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
return (<>
<div
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)}}
>
{cst.alias}
@ -122,7 +122,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
conditionalCellStyles: [
{
when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.selectionError}
style: {backgroundColor: colors.bgWarning}
}
]
},
@ -135,7 +135,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
conditionalCellStyles: [
{
when: (cst: IConstituenta) => isMockCst(cst),
style: {backgroundColor: colors.selectionError}
style: {backgroundColor: colors.bgWarning}
}
]
},
@ -150,7 +150,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
conditionalCellStyles: [
{
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]);
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
value={filterMatch}
onChange={setFilterMatch}

View File

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

View File

@ -19,7 +19,7 @@ function EditorPassword() {
const passwordColor = useMemo(
() => {
return !!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-input-red' : 'clr-input';
return !!newPassword && !!newPasswordRepeat && newPassword !== newPasswordRepeat ? 'clr-warning' : 'clr-input';
}, [newPassword, newPasswordRepeat]);
const canSubmit = useMemo(
@ -48,7 +48,7 @@ function EditorPassword() {
}, [newPassword, oldPassword, newPasswordRepeat, setError]);
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'>
<div>
<TextInput id='old_password'
@ -59,7 +59,7 @@ function EditorPassword() {
/>
<TextInput id='new_password' type='password'
colorClass={passwordColor}
label="Новый пароль"
label='Новый пароль'
value={newPassword}
onChange={event => {
setNewPassword(event.target.value);
@ -67,7 +67,7 @@ function EditorPassword() {
/>
<TextInput id='new_password_repeat' type='password'
colorClass={passwordColor}
label="Повторите новый"
label='Повторите новый'
value={newPasswordRepeat}
onChange={event => {
setNewPasswordRepeat(event.target.value);

View File

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

View File

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

View File

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

View File

@ -5,23 +5,19 @@ export interface IColorTheme {
teal: string
orange: string
text: string
bgDefault: string
bgInput: string
bgControls: string
bgDisabled: string
bgHover: string
bgSelected: string
bgWarning: string
input: string
inputDisabled: string
selection: string
selectionError: string
border: string
// bg100: string
// bg70: string
// bg50: string
// fg100: string
// fg70: string
// fg50: string
// primary: string
// secondary: string
fgDefault: string
fgDisabled: string
fgWarning: string
}
// =========== GENERAL THEMES =========
@ -32,63 +28,94 @@ export const lightT: IColorTheme = {
teal: '#a5e9fa',
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',
inputDisabled: '#f0f2f7',
selection: '#def1ff',
selectionError: '#ffc9c9'
border: 'var(--cl-bg-40)',
fgDefault: 'var(--cl-fg-100)',
fgDisabled: 'var(--cl-fg-60)',
fgWarning: 'var(--cl-red-fg-100)'
};
export const darkT: IColorTheme = {
red: '#bf0d00',
green: '#2b8000',
blue: '#bf0d00',
teal: '#0099bf',
blue: '#394bbf',
teal: '#007a99',
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',
inputDisabled: '#272727', // bg-gray-700
selection: '#394bbf',
selectionError: '#592b2b'
border: 'var(--cd-bg-40)',
fgDefault: 'var(--cd-fg-100)',
fgDisabled: 'var(--cd-fg-60)',
fgWarning: 'var(--cd-red-fg-100)'
};
// ========= DATA TABLE THEMES ========
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: {
default: '#d1d5db'
default: lightT.border
},
striped: {
default: '#f0f2f7'
default: lightT.bgControls,
text: lightT.fgDefault
},
selected: {
default: lightT.bgSelected,
text: lightT.fgDefault
}
}
export const dataTableDarkT = {
text: {
primary: 'rgba(228, 228, 231, 1)',
secondary: 'rgba(228, 228, 231, 0.87)',
disabled: 'rgba(228, 228, 231, 0.54)'
primary: darkT.fgDefault,
secondary: darkT.fgDefault,
disabled: darkT.fgDisabled
},
background: {
default: '#181818'
default: darkT.bgDefault
},
highlightOnHover: {
default: '#606060',
text: 'rgba(228, 228, 231, 1)'
default: darkT.bgHover,
text: darkT.fgDefault
},
divider: {
default: '#6b6b6b'
default: darkT.border
},
striped: {
default: '#272727',
text: 'rgba(228, 228, 228, 1)'
default: darkT.bgControls,
text: darkT.fgDefault
},
selected: {
default: '#181818',
text: 'rgba(228, 228, 231, 1)'
default: darkT.bgSelected,
text: darkT.fgDefault
}
};
@ -144,7 +171,7 @@ export const graphLightT = {
export const graphDarkT = {
canvas: {
background: '#1f2937'
background: '#181818' // var(--cd-bg-100)
},
node: {
fill: '#7a8c9e',
@ -193,27 +220,21 @@ export const graphDarkT = {
// ======== Bracket Matching Themes ===========
export const bracketsLightT = {
'.cc-matchingBracket': {
fontWeight: 600,
},
'.cc-nonmatchingBracket': {
color: '#ef4444',
color: lightT.fgWarning,
fontWeight: 700,
},
'&.cm-focused .cc-matchingBracket': {
backgroundColor: '#dae6f2',
backgroundColor: lightT.bgSelected,
},
};
export const bracketsDarkT = {
'.cc-matchingBracket': {
fontWeight: 600,
},
'.cc-nonmatchingBracket': {
color: '#ef4444',
color: darkT.fgWarning,
fontWeight: 700,
},
'&.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 = {
type: NodeType
@ -44,16 +44,16 @@ export function traverseTree(tree: Tree, { beforeEnter, onEnter, onLeave, }: Tre
export function printTree(tree: Tree): string {
const state = {
output: "",
output: '',
prefixes: [] as string[]
}
traverseTree(tree, {
onEnter: node => {
state.output += "[";
state.output += '[';
state.output += node.type.name;
},
onLeave: () => {
state.output += "]";
state.output += ']';
},
})
return state.output;