grafana/public/app/features/dashboard/components/DashNav/DashNav.tsx
Torkel Ödegaard 49bc70c812
TopNav: Panel edit changes (#54746)
* Progress

* Progress

* Things are working

* More tweaks

* Fixing unit test

* Tweaks and fixing e2e tests

* Remove ... in Save as

* Fixing unit test

* Fixing e2e test

* Fixes

Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
2022-09-12 15:45:14 +02:00

357 lines
10 KiB
TypeScript

import { t, Trans } from '@lingui/macro';
import React, { FC, ReactNode } from 'react';
import { connect, ConnectedProps } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { locationUtil, textUtil } from '@grafana/data';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { locationService } from '@grafana/runtime';
import {
ButtonGroup,
ModalsController,
ToolbarButton,
PageToolbar,
useForceUpdate,
Tag,
ToolbarButtonRow,
} from '@grafana/ui';
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbarSeparator';
import config from 'app/core/config';
import { toggleKioskMode } from 'app/core/navigation/kiosk';
import { DashboardCommentsModal } from 'app/features/dashboard/components/DashboardComments/DashboardCommentsModal';
import { SaveDashboardDrawer } from 'app/features/dashboard/components/SaveDashboard/SaveDashboardDrawer';
import { ShareModal } from 'app/features/dashboard/components/ShareModal';
import { playlistSrv } from 'app/features/playlist/PlaylistSrv';
import { updateTimeZoneForSession } from 'app/features/profile/state/reducers';
import { KioskMode } from 'app/types';
import { setStarred } from '../../../../core/reducers/navBarTree';
import { getDashboardSrv } from '../../services/DashboardSrv';
import { DashboardModel } from '../../state';
import { DashNavButton } from './DashNavButton';
import { DashNavTimeControls } from './DashNavTimeControls';
const mapDispatchToProps = {
setStarred,
updateTimeZoneForSession,
};
const connector = connect(null, mapDispatchToProps);
const selectors = e2eSelectors.pages.Dashboard.DashNav;
export interface OwnProps {
dashboard: DashboardModel;
isFullscreen: boolean;
kioskMode: KioskMode;
hideTimePicker: boolean;
folderTitle?: string;
title: string;
onAddPanel: () => void;
}
interface DashNavButtonModel {
show: (props: Props) => boolean;
component: FC<Partial<Props>>;
index?: number | 'end';
}
const customLeftActions: DashNavButtonModel[] = [];
const customRightActions: DashNavButtonModel[] = [];
export function addCustomLeftAction(content: DashNavButtonModel) {
customLeftActions.push(content);
}
export function addCustomRightAction(content: DashNavButtonModel) {
customRightActions.push(content);
}
type Props = OwnProps & ConnectedProps<typeof connector>;
export const DashNav = React.memo<Props>((props) => {
const forceUpdate = useForceUpdate();
const onStarDashboard = () => {
const dashboardSrv = getDashboardSrv();
const { dashboard, setStarred } = props;
dashboardSrv.starDashboard(dashboard.id, dashboard.meta.isStarred).then((newState) => {
setStarred({ id: dashboard.uid, title: dashboard.title, url: dashboard.meta.url ?? '', isStarred: newState });
dashboard.meta.isStarred = newState;
forceUpdate();
});
};
const onClose = () => {
locationService.partial({ viewPanel: null });
};
const onToggleTVMode = () => {
toggleKioskMode();
};
const onOpenSettings = () => {
locationService.partial({ editview: 'settings' });
};
const onPlaylistPrev = () => {
playlistSrv.prev();
};
const onPlaylistNext = () => {
playlistSrv.next();
};
const onPlaylistStop = () => {
playlistSrv.stop();
forceUpdate();
};
const addCustomContent = (actions: DashNavButtonModel[], buttons: ReactNode[]) => {
actions.map((action, index) => {
const Component = action.component;
const element = <Component {...props} key={`button-custom-${index}`} />;
typeof action.index === 'number' ? buttons.splice(action.index, 0, element) : buttons.push(element);
});
};
const isPlaylistRunning = () => {
return playlistSrv.isPlaying;
};
const renderLeftActions = () => {
const { dashboard, kioskMode } = props;
const { canStar, canShare, isStarred } = dashboard.meta;
const buttons: ReactNode[] = [];
if (kioskMode !== KioskMode.Off || isPlaylistRunning()) {
return [];
}
if (canStar) {
let desc = isStarred
? t({ id: 'dashboard.toolbar.unmark-favorite', message: 'Unmark as favorite' })
: t({ id: 'dashboard.toolbar.mark-favorite', message: 'Mark as favorite' });
buttons.push(
<DashNavButton
tooltip={desc}
icon={isStarred ? 'favorite' : 'star'}
iconType={isStarred ? 'mono' : 'default'}
iconSize="lg"
onClick={onStarDashboard}
key="button-star"
/>
);
}
if (canShare) {
buttons.push(
<ModalsController key="button-share">
{({ showModal, hideModal }) => (
<DashNavButton
tooltip={t({ id: 'dashboard.toolbar.share', message: 'Share dashboard or panel' })}
icon="share-alt"
iconSize="lg"
onClick={() => {
showModal(ShareModal, {
dashboard,
onDismiss: hideModal,
});
}}
/>
)}
</ModalsController>
);
}
if (dashboard.meta.publicDashboardEnabled) {
buttons.push(<Tag name="Public" colorIndex={5} data-testid={selectors.publicDashboardTag}></Tag>);
}
if (dashboard.uid && config.featureToggles.dashboardComments) {
buttons.push(
<ModalsController key="button-dashboard-comments">
{({ showModal, hideModal }) => (
<DashNavButton
tooltip={t({ id: 'dashboard.toolbar.comments-show', message: 'Show dashboard comments' })}
icon="comment-alt-message"
iconSize="lg"
onClick={() => {
showModal(DashboardCommentsModal, {
dashboard,
onDismiss: hideModal,
});
}}
/>
)}
</ModalsController>
);
}
addCustomContent(customLeftActions, buttons);
return buttons;
};
const renderPlaylistControls = () => {
return (
<ButtonGroup key="playlist-buttons">
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.playlist-previous', message: 'Go to previous dashboard' })}
icon="backward"
onClick={onPlaylistPrev}
narrow
/>
<ToolbarButton onClick={onPlaylistStop}>
<Trans id="dashboard.toolbar.playlist-stop">Stop playlist</Trans>
</ToolbarButton>
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.playlist-next', message: 'Go to next dashboard' })}
icon="forward"
onClick={onPlaylistNext}
narrow
/>
</ButtonGroup>
);
};
const renderTimeControls = () => {
const { dashboard, updateTimeZoneForSession, hideTimePicker } = props;
if (hideTimePicker) {
return null;
}
return (
<DashNavTimeControls dashboard={dashboard} onChangeTimeZone={updateTimeZoneForSession} key="time-controls" />
);
};
const renderRightActions = () => {
const { dashboard, onAddPanel, isFullscreen, kioskMode } = props;
const { canSave, canEdit, showSettings } = dashboard.meta;
const { snapshot } = dashboard;
const snapshotUrl = snapshot && snapshot.originalUrl;
const buttons: ReactNode[] = [];
const tvButton = (
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.tv-button', message: 'Cycle view mode' })}
icon="monitor"
onClick={onToggleTVMode}
key="tv-button"
/>
);
if (isPlaylistRunning()) {
return [renderPlaylistControls(), renderTimeControls()];
}
if (kioskMode === KioskMode.TV) {
return [renderTimeControls(), tvButton];
}
if (canEdit && !isFullscreen) {
buttons.push(
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.add-panel', message: 'Add panel' })}
icon="panel-add"
onClick={onAddPanel}
key="button-panel-add"
/>
);
}
if (canSave && !isFullscreen) {
buttons.push(
<ModalsController key="button-save">
{({ showModal, hideModal }) => (
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.save', message: 'Save dashboard' })}
icon="save"
onClick={() => {
showModal(SaveDashboardDrawer, {
dashboard,
onDismiss: hideModal,
});
}}
/>
)}
</ModalsController>
);
}
if (snapshotUrl) {
buttons.push(
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.open-original', message: 'Open original dashboard' })}
onClick={() => gotoSnapshotOrigin(snapshotUrl)}
icon="link"
key="button-snapshot"
/>
);
}
if (showSettings) {
buttons.push(
<ToolbarButton
tooltip={t({ id: 'dashboard.toolbar.settings', message: 'Dashboard settings' })}
icon="cog"
onClick={onOpenSettings}
key="button-settings"
/>
);
}
addCustomContent(customRightActions, buttons);
buttons.push(renderTimeControls());
buttons.push(tvButton);
return buttons;
};
const gotoSnapshotOrigin = (snapshotUrl: string) => {
window.location.href = textUtil.sanitizeUrl(snapshotUrl);
};
const { isFullscreen, title, folderTitle } = props;
// this ensures the component rerenders when the location changes
const location = useLocation();
const titleHref = locationUtil.getUrlForPartial(location, { search: 'open' });
const parentHref = locationUtil.getUrlForPartial(location, { search: 'open', folder: 'current' });
const onGoBack = isFullscreen ? onClose : undefined;
if (config.featureToggles.topnav) {
return (
<AppChromeUpdate
actions={
<>
{renderLeftActions()}
<NavToolbarSeparator leftActionsSeparator />
<ToolbarButtonRow alignment="right">{renderRightActions()}</ToolbarButtonRow>
</>
}
/>
);
}
return (
<PageToolbar
pageIcon={isFullscreen ? undefined : 'apps'}
title={title}
parent={folderTitle}
titleHref={titleHref}
parentHref={parentHref}
onGoBack={onGoBack}
leftItems={renderLeftActions()}
>
{renderRightActions()}
</PageToolbar>
);
});
DashNav.displayName = 'DashNav';
export default connector(DashNav);