From 3267d9270e498243eb9858a525451520fad4dd6f Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 1 Dec 2020 07:45:41 +0100 Subject: [PATCH 01/25] chore: refactor Visualization item to separate Item from Plugin --- src/components/Item/VisualizationItem/Item.js | 189 +++--------------- .../VisualizationItem/ItemHeaderButtons.js | 2 +- .../{ => Visualization}/DefaultPlugin.js | 4 +- .../{ => Visualization}/LoadingMask.js | 0 .../{ => Visualization}/MapPlugin.js | 2 +- .../NoVisualizationMessage.js | 0 .../Visualization/Visualization.js | 136 +++++++++++++ .../__tests__/Visualization.spec.js | 134 +++++++++++++ .../__snapshots__/Visualization.spec.js.snap | 119 +++++++++++ .../__tests__/getVisualizationConfig.spec.js | 9 +- .../Visualization/getFilteredVisualization.js | 42 ++++ .../getVisualizationConfig.js | 2 +- .../{ => Visualization}/plugin.js | 8 +- .../styles/LoadingMask.module.css | 0 .../styles/NoVisualizationMessage.module.css | 0 .../VisualizationItem/__tests__/Item.spec.js | 106 ++-------- .../__tests__/ItemHeaderButtons.spec.js | 2 +- .../__tests__/__snapshots__/Item.spec.js.snap | 21 +- src/components/ItemGrid/ItemGrid.js | 2 +- 19 files changed, 495 insertions(+), 283 deletions(-) rename src/components/Item/VisualizationItem/{ => Visualization}/DefaultPlugin.js (96%) rename src/components/Item/VisualizationItem/{ => Visualization}/LoadingMask.js (100%) rename src/components/Item/VisualizationItem/{ => Visualization}/MapPlugin.js (96%) rename src/components/Item/VisualizationItem/{ => Visualization}/NoVisualizationMessage.js (100%) create mode 100644 src/components/Item/VisualizationItem/Visualization/Visualization.js create mode 100644 src/components/Item/VisualizationItem/Visualization/__tests__/Visualization.spec.js create mode 100644 src/components/Item/VisualizationItem/Visualization/__tests__/__snapshots__/Visualization.spec.js.snap rename src/components/Item/VisualizationItem/{ => Visualization}/__tests__/getVisualizationConfig.spec.js (98%) create mode 100644 src/components/Item/VisualizationItem/Visualization/getFilteredVisualization.js rename src/components/Item/VisualizationItem/{ => Visualization}/getVisualizationConfig.js (95%) rename src/components/Item/VisualizationItem/{ => Visualization}/plugin.js (89%) rename src/components/Item/VisualizationItem/{ => Visualization}/styles/LoadingMask.module.css (100%) rename src/components/Item/VisualizationItem/{ => Visualization}/styles/NoVisualizationMessage.module.css (100%) diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index ef2e560de..6ecd60d73 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -2,20 +2,14 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { connect } from 'react-redux' import uniqueId from 'lodash/uniqueId' -import VisualizationPlugin from '@dhis2/data-visualizer-plugin' -import i18n from '@dhis2/d2-i18n' -import DefaultPlugin from './DefaultPlugin' -import MapPlugin from './MapPlugin' +import Visualization from './Visualization/Visualization' import FatalErrorBoundary from './FatalErrorBoundary' import ItemHeader, { HEADER_MARGIN_HEIGHT } from '../ItemHeader/ItemHeader' import ItemHeaderButtons from './ItemHeaderButtons' import ItemFooter from './ItemFooter' -import LoadingMask from './LoadingMask' -import NoVisualizationMessage from './NoVisualizationMessage' import { apiFetchVisualization } from '../../../api/metadata' -import getVisualizationConfig from './getVisualizationConfig' import { sGetVisualization } from '../../../reducers/visualizations' import { sGetSelectedItemActiveType } from '../../../reducers/selected' import { sGetIsEditing } from '../../../reducers/editDashboard' @@ -25,12 +19,7 @@ import { } from '../../../reducers/itemFilters' import { acAddVisualization } from '../../../actions/visualizations' import { acSetSelectedItemActiveType } from '../../../actions/selected' -import { - VISUALIZATION, - MAP, - CHART, - REPORT_TABLE, -} from '../../../modules/itemTypes' + import { getVisualizationId, getVisualizationName } from '../../../modules/item' import memoizeOne from '../../../modules/memoizeOne' import { @@ -45,7 +34,6 @@ export class Item extends Component { state = { showFooter: false, configLoaded: false, - pluginIsLoaded: false, } constructor(props, context) { @@ -56,11 +44,12 @@ export class Item extends Component { this.contentRef = React.createRef() this.headerRef = React.createRef() - this.memoizedApplyFilters = memoizeOne(this.applyFilters) - - this.memoizedGetVisualizationConfig = memoizeOne(getVisualizationConfig) - - this.memoizedGetContentHeight = memoizeOne(this.getContentHeight) + this.memoizedGetContentHeight = memoizeOne( + (calculatedHeight, measuredHeight, preferMeasured) => + preferMeasured + ? measuredHeight || calculatedHeight + : calculatedHeight + ) } async componentDidMount() { @@ -73,136 +62,8 @@ export class Item extends Component { }) } - componentDidUpdate(prevProps, prevState) { - if ( - prevState.pluginIsLoaded && - (prevProps.visualization !== this.props.visualization || - prevProps.itemFilters !== this.props.itemFilters) - ) { - this.setState({ - pluginIsLoaded: false, - }) - } - } - - applyFilters = (visualization, filters) => { - if (!Object.keys(filters).length) { - return visualization - } - - // deep clone objects in filters to avoid changing the visualization in the Redux store - const visRows = visualization.rows.map(obj => ({ ...obj })) - const visColumns = visualization.columns.map(obj => ({ ...obj })) - const visFilters = visualization.filters.map(obj => ({ ...obj })) - - Object.keys(filters).forEach(dimensionId => { - if (filters[dimensionId]) { - let dimensionFound = false - - ;[visRows, visColumns, visFilters].forEach(dimensionObjects => - dimensionObjects - .filter(obj => obj.dimension === dimensionId) - .forEach(obj => { - dimensionFound = true - obj.items = filters[dimensionId] - }) - ) - - // add dimension to filters if not already present elsewhere - if (!dimensionFound) { - visFilters.push({ - dimension: dimensionId, - items: filters[dimensionId], - }) - } - } - }) - - return { - ...visualization, - rows: visRows, - columns: visColumns, - filters: visFilters, - } - } - getUniqueKey = memoizeOne(() => uniqueId()) - pluginCredentials = null - - getPluginComponent = () => { - const activeType = this.getActiveType() - const visualization = this.memoizedGetVisualizationConfig( - this.props.visualization, - this.props.item.type, - activeType - ) - - if (!visualization) { - return ( - - ) - } - - const props = { - item: this.props.item, - itemFilters: this.props.itemFilters, - activeType, - visualization, - style: this.getPluginStyle(), - } - - switch (activeType) { - case VISUALIZATION: - case CHART: - case REPORT_TABLE: { - return ( - <> - {!this.state.pluginIsLoaded && ( -
- -
- )} - - - ) - } - case MAP: { - return ( - - ) - } - default: { - props.visualization = this.memoizedApplyFilters( - props.visualization, - props.itemFilters - ) - - return - } - } - } - - onLoadingComplete = () => { - this.setState({ - pluginIsLoaded: true, - }) - } - onToggleFooter = () => { this.setState( { showFooter: !this.state.showFooter }, @@ -210,9 +71,9 @@ export class Item extends Component { ) } - selectActiveType = type => { + setActiveType = type => { type !== this.getActiveType() && - this.props.selectActiveType(this.props.item.id, type) + this.props.setActiveType(this.props.item.id, type) } getActiveType = () => { @@ -222,7 +83,7 @@ export class Item extends Component { return this.props.activeType || this.props.item.type } - getPluginStyle = () => { + getAvailableHeight = () => { const calculatedHeight = this.props.item.originalHeight - this.headerRef.current.clientHeight - @@ -237,26 +98,19 @@ export class Item extends Component { ) } - getContentHeight = (calculatedHeight, measuredHeight, preferMeasured) => { - const height = preferMeasured - ? measuredHeight || calculatedHeight - : calculatedHeight - - return { height } - } - render() { const { item, dashboardMode, itemFilters } = this.props const { showFooter } = this.state + const activeType = this.getActiveType() const actionButtons = ( ) @@ -276,7 +130,14 @@ export class Item extends Component { className="dashboard-item-content" ref={ref => (this.contentRef = ref)} > - {this.state.configLoaded && this.getPluginComponent()} + {this.state.configLoaded && ( + + )} {isViewMode(dashboardMode) && showFooter ? ( @@ -297,7 +158,7 @@ Item.propTypes = { isEditing: PropTypes.bool, item: PropTypes.object, itemFilters: PropTypes.object, - selectActiveType: PropTypes.func, + setActiveType: PropTypes.func, updateVisualization: PropTypes.func, visualization: PropTypes.object, onToggleItemExpanded: PropTypes.func, @@ -326,7 +187,7 @@ const mapStateToProps = (state, ownProps) => { } const mapDispatchToProps = { - selectActiveType: acSetSelectedItemActiveType, + setActiveType: acSetSelectedItemActiveType, updateVisualization: acAddVisualization, } diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index 94036a764..5eb1f24df 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -15,7 +15,7 @@ import MapIcon from '@material-ui/icons/Public' import LaunchIcon from '@material-ui/icons/Launch' import { ThreeDots, SpeechBubble } from './assets/icons' -import { pluginIsAvailable, getLink } from './plugin' +import { pluginIsAvailable, getLink } from './Visualization/plugin' import { CHART, MAP, diff --git a/src/components/Item/VisualizationItem/DefaultPlugin.js b/src/components/Item/VisualizationItem/Visualization/DefaultPlugin.js similarity index 96% rename from src/components/Item/VisualizationItem/DefaultPlugin.js rename to src/components/Item/VisualizationItem/Visualization/DefaultPlugin.js index 72b4f849e..eeb39568b 100644 --- a/src/components/Item/VisualizationItem/DefaultPlugin.js +++ b/src/components/Item/VisualizationItem/Visualization/DefaultPlugin.js @@ -5,8 +5,8 @@ import i18n from '@dhis2/d2-i18n' import NoVisualizationMessage from './NoVisualizationMessage' import * as pluginManager from './plugin' -import { getBaseUrl, orObject } from '../../../modules/util' -import { getGridItemDomId } from '../../ItemGrid/gridUtil' +import { getBaseUrl, orObject } from '../../../../modules/util' +import { getGridItemDomId } from '../../../ItemGrid/gridUtil' const pluginCredentials = d2 => { return { diff --git a/src/components/Item/VisualizationItem/LoadingMask.js b/src/components/Item/VisualizationItem/Visualization/LoadingMask.js similarity index 100% rename from src/components/Item/VisualizationItem/LoadingMask.js rename to src/components/Item/VisualizationItem/Visualization/LoadingMask.js diff --git a/src/components/Item/VisualizationItem/MapPlugin.js b/src/components/Item/VisualizationItem/Visualization/MapPlugin.js similarity index 96% rename from src/components/Item/VisualizationItem/MapPlugin.js rename to src/components/Item/VisualizationItem/Visualization/MapPlugin.js index ff16ff87c..793be9f5d 100644 --- a/src/components/Item/VisualizationItem/MapPlugin.js +++ b/src/components/Item/VisualizationItem/Visualization/MapPlugin.js @@ -1,7 +1,7 @@ import React from 'react' import PropTypes from 'prop-types' import DefaultPlugin from './DefaultPlugin' -import { MAP } from '../../../modules/itemTypes' +import { MAP } from '../../../../modules/itemTypes' const MapPlugin = ({ applyFilters, ...props }) => { if (props.item.type === MAP) { diff --git a/src/components/Item/VisualizationItem/NoVisualizationMessage.js b/src/components/Item/VisualizationItem/Visualization/NoVisualizationMessage.js similarity index 100% rename from src/components/Item/VisualizationItem/NoVisualizationMessage.js rename to src/components/Item/VisualizationItem/Visualization/NoVisualizationMessage.js diff --git a/src/components/Item/VisualizationItem/Visualization/Visualization.js b/src/components/Item/VisualizationItem/Visualization/Visualization.js new file mode 100644 index 000000000..b008aa5c6 --- /dev/null +++ b/src/components/Item/VisualizationItem/Visualization/Visualization.js @@ -0,0 +1,136 @@ +import React from 'react' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import VisualizationPlugin from '@dhis2/data-visualizer-plugin' +import i18n from '@dhis2/d2-i18n' + +import DefaultPlugin from './DefaultPlugin' +import MapPlugin from './MapPlugin' +import LoadingMask from './LoadingMask' +import NoVisualizationMessage from './NoVisualizationMessage' + +import getFilteredVisualization from './getFilteredVisualization' +import getVisualizationConfig from './getVisualizationConfig' +import { + VISUALIZATION, + MAP, + CHART, + REPORT_TABLE, +} from '../../../../modules/itemTypes' +import { getVisualizationId } from '../../../../modules/item' +import memoizeOne from '../../../../modules/memoizeOne' +import { sGetVisualization } from '../../../../reducers/visualizations' + +class Visualization extends React.Component { + state = { + pluginLoaded: false, + } + + constructor(props) { + super(props) + + this.memoizedGetFilteredVisualization = memoizeOne( + getFilteredVisualization + ) + this.memoizedGetVisualizationConfig = memoizeOne(getVisualizationConfig) + } + + onLoadingComplete = () => { + this.setState({ pluginLoaded: true }) + } + + render() { + const { + visualization, + activeType, + item, + itemFilters, + availableHeight, + } = this.props + + if (!visualization) { + return ( + + ) + } + + const pluginProps = { + item, + itemFilters, + activeType, + style: { height: availableHeight }, + visualization: this.memoizedGetVisualizationConfig( + visualization, + item.type, + activeType + ), + } + + switch (activeType) { + case VISUALIZATION: + case CHART: + case REPORT_TABLE: { + return ( + <> + {!this.state.pluginLoaded && ( +
+ +
+ )} + + + ) + } + case MAP: { + return ( + + ) + } + default: { + pluginProps.visualization = this.memoizedGetFilteredVisualization( + pluginProps.visualization, + pluginProps.itemFilters + ) + + return + } + } + } +} + +Visualization.propTypes = { + activeType: PropTypes.string, + availableHeight: PropTypes.number, + item: PropTypes.object, + itemFilters: PropTypes.object, + visualization: PropTypes.object, +} + +Visualization.contextTypes = { + d2: PropTypes.object, +} + +const mapStateToProps = (state, ownProps) => { + return { + visualization: sGetVisualization( + state, + getVisualizationId(ownProps.item) + ), + } +} + +export default connect(mapStateToProps)(Visualization) diff --git a/src/components/Item/VisualizationItem/Visualization/__tests__/Visualization.spec.js b/src/components/Item/VisualizationItem/Visualization/__tests__/Visualization.spec.js new file mode 100644 index 000000000..fd364c9ee --- /dev/null +++ b/src/components/Item/VisualizationItem/Visualization/__tests__/Visualization.spec.js @@ -0,0 +1,134 @@ +import React from 'react' +import { render } from '@testing-library/react' +import { Provider } from 'react-redux' +import configureMockStore from 'redux-mock-store' + +import Visualization from '../Visualization' + +jest.mock('@dhis2/data-visualizer-plugin', () => 'VisualizationPlugin') +jest.mock('../MapPlugin', () => 'MapPlugin') +jest.mock('../DefaultPlugin', () => 'DefaultPlugin') + +const mockStore = configureMockStore() + +test('renders a MapPlugin when activeType is MAP', () => { + const store = { + visualizations: { rainbowVis: { rows: [], columns: [], filters: [] } }, + } + const { container } = render( + + + + ) + expect(container).toMatchSnapshot() +}) + +test('renders a VisPlugin when activeType is CHART', () => { + const store = { + visualizations: { rainbowVis: { rows: [], columns: [], filters: [] } }, + } + const { container } = render( + + + + ) + expect(container).toMatchSnapshot() +}) + +test('renders a VisPlugin when activeType is TABLE', () => { + const store = { + visualizations: { rainbowVis: { rows: [], columns: [], filters: [] } }, + } + const { container } = render( + + + + ) + expect(container).toMatchSnapshot() +}) + +test('renders a DefaultPlugin when activeType is EVENT_CHART', () => { + const store = { + visualizations: { rainbowVis: { rows: [], columns: [], filters: [] } }, + } + const { container } = render( + + + + ) + expect(container).toMatchSnapshot() +}) + +test('renders a DefaultPlugin when activeType is EVENT_REPORT', () => { + const store = { + visualizations: { rainbowVis: { rows: [], columns: [], filters: [] } }, + } + const { container } = render( + + + + ) + expect(container).toMatchSnapshot() +}) + +test('renders NoVisMessage when no visualization', () => { + const store = { + visualizations: {}, + } + const { container } = render( + + + + ) + expect(container).toMatchSnapshot() +}) diff --git a/src/components/Item/VisualizationItem/Visualization/__tests__/__snapshots__/Visualization.spec.js.snap b/src/components/Item/VisualizationItem/Visualization/__tests__/__snapshots__/Visualization.spec.js.snap new file mode 100644 index 000000000..73072f429 --- /dev/null +++ b/src/components/Item/VisualizationItem/Visualization/__tests__/__snapshots__/Visualization.spec.js.snap @@ -0,0 +1,119 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders NoVisMessage when no visualization 1`] = ` +
+
+ No data to display +
+
+`; + +exports[`renders a DefaultPlugin when activeType is EVENT_CHART 1`] = ` +
+ +
+`; + +exports[`renders a DefaultPlugin when activeType is EVENT_REPORT 1`] = ` +
+ +
+`; + +exports[`renders a MapPlugin when activeType is MAP 1`] = ` +
+ +
+`; + +exports[`renders a VisPlugin when activeType is CHART 1`] = ` +
+
+
+
+ + + +
+
+
+ +
+`; + +exports[`renders a VisPlugin when activeType is TABLE 1`] = ` +
+
+
+
+ + + +
+
+
+ +
+`; diff --git a/src/components/Item/VisualizationItem/__tests__/getVisualizationConfig.spec.js b/src/components/Item/VisualizationItem/Visualization/__tests__/getVisualizationConfig.spec.js similarity index 98% rename from src/components/Item/VisualizationItem/__tests__/getVisualizationConfig.spec.js rename to src/components/Item/VisualizationItem/Visualization/__tests__/getVisualizationConfig.spec.js index 8e114fa31..d5fb05908 100644 --- a/src/components/Item/VisualizationItem/__tests__/getVisualizationConfig.spec.js +++ b/src/components/Item/VisualizationItem/Visualization/__tests__/getVisualizationConfig.spec.js @@ -1,7 +1,3 @@ -import getVisualizationConfig, { - THEMATIC_LAYER, -} from '../getVisualizationConfig' -import { REPORT_TABLE, CHART, MAP } from '../../../../modules/itemTypes' import { DIMENSION_ID_DATA, DIMENSION_ID_PERIOD, @@ -11,6 +7,11 @@ import { AXIS_ID_FILTERS, } from '@dhis2/analytics' +import getVisualizationConfig, { + THEMATIC_LAYER, +} from '../getVisualizationConfig' +import { REPORT_TABLE, CHART, MAP } from '../../../../../modules/itemTypes' + describe('getVisualizationConfig', () => { let visualization diff --git a/src/components/Item/VisualizationItem/Visualization/getFilteredVisualization.js b/src/components/Item/VisualizationItem/Visualization/getFilteredVisualization.js new file mode 100644 index 000000000..356098fa0 --- /dev/null +++ b/src/components/Item/VisualizationItem/Visualization/getFilteredVisualization.js @@ -0,0 +1,42 @@ +const getFilteredVisualization = (visualization, filters) => { + if (!Object.keys(filters).length) { + return visualization + } + + // deep clone objects in filters to avoid changing the visualization in the Redux store + const visRows = visualization.rows.map(obj => ({ ...obj })) + const visColumns = visualization.columns.map(obj => ({ ...obj })) + const visFilters = visualization.filters.map(obj => ({ ...obj })) + + Object.keys(filters).forEach(dimensionId => { + if (filters[dimensionId]) { + let dimensionFound = false + + ;[visRows, visColumns, visFilters].forEach(dimensionObjects => + dimensionObjects + .filter(obj => obj.dimension === dimensionId) + .forEach(obj => { + dimensionFound = true + obj.items = filters[dimensionId] + }) + ) + + // add dimension to filters if not already present elsewhere + if (!dimensionFound) { + visFilters.push({ + dimension: dimensionId, + items: filters[dimensionId], + }) + } + } + }) + + return { + ...visualization, + rows: visRows, + columns: visColumns, + filters: visFilters, + } +} + +export default getFilteredVisualization diff --git a/src/components/Item/VisualizationItem/getVisualizationConfig.js b/src/components/Item/VisualizationItem/Visualization/getVisualizationConfig.js similarity index 95% rename from src/components/Item/VisualizationItem/getVisualizationConfig.js rename to src/components/Item/VisualizationItem/Visualization/getVisualizationConfig.js index 3429e90e1..62c59b511 100644 --- a/src/components/Item/VisualizationItem/getVisualizationConfig.js +++ b/src/components/Item/VisualizationItem/Visualization/getVisualizationConfig.js @@ -3,7 +3,7 @@ import { VIS_TYPE_PIVOT_TABLE, getAdaptedUiLayoutByType, } from '@dhis2/analytics' -import { REPORT_TABLE, CHART, MAP } from '../../../modules/itemTypes' +import { REPORT_TABLE, CHART, MAP } from '../../../../modules/itemTypes' export const THEMATIC_LAYER = 'thematic' diff --git a/src/components/Item/VisualizationItem/plugin.js b/src/components/Item/VisualizationItem/Visualization/plugin.js similarity index 89% rename from src/components/Item/VisualizationItem/plugin.js rename to src/components/Item/VisualizationItem/Visualization/plugin.js index 480c4976f..9bdf4d1d6 100644 --- a/src/components/Item/VisualizationItem/plugin.js +++ b/src/components/Item/VisualizationItem/Visualization/plugin.js @@ -5,10 +5,10 @@ import { EVENT_REPORT, EVENT_CHART, itemTypeMap, -} from '../../../modules/itemTypes' -import { getBaseUrl } from '../../../modules/util' -import { getVisualizationId } from '../../../modules/item' -import { getGridItemDomId } from '../../ItemGrid/gridUtil' +} from '../../../../modules/itemTypes' +import { getBaseUrl } from '../../../../modules/util' +import { getVisualizationId } from '../../../../modules/item' +import { getGridItemDomId } from '../../../ItemGrid/gridUtil' //external plugins const itemTypeToExternalPlugin = { diff --git a/src/components/Item/VisualizationItem/styles/LoadingMask.module.css b/src/components/Item/VisualizationItem/Visualization/styles/LoadingMask.module.css similarity index 100% rename from src/components/Item/VisualizationItem/styles/LoadingMask.module.css rename to src/components/Item/VisualizationItem/Visualization/styles/LoadingMask.module.css diff --git a/src/components/Item/VisualizationItem/styles/NoVisualizationMessage.module.css b/src/components/Item/VisualizationItem/Visualization/styles/NoVisualizationMessage.module.css similarity index 100% rename from src/components/Item/VisualizationItem/styles/NoVisualizationMessage.module.css rename to src/components/Item/VisualizationItem/Visualization/styles/NoVisualizationMessage.module.css diff --git a/src/components/Item/VisualizationItem/__tests__/Item.spec.js b/src/components/Item/VisualizationItem/__tests__/Item.spec.js index 12a3238f2..c289a2275 100644 --- a/src/components/Item/VisualizationItem/__tests__/Item.spec.js +++ b/src/components/Item/VisualizationItem/__tests__/Item.spec.js @@ -1,24 +1,11 @@ import React from 'react' import { shallow } from 'enzyme' import toJson from 'enzyme-to-json' -// import VisualizationPlugin from '@dhis2/data-visualizer-plugin' -import { CHART, REPORT_TABLE, EVENT_CHART } from '../../../../modules/itemTypes' +import { CHART } from '../../../../modules/itemTypes' import { Item } from '../Item' -import DefaultPlugin from '../DefaultPlugin' -jest.mock('@dhis2/data-visualizer-plugin', () => 'VisualizationPlugin') -jest.mock('../DefaultPlugin', () => 'DefaultPlugin') -jest.mock('../MapPlugin', () => 'MapPlugin') jest.mock('../ItemFooter', () => 'ItemFooter') -jest.mock('../plugin', () => { - return { - getLink: jest.fn(), - unmount: jest.fn(), - pluginIsAvailable: () => true, - getName: () => 'rainbow', - fetch: () => {}, - } -}) +jest.mock('../Visualization/Visualization', () => 'VisualizationComponent') const mockHeaderRef = { clientHeight: 50 } @@ -42,107 +29,44 @@ describe('VisualizationItem/Item', () => { itemFilters: { brilliance: [{ id: 100, name: '100' }], }, - selectActiveType: jest.fn(), + setActiveType: jest.fn(), updateVisualization: jest.fn(), visualization: { - name: 'vis name', id: 'vis id', - description: 'vis description', - rows: [], - columns: [], - filters: [], }, onToggleItemExpanded: jest.fn(), } shallowItem = undefined }) - it('renders a VisualizationPlugin when a CHART is passed', () => { + it('renders an Item with a Visualization', () => { props.item.type = CHART props.item.chart = { id: 'chart1', name: 'Test chart', } - const expectedConfig = { - ...props.visualization, - id: undefined, - filters: [ - { - dimension: 'brilliance', - items: props.itemFilters.brilliance, - }, - ], - } - const component = canvas() component.instance().headerRef.current = mockHeaderRef component.setState({ configLoaded: true }) - const visPlugin = component.find('VisualizationPlugin') + const vis = component.find('VisualizationComponent') - expect(visPlugin.exists()).toBeTruthy() - expect(visPlugin.prop('visualization')).toEqual(expectedConfig) + expect(vis.exists()).toBeTruthy() + expect(vis.prop('item')).toEqual({ + type: CHART, + chart: { id: 'chart1', name: 'Test chart' }, + }) }) - it('renders a VisualizationPlugin when a REPORT_TABLE is passed', () => { - props.item.type = REPORT_TABLE - props.item.reportTable = { - id: 'table1', - name: 'Test table', - } - - const expectedConfig = { - ...props.visualization, - id: undefined, - filters: [ - { - dimension: 'brilliance', - items: props.itemFilters.brilliance, - }, - ], - } - - const component = canvas() - component.instance().headerRef.current = mockHeaderRef - - component.setState({ configLoaded: true }) - - const visPlugin = component.find('VisualizationPlugin') - - expect(visPlugin.exists()).toBeTruthy() - expect(visPlugin.prop('visualization')).toEqual(expectedConfig) - }) - - it('renders a DefaultPlugin when an EVENT_CHART is passed', () => { - props.item.type = EVENT_CHART - props.item.eventChart = { - id: 'evchart1', - name: 'Test evchart', - } - const expectedConfig = { - ...props.visualization, - id: undefined, - filters: [ - { - dimension: 'brilliance', - items: props.itemFilters.brilliance, - }, - ], + it('does not render Visualization if config not loaded', () => { + props.item.type = CHART + props.item.chart = { + id: 'chart1', + name: 'Test chart', } - expect(toJson(canvas())).toMatchSnapshot() - - const component = canvas() - component.instance().headerRef.current = mockHeaderRef - - component.setState({ configLoaded: true }) - - const defaultPlugin = canvas().find(DefaultPlugin) - - expect(defaultPlugin.exists()).toBeTruthy() - expect(defaultPlugin.prop('visualization')).toEqual(expectedConfig) }) }) diff --git a/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js b/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js index 2833c40c2..6918426a1 100644 --- a/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js +++ b/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js @@ -3,7 +3,7 @@ import { shallow } from 'enzyme' import toJson from 'enzyme-to-json' import ItemHeaderButtons from '../ItemHeaderButtons' -jest.mock('../plugin', () => ({ +jest.mock('../Visualization/plugin', () => ({ getLink: () => 'http://rainbowdash', pluginIsAvailable: () => true, })) diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap index 8f5a1506d..49de91dee 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap @@ -1,42 +1,37 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VisualizationItem/Item renders a DefaultPlugin when an EVENT_CHART is passed 1`] = ` +exports[`VisualizationItem/Item does not render Visualization if config not loaded 1`] = ` } dashboardMode="view" - title="Test evchart" + title="Test chart" />
diff --git a/src/components/ItemGrid/ItemGrid.js b/src/components/ItemGrid/ItemGrid.js index 1a2d845cb..cc0f821c4 100644 --- a/src/components/ItemGrid/ItemGrid.js +++ b/src/components/ItemGrid/ItemGrid.js @@ -7,7 +7,7 @@ import { Layer, CenteredContent, CircularLoader } from '@dhis2/ui' import { acUpdateDashboardLayout } from '../../actions/editDashboard' import { Item } from '../Item/Item' -import { resize as pluginResize } from '../Item/VisualizationItem/plugin' +import { resize as pluginResize } from '../Item/VisualizationItem/Visualization/plugin' import { isVisualizationType } from '../../modules/itemTypes' import { GRID_ROW_HEIGHT, From cf6dbb3744774d635907834c65eb679739d65aa2 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 7 Dec 2020 17:02:11 +0100 Subject: [PATCH 02/25] feat: fullscreen start --- i18n/en.pot | 16 +++--- src/components/App.css | 27 +++++++++ src/components/Item/VisualizationItem/Item.js | 55 +++++++++++++++++++ .../VisualizationItem/ItemHeaderButtons.js | 12 ++++ src/components/ItemGrid/ItemGrid.js | 1 + 5 files changed, 103 insertions(+), 8 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 73bcbaa07..f5027dfc7 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-11-21T09:42:35.736Z\n" -"PO-Revision-Date: 2020-11-21T09:42:35.736Z\n" +"POT-Creation-Date: 2020-12-07T15:44:29.595Z\n" +"PO-Revision-Date: 2020-12-07T15:44:29.595Z\n" msgid "Untitled dashboard" msgstr "" @@ -132,15 +132,9 @@ msgstr "" msgid "Add text here" msgstr "" -msgid "Unable to load the plugin for this item" -msgstr "" - msgid "There was a problem loading this dashboard item" msgstr "" -msgid "No data to display" -msgstr "" - msgid "Hide interpretations and details" msgstr "" @@ -159,6 +153,12 @@ msgstr "" msgid "Open in {{appName}} app" msgstr "" +msgid "Unable to load the plugin for this item" +msgstr "" + +msgid "No data to display" +msgstr "" + msgid "Confirm" msgstr "" diff --git a/src/components/App.css b/src/components/App.css index cecfe7266..876251d7b 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -29,6 +29,33 @@ table.pivot * { background-color: #48a999; } +.fullscreen { + height: 95vh !important; + width: 100vw !important; +} + +.pivot-table-container:fullscreen { + background-color: white; + overflow: scroll; +} + +.event-report-container:fullscreen { + background-color: white; + overflow: scroll; +} + +.event-report-container:fullscreen > table { + margin: 0 auto; +} + +.pivot-table-container:fullscreen > table { + margin: 0 auto; +} + +.dashboard-item-content.fullscreen > div { + height: 95vh !important; +} + @media print { body { width: 100% !important; diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 6ecd60d73..8e8a6ec11 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -17,6 +17,7 @@ import { sGetItemFiltersRoot, DEFAULT_STATE_ITEM_FILTERS, } from '../../../reducers/itemFilters' +import { CHART, EVENT_CHART } from '../../../modules/itemTypes' import { acAddVisualization } from '../../../actions/visualizations' import { acSetSelectedItemActiveType } from '../../../actions/selected' @@ -34,6 +35,8 @@ export class Item extends Component { state = { showFooter: false, configLoaded: false, + isFullscreen: false, + itemCoordinates: {}, } constructor(props, context) { @@ -71,6 +74,56 @@ export class Item extends Component { ) } + handleFullscreenChange = () => { + const itemEl = document.getElementById(`item-${this.props.item.id}`) + console.log('handleFullscreenChange', this.oprops.item.id) + if (!document.fullscreenElement) { + itemEl.classList.remove('fullscreen') + } else { + itemEl.classList.add('fullscreen') + } + + if ( + this.getActiveType() === EVENT_CHART || + this.getActiveType() === CHART + ) { + if (!document.fullscreenElement) { + const itemDomId = `#item-${this.props.item.id}` + const chartSvg = document.querySelector(`${itemDomId} svg`) + const chartContainer = document.querySelector( + `${itemDomId} .highcharts-container` + ) + // reset the width, height and viewbox + console.log('reset item to coords', this.state.itemCoordinates) + chartContainer.style.width = `${this.state.itemCoordinates.width}px` + chartContainer.style.height = `${this.state.itemCoordinates.height}px` + chartSvg.setAttribute('width', this.state.itemCoordinates.width) + chartSvg.setAttribute( + 'height', + this.state.itemCoordinates.height + ) + chartSvg.setAttribute( + 'viewBox', + this.state.itemCoordinates.viewBox + ) + } + } + } + + onToggleFullscreen = () => { + console.log('toggleFullscreen', this.props.item.id) + this.setState({ isFullscreen: !this.state.isFullscreen }, () => { + const el = document.querySelector( + `.reactgriditem-${this.props.item.id}` + ) + if (el?.requestFullscreen) { + el.onfullscreenchange = this.handleFullscreenChange + + el.requestFullscreen() + } + }) + } + setActiveType = type => { type !== this.getActiveType() && this.props.setActiveType(this.props.item.id, type) @@ -109,6 +162,7 @@ export class Item extends Component { visualization={this.props.visualization} onSelectActiveType={this.setActiveType} onToggleFooter={this.onToggleFooter} + onToggleFullscreen={this.onToggleFullscreen} activeType={activeType} activeFooter={showFooter} /> @@ -126,6 +180,7 @@ export class Item extends Component { />
(this.contentRef = ref)} diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index 5eb1f24df..816bd230e 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -60,6 +60,11 @@ const ItemHeaderButtons = (props, context) => { } } + const handleToggleFullscreenClick = () => { + props.onToggleFullscreen() + closeMenu() + } + const openMenu = () => setMenuIsOpen(true) const closeMenu = () => setMenuIsOpen(false) @@ -146,6 +151,12 @@ const ItemHeaderButtons = (props, context) => { label={interpretationMenuLabel} onClick={handleInterpretationClick} /> + } + label={i18n.t('View fullscreen')} + onClick={handleToggleFullscreenClick} + /> )} @@ -160,6 +171,7 @@ ItemHeaderButtons.propTypes = { visualization: PropTypes.object, onSelectActiveType: PropTypes.func, onToggleFooter: PropTypes.func, + onToggleFullscreen: PropTypes.func, } ItemHeaderButtons.contextTypes = { diff --git a/src/components/ItemGrid/ItemGrid.js b/src/components/ItemGrid/ItemGrid.js index cc0f821c4..7d9c7cf48 100644 --- a/src/components/ItemGrid/ItemGrid.js +++ b/src/components/ItemGrid/ItemGrid.js @@ -100,6 +100,7 @@ export class ItemGrid extends Component { const itemClassNames = [ item.type, this.props.edit ? 'edit' : 'view', + `reactgriditem-${item.id}`, ].join(' ') return ( From a23479b0f7081b8ff1a4ec35b2865b35307bcce5 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 7 Dec 2020 17:53:56 +0100 Subject: [PATCH 03/25] feat: some cleanup --- src/components/App.css | 22 +++---- src/components/Item/VisualizationItem/Item.js | 57 ++++++------------- .../VisualizationItem/ItemHeaderButtons.js | 14 +++++ 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/src/components/App.css b/src/components/App.css index 876251d7b..6caec88b2 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -34,26 +34,20 @@ table.pivot * { width: 100vw !important; } -.pivot-table-container:fullscreen { - background-color: white; - overflow: scroll; -} - -.event-report-container:fullscreen { - background-color: white; - overflow: scroll; +.dashboard-item-content.fullscreen > div { + height: 95vh !important; } -.event-report-container:fullscreen > table { - margin: 0 auto; +.react-grid-item:fullscreen .item-menu { + display: none; } -.pivot-table-container:fullscreen > table { - margin: 0 auto; +.react-grid-item:fullscreen .item-exit-fullscreen-button { + display: flex; } -.dashboard-item-content.fullscreen > div { - height: 95vh !important; +.react-grid-item:not(:fullscreen) .item-exit-fullscreen-button { + display: none; } @media print { diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 8e8a6ec11..b6842ddab 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -17,7 +17,6 @@ import { sGetItemFiltersRoot, DEFAULT_STATE_ITEM_FILTERS, } from '../../../reducers/itemFilters' -import { CHART, EVENT_CHART } from '../../../modules/itemTypes' import { acAddVisualization } from '../../../actions/visualizations' import { acSetSelectedItemActiveType } from '../../../actions/selected' @@ -36,7 +35,6 @@ export class Item extends Component { showFooter: false, configLoaded: false, isFullscreen: false, - itemCoordinates: {}, } constructor(props, context) { @@ -75,51 +73,29 @@ export class Item extends Component { } handleFullscreenChange = () => { - const itemEl = document.getElementById(`item-${this.props.item.id}`) - console.log('handleFullscreenChange', this.oprops.item.id) - if (!document.fullscreenElement) { - itemEl.classList.remove('fullscreen') - } else { - itemEl.classList.add('fullscreen') - } - - if ( - this.getActiveType() === EVENT_CHART || - this.getActiveType() === CHART - ) { + const itemEl = document.querySelector(`.item-${this.props.item.id}`) + if (itemEl) { if (!document.fullscreenElement) { - const itemDomId = `#item-${this.props.item.id}` - const chartSvg = document.querySelector(`${itemDomId} svg`) - const chartContainer = document.querySelector( - `${itemDomId} .highcharts-container` - ) - // reset the width, height and viewbox - console.log('reset item to coords', this.state.itemCoordinates) - chartContainer.style.width = `${this.state.itemCoordinates.width}px` - chartContainer.style.height = `${this.state.itemCoordinates.height}px` - chartSvg.setAttribute('width', this.state.itemCoordinates.width) - chartSvg.setAttribute( - 'height', - this.state.itemCoordinates.height - ) - chartSvg.setAttribute( - 'viewBox', - this.state.itemCoordinates.viewBox - ) + itemEl.classList.remove('fullscreen') + } else { + itemEl.classList.add('fullscreen') } } } onToggleFullscreen = () => { - console.log('toggleFullscreen', this.props.item.id) this.setState({ isFullscreen: !this.state.isFullscreen }, () => { - const el = document.querySelector( - `.reactgriditem-${this.props.item.id}` - ) - if (el?.requestFullscreen) { - el.onfullscreenchange = this.handleFullscreenChange + if (this.state.isFullscreen) { + const el = document.querySelector( + `.reactgriditem-${this.props.item.id}` + ) + if (el?.requestFullscreen) { + el.onfullscreenchange = this.handleFullscreenChange - el.requestFullscreen() + el.requestFullscreen() + } + } else { + document.exitFullscreen() } }) } @@ -180,9 +156,8 @@ export class Item extends Component { />
(this.contentRef = ref)} > {this.state.configLoaded && ( diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index 816bd230e..04edfb9dd 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -65,6 +65,10 @@ const ItemHeaderButtons = (props, context) => { closeMenu() } + const handleExitFullscreen = () => { + props.onToggleFullscreen() + } + const openMenu = () => setMenuIsOpen(true) const closeMenu = () => setMenuIsOpen(false) @@ -117,10 +121,20 @@ const ItemHeaderButtons = (props, context) => { small secondary onClick={openMenu} + className="item-menu" dataTest="dashboarditem-menu-button" > +
{menuIsOpen && ( Date: Mon, 7 Dec 2020 18:26:17 +0100 Subject: [PATCH 04/25] test: update snapshots --- .../Item/VisualizationItem/__tests__/Item.spec.js | 1 + .../__tests__/__snapshots__/Item.spec.js.snap | 5 ++++- .../__snapshots__/ItemHeaderButtons.spec.js.snap | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/components/Item/VisualizationItem/__tests__/Item.spec.js b/src/components/Item/VisualizationItem/__tests__/Item.spec.js index c289a2275..3d6d13aa0 100644 --- a/src/components/Item/VisualizationItem/__tests__/Item.spec.js +++ b/src/components/Item/VisualizationItem/__tests__/Item.spec.js @@ -63,6 +63,7 @@ describe('VisualizationItem/Item', () => { it('does not render Visualization if config not loaded', () => { props.item.type = CHART + props.item.id = 'chart-item-1' props.item.chart = { id: 'chart1', name: 'Test chart', diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap index 49de91dee..b563caa91 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap @@ -13,11 +13,13 @@ exports[`VisualizationItem/Item does not render Visualization if config not load "id": "chart1", "name": "Test chart", }, + "id": "chart-item-1", "type": "CHART", } } onSelectActiveType={[Function]} onToggleFooter={[Function]} + onToggleFullscreen={[Function]} visualization={ Object { "id": "vis id", @@ -26,11 +28,12 @@ exports[`VisualizationItem/Item does not render Visualization if config not load /> } dashboardMode="view" + itemId="chart-item-1" title="Test chart" />
diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap index 12d89dee1..1ac66e63d 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap @@ -4,6 +4,7 @@ exports[`renders correctly 1`] = `
+
`; From e6d5ea8d25ff15d7ab42dbf0a405c15bd6dbaa37 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 07:48:18 +0100 Subject: [PATCH 05/25] fix: use ref instead of css selectors --- src/components/App.css | 14 ++++--- src/components/Item/VisualizationItem/Item.js | 41 ++++++++----------- .../VisualizationItem/ItemHeaderButtons.js | 6 +-- .../__tests__/__snapshots__/Item.spec.js.snap | 8 ++-- .../ItemHeaderButtons.spec.js.snap | 1 - src/components/ItemGrid/ItemGrid.js | 1 - 6 files changed, 32 insertions(+), 39 deletions(-) diff --git a/src/components/App.css b/src/components/App.css index 6caec88b2..99f2ec434 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -29,24 +29,28 @@ table.pivot * { background-color: #48a999; } -.fullscreen { +.dashboard-item-content.fullscreen { height: 95vh !important; width: 100vw !important; } .dashboard-item-content.fullscreen > div { - height: 95vh !important; + height: 100% !important; +} + +.dashboard-item-container:fullscreen { + background-color: white; } -.react-grid-item:fullscreen .item-menu { +.dashboard-item-container:fullscreen .item-menu { display: none; } -.react-grid-item:fullscreen .item-exit-fullscreen-button { +.dashboard-item-container:fullscreen .item-exit-fullscreen-button { display: flex; } -.react-grid-item:not(:fullscreen) .item-exit-fullscreen-button { +.dashboard-item-container:not(:fullscreen) .item-exit-fullscreen-button { display: none; } diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index b6842ddab..5a20e939e 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -34,7 +34,6 @@ export class Item extends Component { state = { showFooter: false, configLoaded: false, - isFullscreen: false, } constructor(props, context) { @@ -44,6 +43,7 @@ export class Item extends Component { this.contentRef = React.createRef() this.headerRef = React.createRef() + this.containerRef = React.createRef() this.memoizedGetContentHeight = memoizeOne( (calculatedHeight, measuredHeight, preferMeasured) => @@ -73,31 +73,24 @@ export class Item extends Component { } handleFullscreenChange = () => { - const itemEl = document.querySelector(`.item-${this.props.item.id}`) - if (itemEl) { - if (!document.fullscreenElement) { - itemEl.classList.remove('fullscreen') - } else { - itemEl.classList.add('fullscreen') - } + if (!document.fullscreenElement) { + this.contentRef?.classList.remove('fullscreen') + } else { + this.contentRef?.classList.add('fullscreen') } } onToggleFullscreen = () => { - this.setState({ isFullscreen: !this.state.isFullscreen }, () => { - if (this.state.isFullscreen) { - const el = document.querySelector( - `.reactgriditem-${this.props.item.id}` - ) - if (el?.requestFullscreen) { - el.onfullscreenchange = this.handleFullscreenChange - - el.requestFullscreen() - } - } else { - document.exitFullscreen() + if (document.fullscreenElement === null) { + const el = this.containerRef.current + if (el?.requestFullscreen) { + el.onfullscreenchange = this.handleFullscreenChange + + el.requestFullscreen() } - }) + } else { + document.exitFullscreen() + } } setActiveType = type => { @@ -145,7 +138,7 @@ export class Item extends Component { ) return ( - <> +
(this.contentRef = ref)} > {this.state.configLoaded && ( @@ -173,7 +166,7 @@ export class Item extends Component { {isViewMode(dashboardMode) && showFooter ? ( ) : null} - +
) } } diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index 04edfb9dd..bb6a8b564 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -65,10 +65,6 @@ const ItemHeaderButtons = (props, context) => { closeMenu() } - const handleExitFullscreen = () => { - props.onToggleFullscreen() - } - const openMenu = () => setMenuIsOpen(true) const closeMenu = () => setMenuIsOpen(false) @@ -131,7 +127,7 @@ const ItemHeaderButtons = (props, context) => { secondary className="item-exit-fullscreen-button" label={i18n.t('Exit fullscreen')} - onClick={handleExitFullscreen} + onClick={props.onToggleFullscreen} > {i18n.t('Exit fullscreen')} diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap index b563caa91..0b755654e 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap @@ -1,7 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`VisualizationItem/Item does not render Visualization if config not loaded 1`] = ` - +
- +
`; diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap index 1ac66e63d..4a0d95918 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap @@ -17,7 +17,6 @@ exports[`renders correctly 1`] = ` className="item-exit-fullscreen-button" dataTest="dhis2-uicore-button" label="Exit fullscreen" - onClick={[Function]} secondary={true} small={true} type="button" diff --git a/src/components/ItemGrid/ItemGrid.js b/src/components/ItemGrid/ItemGrid.js index 7d9c7cf48..cc0f821c4 100644 --- a/src/components/ItemGrid/ItemGrid.js +++ b/src/components/ItemGrid/ItemGrid.js @@ -100,7 +100,6 @@ export class ItemGrid extends Component { const itemClassNames = [ item.type, this.props.edit ? 'edit' : 'view', - `reactgriditem-${item.id}`, ].join(' ') return ( From 282d153519669d824c2cdaa08ed75bb4a93f7443 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 08:50:51 +0100 Subject: [PATCH 06/25] fix: do things the react way --- src/components/App.css | 25 ------------------- src/components/Item/VisualizationItem/Item.js | 25 +++++++++++-------- .../VisualizationItem/ItemHeaderButtons.js | 22 ++++++---------- .../Visualization/Visualization.js | 3 ++- .../__tests__/__snapshots__/Item.spec.js.snap | 5 ++-- .../ItemHeaderButtons.spec.js.snap | 11 -------- .../VisualizationItem/styles/Item.module.css | 8 ++++++ 7 files changed, 36 insertions(+), 63 deletions(-) create mode 100644 src/components/Item/VisualizationItem/styles/Item.module.css diff --git a/src/components/App.css b/src/components/App.css index 99f2ec434..cecfe7266 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -29,31 +29,6 @@ table.pivot * { background-color: #48a999; } -.dashboard-item-content.fullscreen { - height: 95vh !important; - width: 100vw !important; -} - -.dashboard-item-content.fullscreen > div { - height: 100% !important; -} - -.dashboard-item-container:fullscreen { - background-color: white; -} - -.dashboard-item-container:fullscreen .item-menu { - display: none; -} - -.dashboard-item-container:fullscreen .item-exit-fullscreen-button { - display: flex; -} - -.dashboard-item-container:not(:fullscreen) .item-exit-fullscreen-button { - display: none; -} - @media print { body { width: 100% !important; diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 5a20e939e..96a2c5830 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -19,6 +19,7 @@ import { } from '../../../reducers/itemFilters' import { acAddVisualization } from '../../../actions/visualizations' import { acSetSelectedItemActiveType } from '../../../actions/selected' +import { pluginIsAvailable } from './Visualization/plugin' import { getVisualizationId, getVisualizationName } from '../../../modules/item' import memoizeOne from '../../../modules/memoizeOne' @@ -30,10 +31,13 @@ import { import { ITEM_CONTENT_PADDING_BOTTOM } from '../../ItemGrid/ItemGrid' +import classes from './styles/Item.module.css' + export class Item extends Component { state = { showFooter: false, configLoaded: false, + isFullscreen: false, } constructor(props, context) { @@ -73,15 +77,11 @@ export class Item extends Component { } handleFullscreenChange = () => { - if (!document.fullscreenElement) { - this.contentRef?.classList.remove('fullscreen') - } else { - this.contentRef?.classList.add('fullscreen') - } + this.setState({ isFullscreen: !!document.fullscreenElement }) } onToggleFullscreen = () => { - if (document.fullscreenElement === null) { + if (!document.fullscreenElement) { const el = this.containerRef.current if (el?.requestFullscreen) { el.onfullscreenchange = this.handleFullscreenChange @@ -106,6 +106,10 @@ export class Item extends Component { } getAvailableHeight = () => { + if (this.state.isFullscreen) { + return '100%' + } + const calculatedHeight = this.props.item.originalHeight - this.headerRef.current.clientHeight - @@ -125,7 +129,7 @@ export class Item extends Component { const { showFooter } = this.state const activeType = this.getActiveType() - const actionButtons = ( + const actionButtons = pluginIsAvailable(activeType || item.type) ? ( - ) + ) : null return ( -
+
(this.contentRef = ref)} > {this.state.configLoaded && ( diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index bb6a8b564..c2737ee6f 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -15,7 +15,7 @@ import MapIcon from '@material-ui/icons/Public' import LaunchIcon from '@material-ui/icons/Launch' import { ThreeDots, SpeechBubble } from './assets/icons' -import { pluginIsAvailable, getLink } from './Visualization/plugin' +import { getLink } from './Visualization/plugin' import { CHART, MAP, @@ -31,7 +31,6 @@ const iconFill = { fill: colors.grey600 } const ItemHeaderButtons = (props, context) => { const [menuIsOpen, setMenuIsOpen] = useState(null) - const { item, visualization, onSelectActiveType, activeType } = props const isTrackerType = isTrackerDomainType(item.type) @@ -110,27 +109,21 @@ const ItemHeaderButtons = (props, context) => { const buttonRef = createRef() - return pluginIsAvailable(activeType || item.type) ? ( + return props.isFullscreen ? ( + + ) : ( <>
-
{menuIsOpen && ( { )} - ) : null + ) } ItemHeaderButtons.propTypes = { activeFooter: PropTypes.bool, activeType: PropTypes.string, + isFullscreen: PropTypes.bool, item: PropTypes.object, visualization: PropTypes.object, onSelectActiveType: PropTypes.func, diff --git a/src/components/Item/VisualizationItem/Visualization/Visualization.js b/src/components/Item/VisualizationItem/Visualization/Visualization.js index b008aa5c6..7558e9eff 100644 --- a/src/components/Item/VisualizationItem/Visualization/Visualization.js +++ b/src/components/Item/VisualizationItem/Visualization/Visualization.js @@ -68,6 +68,7 @@ class Visualization extends React.Component { ), } + console.log('render vis') switch (activeType) { case VISUALIZATION: case CHART: @@ -114,7 +115,7 @@ class Visualization extends React.Component { Visualization.propTypes = { activeType: PropTypes.string, - availableHeight: PropTypes.number, + availableHeight: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), item: PropTypes.object, itemFilters: PropTypes.object, visualization: PropTypes.object, diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap index 0b755654e..559b1a36d 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap @@ -2,13 +2,14 @@ exports[`VisualizationItem/Item does not render Visualization if config not loaded 1`] = `
diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap index 4a0d95918..12d89dee1 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap @@ -4,7 +4,6 @@ exports[`renders correctly 1`] = `
-
`; diff --git a/src/components/Item/VisualizationItem/styles/Item.module.css b/src/components/Item/VisualizationItem/styles/Item.module.css new file mode 100644 index 000000000..99fc500fa --- /dev/null +++ b/src/components/Item/VisualizationItem/styles/Item.module.css @@ -0,0 +1,8 @@ +.container:fullscreen .content { + height: 95vh !important; + width: 100vw !important; +} + +.container:fullscreen { + background-color: white; +} From ed7540a7157183cadd5ab42f0643f80112e72512 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 08:54:44 +0100 Subject: [PATCH 07/25] fix: add missing translations --- i18n/en.pot | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f5027dfc7..2aff08aa9 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-12-07T15:44:29.595Z\n" -"PO-Revision-Date: 2020-12-07T15:44:29.595Z\n" +"POT-Creation-Date: 2020-12-08T07:52:49.426Z\n" +"PO-Revision-Date: 2020-12-08T07:52:49.426Z\n" msgid "Untitled dashboard" msgstr "" @@ -150,9 +150,15 @@ msgstr "" msgid "View as Map" msgstr "" +msgid "Exit fullscreen" +msgstr "" + msgid "Open in {{appName}} app" msgstr "" +msgid "View fullscreen" +msgstr "" + msgid "Unable to load the plugin for this item" msgstr "" From 32f5b2625bf4ae677614da60029576311bfd28f9 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 08:59:35 +0100 Subject: [PATCH 08/25] fix: cleanup --- .../Item/VisualizationItem/Visualization/Visualization.js | 1 - src/components/Item/VisualizationItem/__tests__/Item.spec.js | 1 - .../VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/components/Item/VisualizationItem/Visualization/Visualization.js b/src/components/Item/VisualizationItem/Visualization/Visualization.js index 7558e9eff..aae1ea613 100644 --- a/src/components/Item/VisualizationItem/Visualization/Visualization.js +++ b/src/components/Item/VisualizationItem/Visualization/Visualization.js @@ -68,7 +68,6 @@ class Visualization extends React.Component { ), } - console.log('render vis') switch (activeType) { case VISUALIZATION: case CHART: diff --git a/src/components/Item/VisualizationItem/__tests__/Item.spec.js b/src/components/Item/VisualizationItem/__tests__/Item.spec.js index 3d6d13aa0..c289a2275 100644 --- a/src/components/Item/VisualizationItem/__tests__/Item.spec.js +++ b/src/components/Item/VisualizationItem/__tests__/Item.spec.js @@ -63,7 +63,6 @@ describe('VisualizationItem/Item', () => { it('does not render Visualization if config not loaded', () => { props.item.type = CHART - props.item.id = 'chart-item-1' props.item.chart = { id: 'chart1', name: 'Test chart', diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap index 559b1a36d..e47c27b4c 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap @@ -16,7 +16,6 @@ exports[`VisualizationItem/Item does not render Visualization if config not load "id": "chart1", "name": "Test chart", }, - "id": "chart-item-1", "type": "CHART", } } @@ -31,7 +30,6 @@ exports[`VisualizationItem/Item does not render Visualization if config not load /> } dashboardMode="view" - itemId="chart-item-1" title="Test chart" /> From 43233cc2c73ebf93028c99c4ea400b6f49906bfa Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 09:40:10 +0100 Subject: [PATCH 09/25] fix: clean up event listener --- src/components/Item/VisualizationItem/Item.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 96a2c5830..de461b6a8 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -65,6 +65,18 @@ export class Item extends Component { this.setState({ configLoaded: true, }) + + const el = this.containerRef.current + if (el?.requestFullscreen) { + el.onfullscreenchange = this.handleFullscreenChange + } + } + + componentWillUnmount() { + this.containerRef.current.removeEventListener( + 'onfullscreenchange', + this.handleFullscreenChange + ) } getUniqueKey = memoizeOne(() => uniqueId()) @@ -83,11 +95,7 @@ export class Item extends Component { onToggleFullscreen = () => { if (!document.fullscreenElement) { const el = this.containerRef.current - if (el?.requestFullscreen) { - el.onfullscreenchange = this.handleFullscreenChange - - el.requestFullscreen() - } + el?.requestFullscreen && el.requestFullscreen() } else { document.exitFullscreen() } From 93bdea7913ee3255244a3ec2891fb51e7d1a0a90 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 09:50:24 +0100 Subject: [PATCH 10/25] fix: fullscreen icon - temporary for demo only --- .../Item/VisualizationItem/ItemHeaderButtons.js | 4 ++-- .../Item/VisualizationItem/assets/icons.js | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index c2737ee6f..190edbd09 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -14,7 +14,7 @@ import ChartIcon from '@material-ui/icons/InsertChart' import MapIcon from '@material-ui/icons/Public' import LaunchIcon from '@material-ui/icons/Launch' -import { ThreeDots, SpeechBubble } from './assets/icons' +import { ThreeDots, SpeechBubble, Fullscreen } from './assets/icons' import { getLink } from './Visualization/plugin' import { CHART, @@ -156,7 +156,7 @@ const ItemHeaderButtons = (props, context) => { /> } + icon={} label={i18n.t('View fullscreen')} onClick={handleToggleFullscreenClick} /> diff --git a/src/components/Item/VisualizationItem/assets/icons.js b/src/components/Item/VisualizationItem/assets/icons.js index 150abb3e6..d2f6e87d1 100644 --- a/src/components/Item/VisualizationItem/assets/icons.js +++ b/src/components/Item/VisualizationItem/assets/icons.js @@ -44,3 +44,17 @@ export const Warning = () => ( ) + +export const Fullscreen = () => ( + + + +) From 8b0c1d6793bef9dbea075a891d5a5b3497fb4452 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 09:52:48 +0100 Subject: [PATCH 11/25] test: add test for fullscreen button --- .../__tests__/ItemHeaderButtons.spec.js | 23 ++++++++++++++++++- .../ItemHeaderButtons.spec.js.snap | 13 ++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js b/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js index 6918426a1..5b3d3b359 100644 --- a/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js +++ b/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js @@ -8,7 +8,7 @@ jest.mock('../Visualization/plugin', () => ({ pluginIsAvailable: () => true, })) -it('renders correctly', () => { +it('renders correctly when not fullscreen', () => { const buttons = shallow( { ) expect(toJson(buttons)).toMatchSnapshot() }) + +it('renders correctly when fullscreen', () => { + const buttons = shallow( + + ) + expect(toJson(buttons)).toMatchSnapshot() +}) diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap index 12d89dee1..b72e98aab 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap @@ -1,6 +1,17 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`renders correctly 1`] = ` +exports[`renders correctly when fullscreen 1`] = ` + +`; + +exports[`renders correctly when not fullscreen 1`] = `
) : ( <> diff --git a/src/components/Item/VisualizationItem/assets/icons.js b/src/components/Item/VisualizationItem/assets/icons.js index d2f6e87d1..e5ce092f6 100644 --- a/src/components/Item/VisualizationItem/assets/icons.js +++ b/src/components/Item/VisualizationItem/assets/icons.js @@ -58,3 +58,17 @@ export const Fullscreen = () => ( /> ) + +export const ExitFullscreen = () => ( + + + +) From 238547e835f2fd0007ef90e4550da349e90762c5 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 8 Dec 2020 13:39:08 +0100 Subject: [PATCH 18/25] fix: test --- .../__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap index b72e98aab..e8bd42db8 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/ItemHeaderButtons.spec.js.snap @@ -7,7 +7,7 @@ exports[`renders correctly when fullscreen 1`] = ` small={true} type="button" > - Exit fullscreen + `; From e01b83f3d3dc936d38564fb81221004b6e8f92f2 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Thu, 10 Dec 2020 13:18:36 +0100 Subject: [PATCH 19/25] fix: dom el doesnt match in print mode --- i18n/en.pot | 7 ++----- src/components/Item/VisualizationItem/Item.js | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 3c46033ac..0f32be4f6 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-12-09T13:38:15.043Z\n" -"PO-Revision-Date: 2020-12-09T13:38:15.043Z\n" +"POT-Creation-Date: 2020-12-10T12:16:21.390Z\n" +"PO-Revision-Date: 2020-12-10T12:16:21.391Z\n" msgid "Untitled dashboard" msgstr "" @@ -150,9 +150,6 @@ msgstr "" msgid "View as Map" msgstr "" -msgid "Exit fullscreen" -msgstr "" - msgid "Open in {{appName}} app" msgstr "" diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 9806ec9b0..4088c62b5 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -72,7 +72,7 @@ export class Item extends Component { componentWillUnmount() { const el = document.querySelector(this.itemDomElSelector) - el.removeEventListener( + el?.removeEventListener( 'onfullscreenchange', this.handleFullscreenChange ) From 3bb88c90c8c5f4e18290d6f2b4e2a5ad8cc93be7 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 14 Dec 2020 10:08:12 +0100 Subject: [PATCH 20/25] chore: tests to check for fullscreen option --- cypress/assets/backends/sierraLeone_236.js | 8 ++++++++ cypress/integration/ui/fullscreen.feature | 15 +++++++++++++++ .../integration/ui/fullscreen/fullscreen.js | 18 ++++++++++++++++++ cypress/selectors/dashboardItem.js | 5 ++--- 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 cypress/integration/ui/fullscreen.feature create mode 100644 cypress/integration/ui/fullscreen/fullscreen.js diff --git a/cypress/assets/backends/sierraLeone_236.js b/cypress/assets/backends/sierraLeone_236.js index 7531cdbae..977a5c0f9 100644 --- a/cypress/assets/backends/sierraLeone_236.js +++ b/cypress/assets/backends/sierraLeone_236.js @@ -1,6 +1,14 @@ export const dashboards = { 'Antenatal Care': { route: '#/nghVC4wtyzi', + items: { + text: { + itemUid: 'ILRTXgXvurM', + }, + chart: { + itemUid: 'azz0KRlHgLs', + }, + }, }, Delivery: { route: '#/iMnYyBfSxmM', diff --git a/cypress/integration/ui/fullscreen.feature b/cypress/integration/ui/fullscreen.feature new file mode 100644 index 000000000..ad499715c --- /dev/null +++ b/cypress/integration/ui/fullscreen.feature @@ -0,0 +1,15 @@ +Feature: Item context menu fullscreen + + Background: + Given I open the "Antenatal Care" dashboard + And the "Antenatal Care" dashboard displays in view mode + + @nonmutating + Scenario: Text item does not have a context menu + Then the text item does not have a context menu + + @nonmutating + Scenario: Chart item has a fullscreen option + Then the chart item has a fullscreen option in the context menu + + diff --git a/cypress/integration/ui/fullscreen/fullscreen.js b/cypress/integration/ui/fullscreen/fullscreen.js new file mode 100644 index 000000000..bdb5566f9 --- /dev/null +++ b/cypress/integration/ui/fullscreen/fullscreen.js @@ -0,0 +1,18 @@ +import { Then } from 'cypress-cucumber-preprocessor/steps' +import { + getDashboardItem, + itemMenuButton, + clickMenuButton, +} from '../../../selectors/dashboardItem' +import { dashboards } from '../../../assets/backends' + +Then('the text item does not have a context menu', () => { + getDashboardItem(dashboards['Antenatal Care'].items.text.itemUid) + .find(itemMenuButton) + .should('not.exist') +}) + +Then('the chart item has a fullscreen option in the context menu', () => { + clickMenuButton(dashboards['Antenatal Care'].items.chart.itemUid) + cy.contains('View fullscreen').should('be.visible') +}) diff --git a/cypress/selectors/dashboardItem.js b/cypress/selectors/dashboardItem.js index ed19f5299..0681fec0c 100644 --- a/cypress/selectors/dashboardItem.js +++ b/cypress/selectors/dashboardItem.js @@ -6,11 +6,10 @@ export const tableSel = '.pivot-table-container' export const gridItemSel = '.react-grid-item' export const itemDetailsSel = '[data-test="dashboarditem-footer"]' +export const itemMenuButton = '[data-test="dashboarditem-menu-button"]' export const getDashboardItem = itemUid => cy.get(`[data-test="dashboarditem-${itemUid}"]`) export const clickMenuButton = itemUid => - getDashboardItem(itemUid) - .find('[data-test="dashboarditem-menu-button"]') - .click() + getDashboardItem(itemUid).find(itemMenuButton).click() From 0abaefe7f292e95187892878c45d1b520b0a4602 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 14 Dec 2020 10:59:22 +0100 Subject: [PATCH 21/25] fix: safari fs --- .browserslistrc | 5 +++ src/components/Item/VisualizationItem/Item.js | 41 +++++++++++++++---- .../VisualizationItem/ItemHeaderButtons.js | 15 ++++--- 3 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 .browserslistrc diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 000000000..a0b80b79b --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,5 @@ +> 0.5% +last 2 versions +Firefox ESR +not ie 11 +not dead \ No newline at end of file diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 4088c62b5..8ad223105 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -67,15 +67,24 @@ export class Item extends Component { const el = document.querySelector(this.itemDomElSelector) if (el?.requestFullscreen) { el.onfullscreenchange = this.handleFullscreenChange + } else if (el.webkitRequestFullscreen) { + el.onwebkitfullscreenchange = this.handleFullscreenChange } } componentWillUnmount() { const el = document.querySelector(this.itemDomElSelector) - el?.removeEventListener( - 'onfullscreenchange', - this.handleFullscreenChange - ) + if (el?.onfullscreenchange) { + el.removeEventListener( + 'onfullscreenchange', + this.handleFullscreenChange + ) + } else if (el?.onwebkitfullscreenchange) { + el.removeEventListener( + 'onwebkitfullscreenchange', + this.handleFullscreenChange + ) + } } getUniqueKey = memoizeOne(() => uniqueId()) @@ -87,16 +96,31 @@ export class Item extends Component { ) } + isFullscreenSupported = () => { + const el = document.querySelector(this.itemDomElSelector) + return el?.requestFullscreen || el?.webkitRequestFullscreen + } + handleFullscreenChange = () => { - this.setState({ isFullscreen: !!document.fullscreenElement }) + this.setState({ + isFullscreen: + !!document.fullscreenElement || + !!document.webkitFullscreenElement, + }) } onToggleFullscreen = () => { - if (!document.fullscreenElement) { + if (!document.fullscreenElement && !document.webkitFullscreenElement) { const el = document.querySelector(this.itemDomElSelector) - el?.requestFullscreen && el.requestFullscreen() + if (el?.requestFullscreen) { + el.requestFullscreen() + } else if (el?.webkitRequestFullscreen) { + el.webkitRequestFullscreen() // Safari + } } else { - document.exitFullscreen() + document.exitFullscreen + ? document.exitFullscreen() + : document.webkitExitFullscreen() } } @@ -146,6 +170,7 @@ export class Item extends Component { activeType={activeType} activeFooter={showFooter} isFullscreen={this.state.isFullscreen} + fullscreenSupported={this.isFullscreenSupported()} /> ) : null diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index e2b3299f9..e03ed94cd 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -159,12 +159,14 @@ const ItemHeaderButtons = (props, context) => { label={interpretationMenuLabel} onClick={handleInterpretationClick} /> - } - label={i18n.t('View fullscreen')} - onClick={handleToggleFullscreenClick} - /> + {props.fullscreenSupported && ( + } + label={i18n.t('View fullscreen')} + onClick={handleToggleFullscreenClick} + /> + )} )} @@ -175,6 +177,7 @@ const ItemHeaderButtons = (props, context) => { ItemHeaderButtons.propTypes = { activeFooter: PropTypes.bool, activeType: PropTypes.string, + fullscreenSupported: PropTypes.bool, isFullscreen: PropTypes.bool, item: PropTypes.object, visualization: PropTypes.object, From b482fbceb5a6c309d8afaea703a7599f8c1cfcab Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 14 Dec 2020 11:41:39 +0100 Subject: [PATCH 22/25] fix: add css from chrome to support safari --- src/components/App.css | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/src/components/App.css b/src/components/App.css index cae6bac26..17c6ef986 100644 --- a/src/components/App.css +++ b/src/components/App.css @@ -29,13 +29,33 @@ table.pivot * { background-color: #48a999; } -div:fullscreen .dashboard-item-content { - height: 95vh !important; - width: 100vw !important; +div:fullscreen, +div:-webkit-full-screen { + background-color: white; } -div:fullscreen { - background-color: white; +div:-webkit-full-screen { + object-fit: contain; + position: fixed !important; + top: 0px !important; + right: 0px !important; + bottom: 0px !important; + left: 0px !important; + box-sizing: border-box !important; + min-width: 0px !important; + max-width: none !important; + min-height: 0px !important; + max-height: none !important; + width: 100% !important; + height: 100% !important; + transform: none !important; + margin: 0px !important; +} + +div:fullscreen .dashboard-item-content, +div:-webkit-full-screen .dashboard-item-content { + height: 95vh !important; + width: 100vw !important; } @media print { From 575fe8a582c0e2aaa5fc3c5414c170098d30a25b Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 14 Dec 2020 11:56:32 +0100 Subject: [PATCH 23/25] chore: cleanup --- src/components/Item/VisualizationItem/Item.js | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 8ad223105..f29c816de 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -60,9 +60,7 @@ export class Item extends Component { await apiFetchVisualization(this.props.item) ) - this.setState({ - configLoaded: true, - }) + this.setState({ configLoaded: true }) const el = document.querySelector(this.itemDomElSelector) if (el?.requestFullscreen) { @@ -87,15 +85,6 @@ export class Item extends Component { } } - getUniqueKey = memoizeOne(() => uniqueId()) - - onToggleFooter = () => { - this.setState( - { showFooter: !this.state.showFooter }, - this.props.onToggleItemExpanded(this.props.item.id) - ) - } - isFullscreenSupported = () => { const el = document.querySelector(this.itemDomElSelector) return el?.requestFullscreen || el?.webkitRequestFullscreen @@ -110,12 +99,12 @@ export class Item extends Component { } onToggleFullscreen = () => { - if (!document.fullscreenElement && !document.webkitFullscreenElement) { + if (!this.state.isFullscreen) { const el = document.querySelector(this.itemDomElSelector) if (el?.requestFullscreen) { el.requestFullscreen() } else if (el?.webkitRequestFullscreen) { - el.webkitRequestFullscreen() // Safari + el.webkitRequestFullscreen() } } else { document.exitFullscreen @@ -124,6 +113,15 @@ export class Item extends Component { } } + getUniqueKey = memoizeOne(() => uniqueId()) + + onToggleFooter = () => { + this.setState( + { showFooter: !this.state.showFooter }, + this.props.onToggleItemExpanded(this.props.item.id) + ) + } + setActiveType = type => { type !== this.getActiveType() && this.props.setActiveType(this.props.item.id, type) From 04469bf7c882ca3ffad0dbccba6012fa7a6002d4 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 14 Dec 2020 12:04:03 +0100 Subject: [PATCH 24/25] fix: missed ? --- src/components/Item/VisualizationItem/Item.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index f29c816de..c529dd424 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -65,7 +65,7 @@ export class Item extends Component { const el = document.querySelector(this.itemDomElSelector) if (el?.requestFullscreen) { el.onfullscreenchange = this.handleFullscreenChange - } else if (el.webkitRequestFullscreen) { + } else if (el?.webkitRequestFullscreen) { el.onwebkitfullscreenchange = this.handleFullscreenChange } } From 2384dbb8f30c2b3c2e9c904168512a503c89c14d Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Tue, 5 Jan 2021 13:18:02 +0100 Subject: [PATCH 25/25] fix: use viewport height instead of % to improve ff fs rendering --- src/components/Item/VisualizationItem/Item.js | 6 +++--- .../__tests__/__snapshots__/Item.spec.js.snap | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 415dc5aab..293b2cc5f 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -61,7 +61,7 @@ export class Item extends Component { this.props.updateVisualization( await apiFetchVisualization(this.props.item) ) - + try { if ( this.props.gatherDataStatistics && @@ -103,7 +103,7 @@ export class Item extends Component { isFullscreenSupported = () => { const el = document.querySelector(this.itemDomElSelector) - return el?.requestFullscreen || el?.webkitRequestFullscreen + return !!(el?.requestFullscreen || el?.webkitRequestFullscreen) } handleFullscreenChange = () => { @@ -152,7 +152,7 @@ export class Item extends Component { getAvailableHeight = () => { if (this.state.isFullscreen) { - return '100%' + return '95vh' } const calculatedHeight = diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap index bc75dd06d..58f994f13 100644 --- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap +++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap @@ -7,6 +7,7 @@ exports[`VisualizationItem/Item does not render Visualization if config not load