Skip to content

Commit

Permalink
feat: implement dashboard slideshow (#3081)
Browse files Browse the repository at this point in the history
Implements https://dhis2.atlassian.net/browse/DHIS2-13038

Details:

1. When entering the slideshow/fullscreen, the parent div of the ItemGrid is put into browser fullscreen. That same div remains in fullscreen the entire time, while the dashboard items are displayed/hidden in turn using css opacity. Css opacity is used instead of display or visibility so that the items can first be resized to fullscreen while "invisible", and then be made visible. This means that the user doesn't see a flash of the visualization in its regular item size before it gets resized.

2. Progressive loading. When the dashboard goes into fullscreen/slideshow, then progressive loading will load the next and previous items in the fullscreen display order instead of being based on proximity to the viewport.

3. RTL support has been implemented for the slideshow navigation buttons. It was decided after discussions with in-house RTL experts that the order of the dashboard items would remain LTR.
  • Loading branch information
jenniferarnesen authored Dec 20, 2024
1 parent 45e6f4c commit cb2b931
Show file tree
Hide file tree
Showing 80 changed files with 1,239 additions and 376 deletions.
31 changes: 31 additions & 0 deletions cypress/e2e/slideshow.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Feature: Slideshow

Scenario: I view a dashboard in slideshow
Given I open the "Delivery" dashboard
When I click the slideshow button
Then item 1 is shown in fullscreen
When I click the next slide button
Then item 2 is shown in fullscreen
When I click the previous slide button
Then item 1 is shown in fullscreen
When I click the exit slideshow button
Then the normal view is shown


Scenario: I view fullscreen on the second item of the dashboard
Given I open the "Delivery" dashboard
When I click the fullscreen button on the second item
Then item 2 is shown in fullscreen
When I click the exit slideshow button
Then the normal view is shown

Scenario: I view fullscreen on the third item of the dashboard and navigate backwards
Given I open the "Delivery" dashboard
When I click the fullscreen button on the third item
Then item 3 is shown in fullscreen
When I click the previous slide button
Then item 2 is shown in fullscreen
When I click the previous slide button
Then item 1 is shown in fullscreen
When I click the exit slideshow button
Then the normal view is shown
1 change: 1 addition & 0 deletions cypress/e2e/slideshow/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
'../common/index.js'
103 changes: 103 additions & 0 deletions cypress/e2e/slideshow/slideshow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { When, Then } from '@badeball/cypress-cucumber-preprocessor'
import {
getDashboardItem,
clickMenuButton,
} from '../../elements/dashboardItem.js'

const sortedDashboardItemIds = ['GaVhJpqABYX', 'qXsjttMYuoZ', 'Rwb3oXJ3bZ9']

const assertItemIsVisible = (slideshowItemIndex) => {
getDashboardItem(sortedDashboardItemIds[slideshowItemIndex]).should(
'have.css',
'opacity',
'1'
)
}

const assertItemIsNotVisible = (slideshowItemIndex) => {
getDashboardItem(sortedDashboardItemIds[slideshowItemIndex]).should(
'have.css',
'opacity',
'0'
)
}

const getSlideshowExitButton = () =>
cy.getByDataTest('slideshow-exit-button', { timeout: 15000 })

When('I click the slideshow button', () => {
cy.get('button').contains('Slideshow').realClick()
})

Then('item 1 is shown in fullscreen', () => {
getSlideshowExitButton().should('be.visible')

// check that only the first item is shown
assertItemIsVisible(0)
assertItemIsNotVisible(1)
assertItemIsNotVisible(2)

cy.getByDataTest('slideshow-page-counter').should('have.text', '1 / 11')

// visible item does not have context menu button
getDashboardItem(sortedDashboardItemIds[0])
.findByDataTest('dashboarditem-menu-button')
.should('not.exist')
})

When('I click the exit slideshow button', () => {
getSlideshowExitButton().realClick()
})

Then('the normal view is shown', () => {
getSlideshowExitButton().should('not.exist')

// check that multiple items are shown
assertItemIsVisible(0)
assertItemIsVisible(1)
assertItemIsVisible(2)

// items have context menu button
getDashboardItem(sortedDashboardItemIds[0])
.findByDataTest('dashboarditem-menu-button')
.should('be.visible')

getDashboardItem(sortedDashboardItemIds[1])
.findByDataTest('dashboarditem-menu-button')
.should('be.visible')
})

// When I click the next slide button
When('I click the next slide button', () => {
cy.getByDataTest('slideshow-next-button').realClick()
})

Then('item 2 is shown in fullscreen', () => {
assertItemIsNotVisible(0)
assertItemIsVisible(1)
assertItemIsNotVisible(2)

cy.getByDataTest('slideshow-page-counter').should('have.text', '2 / 11')
})

When('I click the previous slide button', () => {
cy.getByDataTest('slideshow-prev-button').realClick()
})

When('I click the fullscreen button on the second item', () => {
clickMenuButton(sortedDashboardItemIds[1])
cy.contains('View fullscreen').realClick()
})

When('I click the fullscreen button on the third item', () => {
clickMenuButton(sortedDashboardItemIds[2])
cy.contains('View fullscreen').realClick()
})

Then('item 3 is shown in fullscreen', () => {
assertItemIsNotVisible(0)
assertItemIsNotVisible(1)
assertItemIsVisible(2)

cy.getByDataTest('slideshow-page-counter').should('have.text', '3 / 11')
})
125 changes: 69 additions & 56 deletions docs/dashboards.md

Large diffs are not rendered by default.

Binary file modified docs/resources/images/dashboard-clear-filters-to-sync.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-confirm-delete.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-create-mode.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/resources/images/dashboard-edit-anc.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/resources/images/dashboard-edit-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-edit-print-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-filter-badges.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-filter-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-filters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/resources/images/dashboard-generic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-item-menu-interpretations.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-item-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-item-selector.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-layout-modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-more-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-new-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-offline-dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/resources/images/dashboard-place-items.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-print-menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file modified docs/resources/images/dashboard-sharing-add-user.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/resources/images/dashboard-sharing-cascade-sharing.png
Binary file modified docs/resources/images/dashboard-sharing-dialog.png
Binary file modified docs/resources/images/dashboard-small-screen.png
Binary file modified docs/resources/images/dashboard-spacer-edit-mode.png
Binary file modified docs/resources/images/dashboard-spacer-view-mode.png
Binary file modified docs/resources/images/dashboard-translation-dialog.png
Binary file modified docs/resources/images/dashboard-view-mode.png
46 changes: 36 additions & 10 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: 2024-12-18T12:26:05.212Z\n"
"PO-Revision-Date: 2024-12-18T12:26:05.212Z\n"
"POT-Creation-Date: 2024-12-19T11:30:27.893Z\n"
"PO-Revision-Date: 2024-12-19T11:30:27.893Z\n"

msgid "Untitled dashboard"
msgstr "Untitled dashboard"
Expand Down Expand Up @@ -50,12 +50,21 @@ msgstr "One item per page"
msgid "Close dashboard"
msgstr "Close dashboard"

msgid "No dashboard items to show in slideshow"
msgstr "No dashboard items to show in slideshow"

msgid "Not available offline"
msgstr "Not available offline"

msgid "Edit"
msgstr "Edit"

msgid "Share"
msgstr "Share"

msgid "Slideshow"
msgstr "Slideshow"

msgid "Clear dashboard filters?"
msgstr "Clear dashboard filters?"

Expand Down Expand Up @@ -111,6 +120,9 @@ msgstr "Search for a dashboard"
msgid "No dashboards found"
msgstr "No dashboards found"

msgid "{{appKey}} app not found"
msgstr "{{appKey}} app not found"

msgid "Remove this item"
msgstr "Remove this item"

Expand Down Expand Up @@ -237,8 +249,11 @@ msgstr "There was an error loading data for this item"
msgid "Open this item in {{appName}}"
msgstr "Open this item in {{appName}}"

msgid "Not available offline"
msgstr "Not available offline"
msgid "Resources"
msgstr "Resources"

msgid "Reports"
msgstr "Reports"

msgid "Visualizations"
msgstr "Visualizations"
Expand All @@ -264,12 +279,6 @@ msgstr "Line lists"
msgid "Apps"
msgstr "Apps"

msgid "Reports"
msgstr "Reports"

msgid "Resources"
msgstr "Resources"

msgid "Users"
msgstr "Users"

Expand Down Expand Up @@ -566,6 +575,23 @@ msgstr ""
msgid "Yes, remove filters"
msgstr "Yes, remove filters"

msgid "Exit slideshow"
msgstr "Exit slideshow"

msgid "Previous item"
msgstr "Previous item"

msgid "Next item"
msgstr "Next item"

msgid "{{name}}: {{filter}}"
msgstr "{{name}}: {{filter}}"

msgid "{{count}} filters active"
msgid_plural "{{count}} filters active"
msgstr[0] "{{count}} filter active"
msgstr[1] "{{count}} filters active"

msgid "Loading dashboard – {{name}}"
msgstr "Loading dashboard – {{name}}"

Expand Down
6 changes: 6 additions & 0 deletions src/actions/slideshow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { SET_SLIDESHOW } from '../reducers/slideshow.js'

export const acSetSlideshow = (isSlideshow) => ({
type: SET_SLIDESHOW,
value: isSlideshow,
})
43 changes: 40 additions & 3 deletions src/components/DashboardsBar/InformationBlock/ActionsBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,19 @@ import { connect } from 'react-redux'
import { useHistory, Redirect } from 'react-router-dom'
import { acClearItemFilters } from '../../../actions/itemFilters.js'
import { acSetShowDescription } from '../../../actions/showDescription.js'
import { acSetSlideshow } from '../../../actions/slideshow.js'
import { apiPostShowDescription } from '../../../api/description.js'
import ConfirmActionDialog from '../../../components/ConfirmActionDialog.js'
import DropdownButton from '../../../components/DropdownButton/DropdownButton.js'
import MenuItem from '../../../components/MenuItemWithTooltip.js'
import { useSystemSettings } from '../../../components/SystemSettingsProvider.js'
import { itemTypeSupportsFullscreen } from '../../../modules/itemTypes.js'
import { useCacheableSection } from '../../../modules/useCacheableSection.js'
import { orObject } from '../../../modules/util.js'
import { ROUTE_START_PATH } from '../../../pages/start/index.js'
import { sGetNamedItemFilters } from '../../../reducers/itemFilters.js'
import { sGetSelected } from '../../../reducers/selected.js'
import { sGetShowDescription } from '../../../reducers/showDescription.js'
import ConfirmActionDialog from '../../ConfirmActionDialog.js'
import DropdownButton from '../../DropdownButton/DropdownButton.js'
import MenuItem from '../../MenuItemWithTooltip.js'
import FilterSelector from './FilterSelector.js'
import classes from './styles/ActionsBar.module.css'

Expand All @@ -32,13 +35,15 @@ const ActionsBar = ({
access,
showDescription,
starred,
setSlideshow,
toggleDashboardStarred,
showAlert,
updateShowDescription,
removeAllFilters,
restrictFilters,
allowedFilters,
filtersLength,
dashboardItems,
}) => {
const history = useHistory()
const [moreOptionsIsOpen, setMoreOptionsIsOpen] = useState(false)
Expand All @@ -49,6 +54,7 @@ const ActionsBar = ({
const { isDisconnected: offline } = useDhis2ConnectionStatus()
const { lastUpdated, isCached, startRecording, remove } =
useCacheableSection(id)
const { allowVisFullscreen } = useSystemSettings().systemSettings

const onRecordError = useCallback(() => {
showAlert({
Expand Down Expand Up @@ -94,6 +100,10 @@ const ActionsBar = ({

const userAccess = orObject(access)

const hasSlideshowItems = dashboardItems?.some(
(i) => itemTypeSupportsFullscreen(i.type) || false
)

const getMoreMenu = () => (
<FlyoutMenu>
{lastUpdated ? (
Expand Down Expand Up @@ -176,6 +186,12 @@ const ActionsBar = ({
return <Redirect to={redirectUrl} />
}

const slideshowTooltipContent = !hasSlideshowItems
? i18n.t('No dashboard items to show in slideshow')
: offline && !isCached
? i18n.t('Not available offline')
: null

return (
<>
<div className={classes.actions}>
Expand Down Expand Up @@ -204,6 +220,24 @@ const ActionsBar = ({
</Button>
</OfflineTooltip>
) : null}
{allowVisFullscreen ? (
<OfflineTooltip
content={slideshowTooltipContent}
disabled={!!slideshowTooltipContent}
disabledWhenOffline={false}
>
<Button
secondary
small
disabled={!!slideshowTooltipContent}
className={classes.slideshowButton}
onClick={() => setSlideshow(0)}
dataTest="enter-slideshow-button"
>
{i18n.t('Slideshow')}
</Button>
</OfflineTooltip>
) : null}
<FilterSelector
allowedFilters={allowedFilters}
restrictFilters={restrictFilters}
Expand Down Expand Up @@ -248,10 +282,12 @@ const ActionsBar = ({
ActionsBar.propTypes = {
access: PropTypes.object,
allowedFilters: PropTypes.array,
dashboardItems: PropTypes.array,
filtersLength: PropTypes.number,
id: PropTypes.string,
removeAllFilters: PropTypes.func,
restrictFilters: PropTypes.bool,
setSlideshow: PropTypes.func,
showAlert: PropTypes.func,
showDescription: PropTypes.bool,
starred: PropTypes.bool,
Expand All @@ -270,6 +306,7 @@ const mapStateToProps = (state) => {
}

export default connect(mapStateToProps, {
setSlideshow: acSetSlideshow,
removeAllFilters: acClearItemFilters,
updateShowDescription: acSetShowDescription,
})(ActionsBar)
Loading

0 comments on commit cb2b931

Please sign in to comment.