import { css, cx } from '@emotion/css'; import classNames from 'classnames'; import { PropsWithChildren, useEffect } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { locationSearchToObject, locationService, useScopes } from '@grafana/runtime'; import { LinkButton, useStyles2, useTheme2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import { Trans } from 'app/core/internationalization'; import store from 'app/core/store'; import { CommandPalette } from 'app/features/commandPalette/CommandPalette'; import { ScopesDashboards } from 'app/features/scopes/dashboards/ScopesDashboards'; import { AppChromeMenu } from './AppChromeMenu'; import { DOCKED_LOCAL_STORAGE_KEY, DOCKED_MENU_OPEN_LOCAL_STORAGE_KEY } from './AppChromeService'; import { EXTENSION_SIDEBAR_WIDTH, ExtensionSidebar } from './ExtensionSidebar/ExtensionSidebar'; import { useExtensionSidebarContext } from './ExtensionSidebar/ExtensionSidebarProvider'; import { MegaMenu, MENU_WIDTH } from './MegaMenu/MegaMenu'; import { useMegaMenuFocusHelper } from './MegaMenu/utils'; import { ReturnToPrevious } from './ReturnToPrevious/ReturnToPrevious'; import { SingleTopBar } from './TopBar/SingleTopBar'; import { SingleTopBarActions } from './TopBar/SingleTopBarActions'; import { TOP_BAR_LEVEL_HEIGHT } from './types'; export interface Props extends PropsWithChildren<{}> {} export function AppChrome({ children }: Props) { const { chrome } = useGrafana(); const { isOpen: isExtensionSidebarOpen, isEnabled: isExtensionSidebarEnabled } = useExtensionSidebarContext(); const state = chrome.useState(); const theme = useTheme2(); const scopes = useScopes(); const dockedMenuBreakpoint = theme.breakpoints.values.xl; const dockedMenuLocalStorageState = store.getBool(DOCKED_LOCAL_STORAGE_KEY, true); const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen; const isScopesDashboardsOpen = Boolean( scopes?.state.enabled && scopes?.state.drawerOpened && !scopes?.state.readOnly ); const styles = useStyles2(getStyles, Boolean(state.actions) || !!scopes?.state.enabled); useMediaQueryChange({ breakpoint: dockedMenuBreakpoint, onChange: (e) => { if (dockedMenuLocalStorageState) { chrome.setMegaMenuDocked(e.matches, false); chrome.setMegaMenuOpen( e.matches ? store.getBool(DOCKED_MENU_OPEN_LOCAL_STORAGE_KEY, state.megaMenuOpen) : false ); } }, }); useMegaMenuFocusHelper(state.megaMenuOpen, state.megaMenuDocked); const contentClass = cx({ [styles.content]: true, [styles.contentChromeless]: state.chromeless, [styles.contentWithSidebar]: isExtensionSidebarOpen && !state.chromeless, }); const handleMegaMenu = () => { chrome.setMegaMenuOpen(!state.megaMenuOpen); }; const { pathname, search } = locationService.getLocation(); const url = pathname + search; const shouldShowReturnToPrevious = state.returnToPrevious && url !== state.returnToPrevious.href; // Clear returnToPrevious when the page is manually navigated to useEffect(() => { if (state.returnToPrevious && url === state.returnToPrevious.href) { chrome.clearReturnToPrevious('auto_dismissed'); } // We only want to pay attention when the location changes // eslint-disable-next-line react-hooks/exhaustive-deps }, [chrome, url]); // Sync updates from kiosk mode query string back into app chrome useEffect(() => { const queryParams = locationSearchToObject(search); chrome.setKioskModeFromUrl(queryParams.kiosk); }, [chrome, search]); // Chromeless routes are without topNav, mega menu, search & command palette // We check chromeless twice here instead of having a separate path so {children} // doesn't get re-mounted when chromeless goes from true to false. return (