2022-07-06 17:00:56 +02:00
|
|
|
import { useObservable } from 'react-use';
|
|
|
|
|
import { BehaviorSubject } from 'rxjs';
|
|
|
|
|
|
2022-09-15 14:04:58 +02:00
|
|
|
import { AppEvents, NavModelItem, UrlQueryValue } from '@grafana/data';
|
2023-01-31 13:03:31 +00:00
|
|
|
import { locationService, reportInteraction } from '@grafana/runtime';
|
2022-09-15 14:04:58 +02:00
|
|
|
import appEvents from 'app/core/app_events';
|
2022-10-06 16:34:04 +01:00
|
|
|
import { t } from 'app/core/internationalization';
|
2022-07-16 17:44:16 +02:00
|
|
|
import store from 'app/core/store';
|
2022-07-06 17:00:56 +02:00
|
|
|
import { isShallowEqual } from 'app/core/utils/isShallowEqual';
|
2022-09-15 14:04:58 +02:00
|
|
|
import { KioskMode } from 'app/types';
|
2022-07-06 17:00:56 +02:00
|
|
|
|
|
|
|
|
import { RouteDescriptor } from '../../navigation/types';
|
|
|
|
|
|
|
|
|
|
export interface AppChromeState {
|
2022-07-29 17:16:14 +02:00
|
|
|
chromeless?: boolean;
|
2022-07-06 17:00:56 +02:00
|
|
|
sectionNav: NavModelItem;
|
|
|
|
|
pageNav?: NavModelItem;
|
|
|
|
|
actions?: React.ReactNode;
|
2022-07-16 17:44:16 +02:00
|
|
|
searchBarHidden?: boolean;
|
|
|
|
|
megaMenuOpen?: boolean;
|
2022-09-15 14:04:58 +02:00
|
|
|
kioskMode: KioskMode | null;
|
2022-07-06 17:00:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class AppChromeService {
|
2022-07-16 17:44:16 +02:00
|
|
|
searchBarStorageKey = 'SearchBar_Hidden';
|
2022-07-29 17:16:14 +02:00
|
|
|
private currentRoute?: RouteDescriptor;
|
|
|
|
|
private routeChangeHandled?: boolean;
|
2022-07-16 17:44:16 +02:00
|
|
|
|
2022-07-06 17:00:56 +02:00
|
|
|
readonly state = new BehaviorSubject<AppChromeState>({
|
|
|
|
|
chromeless: true, // start out hidden to not flash it on pages without chrome
|
2023-01-04 15:02:30 +00:00
|
|
|
sectionNav: { text: t('nav.home.title', 'Home') },
|
2022-07-16 17:44:16 +02:00
|
|
|
searchBarHidden: store.getBool(this.searchBarStorageKey, false),
|
2022-09-15 14:04:58 +02:00
|
|
|
kioskMode: null,
|
2022-07-06 17:00:56 +02:00
|
|
|
});
|
|
|
|
|
|
2022-09-15 14:04:58 +02:00
|
|
|
setMatchedRoute(route: RouteDescriptor) {
|
2022-07-29 17:16:14 +02:00
|
|
|
if (this.currentRoute !== route) {
|
|
|
|
|
this.currentRoute = route;
|
|
|
|
|
this.routeChangeHandled = false;
|
|
|
|
|
}
|
2022-07-06 17:00:56 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-29 17:16:14 +02:00
|
|
|
update(update: Partial<AppChromeState>) {
|
2022-07-06 17:00:56 +02:00
|
|
|
const current = this.state.getValue();
|
|
|
|
|
const newState: AppChromeState = {
|
|
|
|
|
...current,
|
|
|
|
|
};
|
|
|
|
|
|
2022-07-29 17:16:14 +02:00
|
|
|
// when route change update props from route and clear fields
|
|
|
|
|
if (!this.routeChangeHandled) {
|
|
|
|
|
newState.actions = undefined;
|
|
|
|
|
newState.pageNav = undefined;
|
2023-01-04 15:02:30 +00:00
|
|
|
newState.sectionNav = { text: t('nav.home.title', 'Home') };
|
2022-07-29 17:16:14 +02:00
|
|
|
newState.chromeless = this.currentRoute?.chromeless;
|
|
|
|
|
this.routeChangeHandled = true;
|
|
|
|
|
}
|
|
|
|
|
|
2022-11-04 16:49:31 +01:00
|
|
|
Object.assign(newState, update);
|
|
|
|
|
|
2022-09-15 14:04:58 +02:00
|
|
|
// KioskMode overrides chromeless state
|
|
|
|
|
newState.chromeless = newState.kioskMode === KioskMode.Full || this.currentRoute?.chromeless;
|
|
|
|
|
|
2022-07-06 17:00:56 +02:00
|
|
|
if (!isShallowEqual(current, newState)) {
|
|
|
|
|
this.state.next(newState);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-09-15 14:04:58 +02:00
|
|
|
useState() {
|
|
|
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
|
|
|
return useObservable(this.state, this.state.getValue());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onToggleMegaMenu = () => {
|
2023-01-31 13:03:31 +00:00
|
|
|
const isOpen = !this.state.getValue().megaMenuOpen;
|
|
|
|
|
reportInteraction('grafana_toggle_menu_clicked', { action: isOpen ? 'open' : 'close' });
|
|
|
|
|
this.update({ megaMenuOpen: isOpen });
|
2022-07-16 17:44:16 +02:00
|
|
|
};
|
|
|
|
|
|
2022-08-03 16:08:39 +05:00
|
|
|
setMegaMenu = (megaMenuOpen: boolean) => {
|
|
|
|
|
this.update({ megaMenuOpen });
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-15 14:04:58 +02:00
|
|
|
onToggleSearchBar = () => {
|
2022-07-16 17:44:16 +02:00
|
|
|
const searchBarHidden = !this.state.getValue().searchBarHidden;
|
|
|
|
|
store.set(this.searchBarStorageKey, searchBarHidden);
|
|
|
|
|
this.update({ searchBarHidden });
|
|
|
|
|
};
|
|
|
|
|
|
2022-09-15 14:04:58 +02:00
|
|
|
onToggleKioskMode = () => {
|
|
|
|
|
const nextMode = this.getNextKioskMode();
|
|
|
|
|
this.update({ kioskMode: nextMode });
|
|
|
|
|
locationService.partial({ kiosk: this.getKioskUrlValue(nextMode) });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
exitKioskMode() {
|
|
|
|
|
this.update({ kioskMode: undefined });
|
|
|
|
|
locationService.partial({ kiosk: null });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setKioskModeFromUrl(kiosk: UrlQueryValue) {
|
|
|
|
|
switch (kiosk) {
|
|
|
|
|
case 'tv':
|
|
|
|
|
this.update({ kioskMode: KioskMode.TV });
|
|
|
|
|
break;
|
|
|
|
|
case '1':
|
|
|
|
|
case true:
|
|
|
|
|
this.update({ kioskMode: KioskMode.Full });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getKioskUrlValue(mode: KioskMode | null) {
|
|
|
|
|
switch (mode) {
|
|
|
|
|
case KioskMode.TV:
|
|
|
|
|
return 'tv';
|
|
|
|
|
case KioskMode.Full:
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getNextKioskMode() {
|
|
|
|
|
const { kioskMode, searchBarHidden } = this.state.getValue();
|
|
|
|
|
|
|
|
|
|
if (searchBarHidden || kioskMode === KioskMode.TV) {
|
2022-10-06 16:34:04 +01:00
|
|
|
appEvents.emit(AppEvents.alertSuccess, [t('navigation.kiosk.tv-alert', 'Press ESC to exit kiosk mode')]);
|
2022-09-15 14:04:58 +02:00
|
|
|
return KioskMode.Full;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!kioskMode) {
|
|
|
|
|
return KioskMode.TV;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
2022-07-06 17:00:56 +02:00
|
|
|
}
|
|
|
|
|
}
|