Url state -> dashboard model state sync starting to work

This commit is contained in:
Torkel Ödegaard
2019-02-03 12:29:47 +01:00
parent 8dec74689d
commit cba2ca5531
8 changed files with 128 additions and 80 deletions
@@ -13,6 +13,9 @@ import { DashboardModel } from '../../state/DashboardModel';
export interface Props { export interface Props {
dashboard: DashboardModel | null; dashboard: DashboardModel | null;
editview: string;
isEditing: boolean;
isFullscreen: boolean;
updateLocation: typeof updateLocation; updateLocation: typeof updateLocation;
} }
@@ -55,7 +55,5 @@
<i class="fa fa-reply"></i> <i class="fa fa-reply"></i>
</button> </button>
</div> </div>
</div> </div>
<dashboard-search></dashboard-search>
@@ -5,6 +5,9 @@ import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import classNames from 'classnames'; import classNames from 'classnames';
// Services & Utils
import { createErrorNotification } from 'app/core/copy/appNotification';
// Components // Components
import { LoadingPlaceholder } from '@grafana/ui'; import { LoadingPlaceholder } from '@grafana/ui';
import { DashboardGrid } from '../dashgrid/DashboardGrid'; import { DashboardGrid } from '../dashgrid/DashboardGrid';
@@ -14,34 +17,45 @@ import { DashboardSettings } from '../components/DashboardSettings';
// Redux // Redux
import { initDashboard } from '../state/initDashboard'; import { initDashboard } from '../state/initDashboard';
import { setDashboardModel } from '../state/actions'; import { setDashboardModel } from '../state/actions';
import { updateLocation } from 'app/core/actions';
import { notifyApp } from 'app/core/actions';
// Types // Types
import { StoreState } from 'app/types'; import { StoreState } from 'app/types';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { DashboardLoadingState } from 'app/types/dashboard'; import { DashboardLoadingState } from 'app/types/dashboard';
interface Props { interface Props {
panelId: string;
urlUid?: string; urlUid?: string;
urlSlug?: string; urlSlug?: string;
urlType?: string; urlType?: string;
editview: string; editview?: string;
urlPanelId?: string;
$scope: any; $scope: any;
$injector: any; $injector: any;
initDashboard: typeof initDashboard; urlEdit: boolean;
setDashboardModel: typeof setDashboardModel; urlFullscreen: boolean;
loadingState: DashboardLoadingState; loadingState: DashboardLoadingState;
dashboard: DashboardModel; dashboard: DashboardModel;
initDashboard: typeof initDashboard;
setDashboardModel: typeof setDashboardModel;
notifyApp: typeof notifyApp;
updateLocation: typeof updateLocation;
} }
interface State { interface State {
isSettingsOpening: boolean; isSettingsOpening: boolean;
isEditing: boolean;
isFullscreen: boolean;
fullscreenPanel: PanelModel | null;
} }
export class DashboardPage extends PureComponent<Props, State> { export class DashboardPage extends PureComponent<Props, State> {
state: State = { state: State = {
isSettingsOpening: false, isSettingsOpening: false,
isSettingsOpen: false, isEditing: false,
isFullscreen: false,
fullscreenPanel: null,
}; };
async componentDidMount() { async componentDidMount() {
@@ -55,30 +69,66 @@ export class DashboardPage extends PureComponent<Props, State> {
} }
componentDidUpdate(prevProps: Props) { componentDidUpdate(prevProps: Props) {
const { dashboard, editview } = this.props; const { dashboard, editview, urlEdit, urlFullscreen, urlPanelId } = this.props;
// when dashboard has loaded subscribe to somme events if (!dashboard) {
if (prevProps.dashboard === null && dashboard) { return;
dashboard.events.on('view-mode-changed', this.onViewModeChanged);
// set initial fullscreen class state
this.setPanelFullscreenClass();
} }
// handle animation states when opening dashboard settings
if (!prevProps.editview && editview) { if (!prevProps.editview && editview) {
this.setState({ isSettingsOpening: true }); this.setState({ isSettingsOpening: true });
setTimeout(() => { setTimeout(() => {
this.setState({ isSettingsOpening: false}); this.setState({ isSettingsOpening: false });
}, 10); }, 10);
} }
// // when dashboard has loaded subscribe to somme events
// if (prevProps.dashboard === null) {
// // set initial fullscreen class state
// this.setPanelFullscreenClass();
// }
// Sync url state with model
if (urlFullscreen !== dashboard.meta.isFullscreen || urlEdit !== dashboard.meta.isEditing) {
// entering fullscreen/edit mode
if (urlPanelId) {
const panel = dashboard.getPanelById(parseInt(urlPanelId, 10));
if (panel) {
dashboard.setViewMode(panel, urlFullscreen, urlEdit);
this.setState({ isEditing: urlEdit, isFullscreen: urlFullscreen, fullscreenPanel: panel });
} else {
this.handleFullscreenPanelNotFound(urlPanelId);
}
} else {
// handle leaving fullscreen mode
if (this.state.fullscreenPanel) {
dashboard.setViewMode(this.state.fullscreenPanel, urlFullscreen, urlEdit);
}
this.setState({ isEditing: urlEdit, isFullscreen: urlFullscreen, fullscreenPanel: null });
}
this.setPanelFullscreenClass(urlFullscreen);
}
} }
onViewModeChanged = () => { handleFullscreenPanelNotFound(urlPanelId: string) {
this.setPanelFullscreenClass(); // Panel not found
}; this.props.notifyApp(createErrorNotification(`Panel with id ${urlPanelId} not found`));
// Clear url state
this.props.updateLocation({
query: {
edit: null,
fullscreen: null,
panelId: null,
},
partial: true
});
}
setPanelFullscreenClass() { setPanelFullscreenClass(isFullscreen: boolean) {
$('body').toggleClass('panel-in-fullscreen', this.props.dashboard.meta.fullscreen === true); $('body').toggleClass('panel-in-fullscreen', isFullscreen);
} }
componentWillUnmount() { componentWillUnmount() {
@@ -94,10 +144,11 @@ export class DashboardPage extends PureComponent<Props, State> {
renderDashboard() { renderDashboard() {
const { dashboard, editview } = this.props; const { dashboard, editview } = this.props;
const { isEditing, isFullscreen } = this.state;
const classes = classNames({ const classes = classNames({
'dashboard-container': true, 'dashboard-container': true,
'dashboard-container--has-submenu': dashboard.meta.submenuEnabled 'dashboard-container--has-submenu': dashboard.meta.submenuEnabled,
}); });
return ( return (
@@ -105,7 +156,7 @@ export class DashboardPage extends PureComponent<Props, State> {
{dashboard && editview && <DashboardSettings dashboard={dashboard} />} {dashboard && editview && <DashboardSettings dashboard={dashboard} />}
<div className={classes}> <div className={classes}>
<DashboardGrid dashboard={dashboard} /> <DashboardGrid dashboard={dashboard} isEditing={isEditing} isFullscreen={isFullscreen} />
</div> </div>
</div> </div>
); );
@@ -113,7 +164,7 @@ export class DashboardPage extends PureComponent<Props, State> {
render() { render() {
const { dashboard, editview } = this.props; const { dashboard, editview } = this.props;
const { isSettingsOpening } = this.state; const { isSettingsOpening, isEditing, isFullscreen } = this.state;
const classes = classNames({ const classes = classNames({
'dashboard-page--settings-opening': isSettingsOpening, 'dashboard-page--settings-opening': isSettingsOpening,
@@ -122,7 +173,7 @@ export class DashboardPage extends PureComponent<Props, State> {
return ( return (
<div className={classes}> <div className={classes}>
<DashNav dashboard={dashboard} /> <DashNav dashboard={dashboard} isEditing={isEditing} isFullscreen={isFullscreen} editview={editview} />
{!dashboard && this.renderLoadingState()} {!dashboard && this.renderLoadingState()}
{dashboard && this.renderDashboard()} {dashboard && this.renderDashboard()}
</div> </div>
@@ -130,19 +181,26 @@ export class DashboardPage extends PureComponent<Props, State> {
} }
} }
const mapStateToProps = (state: StoreState) => ({ const mapStateToProps = (state: StoreState) => {
urlUid: state.location.routeParams.uid, console.log('state location', state.location.query);
urlSlug: state.location.routeParams.slug, return {
urlType: state.location.routeParams.type, urlUid: state.location.routeParams.uid,
panelId: state.location.query.panelId, urlSlug: state.location.routeParams.slug,
editview: state.location.query.editview, urlType: state.location.routeParams.type,
loadingState: state.dashboard.loadingState, editview: state.location.query.editview,
dashboard: state.dashboard.model as DashboardModel, urlPanelId: state.location.query.panelId,
}); urlFullscreen: state.location.query.fullscreen === true,
urlEdit: state.location.query.edit === true,
loadingState: state.dashboard.loadingState,
dashboard: state.dashboard.model as DashboardModel,
};
};
const mapDispatchToProps = { const mapDispatchToProps = {
initDashboard, initDashboard,
setDashboardModel setDashboardModel,
notifyApp,
updateLocation,
}; };
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage)); export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(DashboardPage));
@@ -1,11 +1,14 @@
import React from 'react'; // Libaries
import React, { PureComponent } from 'react';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import ReactGridLayout, { ItemCallback } from 'react-grid-layout'; import ReactGridLayout, { ItemCallback } from 'react-grid-layout';
import classNames from 'classnames';
import sizeMe from 'react-sizeme';
// Types
import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants'; import { GRID_CELL_HEIGHT, GRID_CELL_VMARGIN, GRID_COLUMN_COUNT } from 'app/core/constants';
import { DashboardPanel } from './DashboardPanel'; import { DashboardPanel } from './DashboardPanel';
import { DashboardModel, PanelModel } from '../state'; import { DashboardModel, PanelModel } from '../state';
import classNames from 'classnames';
import sizeMe from 'react-sizeme';
let lastGridWidth = 1200; let lastGridWidth = 1200;
let ignoreNextWidthChange = false; let ignoreNextWidthChange = false;
@@ -76,19 +79,18 @@ function GridWrapper({
const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper); const SizedReactLayoutGrid = sizeMe({ monitorWidth: true })(GridWrapper);
export interface DashboardGridProps { export interface Props {
dashboard: DashboardModel; dashboard: DashboardModel;
isEditing: boolean;
isFullscreen: boolean;
} }
export class DashboardGrid extends React.Component<DashboardGridProps> { export class DashboardGrid extends PureComponent<Props> {
gridToPanelMap: any; gridToPanelMap: any;
panelMap: { [id: string]: PanelModel }; panelMap: { [id: string]: PanelModel };
constructor(props: DashboardGridProps) { componentDidMount() {
super(props); const { dashboard } = this.props;
// subscribe to dashboard events
const dashboard = this.props.dashboard;
dashboard.on('panel-added', this.triggerForceUpdate); dashboard.on('panel-added', this.triggerForceUpdate);
dashboard.on('panel-removed', this.triggerForceUpdate); dashboard.on('panel-removed', this.triggerForceUpdate);
dashboard.on('repeats-processed', this.triggerForceUpdate); dashboard.on('repeats-processed', this.triggerForceUpdate);
@@ -97,6 +99,16 @@ export class DashboardGrid extends React.Component<DashboardGridProps> {
dashboard.on('row-expanded', this.triggerForceUpdate); dashboard.on('row-expanded', this.triggerForceUpdate);
} }
componentWillUnmount() {
const { dashboard } = this.props;
dashboard.off('panel-added', this.triggerForceUpdate);
dashboard.off('panel-removed', this.triggerForceUpdate);
dashboard.off('repeats-processed', this.triggerForceUpdate);
dashboard.off('view-mode-changed', this.onViewModeChanged);
dashboard.off('row-collapsed', this.triggerForceUpdate);
dashboard.off('row-expanded', this.triggerForceUpdate);
}
buildLayout() { buildLayout() {
const layout = []; const layout = [];
this.panelMap = {}; this.panelMap = {};
@@ -151,7 +163,6 @@ export class DashboardGrid extends React.Component<DashboardGridProps> {
onViewModeChanged = () => { onViewModeChanged = () => {
ignoreNextWidthChange = true; ignoreNextWidthChange = true;
this.forceUpdate();
} }
updateGridPos = (item: ReactGridLayout.Layout, layout: ReactGridLayout.Layout[]) => { updateGridPos = (item: ReactGridLayout.Layout, layout: ReactGridLayout.Layout[]) => {
@@ -197,18 +208,20 @@ export class DashboardGrid extends React.Component<DashboardGridProps> {
} }
render() { render() {
const { dashboard, isFullscreen } = this.props;
return ( return (
<SizedReactLayoutGrid <SizedReactLayoutGrid
className={classNames({ layout: true })} className={classNames({ layout: true })}
layout={this.buildLayout()} layout={this.buildLayout()}
isResizable={this.props.dashboard.meta.canEdit} isResizable={dashboard.meta.canEdit}
isDraggable={this.props.dashboard.meta.canEdit} isDraggable={dashboard.meta.canEdit}
onLayoutChange={this.onLayoutChange} onLayoutChange={this.onLayoutChange}
onWidthChange={this.onWidthChange} onWidthChange={this.onWidthChange}
onDragStop={this.onDragStop} onDragStop={this.onDragStop}
onResize={this.onResize} onResize={this.onResize}
onResizeStop={this.onResizeStop} onResizeStop={this.onResizeStop}
isFullscreen={this.props.dashboard.meta.fullscreen} isFullscreen={isFullscreen}
> >
{this.renderPanels()} {this.renderPanels()}
</SizedReactLayoutGrid> </SizedReactLayoutGrid>
@@ -98,8 +98,6 @@ export class DashboardViewStateSrv {
if (fromRouteUpdated !== true) { if (fromRouteUpdated !== true) {
this.$location.search(this.serializeToUrl()); this.$location.search(this.serializeToUrl());
} }
this.syncState();
} }
toggleCollapsedPanelRow(panelId) { toggleCollapsedPanelRow(panelId) {
@@ -115,34 +113,6 @@ export class DashboardViewStateSrv {
} }
} }
syncState() {
if (this.state.fullscreen) {
const panel = this.dashboard.getPanelById(this.state.panelId);
if (!panel) {
this.state.fullscreen = null;
this.state.panelId = null;
this.state.edit = null;
this.update(this.state);
setTimeout(() => {
appEvents.emit('alert-error', ['Error', 'Panel not found']);
}, 100);
return;
}
if (!panel.fullscreen) {
this.enterFullscreen(panel);
} else if (this.dashboard.meta.isEditing !== this.state.edit) {
this.dashboard.setViewMode(panel, this.state.fullscreen, this.state.edit);
}
} else if (this.fullscreenPanel) {
this.leaveFullscreen();
}
}
leaveFullscreen() { leaveFullscreen() {
const panel = this.fullscreenPanel; const panel = this.fullscreenPanel;
+2
View File
@@ -165,6 +165,8 @@ export function grafanaAppDirective(playlistSrv, contextSrv, $timeout, $rootScop
for (const drop of Drop.drops) { for (const drop of Drop.drops) {
drop.destroy(); drop.destroy();
} }
appEvents.emit('hide-dash-search');
}); });
// handle kiosk mode // handle kiosk mode
+4
View File
@@ -1,6 +1,10 @@
import { DashboardAcl } from './acl'; import { DashboardAcl } from './acl';
export interface MutableDashboard { export interface MutableDashboard {
meta: {
fullscreen: boolean;
isEditing: boolean;
}
} }
export enum DashboardLoadingState { export enum DashboardLoadingState {
+1 -1
View File
@@ -189,7 +189,7 @@
<grafana-app class="grafana-app" ng-cloak> <grafana-app class="grafana-app" ng-cloak>
<sidemenu class="sidemenu"></sidemenu> <sidemenu class="sidemenu"></sidemenu>
<app-notifications-list class="page-alert-list"></app-notifications-list> <app-notifications-list class="page-alert-list"></app-notifications-list>
<dashboard-search></dashboard-search>
<div class="main-view"> <div class="main-view">
<div class="scroll-canvas" page-scrollbar> <div class="scroll-canvas" page-scrollbar>