mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 13:00:39 +03:00
Refactor navigation
This commit is contained in:
parent
895ad1554b
commit
b53fe27b1e
|
@ -3,6 +3,7 @@ import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
|
|||
import ConceptToaster from './components/ConceptToaster';
|
||||
import Footer from './components/Footer';
|
||||
import Navigation from './components/Navigation/Navigation';
|
||||
import { NavigationState } from './context/NagivationContext';
|
||||
import { useConceptTheme } from './context/ThemeContext';
|
||||
import CreateRSFormPage from './pages/CreateRSFormPage';
|
||||
import HomePage from './pages/HomePage';
|
||||
|
@ -14,25 +15,36 @@ 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 { noNavigation, noFooter, viewportHeight, mainHeight } = useConceptTheme();
|
||||
const { noNavigation, noFooter, viewportHeight, mainHeight, showScroll } = useConceptTheme();
|
||||
return (
|
||||
<NavigationState>
|
||||
<div className='w-screen antialiased clr-app'>
|
||||
<Navigation />
|
||||
|
||||
<ConceptToaster
|
||||
className='mt-[4rem] text-sm'
|
||||
autoClose={3000}
|
||||
draggable={false}
|
||||
pauseOnFocusLoss={false}
|
||||
/>
|
||||
<div className='w-full overflow-auto' style={{maxHeight: viewportHeight}}>
|
||||
<main className='w-full h-full' style={{minHeight: mainHeight}}>
|
||||
<Outlet />
|
||||
</main>
|
||||
{!noNavigation && !noFooter && <Footer />}
|
||||
|
||||
<Navigation />
|
||||
<div id={globalIDs.main_scroll}
|
||||
className='w-full overflow-x-auto'
|
||||
style={{
|
||||
maxHeight: viewportHeight,
|
||||
overflowY: showScroll ? 'scroll': 'auto'
|
||||
}}
|
||||
>
|
||||
<main className='w-full h-full' style={{minHeight: mainHeight}}>
|
||||
<Outlet />
|
||||
</main>
|
||||
{!noNavigation && !noFooter && <Footer />}
|
||||
</div>
|
||||
</div>
|
||||
</NavigationState>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,17 +6,17 @@ function HelpMain() {
|
|||
return (
|
||||
<div className='flex flex-col w-full'>
|
||||
<h1>Портал</h1>
|
||||
<p className='lg:text-justify'>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p>
|
||||
<p className='mt-2 lg:text-justify'>Навигация по порталу осуществляется верхнюю панель или ссылки в "подвале" страницы. Их можно скрыть с помощью кнопки в правом верхнем углу.</p>
|
||||
<p className='mt-2 lg:text-justify'>В меню пользователя (правый верхний угол) доступно редактирование данных пользователя и изменение цветовой темы.</p>
|
||||
<p className=''>Портал позволяет анализировать предметные области, формально записывать системы определений (концептуальные схемы) и синтезировать их с помощью математического аппарата родов структур.</p>
|
||||
<p className='mt-2'>Навигация по порталу осуществляется верхнюю панель или ссылки в "подвале" страницы. Их можно скрыть с помощью кнопки в правом верхнем углу.</p>
|
||||
<p className='mt-2'>В меню пользователя (правый верхний угол) доступно редактирование данных пользователя и изменение цветовой темы.</p>
|
||||
<p className='mt-4 mb-1 text-center'><b>Основные разделы</b></p>
|
||||
<li className='text-left'><TextURL text='Библиотека' href='/library' /> - все схемы доступные пользователю</li>
|
||||
<li className='text-left'><TextURL text='Общие схемы' href={`/library?filter=${LibraryFilterStrategy.COMMON}`} /> - общедоступные схемы и инструменты поиска и навигации по ним</li>
|
||||
<li className='text-left'><TextURL text='Мои схемы' href={`/library?filter=${LibraryFilterStrategy.PERSONAL}`} /> - отслеживаемые и редактируемые схемы. Основной рабочий раздел</li>
|
||||
<li className='text-left'><TextURL text='Профиль' href='/profile' /> - данные пользователя и смена пароля</li>
|
||||
<p className='mt-4 mb-1 text-center'><b>Поддержка</b></p>
|
||||
<p className='lg:text-justify'>Портал разрабатывается <TextURL text='Центром Концепт' href={urls.concept}/> и является проектом с открытым исходным кодом, доступным на <TextURL text='Github' href={urls.gitrepo}/>.</p>
|
||||
<p className='mt-2 lg:text-justify'>Ждём Ваши пожелания по доработке, найденные ошибки и иные предложения по адресу <TextURL href={urls.mailportal} text='portal@acconcept.ru'/></p>
|
||||
<p className=''>Портал разрабатывается <TextURL text='Центром Концепт' href={urls.concept}/> и является проектом с открытым исходным кодом, доступным на <TextURL text='Github' href={urls.gitrepo}/>.</p>
|
||||
<p className='mt-2'>Ждём Ваши пожелания по доработке, найденные ошибки и иные предложения по адресу <TextURL href={urls.mailportal} text='portal@acconcept.ru'/></p>
|
||||
<p></p>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { EducationIcon, LibraryIcon, PlusIcon } from '../Icons';
|
||||
import Logo from './Logo'
|
||||
|
@ -7,12 +6,12 @@ import NavigationButton from './NavigationButton';
|
|||
import UserMenu from './UserMenu';
|
||||
|
||||
function Navigation () {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const { noNavigation, toggleNoNavigation } = useConceptTheme();
|
||||
|
||||
const navigateLibrary = () => navigate('/library');
|
||||
const navigateHelp = () => navigate('/manuals');
|
||||
const navigateCreateNew = () => navigate('/rsform-create');
|
||||
const navigateLibrary = () => navigateTo('/library');
|
||||
const navigateHelp = () => navigateTo('/manuals');
|
||||
const navigateCreateNew = () => navigateTo('/rsform-create');
|
||||
|
||||
return (
|
||||
<nav className='sticky top-0 left-0 right-0 z-50 select-none h-fit'>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { LibraryFilterStrategy } from '../../utils/models';
|
||||
import Dropdown from '../Common/Dropdown';
|
||||
|
@ -12,23 +11,23 @@ interface UserDropdownProps {
|
|||
|
||||
function UserDropdown({ hideDropdown }: UserDropdownProps) {
|
||||
const { darkMode, toggleDarkMode } = useConceptTheme();
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const { user, logout } = useAuth();
|
||||
|
||||
const navigateProfile = () => {
|
||||
hideDropdown();
|
||||
navigate('/profile');
|
||||
navigateTo('/profile');
|
||||
};
|
||||
|
||||
const logoutAndRedirect =
|
||||
() => {
|
||||
hideDropdown();
|
||||
logout(() => navigate('/login/'));
|
||||
logout(() => navigateTo('/login/'));
|
||||
};
|
||||
|
||||
const navigateMyWork = () => {
|
||||
hideDropdown();
|
||||
navigate(`/library?filter=${LibraryFilterStrategy.PERSONAL}`);
|
||||
navigateTo(`/library?filter=${LibraryFilterStrategy.PERSONAL}`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import useDropdown from '../../hooks/useDropdown';
|
||||
import { InDoor, UserIcon } from '../Icons';
|
||||
import NavigationButton from './NavigationButton';
|
||||
import UserDropdown from './UserDropdown';
|
||||
|
||||
function UserMenu() {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
const menu = useDropdown();
|
||||
|
||||
const navigateLogin = () => navigate('/login');
|
||||
const navigateLogin = () => navigateTo('/login');
|
||||
return (
|
||||
<div ref={menu.ref} className='h-full'>
|
||||
<div className='flex items-center justify-end h-full w-fit'>
|
||||
|
|
60
rsconcept/frontend/src/context/NagivationContext.tsx
Normal file
60
rsconcept/frontend/src/context/NagivationContext.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { createContext, useCallback, useContext, useEffect } from 'react';
|
||||
import { NavigateOptions, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { globalIDs } from '../utils/constants';
|
||||
|
||||
interface INagivationContext{
|
||||
navigateTo: (path: string, options?: NavigateOptions) => void
|
||||
navigateHistory: (offset: number) => void
|
||||
}
|
||||
|
||||
const NagivationContext = createContext<INagivationContext | null>(null);
|
||||
export const useConceptNavigation = () => {
|
||||
const context = useContext(NagivationContext);
|
||||
if (!context) {
|
||||
throw new Error('useConceptNavigation has to be used within <NavigationState.Provider>');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
interface NavigationStateProps {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export const NavigationState = ({ children }: NavigationStateProps) => {
|
||||
const implNavigate = useNavigate();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
function scrollTop() {
|
||||
window.scrollTo(0, 0);
|
||||
const mainScroll = document.getElementById(globalIDs.main_scroll);
|
||||
if (mainScroll) {
|
||||
mainScroll.scroll(0,0);
|
||||
}
|
||||
}
|
||||
|
||||
const navigateTo = useCallback(
|
||||
(path: string, options?: NavigateOptions) => {
|
||||
scrollTop();
|
||||
implNavigate(path, options);
|
||||
}, [implNavigate]);
|
||||
|
||||
const navigateHistory = useCallback(
|
||||
(offset: number) => {
|
||||
scrollTop();
|
||||
implNavigate(offset);
|
||||
}, [implNavigate]);
|
||||
|
||||
useEffect(() => {
|
||||
scrollTop();
|
||||
}, [pathname]);
|
||||
|
||||
|
||||
return (
|
||||
<NagivationContext.Provider value={{
|
||||
navigateTo, navigateHistory
|
||||
}}>
|
||||
{children}
|
||||
</NagivationContext.Provider>
|
||||
);
|
||||
}
|
|
@ -13,9 +13,13 @@ interface IThemeContext {
|
|||
toggleDarkMode: () => void
|
||||
|
||||
noNavigation: boolean
|
||||
toggleNoNavigation: () => void
|
||||
|
||||
noFooter: boolean
|
||||
setNoFooter: (value: boolean) => void
|
||||
toggleNoNavigation: () => void
|
||||
|
||||
showScroll: boolean
|
||||
setShowScroll: (value: boolean) => void
|
||||
}
|
||||
|
||||
const ThemeContext = createContext<IThemeContext | null>(null);
|
||||
|
@ -36,6 +40,7 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
|||
const [colors, setColors] = useState<IColorTheme>(lightT);
|
||||
const [noNavigation, setNoNavigation] = useState(false);
|
||||
const [noFooter, setNoFooter] = useState(false);
|
||||
const [showScroll, setShowScroll] = useState(false);
|
||||
|
||||
function setDarkClass(isDark: boolean) {
|
||||
const root = window.document.documentElement;
|
||||
|
@ -72,10 +77,10 @@ export const ThemeState = ({ children }: ThemeStateProps) => {
|
|||
return (
|
||||
<ThemeContext.Provider value={{
|
||||
darkMode, colors,
|
||||
noNavigation, noFooter,
|
||||
noNavigation, noFooter, showScroll,
|
||||
toggleDarkMode: () => setDarkMode(prev => !prev),
|
||||
toggleNoNavigation: () => setNoNavigation(prev => !prev),
|
||||
setNoFooter,
|
||||
setNoFooter, setShowScroll,
|
||||
viewportHeight, mainHeight
|
||||
}}>
|
||||
{children}
|
||||
|
|
|
@ -153,7 +153,7 @@
|
|||
}
|
||||
|
||||
:is(.clr-selected,
|
||||
.clr-btn-primary,
|
||||
.clr-btn-primary
|
||||
) {
|
||||
color: var(--cl-fg-100);
|
||||
background-color: var(--cl-prim-bg-80);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError from '../components/BackendError';
|
||||
|
@ -12,11 +12,12 @@ import TextArea from '../components/Common/TextArea';
|
|||
import TextInput from '../components/Common/TextInput';
|
||||
import RequireAuth from '../components/RequireAuth';
|
||||
import { useLibrary } from '../context/LibraryContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { IRSFormCreateData, LibraryItemType } from '../utils/models';
|
||||
|
||||
function CreateRSFormPage() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo, navigateHistory } = useConceptNavigation();
|
||||
const { createSchema, error, setError, processing } = useLibrary();
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
|
@ -39,9 +40,9 @@ function CreateRSFormPage() {
|
|||
|
||||
function handleCancel() {
|
||||
if (location.key !== 'default') {
|
||||
navigate(-1);
|
||||
navigateHistory(-1);
|
||||
} else {
|
||||
navigate('/library');
|
||||
navigateTo('/library');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +63,7 @@ function CreateRSFormPage() {
|
|||
};
|
||||
createSchema(data, (newSchema) => {
|
||||
toast.success('Схема успешно создана');
|
||||
navigate(`/rsforms/${newSchema.id}`);
|
||||
navigateTo(`/rsforms/${newSchema.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { useLayoutEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { TIMEOUT_UI_REFRESH } from '../utils/constants';
|
||||
|
||||
function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const { user } = useAuth();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!user) {
|
||||
setTimeout(() => {
|
||||
navigate('/manuals');
|
||||
navigateTo('/manuals');
|
||||
}, TIMEOUT_UI_REFRESH);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
navigate('/library');
|
||||
navigateTo('/library');
|
||||
}, TIMEOUT_UI_REFRESH);
|
||||
}
|
||||
}, [navigate, user])
|
||||
}, [navigateTo, user])
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center w-full px-4 py-2'>
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { MagnifyingGlassIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import useLocalStorage from '../../hooks/useLocalStorage';
|
||||
import { ILibraryFilter, LibraryFilterStrategy } from '../../utils/models';
|
||||
import PickerStrategy from './PickerStrategy';
|
||||
|
@ -26,7 +27,7 @@ interface SearchPanelProps {
|
|||
}
|
||||
|
||||
function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const search = useLocation().search;
|
||||
const { user } = useAuth();
|
||||
|
||||
|
@ -51,29 +52,29 @@ function SearchPanel({ total, filtered, setFilter }: SearchPanelProps) {
|
|||
useLayoutEffect(() => {
|
||||
const searchFilter = new URLSearchParams(search).get('filter') as LibraryFilterStrategy | null;
|
||||
if (searchFilter === null) {
|
||||
navigate(`/library?filter=${strategy}`, { replace: true });
|
||||
navigateTo(`/library?filter=${strategy}`, { replace: true });
|
||||
return;
|
||||
}
|
||||
const inputStrategy = searchFilter && Object.values(LibraryFilterStrategy).includes(searchFilter) ? searchFilter : LibraryFilterStrategy.MANUAL;
|
||||
setQuery('')
|
||||
setStrategy(inputStrategy)
|
||||
setFilter(ApplyStrategy(inputStrategy));
|
||||
}, [user, search, setQuery, setFilter, setStrategy, strategy, navigate]);
|
||||
}, [user, search, setQuery, setFilter, setStrategy, strategy, navigateTo]);
|
||||
|
||||
const handleChangeStrategy = useCallback(
|
||||
(value: LibraryFilterStrategy) => {
|
||||
if (value === strategy) {
|
||||
return;
|
||||
}
|
||||
navigate(`/library?filter=${value}`)
|
||||
}, [strategy, navigate]);
|
||||
navigateTo(`/library?filter=${value}`)
|
||||
}, [strategy, navigateTo]);
|
||||
|
||||
return (
|
||||
<div className='sticky top-0 left-0 right-0 z-30 flex items-center justify-start w-full border-b clr-input'>
|
||||
<div className='px-2 py-1 select-none whitespace-nowrap min-w-[10rem]'>
|
||||
Фильтр
|
||||
<span className='ml-2'>
|
||||
<b>{filtered}</b> из {total}
|
||||
{filtered} из {total}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-center w-full pr-[10rem]'>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ConceptDataTable from '../../components/Common/ConceptDataTable';
|
||||
import ConceptTooltip from '../../components/Common/ConceptTooltip';
|
||||
|
@ -8,6 +7,7 @@ import TextURL from '../../components/Common/TextURL';
|
|||
import HelpLibrary from '../../components/Help/HelpLibrary';
|
||||
import { EducationIcon, EyeIcon, GroupIcon, HelpIcon } from '../../components/Icons';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useUsers } from '../../context/UsersContext';
|
||||
import { prefixes } from '../../utils/constants';
|
||||
import { ILibraryItem } from '../../utils/models';
|
||||
|
@ -18,12 +18,12 @@ interface ViewLibraryProps {
|
|||
}
|
||||
|
||||
function ViewLibrary({ items, cleanQuery }: ViewLibraryProps) {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const intl = useIntl();
|
||||
const { user } = useAuth();
|
||||
const { getUserLabel } = useUsers();
|
||||
|
||||
const openRSForm = (item: ILibraryItem) => navigate(`/rsforms/${item.id}`);
|
||||
const openRSForm = (item: ILibraryItem) => navigateTo(`/rsforms/${item.id}`);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
|
|
|
@ -3,16 +3,24 @@ import { useLayoutEffect, useState } from 'react';
|
|||
import BackendError from '../../components/BackendError'
|
||||
import { ConceptLoader } from '../../components/Common/ConceptLoader'
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { ILibraryFilter, ILibraryItem } from '../../utils/models';
|
||||
import SearchPanel from './SearchPanel';
|
||||
import ViewLibrary from './ViewLibrary';
|
||||
|
||||
function LibraryPage() {
|
||||
const library = useLibrary();
|
||||
const { setShowScroll } = useConceptTheme();
|
||||
|
||||
const [ filter, setFilter ] = useState<ILibraryFilter>({});
|
||||
const [ items, setItems ] = useState<ILibraryItem[]>([]);
|
||||
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setShowScroll(true);
|
||||
return () => setShowScroll(false);
|
||||
}, [setShowScroll]);
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
setItems(library.filter(filter));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import axios from 'axios';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import BackendError, { ErrorInfo } from '../components/BackendError';
|
||||
import Form from '../components/Common/Form';
|
||||
|
@ -8,6 +8,7 @@ import SubmitButton from '../components/Common/SubmitButton';
|
|||
import TextInput from '../components/Common/TextInput';
|
||||
import TextURL from '../components/Common/TextURL';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { useConceptTheme } from '../context/ThemeContext';
|
||||
import { IUserLoginData } from '../utils/models';
|
||||
|
||||
|
@ -26,7 +27,7 @@ function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
|
|||
function LoginPage() {
|
||||
const {mainHeight} = useConceptTheme();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo, navigateHistory } = useConceptNavigation();
|
||||
const search = useLocation().search;
|
||||
const { user, login, loading, error, setError } = useAuth();
|
||||
|
||||
|
@ -52,9 +53,9 @@ function LoginPage() {
|
|||
};
|
||||
login(data, () => {
|
||||
if (location.key !== 'default') {
|
||||
navigate(-1);
|
||||
navigateHistory(-1);
|
||||
} else {
|
||||
navigate('/library');
|
||||
navigateTo('/library');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,36 +1,37 @@
|
|||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import { HelpTopic } from '../../utils/models';
|
||||
import TopicsList from './TopicsList';
|
||||
import ViewTopic from './ViewTopic';
|
||||
|
||||
function ManualsPage() {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const search = useLocation().search;
|
||||
const { mainHeight } = useConceptTheme();
|
||||
const [activeTopic, setActiveTopic] = useState<HelpTopic>(HelpTopic.MAIN);
|
||||
|
||||
const navigateTo = useCallback(
|
||||
const navigateTopic = useCallback(
|
||||
(newTopic: HelpTopic) => {
|
||||
navigate(`/manuals?topic=${newTopic}`);
|
||||
}, [navigate]);
|
||||
navigateTo(`/manuals?topic=${newTopic}`);
|
||||
}, [navigateTo]);
|
||||
|
||||
|
||||
function onSelectTopic(newTopic: HelpTopic) {
|
||||
navigateTo(newTopic);
|
||||
navigateTopic(newTopic);
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const topic = new URLSearchParams(search).get('topic') as HelpTopic;
|
||||
if (!Object.values(HelpTopic).includes(topic)) {
|
||||
navigateTo(HelpTopic.MAIN);
|
||||
navigateTopic(HelpTopic.MAIN);
|
||||
return;
|
||||
} else {
|
||||
setActiveTopic(topic);
|
||||
}
|
||||
}, [search, setActiveTopic, navigateTo]);
|
||||
}, [search, setActiveTopic, navigateTopic]);
|
||||
|
||||
return (
|
||||
<div className='flex w-full gap-2 justify-stretch' style={{minHeight: mainHeight}}>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import Checkbox from '../../components/Common/Checkbox';
|
||||
|
@ -7,6 +6,7 @@ import Modal from '../../components/Common/Modal';
|
|||
import TextArea from '../../components/Common/TextArea';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { IRSFormCreateData } from '../../utils/models';
|
||||
import { getCloneTitle } from '../../utils/staticUI';
|
||||
|
@ -16,7 +16,7 @@ interface DlgCloneRSFormProps {
|
|||
}
|
||||
|
||||
function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const [title, setTitle] = useState('');
|
||||
const [alias, setAlias] = useState('');
|
||||
const [comment, setComment] = useState('');
|
||||
|
@ -50,7 +50,7 @@ function DlgCloneRSForm({ hideWindow }: DlgCloneRSFormProps) {
|
|||
};
|
||||
cloneSchema(schema.id, data, newSchema => {
|
||||
toast.success(`Схема создана: ${newSchema.alias}`);
|
||||
navigate(`/rsforms/${newSchema.id}`);
|
||||
navigateTo(`/rsforms/${newSchema.id}`);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -264,7 +264,7 @@ function EditorItems({ onOpenEdit, onCreateCst, onDeleteCst }: EditorItemsProps)
|
|||
<div className='mr-3 whitespace-nowrap'>
|
||||
Выбраны
|
||||
<span className='ml-2'>
|
||||
<b>{selected.length}</b> из {schema?.stats?.count_all ?? 0}
|
||||
{selected.length} из {schema?.stats?.count_all ?? 0}
|
||||
</span>
|
||||
</div>
|
||||
<div className='flex items-center justify-start w-full gap-1'>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import axios from 'axios';
|
||||
import fileDownload from 'js-file-download';
|
||||
import { useCallback, useLayoutEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { TabList, TabPanel, Tabs } from 'react-tabs';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { ConceptLoader } from '../../components/Common/ConceptLoader';
|
|||
import ConceptTab from '../../components/Common/ConceptTab';
|
||||
import TextURL from '../../components/Common/TextURL';
|
||||
import { useLibrary } from '../../context/LibraryContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { useRSForm } from '../../context/RSFormContext';
|
||||
import { useConceptTheme } from '../../context/ThemeContext';
|
||||
import useModificationPrompt from '../../hooks/useModificationPrompt';
|
||||
|
@ -50,7 +51,7 @@ function ProcessError({error}: {error: ErrorInfo}): React.ReactElement {
|
|||
}
|
||||
|
||||
function RSTabs() {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const search = useLocation().search;
|
||||
const {
|
||||
error, schema, loading, claim, download, isTracking,
|
||||
|
@ -101,25 +102,25 @@ function RSTabs() {
|
|||
}, [search, setActiveTab, setActiveID, schema, setNoFooter]);
|
||||
|
||||
function onSelectTab(index: number) {
|
||||
navigateTo(index, activeID);
|
||||
navigateTab(index, activeID);
|
||||
}
|
||||
|
||||
const navigateTo = useCallback(
|
||||
const navigateTab = useCallback(
|
||||
(tab: RSTabID, activeID?: number) => {
|
||||
if (!schema) {
|
||||
return;
|
||||
}
|
||||
if (activeID) {
|
||||
navigate(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`, {
|
||||
navigateTo(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`, {
|
||||
replace: tab === activeTab && tab !== RSTabID.CST_EDIT
|
||||
});
|
||||
} else if (tab !== activeTab && tab === RSTabID.CST_EDIT && schema.items.length > 0) {
|
||||
activeID = schema.items[0].id;
|
||||
navigate(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`, { replace: true });
|
||||
navigateTo(`/rsforms/${schema.id}?tab=${tab}&active=${activeID}`, { replace: true });
|
||||
} else {
|
||||
navigate(`/rsforms/${schema.id}?tab=${tab}`);
|
||||
navigateTo(`/rsforms/${schema.id}?tab=${tab}`);
|
||||
}
|
||||
}, [navigate, schema, activeTab]);
|
||||
}, [navigateTo, schema, activeTab]);
|
||||
|
||||
const handleCreateCst = useCallback(
|
||||
(data: ICstCreateData) => {
|
||||
|
@ -129,7 +130,7 @@ function RSTabs() {
|
|||
data.alias = createAliasFor(data.cst_type, schema);
|
||||
cstCreate(data, newCst => {
|
||||
toast.success(`Конституента добавлена: ${newCst.alias}`);
|
||||
navigateTo(activeTab, newCst.id);
|
||||
navigateTab(activeTab, newCst.id);
|
||||
if (activeTab === RSTabID.CST_EDIT || activeTab === RSTabID.CST_LIST) {
|
||||
setTimeout(() => {
|
||||
const element = document.getElementById(`${prefixes.cst_list}${newCst.alias}`);
|
||||
|
@ -143,7 +144,7 @@ function RSTabs() {
|
|||
}, TIMEOUT_UI_REFRESH);
|
||||
}
|
||||
});
|
||||
}, [schema, cstCreate, navigateTo, activeTab]);
|
||||
}, [schema, cstCreate, navigateTab, activeTab]);
|
||||
|
||||
const promptCreateCst = useCallback(
|
||||
(initialData: ICstCreateData, skipDialog?: boolean) => {
|
||||
|
@ -181,7 +182,7 @@ function RSTabs() {
|
|||
const deletedNames = deleted.map(id => schema.items.find(cst => cst.id === id)?.alias).join(', ');
|
||||
toast.success(`Конституенты удалены: ${deletedNames}`);
|
||||
if (deleted.length === schema.items.length) {
|
||||
navigateTo(RSTabID.CST_LIST);
|
||||
navigateTab(RSTabID.CST_LIST);
|
||||
}
|
||||
if (activeIndex) {
|
||||
while (activeIndex < schema.items.length && deleted.find(id => id === schema.items[activeIndex].id)) {
|
||||
|
@ -193,11 +194,11 @@ function RSTabs() {
|
|||
--activeIndex;
|
||||
}
|
||||
}
|
||||
navigateTo(activeTab, schema.items[activeIndex].id);
|
||||
navigateTab(activeTab, schema.items[activeIndex].id);
|
||||
}
|
||||
if (afterDelete) afterDelete(deleted);
|
||||
});
|
||||
}, [afterDelete, cstDelete, schema, activeID, activeTab, navigateTo]);
|
||||
}, [afterDelete, cstDelete, schema, activeID, activeTab, navigateTab]);
|
||||
|
||||
const promptDeleteCst = useCallback(
|
||||
(selected: number[], callback?: (items: number[]) => void) => {
|
||||
|
@ -218,8 +219,8 @@ function RSTabs() {
|
|||
|
||||
const onOpenCst = useCallback(
|
||||
(cstID: number) => {
|
||||
navigateTo(RSTabID.CST_EDIT, cstID)
|
||||
}, [navigateTo]);
|
||||
navigateTab(RSTabID.CST_EDIT, cstID)
|
||||
}, [navigateTab]);
|
||||
|
||||
const onDestroySchema = useCallback(
|
||||
() => {
|
||||
|
@ -228,9 +229,9 @@ function RSTabs() {
|
|||
}
|
||||
destroySchema(schema.id, () => {
|
||||
toast.success('Схема удалена');
|
||||
navigate('/library');
|
||||
navigateTo('/library');
|
||||
});
|
||||
}, [schema, destroySchema, navigate]);
|
||||
}, [schema, destroySchema, navigateTo]);
|
||||
|
||||
const onClaimSchema = useCallback(
|
||||
() => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError from '../components/BackendError';
|
||||
|
@ -8,11 +8,12 @@ import Form from '../components/Common/Form';
|
|||
import SubmitButton from '../components/Common/SubmitButton';
|
||||
import TextInput from '../components/Common/TextInput';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { useConceptNavigation } from '../context/NagivationContext';
|
||||
import { type IUserSignupData } from '../utils/models';
|
||||
|
||||
function RegisterPage() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo, navigateHistory } = useConceptNavigation();
|
||||
const { user, signup, loading, error, setError } = useAuth();
|
||||
|
||||
const [username, setUsername] = useState('');
|
||||
|
@ -28,9 +29,9 @@ function RegisterPage() {
|
|||
|
||||
function handleCancel() {
|
||||
if (location.key !== 'default') {
|
||||
navigate(-1);
|
||||
navigateHistory(-1);
|
||||
} else {
|
||||
navigate('/library');
|
||||
navigateTo('/library');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,7 @@ function RegisterPage() {
|
|||
last_name: lastName
|
||||
};
|
||||
signup(data, createdUser => {
|
||||
navigate(`/login?username=${createdUser.username}`);
|
||||
navigateTo(`/login?username=${createdUser.username}`);
|
||||
toast.success(`Пользователь успешно создан: ${createdUser.username}`);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
import BackendError from '../../components/BackendError';
|
||||
import SubmitButton from '../../components/Common/SubmitButton';
|
||||
import TextInput from '../../components/Common/TextInput';
|
||||
import { useAuth } from '../../context/AuthContext';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { IUserUpdatePassword } from '../../utils/models';
|
||||
|
||||
|
||||
function EditorPassword() {
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const { updatePassword, error, setError, loading } = useAuth();
|
||||
|
||||
const [oldPassword, setOldPassword] = useState('');
|
||||
const [newPassword, setNewPassword] = useState('');
|
||||
const [newPasswordRepeat, setNewPasswordRepeat] = useState('');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const passwordColor = useMemo(
|
||||
() => {
|
||||
|
@ -39,7 +39,7 @@ function EditorPassword() {
|
|||
};
|
||||
updatePassword(data, () => {
|
||||
toast.success('Изменения сохранены');
|
||||
navigate('/login')
|
||||
navigateTo('/login');
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useMemo } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ConceptDataTable from '../../components/Common/ConceptDataTable';
|
||||
import { useConceptNavigation } from '../../context/NagivationContext';
|
||||
import { ILibraryItem } from '../../utils/models';
|
||||
|
||||
interface ViewSubscriptionsProps {
|
||||
|
@ -10,10 +10,10 @@ interface ViewSubscriptionsProps {
|
|||
}
|
||||
|
||||
function ViewSubscriptions({items}: ViewSubscriptionsProps) {
|
||||
const navigate = useNavigate();
|
||||
const { navigateTo } = useConceptNavigation();
|
||||
const intl = useIntl();
|
||||
|
||||
const openRSForm = (item: ILibraryItem) => navigate(`/rsforms/${item.id}`);
|
||||
const openRSForm = (item: ILibraryItem) => navigateTo(`/rsforms/${item.id}`);
|
||||
|
||||
const columns = useMemo(() =>
|
||||
[
|
||||
|
|
|
@ -25,6 +25,10 @@ export const resources = {
|
|||
graph_font: '/DejaVu.ttf'
|
||||
}
|
||||
|
||||
export const globalIDs = {
|
||||
main_scroll: 'main-scroll'
|
||||
}
|
||||
|
||||
export const prefixes = {
|
||||
cst_list: 'cst-list-',
|
||||
cst_status_list: 'cst-status-list-',
|
||||
|
|
Loading…
Reference in New Issue
Block a user