Commit f455f065 by Agnès Toulet Committed by GitHub

DashNav: refactor action buttons and custom content (#23868)

* DashNav: refactor action buttons and custom content

* DashNav: remove code duplication

* DashNav: fix custom element display

* DashNav: fix strictNullChecks
parent b0488259
// Libaries // Libaries
import React, { FC, PureComponent } from 'react'; import React, { PureComponent, FC, ReactNode } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { css } from 'emotion'; import { css } from 'emotion';
// Utils & Services // Utils & Services
...@@ -27,10 +27,21 @@ export interface OwnProps { ...@@ -27,10 +27,21 @@ export interface OwnProps {
onAddPanel: () => void; onAddPanel: () => void;
} }
const customNavbarContent: Array<FC<Partial<OwnProps>>> = []; interface DashNavButtonModel {
show: (props: Props) => boolean;
component: FC<Partial<Props>>;
index?: number | 'end';
}
const customLeftActions: DashNavButtonModel[] = [];
const customRightActions: DashNavButtonModel[] = [];
export function addNavbarContent(content: FC<Partial<OwnProps>>) { export function addCustomLeftAction(content: DashNavButtonModel) {
customNavbarContent.push(content); customLeftActions.push(content);
}
export function addCustomRightAction(content: DashNavButtonModel) {
customRightActions.push(content);
} }
export interface StateProps { export interface StateProps {
...@@ -102,6 +113,60 @@ class DashNav extends PureComponent<Props> { ...@@ -102,6 +113,60 @@ class DashNav extends PureComponent<Props> {
this.forceUpdate(); this.forceUpdate();
}; };
addCustomContent(actions: DashNavButtonModel[], buttons: ReactNode[]) {
actions.map((action, index) => {
const Component = action.component;
const element = <Component {...this.props} key={`button-custom-${index}`} />;
typeof action.index === 'number' ? buttons.splice(action.index, 0, element) : buttons.push(element);
});
}
renderLeftActionsButton() {
const { dashboard } = this.props;
const { canStar, canShare, isStarred } = dashboard.meta;
const buttons: ReactNode[] = [];
if (canStar) {
buttons.push(
<DashNavButton
tooltip="Mark as favorite"
classSuffix="star"
icon={isStarred ? 'favorite' : 'star'}
iconType={isStarred ? 'mono' : 'default'}
iconSize="lg"
noBorder={true}
onClick={this.onStarDashboard}
key="button-star"
/>
);
}
if (canShare) {
buttons.push(
<ModalsController key="button-share">
{({ showModal, hideModal }) => (
<DashNavButton
tooltip="Share dashboard"
classSuffix="share"
icon="share-alt"
iconSize="lg"
noBorder={true}
onClick={() => {
showModal(ShareModal, {
dashboard,
onDismiss: hideModal,
});
}}
/>
)}
</ModalsController>
);
}
this.addCustomContent(customLeftActions, buttons);
return buttons;
}
renderDashboardTitleSearchButton() { renderDashboardTitleSearchButton() {
const { dashboard, isFullscreen } = this.props; const { dashboard, isFullscreen } = this.props;
/* Hard-coded value so we don't have to wrap whole component in withTheme because of 1 variable */ /* Hard-coded value so we don't have to wrap whole component in withTheme because of 1 variable */
...@@ -135,6 +200,7 @@ class DashNav extends PureComponent<Props> { ...@@ -135,6 +200,7 @@ class DashNav extends PureComponent<Props> {
</a> </a>
</div> </div>
</div> </div>
<div className="navbar-buttons navbar-buttons--actions">{this.renderLeftActionsButton()}</div>
<div className="navbar__spacer" /> <div className="navbar__spacer" />
</> </>
); );
...@@ -148,46 +214,15 @@ class DashNav extends PureComponent<Props> { ...@@ -148,46 +214,15 @@ class DashNav extends PureComponent<Props> {
); );
} }
render() { renderRightActionsButton() {
const { dashboard, onAddPanel, location, isFullscreen } = this.props; const { dashboard, onAddPanel } = this.props;
const { canStar, canSave, canShare, showSettings, isStarred } = dashboard.meta; const { canSave, showSettings } = dashboard.meta;
const { snapshot } = dashboard; const { snapshot } = dashboard;
const snapshotUrl = snapshot && snapshot.originalUrl; const snapshotUrl = snapshot && snapshot.originalUrl;
return ( const buttons: ReactNode[] = [];
<div className="navbar"> if (canSave) {
{isFullscreen && this.renderBackButton()} buttons.push(
{this.renderDashboardTitleSearchButton()}
{this.playlistSrv.isPlaying && (
<div className="navbar-buttons navbar-buttons--playlist">
<DashNavButton
tooltip="Go to previous dashboard"
classSuffix="tight"
icon="step-backward"
onClick={this.onPlaylistPrev}
/>
<DashNavButton
tooltip="Stop playlist"
classSuffix="tight"
icon="square-shape"
onClick={this.onPlaylistStop}
/>
<DashNavButton
tooltip="Go to next dashboard"
classSuffix="tight"
icon="forward"
onClick={this.onPlaylistNext}
/>
</div>
)}
{customNavbarContent.map((Component, index) => (
<Component {...this.props} key={`navbar-custom-content-${index}`} />
))}
<div className="navbar-buttons navbar-buttons--actions">
{canSave && (
<DashNavButton <DashNavButton
classSuffix="save" classSuffix="save"
tooltip="Add panel" tooltip="Add panel"
...@@ -195,39 +230,11 @@ class DashNav extends PureComponent<Props> { ...@@ -195,39 +230,11 @@ class DashNav extends PureComponent<Props> {
onClick={onAddPanel} onClick={onAddPanel}
iconType="mono" iconType="mono"
iconSize="xl" iconSize="xl"
key="button-panel-add"
/> />
)} );
buttons.push(
{canStar && ( <ModalsController key="button-save">
<DashNavButton
tooltip="Mark as favorite"
classSuffix="star"
icon={isStarred ? 'favorite' : 'star'}
iconType={isStarred ? 'mono' : 'default'}
onClick={this.onStarDashboard}
/>
)}
{canShare && (
<ModalsController>
{({ showModal, hideModal }) => (
<DashNavButton
tooltip="Share dashboard"
classSuffix="share"
icon="share-alt"
onClick={() => {
showModal(ShareModal, {
dashboard,
onDismiss: hideModal,
});
}}
/>
)}
</ModalsController>
)}
{canSave && (
<ModalsController>
{({ showModal, hideModal }) => ( {({ showModal, hideModal }) => (
<DashNavButton <DashNavButton
tooltip="Save dashboard" tooltip="Save dashboard"
...@@ -242,26 +249,69 @@ class DashNav extends PureComponent<Props> { ...@@ -242,26 +249,69 @@ class DashNav extends PureComponent<Props> {
/> />
)} )}
</ModalsController> </ModalsController>
)} );
}
{snapshotUrl && ( if (snapshotUrl) {
buttons.push(
<DashNavButton <DashNavButton
tooltip="Open original dashboard" tooltip="Open original dashboard"
classSuffix="snapshot-origin" classSuffix="snapshot-origin"
href={textUtil.sanitizeUrl(snapshotUrl)} href={textUtil.sanitizeUrl(snapshotUrl)}
icon="link" icon="link"
key="button-snapshot"
/> />
)} );
}
{showSettings && ( if (showSettings) {
buttons.push(
<DashNavButton <DashNavButton
tooltip="Dashboard settings" tooltip="Dashboard settings"
classSuffix="settings" classSuffix="settings"
icon="cog" icon="cog"
onClick={this.onOpenSettings} onClick={this.onOpenSettings}
key="button-settings"
/>
);
}
this.addCustomContent(customRightActions, buttons);
return buttons;
}
render() {
const { dashboard, location, isFullscreen } = this.props;
return (
<div className="navbar">
{isFullscreen && this.renderBackButton()}
{this.renderDashboardTitleSearchButton()}
{this.playlistSrv.isPlaying && (
<div className="navbar-buttons navbar-buttons--playlist">
<DashNavButton
tooltip="Go to previous dashboard"
classSuffix="tight"
icon="step-backward"
onClick={this.onPlaylistPrev}
/>
<DashNavButton
tooltip="Stop playlist"
classSuffix="tight"
icon="square-shape"
onClick={this.onPlaylistStop}
/>
<DashNavButton
tooltip="Go to next dashboard"
classSuffix="tight"
icon="forward"
onClick={this.onPlaylistNext}
/> />
)}
</div> </div>
)}
<div className="navbar-buttons navbar-buttons--actions">{this.renderRightActionsButton()}</div>
<div className="navbar-buttons navbar-buttons--tv"> <div className="navbar-buttons navbar-buttons--tv">
<DashNavButton tooltip="Cycle view mode" classSuffix="tv" icon="monitor" onClick={this.onToggleTVMode} /> <DashNavButton tooltip="Cycle view mode" classSuffix="tv" icon="monitor" onClick={this.onToggleTVMode} />
......
// Libraries // Libraries
import React, { FunctionComponent } from 'react'; import React, { FunctionComponent } from 'react';
import { css } from 'emotion';
// Components // Components
import { Icon, IconName, IconSize, IconType, Tooltip } from '@grafana/ui'; import { Tooltip, Icon, IconName, IconType, IconSize, IconButton, useTheme, stylesFactory } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { GrafanaTheme } from '@grafana/data';
interface Props { interface Props {
icon?: IconName; icon?: IconName;
...@@ -13,8 +15,16 @@ interface Props { ...@@ -13,8 +15,16 @@ interface Props {
children?: React.ReactNode; children?: React.ReactNode;
iconType?: IconType; iconType?: IconType;
iconSize?: IconSize; iconSize?: IconSize;
noBorder?: boolean;
} }
const getStyles = stylesFactory((theme: GrafanaTheme) => ({
noBorderContainer: css`
padding: 0 ${theme.spacing.xs};
display: flex;
`,
}));
export const DashNavButton: FunctionComponent<Props> = ({ export const DashNavButton: FunctionComponent<Props> = ({
icon, icon,
iconType, iconType,
...@@ -24,10 +34,31 @@ export const DashNavButton: FunctionComponent<Props> = ({ ...@@ -24,10 +34,31 @@ export const DashNavButton: FunctionComponent<Props> = ({
onClick, onClick,
href, href,
children, children,
noBorder,
}) => { }) => {
if (onClick) { const theme = useTheme();
const styles = getStyles(theme);
if (noBorder) {
return (
<div className={styles.noBorderContainer}>
{icon && (
<IconButton
name={icon}
size={iconSize}
iconType={iconType}
tooltip={tooltip}
tooltipPlacement="bottom"
onClick={onClick}
/>
)}
{children}
</div>
);
}
return ( return (
<Tooltip content={tooltip} placement="bottom"> <Tooltip content={tooltip} placement="bottom">
{onClick ? (
<button <button
className={`btn navbar-button navbar-button--${classSuffix}`} className={`btn navbar-button navbar-button--${classSuffix}`}
onClick={onClick} onClick={onClick}
...@@ -36,16 +67,12 @@ export const DashNavButton: FunctionComponent<Props> = ({ ...@@ -36,16 +67,12 @@ export const DashNavButton: FunctionComponent<Props> = ({
{icon && <Icon name={icon} type={iconType} size={iconSize || 'lg'} />} {icon && <Icon name={icon} type={iconType} size={iconSize || 'lg'} />}
{children} {children}
</button> </button>
</Tooltip> ) : (
);
}
return (
<Tooltip content={tooltip} placement="bottom">
<a className={`btn navbar-button navbar-button--${classSuffix}`} href={href}> <a className={`btn navbar-button navbar-button--${classSuffix}`} href={href}>
{icon && <Icon name={icon} type={iconType} size="lg" />} {icon && <Icon name={icon} type={iconType} size="lg" />}
{children} {children}
</a> </a>
)}
</Tooltip> </Tooltip>
); );
}; };
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment