From 3cd68f9ca0ea95547278fcaf1374db1784673250 Mon Sep 17 00:00:00 2001 From: Diana Nanyanzi Date: Fri, 27 Sep 2024 03:11:42 +0300 Subject: [PATCH] feat: implement command palette - Launch modal on clicking AllApps icon - Add ordered apps section and action buttons section - Add different views depending on what the user is doing --- components/header-bar/src/apps.js | 598 +++++++++++++++++++----- components/header-bar/src/header-bar.js | 34 +- 2 files changed, 512 insertions(+), 120 deletions(-) diff --git a/components/header-bar/src/apps.js b/components/header-bar/src/apps.js index 37e5f805ce..95fd4ab99e 100755 --- a/components/header-bar/src/apps.js +++ b/components/header-bar/src/apps.js @@ -1,10 +1,19 @@ -import { useConfig } from '@dhis2/app-runtime' +import { clearSensitiveCaches, useConfig } from '@dhis2/app-runtime' import { colors, spacers, theme } from '@dhis2/ui-constants' -import { IconApps24, IconSettings24 } from '@dhis2/ui-icons' +import { + IconApps24, + IconApps16, + IconTerminalWindow16, + IconLogOut16, + IconArrowLeft16, +} from '@dhis2/ui-icons' +import { Button } from '@dhis2-ui/button' import { Card } from '@dhis2-ui/card' import { InputField } from '@dhis2-ui/input' +import { MenuItem } from '@dhis2-ui/menu' +import { Modal, ModalContent } from '@dhis2-ui/modal' import PropTypes from 'prop-types' -import React, { useState, useEffect, useCallback, useRef } from 'react' +import React, { useState, useCallback, useRef } from 'react' import { joinPath } from './join-path.js' import i18n from './locales/index.js' @@ -17,43 +26,28 @@ function escapeRegExpCharacters(text) { } function Search({ value, onChange }) { - const { baseUrl } = useConfig() - return (
- - - - - - -
) @@ -74,13 +68,13 @@ function Item({ name, path, img }) { + + ) +} - min-height: 200px; - max-height: 465px; - margin-block-start: 0; - margin-block-end: 8px; - margin-inline: 8px; +AppItem.propTypes = { + description: PropTypes.string, + image: PropTypes.string, + name: PropTypes.string, + path: PropTypes.string, +} - overflow: auto; - overflow-x: hidden; - } - `} - +function BackToHomeButton({ setView }) { + const handleClick = () => { + setView('home') + } + return ( + {show ? ( - @@ -269,8 +279,360 @@ const Apps = ({ apps }) => { ) } -Apps.propTypes = { - apps: PropTypes.array.isRequired, +CommandPalette.propTypes = { + apps: PropTypes.array, + commands: PropTypes.array, +} + +export const MenuModal = ({ show, apps, commands, filter, onFilterChange }) => { + console.log(apps, 'apps') + const [currentView, setCurrentView] = useState('home') + const showActions = filter.length <= 0 && currentView === 'home' + + return ( +
+ {show && ( + + +
+ + + + {showActions ? ( + + ) : null} + +
+
+
+ )} +
+ ) +} + +MenuModal.propTypes = { + apps: PropTypes.array, + commands: PropTypes.array, + filter: PropTypes.string, + show: PropTypes.bool, + onFilterChange: PropTypes.func, +} + +function ViewSwitcher({ apps, commands, filter, view, setView }) { + switch (view) { + case 'apps': + return + case 'commands': + return ( + + ) + case 'home': + default: + return + } +} + +ViewSwitcher.propTypes = { + apps: PropTypes.array, + commands: PropTypes.array, + filter: PropTypes.string, + setView: PropTypes.func, + view: PropTypes.string, +} + +function AllAppsView({ apps, filter, setView }) { + const filteredApps = apps.filter(({ displayName, name }) => { + const appName = displayName || name + const formattedAppName = appName.toLowerCase() + const formattedFilter = escapeRegExpCharacters(filter).toLowerCase() + + return filter.length > 0 + ? formattedAppName.match(formattedFilter) + : true + }) + + return ( + <> + +
+ {filter ? ( + filteredApps.length > 0 ? ( +

+ Results for {filter} +

+ ) : ( +

+ Nothing found for {filter} +

+ ) + ) : ( +
+ {i18n.t('All Apps')} +
+ )} +
+ + + ) +} + +AllAppsView.propTypes = { + apps: PropTypes.array, + filter: PropTypes.string, + setView: PropTypes.func, +} + +function CommandsView({ commands, filter, setView }) { + const filteredCommands = commands.filter(({ displayName, name }) => { + const commandName = displayName || name + const formattedAppName = commandName.toLowerCase() + const formattedFilter = escapeRegExpCharacters(filter).toLowerCase() + + return filter.length > 0 + ? formattedAppName.match(formattedFilter) + : true + }) + + return ( + <> + +
+ {filter ? ( + filteredCommands.length > 0 ? ( + Results for {filter} + ) : ( + Nothing found for {filter} + ) + ) : ( +
+ {i18n.t('All Commands')} +
+ )} +
+ {filteredCommands.map( + ( + { displayName, name, defaultAction, icon, description }, + idx + ) => ( + + ) + )} + + ) +} + +CommandsView.propTypes = { + commands: PropTypes.array, + filter: PropTypes.string, + setView: PropTypes.func, +} + +function HomeView({ apps, filter }) { + const filteredApps = apps.filter(({ displayName, name }) => { + const appName = displayName || name + const formattedAppName = appName.toLowerCase() + const formattedFilter = escapeRegExpCharacters(filter).toLowerCase() + + return filter.length > 0 + ? formattedAppName.match(formattedFilter) + : true + }) + return ( + <> + {filter ? ( + filteredApps.length > 0 ? ( + Results for {filter} + ) : ( + Nothing found for {filter} + ) + ) : ( +
{i18n.t('Top Apps')}
+ )} +
+ {filteredApps.length > 0 && + filteredApps + .slice(0, 8) + .map( + ( + { displayName, name, defaultAction, icon }, + idx + ) => ( + + ) + )} + + +
+ + ) +} + +HomeView.propTypes = { + apps: PropTypes.array, + filter: PropTypes.string, +} + +function List({ apps, filter }) { + return ( +
+ {apps + .filter(({ displayName, name }) => { + const appName = displayName || name + const formattedAppName = appName.toLowerCase() + const formattedFilter = + escapeRegExpCharacters(filter).toLowerCase() + + return filter.length > 0 + ? formattedAppName.match(formattedFilter) + : true + }) + .map(({ displayName, name, defaultAction, icon }, idx) => ( + + ))} + + +
+ ) +} +List.propTypes = { + apps: PropTypes.array, + filter: PropTypes.string, +} + +function Actions({ setView }) { + const actions = [ + { + icon: IconApps16, + type: 'apps', + action: 'Browse apps', + }, + { + icon: IconTerminalWindow16, + type: 'commands', + action: 'Browse commands', + }, + ] + + const { baseUrl } = useConfig() + + return ( + <> +
{i18n.t('Actions')}
+ {actions.map((action, index) => ( + { + console.log(payload.value, event.target) + setView(action.type) + }} + label={i18n.t(`${action.action}`)} + value={action.action} + icon={} + /> + ))} + { + // setLoading(true) + await clearSensitiveCaches() + // setLoading(false) + window.location.assign( + joinPath( + baseUrl, + 'dhis-web-commons-security/logout.action' + ) + ) + }} + label={i18n.t('Logout')} + value="logout" + icon={} + /> + + ) +} + +Actions.propTypes = { + setView: PropTypes.func, } -export default Apps +export default CommandPalette diff --git a/components/header-bar/src/header-bar.js b/components/header-bar/src/header-bar.js index 673c6b4ab9..f8ebd5094e 100755 --- a/components/header-bar/src/header-bar.js +++ b/components/header-bar/src/header-bar.js @@ -2,7 +2,7 @@ import { useDataQuery, useConfig } from '@dhis2/app-runtime' import { colors } from '@dhis2/ui-constants' import PropTypes from 'prop-types' import React, { useMemo } from 'react' -import Apps from './apps.js' +import CommandPalette from './apps.js' import { HeaderBarContextProvider } from './header-bar-context.js' import { joinPath } from './join-path.js' import i18n from './locales/index.js' @@ -55,6 +55,36 @@ export const HeaderBar = ({ })) }, [data, baseUrl]) + const commands = [ + { + defaultAction: function handleOpen() { + console.log('open...') + }, + description: 'Search for and open a visualisation, chart, or table', + displayName: 'Open...', + icon: 'https://domain.tld/api/../icons/dhis-web-dashboard.png', + name: 'open', + }, + { + defaultAction: function handleOpen() { + console.log('debug...') + }, + description: 'Copy debug information to the clipboard', + displayName: 'Debug...', + icon: 'https://domain.tld/api/../icons/dhis-web-dashboard.png', + name: 'debug', + }, + { + defaultAction: function handleOpen() { + console.log('clearing cache...') + }, + description: 'Empty system cache', + displayName: 'Clear cache...', + icon: 'https://domain.tld/api/../icons/dhis-web-dashboard.png', + name: 'clear-cache', + }, + ] + // See https://jira.dhis2.org/browse/LIBS-180 if (!loading && !error) { // TODO: This will run every render which is probably wrong! @@ -94,7 +124,7 @@ export const HeaderBar = ({ } userAuthorities={data.user.authorities} /> - +