Refactoring: rework application files structure

This commit is contained in:
IRBorisov 2024-01-04 19:30:10 +03:00
parent 5310e0c9ed
commit fa6fef5fa5
43 changed files with 362 additions and 415 deletions

View File

@ -1,102 +0,0 @@
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import ConceptToaster from '@/components/ConceptToaster';
import Footer from '@/components/Footer';
import Navigation from '@/components/Navigation';
import { NavigationState } from '@/context/NavigationContext';
import { useConceptTheme } from '@/context/ThemeContext';
import CreateRSFormPage from '@/pages/CreateRSFormPage';
import HomePage from '@/pages/HomePage';
import LibraryPage from '@/pages/LibraryPage';
import LoginPage from '@/pages/LoginPage';
import ManualsPage from '@/pages/ManualsPage';
import NotFoundPage from '@/pages/NotFoundPage';
import RegisterPage from '@/pages/RegisterPage';
import RestorePasswordPage from '@/pages/RestorePasswordPage';
import RSFormPage from '@/pages/RSFormPage';
import UserProfilePage from '@/pages/UserProfilePage';
import { globalIDs } from '@/utils/constants';
function Root() {
const { viewportHeight, mainHeight, showScroll } = useConceptTheme();
return (
<NavigationState>
<div className='min-w-[30rem] clr-app antialiased'>
<ConceptToaster
className='mt-[4rem] text-sm' // prettier: split lines
autoClose={3000}
draggable={false}
pauseOnFocusLoss={false}
/>
<Navigation />
<div
id={globalIDs.main_scroll}
className='overflow-y-auto overscroll-none min-w-fit'
style={{
maxHeight: viewportHeight,
overflowY: showScroll ? 'scroll' : 'auto'
}}
>
<main className='flex flex-col items-center' style={{ minHeight: mainHeight }}>
<Outlet />
</main>
<Footer />
</div>
</div>
</NavigationState>
);
}
const router = createBrowserRouter([
{
path: '/',
element: <Root />,
errorElement: <NotFoundPage />,
children: [
{
path: '',
element: <HomePage />
},
{
path: 'login',
element: <LoginPage />
},
{
path: 'signup',
element: <RegisterPage />
},
{
path: 'restore-password',
element: <RestorePasswordPage />
},
{
path: 'profile',
element: <UserProfilePage />
},
{
path: 'manuals',
element: <ManualsPage />
},
{
path: 'library',
element: <LibraryPage />
},
{
path: 'library/create',
element: <CreateRSFormPage />
},
{
path: 'rsforms/:id',
element: <RSFormPage />
}
]
}
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;

View File

@ -0,0 +1,42 @@
import { Outlet } from 'react-router-dom';
import ConceptToaster from '@/app/ConceptToaster';
import Footer from '@/app/Footer';
import Navigation from '@/app/Navigation';
import { NavigationState } from '@/context/NavigationContext';
import { useConceptTheme } from '@/context/ThemeContext';
import { globalIDs } from '@/utils/constants';
function ApplicationLayout() {
const { viewportHeight, mainHeight, showScroll } = useConceptTheme();
return (
<NavigationState>
<div className='min-w-[30rem] clr-app antialiased'>
<ConceptToaster
className='mt-[4rem] text-sm' // prettier: split lines
autoClose={3000}
draggable={false}
pauseOnFocusLoss={false}
/>
<Navigation />
<div
id={globalIDs.main_scroll}
className='overflow-y-auto overscroll-none min-w-fit'
style={{
maxHeight: viewportHeight,
overflowY: showScroll ? 'scroll' : 'auto'
}}
>
<main className='flex flex-col items-center' style={{ minHeight: mainHeight }}>
<Outlet />
</main>
<Footer />
</div>
</div>
</NavigationState>
);
}
export default ApplicationLayout;

View File

@ -1,7 +1,7 @@
import { type FallbackProps } from 'react-error-boundary'; import { type FallbackProps } from 'react-error-boundary';
import Button from './Common/Button'; import Button from '../components/Common/Button';
import InfoError from './InfoError'; import InfoError from '../components/InfoError';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return ( return (

View File

@ -3,7 +3,7 @@ import clsx from 'clsx';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { urls } from '@/utils/constants'; import { urls } from '@/utils/constants';
import TextURL from './Common/TextURL'; import TextURL from '../components/Common/TextURL';
function Footer() { function Footer() {
const { noNavigation, noFooter } = useConceptTheme(); const { noNavigation, noFooter } = useConceptTheme();

View File

@ -9,7 +9,7 @@ import { LibraryState } from '@/context/LibraryContext';
import { ThemeState } from '@/context/ThemeContext'; import { ThemeState } from '@/context/ThemeContext';
import { UsersState } from '@/context/UsersContext'; import { UsersState } from '@/context/UsersContext';
import ErrorFallback from './components/ErrorFallback'; import ErrorFallback from './ErrorFallback';
pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.js', import.meta.url).toString(); pdfjs.GlobalWorkerOptions.workerSrc = new URL('pdfjs-dist/build/pdf.worker.min.js', import.meta.url).toString();

View File

@ -0,0 +1,60 @@
import { createBrowserRouter } from 'react-router-dom';
import CreateRSFormPage from '@/pages/CreateRSFormPage';
import HomePage from '@/pages/HomePage';
import LibraryPage from '@/pages/LibraryPage';
import LoginPage from '@/pages/LoginPage';
import ManualsPage from '@/pages/ManualsPage';
import NotFoundPage from '@/pages/NotFoundPage';
import RegisterPage from '@/pages/RegisterPage';
import RestorePasswordPage from '@/pages/RestorePasswordPage';
import RSFormPage from '@/pages/RSFormPage';
import UserProfilePage from '@/pages/UserProfilePage';
import ApplicationLayout from './ApplicationLayout';
export const Router = createBrowserRouter([
{
path: '/',
element: <ApplicationLayout />,
errorElement: <NotFoundPage />,
children: [
{
path: '',
element: <HomePage />
},
{
path: 'login',
element: <LoginPage />
},
{
path: 'signup',
element: <RegisterPage />
},
{
path: 'restore-password',
element: <RestorePasswordPage />
},
{
path: 'profile',
element: <UserProfilePage />
},
{
path: 'manuals',
element: <ManualsPage />
},
{
path: 'library',
element: <LibraryPage />
},
{
path: 'library/create',
element: <CreateRSFormPage />
},
{
path: 'rsforms/:id',
element: <RSFormPage />
}
]
}
]);

View File

@ -0,0 +1,9 @@
import { RouterProvider } from 'react-router-dom';
import { Router } from './Router';
function App() {
return <RouterProvider router={Router} />;
}
export default App;

View File

@ -6,12 +6,21 @@ import { globalIDs } from '@/utils/constants';
import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons'; import { CheckboxCheckedIcon, CheckboxNullIcon } from '../Icons';
import { CheckboxProps } from './Checkbox'; import { CheckboxProps } from './Checkbox';
export interface TristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> { export interface CheckboxTristateProps extends Omit<CheckboxProps, 'value' | 'setValue'> {
value: boolean | null; value: boolean | null;
setValue?: (newValue: boolean | null) => void; setValue?: (newValue: boolean | null) => void;
} }
function Tristate({ id, disabled, label, title, className, value, setValue, ...restProps }: TristateProps) { function CheckboxTristate({
id,
disabled,
label,
title,
className,
value,
setValue,
...restProps
}: CheckboxTristateProps) {
const cursor = useMemo(() => { const cursor = useMemo(() => {
if (disabled) { if (disabled) {
return 'cursor-not-allowed'; return 'cursor-not-allowed';
@ -71,4 +80,4 @@ function Tristate({ id, disabled, label, title, className, value, setValue, ...r
); );
} }
export default Tristate; export default CheckboxTristate;

View File

@ -4,11 +4,11 @@ import { ThreeDots } from 'react-loader-spinner';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
interface ConceptLoaderProps { interface LoaderProps {
size?: number; size?: number;
} }
export function ConceptLoader({ size = 10 }: ConceptLoaderProps) { export function Loader({ size = 10 }: LoaderProps) {
const { colors } = useConceptTheme(); const { colors } = useConceptTheme();
return ( return (
<div className='flex justify-center'> <div className='flex justify-center'>

View File

@ -4,13 +4,13 @@ import { CProps } from '../props';
import Overlay from './Overlay'; import Overlay from './Overlay';
import TextInput from './TextInput'; import TextInput from './TextInput';
interface ConceptSearchProps extends CProps.Styling { interface SearchBarProps extends CProps.Styling {
value: string; value: string;
onChange?: (newValue: string) => void; onChange?: (newValue: string) => void;
noBorder?: boolean; noBorder?: boolean;
} }
function ConceptSearch({ value, onChange, noBorder, ...restProps }: ConceptSearchProps) { function SearchBar({ value, onChange, noBorder, ...restProps }: SearchBarProps) {
return ( return (
<div {...restProps}> <div {...restProps}>
<Overlay position='top-[-0.125rem] left-3 translate-y-1/2' className='pointer-events-none clr-text-controls'> <Overlay position='top-[-0.125rem] left-3 translate-y-1/2' className='pointer-events-none clr-text-controls'>
@ -19,6 +19,7 @@ function ConceptSearch({ value, onChange, noBorder, ...restProps }: ConceptSearc
<TextInput <TextInput
noOutline noOutline
placeholder='Поиск' placeholder='Поиск'
type='search'
className='w-full pl-10' className='w-full pl-10'
noBorder={noBorder} noBorder={noBorder}
value={value} value={value}
@ -28,4 +29,4 @@ function ConceptSearch({ value, onChange, noBorder, ...restProps }: ConceptSearc
); );
} }
export default ConceptSearch; export default SearchBar;

View File

@ -1,47 +0,0 @@
import clsx from 'clsx';
import { CProps } from '../props';
interface SwitchButtonProps<ValueType> extends CProps.Styling {
id?: string;
value: ValueType;
label?: string;
icon?: React.ReactNode;
title?: string;
isSelected?: boolean;
onSelect: (value: ValueType) => void;
}
function SwitchButton<ValueType>({
value,
icon,
label,
className,
isSelected,
onSelect,
...restProps
}: SwitchButtonProps<ValueType>) {
return (
<button
type='button'
tabIndex={-1}
onClick={() => onSelect(value)}
className={clsx(
'px-2 py-1',
'border rounded-none',
'font-controls',
'clr-btn-clear clr-hover',
'cursor-pointer',
isSelected && 'clr-selected',
className
)}
{...restProps}
>
{icon ? icon : null}
{label}
</button>
);
}
export default SwitchButton;

View File

@ -1,16 +1,16 @@
import clsx from 'clsx'; import clsx from 'clsx';
import type { TabProps } from 'react-tabs'; import type { TabProps as TabPropsImpl } from 'react-tabs';
import { Tab } from 'react-tabs'; import { Tab as TabImpl } from 'react-tabs';
import { globalIDs } from '@/utils/constants'; import { globalIDs } from '@/utils/constants';
interface ConceptTabProps extends Omit<TabProps, 'children'> { interface TabLabelProps extends Omit<TabPropsImpl, 'children'> {
label?: string; label?: string;
} }
function ConceptTab({ label, title, className, ...otherProps }: ConceptTabProps) { function TabLabel({ label, title, className, ...otherProps }: TabLabelProps) {
return ( return (
<Tab <TabImpl
className={clsx( className={clsx(
'min-w-[6rem]', 'min-w-[6rem]',
'px-2 py-1 flex justify-center', 'px-2 py-1 flex justify-center',
@ -24,10 +24,10 @@ function ConceptTab({ label, title, className, ...otherProps }: ConceptTabProps)
{...otherProps} {...otherProps}
> >
{label} {label}
</Tab> </TabImpl>
); );
} }
ConceptTab.tabsRole = 'Tab'; TabLabel.tabsRole = 'Tab';
export default ConceptTab; export default TabLabel;

View File

@ -3,16 +3,16 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { ITooltip, Tooltip } from 'react-tooltip'; import { ITooltip, Tooltip as TooltipImpl } from 'react-tooltip';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
interface ConceptTooltipProps extends Omit<ITooltip, 'variant'> { interface TooltipProps extends Omit<ITooltip, 'variant'> {
layer?: string; layer?: string;
text?: string; text?: string;
} }
function ConceptTooltip({ function Tooltip({
text, text,
children, children,
layer = 'z-tooltip', layer = 'z-tooltip',
@ -20,13 +20,13 @@ function ConceptTooltip({
className, className,
style, style,
...restProps ...restProps
}: ConceptTooltipProps) { }: TooltipProps) {
const { darkMode } = useConceptTheme(); const { darkMode } = useConceptTheme();
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return null; return null;
} }
return createPortal( return createPortal(
<Tooltip <TooltipImpl
delayShow={1000} delayShow={1000}
delayHide={100} delayHide={100}
opacity={0.97} opacity={0.97}
@ -39,9 +39,9 @@ function ConceptTooltip({
> >
{text ? text : null} {text ? text : null}
{children as ReactNode} {children as ReactNode}
</Tooltip>, </TooltipImpl>,
document.body document.body
); );
} }
export default ConceptTooltip; export default Tooltip;

View File

@ -1,6 +1,6 @@
import { Table } from '@tanstack/react-table'; import { Table } from '@tanstack/react-table';
import Tristate from '@/components/Common/Tristate'; import CheckboxTristate from '@/components/Common/CheckboxTristate';
interface SelectAllProps<TData> { interface SelectAllProps<TData> {
table: Table<TData>; table: Table<TData>;
@ -8,7 +8,7 @@ interface SelectAllProps<TData> {
function SelectAll<TData>({ table }: SelectAllProps<TData>) { function SelectAll<TData>({ table }: SelectAllProps<TData>) {
return ( return (
<Tristate <CheckboxTristate
tabIndex={-1} tabIndex={-1}
title='Выделить все' title='Выделить все'
value={ value={

View File

@ -1,4 +1,4 @@
import ConceptTooltip from '@/components/Common/ConceptTooltip'; import Tooltip from '@/components/Common/Tooltip';
import InfoConstituenta from '@/components/Shared/InfoConstituenta'; import InfoConstituenta from '@/components/Shared/InfoConstituenta';
import { IConstituenta } from '@/models/rsform'; import { IConstituenta } from '@/models/rsform';
@ -9,9 +9,9 @@ interface ConstituentaTooltipProps {
function ConstituentaTooltip({ data, anchor }: ConstituentaTooltipProps) { function ConstituentaTooltip({ data, anchor }: ConstituentaTooltipProps) {
return ( return (
<ConceptTooltip clickable anchorSelect={anchor} className='max-w-[30rem]'> <Tooltip clickable anchorSelect={anchor} className='max-w-[30rem]'>
<InfoConstituenta data={data} /> <InfoConstituenta data={data} />
</ConceptTooltip> </Tooltip>
); );
} }

View File

@ -7,9 +7,14 @@ function HelpAPI() {
<h1>Программный интерфейс Портала</h1> <h1>Программный интерфейс Портала</h1>
<p>В качестве программного интерфейса сервера используется REST API, реализованный с помощью Django.</p> <p>В качестве программного интерфейса сервера используется REST API, реализованный с помощью Django.</p>
<p>На данный момент API находится в разработке, поэтому поддержка внешних запросов не производится.</p> <p>На данный момент API находится в разработке, поэтому поддержка внешних запросов не производится.</p>
<p>С описанием интерфейса можно ознакомиться <TextURL text='по ссылке' href={urls.restAPI}/>.</p> <p>
<p><TextURL text='Принять участие в разработке' href={urls.git_repo}/></p> С описанием интерфейса можно ознакомиться <TextURL text='по ссылке' href={urls.restAPI} />.
</div>); </p>
<p>
<TextURL text='Принять участие в разработке' href={urls.git_repo} />
</p>
</div>
);
} }
export default HelpAPI; export default HelpAPI;

View File

@ -1,7 +1,7 @@
import { BiInfoCircle } from 'react-icons/bi'; import { BiInfoCircle } from 'react-icons/bi';
import ConceptTooltip from '@/components/Common/ConceptTooltip';
import TextURL from '@/components/Common/TextURL'; import TextURL from '@/components/Common/TextURL';
import Tooltip from '@/components/Common/Tooltip';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
import { CProps } from '../props'; import { CProps } from '../props';
@ -16,14 +16,14 @@ function HelpButton({ topic, ...restProps }: HelpButtonProps) {
return ( return (
<div id={`help-${topic}`} className='p-1'> <div id={`help-${topic}`} className='p-1'>
<BiInfoCircle size='1.25rem' className='clr-text-primary' /> <BiInfoCircle size='1.25rem' className='clr-text-primary' />
<ConceptTooltip clickable anchorSelect={`#help-${topic}`} layer='z-modal-tooltip' {...restProps}> <Tooltip clickable anchorSelect={`#help-${topic}`} layer='z-modal-tooltip' {...restProps}>
<div className='relative'> <div className='relative'>
<div className='absolute right-0 text-sm top-[0.4rem]'> <div className='absolute right-0 text-sm top-[0.4rem]'>
<TextURL text='Справка...' href={`/manuals?topic=${topic}`} /> <TextURL text='Справка...' href={`/manuals?topic=${topic}`} />
</div> </div>
</div> </div>
<InfoTopic topic={topic} /> <InfoTopic topic={topic} />
</ConceptTooltip> </Tooltip>
</div> </div>
); );
} }

View File

@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import ConceptSearch from '@/components/Common/ConceptSearch'; import SearchBar from '@/components/Common/SearchBar';
import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/DataTable'; import DataTable, { createColumnHelper, IConditionalStyle } from '@/components/DataTable';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { CstMatchMode } from '@/models/miscellaneous'; import { CstMatchMode } from '@/models/miscellaneous';
@ -83,7 +83,7 @@ function ConstituentaPicker({
return ( return (
<div> <div>
<ConceptSearch value={filterText} onChange={newValue => setFilterText(newValue)} /> <SearchBar value={filterText} onChange={newValue => setFilterText(newValue)} />
<DataTable <DataTable
dense dense
noHeader noHeader

View File

@ -3,7 +3,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react'; import { createContext, useCallback, useContext, useLayoutEffect, useMemo, useState } from 'react';
import ConceptTooltip from '@/components/Common/ConceptTooltip'; import Tooltip from '@/components/Common/Tooltip';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
import { animationDuration } from '@/utils/animations'; import { animationDuration } from '@/utils/animations';
import { darkT, IColorTheme, lightT } from '@/utils/color'; import { darkT, IColorTheme, lightT } from '@/utils/color';
@ -104,7 +104,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
}} }}
> >
<> <>
<ConceptTooltip <Tooltip
float float
id={`${globalIDs.tooltip}`} id={`${globalIDs.tooltip}`}
layer='z-topmost' layer='z-topmost'

View File

@ -4,9 +4,9 @@ import clsx from 'clsx';
import { useLayoutEffect, useState } from 'react'; import { useLayoutEffect, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import ConceptTab from '@/components/Common/ConceptTab';
import Modal, { ModalProps } from '@/components/Common/Modal'; import Modal, { ModalProps } from '@/components/Common/Modal';
import Overlay from '@/components/Common/Overlay'; import Overlay from '@/components/Common/Overlay';
import TabLabel from '@/components/Common/TabLabel';
import HelpButton from '@/components/Help/HelpButton'; import HelpButton from '@/components/Help/HelpButton';
import usePartialUpdate from '@/hooks/usePartialUpdate'; import usePartialUpdate from '@/hooks/usePartialUpdate';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
@ -125,9 +125,9 @@ function DlgConstituentaTemplate({ hideWindow, schema, onCreate, insertAfter }:
onSelect={setActiveTab} onSelect={setActiveTab}
> >
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}> <TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<ConceptTab label='Шаблон' title='Выбор шаблона выражения' className='w-[8rem]' /> <TabLabel label='Шаблон' title='Выбор шаблона выражения' className='w-[8rem]' />
<ConceptTab label='Аргументы' title='Подстановка аргументов шаблона' className='w-[8rem]' /> <TabLabel label='Аргументы' title='Подстановка аргументов шаблона' className='w-[8rem]' />
<ConceptTab label='Конституента' title='Редактирование атрибутов конституенты' className='w-[8rem]' /> <TabLabel label='Конституента' title='Редактирование атрибутов конституенты' className='w-[8rem]' />
</TabList> </TabList>
<TabPanel style={{ display: activeTab === TabID.TEMPLATE ? '' : 'none' }}> <TabPanel style={{ display: activeTab === TabID.TEMPLATE ? '' : 'none' }}>

View File

@ -4,7 +4,7 @@ import clsx from 'clsx';
import { useState } from 'react'; import { useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import ConceptTab from '@/components/Common/ConceptTab'; import TabLabel from '@/components/Common/TabLabel';
import Modal from '@/components/Common/Modal'; import Modal from '@/components/Common/Modal';
import Overlay from '@/components/Common/Overlay'; import Overlay from '@/components/Common/Overlay';
import HelpButton from '@/components/Help/HelpButton'; import HelpButton from '@/components/Help/HelpButton';
@ -64,12 +64,12 @@ function DlgEditReference({ hideWindow, items, initial, onSave }: DlgEditReferen
onSelect={setActiveTab} onSelect={setActiveTab}
> >
<TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}> <TabList className={clsx('mb-3 self-center', 'flex', 'border divide-x rounded-none')}>
<ConceptTab <TabLabel
title='Отсылка на термин в заданной словоформе' title='Отсылка на термин в заданной словоформе'
label={labelReferenceType(ReferenceType.ENTITY)} label={labelReferenceType(ReferenceType.ENTITY)}
className='w-[12rem]' className='w-[12rem]'
/> />
<ConceptTab <TabLabel
title='Установление синтаксической связи с отсылкой на термин' title='Установление синтаксической связи с отсылкой на термин'
label={labelReferenceType(ReferenceType.SYNTACTIC)} label={labelReferenceType(ReferenceType.SYNTACTIC)}
className='w-[12rem]' className='w-[12rem]'

View File

@ -2,8 +2,8 @@
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import GraphUI, { GraphEdge, GraphNode } from '@/components/Common/GraphUI';
import Modal, { ModalProps } from '@/components/Common/Modal'; import Modal, { ModalProps } from '@/components/Common/Modal';
import GraphUI, { GraphEdge, GraphNode } from '@/components/GraphUI';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { SyntaxTree } from '@/models/rslang'; import { SyntaxTree } from '@/models/rslang';
import { graphDarkT, graphLightT } from '@/utils/color'; import { graphDarkT, graphLightT } from '@/utils/color';

View File

@ -2,8 +2,8 @@ import './index.css';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import App from './App.tsx'; import App from './app';
import GlobalProviders from './GlobalProviders'; import GlobalProviders from './app/GlobalProviders';
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<GlobalProviders> <GlobalProviders>

View File

@ -2,7 +2,7 @@
import { useCallback, useLayoutEffect, useState } from 'react'; import { useCallback, useLayoutEffect, useState } from 'react';
import { ConceptLoader } from '@/components/Common/ConceptLoader'; import { Loader } from '@/components/Common/Loader';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
import { useLibrary } from '@/context/LibraryContext'; import { useLibrary } from '@/context/LibraryContext';
@ -65,7 +65,7 @@ function LibraryPage() {
return ( return (
<> <>
{library.loading ? <ConceptLoader /> : null} {library.loading ? <Loader /> : null}
{library.error ? <InfoError error={library.error} /> : null} {library.error ? <InfoError error={library.error} /> : null}
{!library.loading && library.items ? ( {!library.loading && library.items ? (
<> <>

View File

@ -3,7 +3,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback } from 'react'; import { useCallback } from 'react';
import ConceptSearch from '@/components/Common/ConceptSearch'; import SearchBar from '@/components/Common/SearchBar';
import { useConceptNavigation } from '@/context/NavigationContext'; import { useConceptNavigation } from '@/context/NavigationContext';
import { ILibraryFilter } from '@/models/miscellaneous'; import { ILibraryFilter } from '@/models/miscellaneous';
import { LibraryFilterStrategy } from '@/models/miscellaneous'; import { LibraryFilterStrategy } from '@/models/miscellaneous';
@ -67,7 +67,7 @@ function SearchPanel({ total, filtered, query, setQuery, strategy, setFilter }:
</span> </span>
</div> </div>
<div className={clsx('flex-grow', 'flex gap-1 justify-center items-center')}> <div className={clsx('flex-grow', 'flex gap-1 justify-center items-center')}>
<ConceptSearch noBorder className='min-w-[10rem]' value={query} onChange={handleChangeQuery} /> <SearchBar noBorder className='min-w-[10rem]' value={query} onChange={handleChangeQuery} />
<PickerStrategy value={strategy} onChange={handleChangeStrategy} /> <PickerStrategy value={strategy} onChange={handleChangeStrategy} />
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ConceptLoader } from '@/components/Common/ConceptLoader'; import { Loader } from '@/components/Common/Loader';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { ExpressionStatus } from '@/models/rsform'; import { ExpressionStatus } from '@/models/rsform';
import { type IConstituenta } from '@/models/rsform'; import { type IConstituenta } from '@/models/rsform';
@ -52,7 +52,7 @@ function StatusBar({ isModified, processing, constituenta, parseData, onAnalyze
onClick={onAnalyze} onClick={onAnalyze}
> >
{processing ? ( {processing ? (
<ConceptLoader size={3} /> <Loader size={3} />
) : ( ) : (
<> <>
<StatusIcon status={status} /> <StatusIcon status={status} />

View File

@ -14,21 +14,26 @@ import RSFormStats from './RSFormStats';
import RSFormToolbar from './RSFormToolbar'; import RSFormToolbar from './RSFormToolbar';
interface EditorRSFormProps { interface EditorRSFormProps {
isModified: boolean isModified: boolean;
isMutable: boolean isMutable: boolean;
setIsModified: Dispatch<SetStateAction<boolean>> setIsModified: Dispatch<SetStateAction<boolean>>;
onDestroy: () => void onDestroy: () => void;
onClaim: () => void onClaim: () => void;
onShare: () => void onShare: () => void;
onDownload: () => void onDownload: () => void;
onToggleSubscribe: () => void onToggleSubscribe: () => void;
} }
function EditorRSForm({ function EditorRSForm({
isModified, isMutable, isModified,
onDestroy, onClaim, onShare, setIsModified, isMutable,
onDownload, onToggleSubscribe onDestroy,
onClaim,
onShare,
setIsModified,
onDownload,
onToggleSubscribe
}: EditorRSFormProps) { }: EditorRSFormProps) {
const { schema, isClaimable, isSubscribed, processing } = useRSForm(); const { schema, isClaimable, isSubscribed, processing } = useRSForm();
const { user } = useAuth(); const { user } = useAuth();
@ -58,7 +63,6 @@ function EditorRSForm({
modified={isModified} modified={isModified}
claimable={isClaimable} claimable={isClaimable}
anonymous={!user} anonymous={!user}
onSubmit={initiateSubmit} onSubmit={initiateSubmit}
onShare={onShare} onShare={onShare}
onDownload={onDownload} onDownload={onDownload}
@ -66,12 +70,10 @@ function EditorRSForm({
onDestroy={onDestroy} onDestroy={onDestroy}
onToggleSubscribe={onToggleSubscribe} onToggleSubscribe={onToggleSubscribe}
/> />
<div tabIndex={-1} <div tabIndex={-1} className='flex' onKeyDown={handleInput}>
className='flex'
onKeyDown={handleInput}
>
<FlexColumn className='px-4 pb-2'> <FlexColumn className='px-4 pb-2'>
<FormRSForm disabled={!isMutable} <FormRSForm
disabled={!isMutable}
id={globalIDs.library_item_editor} id={globalIDs.library_item_editor}
isModified={isModified} isModified={isModified}
setIsModified={setIsModified} setIsModified={setIsModified}
@ -84,7 +86,8 @@ function EditorRSForm({
<RSFormStats stats={schema?.stats} /> <RSFormStats stats={schema?.stats} />
</div> </div>
</>); </>
);
} }
export default EditorRSForm; export default EditorRSForm;

View File

@ -3,7 +3,7 @@ import LabeledValue from '@/components/Common/LabeledValue';
import { type IRSFormStats } from '@/models/rsform'; import { type IRSFormStats } from '@/models/rsform';
interface RSFormStatsProps { interface RSFormStatsProps {
stats?: IRSFormStats stats?: IRSFormStats;
} }
function RSFormStats({ stats }: RSFormStatsProps) { function RSFormStats({ stats }: RSFormStatsProps) {
@ -12,83 +12,45 @@ function RSFormStats({ stats }: RSFormStatsProps) {
} }
return ( return (
<div className='flex flex-col gap-1 px-4 mt-8 w-[16rem]'> <div className='flex flex-col gap-1 px-4 mt-8 w-[16rem]'>
<LabeledValue id='count_all' <LabeledValue id='count_all' label='Всего конституент ' text={stats.count_all} />
label='Всего конституент ' <LabeledValue id='count_errors' label='Некорректных' text={stats.count_errors} />
text={stats.count_all} {stats.count_property !== 0 ? (
/> <LabeledValue id='count_property' label='Неразмерных' text={stats.count_property} />
<LabeledValue id='count_errors' ) : null}
label='Некорректных' {stats.count_incalculable !== 0 ? (
text={stats.count_errors} <LabeledValue id='count_incalculable' label='Невычислимых' text={stats.count_incalculable} />
/> ) : null}
{stats.count_property !== 0 ?
<LabeledValue id='count_property'
label='Неразмерных'
text={stats.count_property}
/> : null}
{stats.count_incalculable !== 0 ?
<LabeledValue id='count_incalculable'
label='Невычислимых'
text={stats.count_incalculable}
/> : null}
<Divider margins='my-2' /> <Divider margins='my-2' />
<LabeledValue id='count_text_term' <LabeledValue id='count_text_term' label='Термины' text={stats.count_text_term} />
label='Термины' <LabeledValue id='count_definition' label='Определения' text={stats.count_definition} />
text={stats.count_text_term} <LabeledValue id='count_convention' label='Конвенции' text={stats.count_convention} />
/>
<LabeledValue id='count_definition'
label='Определения'
text={stats.count_definition}
/>
<LabeledValue id='count_convention'
label='Конвенции'
text={stats.count_convention}
/>
<Divider margins='my-2' /> <Divider margins='my-2' />
{stats.count_base !== 0 ? {stats.count_base !== 0 ? (
<LabeledValue id='count_base' <LabeledValue id='count_base' label='Базисные множества ' text={stats.count_base} />
label='Базисные множества ' ) : null}
text={stats.count_base} {stats.count_constant !== 0 ? (
/> : null} <LabeledValue id='count_constant' label='Константные множества ' text={stats.count_constant} />
{ stats.count_constant !== 0 ? ) : null}
<LabeledValue id='count_constant' {stats.count_structured !== 0 ? (
label='Константные множества ' <LabeledValue id='count_structured' label='Родовые структуры ' text={stats.count_structured} />
text={stats.count_constant} ) : null}
/> : null} {stats.count_axiom !== 0 ? <LabeledValue id='count_axiom' label='Аксиомы ' text={stats.count_axiom} /> : null}
{stats.count_structured !== 0 ? {stats.count_term !== 0 ? <LabeledValue id='count_term' label='Термы ' text={stats.count_term} /> : null}
<LabeledValue id='count_structured' {stats.count_function !== 0 ? (
label='Родовые структуры ' <LabeledValue id='count_function' label='Терм-функции ' text={stats.count_function} />
text={stats.count_structured} ) : null}
/> : null} {stats.count_predicate !== 0 ? (
{stats.count_axiom !== 0 ? <LabeledValue id='count_predicate' label='Предикат-функции ' text={stats.count_predicate} />
<LabeledValue id='count_axiom' ) : null}
label='Аксиомы ' {stats.count_theorem !== 0 ? (
text={stats.count_axiom} <LabeledValue id='count_theorem' label='Теоремы ' text={stats.count_theorem} />
/> : null} ) : null}
{stats.count_term !== 0 ? </div>
<LabeledValue id='count_term' );
label='Термы '
text={stats.count_term}
/> : null}
{stats.count_function !== 0 ?
<LabeledValue id='count_function'
label='Терм-функции '
text={stats.count_function}
/> : null}
{stats.count_predicate !== 0 ?
<LabeledValue id='count_predicate'
label='Предикат-функции '
text={stats.count_predicate}
/> : null}
{stats.count_theorem !== 0 ?
<LabeledValue id='count_theorem'
label='Теоремы '
text={stats.count_theorem}
/> : null}
</div>);
} }
export default RSFormStats; export default RSFormStats;

View File

@ -11,28 +11,36 @@ import HelpButton from '@/components/Help/HelpButton';
import { HelpTopic } from '@/models/miscellaneous'; import { HelpTopic } from '@/models/miscellaneous';
interface RSFormToolbarProps { interface RSFormToolbarProps {
isMutable: boolean isMutable: boolean;
isSubscribed: boolean isSubscribed: boolean;
modified: boolean modified: boolean;
claimable: boolean claimable: boolean;
anonymous: boolean anonymous: boolean;
processing: boolean processing: boolean;
onSubmit: () => void onSubmit: () => void;
onShare: () => void onShare: () => void;
onDownload: () => void onDownload: () => void;
onClaim: () => void onClaim: () => void;
onDestroy: () => void onDestroy: () => void;
onToggleSubscribe: () => void onToggleSubscribe: () => void;
} }
function RSFormToolbar({ function RSFormToolbar({
isMutable, modified, claimable, anonymous, isMutable,
isSubscribed, onToggleSubscribe, processing, modified,
onSubmit, onShare, onDownload, claimable,
onClaim, onDestroy anonymous,
isSubscribed,
onToggleSubscribe,
processing,
onSubmit,
onShare,
onDownload,
onClaim,
onDestroy
}: RSFormToolbarProps) { }: RSFormToolbarProps) {
const canSave = useMemo(() => (modified && isMutable), [modified, isMutable]); const canSave = useMemo(() => modified && isMutable, [modified, isMutable]);
return ( return (
<Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'> <Overlay position='top-1 right-1/2 translate-x-1/2' className='flex'>
<MiniButton <MiniButton
@ -54,9 +62,12 @@ function RSFormToolbar({
<MiniButton <MiniButton
title={`Отслеживание ${isSubscribed ? 'включено' : 'выключено'}`} title={`Отслеживание ${isSubscribed ? 'включено' : 'выключено'}`}
disabled={anonymous || processing} disabled={anonymous || processing}
icon={isSubscribed icon={
? <FiBell size='1.25rem' className='clr-text-primary' /> isSubscribed ? (
: <FiBellOff size='1.25rem' className='clr-text-controls' /> <FiBell size='1.25rem' className='clr-text-primary' />
) : (
<FiBellOff size='1.25rem' className='clr-text-controls' />
)
} }
style={{ outlineColor: 'transparent' }} style={{ outlineColor: 'transparent' }}
onClick={onToggleSubscribe} onClick={onToggleSubscribe}
@ -74,7 +85,8 @@ function RSFormToolbar({
icon={<BiTrash size='1.25rem' className={isMutable ? 'clr-text-warning' : ''} />} icon={<BiTrash size='1.25rem' className={isMutable ? 'clr-text-warning' : ''} />}
/> />
<HelpButton topic={HelpTopic.RSFORM} offset={4} /> <HelpButton topic={HelpTopic.RSFORM} offset={4} />
</Overlay>); </Overlay>
);
} }
export default RSFormToolbar; export default RSFormToolbar;

View File

@ -2,14 +2,7 @@
import { useCallback, useLayoutEffect, useMemo, useRef } from 'react'; import { useCallback, useLayoutEffect, useMemo, useRef } from 'react';
import GraphUI, { import GraphUI, { GraphCanvasRef, GraphEdge, GraphNode, LayoutTypes, Sphere, useSelection } from '@/components/GraphUI';
GraphCanvasRef,
GraphEdge,
GraphNode,
LayoutTypes,
Sphere,
useSelection
} from '@/components/Common/GraphUI';
import { useConceptTheme } from '@/context/ThemeContext'; import { useConceptTheme } from '@/context/ThemeContext';
import { graphDarkT, graphLightT } from '@/utils/color'; import { graphDarkT, graphLightT } from '@/utils/color';
import { resources } from '@/utils/constants'; import { resources } from '@/utils/constants';

View File

@ -8,8 +8,8 @@ import { useCallback, useLayoutEffect, useMemo, useState } from 'react';
import { TabList, TabPanel, Tabs } from 'react-tabs'; import { TabList, TabPanel, Tabs } from 'react-tabs';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { ConceptLoader } from '@/components/Common/ConceptLoader'; import { Loader } from '@/components/Common/Loader';
import ConceptTab from '@/components/Common/ConceptTab'; import TabLabel from '@/components/Common/TabLabel';
import TextURL from '@/components/Common/TextURL'; import TextURL from '@/components/Common/TextURL';
import InfoError, { ErrorData } from '@/components/InfoError'; import InfoError, { ErrorData } from '@/components/InfoError';
import { useAccessMode } from '@/context/AccessModeContext'; import { useAccessMode } from '@/context/AccessModeContext';
@ -360,7 +360,7 @@ function RSTabs() {
return ( return (
<> <>
{loading ? <ConceptLoader /> : null} {loading ? <Loader /> : null}
{error ? <ProcessError error={error} /> : null} {error ? <ProcessError error={error} /> : null}
<AnimatePresence> <AnimatePresence>
{showUpload ? <DlgUploadRSForm hideWindow={() => setShowUpload(false)} /> : null} {showUpload ? <DlgUploadRSForm hideWindow={() => setShowUpload(false)} /> : null}
@ -425,13 +425,13 @@ function RSTabs() {
showCloneDialog={promptClone} showCloneDialog={promptClone}
showUploadDialog={() => setShowUpload(true)} showUploadDialog={() => setShowUpload(true)}
/> />
<ConceptTab label='Карточка' title={`Название схемы: ${schema.title ?? ''}`} /> <TabLabel label='Карточка' title={`Название схемы: ${schema.title ?? ''}`} />
<ConceptTab <TabLabel
label='Содержание' label='Содержание'
title={`Конституент: ${schema.stats?.count_all ?? 0} | Ошибок: ${schema.stats?.count_errors ?? 0}`} title={`Конституент: ${schema.stats?.count_all ?? 0} | Ошибок: ${schema.stats?.count_errors ?? 0}`}
/> />
<ConceptTab label='Редактор' /> <TabLabel label='Редактор' />
<ConceptTab label='Граф термов' /> <TabLabel label='Граф термов' />
</TabList> </TabList>
<TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '' : 'none' }}> <TabPanel forceRender style={{ display: activeTab === RSTabID.CARD ? '' : 'none' }}>

View File

@ -3,9 +3,9 @@
import { useCallback, useLayoutEffect } from 'react'; import { useCallback, useLayoutEffect } from 'react';
import { BiCog, BiFilterAlt } from 'react-icons/bi'; import { BiCog, BiFilterAlt } from 'react-icons/bi';
import ConceptSearch from '@/components/Common/ConceptSearch';
import Dropdown from '@/components/Common/Dropdown'; import Dropdown from '@/components/Common/Dropdown';
import DropdownButton from '@/components/Common/DropdownButton'; import DropdownButton from '@/components/Common/DropdownButton';
import SearchBar from '@/components/Common/SearchBar';
import SelectorButton from '@/components/Common/SelectorButton'; import SelectorButton from '@/components/Common/SelectorButton';
import useDropdown from '@/hooks/useDropdown'; import useDropdown from '@/hooks/useDropdown';
import useLocalStorage from '@/hooks/useLocalStorage'; import useLocalStorage from '@/hooks/useLocalStorage';
@ -75,7 +75,7 @@ function ConstituentsSearch({ schema, activeID, activeExpression, setFiltered }:
return ( return (
<div className='flex border-b clr-input'> <div className='flex border-b clr-input'>
<ConceptSearch noBorder className='min-w-[6rem] pr-2 flex-grow' value={filterText} onChange={setFilterText} /> <SearchBar noBorder className='min-w-[6rem] pr-2 flex-grow' value={filterText} onChange={setFilterText} />
<div ref={matchModeMenu.ref}> <div ref={matchModeMenu.ref}>
<SelectorButton <SelectorButton

View File

@ -7,12 +7,12 @@ import { toast } from 'react-toastify';
import Button from '@/components/Common/Button'; import Button from '@/components/Common/Button';
import Checkbox from '@/components/Common/Checkbox'; import Checkbox from '@/components/Common/Checkbox';
import ConceptTooltip from '@/components/Common/ConceptTooltip';
import FlexColumn from '@/components/Common/FlexColumn'; import FlexColumn from '@/components/Common/FlexColumn';
import Overlay from '@/components/Common/Overlay'; import Overlay from '@/components/Common/Overlay';
import SubmitButton from '@/components/Common/SubmitButton'; import SubmitButton from '@/components/Common/SubmitButton';
import TextInput from '@/components/Common/TextInput'; import TextInput from '@/components/Common/TextInput';
import TextURL from '@/components/Common/TextURL'; import TextURL from '@/components/Common/TextURL';
import Tooltip from '@/components/Common/Tooltip';
import ExpectedAnonymous from '@/components/ExpectedAnonymous'; import ExpectedAnonymous from '@/components/ExpectedAnonymous';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
import { useAuth } from '@/context/AuthContext'; import { useAuth } from '@/context/AuthContext';
@ -75,10 +75,10 @@ function RegisterPage() {
<Overlay id={globalIDs.password_tooltip} position='top-[4.8rem] left-[3.4rem] absolute'> <Overlay id={globalIDs.password_tooltip} position='top-[4.8rem] left-[3.4rem] absolute'>
<BiInfoCircle size='1.25rem' className='clr-text-primary' /> <BiInfoCircle size='1.25rem' className='clr-text-primary' />
</Overlay> </Overlay>
<ConceptTooltip anchorSelect={`#${globalIDs.password_tooltip}`} offset={6}> <Tooltip anchorSelect={`#${globalIDs.password_tooltip}`} offset={6}>
<p>- используйте уникальный пароль</p> <p>- используйте уникальный пароль</p>
<p>- портал функционирует в тестовом режиме</p> <p>- портал функционирует в тестовом режиме</p>
</ConceptTooltip> </Tooltip>
</div> </div>
<TextInput <TextInput

View File

@ -4,7 +4,7 @@ import { AnimatePresence } from 'framer-motion';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { FiBell, FiBellOff } from 'react-icons/fi'; import { FiBell, FiBellOff } from 'react-icons/fi';
import { ConceptLoader } from '@/components/Common/ConceptLoader'; import { Loader } from '@/components/Common/Loader';
import MiniButton from '@/components/Common/MiniButton'; import MiniButton from '@/components/Common/MiniButton';
import Overlay from '@/components/Common/Overlay'; import Overlay from '@/components/Common/Overlay';
import InfoError from '@/components/InfoError'; import InfoError from '@/components/InfoError';
@ -29,7 +29,7 @@ function UserTabs() {
return ( return (
<> <>
{loading ? <ConceptLoader /> : null} {loading ? <Loader /> : null}
{error ? <InfoError error={error} /> : null} {error ? <InfoError error={error} /> : null}
{user ? ( {user ? (
<div className='flex gap-6 py-2'> <div className='flex gap-6 py-2'>