Skip to content

Commit

Permalink
feat: add fullscreen option to visualization items [DHIS2-9879] (#1358)
Browse files Browse the repository at this point in the history
Adds fullscreen button to Visualization items

* No cypress tests added since it isn't currently possible to test the fullscreen api in cypress. I'll be keeping an eye on if/when this gets implemented in cypress (cypress-io/cypress#7776)
* Minor refactor: move check for whether to show ItemHeaderButtons (pluginIsAvailable) out of ItemHeaderButtons and to Item, so that the logic doesn't get too complex in ItemHeaderButtons (would have been first checking pluginIsAvailable, then checking the fullsreen prop)
  • Loading branch information
jenniferarnesen authored Jan 5, 2021
1 parent 132322d commit 746d7b0
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 16 deletions.
5 changes: 5 additions & 0 deletions .browserslistrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
> 0.5%
last 2 versions
Firefox ESR
not ie 11
not dead
8 changes: 8 additions & 0 deletions cypress/assets/backends/sierraLeone_236.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
export const dashboards = {
'Antenatal Care': {
route: '#/nghVC4wtyzi',
items: {
text: {
itemUid: 'ILRTXgXvurM',
},
chart: {
itemUid: 'azz0KRlHgLs',
},
},
},
Delivery: {
route: '#/iMnYyBfSxmM',
Expand Down
15 changes: 15 additions & 0 deletions cypress/integration/ui/fullscreen.feature
Original file line number Diff line number Diff line change
@@ -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


18 changes: 18 additions & 0 deletions cypress/integration/ui/fullscreen/fullscreen.js
Original file line number Diff line number Diff line change
@@ -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')
})
5 changes: 2 additions & 3 deletions cypress/selectors/dashboardItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
7 changes: 5 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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 ""
Expand Down Expand Up @@ -153,6 +153,9 @@ msgstr ""
msgid "Open in {{appName}} app"
msgstr ""

msgid "View fullscreen"
msgstr ""

msgid "Unable to load the plugin for this item"
msgstr ""

Expand Down
29 changes: 29 additions & 0 deletions src/components/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,35 @@ table.pivot * {
background-color: #48a999;
}

div:fullscreen,
div:-webkit-full-screen {
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 {
body {
width: 100% !important;
Expand Down
64 changes: 61 additions & 3 deletions src/components/Item/VisualizationItem/Item.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { sGatherAnalyticalObjectStatisticsInDashboardViews } from '../../../reducers/settings'
import { acAddVisualization } from '../../../actions/visualizations'
import { acSetSelectedItemActiveType } from '../../../actions/selected'
import { pluginIsAvailable } from './Visualization/plugin'
import { getDataStatisticsName } from '../../../modules/itemTypes'
import { getVisualizationId, getVisualizationName } from '../../../modules/item'
import memoizeOne from '../../../modules/memoizeOne'
Expand All @@ -36,6 +37,7 @@ export class Item extends Component {
state = {
showFooter: false,
configLoaded: false,
isFullscreen: false,
}

constructor(props, context) {
Expand All @@ -45,6 +47,7 @@ export class Item extends Component {

this.contentRef = React.createRef()
this.headerRef = React.createRef()
this.itemDomElSelector = `.reactgriditem-${this.props.item.id}`

this.memoizedGetContentHeight = memoizeOne(
(calculatedHeight, measuredHeight, preferMeasured) =>
Expand Down Expand Up @@ -73,11 +76,59 @@ export class Item extends Component {
console.log(e)
}

this.setState({ configLoaded: true })

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)
if (el?.onfullscreenchange) {
el.removeEventListener(
'onfullscreenchange',
this.handleFullscreenChange
)
} else if (el?.onwebkitfullscreenchange) {
el.removeEventListener(
'onwebkitfullscreenchange',
this.handleFullscreenChange
)
}
}

isFullscreenSupported = () => {
const el = document.querySelector(this.itemDomElSelector)
return !!(el?.requestFullscreen || el?.webkitRequestFullscreen)
}

handleFullscreenChange = () => {
this.setState({
configLoaded: true,
isFullscreen:
!!document.fullscreenElement ||
!!document.webkitFullscreenElement,
})
}

onToggleFullscreen = () => {
if (!this.state.isFullscreen) {
const el = document.querySelector(this.itemDomElSelector)
if (el?.requestFullscreen) {
el.requestFullscreen()
} else if (el?.webkitRequestFullscreen) {
el.webkitRequestFullscreen()
}
} else {
document.exitFullscreen
? document.exitFullscreen()
: document.webkitExitFullscreen()
}
}

getUniqueKey = memoizeOne(() => uniqueId())

onToggleFooter = () => {
Expand All @@ -100,6 +151,10 @@ export class Item extends Component {
}

getAvailableHeight = () => {
if (this.state.isFullscreen) {
return '95vh'
}

const calculatedHeight =
this.props.item.originalHeight -
this.headerRef.current.clientHeight -
Expand All @@ -119,16 +174,19 @@ export class Item extends Component {
const { showFooter } = this.state
const activeType = this.getActiveType()

const actionButtons = (
const actionButtons = pluginIsAvailable(activeType || item.type) ? (
<ItemHeaderButtons
item={item}
visualization={this.props.visualization}
onSelectActiveType={this.setActiveType}
onToggleFooter={this.onToggleFooter}
onToggleFullscreen={this.onToggleFullscreen}
activeType={activeType}
activeFooter={showFooter}
isFullscreen={this.state.isFullscreen}
fullscreenSupported={this.isFullscreenSupported()}
/>
)
) : null

return (
<>
Expand Down
34 changes: 29 additions & 5 deletions src/components/Item/VisualizationItem/ItemHeaderButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,13 @@ 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 { pluginIsAvailable, getLink } from './Visualization/plugin'
import {
ThreeDots,
SpeechBubble,
Fullscreen,
ExitFullscreen,
} from './assets/icons'
import { getLink } from './Visualization/plugin'
import {
CHART,
MAP,
Expand All @@ -31,7 +36,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)
Expand Down Expand Up @@ -60,6 +64,11 @@ const ItemHeaderButtons = (props, context) => {
}
}

const handleToggleFullscreenClick = () => {
props.onToggleFullscreen()
closeMenu()
}

const openMenu = () => setMenuIsOpen(true)
const closeMenu = () => setMenuIsOpen(false)

Expand Down Expand Up @@ -105,7 +114,11 @@ const ItemHeaderButtons = (props, context) => {

const buttonRef = createRef()

return pluginIsAvailable(activeType || item.type) ? (
return props.isFullscreen ? (
<Button small secondary onClick={props.onToggleFullscreen}>
<ExitFullscreen />
</Button>
) : (
<>
<div ref={buttonRef}>
<Button
Expand Down Expand Up @@ -146,20 +159,31 @@ const ItemHeaderButtons = (props, context) => {
label={interpretationMenuLabel}
onClick={handleInterpretationClick}
/>
{props.fullscreenSupported && (
<MenuItem
dense
icon={<Fullscreen />}
label={i18n.t('View fullscreen')}
onClick={handleToggleFullscreenClick}
/>
)}
</Menu>
</Popover>
)}
</>
) : null
)
}

ItemHeaderButtons.propTypes = {
activeFooter: PropTypes.bool,
activeType: PropTypes.string,
fullscreenSupported: PropTypes.bool,
isFullscreen: PropTypes.bool,
item: PropTypes.object,
visualization: PropTypes.object,
onSelectActiveType: PropTypes.func,
onToggleFooter: PropTypes.func,
onToggleFullscreen: PropTypes.func,
}

ItemHeaderButtons.contextTypes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jest.mock('../Visualization/plugin', () => ({
pluginIsAvailable: () => true,
}))

it('renders correctly', () => {
it('renders correctly when not fullscreen', () => {
const buttons = shallow(
<ItemHeaderButtons
item={{
Expand All @@ -27,3 +27,24 @@ it('renders correctly', () => {
)
expect(toJson(buttons)).toMatchSnapshot()
})

it('renders correctly when fullscreen', () => {
const buttons = shallow(
<ItemHeaderButtons
item={{
type: 'CHART',
chart: { type: 'NOT_YOY', domainType: 'AGGREGATE' },
}}
visualization={{
type: 'SINGLE_VALUE',
}}
onSelectActiveType={Function.prototype}
activeFooter={false}
activeType={'CHART'}
d2={{}}
onToggleFooter={Function.prototype}
isFullscreen={true}
/>
)
expect(toJson(buttons)).toMatchSnapshot()
})
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ exports[`VisualizationItem/Item does not render Visualization if config not load
<ItemHeaderButtons
activeFooter={false}
activeType="CHART"
fullscreenSupported={false}
isFullscreen={false}
item={
Object {
"chart": Object {
Expand All @@ -18,6 +20,7 @@ exports[`VisualizationItem/Item does not render Visualization if config not load
}
onSelectActiveType={[Function]}
onToggleFooter={[Function]}
onToggleFullscreen={[Function]}
visualization={
Object {
"id": "vis id",
Expand Down
Loading

0 comments on commit 746d7b0

Please sign in to comment.