1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-23 15:29:42 +02:00
portainer/app/react/sidebar/useSidebarState.tsx
Chaim Lev-Ari 84611a90a1
refactor(sidebar): migrate sidebar to react [EE-2907] (#6725)
* refactor(sidebar): migrate sidebar to react [EE-2907]

fixes [EE-2907]

feat(sidebar): show label for help

fix(sidebar): apply changes from ddExtension

fix(sidebar): resolve conflicts

style(ts): add explanation for ddExtension

fix(sidebar): use enum for status

refactor(sidebar): rename to EdgeComputeSidebar

refactor(sidebar): removed the need of `ident` prop

style(sidebar): add ref for mobile breakpoint

refactor(app): document testing props

refactor(sidebar): use single sidebar item

refactor(sidebar): use section for nav

refactor(sidebar): rename sidebarlink to link

refactor(sidebar): memoize menu paths

fix(kubectl-shell): infinite loop on hooks dependencies

refactor(sidebar): use authorized element

feat(k8s/shell): track open shell

refactor(k8s/shell): remove memoization

refactor(settings): move settings queries to queries

fix(sidebar): close sidebar on mobile

refactor(settings): use mutation helpers

refactor(sidebar): remove memo

refactor(sidebar): rename sidebar item for storybook

refactor(sidebar): move to react

gprefactor(sidebar): remove dependence on EndProvider

feat(environments): rename settings type

feat(kube): move kubeconfig button

fix(sidebar): open submenus

fix(sidebar): open on expand

fix(sibebar): show kube shell correctly

* fix(sidebar): import from react component

* chore(tests): fix missing prop
2022-06-23 10:25:56 +03:00

158 lines
3.2 KiB
TypeScript

import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import angular, { IScope } from 'angular';
import _ from 'lodash';
import * as storage from '@/portainer/hooks/useLocalStorage';
// using bootstrap breakpoint - https://getbootstrap.com/docs/5.0/layout/breakpoints/#min-width
const mobileWidth = 992;
const storageKey = 'toolbar_toggle';
interface State {
isOpen: boolean;
toggle(): void;
}
const Context = createContext<State | null>(null);
export function useSidebarState() {
const context = useContext(Context);
if (!context) {
throw new Error('useSidebarContext must be used within a SidebarProvider');
}
return context;
}
export function SidebarProvider({ children }: { children: React.ReactNode }) {
const state = useSidebarStateLocal();
return <Context.Provider value={state}> {children} </Context.Provider>;
}
/* @ngInject */
export function AngularSidebarService($rootScope: IScope) {
const state = {
isOpen: false,
};
function isSidebarOpen() {
return state.isOpen;
}
function setIsOpen(isOpen: boolean) {
$rootScope.$evalAsync(() => {
state.isOpen = isOpen;
});
}
return { isSidebarOpen, setIsOpen };
}
function useSidebarStateLocal() {
const [storageIsOpen, setIsOpenInStorage] = storage.useLocalStorage(
storageKey,
true
);
const [isOpen, setIsOpen, undoIsOpenChange] = useStateWithUndo(
initialState()
);
const onResize = useMemo(
() =>
_.debounce(() => {
if (isMobile()) {
if (isOpen) {
setIsOpen(false);
}
} else {
undoIsOpenChange();
}
}, 50),
[setIsOpen, undoIsOpenChange, isOpen]
);
useUpdateAngularService(isOpen);
useEffect(() => {
if (window.ddExtension) {
return undefined;
}
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, [onResize]);
return {
isOpen,
toggle,
};
function toggle(value = !isOpen) {
setIsOpenInStorage(value);
setIsOpen(value);
}
function initialState() {
if (isMobile() || window.ddExtension) {
return false;
}
return storageIsOpen;
}
}
function useStateWithUndo<T>(
initialState: T
): [T, (value: T) => void, () => void] {
const [state, setState] = useState(initialState);
const prevState = useRef<T>();
const undo = useCallback(() => {
if (!prevState.current) {
return;
}
setState(prevState.current);
prevState.current = undefined;
}, [prevState]);
const handleSetState = useCallback(
(newState: T) => {
prevState.current = state;
setState(newState);
},
[state]
);
return [state, handleSetState, undo];
}
function useUpdateAngularService(isOpen: boolean) {
useEffect(() => {
// to sync "outside state" - for angularjs
const $injector = angular.element(document).injector();
$injector.invoke(
/* @ngInject */ (
SidebarService: ReturnType<typeof AngularSidebarService>
) => {
SidebarService.setIsOpen(isOpen);
}
);
}, [isOpen]);
}
function isMobile() {
const width = window.innerWidth;
return width < mobileWidth;
}