Reactify full screen mode placeholder and remove from scope (#14886) (#15061)

* Reactify full screen mode placeholder and remove from scope

With the toggle in react, no need to store it on scope.

* Simplify implementation by bringing in chrome.setVisible internal to the react placeholder

* rename FullScreenModePlaceholder => ExitFullScreenButton

* div => KuiButton

* Add jest tests for exit full screen button

* Use native Object.values.map instead of loadash _.map

* get rid of unnecessary return value
This commit is contained in:
Stacey Gammon 2017-11-20 15:40:42 -05:00 committed by GitHub
parent 2a4600f84a
commit 3a7b2f52be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 208 additions and 71 deletions

View file

@ -0,0 +1,33 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`is rendered 1`] = `
<div
class="exitFullScreenButton"
>
<button
aria-label="Exit full screen mode"
class="kuiButton exitFullScreenMode"
type="hollow"
>
<span
class="kuiButton__inner"
>
<span>
<span
class="exitFullScreenModeLogo"
data-test-subj="exitFullScreenModeLogo"
/>
<span
class="exitFullScreenModeText"
data-test-subj="exitFullScreenModeText"
>
Exit full screen
<span
class="kuiIcon fa fa-angle-left"
/>
</span>
</span>
</span>
</button>
</div>
`;

View file

@ -0,0 +1,56 @@
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import chrome from 'ui/chrome';
import {
KuiButton,
} from 'ui_framework/components';
import {
keyCodes,
} from 'ui_framework/services';
export class ExitFullScreenButton extends PureComponent {
onKeyDown = (e) => {
if (e.keyCode === keyCodes.ESCAPE) {
this.props.onExitFullScreenMode();
}
};
componentWillMount() {
document.addEventListener('keydown', this.onKeyDown, false);
chrome.setVisible(false);
}
componentWillUnmount() {
document.removeEventListener('keydown', this.onKeyDown, false);
chrome.setVisible(true);
}
render() {
return (
<div
className="exitFullScreenButton"
>
<KuiButton
type="hollow"
aria-label="Exit full screen mode"
className="exitFullScreenMode"
onClick={this.props.onExitFullScreenMode}
>
<span className="exitFullScreenModeLogo" data-test-subj="exitFullScreenModeLogo"/>
<span className="exitFullScreenModeText" data-test-subj="exitFullScreenModeText">
Exit full screen
<span className="kuiIcon fa fa-angle-left"/>
</span>
</KuiButton>
</div>
);
}
}
ExitFullScreenButton.propTypes = {
onExitFullScreenMode: PropTypes.func.isRequired,
};

View file

@ -0,0 +1,78 @@
jest.mock('ui/chrome',
() => ({
getKibanaVersion: () => '6.0.0',
setVisible: () => {},
}), { virtual: true });
import React from 'react';
import { render, mount } from 'enzyme';
import sinon from 'sinon';
import chrome from 'ui/chrome';
import {
ExitFullScreenButton,
} from './exit_full_screen_button';
import { keyCodes } from 'ui_framework/services';
test('is rendered', () => {
const component = render(
<ExitFullScreenButton onExitFullScreenMode={() => {}}/>
);
expect(component)
.toMatchSnapshot();
});
describe('onExitFullScreenMode', () => {
test('is called when the button is pressed', () => {
const onExitHandler = sinon.stub();
const component = mount(
<ExitFullScreenButton onExitFullScreenMode={onExitHandler} />
);
component.find('button').simulate('click');
sinon.assert.calledOnce(onExitHandler);
});
test('is called when the ESC key is pressed', () => {
const onExitHandler = sinon.stub();
mount(<ExitFullScreenButton onExitFullScreenMode={onExitHandler} />);
const escapeKeyEvent = new KeyboardEvent('keydown', { keyCode: keyCodes.ESCAPE });
document.dispatchEvent(escapeKeyEvent);
sinon.assert.calledOnce(onExitHandler);
});
});
describe('chrome.setVisible', () => {
test('is called with false when the component is rendered', () => {
chrome.setVisible = sinon.stub();
const component = mount(
<ExitFullScreenButton onExitFullScreenMode={() => {}} />
);
component.find('button').simulate('click');
sinon.assert.calledOnce(chrome.setVisible);
sinon.assert.calledWith(chrome.setVisible, false);
});
test('is called with true the component is unmounted', () => {
const component = mount(
<ExitFullScreenButton onExitFullScreenMode={() => {}} />
);
chrome.setVisible = sinon.stub();
component.unmount();
sinon.assert.calledOnce(chrome.setVisible);
sinon.assert.calledWith(chrome.setVisible, true);
});
});

View file

@ -2,21 +2,6 @@
class="app-container dashboard-container"
ng-class="{'dashboard-container-with-margins': model.useMargins}"
>
<div class="fullScreenModePlaceholder">
<div
aria-label="Exit full screen mode"
kbn-accessible-click
ng-if="fullScreenMode"
class="exitFullScreenMode"
ng-click="exitFullScreenMode()"
>
<span class="exitFullScreenModeLogo" data-test-subj="exitFullScreenModeLogo"></span>
<span class="exitFullScreenModeText" data-test-subj="exitFullScreenModeText">
Exit full screen
<span class="kuiIcon fa fa-angle-left"></span>
</span>
</div>
</div>
<!-- Local nav. -->
<kbn-top-nav name="dashboard" config="topNavMenu">
<!-- Transcluded elements. -->

View file

@ -18,7 +18,6 @@ import { DashboardStateManager } from './dashboard_state_manager';
import { saveDashboard } from './lib';
import { showCloneModal } from './top_nav/show_clone_modal';
import { migrateLegacyQuery } from 'ui/utils/migrateLegacyQuery';
import { keyCodes } from 'ui_framework/services';
import { DashboardContainerAPI } from './dashboard_container_api';
import * as filterActions from 'ui/doc_table/actions/filter';
import { FilterManagerProvider } from 'ui/filter_manager';
@ -98,7 +97,6 @@ app.directive('dashboardApp', function ($injector) {
description: dashboardStateManager.getDescription(),
};
$scope.panels = dashboardStateManager.getPanels();
$scope.fullScreenMode = dashboardStateManager.getFullScreenMode();
$scope.indexPatterns = dashboardStateManager.getPanelIndexPatterns();
};
@ -268,50 +266,19 @@ app.directive('dashboardApp', function ($injector) {
}).catch(notify.error);
};
$scope.showFilterBar = () => filterBar.getFilters().length > 0 || !$scope.fullScreenMode;
let onRouteChange;
const setFullScreenMode = (fullScreenMode) => {
$scope.fullScreenMode = fullScreenMode;
dashboardStateManager.setFullScreenMode(fullScreenMode);
chrome.setVisible(!fullScreenMode);
$scope.$broadcast('reLayout');
// Make sure that if we exit the dashboard app, the chrome becomes visible again
// (e.g. if the user clicks the back button).
if (fullScreenMode) {
onRouteChange = $scope.$on('$routeChangeStart', () => {
chrome.setVisible(true);
onRouteChange();
});
} else if (onRouteChange) {
onRouteChange();
}
};
$scope.$watch('fullScreenMode', () => setFullScreenMode(dashboardStateManager.getFullScreenMode()));
$scope.exitFullScreenMode = () => setFullScreenMode(false);
document.addEventListener('keydown', (e) => {
if (e.keyCode === keyCodes.ESCAPE) {
setFullScreenMode(false);
}
}, false);
$scope.showFilterBar = () => filterBar.getFilters().length > 0 || !dashboardStateManager.getFullScreenMode();
$scope.showAddPanel = () => {
if ($scope.fullScreenMode) {
$scope.exitFullScreenMode();
}
dashboardStateManager.setFullScreenMode(false);
$scope.kbnTopNav.open('add');
};
$scope.enterEditMode = () => {
if ($scope.fullScreenMode) {
$scope.exitFullScreenMode();
}
dashboardStateManager.setFullScreenMode(false);
$scope.kbnTopNav.click('edit');
};
const navActions = {};
navActions[TopNavIds.FULL_SCREEN] = () => setFullScreenMode(true);
navActions[TopNavIds.FULL_SCREEN] = () =>
dashboardStateManager.setFullScreenMode(true);
navActions[TopNavIds.EXIT_EDIT_MODE] = () => onChangeViewMode(DashboardViewMode.VIEW);
navActions[TopNavIds.ENTER_EDIT_MODE] = () => onChangeViewMode(DashboardViewMode.EDIT);
navActions[TopNavIds.CLONE] = () => {

View file

@ -45,7 +45,7 @@ import {
* - maximizedPanelId
*
* State that is shared and needs to be synced:
* - fullScreenMode - changes only propagate from AppState -> Store
* - fullScreenMode - changes propagate from AppState -> Store and from Store -> AppState.
* - viewMode - changes only propagate from AppState -> Store
* - panels - changes propagate from AppState -> Store and from Store -> AppState.
*
@ -152,21 +152,22 @@ export class DashboardStateManager {
}
_handleStoreChanges() {
if (this._areStoreAndAppStatePanelsEqual()) {
return;
let dirty = false;
if (!this._areStoreAndAppStatePanelsEqual()) {
const panels = getPanels(store.getState());
this.appState.panels = [];
Object.values(panels).map(panel => {
this.appState.panels.push(panel);
});
dirty = true;
}
const state = store.getState();
// The only state that the store deals with that appState cares about is the panels array. Every other state change
// (that appState cares about) is initiated from appState (e.g. view mode).
this.appState.panels = [];
_.map(getPanels(state), panel => {
this.appState.panels.push(panel);
});
const fullScreen = getFullScreenMode(store.getState());
if (fullScreen !== this.getFullScreenMode()) {
this.setFullScreenMode(fullScreen);
}
this.changeListeners.forEach(function (listener) {
return listener({ dirty: true, clean: false });
});
this.changeListeners.forEach(listener => listener({ dirty }));
this.saveState();
}

View file

@ -4,7 +4,7 @@
@import "~react-grid-layout/css/styles.css";
@import "~react-resizable/css/styles.css";
.fullScreenModePlaceholder {
.exitFullScreenButton {
text-align: center;
width: 100%;
height: 0;

View file

@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { DashboardGrid } from '../grid';
import { ExitFullScreenButton } from '../components/exit_full_screen_button';
export function DashboardViewport({
getContainerApi,
@ -10,6 +11,8 @@ export function DashboardViewport({
title,
description,
useMargins,
isFullScreenMode,
onExitFullScreenMode,
}) {
return (
<div
@ -19,6 +22,7 @@ export function DashboardViewport({
data-description={description}
className={useMargins ? 'dashboard-viewport-with-margins' : 'dashboard-viewport'}
>
{ isFullScreenMode && <ExitFullScreenButton onExitFullScreenMode={onExitFullScreenMode} /> }
<DashboardGrid
getEmbeddableFactory={getEmbeddableFactory}
getContainerApi={getContainerApi}

View file

@ -1,6 +1,14 @@
import { connect } from 'react-redux';
import { DashboardViewport } from './dashboard_viewport';
import { getMaximizedPanelId, getPanels, getTitle, getDescription, getUseMargins } from '../selectors';
import { updateIsFullScreenMode } from '../actions';
import {
getMaximizedPanelId,
getPanels,
getTitle,
getDescription,
getUseMargins,
getFullScreenMode,
} from '../selectors';
const mapStateToProps = ({ dashboard }) => {
const maximizedPanelId = getMaximizedPanelId(dashboard);
@ -10,10 +18,15 @@ const mapStateToProps = ({ dashboard }) => {
description: getDescription(dashboard),
title: getTitle(dashboard),
useMargins: getUseMargins(dashboard),
isFullScreenMode: getFullScreenMode(dashboard),
};
};
export const DashboardViewportContainer = connect(
mapStateToProps
)(DashboardViewport);
const mapDispatchToProps = (dispatch) => ({
onExitFullScreenMode: () => dispatch(updateIsFullScreenMode(false)),
});
export const DashboardViewportContainer = connect(
mapStateToProps,
mapDispatchToProps,
)(DashboardViewport);