mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Merge branch 'main' of https://github.com/IRBorisov/ConceptPortal
This commit is contained in:
commit
029298a9d3
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
@ -29,5 +29,13 @@
|
||||||
"name": "django",
|
"name": "django",
|
||||||
"depth": 5
|
"depth": 5
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"colorize.include": [".tsx", ".jsx", ".ts", ".js"],
|
||||||
|
"colorize.languages": [
|
||||||
|
"typescript",
|
||||||
|
"javascript",
|
||||||
|
"css",
|
||||||
|
"typescriptreact",
|
||||||
|
"javascriptreact"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -53,7 +53,7 @@ This readme file is used mostly to document project dependencies
|
||||||
<summary>VS Code plugins</summary>
|
<summary>VS Code plugins</summary>
|
||||||
<pre>
|
<pre>
|
||||||
- ESLint
|
- ESLint
|
||||||
-
|
- Colorize
|
||||||
</pre>
|
</pre>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
5
TODO.txt
5
TODO.txt
|
@ -4,8 +4,6 @@ For more specific TODOs see comments in code
|
||||||
|
|
||||||
[Functionality]
|
[Functionality]
|
||||||
- home page
|
- home page
|
||||||
- manuals
|
|
||||||
- текстовый модуль для разрешения отсылок
|
|
||||||
- блок нотификаций пользователей
|
- блок нотификаций пользователей
|
||||||
- блок синтеза
|
- блок синтеза
|
||||||
- блок организации библиотеки моделей
|
- блок организации библиотеки моделей
|
||||||
|
@ -13,9 +11,6 @@ For more specific TODOs see comments in code
|
||||||
- обратная связь - система баг репортов
|
- обратная связь - система баг репортов
|
||||||
|
|
||||||
[Tech]
|
[Tech]
|
||||||
- Use migtation/fixtures to provide initial data for testing
|
|
||||||
- USe migtation/fixtures to load example common data
|
|
||||||
|
|
||||||
- create custom Select component
|
- create custom Select component
|
||||||
- reload react-data-table-component
|
- reload react-data-table-component
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ RUN pip install --no-cache /wheels/* && \
|
||||||
COPY apps/ ./apps
|
COPY apps/ ./apps
|
||||||
COPY cctext/ ./cctext
|
COPY cctext/ ./cctext
|
||||||
COPY project/ ./project
|
COPY project/ ./project
|
||||||
COPY data/ ./data
|
COPY fixtures/ ./fixtures
|
||||||
COPY manage.py entrypoint.sh ./
|
COPY manage.py entrypoint.sh ./
|
||||||
RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.sh && \
|
RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.sh && \
|
||||||
chmod +x $APP_HOME/entrypoint.sh && \
|
chmod +x $APP_HOME/entrypoint.sh && \
|
||||||
|
|
|
@ -24,7 +24,7 @@ class LibraryActiveView(generics.ListAPIView):
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
if not user.is_anonymous:
|
if not user.is_anonymous:
|
||||||
# pyling: disable=unsupported-binary-operation
|
# pylint: disable=unsupported-binary-operation
|
||||||
return m.LibraryItem.objects.filter(Q(is_common=True) | Q(owner=user) | Q(subscription__user=user))
|
return m.LibraryItem.objects.filter(Q(is_common=True) | Q(owner=user) | Q(subscription__user=user))
|
||||||
else:
|
else:
|
||||||
return m.LibraryItem.objects.filter(is_common=True)
|
return m.LibraryItem.objects.filter(is_common=True)
|
||||||
|
|
|
@ -16,7 +16,7 @@ import RSFormPage from './pages/RSFormPage';
|
||||||
import UserProfilePage from './pages/UserProfilePage';
|
import UserProfilePage from './pages/UserProfilePage';
|
||||||
|
|
||||||
function App () {
|
function App () {
|
||||||
const { noNavigation, viewportHeight, mainHeight } = useConceptTheme();
|
const { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme();
|
||||||
return (
|
return (
|
||||||
<div className='antialiased clr-app'>
|
<div className='antialiased clr-app'>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
|
@ -45,7 +45,7 @@ function App () {
|
||||||
<Route path='*' element={ <NotFoundPage/>} />
|
<Route path='*' element={ <NotFoundPage/>} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
{!noNavigation && <Footer />}
|
{!noNavigation && !noFooter && <Footer />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import DataTable, { createTheme, type TableProps } from 'react-data-table-component';
|
import DataTable, { createTheme, type TableProps } from 'react-data-table-component';
|
||||||
|
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import { dataTableDarkT, dataTableLightT } from '../../utils/color';
|
||||||
|
|
||||||
export interface SelectionInfo<T> {
|
export interface SelectionInfo<T> {
|
||||||
allSelected: boolean
|
allSelected: boolean
|
||||||
|
@ -8,47 +9,21 @@ export interface SelectionInfo<T> {
|
||||||
selectedRows: T[]
|
selectedRows: T[]
|
||||||
}
|
}
|
||||||
|
|
||||||
createTheme('customDark', {
|
createTheme('customDark', dataTableDarkT, 'dark');
|
||||||
text: {
|
createTheme('customLight', dataTableLightT, 'light');
|
||||||
primary: 'rgba(228, 228, 231, 1)',
|
|
||||||
secondary: 'rgba(228, 228, 231, 0.87)',
|
|
||||||
disabled: 'rgba(228, 228, 231, 0.54)'
|
|
||||||
},
|
|
||||||
background: {
|
|
||||||
default: '#111827'
|
|
||||||
},
|
|
||||||
highlightOnHover: {
|
|
||||||
default: '#4d6080',
|
|
||||||
text: 'rgba(228, 228, 231, 1)'
|
|
||||||
},
|
|
||||||
divider: {
|
|
||||||
default: '#6b6b6b'
|
|
||||||
},
|
|
||||||
striped: {
|
|
||||||
default: '#374151',
|
|
||||||
text: 'rgba(228, 228, 231, 1)'
|
|
||||||
},
|
|
||||||
selected: {
|
|
||||||
default: '#4d6080',
|
|
||||||
text: 'rgba(228, 228, 231, 1)'
|
|
||||||
}
|
|
||||||
}, 'dark');
|
|
||||||
|
|
||||||
createTheme('customLight', {
|
interface ConceptDataTableProps<T>
|
||||||
divider: {
|
extends Omit<TableProps<T>, 'paginationComponentOptions'> {}
|
||||||
default: '#d1d5db'
|
|
||||||
},
|
|
||||||
striped: {
|
|
||||||
default: '#f0f2f7'
|
|
||||||
},
|
|
||||||
}, 'light');
|
|
||||||
|
|
||||||
function ConceptDataTable<T>({ theme, ...props }: TableProps<T>) {
|
function ConceptDataTable<T>({ theme, ...props }: ConceptDataTableProps<T>) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable<T>
|
<DataTable<T>
|
||||||
theme={ theme ?? (darkMode ? 'customDark' : 'customLight')}
|
theme={ theme ?? (darkMode ? 'customDark' : 'customLight')}
|
||||||
|
paginationComponentOptions={{
|
||||||
|
rowsPerPageText: 'строк на страницу'
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
import type { TabProps } from 'react-tabs';
|
import type { TabProps } from 'react-tabs';
|
||||||
import { Tab } from 'react-tabs';
|
import { Tab } from 'react-tabs';
|
||||||
|
|
||||||
function ConceptTab({ children, className, ...otherProps }: TabProps) {
|
interface ConceptTabProps
|
||||||
|
extends Omit<TabProps, 'className'> {
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConceptTab({ children, className, ...otherProps }: ConceptTabProps) {
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
className={`px-2 py-1 text-sm hover:cursor-pointer clr-tab whitespace-nowrap ${className}`}
|
||||||
className={`px-2 py-1 border-r-2 text-sm hover:cursor-pointer clr-tab whitespace-nowrap ${className}`}
|
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -6,8 +6,8 @@ interface DropdownProps {
|
||||||
|
|
||||||
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
|
function Dropdown({ children, widthClass = 'w-fit', stretchLeft }: DropdownProps) {
|
||||||
return (
|
return (
|
||||||
<div className='relative'>
|
<div className='relative text-sm'>
|
||||||
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} py-2 z-40 flex flex-col items-stretch justify-start px-2 mt-2 text-sm origin-top-right bg-white border border-gray-100 divide-y rounded-md shadow-lg dark:border-gray-500 dark:bg-gray-900 ${widthClass}`}>
|
<div className={`absolute ${stretchLeft ? 'right-0' : 'left-0'} mt-2 z-40 flex flex-col items-stretch justify-start origin-top-right border divide-y rounded-md shadow-lg clr-input clr-border ${widthClass}`}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -46,7 +46,7 @@ function Modal({
|
||||||
<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'>
|
<div className='flex justify-center w-full gap-4 pt-4 mt-2 border-t-4 clr-border'>
|
||||||
{!readonly &&
|
{!readonly &&
|
||||||
<Button
|
<Button
|
||||||
text={submitText}
|
text={submitText}
|
||||||
|
|
|
@ -27,7 +27,7 @@ function TextInput({
|
||||||
/>
|
/>
|
||||||
<input id={id}
|
<input id={id}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
className={`px-3 py-2 mt-2 leading-tight border shadow truncate hover:text-clip ${colorClass} ${singleRow ? '' : widthClass}`}
|
className={`px-3 py-2 leading-tight border shadow truncate hover:text-clip ${colorClass} ${singleRow ? '' : 'mt-2 ' + widthClass}`}
|
||||||
required={required}
|
required={required}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,7 +11,7 @@ function Footer() {
|
||||||
<Link to='/manuals' tabIndex={-1}>Справка</Link> <br/>
|
<Link to='/manuals' tabIndex={-1}>Справка</Link> <br/>
|
||||||
</div>
|
</div>
|
||||||
<div className=''>
|
<div className=''>
|
||||||
<a href={urls.concept} tabIndex={-1} className='underline'>Центр Концепт</a>
|
<p className='w-full text-center underline'><a href={urls.concept} tabIndex={-1} >Центр Концепт</a></p>
|
||||||
<p className='mt-0.5 text-center'>© 2023 ЦИВТ КОНЦЕПТ</p>
|
<p className='mt-0.5 text-center'>© 2023 ЦИВТ КОНЦЕПТ</p>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex flex-col underline'>
|
<div className='flex flex-col underline'>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { urls } from '../../utils/constants';
|
import { urls } from '../../utils/constants';
|
||||||
|
import { LibraryFilterStrategy } from '../../utils/models';
|
||||||
import TextURL from '../Common/TextURL';
|
import TextURL from '../Common/TextURL';
|
||||||
|
|
||||||
function HelpMain() {
|
function HelpMain() {
|
||||||
|
@ -9,8 +10,9 @@ function HelpMain() {
|
||||||
<p>Навигация по порталу осуществляется верхнюю панель или ссылки в "подвале" страницы. Их можно скрыть с помощью кнопки в правом верхнем углу</p>
|
<p>Навигация по порталу осуществляется верхнюю панель или ссылки в "подвале" страницы. Их можно скрыть с помощью кнопки в правом верхнем углу</p>
|
||||||
<p>В меню пользователя (правый верхний угол) редактирование данных пользователя и изменение цветовой темы.</p>
|
<p>В меню пользователя (правый верхний угол) редактирование данных пользователя и изменение цветовой темы.</p>
|
||||||
<p className='mt-2'><b>Основные разделы Портала</b></p>
|
<p className='mt-2'><b>Основные разделы Портала</b></p>
|
||||||
<li><TextURL text='Библиотека' href='/library?filter=common' /> - общедоступные схемы и инструменты поиска и навигации по ним</li>
|
<li><TextURL text='Библиотека' href='/library' /> - все схемы доступные пользователю</li>
|
||||||
<li><TextURL text='Мои схемы' href='/library?filter=personal' /> - отслеживаемые и редактируемые схемы. Основной рабочий раздел</li>
|
<li><TextURL text='Общие схемы' href={`/library?filter=${LibraryFilterStrategy.COMMON}`} /> - общедоступные схемы и инструменты поиска и навигации по ним</li>
|
||||||
|
<li><TextURL text='Мои схемы' href={`/library?filter=${LibraryFilterStrategy.PERSONAL}`} /> - отслеживаемые и редактируемые схемы. Основной рабочий раздел</li>
|
||||||
<li><TextURL text='Профиль' href='/profile' /> - данные пользователя и смена пароля</li>
|
<li><TextURL text='Профиль' href='/profile' /> - данные пользователя и смена пароля</li>
|
||||||
<p className='mt-2'><b>Поддержка Портала</b></p>
|
<p className='mt-2'><b>Поддержка Портала</b></p>
|
||||||
<p>Портал разрабатывается <TextURL text='Центром Концепт' href={urls.concept}/> и является проектом с открытым исходным кодом, доступным на <TextURL text='Github' href={urls.gitrepo}/>.</p>
|
<p>Портал разрабатывается <TextURL text='Центром Концепт' href={urls.concept}/> и является проектом с открытым исходным кодом, доступным на <TextURL text='Github' href={urls.gitrepo}/>.</p>
|
||||||
|
|
|
@ -6,6 +6,7 @@ function HelpRSFormMeta() {
|
||||||
<p>Для <b>общедоступных</b> схем владельцем может стать любой пользователь</p>
|
<p>Для <b>общедоступных</b> схем владельцем может стать любой пользователь</p>
|
||||||
<p>Для <b>библиотечных</b> схем правом редактирования обладают только администраторы</p>
|
<p>Для <b>библиотечных</b> схем правом редактирования обладают только администраторы</p>
|
||||||
<p><b>Клонировать</b> - создать копию схемы для дальнейшего редактирования</p>
|
<p><b>Клонировать</b> - создать копию схемы для дальнейшего редактирования</p>
|
||||||
|
<p><b>Отслеживание</b> - возможность видеть схему в Библиотеке и использовать фильтры</p>
|
||||||
<p><b>Загрузить/Выгрузить схему</b> - взаимодействие с Экстеор через файлы формата TRS</p>
|
<p><b>Загрузить/Выгрузить схему</b> - взаимодействие с Экстеор через файлы формата TRS</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import { prefixes } from '../../utils/constants';
|
import { prefixes } from '../../utils/constants';
|
||||||
import { mapCstClassInfo } from '../../utils/staticUI';
|
import { getCstClassColor, mapCstClassInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface InfoCstClassProps {
|
interface InfoCstClassProps {
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfoCstClass({ title }: InfoCstClassProps) {
|
function InfoCstClass({ title }: InfoCstClassProps) {
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
{ title && <h1>{title}</h1>}
|
{ title && <h1>{title}</h1>}
|
||||||
{ [... mapCstClassInfo.values()].map(
|
{ [... mapCstClassInfo.entries()].map(
|
||||||
(info, index) => {
|
([cstClass, info], index) => {
|
||||||
return (
|
return (
|
||||||
<p key={`${prefixes.cst_status_list}${index}`}>
|
<p key={`${prefixes.cst_status_list}${index}`}>
|
||||||
<span className={`px-1 inline-block font-semibold min-w-[6.5rem] text-center border ${info.color}`}>
|
<span
|
||||||
|
className='px-1 inline-block font-semibold min-w-[6.5rem] text-center borde'
|
||||||
|
style={{backgroundColor: getCstClassColor(cstClass, colors)}}
|
||||||
|
|
||||||
|
>
|
||||||
{info.text}
|
{info.text}
|
||||||
</span>
|
</span>
|
||||||
<span> - </span>
|
<span> - </span>
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import { prefixes } from '../../utils/constants';
|
import { prefixes } from '../../utils/constants';
|
||||||
import { mapStatusInfo } from '../../utils/staticUI';
|
import { getCstStatusColor, mapStatusInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface InfoCstStatusProps {
|
interface InfoCstStatusProps {
|
||||||
title?: string
|
title?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfoCstStatus({ title }: InfoCstStatusProps) {
|
function InfoCstStatus({ title }: InfoCstStatusProps) {
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col gap-1'>
|
<div className='flex flex-col gap-1'>
|
||||||
{ title && <h1>{title}</h1>}
|
{ title && <h1>{title}</h1>}
|
||||||
{ [... mapStatusInfo.values()].map(
|
{ [... mapStatusInfo.entries()].map(
|
||||||
(info, index) => {
|
([status, info], index) => {
|
||||||
return (
|
return (
|
||||||
<p key={`${prefixes.cst_status_list}${index}`}>
|
<p key={`${prefixes.cst_status_list}${index}`}>
|
||||||
<span className={`px-1 inline-block font-semibold min-w-[4rem] text-center border ${info.color}`}>
|
<span
|
||||||
|
className='px-1 inline-block font-semibold min-w-[4rem] text-center border'
|
||||||
|
style={{backgroundColor: getCstStatusColor(status, colors)}}
|
||||||
|
>
|
||||||
{info.text}
|
{info.text}
|
||||||
</span>
|
</span>
|
||||||
<span> - </span>
|
<span> - </span>
|
||||||
|
|
|
@ -8,7 +8,7 @@ function Logo({ title }: LogoProps) {
|
||||||
return (
|
return (
|
||||||
<Link to='/' className='flex items-center mr-4' tabIndex={-1}>
|
<Link to='/' className='flex items-center mr-4' tabIndex={-1}>
|
||||||
<img src='/favicon.svg' className='min-h-[2rem] mr-2 min-w-[2rem]' alt=''/>
|
<img src='/favicon.svg' className='min-h-[2rem] mr-2 min-w-[2rem]' alt=''/>
|
||||||
<span className='self-center hidden text-xl font-semibold lg:block whitespace-nowrap dark:text-white'>{title}</span>
|
<span className='self-center hidden text-xl font-semibold md:block whitespace-nowrap dark:text-white'>{title}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@ function Navigation () {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { noNavigation, toggleNoNavigation } = useConceptTheme();
|
const { noNavigation, toggleNoNavigation } = useConceptTheme();
|
||||||
|
|
||||||
const navigateCommon = () => { navigate('/library?filter=common') };
|
const navigateLibrary = () => navigate('/library');
|
||||||
const navigateHelp = () => { navigate('/manuals') };
|
const navigateHelp = () => navigate('/manuals');
|
||||||
|
|
||||||
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'>
|
||||||
|
@ -42,7 +42,7 @@ function Navigation () {
|
||||||
text='Библиотека'
|
text='Библиотека'
|
||||||
description='Библиотека концептуальных схем'
|
description='Библиотека концептуальных схем'
|
||||||
icon={<LibraryIcon />}
|
icon={<LibraryIcon />}
|
||||||
onClick={navigateCommon}
|
onClick={navigateLibrary}
|
||||||
/>
|
/>
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
text='Справка'
|
text='Справка'
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
|
import { LibraryFilterStrategy } from '../../utils/models';
|
||||||
import Dropdown from '../Common/Dropdown';
|
import Dropdown from '../Common/Dropdown';
|
||||||
import DropdownButton from '../Common/DropdownButton';
|
import DropdownButton from '../Common/DropdownButton';
|
||||||
|
|
||||||
|
@ -15,27 +16,33 @@ function UserDropdown({ hideDropdown }: UserDropdownProps) {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
|
|
||||||
const navigateProfile = () => {
|
const navigateProfile = () => {
|
||||||
hideDropdown()
|
hideDropdown();
|
||||||
navigate('/profile');
|
navigate('/profile');
|
||||||
};
|
};
|
||||||
|
|
||||||
const logoutAndRedirect =
|
const logoutAndRedirect =
|
||||||
() => {
|
() => {
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
logout(() => { navigate('/login/'); })
|
logout(() => navigate('/login/'));
|
||||||
};
|
};
|
||||||
|
|
||||||
const navigateMyWork = () => {
|
const navigateMyWork = () => {
|
||||||
hideDropdown();
|
hideDropdown();
|
||||||
navigate('/library?filter=personal');
|
navigate(`/library?filter=${LibraryFilterStrategy.PERSONAL}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown widthClass='w-36' stretchLeft>
|
<Dropdown widthClass='w-36' stretchLeft>
|
||||||
<DropdownButton description='Профиль пользователя' onClick={navigateProfile}>
|
<DropdownButton
|
||||||
|
description='Профиль пользователя'
|
||||||
|
onClick={navigateProfile}
|
||||||
|
>
|
||||||
{user?.username}
|
{user?.username}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton description='Переключение темы оформления' onClick={toggleDarkMode}>
|
<DropdownButton
|
||||||
|
description='Переключение темы оформления'
|
||||||
|
onClick={toggleDarkMode}
|
||||||
|
>
|
||||||
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
{darkMode ? 'Светлая тема' : 'Темная тема'}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
<DropdownButton onClick={navigateMyWork}>
|
<DropdownButton onClick={navigateMyWork}>
|
||||||
|
|
|
@ -30,7 +30,7 @@ function UserMenu() {
|
||||||
</div>
|
</div>
|
||||||
{ user && menu.isActive &&
|
{ user && menu.isActive &&
|
||||||
<UserDropdown
|
<UserDropdown
|
||||||
hideDropdown={() => { menu.hide(); }}
|
hideDropdown={() => menu.hide()}
|
||||||
/>}
|
/>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { bracketMatching, MatchResult } from '@codemirror/language';
|
import { bracketMatching, MatchResult } from '@codemirror/language';
|
||||||
import { Decoration, EditorView } from '@codemirror/view';
|
import { Decoration, EditorView } from '@codemirror/view';
|
||||||
|
|
||||||
|
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"});
|
||||||
|
|
||||||
|
@ -14,31 +16,9 @@ function bracketRender(match: MatchResult) {
|
||||||
return decorations;
|
return decorations;
|
||||||
}
|
}
|
||||||
|
|
||||||
const darkTheme = EditorView.baseTheme({
|
const darkTheme = EditorView.baseTheme(bracketsDarkT);
|
||||||
'.cc-matchingBracket': {
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
'.cc-nonmatchingBracket': {
|
|
||||||
color: '#ef4444',
|
|
||||||
fontWeight: 700,
|
|
||||||
},
|
|
||||||
'&.cm-focused .cc-matchingBracket': {
|
|
||||||
backgroundColor: '#734f00',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const lightTheme = EditorView.baseTheme({
|
const lightTheme = EditorView.baseTheme(bracketsLightT);
|
||||||
'.cc-matchingBracket': {
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
'.cc-nonmatchingBracket': {
|
|
||||||
color: '#ef4444',
|
|
||||||
fontWeight: 700,
|
|
||||||
},
|
|
||||||
'&.cm-focused .cc-matchingBracket': {
|
|
||||||
backgroundColor: '#dae6f2',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ccBracketMatching(darkMode: boolean) {
|
export function ccBracketMatching(darkMode: boolean) {
|
||||||
return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme];
|
return [bracketMatching({ renderMatch: bracketRender }), darkMode ? darkTheme : lightTheme];
|
||||||
|
|
|
@ -54,7 +54,7 @@ function RSInput({
|
||||||
id, label, innerref, onChange, editable,
|
id, label, innerref, onChange, editable,
|
||||||
...props
|
...props
|
||||||
}: RSInputProps) {
|
}: RSInputProps) {
|
||||||
const { darkMode } = useConceptTheme();
|
const { darkMode, colors } = useConceptTheme();
|
||||||
const { schema } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
|
|
||||||
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
const internalRef = useRef<ReactCodeMirrorRef>(null);
|
||||||
|
@ -69,9 +69,9 @@ function RSInput({
|
||||||
theme: 'light',
|
theme: 'light',
|
||||||
settings: {
|
settings: {
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
background: editable ? '#ffffff' : '#f0f2f7',
|
background: editable ? colors.input : colors.inputDisabled,
|
||||||
foreground: '#000000',
|
foreground: colors.text,
|
||||||
selection: '#aacef2',
|
selection: colors.selection,
|
||||||
caret: '#5d00ff',
|
caret: '#5d00ff',
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
|
@ -83,16 +83,16 @@ function RSInput({
|
||||||
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
|
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
|
||||||
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
|
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
|
||||||
]
|
]
|
||||||
}), [editable]);
|
}), [editable, colors]);
|
||||||
|
|
||||||
const darkTheme: Extension = useMemo(
|
const darkTheme: Extension = useMemo(
|
||||||
() => createTheme({
|
() => createTheme({
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
settings: {
|
settings: {
|
||||||
fontFamily: 'inherit',
|
fontFamily: 'inherit',
|
||||||
background: editable ? '#070b12' : '#374151',
|
background: editable ? colors.input : colors.inputDisabled,
|
||||||
foreground: '#e4e4e7',
|
foreground: colors.text,
|
||||||
selection: '#8c6000',
|
selection: colors.selection,
|
||||||
caret: '#ffaa00'
|
caret: '#ffaa00'
|
||||||
},
|
},
|
||||||
styles: [
|
styles: [
|
||||||
|
@ -104,7 +104,7 @@ function RSInput({
|
||||||
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
|
{ tag: t.controlKeyword, class: 'font-semibold'}, // R | I | D
|
||||||
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
|
{ tag: t.unit, class: 'text-[0.75rem]' }, // indicies
|
||||||
]
|
]
|
||||||
}), [editable]);
|
}), [editable, colors]);
|
||||||
|
|
||||||
const editorExtensions = useMemo(
|
const editorExtensions = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
|
|
@ -3,7 +3,8 @@ import { createContext, useCallback, useContext, useLayoutEffect, useState } fro
|
||||||
import { type ErrorInfo } from '../components/BackendError';
|
import { type ErrorInfo } from '../components/BackendError';
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
import useLocalStorage from '../hooks/useLocalStorage';
|
||||||
import { type DataCallback, getAuth, patchPassword,postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
import { type DataCallback, getAuth, patchPassword,postLogin, postLogout, postSignup } from '../utils/backendAPI';
|
||||||
import { ICurrentUser, IUserLoginData, IUserProfile, IUserSignupData, IUserUpdatePassword } from '../utils/models';
|
import { ICurrentUser, IUserInfo, IUserLoginData, IUserProfile, IUserSignupData, IUserUpdatePassword } from '../utils/models';
|
||||||
|
import { useUsers } from './UsersContext';
|
||||||
|
|
||||||
interface IAuthContext {
|
interface IAuthContext {
|
||||||
user: ICurrentUser | undefined
|
user: ICurrentUser | undefined
|
||||||
|
@ -32,6 +33,7 @@ interface AuthStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthState = ({ children }: AuthStateProps) => {
|
export const AuthState = ({ children }: AuthStateProps) => {
|
||||||
|
const { users } = useUsers();
|
||||||
const [user, setUser] = useLocalStorage<ICurrentUser | undefined>('user', undefined);
|
const [user, setUser] = useLocalStorage<ICurrentUser | undefined>('user', undefined);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
@ -82,6 +84,7 @@ export const AuthState = ({ children }: AuthStateProps) => {
|
||||||
setLoading: setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newData => reload(() => {
|
onSuccess: newData => reload(() => {
|
||||||
|
users.push(newData as IUserInfo);
|
||||||
if (callback) callback(newData);
|
if (callback) callback(newData);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,16 +43,23 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
const filter = useCallback(
|
const filter = useCallback(
|
||||||
(params: ILibraryFilter) => {
|
(params: ILibraryFilter) => {
|
||||||
let result = items;
|
let result = items;
|
||||||
if (params.ownedBy) {
|
if (params.is_owned) {
|
||||||
result = result.filter(item =>
|
result = result.filter(item => item.owner === user?.id);
|
||||||
item.owner === params.ownedBy
|
|
||||||
|| user?.subscriptions.includes(item.id));
|
|
||||||
}
|
}
|
||||||
if (params.is_common !== undefined) {
|
if (params.is_common !== undefined) {
|
||||||
result = result.filter(item => item.is_common === params.is_common);
|
result = result.filter(item => item.is_common === params.is_common);
|
||||||
}
|
}
|
||||||
if (params.queryMeta) {
|
if (params.is_canonical !== undefined) {
|
||||||
result = result.filter(item => matchLibraryItem(params.queryMeta!, item));
|
result = result.filter(item => item.is_canonical === params.is_canonical);
|
||||||
|
}
|
||||||
|
if (params.is_subscribed !== undefined) {
|
||||||
|
result = result.filter(item => user?.subscriptions.includes(item.id));
|
||||||
|
}
|
||||||
|
if (params.is_personal !== undefined) {
|
||||||
|
result = result.filter(item => user?.subscriptions.includes(item.id) || item.owner === user?.id);
|
||||||
|
}
|
||||||
|
if (params.query) {
|
||||||
|
result = result.filter(item => matchLibraryItem(params.query!, item));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}, [items, user]);
|
}, [items, user]);
|
||||||
|
@ -84,12 +91,14 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newSchema => {
|
onSuccess: newSchema => reload(() => {
|
||||||
reload();
|
if (user && !user.subscriptions.includes(newSchema.id)) {
|
||||||
|
user.subscriptions.push(newSchema.id);
|
||||||
|
}
|
||||||
if (callback) callback(newSchema);
|
if (callback) callback(newSchema);
|
||||||
}
|
})
|
||||||
});
|
});
|
||||||
}, [reload]);
|
}, [reload, user]);
|
||||||
|
|
||||||
const destroySchema = useCallback(
|
const destroySchema = useCallback(
|
||||||
(target: number, callback?: () => void) => {
|
(target: number, callback?: () => void) => {
|
||||||
|
@ -99,10 +108,13 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: () => reload(() => {
|
onSuccess: () => reload(() => {
|
||||||
|
if (user && user.subscriptions.includes(target)) {
|
||||||
|
user.subscriptions.splice(user.subscriptions.findIndex(item => item === target), 1);
|
||||||
|
}
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}, [setError, reload]);
|
}, [setError, reload, user]);
|
||||||
|
|
||||||
const cloneSchema = useCallback(
|
const cloneSchema = useCallback(
|
||||||
(target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => {
|
(target: number, data: IRSFormCreateData, callback: DataCallback<IRSFormData>) => {
|
||||||
|
@ -116,6 +128,9 @@ export const LibraryState = ({ children }: LibraryStateProps) => {
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newSchema => reload(() => {
|
onSuccess: newSchema => reload(() => {
|
||||||
|
if (user && !user.subscriptions.includes(newSchema.id)) {
|
||||||
|
user.subscriptions.push(newSchema.id);
|
||||||
|
}
|
||||||
if (callback) callback(newSchema);
|
if (callback) callback(newSchema);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
ILibraryUpdateData, IRSForm, IRSFormUploadData
|
ILibraryUpdateData, IRSForm, IRSFormUploadData
|
||||||
} from '../utils/models'
|
} from '../utils/models'
|
||||||
import { useAuth } from './AuthContext'
|
import { useAuth } from './AuthContext'
|
||||||
|
import { useLibrary } from './LibraryContext'
|
||||||
|
|
||||||
interface IRSFormContext {
|
interface IRSFormContext {
|
||||||
schema?: IRSForm
|
schema?: IRSForm
|
||||||
|
@ -66,6 +67,7 @@ interface RSFormStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
|
const library = useLibrary();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID });
|
const { schema, reload, error, setError, setSchema, loading } = useRSFormDetails({ target: schemaID });
|
||||||
const [ processing, setProcessing ] = useState(false);
|
const [ processing, setProcessing ] = useState(false);
|
||||||
|
@ -114,10 +116,14 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setSchema(Object.assign(schema, newData));
|
setSchema(Object.assign(schema, newData));
|
||||||
|
const libraryItem = library.items.find(item => item.id === newData.id);
|
||||||
|
if (libraryItem) {
|
||||||
|
Object.assign(libraryItem, newData);
|
||||||
|
}
|
||||||
if (callback) callback(newData);
|
if (callback) callback(newData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [schemaID, setError, setSchema, schema]);
|
}, [schemaID, setError, setSchema, schema, library]);
|
||||||
|
|
||||||
const upload = useCallback(
|
const upload = useCallback(
|
||||||
(data: IRSFormUploadData, callback?: () => void) => {
|
(data: IRSFormUploadData, callback?: () => void) => {
|
||||||
|
@ -132,10 +138,14 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setSchema(newData);
|
setSchema(newData);
|
||||||
|
const libraryItem = library.items.find(item => item.id === newData.id);
|
||||||
|
if (libraryItem) {
|
||||||
|
Object.assign(libraryItem, newData);
|
||||||
|
}
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [schemaID, setError, setSchema, schema]);
|
}, [schemaID, setError, setSchema, schema, library]);
|
||||||
|
|
||||||
const claim = useCallback(
|
const claim = useCallback(
|
||||||
(callback?: DataCallback<ILibraryItem>) => {
|
(callback?: DataCallback<ILibraryItem>) => {
|
||||||
|
@ -149,10 +159,17 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setSchema(Object.assign(schema, newData));
|
setSchema(Object.assign(schema, newData));
|
||||||
|
const libraryItem = library.items.find(item => item.id === newData.id);
|
||||||
|
if (libraryItem) {
|
||||||
|
libraryItem.owner = user.id
|
||||||
|
}
|
||||||
|
if (!user.subscriptions.includes(schema.id)) {
|
||||||
|
user.subscriptions.push(schema.id);
|
||||||
|
}
|
||||||
if (callback) callback(newData);
|
if (callback) callback(newData);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [schemaID, setError, schema, user, setSchema]);
|
}, [schemaID, setError, schema, user, setSchema, library]);
|
||||||
|
|
||||||
const subscribe = useCallback(
|
const subscribe = useCallback(
|
||||||
(callback?: () => void) => {
|
(callback?: () => void) => {
|
||||||
|
@ -266,9 +283,9 @@ export const RSFormState = ({ schemaID, children }: RSFormStateProps) => {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => setError(error),
|
onError: error => setError(error),
|
||||||
onSuccess: newData => {
|
onSuccess: newData => reload(setProcessing, () => {
|
||||||
reload(setProcessing, () => { if (callback) callback(newData); })
|
if (callback) callback(newData);
|
||||||
}
|
})
|
||||||
});
|
});
|
||||||
}, [setError, reload]);
|
}, [setError, reload]);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react';
|
import { createContext, useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import useLocalStorage from '../hooks/useLocalStorage';
|
import useLocalStorage from '../hooks/useLocalStorage';
|
||||||
|
import { darkT, IColorTheme, lightT } from '../utils/color';
|
||||||
|
|
||||||
interface IThemeContext {
|
interface IThemeContext {
|
||||||
darkMode: boolean
|
|
||||||
noNavigation: boolean
|
|
||||||
viewportHeight: string
|
viewportHeight: string
|
||||||
mainHeight: string
|
mainHeight: string
|
||||||
|
|
||||||
|
colors: IColorTheme
|
||||||
|
|
||||||
|
darkMode: boolean
|
||||||
toggleDarkMode: () => void
|
toggleDarkMode: () => void
|
||||||
|
|
||||||
|
noNavigation: boolean
|
||||||
|
noFooter: boolean
|
||||||
|
setNoFooter: (value: boolean) => void
|
||||||
toggleNoNavigation: () => void
|
toggleNoNavigation: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +22,7 @@ const ThemeContext = createContext<IThemeContext | null>(null);
|
||||||
export const useConceptTheme = () => {
|
export const useConceptTheme = () => {
|
||||||
const context = useContext(ThemeContext);
|
const context = useContext(ThemeContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
throw new Error('useConceptTheme has to be used within <ThemeState.Provider>');
|
||||||
'useConceptTheme has to be used within <ThemeState.Provider>'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
@ -28,9 +33,11 @@ interface ThemeStateProps {
|
||||||
|
|
||||||
export const ThemeState = ({ children }: ThemeStateProps) => {
|
export const ThemeState = ({ children }: ThemeStateProps) => {
|
||||||
const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
|
const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
|
||||||
|
const [colors, setColors] = useState<IColorTheme>(lightT);
|
||||||
const [noNavigation, setNoNavigation] = useState(false);
|
const [noNavigation, setNoNavigation] = useState(false);
|
||||||
|
const [noFooter, setNoFooter] = useState(false);
|
||||||
|
|
||||||
const setDarkClass = (isDark: boolean) => {
|
function setDarkClass(isDark: boolean) {
|
||||||
const root = window.document.documentElement;
|
const root = window.document.documentElement;
|
||||||
if (isDark) {
|
if (isDark) {
|
||||||
root.classList.add('dark');
|
root.classList.add('dark');
|
||||||
|
@ -38,12 +45,16 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
||||||
root.classList.remove('dark');
|
root.classList.remove('dark');
|
||||||
}
|
}
|
||||||
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
|
root.setAttribute('data-color-scheme', !isDark ? 'light' : 'dark');
|
||||||
};
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
setDarkClass(darkMode)
|
setDarkClass(darkMode)
|
||||||
}, [darkMode]);
|
}, [darkMode]);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
setColors(darkMode ? darkT : lightT)
|
||||||
|
}, [darkMode, setColors]);
|
||||||
|
|
||||||
const mainHeight = useMemo(
|
const mainHeight = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return !noNavigation ?
|
return !noNavigation ?
|
||||||
|
@ -60,10 +71,11 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeContext.Provider value={{
|
<ThemeContext.Provider value={{
|
||||||
darkMode,
|
darkMode, colors,
|
||||||
noNavigation,
|
noNavigation, noFooter,
|
||||||
toggleDarkMode: () => setDarkMode(prev => !prev),
|
toggleDarkMode: () => setDarkMode(prev => !prev),
|
||||||
toggleNoNavigation: () => setNoNavigation(prev => !prev),
|
toggleNoNavigation: () => setNoNavigation(prev => !prev),
|
||||||
|
setNoFooter,
|
||||||
viewportHeight, mainHeight
|
viewportHeight, mainHeight
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { createContext, useCallback, useContext, useEffect, useState } from 'rea
|
||||||
import { ErrorInfo } from '../components/BackendError';
|
import { ErrorInfo } from '../components/BackendError';
|
||||||
import { DataCallback, getProfile, patchProfile } from '../utils/backendAPI';
|
import { DataCallback, getProfile, patchProfile } from '../utils/backendAPI';
|
||||||
import { IUserProfile, IUserUpdateData } from '../utils/models';
|
import { IUserProfile, IUserUpdateData } from '../utils/models';
|
||||||
|
import { useUsers } from './UsersContext';
|
||||||
|
|
||||||
interface IUserProfileContext {
|
interface IUserProfileContext {
|
||||||
user: IUserProfile | undefined
|
user: IUserProfile | undefined
|
||||||
|
@ -30,39 +31,43 @@ interface UserProfileStateProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserProfileState = ({ children }: UserProfileStateProps) => {
|
export const UserProfileState = ({ children }: UserProfileStateProps) => {
|
||||||
|
const { users } = useUsers();
|
||||||
const [user, setUser] = useState<IUserProfile | undefined>(undefined);
|
const [user, setUser] = useState<IUserProfile | undefined>(undefined);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [processing, setProcessing] = useState(false);
|
const [processing, setProcessing] = useState(false);
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
|
|
||||||
const reload = useCallback(
|
const reload = useCallback(
|
||||||
() => {
|
() => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
getProfile({
|
getProfile({
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setLoading,
|
setLoading: setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => setError(error),
|
||||||
onSuccess: newData => { setUser(newData); }
|
onSuccess: newData => setUser(newData)
|
||||||
});
|
});
|
||||||
}, [setUser]
|
}, [setUser]);
|
||||||
);
|
|
||||||
|
|
||||||
const updateUser = useCallback(
|
const updateUser = useCallback(
|
||||||
(data: IUserUpdateData, callback?: DataCallback<IUserProfile>) => {
|
(data: IUserUpdateData, callback?: DataCallback<IUserProfile>) => {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
patchProfile({
|
patchProfile({
|
||||||
data: data,
|
data: data,
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setProcessing,
|
setLoading: setProcessing,
|
||||||
onError: error => { setError(error); },
|
onError: error => setError(error),
|
||||||
onSuccess: newData => {
|
onSuccess: newData => {
|
||||||
setUser(newData);
|
setUser(newData);
|
||||||
if (callback) callback(newData);
|
const libraryUser = users.find(item => item.id === user?.id);
|
||||||
|
if (libraryUser) {
|
||||||
|
libraryUser.first_name = newData.first_name;
|
||||||
|
libraryUser.last_name = newData.last_name;
|
||||||
}
|
}
|
||||||
});
|
if (callback) callback(newData);
|
||||||
}, [setUser]
|
}
|
||||||
);
|
});
|
||||||
|
}, [setUser, users]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
reload();
|
reload();
|
||||||
|
|
|
@ -13,9 +13,7 @@ const UsersContext = createContext<IUsersContext | null>(null)
|
||||||
export const useUsers = (): IUsersContext => {
|
export const useUsers = (): IUsersContext => {
|
||||||
const context = useContext(UsersContext);
|
const context = useContext(UsersContext);
|
||||||
if (context === null) {
|
if (context === null) {
|
||||||
throw new Error(
|
throw new Error('useUsers has to be used within <UsersState.Provider>');
|
||||||
'useUsers has to be used within <UsersState.Provider>'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,23 +6,52 @@ import { RSErrorType } from '../utils/enums';
|
||||||
import { CstType, IConstituenta, IExpressionParse, IFunctionArg, type IRSForm } from '../utils/models';
|
import { CstType, IConstituenta, IExpressionParse, IFunctionArg, type IRSForm } from '../utils/models';
|
||||||
import { getCstExpressionPrefix } from '../utils/staticUI';
|
import { getCstExpressionPrefix } from '../utils/staticUI';
|
||||||
|
|
||||||
|
const LOGIC_TYPIIFCATION = 'LOGIC';
|
||||||
|
|
||||||
function checkTypeConsistency(type: CstType, typification: string, args: IFunctionArg[]): boolean {
|
function checkTypeConsistency(type: CstType, typification: string, args: IFunctionArg[]): boolean {
|
||||||
|
console.log(typification)
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case CstType.BASE:
|
case CstType.BASE:
|
||||||
case CstType.CONSTANT:
|
case CstType.CONSTANT:
|
||||||
case CstType.STRUCTURED:
|
case CstType.STRUCTURED:
|
||||||
case CstType.TERM:
|
case CstType.TERM:
|
||||||
return typification !== '' && args.length === 0;
|
return typification !== LOGIC_TYPIIFCATION && args.length === 0;
|
||||||
|
|
||||||
case CstType.AXIOM:
|
case CstType.AXIOM:
|
||||||
case CstType.THEOREM:
|
case CstType.THEOREM:
|
||||||
return typification === '' && args.length === 0;
|
return typification === LOGIC_TYPIIFCATION && args.length === 0;
|
||||||
|
|
||||||
case CstType.FUNCTION:
|
case CstType.FUNCTION:
|
||||||
return typification !== '' && args.length !== 0;
|
return typification !== LOGIC_TYPIIFCATION && args.length !== 0;
|
||||||
|
|
||||||
case CstType.PREDICATE:
|
case CstType.PREDICATE:
|
||||||
return typification === '' && args.length !== 0;
|
return typification === LOGIC_TYPIIFCATION && args.length !== 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustResults(parse: IExpressionParse, emptyExpression: boolean, cstType: CstType) {
|
||||||
|
if (!parse.parseResult && parse.errors.length > 0 && parse.errors[0].errorType !== RSErrorType.syntax) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cstType === CstType.BASE || cstType === CstType.CONSTANT) {
|
||||||
|
if (!emptyExpression) {
|
||||||
|
parse.parseResult = false;
|
||||||
|
parse.errors.push({
|
||||||
|
errorType: RSErrorType.globalNonemptyBase,
|
||||||
|
isCritical: true,
|
||||||
|
params: [],
|
||||||
|
position: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!checkTypeConsistency(cstType, parse.typification, parse.args)) {
|
||||||
|
parse.parseResult = false;
|
||||||
|
parse.errors.push({
|
||||||
|
errorType: RSErrorType.globalUnexpectedType,
|
||||||
|
isCritical: true,
|
||||||
|
params: [],
|
||||||
|
position: 0
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +60,7 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
const [parseData, setParseData] = useState<IExpressionParse | undefined>(undefined);
|
const [parseData, setParseData] = useState<IExpressionParse | undefined>(undefined);
|
||||||
|
|
||||||
const resetParse = useCallback(() => { setParseData(undefined); }, []);
|
const resetParse = useCallback(() => setParseData(undefined), []);
|
||||||
|
|
||||||
function checkExpression(expression: string, activeCst?: IConstituenta, onSuccess?: DataCallback<IExpressionParse>) {
|
function checkExpression(expression: string, activeCst?: IConstituenta, onSuccess?: DataCallback<IExpressionParse>) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
@ -39,29 +68,10 @@ function useCheckExpression({ schema }: { schema?: IRSForm }) {
|
||||||
data: { expression: expression },
|
data: { expression: expression },
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => setError(error),
|
||||||
onSuccess: parse => {
|
onSuccess: parse => {
|
||||||
if (activeCst && parse.parseResult) {
|
if (activeCst) {
|
||||||
if (activeCst.cstType == CstType.BASE || activeCst.cstType == CstType.CONSTANT) {
|
adjustResults(parse, expression === getCstExpressionPrefix(activeCst), activeCst.cstType);
|
||||||
if (expression !== getCstExpressionPrefix(activeCst)) {
|
|
||||||
parse.parseResult = false;
|
|
||||||
parse.errors.push({
|
|
||||||
errorType: RSErrorType.globalNonemptyBase,
|
|
||||||
isCritical: true,
|
|
||||||
params: [],
|
|
||||||
position: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!checkTypeConsistency(activeCst.cstType, parse.typification, parse.args)) {
|
|
||||||
parse.parseResult = false;
|
|
||||||
parse.errors.push({
|
|
||||||
errorType: RSErrorType.globalUnexpectedType,
|
|
||||||
isCritical: true,
|
|
||||||
params: [],
|
|
||||||
position: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setParseData(parse);
|
setParseData(parse);
|
||||||
if (onSuccess) onSuccess(parse);
|
if (onSuccess) onSuccess(parse);
|
||||||
|
|
|
@ -6,14 +6,14 @@ function useDropdown() {
|
||||||
const [isActive, setIsActive] = useState(false);
|
const [isActive, setIsActive] = useState(false);
|
||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
|
|
||||||
useClickedOutside({ ref, callback: () => { setIsActive(false); } })
|
useClickedOutside({ ref, callback: () => setIsActive(false) })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
ref,
|
ref,
|
||||||
isActive,
|
isActive,
|
||||||
setIsActive,
|
setIsActive,
|
||||||
toggle: () => { setIsActive(!isActive); },
|
toggle: () => setIsActive(!isActive),
|
||||||
hide: () => { setIsActive(false); }
|
hide: () => setIsActive(false)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,10 @@ export function useRSFormDetails({ target }: { target?: string }) {
|
||||||
getRSFormDetails(target, {
|
getRSFormDetails(target, {
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading: setCustomLoading ?? setLoading,
|
setLoading: setCustomLoading ?? setLoading,
|
||||||
onError: error => { setInnerSchema(undefined); setError(error); },
|
onError: error => {
|
||||||
|
setInnerSchema(undefined);
|
||||||
|
setError(error);
|
||||||
|
},
|
||||||
onSuccess: schema => {
|
onSuccess: schema => {
|
||||||
setSchema(schema);
|
setSchema(schema);
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
|
|
@ -9,7 +9,7 @@ function useResolveText({ schema }: { schema?: IRSForm }) {
|
||||||
const [error, setError] = useState<ErrorInfo>(undefined);
|
const [error, setError] = useState<ErrorInfo>(undefined);
|
||||||
const [refsData, setRefsData] = useState<IReferenceData | undefined>(undefined);
|
const [refsData, setRefsData] = useState<IReferenceData | undefined>(undefined);
|
||||||
|
|
||||||
const resetData = useCallback(() => { setRefsData(undefined); }, []);
|
const resetData = useCallback(() => setRefsData(undefined), []);
|
||||||
|
|
||||||
function resolveText(text: string, onSuccess?: DataCallback<IReferenceData>) {
|
function resolveText(text: string, onSuccess?: DataCallback<IReferenceData>) {
|
||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
@ -17,7 +17,7 @@ function useResolveText({ schema }: { schema?: IRSForm }) {
|
||||||
data: { text: text },
|
data: { text: text },
|
||||||
showError: true,
|
showError: true,
|
||||||
setLoading,
|
setLoading,
|
||||||
onError: error => { setError(error); },
|
onError: error => setError(error),
|
||||||
onSuccess: data => {
|
onSuccess: data => {
|
||||||
setRefsData(data);
|
setRefsData(data);
|
||||||
if (onSuccess) onSuccess(data);
|
if (onSuccess) onSuccess(data);
|
||||||
|
|
|
@ -42,6 +42,10 @@
|
||||||
@apply clr-border rounded
|
@apply clr-border rounded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-btn {
|
||||||
|
@apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400
|
||||||
|
}
|
||||||
|
|
||||||
.clr-border {
|
.clr-border {
|
||||||
@apply border-gray-300 dark:border-[#434343]
|
@apply border-gray-300 dark:border-[#434343]
|
||||||
}
|
}
|
||||||
|
@ -102,7 +106,7 @@
|
||||||
@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
|
@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 {
|
.clr-btn-default {
|
||||||
@apply text-gray-600 dark:text-zinc-200 dark:disabled:text-zinc-400 disabled:text-gray-400 bg-[#f0f2f7] hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400
|
@apply bg-[#f0f2f7] hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-400 text-btn
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Transparent button */
|
/* Transparent button */
|
||||||
|
|
|
@ -35,15 +35,15 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
>
|
>
|
||||||
<IntlProvider locale='ru' defaultLocale='ru'>
|
<IntlProvider locale='ru' defaultLocale='ru'>
|
||||||
<ThemeState>
|
<ThemeState>
|
||||||
<AuthState>
|
|
||||||
<UsersState>
|
<UsersState>
|
||||||
|
<AuthState>
|
||||||
<LibraryState>
|
<LibraryState>
|
||||||
|
|
||||||
<App />
|
<App />
|
||||||
|
|
||||||
</LibraryState>
|
</LibraryState>
|
||||||
</UsersState>
|
|
||||||
</AuthState>
|
</AuthState>
|
||||||
|
</UsersState>
|
||||||
</ThemeState>
|
</ThemeState>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
|
|
@ -15,7 +15,7 @@ function HomePage() {
|
||||||
}, TIMEOUT_UI_REFRESH);
|
}, TIMEOUT_UI_REFRESH);
|
||||||
} else if(!user.is_staff) {
|
} else if(!user.is_staff) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate('/library?filter=personal');
|
navigate('/library');
|
||||||
}, TIMEOUT_UI_REFRESH);
|
}, TIMEOUT_UI_REFRESH);
|
||||||
}
|
}
|
||||||
}, [navigate, user])
|
}, [navigate, user])
|
||||||
|
|
89
rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx
Normal file
89
rsconcept/frontend/src/pages/LibraryPage/PickerStrategy.tsx
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import Button from '../../components/Common/Button';
|
||||||
|
import Checkbox from '../../components/Common/Checkbox';
|
||||||
|
import Dropdown from '../../components/Common/Dropdown';
|
||||||
|
import DropdownButton from '../../components/Common/DropdownButton';
|
||||||
|
import { FilterCogIcon } from '../../components/Icons';
|
||||||
|
import useDropdown from '../../hooks/useDropdown';
|
||||||
|
import { LibraryFilterStrategy } from '../../utils/models';
|
||||||
|
|
||||||
|
interface PickerStrategyProps {
|
||||||
|
value: LibraryFilterStrategy
|
||||||
|
onChange: (value: LibraryFilterStrategy) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function PickerStrategy({ value, onChange }: PickerStrategyProps) {
|
||||||
|
const pickerMenu = useDropdown();
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(newValue: LibraryFilterStrategy) => {
|
||||||
|
pickerMenu.hide();
|
||||||
|
onChange(newValue);
|
||||||
|
}, [pickerMenu, onChange]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={pickerMenu.ref} className='h-full text-right'>
|
||||||
|
<Button
|
||||||
|
icon={<FilterCogIcon size={6} />}
|
||||||
|
dense
|
||||||
|
tooltip='Фильтры'
|
||||||
|
colorClass='clr-input clr-hover text-btn'
|
||||||
|
widthClass='h-full py-1 px-2 border-none'
|
||||||
|
onClick={pickerMenu.toggle}
|
||||||
|
/>
|
||||||
|
{ pickerMenu.isActive &&
|
||||||
|
<Dropdown>
|
||||||
|
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.MANUAL)}>
|
||||||
|
<Checkbox
|
||||||
|
value={value === LibraryFilterStrategy.MANUAL}
|
||||||
|
label='Отображать все'
|
||||||
|
widthClass='w-fit px-2'
|
||||||
|
/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.COMMON)}>
|
||||||
|
<Checkbox
|
||||||
|
value={value === LibraryFilterStrategy.COMMON}
|
||||||
|
label='Общедоступные'
|
||||||
|
widthClass='w-fit px-2'
|
||||||
|
tooltip='Отображать только общедоступные схемы'
|
||||||
|
/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.CANONICAL)}>
|
||||||
|
<Checkbox
|
||||||
|
value={value === LibraryFilterStrategy.CANONICAL}
|
||||||
|
label='Библиотечные'
|
||||||
|
widthClass='w-fit px-2'
|
||||||
|
tooltip='Отображать только библиотечные схемы'
|
||||||
|
/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.PERSONAL)}>
|
||||||
|
<Checkbox
|
||||||
|
value={value === LibraryFilterStrategy.PERSONAL}
|
||||||
|
label='Личные'
|
||||||
|
widthClass='w-fit px-2'
|
||||||
|
tooltip='Отображать только подписки и владеемые схемы'
|
||||||
|
/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.SUBSCRIBE)}>
|
||||||
|
<Checkbox
|
||||||
|
value={value === LibraryFilterStrategy.SUBSCRIBE}
|
||||||
|
label='Подписки'
|
||||||
|
widthClass='w-fit px-2'
|
||||||
|
tooltip='Отображать только подписки'
|
||||||
|
/>
|
||||||
|
</DropdownButton>
|
||||||
|
<DropdownButton onClick={() => handleChange(LibraryFilterStrategy.OWNED)}>
|
||||||
|
<Checkbox
|
||||||
|
value={value === LibraryFilterStrategy.OWNED}
|
||||||
|
label='Я - Владелец!'
|
||||||
|
widthClass='w-fit px-2'
|
||||||
|
tooltip='Отображать только владеемые схемы'
|
||||||
|
/>
|
||||||
|
</DropdownButton>
|
||||||
|
</Dropdown>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PickerStrategy;
|
|
@ -1,70 +1,96 @@
|
||||||
import { useLayoutEffect, useState } from 'react';
|
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { MagnifyingGlassIcon } from '../../components/Icons';
|
import { MagnifyingGlassIcon } from '../../components/Icons';
|
||||||
import { useAuth } from '../../context/AuthContext';
|
import { useAuth } from '../../context/AuthContext';
|
||||||
import { ILibraryFilter } from '../../utils/models';
|
import { ILibraryFilter, LibraryFilterStrategy } from '../../utils/models';
|
||||||
|
import PickerStrategy from './PickerStrategy';
|
||||||
|
|
||||||
|
|
||||||
|
function ApplyStrategy(strategy: LibraryFilterStrategy): ILibraryFilter {
|
||||||
|
switch (strategy) {
|
||||||
|
case LibraryFilterStrategy.MANUAL: return {};
|
||||||
|
case LibraryFilterStrategy.COMMON: return { is_common: true };
|
||||||
|
case LibraryFilterStrategy.CANONICAL: return { is_canonical: true };
|
||||||
|
case LibraryFilterStrategy.PERSONAL: return { is_personal: true };
|
||||||
|
case LibraryFilterStrategy.SUBSCRIBE: return { is_subscribed: true };
|
||||||
|
case LibraryFilterStrategy.OWNED: return { is_owned: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface SearchPanelProps {
|
interface SearchPanelProps {
|
||||||
filter: ILibraryFilter
|
total: number
|
||||||
|
filtered: number
|
||||||
setFilter: React.Dispatch<React.SetStateAction<ILibraryFilter>>
|
setFilter: React.Dispatch<React.SetStateAction<ILibraryFilter>>
|
||||||
}
|
}
|
||||||
|
|
||||||
function SearchPanel({ filter, setFilter }: SearchPanelProps) {
|
function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
|
||||||
|
const navigate = useNavigate();
|
||||||
const search = useLocation().search;
|
const search = useLocation().search;
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('');
|
||||||
|
const [strategy, setStrategy] = useState(LibraryFilterStrategy.MANUAL);
|
||||||
|
|
||||||
|
function handleChangeQuery(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
const newQuery = event.target.value;
|
||||||
|
setQuery(newQuery);
|
||||||
|
setFilter(prev => {
|
||||||
|
return {
|
||||||
|
query: newQuery,
|
||||||
|
is_owned: prev.is_owned,
|
||||||
|
is_common: prev.is_common,
|
||||||
|
is_canonical: prev.is_canonical,
|
||||||
|
is_subscribed: prev.is_subscribed,
|
||||||
|
is_personal: prev.is_personal
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const filterType = new URLSearchParams(search).get('filter');
|
const searchFilter = new URLSearchParams(search).get('filter') as LibraryFilterStrategy | null;
|
||||||
if (filterType === 'common') {
|
const inputStrategy = searchFilter && Object.values(LibraryFilterStrategy).includes(searchFilter) ? searchFilter : LibraryFilterStrategy.MANUAL;
|
||||||
setQuery('');
|
setQuery('')
|
||||||
setFilter({
|
setStrategy(inputStrategy)
|
||||||
is_common: true
|
setFilter(ApplyStrategy(inputStrategy));
|
||||||
});
|
|
||||||
} else if (filterType === 'personal' && user) {
|
|
||||||
setQuery('');
|
|
||||||
setFilter({
|
|
||||||
ownedBy: user.id!
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [user, search, setQuery, setFilter]);
|
}, [user, search, setQuery, setFilter]);
|
||||||
|
|
||||||
|
const handleChangeStrategy = useCallback(
|
||||||
|
(value: LibraryFilterStrategy) => {
|
||||||
|
if (value === strategy) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
navigate(`/library?filter=${value}`)
|
||||||
|
}, [strategy, navigate]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='sticky top-0 left-0 right-0 z-10 flex justify-center w-full border-b clr-bg-pop'>
|
<div className='sticky top-0 left-0 right-0 z-10 flex items-center justify-start w-full border-b clr-input'>
|
||||||
<div className='relative w-96'>
|
<div className='px-2 py-1 select-none whitespace-nowrap min-w-[10rem]'>
|
||||||
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
Фильтр
|
||||||
<MagnifyingGlassIcon />
|
<span className='ml-2'>
|
||||||
</div>
|
<b>{filtered}</b> из {total}
|
||||||
<input
|
</span>
|
||||||
type='text'
|
</div>
|
||||||
value={query}
|
<div className='flex items-center justify-center w-full pr-[10rem]'>
|
||||||
className='w-full p-2 pl-10 text-sm outline-none clr-bg-pop border-x clr-border'
|
<PickerStrategy
|
||||||
placeholder='Поиск схемы...'
|
value={strategy}
|
||||||
onChange={data => setQuery(data.target.value)}
|
onChange={handleChangeStrategy}
|
||||||
/>
|
/>
|
||||||
|
<div className='relative w-96 min-w-[10rem]'>
|
||||||
|
<div className='absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none'>
|
||||||
|
<MagnifyingGlassIcon />
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
value={query}
|
||||||
|
className='w-full p-2 pl-10 text-sm outline-none clr-input'
|
||||||
|
placeholder='Поиск схемы...'
|
||||||
|
onChange={handleChangeQuery}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SearchPanel;
|
export default SearchPanel;
|
||||||
|
|
||||||
|
|
||||||
{/* <div className='sticky top-0 left-0 right-0 z-10 flex items-start justify-between w-full gap-1 px-2 py-1 bg-white border-b rounded clr-bg-pop clr-border'>
|
|
||||||
<MatchModePicker
|
|
||||||
value={filterMatch}
|
|
||||||
onChange={setFilterMatch}
|
|
||||||
/>
|
|
||||||
<input type='text'
|
|
||||||
className='w-full px-2 bg-white outline-none select-none hover:text-clip clr-bg-pop'
|
|
||||||
placeholder='наберите текст фильтра'
|
|
||||||
value={filterText}
|
|
||||||
onChange={event => setFilterText(event.target.value)}
|
|
||||||
/>
|
|
||||||
<DependencyModePicker
|
|
||||||
value={filterSource}
|
|
||||||
onChange={setFilterSource}
|
|
||||||
/>
|
|
||||||
</div> */}
|
|
|
@ -124,7 +124,7 @@ function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
||||||
<p>
|
<p>
|
||||||
<TextURL text='Создать схему' href='/rsform-create'/>
|
<TextURL text='Создать схему' href='/rsform-create'/>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<TextURL text='Все схемы' href='/library?filter=common'/>
|
<TextURL text='Все схемы' href='/library'/>
|
||||||
<span> | </span>
|
<span> | </span>
|
||||||
<span className='cursor-pointer hover:underline text-url' onClick={cleanQuery}>
|
<span className='cursor-pointer hover:underline text-url' onClick={cleanQuery}>
|
||||||
<b>Очистить фильтр</b>
|
<b>Очистить фильтр</b>
|
||||||
|
|
|
@ -10,13 +10,13 @@ import ViewLibrary from './ViewLibrary';
|
||||||
function LibraryPage() {
|
function LibraryPage() {
|
||||||
const library = useLibrary();
|
const library = useLibrary();
|
||||||
|
|
||||||
const [ filterParams, setFilterParams ] = useState<ILibraryFilter>({});
|
const [ filter, setFilter ] = useState<ILibraryFilter>({});
|
||||||
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(
|
||||||
const filter = filterParams;
|
() => {
|
||||||
setItems(library.filter(filter));
|
setItems(library.filter(filter));
|
||||||
}, [library, filterParams]);
|
}, [library, filter, filter.query]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
|
@ -25,11 +25,12 @@ function LibraryPage() {
|
||||||
{ !library.loading && library.items &&
|
{ !library.loading && library.items &&
|
||||||
<div className='flex flex-col w-full'>
|
<div className='flex flex-col w-full'>
|
||||||
<SearchPanel
|
<SearchPanel
|
||||||
filter={filterParams}
|
total={library.items.length ?? 0}
|
||||||
setFilter={setFilterParams}
|
filtered={items.length}
|
||||||
|
setFilter={setFilter}
|
||||||
/>
|
/>
|
||||||
<ViewLibrary
|
<ViewLibrary
|
||||||
cleanQuery={() => setFilterParams({})}
|
cleanQuery={() => setFilter({})}
|
||||||
items={items}
|
items={items}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -34,28 +34,34 @@ function LoginPage() {
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
login(data, () => { navigate('/library?filter=personal'); });
|
login(data, () => navigate('/library'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full py-4'> { user
|
<div className='flex justify-center w-full'>
|
||||||
|
<div className='py-2'> { user
|
||||||
? <b>{`Вы вошли в систему как ${user.username}`}</b>
|
? <b>{`Вы вошли в систему как ${user.username}`}</b>
|
||||||
: <Form title='Ввод данных пользователя' onSubmit={handleSubmit} widthClass='w-[21rem]'>
|
:
|
||||||
|
<Form
|
||||||
|
title='Ввод данных пользователя'
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
widthClass='w-[24rem]'
|
||||||
|
>
|
||||||
<TextInput id='username'
|
<TextInput id='username'
|
||||||
label='Имя пользователя'
|
label='Имя пользователя'
|
||||||
required
|
required
|
||||||
type='text'
|
type='text'
|
||||||
value={username}
|
value={username}
|
||||||
autoFocus
|
autoFocus
|
||||||
onChange={event => { setUsername(event.target.value); }}
|
onChange={event => setUsername(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput id='password'
|
<TextInput id='password'
|
||||||
label='Пароль'
|
label='Пароль'
|
||||||
required
|
required
|
||||||
type='password'
|
type='password'
|
||||||
value={password}
|
value={password}
|
||||||
onChange={event => { setPassword(event.target.value); }}
|
onChange={event => setPassword(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex justify-center w-full gap-2 mt-4'>
|
<div className='flex justify-center w-full gap-2 mt-4'>
|
||||||
|
@ -72,6 +78,7 @@ function LoginPage() {
|
||||||
{ error && <BackendError error={error} />}
|
{ error && <BackendError error={error} />}
|
||||||
</Form>
|
</Form>
|
||||||
}</div>
|
}</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,21 +65,21 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
||||||
<TextInput id='title' label='Полное название' type='text'
|
<TextInput id='title' label='Полное название' type='text'
|
||||||
required
|
required
|
||||||
value={title}
|
value={title}
|
||||||
onChange={event => { setTitle(event.target.value); }}
|
onChange={event => setTitle(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput id='alias' label='Сокращение' type='text'
|
<TextInput id='alias' label='Сокращение' type='text'
|
||||||
required
|
required
|
||||||
value={alias}
|
value={alias}
|
||||||
widthClass='max-w-sm'
|
widthClass='max-w-sm'
|
||||||
onChange={event => { setAlias(event.target.value); }}
|
onChange={event => setAlias(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextArea id='comment' label='Комментарий'
|
<TextArea id='comment' label='Комментарий'
|
||||||
value={comment}
|
value={comment}
|
||||||
onChange={event => { setComment(event.target.value); }}
|
onChange={event => setComment(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<Checkbox id='common' label='Общедоступная схема'
|
<Checkbox id='common' label='Общедоступная схема'
|
||||||
value={common}
|
value={common}
|
||||||
onChange={event => { setCommon(event.target.checked); }}
|
onChange={event => setCommon(event.target.checked)}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -34,9 +34,7 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => onCreate(getData());
|
||||||
onCreate(getData());
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initial) {
|
if (initial) {
|
||||||
|
@ -48,10 +46,7 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
|
||||||
}
|
}
|
||||||
}, [initial]);
|
}, [initial]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => setValidated(selectedType !== undefined), [selectedType]);
|
||||||
setValidated(selectedType !== undefined);
|
|
||||||
}, [selectedType]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -67,7 +62,7 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
|
||||||
options={CstTypeSelector}
|
options={CstTypeSelector}
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
values={selectedType ? [{ value: selectedType, label: getCstTypeLabel(selectedType) }] : []}
|
||||||
onChange={data => { setSelectedType(data.length > 0 ? data[0].value : CstType.BASE); }}
|
onChange={data => setSelectedType(data.length > 0 ? data[0].value : CstType.BASE)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<TextArea id='term' label='Термин'
|
<TextArea id='term' label='Термин'
|
||||||
|
@ -89,14 +84,14 @@ function DlgCreateCst({ hideWindow, initial, onCreate }: DlgCreateCstProps) {
|
||||||
rows={2}
|
rows={2}
|
||||||
value={textDefinition}
|
value={textDefinition}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setTextDefinition(event.target.value); }}
|
onChange={event => setTextDefinition(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||||
rows={2}
|
rows={2}
|
||||||
value={convention}
|
value={convention}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setConvention(event.target.value); }}
|
onChange={event => setConvention(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -31,11 +31,11 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
||||||
const handleSubmit = () => onRename(getData());
|
const handleSubmit = () => onRename(getData());
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
if (schema && initial && cstType !== initial.cst_type) {
|
if (schema && initial && cstType !== initial.cst_type) {
|
||||||
setAlias(createAliasFor(cstType, schema));
|
setAlias(createAliasFor(cstType, schema));
|
||||||
}
|
}
|
||||||
}, [initial, cstType, schema]);
|
}, [initial, cstType, schema]);
|
||||||
|
|
||||||
useLayoutEffect(
|
useLayoutEffect(
|
||||||
() => {
|
() => {
|
||||||
|
@ -66,13 +66,13 @@ function DlgRenameCst({ hideWindow, initial, onRename }: DlgRenameCstProps) {
|
||||||
submitInvalidTooltip={'Введите имя, соответствующее типу и отсутствующее в схеме'}
|
submitInvalidTooltip={'Введите имя, соответствующее типу и отсутствующее в схеме'}
|
||||||
submitText='Переименовать'
|
submitText='Переименовать'
|
||||||
>
|
>
|
||||||
<div className='flex items-center gap-4 px-2 mb-2 h-fit min-w-[25rem]'>
|
<div className='flex items-center gap-4 px-2 my-2 h-fit min-w-[25rem]'>
|
||||||
<ConceptSelect
|
<ConceptSelect
|
||||||
className='mt-2 min-w-[14rem] self-center'
|
className='min-w-[14rem] self-center'
|
||||||
options={CstTypeSelector}
|
options={CstTypeSelector}
|
||||||
placeholder='Выберите тип'
|
placeholder='Выберите тип'
|
||||||
values={cstType ? [{ value: cstType, label: getCstTypeLabel(cstType) }] : []}
|
values={cstType ? [{ value: cstType, label: getCstTypeLabel(cstType) }] : []}
|
||||||
onChange={data => { setCstType(data.length > 0 ? data[0].value : CstType.BASE); }}
|
onChange={data => setCstType(data.length > 0 ? data[0].value : CstType.BASE)}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<TextInput id='alias' label='Имя'
|
<TextInput id='alias' label='Имя'
|
||||||
|
|
|
@ -32,7 +32,7 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
|
||||||
if (event.target.files && event.target.files.length > 0) {
|
if (event.target.files && event.target.files.length > 0) {
|
||||||
setFile(event.target.files[0]);
|
setFile(event.target.files[0]);
|
||||||
} else {
|
} else {
|
||||||
setFile(undefined)
|
setFile(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ function DlgUploadRSForm({ hideWindow }: DlgUploadRSFormProps) {
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='Загружать название и комментарий'
|
label='Загружать название и комментарий'
|
||||||
value={loadMetadata}
|
value={loadMetadata}
|
||||||
onChange={event => { setLoadMetadata(event.target.checked); }}
|
onChange={event => setLoadMetadata(event.target.checked)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -45,7 +45,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
|
|
||||||
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
const isEnabled = useMemo(() => activeCst && isEditable, [activeCst, isEditable]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
if (!activeCst) {
|
if (!activeCst) {
|
||||||
setIsModified(false);
|
setIsModified(false);
|
||||||
return;
|
return;
|
||||||
|
@ -60,7 +61,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
activeCst?.definition.text.raw, activeCst?.convention,
|
activeCst?.definition.text.raw, activeCst?.convention,
|
||||||
term, textDefinition, expression, convention]);
|
term, textDefinition, expression, convention]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(
|
||||||
|
() => {
|
||||||
if (activeCst) {
|
if (activeCst) {
|
||||||
setAlias(activeCst.alias);
|
setAlias(activeCst.alias);
|
||||||
setConvention(activeCst.convention ?? '');
|
setConvention(activeCst.convention ?? '');
|
||||||
|
@ -73,8 +75,10 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
}
|
}
|
||||||
}, [activeCst, onOpenEdit, schema]);
|
}, [activeCst, onOpenEdit, schema]);
|
||||||
|
|
||||||
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(event?: React.FormEvent<HTMLFormElement>) {
|
||||||
event.preventDefault();
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
if (!activeID || processing) {
|
if (!activeID || processing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -86,7 +90,7 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
definition_raw: textDefinition,
|
definition_raw: textDefinition,
|
||||||
term_raw: term
|
term_raw: term
|
||||||
};
|
};
|
||||||
cstUpdate(data, () => { toast.success('Изменения сохранены'); });
|
cstUpdate(data, () => toast.success('Изменения сохранены'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDelete() {
|
function handleDelete() {
|
||||||
|
@ -126,13 +130,14 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
|
|
||||||
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'>
|
<form onSubmit={handleSubmit} className='min-w-[50rem] max-w-min px-4 py-2 border-y border-r clr-border'>
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<div className='absolute top-0 left-0'>
|
<div className='absolute top-0 left-0'>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip='Сохранить изменения'
|
tooltip='Сохранить изменения'
|
||||||
disabled={!isModified || !isEnabled}
|
disabled={!isModified || !isEnabled}
|
||||||
icon={<SaveIcon size={6} color={isModified && isEnabled ? 'text-primary' : ''}/>}
|
icon={<SaveIcon size={6} color={isModified && isEnabled ? 'text-primary' : ''}/>}
|
||||||
|
onClick={() => handleSubmit()}
|
||||||
>
|
>
|
||||||
</MiniButton>
|
</MiniButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -208,8 +213,8 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
resolved={activeCst?.definition.text.resolved ?? ''}
|
resolved={activeCst?.definition.text.resolved ?? ''}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setTextDefinition(event.target.value); }}
|
onChange={event => setTextDefinition(event.target.value)}
|
||||||
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
/>
|
/>
|
||||||
<TextArea id='convention' label='Конвенция / Комментарий'
|
<TextArea id='convention' label='Конвенция / Комментарий'
|
||||||
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
placeholder='Договоренность об интерпретации неопределяемого понятия
Комментарий к производному понятию'
|
||||||
|
@ -217,10 +222,10 @@ function EditorConstituenta({ activeID, onShowAST, onCreateCst, onRenameCst, onO
|
||||||
value={convention}
|
value={convention}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
spellCheck
|
spellCheck
|
||||||
onChange={event => { setConvention(event.target.value); }}
|
onChange={event => setConvention(event.target.value)}
|
||||||
onFocus={() => { setEditMode(EditMode.TEXT); }}
|
onFocus={() => setEditMode(EditMode.TEXT)}
|
||||||
/>
|
/>
|
||||||
<div className='flex justify-center w-full mt-2'>
|
<div className='flex justify-center w-full mt-4 mb-2'>
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Сохранить изменения'
|
text='Сохранить изменения'
|
||||||
disabled={!isModified || !isEnabled}
|
disabled={!isModified || !isEnabled}
|
||||||
|
|
|
@ -8,9 +8,10 @@ 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, ArrowsRotateIcon, ArrowUpIcon, DumpBinIcon, HelpIcon, SmallPlusIcon } from '../../components/Icons';
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import { prefixes } from '../../utils/constants';
|
import { prefixes } from '../../utils/constants';
|
||||||
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models'
|
import { CstType, IConstituenta, ICstCreateData, ICstMovetoData } from '../../utils/models'
|
||||||
import { getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
|
import { getCstStatusColor, getCstTypePrefix, getCstTypeShortcut, getCstTypificationLabel, mapStatusInfo } from '../../utils/staticUI';
|
||||||
|
|
||||||
interface EditorItemsProps {
|
interface EditorItemsProps {
|
||||||
onOpenEdit: (cstID: number) => void
|
onOpenEdit: (cstID: number) => void
|
||||||
|
@ -19,6 +20,7 @@ interface EditorItemsProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) {
|
function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps) {
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
|
const { schema, isEditable, cstMoveTo, resetAliases } = useRSForm();
|
||||||
const [selected, setSelected] = useState<number[]>([]);
|
const [selected, setSelected] = useState<number[]>([]);
|
||||||
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
|
const nothingSelected = useMemo(() => selected.length === 0, [selected]);
|
||||||
|
@ -51,7 +53,9 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
}, -1);
|
}, -1);
|
||||||
const target = Math.max(0, currentIndex - 1) + 1
|
const target = Math.max(0, currentIndex - 1) + 1
|
||||||
const data = {
|
const data = {
|
||||||
items: selected.map(id => { return { id: id }; }),
|
items: selected.map(id => {
|
||||||
|
return { id: id };
|
||||||
|
}),
|
||||||
move_to: target
|
move_to: target
|
||||||
}
|
}
|
||||||
cstMoveTo(data);
|
cstMoveTo(data);
|
||||||
|
@ -76,7 +80,9 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
}, -1);
|
}, -1);
|
||||||
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
const target = Math.min(schema.items.length - 1, currentIndex - count + 2) + 1
|
||||||
const data: ICstMovetoData = {
|
const data: ICstMovetoData = {
|
||||||
items: selected.map(id => { return { id: id }; }),
|
items: selected.map(id => {
|
||||||
|
return { id: id };
|
||||||
|
}),
|
||||||
move_to: target
|
move_to: target
|
||||||
}
|
}
|
||||||
cstMoveTo(data);
|
cstMoveTo(data);
|
||||||
|
@ -87,7 +93,6 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
resetAliases(() => toast.success('Переиндексация конституент успешна'));
|
resetAliases(() => toast.success('Переиндексация конституент успешна'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new constituenta
|
|
||||||
function handleCreateCst(type?: CstType) {
|
function handleCreateCst(type?: CstType) {
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return;
|
||||||
|
@ -180,7 +185,8 @@ 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 rounded-md text-center ${info.color}`}
|
className='w-full text-center rounded-md'
|
||||||
|
style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
|
||||||
>
|
>
|
||||||
{cst.alias}
|
{cst.alias}
|
||||||
</div>
|
</div>
|
||||||
|
@ -248,7 +254,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
||||||
reorder: true,
|
reorder: true,
|
||||||
hide: 1800
|
hide: 1800
|
||||||
}
|
}
|
||||||
], []);
|
], [colors]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='w-full'>
|
<div className='w-full'>
|
||||||
|
|
|
@ -27,7 +27,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
const { getUserLabel } = useUsers();
|
const { getUserLabel } = useUsers();
|
||||||
const {
|
const {
|
||||||
schema, update, isForceAdmin,
|
schema, update, isForceAdmin,
|
||||||
isEditable, isOwned, isClaimable, processing
|
isEditable, isClaimable, processing
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit} className='flex-grow max-w-xl px-4 py-2 border min-w-fit'>
|
<form onSubmit={handleSubmit} className='flex-grow max-w-[35.3rem] px-4 py-2 border-y border-r clr-border 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
|
||||||
|
@ -93,8 +93,8 @@ function EditorRSForm({ onDestroy, onClaim, onShare, onDownload }: EditorRSFormP
|
||||||
onClick={onDownload}
|
onClick={onDownload}
|
||||||
/>
|
/>
|
||||||
<MiniButton
|
<MiniButton
|
||||||
tooltip={isClaimable ? 'Стать владельцем' : 'Вы уже являетесь владельцем' }
|
tooltip={isClaimable ? 'Стать владельцем' : 'Невозможно стать владельцем' }
|
||||||
icon={<CrownIcon size={5} color={isOwned ? '' : 'text-green'}/>}
|
icon={<CrownIcon size={5} color={!isClaimable ? '' : 'text-green'}/>}
|
||||||
disabled={!isClaimable || !user}
|
disabled={!isClaimable || !user}
|
||||||
onClick={onClaim}
|
onClick={onClaim}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { darkTheme, GraphCanvas, GraphCanvasRef, GraphEdge,
|
import { GraphCanvas, GraphCanvasRef, GraphEdge,
|
||||||
GraphNode, LayoutTypes, lightTheme, Sphere, useSelection
|
GraphNode, LayoutTypes, Sphere, useSelection
|
||||||
} from 'reagraph';
|
} from 'reagraph';
|
||||||
|
|
||||||
import Button from '../../components/Common/Button';
|
import Button from '../../components/Common/Button';
|
||||||
|
@ -15,6 +15,7 @@ import { ArrowsRotateIcon, DumpBinIcon, FilterCogIcon, HelpIcon, SmallPlusIcon }
|
||||||
import { useRSForm } from '../../context/RSFormContext';
|
import { useRSForm } from '../../context/RSFormContext';
|
||||||
import { useConceptTheme } from '../../context/ThemeContext';
|
import { useConceptTheme } from '../../context/ThemeContext';
|
||||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||||
|
import { graphDarkT, graphLightT, IColorTheme } from '../../utils/color';
|
||||||
import { prefixes, resources } from '../../utils/constants';
|
import { prefixes, resources } from '../../utils/constants';
|
||||||
import { Graph } from '../../utils/Graph';
|
import { Graph } from '../../utils/Graph';
|
||||||
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
|
import { CstType, IConstituenta, ICstCreateData } from '../../utils/models';
|
||||||
|
@ -28,14 +29,14 @@ import ConstituentaTooltip from './elements/ConstituentaTooltip';
|
||||||
export type ColoringScheme = 'none' | 'status' | 'type';
|
export type ColoringScheme = 'none' | 'status' | 'type';
|
||||||
const TREE_SIZE_MILESTONE = 50;
|
const TREE_SIZE_MILESTONE = 50;
|
||||||
|
|
||||||
function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, darkMode: boolean): string {
|
function getCstNodeColor(cst: IConstituenta, coloringScheme: ColoringScheme, colors: IColorTheme): string {
|
||||||
if (coloringScheme === 'type') {
|
if (coloringScheme === 'type') {
|
||||||
return getCstClassColor(cst.cstClass, darkMode);
|
return getCstClassColor(cst.cstClass, colors);
|
||||||
}
|
}
|
||||||
if (coloringScheme === 'status') {
|
if (coloringScheme === 'status') {
|
||||||
return getCstStatusColor(cst.status, darkMode);
|
return getCstStatusColor(cst.status, colors);
|
||||||
}
|
}
|
||||||
return (darkMode ? '#7a8c9e' :'#7ca0ab');
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GraphEditorParams {
|
export interface GraphEditorParams {
|
||||||
|
@ -62,7 +63,7 @@ interface EditorTermGraphProps {
|
||||||
|
|
||||||
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGraphProps) {
|
||||||
const { schema, isEditable } = useRSForm();
|
const { schema, isEditable } = useRSForm();
|
||||||
const { darkMode, 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', 'none');
|
||||||
|
@ -171,13 +172,13 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
if (cst) {
|
if (cst) {
|
||||||
result.push({
|
result.push({
|
||||||
id: String(node.id),
|
id: String(node.id),
|
||||||
fill: getCstNodeColor(cst, coloringScheme, darkMode),
|
fill: getCstNodeColor(cst, coloringScheme, colors),
|
||||||
label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
|
label: cst.term.resolved && !noTerms ? `${cst.alias}: ${cst.term.resolved}` : cst.alias
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}, [schema, coloringScheme, filtered.nodes, darkMode, noTerms]);
|
}, [schema, coloringScheme, filtered.nodes, noTerms, colors]);
|
||||||
|
|
||||||
const edges: GraphEdge[] = useMemo(
|
const edges: GraphEdge[] = useMemo(
|
||||||
() => {
|
() => {
|
||||||
|
@ -337,8 +338,8 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
const canvasHeight = useMemo(
|
const canvasHeight = useMemo(
|
||||||
() => {
|
() => {
|
||||||
return !noNavigation ?
|
return !noNavigation ?
|
||||||
'calc(100vh - 9.8rem)'
|
'calc(100vh - 10.1rem)'
|
||||||
: 'calc(100vh - 1.8rem)';
|
: 'calc(100vh - 2.1rem)';
|
||||||
}, [noNavigation]);
|
}, [noNavigation]);
|
||||||
|
|
||||||
const dismissedStyle = useCallback(
|
const dismissedStyle = useCallback(
|
||||||
|
@ -354,7 +355,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
initial={getOptions()}
|
initial={getOptions()}
|
||||||
onConfirm={handleChangeOptions}
|
onConfirm={handleChangeOptions}
|
||||||
/>}
|
/>}
|
||||||
<div className='flex flex-col border-t border-r max-w-[12.5rem] pr-2 pb-2 text-sm select-none' 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 clr-border' style={{height: canvasHeight}}>
|
||||||
{hoverCst &&
|
{hoverCst &&
|
||||||
<div className='relative'>
|
<div className='relative'>
|
||||||
<InfoConstituenta
|
<InfoConstituenta
|
||||||
|
@ -394,12 +395,12 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
onClick={() => setShowOptions(true)}
|
onClick={() => setShowOptions(true)}
|
||||||
/>
|
/>
|
||||||
<ConceptSelect
|
<ConceptSelect
|
||||||
className='min-w-[9.3rem]'
|
className='min-w-[9.8rem]'
|
||||||
options={GraphColoringSelector}
|
options={GraphColoringSelector}
|
||||||
searchable={false}
|
searchable={false}
|
||||||
placeholder='Выберите цвет'
|
placeholder='Выберите цвет'
|
||||||
values={coloringScheme ? [{ value: coloringScheme, label: mapColoringLabels.get(coloringScheme) }] : []}
|
values={coloringScheme ? [{ value: coloringScheme, label: mapColoringLabels.get(coloringScheme) }] : []}
|
||||||
onChange={data => { setColoringScheme(data.length > 0 ? data[0].value : GraphColoringSelector[0].value); }}
|
onChange={data => setColoringScheme(data.length > 0 ? data[0].value : GraphColoringSelector[0].value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -409,7 +410,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
searchable={false}
|
searchable={false}
|
||||||
placeholder='Способ расположения'
|
placeholder='Способ расположения'
|
||||||
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
values={layout ? [{ value: layout, label: mapLayoutLabels.get(layout) }] : []}
|
||||||
onChange={data => { setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value); }}
|
onChange={data => setLayout(data.length > 0 ? data[0].value : GraphLayoutSelector[0].value)}
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='Скрыть текст'
|
label='Скрыть текст'
|
||||||
|
@ -441,7 +442,10 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
key={`${cst.alias}`}
|
key={`${cst.alias}`}
|
||||||
id={`${prefixes.cst_list}${cst.alias}`}
|
id={`${prefixes.cst_list}${cst.alias}`}
|
||||||
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer'
|
className='w-fit min-w-[3rem] rounded-md text-center cursor-pointer'
|
||||||
style={ { backgroundColor: getCstNodeColor(cst, adjustedColoring, darkMode), ...dismissedStyle(cstID) }}
|
style={{
|
||||||
|
backgroundColor: getCstNodeColor(cst, adjustedColoring, colors),
|
||||||
|
...dismissedStyle(cstID)
|
||||||
|
}}
|
||||||
onClick={() => toggleDismissed(cstID)}
|
onClick={() => toggleDismissed(cstID)}
|
||||||
onDoubleClick={() => onOpenEdit(cstID)}
|
onDoubleClick={() => onOpenEdit(cstID)}
|
||||||
>
|
>
|
||||||
|
@ -458,7 +462,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
</div>
|
</div>
|
||||||
<div className='w-full h-full overflow-auto'>
|
<div className='w-full h-full overflow-auto'>
|
||||||
<div
|
<div
|
||||||
className='relative border-r border-y'
|
className='relative'
|
||||||
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
|
style={{width: canvasWidth, height: canvasHeight, borderBottomWidth: noNavigation ? '1px': ''}}
|
||||||
>
|
>
|
||||||
<div className='relative top-0 right-0 z-10 flex mt-1 ml-2 flex-start'>
|
<div className='relative top-0 right-0 z-10 flex mt-1 ml-2 flex-start'>
|
||||||
|
@ -492,7 +496,7 @@ function EditorTermGraph({ onOpenEdit, onCreateCst, onDeleteCst }: EditorTermGra
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
labelFontUrl={resources.graph_font}
|
labelFontUrl={resources.graph_font}
|
||||||
theme={darkMode ? darkTheme : lightTheme}
|
theme={darkMode ? graphDarkT : graphLightT}
|
||||||
renderNode={({ node, ...rest }) => (
|
renderNode={({ node, ...rest }) => (
|
||||||
<Sphere {...rest} node={node} />
|
<Sphere {...rest} node={node} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,8 +9,9 @@ import ConceptTab from '../../components/Common/ConceptTab';
|
||||||
import { Loader } from '../../components/Common/Loader';
|
import { Loader } from '../../components/Common/Loader';
|
||||||
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 { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
import { prefixes, TIMEOUT_UI_REFRESH } from '../../utils/constants';
|
||||||
import { ICstCreateData, ICstRenameData, SyntaxTree } from '../../utils/models';
|
import { ICstCreateData, ICstRenameData, LibraryFilterStrategy, SyntaxTree } from '../../utils/models';
|
||||||
import { createAliasFor } from '../../utils/staticUI';
|
import { createAliasFor } from '../../utils/staticUI';
|
||||||
import DlgCloneRSForm from './DlgCloneRSForm';
|
import DlgCloneRSForm from './DlgCloneRSForm';
|
||||||
import DlgCreateCst from './DlgCreateCst';
|
import DlgCreateCst from './DlgCreateCst';
|
||||||
|
@ -25,7 +26,7 @@ import EditorTermGraph from './EditorTermGraph';
|
||||||
import RSFormStats from './elements/RSFormStats';
|
import RSFormStats from './elements/RSFormStats';
|
||||||
import RSTabsMenu from './RSTabsMenu';
|
import RSTabsMenu from './RSTabsMenu';
|
||||||
|
|
||||||
export enum RSTabsList {
|
export enum RSTabID {
|
||||||
CARD = 0,
|
CARD = 0,
|
||||||
CST_LIST = 1,
|
CST_LIST = 1,
|
||||||
CST_EDIT = 2,
|
CST_EDIT = 2,
|
||||||
|
@ -40,8 +41,9 @@ function RSTabs() {
|
||||||
cstCreate, cstDelete, cstRename, subscribe, unsubscribe
|
cstCreate, cstDelete, cstRename, subscribe, unsubscribe
|
||||||
} = useRSForm();
|
} = useRSForm();
|
||||||
const { destroySchema } = useLibrary();
|
const { destroySchema } = useLibrary();
|
||||||
|
const { setNoFooter } = useConceptTheme();
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState(RSTabsList.CARD);
|
const [activeTab, setActiveTab] = useState<RSTabID>(RSTabID.CARD);
|
||||||
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
const [activeID, setActiveID] = useState<number | undefined>(undefined);
|
||||||
|
|
||||||
const [showUpload, setShowUpload] = useState(false);
|
const [showUpload, setShowUpload] = useState(false);
|
||||||
|
@ -72,21 +74,23 @@ function RSTabs() {
|
||||||
}, [schema]);
|
}, [schema]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const activeTab = Number(new URLSearchParams(search).get('tab')) ?? RSTabsList.CARD;
|
const activeTab = (Number(new URLSearchParams(search).get('tab')) ?? RSTabID.CARD) as RSTabID;
|
||||||
const cstQuery = new URLSearchParams(search).get('active');
|
const cstQuery = new URLSearchParams(search).get('active');
|
||||||
setActiveTab(activeTab);
|
setActiveTab(activeTab);
|
||||||
|
setNoFooter(activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST);
|
||||||
setActiveID(Number(cstQuery) ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined));
|
setActiveID(Number(cstQuery) ?? ((schema && schema?.items.length > 0) ? schema.items[0].id : undefined));
|
||||||
}, [search, setActiveTab, setActiveID, schema]);
|
return () => setNoFooter(false);
|
||||||
|
}, [search, setActiveTab, setActiveID, schema, setNoFooter]);
|
||||||
|
|
||||||
function onSelectTab(index: number) {
|
function onSelectTab(index: number) {
|
||||||
navigateTo(index, activeID);
|
navigateTo(index, activeID);
|
||||||
}
|
}
|
||||||
|
|
||||||
const navigateTo = useCallback(
|
const navigateTo = useCallback(
|
||||||
(tab: RSTabsList, activeID?: number) => {
|
(tab: RSTabID, activeID?: number) => {
|
||||||
if (activeID) {
|
if (activeID) {
|
||||||
navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, {
|
navigate(`/rsforms/${schema!.id}?tab=${tab}&active=${activeID}`, {
|
||||||
replace: tab === activeTab && tab !== RSTabsList.CST_EDIT
|
replace: tab === activeTab && tab !== RSTabID.CST_EDIT
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
navigate(`/rsforms/${schema!.id}?tab=${tab}`);
|
navigate(`/rsforms/${schema!.id}?tab=${tab}`);
|
||||||
|
@ -102,7 +106,7 @@ function RSTabs() {
|
||||||
cstCreate(data, newCst => {
|
cstCreate(data, newCst => {
|
||||||
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
||||||
navigateTo(activeTab, newCst.id);
|
navigateTo(activeTab, newCst.id);
|
||||||
if (activeTab === RSTabsList.CST_EDIT || activeTab === RSTabsList.CST_LIST) {
|
if (activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
||||||
if (element) {
|
if (element) {
|
||||||
|
@ -144,14 +148,16 @@ function RSTabs() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = {
|
const data = {
|
||||||
items: deleted.map(id => { return { id: id }; })
|
items: deleted.map(id => {
|
||||||
|
return { id: id };
|
||||||
|
})
|
||||||
};
|
};
|
||||||
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
|
let activeIndex = schema.items.findIndex(cst => cst.id === activeID);
|
||||||
cstDelete(data, () => {
|
cstDelete(data, () => {
|
||||||
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
|
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
|
||||||
toast.success(`Конституенты удалены: ${deletedNames}`);
|
toast.success(`Конституенты удалены: ${deletedNames}`);
|
||||||
if (deleted.length === schema.items.length) {
|
if (deleted.length === schema.items.length) {
|
||||||
navigateTo(RSTabsList.CST_LIST);
|
navigateTo(RSTabID.CST_LIST);
|
||||||
}
|
}
|
||||||
if (activeIndex) {
|
if (activeIndex) {
|
||||||
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
|
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
|
||||||
|
@ -182,7 +188,7 @@ function RSTabs() {
|
||||||
|
|
||||||
const onOpenCst = useCallback(
|
const onOpenCst = useCallback(
|
||||||
(cstID: number) => {
|
(cstID: number) => {
|
||||||
navigateTo(RSTabsList.CST_EDIT, cstID)
|
navigateTo(RSTabID.CST_EDIT, cstID)
|
||||||
}, [navigateTo]);
|
}, [navigateTo]);
|
||||||
|
|
||||||
const onDestroySchema = useCallback(
|
const onDestroySchema = useCallback(
|
||||||
|
@ -192,7 +198,7 @@ function RSTabs() {
|
||||||
}
|
}
|
||||||
destroySchema(schema.id, () => {
|
destroySchema(schema.id, () => {
|
||||||
toast.success('Схема удалена');
|
toast.success('Схема удалена');
|
||||||
navigate('/library?filter=personal');
|
navigate(`/library?filter=${LibraryFilterStrategy.PERSONAL}`);
|
||||||
});
|
});
|
||||||
}, [schema, destroySchema, navigate]);
|
}, [schema, destroySchema, navigate]);
|
||||||
|
|
||||||
|
@ -283,7 +289,7 @@ function RSTabs() {
|
||||||
defaultFocus={true}
|
defaultFocus={true}
|
||||||
selectedTabClassName='font-bold'
|
selectedTabClassName='font-bold'
|
||||||
>
|
>
|
||||||
<TabList className='flex items-start pl-2 select-none w-fit clr-bg-pop'>
|
<TabList className='flex items-start pl-2 border-b border-r-2 select-none w-fit clr-bg-pop clr-border'>
|
||||||
<RSTabsMenu
|
<RSTabsMenu
|
||||||
onDownload={onDownloadSchema}
|
onDownload={onDownloadSchema}
|
||||||
onDestroy={onDestroySchema}
|
onDestroy={onDestroySchema}
|
||||||
|
@ -302,7 +308,7 @@ function RSTabs() {
|
||||||
<ConceptTab className='min-w-[6.5rem]'>Граф термов</ConceptTab>
|
<ConceptTab className='min-w-[6.5rem]'>Граф термов</ConceptTab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanel className='flex items-start w-full gap-2 px-2'>
|
<TabPanel className='flex w-full gap-4'>
|
||||||
<EditorRSForm
|
<EditorRSForm
|
||||||
onDownload={onDownloadSchema}
|
onDownload={onDownloadSchema}
|
||||||
onDestroy={onDestroySchema}
|
onDestroy={onDestroySchema}
|
||||||
|
@ -320,7 +326,7 @@ function RSTabs() {
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel className='pl-2'>
|
<TabPanel>
|
||||||
<EditorConstituenta
|
<EditorConstituenta
|
||||||
activeID={activeID}
|
activeID={activeID}
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
|
@ -331,7 +337,7 @@ function RSTabs() {
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel className='pl-2'>
|
<TabPanel>
|
||||||
<EditorTermGraph
|
<EditorTermGraph
|
||||||
onOpenEdit={onOpenCst}
|
onOpenEdit={onOpenCst}
|
||||||
onCreateCst={promptCreateCst}
|
onCreateCst={promptCreateCst}
|
||||||
|
|
|
@ -52,14 +52,6 @@ function DependencyModePicker({ value, onChange }: DependencyModePickerProps) {
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
// case DependencyMode.OUTPUTS: return 'потребители';
|
|
||||||
// case DependencyMode.INPUTS: return 'поставщики';
|
|
||||||
// case DependencyMode.EXPAND_INPUTS: return 'влияющие';
|
|
||||||
// case DependencyMode.EXPAND_OUTPUTS: return 'зависимые';
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ interface RSFormStatsProps {
|
||||||
|
|
||||||
function RSFormStats({ stats }: RSFormStatsProps) {
|
function RSFormStats({ stats }: RSFormStatsProps) {
|
||||||
return (
|
return (
|
||||||
<div className='px-4 py-2 border'>
|
<div className='flex flex-col gap-1 px-4 py-2 border min-w-[16rem]'>
|
||||||
<LabeledText id='count_all'
|
<LabeledText id='count_all'
|
||||||
label='Всего конституент '
|
label='Всего конституент '
|
||||||
text={stats.count_all}
|
text={stats.count_all}
|
||||||
|
@ -27,12 +27,20 @@ function RSFormStats({ stats }: RSFormStatsProps) {
|
||||||
label='Невычислимы '
|
label='Невычислимы '
|
||||||
text={stats.count_incalc}
|
text={stats.count_incalc}
|
||||||
/>}
|
/>}
|
||||||
<Divider margins='my-1' />
|
<Divider margins='my-2' />
|
||||||
<LabeledText id='count_termin'
|
<LabeledText id='count_termin'
|
||||||
label='Термины '
|
label='Термины '
|
||||||
text={stats.count_termin}
|
text={stats.count_termin}
|
||||||
/>
|
/>
|
||||||
<Divider margins='my-1' />
|
<LabeledText id='count_definition'
|
||||||
|
label='Определения '
|
||||||
|
text={stats.count_definition}
|
||||||
|
/>
|
||||||
|
<LabeledText id='count_convention'
|
||||||
|
label='Конвенции '
|
||||||
|
text={stats.count_convention}
|
||||||
|
/>
|
||||||
|
<Divider margins='my-2' />
|
||||||
{ stats.count_base > 0 &&
|
{ stats.count_base > 0 &&
|
||||||
<LabeledText id='count_base'
|
<LabeledText id='count_base'
|
||||||
label='Базисные множества '
|
label='Базисные множества '
|
||||||
|
|
|
@ -12,7 +12,7 @@ function RSLocalButton({ text, tooltip, disabled, onInsert }: RSLocalButtonProps
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={() => { onInsert(TokenID.ID_LOCAL, text); }}
|
onClick={() => onInsert(TokenID.ID_LOCAL, text)}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className='w-[1.5rem] h-7 cursor-pointer border rounded-none clr-btn-clear'
|
className='w-[1.5rem] h-7 cursor-pointer border rounded-none clr-btn-clear'
|
||||||
|
|
|
@ -14,7 +14,7 @@ function RSTokenButton({ id, disabled, onInsert }: RSTokenButtonProps) {
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
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-btn-clear`}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
|
import { ExpressionStatus, type IConstituenta, IExpressionParse,inferStatus, ParsingStatus } from '../../../utils/models';
|
||||||
import { mapStatusInfo } from '../../../utils/staticUI';
|
import { getCstStatusColor, mapStatusInfo } from '../../../utils/staticUI';
|
||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
isModified?: boolean
|
isModified?: boolean
|
||||||
|
@ -10,6 +11,7 @@ interface StatusBarProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
||||||
|
const { colors } = useConceptTheme();
|
||||||
const status = useMemo(() => {
|
const status = useMemo(() => {
|
||||||
if (isModified) {
|
if (isModified) {
|
||||||
return ExpressionStatus.UNKNOWN;
|
return ExpressionStatus.UNKNOWN;
|
||||||
|
@ -24,7 +26,9 @@ function StatusBar({ isModified, constituenta, parseData }: StatusBarProps) {
|
||||||
const data = mapStatusInfo.get(status)!;
|
const data = mapStatusInfo.get(status)!;
|
||||||
return (
|
return (
|
||||||
<div title={data.tooltip}
|
<div title={data.tooltip}
|
||||||
className={`text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center select-none justify-center align-middle ${data.color}`}>
|
className='text-sm h-[1.6rem] w-[10rem] font-semibold inline-flex border items-center select-none justify-center align-middle'
|
||||||
|
style={{backgroundColor: getCstStatusColor(status, colors)}}
|
||||||
|
>
|
||||||
Статус: [ {data.text} ]
|
Статус: [ {data.text} ]
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,13 +6,13 @@ import { useConceptTheme } from '../../../context/ThemeContext';
|
||||||
import useLocalStorage from '../../../hooks/useLocalStorage';
|
import useLocalStorage from '../../../hooks/useLocalStorage';
|
||||||
import { prefixes } from '../../../utils/constants';
|
import { prefixes } from '../../../utils/constants';
|
||||||
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models';
|
import { applyGraphFilter, CstMatchMode, CstType, DependencyMode, extractGlobals, IConstituenta, matchConstituenta } from '../../../utils/models';
|
||||||
import { getCstDescription, getMockConstituenta, mapStatusInfo } from '../../../utils/staticUI';
|
import { getCstDescription, getCstStatusColor, getMockConstituenta } from '../../../utils/staticUI';
|
||||||
import ConstituentaTooltip from './ConstituentaTooltip';
|
import ConstituentaTooltip from './ConstituentaTooltip';
|
||||||
import DependencyModePicker from './DependencyModePicker';
|
import DependencyModePicker from './DependencyModePicker';
|
||||||
import MatchModePicker from './MatchModePicker';
|
import MatchModePicker from './MatchModePicker';
|
||||||
|
|
||||||
// Height that should be left to accomodate navigation panel + bottom margin
|
// Height that should be left to accomodate navigation panel + bottom margin
|
||||||
const LOCAL_NAVIGATION_H = '2.6rem';
|
const LOCAL_NAVIGATION_H = '2.1rem';
|
||||||
|
|
||||||
interface ViewSideConstituentsProps {
|
interface ViewSideConstituentsProps {
|
||||||
expression: string
|
expression: string
|
||||||
|
@ -26,7 +26,7 @@ function isMockCst(cst: IConstituenta) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }: ViewSideConstituentsProps) {
|
||||||
const { darkMode, noNavigation } = useConceptTheme();
|
const { noNavigation, colors } = useConceptTheme();
|
||||||
const { schema } = useRSForm();
|
const { schema } = useRSForm();
|
||||||
|
|
||||||
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
const [filterMatch, setFilterMatch] = useLocalStorage('side-filter-match', CstMatchMode.ALL);
|
||||||
|
@ -81,10 +81,10 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => cst.id === activeID,
|
when: (cst: IConstituenta) => cst.id === activeID,
|
||||||
style: {
|
style: {
|
||||||
backgroundColor: darkMode ? '#0068b3' : '#def1ff',
|
backgroundColor: colors.selection,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
], [activeID, darkMode]);
|
], [activeID, colors]);
|
||||||
|
|
||||||
const columns = useMemo(
|
const columns = useMemo(
|
||||||
() => [
|
() => [
|
||||||
|
@ -97,11 +97,11 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
name: 'ID',
|
name: 'ID',
|
||||||
id: 'alias',
|
id: 'alias',
|
||||||
cell: (cst: IConstituenta) => {
|
cell: (cst: IConstituenta) => {
|
||||||
const info = mapStatusInfo.get(cst.status)!;
|
|
||||||
return (<>
|
return (<>
|
||||||
<div
|
<div
|
||||||
id={`${prefixes.cst_list}${cst.alias}`}
|
id={`${prefixes.cst_list}${cst.alias}`}
|
||||||
className={`w-full rounded-md text-center ${info.color}`}
|
className='w-full text-center rounded-md'
|
||||||
|
style={{backgroundColor: getCstStatusColor(cst.status, colors)}}
|
||||||
>
|
>
|
||||||
{cst.alias}
|
{cst.alias}
|
||||||
</div>
|
</div>
|
||||||
|
@ -113,7 +113,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
conditionalCellStyles: [
|
conditionalCellStyles: [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => isMockCst(cst),
|
when: (cst: IConstituenta) => isMockCst(cst),
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
style: {backgroundColor: colors.selectionError}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -126,7 +126,7 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
conditionalCellStyles: [
|
conditionalCellStyles: [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => isMockCst(cst),
|
when: (cst: IConstituenta) => isMockCst(cst),
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
style: {backgroundColor: colors.selectionError}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -141,11 +141,11 @@ function ViewSideConstituents({ expression, baseHeight, activeID, onOpenEdit }:
|
||||||
conditionalCellStyles: [
|
conditionalCellStyles: [
|
||||||
{
|
{
|
||||||
when: (cst: IConstituenta) => isMockCst(cst),
|
when: (cst: IConstituenta) => isMockCst(cst),
|
||||||
classNames: ['bg-[#ffc9c9]', 'dark:bg-[#592b2b]']
|
style: {backgroundColor: colors.selectionError}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
], []);
|
], [colors]);
|
||||||
|
|
||||||
const maxHeight = useMemo(
|
const maxHeight = useMemo(
|
||||||
() => {
|
() => {
|
||||||
|
@ -156,13 +156,13 @@ 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 bg-white border-b rounded clr-bg-pop 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 clr-border'>
|
||||||
<MatchModePicker
|
<MatchModePicker
|
||||||
value={filterMatch}
|
value={filterMatch}
|
||||||
onChange={setFilterMatch}
|
onChange={setFilterMatch}
|
||||||
/>
|
/>
|
||||||
<input type='text'
|
<input type='text'
|
||||||
className='w-full px-2 bg-white outline-none select-none hover:text-clip clr-bg-pop'
|
className='w-full px-2 outline-none select-none hover:text-clip clr-input'
|
||||||
placeholder='наберите текст фильтра'
|
placeholder='наберите текст фильтра'
|
||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={event => setFilterText(event.target.value)}
|
onChange={event => setFilterText(event.target.value)}
|
||||||
|
|
|
@ -47,26 +47,30 @@ function RegisterPage() {
|
||||||
{ user &&
|
{ user &&
|
||||||
<b>{`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`}</b>}
|
<b>{`Вы вошли в систему как ${user.username}. Если хотите зарегистрировать нового пользователя, выйдите из системы (меню в правом верхнем углу экрана)`}</b>}
|
||||||
{ !user &&
|
{ !user &&
|
||||||
<Form title='Регистрация пользователя' onSubmit={handleSubmit}>
|
<Form
|
||||||
|
title='Регистрация пользователя'
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
widthClass='w-[24rem]'
|
||||||
|
>
|
||||||
<TextInput id='username' label='Имя пользователя' type='text'
|
<TextInput id='username' label='Имя пользователя' type='text'
|
||||||
required
|
required
|
||||||
value={username}
|
value={username}
|
||||||
onChange={event => { setUsername(event.target.value); }}
|
onChange={event => setUsername(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput id='password' label='Пароль' type='password'
|
<TextInput id='password' label='Пароль' type='password'
|
||||||
required
|
required
|
||||||
value={password}
|
value={password}
|
||||||
onChange={event => { setPassword(event.target.value); }}
|
onChange={event => setPassword(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput id='password2' label='Повторите пароль' type='password'
|
<TextInput id='password2' label='Повторите пароль' type='password'
|
||||||
required
|
required
|
||||||
value={password2}
|
value={password2}
|
||||||
onChange={event => { setPassword2(event.target.value); }}
|
onChange={event => setPassword2(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className='text-sm'>
|
<div className='text-sm'>
|
||||||
<p>- используйте уникальный пароль</p>
|
<p>- используйте уникальный пароль</p>
|
||||||
<p>- портал функционирует в тестовом режиме</p>
|
<p>- портал функционирует в тестовом режиме</p>
|
||||||
<p>- безопасность информации пользователей не гарантируется</p>
|
<p className='font-semibold text-red'>- безопасность информации не гарантируется</p>
|
||||||
{/* <p>- минимум 8 символов</p>
|
{/* <p>- минимум 8 символов</p>
|
||||||
<p>- большие, маленькие буквы, цифры</p>
|
<p>- большие, маленькие буквы, цифры</p>
|
||||||
<p>- минимум 1 спец. символ</p> */}
|
<p>- минимум 1 спец. символ</p> */}
|
||||||
|
@ -74,18 +78,18 @@ function RegisterPage() {
|
||||||
<TextInput id='email' label='email' type='text'
|
<TextInput id='email' label='email' type='text'
|
||||||
required
|
required
|
||||||
value={email}
|
value={email}
|
||||||
onChange={event => { setEmail(event.target.value); }}
|
onChange={event => setEmail(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput id='first_name' label='Имя' type='text'
|
<TextInput id='first_name' label='Имя' type='text'
|
||||||
value={firstName}
|
value={firstName}
|
||||||
onChange={event => { setFirstName(event.target.value); }}
|
onChange={event => setFirstName(event.target.value)}
|
||||||
/>
|
/>
|
||||||
<TextInput id='last_name' label='Фамилия' type='text'
|
<TextInput id='last_name' label='Фамилия' type='text'
|
||||||
value={lastName}
|
value={lastName}
|
||||||
onChange={event => { setLastName(event.target.value); }}
|
onChange={event => setLastName(event.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className='flex items-center justify-between my-4'>
|
<div className='flex items-center justify-center w-full my-4'>
|
||||||
<SubmitButton text='Регистрировать' loading={loading}/>
|
<SubmitButton text='Регистрировать' loading={loading}/>
|
||||||
</div>
|
</div>
|
||||||
{ error && <BackendError error={error} />}
|
{ error && <BackendError error={error} />}
|
||||||
|
|
|
@ -14,6 +14,20 @@ function EditorProfile() {
|
||||||
const [first_name, setFirstName] = useState('');
|
const [first_name, setFirstName] = useState('');
|
||||||
const [last_name, setLastName] = useState('');
|
const [last_name, setLastName] = useState('');
|
||||||
|
|
||||||
|
const [isModified, setIsModified] = useState(true);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!user) {
|
||||||
|
setIsModified(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsModified(
|
||||||
|
user.email !== email ||
|
||||||
|
user.first_name !== first_name ||
|
||||||
|
user.last_name !== last_name
|
||||||
|
);
|
||||||
|
}, [user, user?.email, user?.first_name, user?.last_name, email, first_name, last_name]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
setUsername(user.username);
|
setUsername(user.username);
|
||||||
|
@ -57,6 +71,7 @@ function EditorProfile() {
|
||||||
<SubmitButton
|
<SubmitButton
|
||||||
text='Сохранить данные'
|
text='Сохранить данные'
|
||||||
loading={processing}
|
loading={processing}
|
||||||
|
disabled={!isModified}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -16,7 +16,7 @@ function UserTabs() {
|
||||||
const { user: auth } = useAuth();
|
const { user: auth } = useAuth();
|
||||||
const { items } = useLibrary();
|
const { items } = useLibrary();
|
||||||
|
|
||||||
const [showSubs, setShowSubs] = useState(true);
|
const [showSubs, setShowSubs] = useState(false);
|
||||||
|
|
||||||
const subscriptions = useMemo(
|
const subscriptions = useMemo(
|
||||||
() => {
|
() => {
|
||||||
|
|
219
rsconcept/frontend/src/utils/color.ts
Normal file
219
rsconcept/frontend/src/utils/color.ts
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
export interface IColorTheme {
|
||||||
|
red: string
|
||||||
|
green: string
|
||||||
|
blue: string
|
||||||
|
teal: string
|
||||||
|
orange: string
|
||||||
|
|
||||||
|
text: string
|
||||||
|
|
||||||
|
input: string
|
||||||
|
inputDisabled: string
|
||||||
|
selection: string
|
||||||
|
selectionError: string
|
||||||
|
|
||||||
|
// bg100: string
|
||||||
|
// bg70: string
|
||||||
|
// bg50: string
|
||||||
|
|
||||||
|
// fg100: string
|
||||||
|
// fg70: string
|
||||||
|
// fg50: string
|
||||||
|
|
||||||
|
// primary: string
|
||||||
|
// secondary: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========== GENERAL THEMES =========
|
||||||
|
export const lightT: IColorTheme = {
|
||||||
|
red: '#ffc9c9',
|
||||||
|
green: '#aaff80',
|
||||||
|
blue: '#b3bdff',
|
||||||
|
teal: '#a5e9fa',
|
||||||
|
orange: '#ffbb80',
|
||||||
|
|
||||||
|
text: '#000000',
|
||||||
|
|
||||||
|
input: '#ffffff',
|
||||||
|
inputDisabled: '#f0f2f7',
|
||||||
|
selection: '#def1ff',
|
||||||
|
selectionError: '#ffc9c9'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const darkT: IColorTheme = {
|
||||||
|
red: '#bf0d00',
|
||||||
|
green: '#2b8000',
|
||||||
|
blue: '#394bbf',
|
||||||
|
teal: '#0099bf',
|
||||||
|
orange: '#964600',
|
||||||
|
|
||||||
|
text: '#e4e4e7',
|
||||||
|
|
||||||
|
input: '#070b12',
|
||||||
|
inputDisabled: '#374151', // bg-gray-700
|
||||||
|
selection: '#8c6000',
|
||||||
|
selectionError: '#592b2b'
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// ========= DATA TABLE THEMES ========
|
||||||
|
export const dataTableLightT = {
|
||||||
|
divider: {
|
||||||
|
default: '#d1d5db'
|
||||||
|
},
|
||||||
|
striped: {
|
||||||
|
default: '#f0f2f7'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dataTableDarkT = {
|
||||||
|
text: {
|
||||||
|
primary: 'rgba(228, 228, 231, 1)',
|
||||||
|
secondary: 'rgba(228, 228, 231, 0.87)',
|
||||||
|
disabled: 'rgba(228, 228, 231, 0.54)'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
default: '#111827'
|
||||||
|
},
|
||||||
|
highlightOnHover: {
|
||||||
|
default: '#4d6080',
|
||||||
|
text: 'rgba(228, 228, 231, 1)'
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
default: '#6b6b6b'
|
||||||
|
},
|
||||||
|
striped: {
|
||||||
|
default: '#374151',
|
||||||
|
text: 'rgba(228, 228, 231, 1)'
|
||||||
|
},
|
||||||
|
selected: {
|
||||||
|
default: '#4d6080',
|
||||||
|
text: 'rgba(228, 228, 231, 1)'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============ GRAPH THEMES ==========
|
||||||
|
export const graphLightT = {
|
||||||
|
canvas: {
|
||||||
|
background: '#f9fafb',
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fill: '#7ca0ab',
|
||||||
|
activeFill: '#1DE9AC',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.2,
|
||||||
|
label: {
|
||||||
|
color: '#2A6475',
|
||||||
|
stroke: '#fff',
|
||||||
|
activeColor: '#1DE9AC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lasso: {
|
||||||
|
border: '1px solid #55aaff',
|
||||||
|
background: 'rgba(75, 160, 255, 0.1)'
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
fill: '#D8E6EA',
|
||||||
|
activeFill: '#1DE9AC'
|
||||||
|
},
|
||||||
|
edge: {
|
||||||
|
fill: '#D8E6EA',
|
||||||
|
activeFill: '#1DE9AC',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.1,
|
||||||
|
label: {
|
||||||
|
stroke: '#fff',
|
||||||
|
color: '#2A6475',
|
||||||
|
activeColor: '#1DE9AC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
fill: '#D8E6EA',
|
||||||
|
activeFill: '#1DE9AC'
|
||||||
|
},
|
||||||
|
cluster: {
|
||||||
|
stroke: '#D8E6EA',
|
||||||
|
label: {
|
||||||
|
stroke: '#fff',
|
||||||
|
color: '#2A6475'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const graphDarkT = {
|
||||||
|
canvas: {
|
||||||
|
background: '#1f2937'
|
||||||
|
},
|
||||||
|
node: {
|
||||||
|
fill: '#7a8c9e',
|
||||||
|
activeFill: '#1DE9AC',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.2,
|
||||||
|
label: {
|
||||||
|
stroke: '#1E2026',
|
||||||
|
color: '#ACBAC7',
|
||||||
|
activeColor: '#1DE9AC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lasso: {
|
||||||
|
border: '1px solid #55aaff',
|
||||||
|
background: 'rgba(75, 160, 255, 0.1)'
|
||||||
|
},
|
||||||
|
ring: {
|
||||||
|
fill: '#54616D',
|
||||||
|
activeFill: '#1DE9AC'
|
||||||
|
},
|
||||||
|
edge: {
|
||||||
|
fill: '#474B56',
|
||||||
|
activeFill: '#1DE9AC',
|
||||||
|
opacity: 1,
|
||||||
|
selectedOpacity: 1,
|
||||||
|
inactiveOpacity: 0.1,
|
||||||
|
label: {
|
||||||
|
stroke: '#1E2026',
|
||||||
|
color: '#ACBAC7',
|
||||||
|
activeColor: '#1DE9AC'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
arrow: {
|
||||||
|
fill: '#474B56',
|
||||||
|
activeFill: '#1DE9AC'
|
||||||
|
},
|
||||||
|
cluster: {
|
||||||
|
stroke: '#474B56',
|
||||||
|
label: {
|
||||||
|
stroke: '#1E2026',
|
||||||
|
color: '#ACBAC7'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======== Bracket Matching Themes ===========
|
||||||
|
export const bracketsLightT = {
|
||||||
|
'.cc-matchingBracket': {
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
'.cc-nonmatchingBracket': {
|
||||||
|
color: '#ef4444',
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
'&.cm-focused .cc-matchingBracket': {
|
||||||
|
backgroundColor: '#dae6f2',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bracketsDarkT = {
|
||||||
|
'.cc-matchingBracket': {
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
'.cc-nonmatchingBracket': {
|
||||||
|
color: '#ef4444',
|
||||||
|
fontWeight: 700,
|
||||||
|
},
|
||||||
|
'&.cm-focused .cc-matchingBracket': {
|
||||||
|
backgroundColor: '#734f00',
|
||||||
|
},
|
||||||
|
};
|
|
@ -247,6 +247,8 @@ export interface IRSFormStats {
|
||||||
count_incalc: number
|
count_incalc: number
|
||||||
|
|
||||||
count_termin: number
|
count_termin: number
|
||||||
|
count_definition: number
|
||||||
|
count_convention: number
|
||||||
|
|
||||||
count_base: number
|
count_base: number
|
||||||
count_constant: number
|
count_constant: number
|
||||||
|
@ -281,10 +283,22 @@ export interface IRSFormUploadData {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Library =====
|
// ========== Library =====
|
||||||
|
export enum LibraryFilterStrategy {
|
||||||
|
MANUAL = 'manual',
|
||||||
|
PERSONAL = 'personal',
|
||||||
|
COMMON = 'common',
|
||||||
|
SUBSCRIBE = 'subscribe',
|
||||||
|
CANONICAL = 'canonical',
|
||||||
|
OWNED = 'owned'
|
||||||
|
}
|
||||||
|
|
||||||
export interface ILibraryFilter {
|
export interface ILibraryFilter {
|
||||||
ownedBy?: number
|
query?: string
|
||||||
|
is_personal?: boolean
|
||||||
|
is_owned?: boolean
|
||||||
is_common?: boolean
|
is_common?: boolean
|
||||||
queryMeta?: string
|
is_canonical?: boolean
|
||||||
|
is_subscribed?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
// ================ Misc types ================
|
// ================ Misc types ================
|
||||||
|
@ -392,6 +406,8 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
||||||
count_incalc: 0,
|
count_incalc: 0,
|
||||||
|
|
||||||
count_termin: 0,
|
count_termin: 0,
|
||||||
|
count_definition: 0,
|
||||||
|
count_convention: 0,
|
||||||
|
|
||||||
count_base: 0,
|
count_base: 0,
|
||||||
count_constant: 0,
|
count_constant: 0,
|
||||||
|
@ -416,6 +432,10 @@ export function LoadRSFormData(schema: IRSFormData): IRSForm {
|
||||||
|
|
||||||
count_termin: result.items.reduce(
|
count_termin: result.items.reduce(
|
||||||
(sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0),
|
(sum, cst) => (sum + (cst.term?.raw ? 1 : 0) || 0), 0),
|
||||||
|
count_definition: result.items.reduce(
|
||||||
|
(sum, cst) => (sum + (cst.definition?.text.raw ? 1 : 0) || 0), 0),
|
||||||
|
count_convention: result.items.reduce(
|
||||||
|
(sum, cst) => (sum + (cst.convention ? 1 : 0) || 0), 0),
|
||||||
|
|
||||||
count_base: result.items.reduce(
|
count_base: result.items.reduce(
|
||||||
(sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0),
|
(sum, cst) => sum + (cst.cstType === CstType.BASE ? 1 : 0), 0),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { LayoutTypes } from 'reagraph';
|
import { LayoutTypes } from 'reagraph';
|
||||||
|
|
||||||
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
import { ColoringScheme } from '../pages/RSFormPage/EditorTermGraph';
|
||||||
|
import { IColorTheme } from './color';
|
||||||
import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums';
|
import { resolveErrorClass,RSErrorClass, RSErrorType, TokenID } from './enums';
|
||||||
import {
|
import {
|
||||||
CstClass, CstMatchMode, CstType,
|
CstClass, CstMatchMode, CstType,
|
||||||
|
@ -9,18 +10,7 @@ import {
|
||||||
ISyntaxTreeNode, ParsingStatus, ValueClass
|
ISyntaxTreeNode, ParsingStatus, ValueClass
|
||||||
} from './models';
|
} from './models';
|
||||||
|
|
||||||
export interface IRSButtonData {
|
export interface IDescriptor {
|
||||||
text: string
|
|
||||||
tooltip: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IFormatInfo {
|
|
||||||
text: string
|
|
||||||
color: string
|
|
||||||
tooltip: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITopicInfo {
|
|
||||||
text: string
|
text: string
|
||||||
tooltip: string
|
tooltip: string
|
||||||
}
|
}
|
||||||
|
@ -64,7 +54,7 @@ export function getCstExpressionPrefix(cst: IConstituenta): string {
|
||||||
return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
return cst.alias + (cst.cstType === CstType.STRUCTURED ? '::=' : ':==');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRSButtonData(id: TokenID): IRSButtonData {
|
export function getRSButtonData(id: TokenID): IDescriptor {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case TokenID.BOOLEAN: return {
|
case TokenID.BOOLEAN: return {
|
||||||
text: 'ℬ()',
|
text: 'ℬ()',
|
||||||
|
@ -323,51 +313,45 @@ export const GraphColoringSelector: {value: ColoringScheme, label: string}[] = [
|
||||||
{ value: 'type', label: 'Цвет: класс'},
|
{ value: 'type', label: 'Цвет: класс'},
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getCstStatusColor(status: ExpressionStatus, darkMode: boolean): string {
|
export function getCstStatusColor(status: ExpressionStatus, colors: IColorTheme): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case ExpressionStatus.VERIFIED: return darkMode ? '#2b8000': '#aaff80';
|
case ExpressionStatus.VERIFIED: return colors.green;
|
||||||
case ExpressionStatus.INCORRECT: return darkMode ? '#592b2b': '#ffc9c9';
|
case ExpressionStatus.INCORRECT: return colors.red;
|
||||||
case ExpressionStatus.INCALCULABLE: return darkMode ? '#964600': '#ffbb80';
|
case ExpressionStatus.INCALCULABLE: return colors.orange;
|
||||||
case ExpressionStatus.PROPERTY: return darkMode ? '#36899e': '#a5e9fa';
|
case ExpressionStatus.PROPERTY: return colors.teal;
|
||||||
case ExpressionStatus.UNKNOWN: return darkMode ? '#1e00b3': '#b3bdff';
|
case ExpressionStatus.UNKNOWN: return colors.blue;
|
||||||
case ExpressionStatus.UNDEFINED: return darkMode ? '#1e00b3': '#b3bdff';
|
case ExpressionStatus.UNDEFINED: return colors.blue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapStatusInfo: Map<ExpressionStatus, IFormatInfo> = new Map([
|
export const mapStatusInfo: Map<ExpressionStatus, IDescriptor> = new Map([
|
||||||
[ ExpressionStatus.VERIFIED, {
|
[ ExpressionStatus.VERIFIED, {
|
||||||
text: 'ок',
|
text: 'ок',
|
||||||
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
|
|
||||||
tooltip: 'выражение корректно и вычислимо'
|
tooltip: 'выражение корректно и вычислимо'
|
||||||
}],
|
}],
|
||||||
[ ExpressionStatus.INCORRECT, {
|
[ ExpressionStatus.INCORRECT, {
|
||||||
text: 'ошибка',
|
text: 'ошибка',
|
||||||
color: 'bg-[#ffc9c9] dark:bg-[#592b2b]',
|
|
||||||
tooltip: 'ошибка в выражении'
|
tooltip: 'ошибка в выражении'
|
||||||
}],
|
}],
|
||||||
[ ExpressionStatus.INCALCULABLE, {
|
[ ExpressionStatus.INCALCULABLE, {
|
||||||
text: 'невыч',
|
text: 'невыч',
|
||||||
color: 'bg-[#ffbb80] dark:bg-[#964600]',
|
|
||||||
tooltip: 'выражение не вычислимо'
|
tooltip: 'выражение не вычислимо'
|
||||||
}],
|
}],
|
||||||
[ ExpressionStatus.PROPERTY, {
|
[ ExpressionStatus.PROPERTY, {
|
||||||
text: 'св-во',
|
text: 'св-во',
|
||||||
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
|
|
||||||
tooltip: 'можно проверить принадлежность, но нельзя получить значение'
|
tooltip: 'можно проверить принадлежность, но нельзя получить значение'
|
||||||
}],
|
}],
|
||||||
[ ExpressionStatus.UNKNOWN, {
|
[ ExpressionStatus.UNKNOWN, {
|
||||||
text: 'неизв',
|
text: 'неизв',
|
||||||
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
|
||||||
tooltip: 'требует проверки выражения'
|
tooltip: 'требует проверки выражения'
|
||||||
}],
|
}],
|
||||||
[ ExpressionStatus.UNDEFINED, {
|
[ ExpressionStatus.UNDEFINED, {
|
||||||
text: 'N/A',
|
text: 'N/A',
|
||||||
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
|
||||||
tooltip: 'произошла ошибка при проверке выражения'
|
tooltip: 'произошла ошибка при проверке выражения'
|
||||||
}]
|
}]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const mapTopicInfo: Map<HelpTopic, ITopicInfo> = new Map([
|
export const mapTopicInfo: Map<HelpTopic, IDescriptor> = new Map([
|
||||||
[ HelpTopic.MAIN, {
|
[ HelpTopic.MAIN, {
|
||||||
text: 'Портал',
|
text: 'Портал',
|
||||||
tooltip: 'Общая справка по порталу'
|
tooltip: 'Общая справка по порталу'
|
||||||
|
@ -406,34 +390,30 @@ export const mapTopicInfo: Map<HelpTopic, ITopicInfo> = new Map([
|
||||||
}],
|
}],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export function getCstClassColor(cstClass: CstClass, darkMode: boolean): string {
|
export function getCstClassColor(cstClass: CstClass, colors: IColorTheme): string {
|
||||||
switch (cstClass) {
|
switch (cstClass) {
|
||||||
case CstClass.TEMPLATE: return darkMode ? '#36899e': '#a5e9fa';
|
case CstClass.BASIC: return colors.green;
|
||||||
case CstClass.BASIC: return darkMode ? '#2b8000': '#aaff80';
|
case CstClass.DERIVED: return colors.blue;
|
||||||
case CstClass.DERIVED: return darkMode ? '#1e00b3': '#b3bdff';
|
case CstClass.STATEMENT: return colors.red;
|
||||||
case CstClass.STATEMENT: return darkMode ? '#592b2b': '#ffc9c9';
|
case CstClass.TEMPLATE: return colors.teal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mapCstClassInfo: Map<CstClass, IFormatInfo> = new Map([
|
export const mapCstClassInfo: Map<CstClass, IDescriptor> = new Map([
|
||||||
[ CstClass.BASIC, {
|
[ CstClass.BASIC, {
|
||||||
text: 'базовый',
|
text: 'базовый',
|
||||||
color: 'bg-[#aaff80] dark:bg-[#2b8000]',
|
|
||||||
tooltip: 'неопределяемое понятие, требует конвенции'
|
tooltip: 'неопределяемое понятие, требует конвенции'
|
||||||
}],
|
}],
|
||||||
[ CstClass.DERIVED, {
|
[ CstClass.DERIVED, {
|
||||||
text: 'производный',
|
text: 'производный',
|
||||||
color: 'bg-[#b3bdff] dark:bg-[#1e00b3]',
|
|
||||||
tooltip: 'выводимое понятие, задаваемое определением'
|
tooltip: 'выводимое понятие, задаваемое определением'
|
||||||
}],
|
}],
|
||||||
[ CstClass.STATEMENT, {
|
[ CstClass.STATEMENT, {
|
||||||
text: 'утверждение',
|
text: 'утверждение',
|
||||||
color: 'bg-[#ffc9c9] dark:bg-[#592b2b]',
|
|
||||||
tooltip: 'неопределяемое понятие, требует конвенции'
|
tooltip: 'неопределяемое понятие, требует конвенции'
|
||||||
}],
|
}],
|
||||||
[ CstClass.TEMPLATE, {
|
[ CstClass.TEMPLATE, {
|
||||||
text: 'шаблон',
|
text: 'шаблон',
|
||||||
color: 'bg-[#a5e9fa] dark:bg-[#36899e]',
|
|
||||||
tooltip: 'параметризованный шаблон определения'
|
tooltip: 'параметризованный шаблон определения'
|
||||||
}],
|
}],
|
||||||
]);
|
]);
|
||||||
|
@ -500,7 +480,7 @@ export function getTypificationLabel({isValid, resultType, args}: {
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
return 'N/A';
|
return 'N/A';
|
||||||
}
|
}
|
||||||
if (resultType === '') {
|
if (resultType === '' || resultType === 'LOGIC') {
|
||||||
resultType = 'Логический'
|
resultType = 'Логический'
|
||||||
}
|
}
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user