DashNav.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. import React, { FC, ReactNode } from 'react';
  2. import { connect, ConnectedProps } from 'react-redux';
  3. import { useLocation } from 'react-router-dom';
  4. import { locationUtil, textUtil } from '@grafana/data';
  5. import { locationService } from '@grafana/runtime';
  6. import { ButtonGroup, ModalsController, ToolbarButton, PageToolbar, useForceUpdate } from '@grafana/ui';
  7. import config from 'app/core/config';
  8. import { toggleKioskMode } from 'app/core/navigation/kiosk';
  9. import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal';
  10. import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
  11. import { ShareModal } from 'app/features/dashboard/components/ShareModal';
  12. import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
  13. import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
  14. import { KioskMode } from 'app/types';
  15. import { setStarred } from '../../../../core/reducers/navBarTree';
  16. import { getDashboardSrv } from '../../services/DashboardSrv';
  17. import { DashboardModel } from '../../state';
  18. import { DashNavButton } from './DashNavButton';
  19. import { DashNavTimeControls } from './DashNavTimeControls';
  20. const mapDispatchToProps = {
  21. setStarred,
  22. updateTimeZoneForSession,
  23. };
  24. const connector = connect(null, mapDispatchToProps);
  25. export interface OwnProps {
  26. dashboard: DashboardModel;
  27. isFullscreen: boolean;
  28. kioskMode: KioskMode;
  29. hideTimePicker: boolean;
  30. folderTitle?: string;
  31. title: string;
  32. onAddPanel: () => void;
  33. }
  34. interface DashNavButtonModel {
  35. show: (props: Props) => boolean;
  36. component: FC<Partial<Props>>;
  37. index?: number | 'end';
  38. }
  39. const customLeftActions: DashNavButtonModel[] = [];
  40. const customRightActions: DashNavButtonModel[] = [];
  41. export function addCustomLeftAction(content: DashNavButtonModel) {
  42. customLeftActions.push(content);
  43. }
  44. export function addCustomRightAction(content: DashNavButtonModel) {
  45. customRightActions.push(content);
  46. }
  47. type Props = OwnProps & ConnectedProps<typeof connector>;
  48. export const DashNav = React.memo<Props>((props) => {
  49. const forceUpdate = useForceUpdate();
  50. const onStarDashboard = () => {
  51. const dashboardSrv = getDashboardSrv();
  52. const { dashboard, setStarred } = props;
  53. dashboardSrv.starDashboard(dashboard.id, dashboard.meta.isStarred).then((newState: any) => {
  54. setStarred({ id: dashboard.uid, title: dashboard.title, url: dashboard.meta.url ?? '', isStarred: newState });
  55. dashboard.meta.isStarred = newState;
  56. forceUpdate();
  57. });
  58. };
  59. const onClose = () => {
  60. locationService.partial({ viewPanel: null });
  61. };
  62. const onToggleTVMode = () => {
  63. toggleKioskMode();
  64. };
  65. const onOpenSettings = () => {
  66. locationService.partial({ editview: 'settings' });
  67. };
  68. const onPlaylistPrev = () => {
  69. playlistSrv.prev();
  70. };
  71. const onPlaylistNext = () => {
  72. playlistSrv.next();
  73. };
  74. const onPlaylistStop = () => {
  75. playlistSrv.stop();
  76. forceUpdate();
  77. };
  78. const addCustomContent = (actions: DashNavButtonModel[], buttons: ReactNode[]) => {
  79. actions.map((action, index) => {
  80. const Component = action.component;
  81. const element = <Component {...props} key={`button-custom-${index}`} />;
  82. typeof action.index === 'number' ? buttons.splice(action.index, 0, element) : buttons.push(element);
  83. });
  84. };
  85. const isPlaylistRunning = () => {
  86. return playlistSrv.isPlaying;
  87. };
  88. const renderLeftActionsButton = () => {
  89. const { dashboard, kioskMode } = props;
  90. const { canStar, canShare, isStarred } = dashboard.meta;
  91. const buttons: ReactNode[] = [];
  92. if (kioskMode !== KioskMode.Off || isPlaylistRunning()) {
  93. return [];
  94. }
  95. if (canStar) {
  96. let desc = isStarred ? 'Unmark as favorite' : 'Mark as favorite';
  97. buttons.push(
  98. <DashNavButton
  99. tooltip={desc}
  100. icon={isStarred ? 'favorite' : 'star'}
  101. iconType={isStarred ? 'mono' : 'default'}
  102. iconSize="lg"
  103. onClick={onStarDashboard}
  104. key="button-star"
  105. />
  106. );
  107. }
  108. if (canShare) {
  109. let desc = 'Share dashboard or panel';
  110. buttons.push(
  111. <ModalsController key="button-share">
  112. {({ showModal, hideModal }) => (
  113. <DashNavButton
  114. tooltip={desc}
  115. icon="share-alt"
  116. iconSize="lg"
  117. onClick={() => {
  118. showModal(ShareModal, {
  119. dashboard,
  120. onDismiss: hideModal,
  121. });
  122. }}
  123. />
  124. )}
  125. </ModalsController>
  126. );
  127. }
  128. if (dashboard.uid && config.featureToggles.dashboardComments) {
  129. buttons.push(
  130. <ModalsController key="button-dashboard-comments">
  131. {({ showModal, hideModal }) => (
  132. <DashNavButton
  133. tooltip="Show dashboard comments"
  134. icon="comment-alt-message"
  135. iconSize="lg"
  136. onClick={() => {
  137. showModal(DashboardCommentsModal, {
  138. dashboard,
  139. onDismiss: hideModal,
  140. });
  141. }}
  142. />
  143. )}
  144. </ModalsController>
  145. );
  146. }
  147. addCustomContent(customLeftActions, buttons);
  148. return buttons;
  149. };
  150. const renderPlaylistControls = () => {
  151. return (
  152. <ButtonGroup key="playlist-buttons">
  153. <ToolbarButton tooltip="Go to previous dashboard" icon="backward" onClick={onPlaylistPrev} narrow />
  154. <ToolbarButton onClick={onPlaylistStop}>Stop playlist</ToolbarButton>
  155. <ToolbarButton tooltip="Go to next dashboard" icon="forward" onClick={onPlaylistNext} narrow />
  156. </ButtonGroup>
  157. );
  158. };
  159. const renderTimeControls = () => {
  160. const { dashboard, updateTimeZoneForSession, hideTimePicker } = props;
  161. if (hideTimePicker) {
  162. return null;
  163. }
  164. return (
  165. <DashNavTimeControls dashboard={dashboard} onChangeTimeZone={updateTimeZoneForSession} key="time-controls" />
  166. );
  167. };
  168. const renderRightActionsButton = () => {
  169. const { dashboard, onAddPanel, isFullscreen, kioskMode } = props;
  170. const { canSave, canEdit, showSettings } = dashboard.meta;
  171. const { snapshot } = dashboard;
  172. const snapshotUrl = snapshot && snapshot.originalUrl;
  173. const buttons: ReactNode[] = [];
  174. const tvButton = (
  175. <ToolbarButton tooltip="Cycle view mode" icon="monitor" onClick={onToggleTVMode} key="tv-button" />
  176. );
  177. if (isPlaylistRunning()) {
  178. return [renderPlaylistControls(), renderTimeControls()];
  179. }
  180. if (kioskMode === KioskMode.TV) {
  181. return [renderTimeControls(), tvButton];
  182. }
  183. if (canEdit && !isFullscreen) {
  184. buttons.push(<ToolbarButton tooltip="Add panel" icon="panel-add" onClick={onAddPanel} key="button-panel-add" />);
  185. }
  186. if (canSave && !isFullscreen) {
  187. buttons.push(
  188. <ModalsController key="button-save">
  189. {({ showModal, hideModal }) => (
  190. <ToolbarButton
  191. tooltip="Save dashboard"
  192. icon="save"
  193. onClick={() => {
  194. showModal(SaveDashboardDrawer, {
  195. dashboard,
  196. onDismiss: hideModal,
  197. });
  198. }}
  199. />
  200. )}
  201. </ModalsController>
  202. );
  203. }
  204. if (snapshotUrl) {
  205. buttons.push(
  206. <ToolbarButton
  207. tooltip="Open original dashboard"
  208. onClick={() => gotoSnapshotOrigin(snapshotUrl)}
  209. icon="link"
  210. key="button-snapshot"
  211. />
  212. );
  213. }
  214. if (showSettings) {
  215. buttons.push(
  216. <ToolbarButton tooltip="Dashboard settings" icon="cog" onClick={onOpenSettings} key="button-settings" />
  217. );
  218. }
  219. addCustomContent(customRightActions, buttons);
  220. buttons.push(renderTimeControls());
  221. buttons.push(tvButton);
  222. return buttons;
  223. };
  224. const gotoSnapshotOrigin = (snapshotUrl: string) => {
  225. window.location.href = textUtil.sanitizeUrl(snapshotUrl);
  226. };
  227. const { isFullscreen, title, folderTitle } = props;
  228. // this ensures the component rerenders when the location changes
  229. const location = useLocation();
  230. const titleHref = locationUtil.getUrlForPartial(location, { search: 'open' });
  231. const parentHref = locationUtil.getUrlForPartial(location, { search: 'open', folder: 'current' });
  232. const onGoBack = isFullscreen ? onClose : undefined;
  233. return (
  234. <PageToolbar
  235. pageIcon={isFullscreen ? undefined : 'apps'}
  236. title={title}
  237. parent={folderTitle}
  238. titleHref={titleHref}
  239. parentHref={parentHref}
  240. onGoBack={onGoBack}
  241. leftItems={renderLeftActionsButton()}
  242. >
  243. {renderRightActionsButton()}
  244. </PageToolbar>
  245. );
  246. });
  247. DashNav.displayName = 'DashNav';
  248. export default connector(DashNav);