mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
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
This commit is contained in:
parent
f78a6568a6
commit
84611a90a1
118 changed files with 2284 additions and 1648 deletions
158
app/react/sidebar/useSidebarState.tsx
Normal file
158
app/react/sidebar/useSidebarState.tsx
Normal file
|
@ -0,0 +1,158 @@
|
|||
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;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue