mirror of
https://github.com/IRBorisov/ConceptPortal.git
synced 2025-06-26 21:10:38 +03:00
123 lines
3.3 KiB
TypeScript
123 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import { createContext, use, useEffect, useState } from 'react';
|
|
import { useNavigate } from 'react-router';
|
|
|
|
export interface NavigationProps {
|
|
path: string;
|
|
newTab?: boolean;
|
|
force?: boolean;
|
|
}
|
|
|
|
interface INavigationContext {
|
|
push: (props: NavigationProps) => void;
|
|
pushAsync: (props: NavigationProps) => void | Promise<void>;
|
|
replace: (props: Omit<NavigationProps, 'newTab'>) => void;
|
|
replaceAsync: (props: Omit<NavigationProps, 'newTab'>) => void | Promise<void>;
|
|
back: (force?: boolean) => void;
|
|
forward: (force?: boolean) => void;
|
|
|
|
canBack: () => boolean;
|
|
|
|
isBlocked: boolean;
|
|
setIsBlocked: (value: boolean) => void;
|
|
}
|
|
|
|
export const NavigationContext = createContext<INavigationContext | null>(null);
|
|
export const useConceptNavigation = () => {
|
|
const context = use(NavigationContext);
|
|
if (!context) {
|
|
throw new Error('useConceptNavigation has to be used within <NavigationState>');
|
|
}
|
|
return context;
|
|
};
|
|
|
|
export const NavigationState = ({ children }: React.PropsWithChildren) => {
|
|
const router = useNavigate();
|
|
|
|
const [isBlocked, setIsBlocked] = useState(false);
|
|
const [internalNavigation, setInternalNavigation] = useState(false);
|
|
|
|
function validate() {
|
|
return !isBlocked || confirm('Изменения не сохранены. Вы уверены что хотите совершить переход?');
|
|
}
|
|
|
|
function canBack() {
|
|
return internalNavigation && !!window.history && window.history?.length !== 0;
|
|
}
|
|
|
|
function push(props: NavigationProps) {
|
|
if (props.newTab) {
|
|
window.open(`${props.path}`, '_blank');
|
|
} else if (props.force || validate()) {
|
|
setIsBlocked(false);
|
|
setInternalNavigation(true);
|
|
Promise.resolve(router(props.path, { viewTransition: true })).catch(console.error);
|
|
}
|
|
}
|
|
|
|
function pushAsync(props: NavigationProps): void | Promise<void> {
|
|
if (props.newTab) {
|
|
window.open(`${props.path}`, '_blank');
|
|
} else if (props.force || validate()) {
|
|
setIsBlocked(false);
|
|
setInternalNavigation(true);
|
|
return router(props.path, { viewTransition: true });
|
|
}
|
|
}
|
|
|
|
function replace(props: Omit<NavigationProps, 'newTab'>) {
|
|
if (props.force || validate()) {
|
|
setIsBlocked(false);
|
|
Promise.resolve(router(props.path, { replace: true, viewTransition: true })).catch(console.error);
|
|
}
|
|
}
|
|
|
|
function replaceAsync(props: Omit<NavigationProps, 'newTab'>): void | Promise<void> {
|
|
if (props.force || validate()) {
|
|
setIsBlocked(false);
|
|
return router(props.path, { replace: true, viewTransition: true });
|
|
}
|
|
}
|
|
|
|
function back(force?: boolean) {
|
|
if (force || validate()) {
|
|
Promise.resolve(router(-1)).catch(console.error);
|
|
setIsBlocked(false);
|
|
}
|
|
}
|
|
|
|
function forward(force?: boolean) {
|
|
if (force || validate()) {
|
|
Promise.resolve(router(1)).catch(console.error);
|
|
setIsBlocked(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<NavigationContext
|
|
value={{
|
|
push,
|
|
pushAsync,
|
|
replace,
|
|
replaceAsync,
|
|
back,
|
|
forward,
|
|
canBack,
|
|
isBlocked,
|
|
setIsBlocked
|
|
}}
|
|
>
|
|
{children}
|
|
</NavigationContext>
|
|
);
|
|
};
|
|
|
|
export function useBlockNavigation(isBlocked: boolean) {
|
|
const router = useConceptNavigation();
|
|
useEffect(() => {
|
|
router.setIsBlocked(isBlocked);
|
|
return () => router.setIsBlocked(false);
|
|
}, [router, isBlocked]);
|
|
}
|