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>
This commit is contained in:
Torkel Ödegaard
2022-09-12 15:45:14 +02:00
committed by GitHub
parent 4c3e08db91
commit 49bc70c812
22 changed files with 229 additions and 130 deletions

View File

@@ -1,21 +1,19 @@
import React, { useEffect } from 'react';
import { NavModelItem } from '@grafana/data';
import { useGrafana } from 'app/core/context/GrafanaContext';
export interface AppChromeUpdateProps {
pageNav?: NavModelItem;
actions?: React.ReactNode;
}
/**
* This needs to be moved to @grafana/ui or runtime.
* This is the way core pages and plugins update the breadcrumbs and page toolbar actions
*/
export const AppChromeUpdate = React.memo<AppChromeUpdateProps>(({ pageNav, actions }: AppChromeUpdateProps) => {
export const AppChromeUpdate = React.memo<AppChromeUpdateProps>(({ actions }: AppChromeUpdateProps) => {
const { chrome } = useGrafana();
useEffect(() => {
chrome.update({ pageNav, actions });
chrome.update({ actions });
});
return null;
});

View File

@@ -52,7 +52,7 @@ const getStyles = (theme: GrafanaTheme2) => {
pageToolbar: css({
height: TOP_BAR_LEVEL_HEIGHT,
display: 'flex',
padding: theme.spacing(0, 2),
padding: theme.spacing(0, 1, 0, 2),
alignItems: 'center',
justifyContent: 'space-between',
}),

View File

@@ -1,13 +1,14 @@
import { css, cx } from '@emotion/css';
import { FocusScope } from '@react-aria/focus';
import { Location as HistoryLocation } from 'history';
import { cloneDeep } from 'lodash';
import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { GrafanaTheme2, NavModelItem, NavSection } from '@grafana/data';
import { config, locationService, reportInteraction } from '@grafana/runtime';
import { CustomScrollbar, Icon, useTheme2 } from '@grafana/ui';
import { config, locationSearchToObject, locationService, reportInteraction } from '@grafana/runtime';
import { Icon, useTheme2, CustomScrollbar } from '@grafana/ui';
import { getKioskMode } from 'app/core/navigation/kiosk';
import { KioskMode, StoreState } from 'app/types';
@@ -38,7 +39,6 @@ export const NavBar = React.memo(() => {
const theme = useTheme2();
const styles = getStyles(theme);
const location = useLocation();
const kiosk = getKioskMode();
const [showSwitcherModal, setShowSwitcherModal] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const [menuAnimationInProgress, setMenuAnimationInProgress] = useState(false);
@@ -85,9 +85,10 @@ export const NavBar = React.memo(() => {
const activeItem = isSearchActive(location) ? searchItem : getActiveItem(navTree, location.pathname);
if (kiosk !== KioskMode.Off) {
if (shouldHideNavBar(location)) {
return null;
}
return (
<div className={styles.navWrapper}>
<nav className={cx(styles.sidemenu, 'sidemenu')} data-testid="sidemenu" aria-label="Main menu">
@@ -174,6 +175,22 @@ export const NavBar = React.memo(() => {
);
});
function shouldHideNavBar(location: HistoryLocation) {
const queryParams = locationSearchToObject(location.search);
const kiosk = getKioskMode(queryParams);
if (kiosk !== KioskMode.Off) {
return true;
}
// Temporary, can be removed after topnav is made permanent
if ((location.pathname.indexOf('/d/') === 0 && queryParams.editview) || queryParams.editPanel) {
return true;
}
return false;
}
NavBar.displayName = 'NavBar';
const getStyles = (theme: GrafanaTheme2) => ({

View File

@@ -26,6 +26,8 @@ export const OldPage: PageType = ({
scrollRef,
scrollTop,
layout = PageLayoutType.Standard,
subTitle,
...otherProps
}) => {
const styles = useStyles2(getStyles);
const navModel = usePageNav(navId, oldNavProp);
@@ -35,10 +37,10 @@ export const OldPage: PageType = ({
const pageHeaderNav = pageNav ?? navModel?.main;
return (
<div className={cx(styles.wrapper, className)}>
<div className={cx(styles.wrapper, className)} {...otherProps}>
{layout === PageLayoutType.Standard && (
<CustomScrollbar autoHeightMin={'100%'} scrollTop={scrollTop} scrollRefCallback={scrollRef}>
<div className="page-scrollbar-content">
<div className={cx('page-scrollbar-content', className)}>
{pageHeaderNav && <PageHeader navItem={pageHeaderNav} />}
{children}
<Footer />
@@ -55,6 +57,12 @@ export const OldPage: PageType = ({
</div>
</>
)}
{layout === PageLayoutType.Custom && (
<>
{toolbar}
{children}
</>
)}
</div>
);
};

View File

@@ -27,6 +27,7 @@ export const Page: PageType = ({
toolbar,
scrollTop,
scrollRef,
...otherProps
}) => {
const styles = useStyles2(getStyles);
const navModel = usePageNav(navId, oldNavProp);
@@ -46,7 +47,7 @@ export const Page: PageType = ({
}, [navModel, pageNav, chrome]);
return (
<div className={cx(styles.wrapper, className)}>
<div className={cx(styles.wrapper, className)} {...otherProps}>
{layout === PageLayoutType.Standard && (
<div className={styles.panes}>
{navModel && navModel.main.children && <SectionNav model={navModel} />}
@@ -70,6 +71,12 @@ export const Page: PageType = ({
</div>
</CustomScrollbar>
)}
{layout === PageLayoutType.Custom && (
<>
{toolbar}
{children}
</>
)}
</div>
);
};

View File

@@ -1,6 +1,6 @@
import { t } from '@lingui/macro';
import { AppEvents } from '@grafana/data';
import { AppEvents, UrlQueryMap } from '@grafana/data';
import { locationService } from '@grafana/runtime';
import { KioskMode } from '../../types';
@@ -27,10 +27,8 @@ export function toggleKioskMode() {
locationService.partial({ kiosk });
}
export function getKioskMode(): KioskMode {
const kiosk = locationService.getSearchObject().kiosk;
switch (kiosk) {
export function getKioskMode(queryParams: UrlQueryMap): KioskMode {
switch (queryParams.kiosk) {
case 'tv':
return KioskMode.TV;
// legacy support

View File

@@ -325,7 +325,6 @@ export const DashNav = React.memo<Props>((props) => {
if (config.featureToggles.topnav) {
return (
<AppChromeUpdate
pageNav={{ text: title }}
actions={
<>
{renderLeftActions()}

View File

@@ -15,6 +15,7 @@ import { DashboardModel } from '../../state';
export interface Props {
dashboard: DashboardModel;
onChangeTimeZone: (timeZone: TimeZone) => void;
isOnCanvas?: boolean;
}
export class DashNavTimeControls extends Component<Props> {
@@ -77,7 +78,7 @@ export class DashNavTimeControls extends Component<Props> {
};
render() {
const { dashboard } = this.props;
const { dashboard, isOnCanvas } = this.props;
const { refresh_intervals } = dashboard.timepicker;
const intervals = getTimeSrv().getValidIntervals(refresh_intervals || defaultIntervals);
@@ -98,12 +99,14 @@ export class DashNavTimeControls extends Component<Props> {
onZoom={this.onZoom}
onChangeTimeZone={this.onChangeTimeZone}
onChangeFiscalYearStartMonth={this.onChangeFiscalYearStartMonth}
isOnCanvas={isOnCanvas}
/>
<RefreshPicker
onIntervalChanged={this.onChangeRefreshInterval}
onRefresh={this.onRefresh}
value={dashboard.refresh}
intervals={intervals}
isOnCanvas={isOnCanvas}
tooltip={t({ id: 'dashboard.toolbar.refresh', message: 'Refresh dashboard' })}
noIntervalPicker={hideIntervalPicker}
offDescriptionAriaLabelMsg={t({

View File

@@ -46,12 +46,19 @@ export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }:
const location = useLocation();
const editIndex = getEditIndex(location);
const subSectionNav = getSectionNav(pageNav, sectionNav, pages, currentPage, location);
const size = config.featureToggles.topnav ? 'sm' : 'md';
const actions = [
canSaveAs && (
<SaveDashboardAsButton dashboard={dashboard} onSaveSuccess={onPostSave} variant="secondary" key="save as" />
<SaveDashboardAsButton
dashboard={dashboard}
onSaveSuccess={onPostSave}
variant="secondary"
key="save as"
size={size}
/>
),
canSave && <SaveDashboardButton dashboard={dashboard} onSaveSuccess={onPostSave} key="Save" />,
canSave && <SaveDashboardButton dashboard={dashboard} onSaveSuccess={onPostSave} key="Save" size={size} />,
];
return (

View File

@@ -4,20 +4,25 @@ import { connect, ConnectedProps } from 'react-redux';
import AutoSizer from 'react-virtualized-auto-sizer';
import { Subscription } from 'rxjs';
import { FieldConfigSource, GrafanaTheme2 } from '@grafana/data';
import { FieldConfigSource, GrafanaTheme2, NavModel, NavModelItem, PageLayoutType } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { locationService } from '@grafana/runtime';
import { config, locationService } from '@grafana/runtime';
import {
Button,
HorizontalGroup,
InlineSwitch,
ModalsController,
PageToolbar,
RadioButtonGroup,
Stack,
stylesFactory,
Themeable2,
ToolbarButton,
ToolbarButtonRow,
withTheme2,
} from '@grafana/ui';
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { Page } from 'app/core/components/Page/Page';
import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper';
import { appEvents } from 'app/core/core';
import { SubMenuItems } from 'app/features/dashboard/components/SubMenu/SubMenuItems';
@@ -50,6 +55,9 @@ import { calculatePanelSize } from './utils';
interface OwnProps {
dashboard: DashboardModel;
sourcePanel: PanelModel;
sectionNav: NavModel;
pageNav: NavModelItem;
className?: string;
tab?: string;
}
@@ -127,12 +135,6 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
this.onBack();
};
onOpenDashboardSettings = () => {
locationService.partial({
editview: 'settings',
});
};
onSaveDashboard = () => {
appEvents.publish(
new ShowModalReactEvent({
@@ -293,7 +295,7 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
<div className={styles.panelToolbar}>
<HorizontalGroup justify={variables.length > 0 ? 'space-between' : 'flex-end'} align="flex-start">
{this.renderTemplateVariables(styles)}
<HorizontalGroup>
<Stack gap={1}>
<InlineSwitch
label="Table view"
showLabel={true}
@@ -303,47 +305,58 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
aria-label={selectors.components.PanelEditor.toggleTableView}
/>
<RadioButtonGroup value={uiState.mode} options={displayModes} onChange={this.onDisplayModeChange} />
<DashNavTimeControls dashboard={dashboard} onChangeTimeZone={updateTimeZoneForSession} />
<DashNavTimeControls dashboard={dashboard} onChangeTimeZone={updateTimeZoneForSession} isOnCanvas={true} />
{!uiState.isPanelOptionsVisible && <VisualizationButton panel={panel} />}
</HorizontalGroup>
</Stack>
</HorizontalGroup>
</div>
);
}
renderEditorActions() {
const size = config.featureToggles.topnav ? 'sm' : 'md';
let editorActions = [
<ToolbarButton
icon="cog"
onClick={this.onOpenDashboardSettings}
tooltip="Open dashboard settings"
key="settings"
/>,
<ToolbarButton onClick={this.onDiscard} tooltip="Undo all changes" key="discard">
<Button
onClick={this.onDiscard}
title="Undo all changes"
key="discard"
size={size}
variant="destructive"
fill="outline"
>
Discard
</ToolbarButton>,
</Button>,
this.props.panel.libraryPanel ? (
<ToolbarButton
<Button
onClick={this.onSaveLibraryPanel}
variant="primary"
tooltip="Apply changes and save library panel"
size={size}
title="Apply changes and save library panel"
key="save-panel"
>
Save library panel
</ToolbarButton>
</Button>
) : (
<ToolbarButton onClick={this.onSaveDashboard} tooltip="Apply changes and save dashboard" key="save">
<Button
onClick={this.onSaveDashboard}
title="Apply changes and save dashboard"
key="save"
size={size}
variant="secondary"
>
Save
</ToolbarButton>
</Button>
),
<ToolbarButton
<Button
onClick={this.onBack}
variant="primary"
tooltip="Apply changes and go back to dashboard"
title="Apply changes and go back to dashboard"
data-testid={selectors.components.PanelEditor.applyButton}
key="apply"
size={size}
>
Apply
</ToolbarButton>,
</Button>,
];
if (this.props.panel.libraryPanel) {
@@ -410,8 +423,24 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
this.setState({ showSaveLibraryPanelModal: false });
};
renderToolbar() {
if (config.featureToggles.topnav) {
return (
<AppChromeUpdate
actions={<ToolbarButtonRow alignment="right">{this.renderEditorActions()}</ToolbarButtonRow>}
/>
);
}
return (
<PageToolbar title={this.props.dashboard.title} section="Edit Panel" onGoBack={this.onGoBackToDashboard}>
{this.renderEditorActions()}
</PageToolbar>
);
}
render() {
const { dashboard, initDone, updatePanelEditorUIState, uiState, theme } = this.props;
const { initDone, updatePanelEditorUIState, uiState, theme, sectionNav, pageNav, className } = this.props;
const styles = getStyles(theme, this.props);
if (!initDone) {
@@ -419,29 +448,35 @@ export class PanelEditorUnconnected extends PureComponent<Props> {
}
return (
<div className={styles.wrapper} aria-label={selectors.components.PanelEditor.General.content}>
<PageToolbar title={dashboard.title} section="Edit Panel" onGoBack={this.onGoBackToDashboard}>
{this.renderEditorActions()}
</PageToolbar>
<div className={styles.verticalSplitPanesWrapper}>
<SplitPaneWrapper
leftPaneComponents={this.renderPanelAndEditor(styles)}
rightPaneComponents={this.renderOptionsPane()}
uiState={uiState}
updateUiState={updatePanelEditorUIState}
rightPaneVisible={uiState.isPanelOptionsVisible}
/>
<Page
navModel={sectionNav}
pageNav={pageNav}
aria-label={selectors.components.PanelEditor.General.content}
layout={PageLayoutType.Custom}
toolbar={this.renderToolbar()}
className={className}
>
<div className={styles.wrapper}>
<div className={styles.verticalSplitPanesWrapper}>
<SplitPaneWrapper
leftPaneComponents={this.renderPanelAndEditor(styles)}
rightPaneComponents={this.renderOptionsPane()}
uiState={uiState}
updateUiState={updatePanelEditorUIState}
rightPaneVisible={uiState.isPanelOptionsVisible}
/>
</div>
{this.state.showSaveLibraryPanelModal && (
<SaveLibraryPanelModal
panel={this.props.panel as PanelModelWithLibraryPanel}
folderId={this.props.dashboard.meta.folderId as number}
onConfirm={this.onConfirmAndDismissLibarayPanelModel}
onDiscard={this.onDiscard}
onDismiss={this.onConfirmAndDismissLibarayPanelModel}
/>
)}
</div>
{this.state.showSaveLibraryPanelModal && (
<SaveLibraryPanelModal
panel={this.props.panel as PanelModelWithLibraryPanel}
folderId={this.props.dashboard.meta.folderId as number}
onConfirm={this.onConfirmAndDismissLibarayPanelModel}
onDiscard={this.onDiscard}
onDismiss={this.onConfirmAndDismissLibarayPanelModel}
/>
)}
</div>
</Page>
);
}
}
@@ -456,19 +491,13 @@ export const getStyles = stylesFactory((theme: GrafanaTheme2, props: Props) => {
const paneSpacing = theme.spacing(2);
return {
wrapper: css`
width: 100%;
height: 100%;
position: fixed;
z-index: ${theme.zIndex.sidemenu};
top: 0;
left: 0;
right: 0;
bottom: 0;
background: ${theme.colors.background.canvas};
display: flex;
flex-direction: column;
`,
wrapper: css({
width: '100%',
flexGrow: 1,
minHeight: 0,
display: 'flex',
paddingTop: config.featureToggles.topnav ? theme.spacing(2) : 0,
}),
verticalSplitPanesWrapper: css`
display: flex;
flex-direction: column;

View File

@@ -45,6 +45,7 @@ export const VisualizationButton: FC<Props> = ({ panel }) => {
isOpen={isVizPickerOpen}
onClick={onToggleOpen}
aria-label={selectors.components.PanelEditor.toggleVizPicker}
variant="canvas"
fullWidth
>
{plugin.meta.name}
@@ -53,6 +54,7 @@ export const VisualizationButton: FC<Props> = ({ panel }) => {
tooltip={isPanelOptionsVisible ? 'Close options pane' : 'Show options pane'}
icon={isPanelOptionsVisible ? 'angle-right' : 'angle-left'}
onClick={onToggleOptionsPane}
variant="canvas"
aria-label={selectors.components.PanelEditor.toggleVizOptions}
/>
</ButtonGroup>

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { selectors } from '@grafana/e2e-selectors';
import { Button, ButtonVariant, ModalsController } from '@grafana/ui';
import { Button, ButtonVariant, ComponentSize, ModalsController } from '@grafana/ui';
import { DashboardModel } from 'app/features/dashboard/state';
import { SaveDashboardDrawer } from './SaveDashboardDrawer';
@@ -9,14 +9,16 @@ import { SaveDashboardDrawer } from './SaveDashboardDrawer';
interface SaveDashboardButtonProps {
dashboard: DashboardModel;
onSaveSuccess?: () => void;
size?: ComponentSize;
}
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({ dashboard, onSaveSuccess }) => {
export const SaveDashboardButton: React.FC<SaveDashboardButtonProps> = ({ dashboard, onSaveSuccess, size }) => {
return (
<ModalsController>
{({ showModal, hideModal }) => {
return (
<Button
size={size}
onClick={() => {
showModal(SaveDashboardDrawer, {
dashboard,
@@ -38,12 +40,14 @@ export const SaveDashboardAsButton: React.FC<SaveDashboardButtonProps & { varian
dashboard,
onSaveSuccess,
variant,
size,
}) => {
return (
<ModalsController>
{({ showModal, hideModal }) => {
return (
<Button
size={size}
onClick={() => {
showModal(SaveDashboardDrawer, {
dashboard,
@@ -55,7 +59,7 @@ export const SaveDashboardAsButton: React.FC<SaveDashboardButtonProps & { varian
variant={variant}
aria-label={selectors.pages.Dashboard.Settings.General.saveAsDashBoard}
>
Save As...
Save as
</Button>
);
}}

View File

@@ -241,7 +241,7 @@ describe('DashboardPage', () => {
});
it('Should render panel editor', () => {
expect(screen.getByLabelText('Apply changes and go back to dashboard')).toBeInTheDocument();
expect(screen.getByTitle('Apply changes and go back to dashboard')).toBeInTheDocument();
});
it('Should reset state when leaving', () => {
@@ -294,9 +294,8 @@ describe('DashboardPage', () => {
dashboardPageScenario('When in full kiosk mode', (ctx) => {
ctx.setup(() => {
locationService.partial({ kiosk: true });
ctx.mount({
queryParams: {},
queryParams: { kiosk: true },
dashboard: getTestDashboard(),
});
ctx.rerender({ dashboard: ctx.dashboard });

View File

@@ -53,6 +53,7 @@ export type DashboardPageRouteSearchParams = {
from?: string;
to?: string;
refresh?: string;
kiosk?: string | true;
};
export const mapStateToProps = (state: StoreState) => ({
@@ -85,7 +86,7 @@ export interface State {
editPanel: PanelModel | null;
viewPanel: PanelModel | null;
updateScrollTop?: number;
rememberScrollTop: number;
rememberScrollTop?: number;
showLoadingState: boolean;
panelNotFound: boolean;
editPanelAccessDenied: boolean;
@@ -103,7 +104,6 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
editPanel: null,
viewPanel: null,
showLoadingState: false,
rememberScrollTop: 0,
panelNotFound: false,
editPanelAccessDenied: false,
};
@@ -227,54 +227,58 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
return state;
}
state = updateStatePageNavFromProps(props, state);
const updatedState = { ...state };
// Entering edit mode
if (!state.editPanel && urlEditPanelId) {
const panel = dashboard.getPanelByUrlId(urlEditPanelId);
if (!panel) {
return { ...state, panelNotFound: true };
}
if (dashboard.canEditPanel(panel)) {
return { ...state, editPanel: panel, rememberScrollTop: state.scrollElement?.scrollTop };
if (panel) {
if (dashboard.canEditPanel(panel)) {
updatedState.editPanel = panel;
updatedState.rememberScrollTop = state.scrollElement?.scrollTop;
} else {
updatedState.editPanelAccessDenied = true;
}
} else {
return { ...state, editPanelAccessDenied: true };
updatedState.panelNotFound = true;
}
}
// Leaving edit mode
else if (state.editPanel && !urlEditPanelId) {
return { ...state, editPanel: null, updateScrollTop: state.rememberScrollTop };
updatedState.editPanel = null;
updatedState.updateScrollTop = state.rememberScrollTop;
}
// Entering view mode
if (!state.viewPanel && urlViewPanelId) {
const panel = dashboard.getPanelByUrlId(urlViewPanelId);
if (!panel) {
return { ...state, panelNotFound: urlEditPanelId };
if (panel) {
// This mutable state feels wrong to have in getDerivedStateFromProps
// Should move this state out of dashboard in the future
dashboard.initViewPanel(panel);
updatedState.viewPanel = panel;
updatedState.rememberScrollTop = state.scrollElement?.scrollTop;
updatedState.updateScrollTop = 0;
} else {
updatedState.panelNotFound = true;
}
// This mutable state feels wrong to have in getDerivedStateFromProps
// Should move this state out of dashboard in the future
dashboard.initViewPanel(panel);
return { ...state, viewPanel: panel, rememberScrollTop: state.scrollElement?.scrollTop, updateScrollTop: 0 };
}
// Leaving view mode
else if (state.viewPanel && !urlViewPanelId) {
// This mutable state feels wrong to have in getDerivedStateFromProps
// Should move this state out of dashboard in the future
dashboard.exitViewPanel(state.viewPanel);
return { ...state, viewPanel: null, updateScrollTop: state.rememberScrollTop };
updatedState.viewPanel = null;
updatedState.updateScrollTop = state.rememberScrollTop;
}
// if we removed url edit state, clear any panel not found state
if (state.panelNotFound || (state.editPanelAccessDenied && !urlEditPanelId)) {
return { ...state, panelNotFound: false, editPanelAccessDenied: false };
updatedState.panelNotFound = false;
updatedState.editPanelAccessDenied = false;
}
return state;
return updateStatePageNavFromProps(props, updatedState);
}
onAddPanel = () => {
@@ -325,7 +329,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
render() {
const { dashboard, initError, queryParams, isPublic } = this.props;
const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state;
const kioskMode = !isPublic ? getKioskMode() : KioskMode.Full;
const kioskMode = !isPublic ? getKioskMode(this.props.queryParams) : KioskMode.Full;
if (!dashboard || !pageNav || !sectionNav) {
return <DashboardLoading initPhase={this.props.initPhase} />;
@@ -348,6 +352,11 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
</header>
);
const pageClassName = cx({
'panel-in-fullscreen': Boolean(viewPanel),
'page-hidden': Boolean(queryParams.editview || editPanel),
});
return (
<>
<Page
@@ -355,7 +364,7 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
pageNav={pageNav}
layout={PageLayoutType.Canvas}
toolbar={toolbar}
className={cx(viewPanel && 'panel-in-fullscreen', queryParams.editview && 'dashboard-content--hidden')}
className={pageClassName}
scrollRef={this.setScrollRef}
scrollTop={updateScrollTop}
>
@@ -371,8 +380,16 @@ export class UnthemedDashboardPage extends PureComponent<Props, State> {
<DashboardGrid dashboard={dashboard} viewPanel={viewPanel} editPanel={editPanel} />
{inspectPanel && <PanelInspector dashboard={dashboard} panel={inspectPanel} />}
{editPanel && <PanelEditor dashboard={dashboard} sourcePanel={editPanel} tab={this.props.queryParams.tab} />}
</Page>
{editPanel && (
<PanelEditor
dashboard={dashboard}
sourcePanel={editPanel}
tab={this.props.queryParams.tab}
sectionNav={sectionNav}
pageNav={pageNav}
/>
)}
{queryParams.editview && (
<DashboardSettings
dashboard={dashboard}
@@ -429,6 +446,14 @@ function updateStatePageNavFromProps(props: Props, state: State): State {
sectionNav = getNavModel(props.navIndex, config.featureToggles.topnav ? 'dashboards/browse' : 'dashboards');
}
if (state.editPanel || state.viewPanel) {
pageNav = {
...pageNav,
text: `${state.editPanel ? 'Edit' : 'View'} panel`,
parentItem: pageNav,
};
}
if (state.pageNav === pageNav && state.sectionNav === sectionNav) {
return state;
}

View File

@@ -48,13 +48,13 @@ function SceneRenderer({ model }: SceneComponentProps<Scene>) {
}
const pageToolbar = config.featureToggles.topnav ? (
<AppChromeUpdate pageNav={{ text: title }} actions={toolbarActions} />
<AppChromeUpdate actions={toolbarActions} />
) : (
<PageToolbar title={title}>{toolbarActions}</PageToolbar>
);
return (
<Page navId="scenes" layout={PageLayoutType.Canvas} toolbar={pageToolbar}>
<Page navId="scenes" pageNav={{ text: title }} layout={PageLayoutType.Canvas} toolbar={pageToolbar}>
<div style={{ flexGrow: 1, display: 'flex', gap: '8px', overflow: 'auto' }}>
<layout.Component model={layout} isEditing={isEditing} />
{$editor && <$editor.Component model={$editor} isEditing={isEditing} />}