diff options
| author | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2022-09-14 23:02:46 +0200 |
|---|---|---|
| committer | Fabian Mastenbroek <mail.fabianm@gmail.com> | 2022-09-20 16:07:05 +0200 |
| commit | 9dd75a9a40f7f2aebbc617980c99085f9dc688f8 (patch) | |
| tree | 12b9fc2ef1b864c5773b78fe102f789508af7716 /opendc-web/opendc-web-ui/src | |
| parent | d2c0b9c038f5cbcb2b1528d4cb22b862309bd99a (diff) | |
refactor(web/ui): Move project selector into masthead
This change moves the project selector into the masthead since it
affects the whole application. This follows the PatternFly guidelines.
Diffstat (limited to 'opendc-web/opendc-web-ui/src')
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppHeader.js | 46 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppHeader.module.scss (renamed from opendc-web/opendc-web-ui/src/components/AppLogo.module.scss) | 6 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppHeaderTools.js | 96 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppHeaderUser.js | 81 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/AppLogo.js | 46 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/context/ContextSelector.js | 9 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js | 8 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js | 10 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js | 2 | ||||
| -rw-r--r-- | opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js | 2 |
11 files changed, 157 insertions, 151 deletions
diff --git a/opendc-web/opendc-web-ui/src/components/AppHeader.js b/opendc-web/opendc-web-ui/src/components/AppHeader.js index fd54b3ad..54f3bbf3 100644 --- a/opendc-web/opendc-web-ui/src/components/AppHeader.js +++ b/opendc-web/opendc-web-ui/src/components/AppHeader.js @@ -20,24 +20,44 @@ * SOFTWARE. */ -import { PageHeader } from '@patternfly/react-core' import React from 'react' +import { + Masthead, + MastheadMain, + MastheadBrand, + MastheadContent, + Toolbar, + ToolbarContent, + ToolbarItem, +} from '@patternfly/react-core' +import Link from "next/link"; import AppHeaderTools from './AppHeaderTools' -import { AppNavigation } from './AppNavigation' -import AppLogo from './AppLogo' +import AppHeaderUser from './AppHeaderUser' +import ProjectSelector from './context/ProjectSelector' -export function AppHeader() { - // eslint-disable-next-line @next/next/no-img-element - const logo = <img src="/img/logo.png" width={30} height={30} alt="OpenDC" /> +import styles from './AppHeader.module.scss' +export function AppHeader() { return ( - <PageHeader - logo={logo} - logoProps={{ href: '/' }} - logoComponent={AppLogo} - headerTools={<AppHeaderTools />} - topNav={<AppNavigation />} - /> + <Masthead id="app-header"> + <MastheadMain> + <MastheadBrand className={styles.logo} component={props => <Link href="/projects"><a {...props} /></Link>}> + <img src="/img/logo.svg" alt="OpenDC logo" width={30} height={30} /> + <span>OpenDC</span> + </MastheadBrand> + </MastheadMain> + <MastheadContent> + <Toolbar id="toolbar" isFullHeight isStatic> + <ToolbarContent> + <ToolbarItem> + <ProjectSelector /> + </ToolbarItem> + <AppHeaderTools /> + <AppHeaderUser /> + </ToolbarContent> + </Toolbar> + </MastheadContent> + </Masthead> ) } diff --git a/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss b/opendc-web/opendc-web-ui/src/components/AppHeader.module.scss index 3d228cb6..a7a6e325 100644 --- a/opendc-web/opendc-web-ui/src/components/AppLogo.module.scss +++ b/opendc-web/opendc-web-ui/src/components/AppHeader.module.scss @@ -20,10 +20,12 @@ * SOFTWARE. */ -.appLogo { +.logo { span { - margin-left: 4px; + margin-left: 8px; color: #fff; + align-self: center; + font-weight: 500; } &:hover, diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js index 3e58b209..499bceef 100644 --- a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js +++ b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js @@ -21,29 +21,19 @@ */ import { - Avatar, Button, ButtonVariant, Dropdown, - DropdownGroup, DropdownItem, - DropdownToggle, KebabToggle, - PageHeaderTools, - PageHeaderToolsGroup, - PageHeaderToolsItem, - Skeleton, + ToolbarGroup, + ToolbarItem, } from '@patternfly/react-core' -import { useState } from 'react' -import { useAuth } from '../auth' +import { useReducer } from 'react' import { GithubIcon, HelpIcon } from '@patternfly/react-icons' function AppHeaderTools() { - const { logout, user, isAuthenticated, isLoading } = useAuth() - const username = isAuthenticated || isLoading ? user?.name : 'Anonymous' - const avatar = isAuthenticated || isLoading ? user?.picture : '/img/avatar.svg' - - const [isKebabDropdownOpen, setKebabDropdownOpen] = useState(false) + const [isKebabDropdownOpen, toggleKebabDropdown] = useReducer((t) => !t, false) const kebabDropdownItems = [ <DropdownItem key={0} @@ -55,23 +45,14 @@ function AppHeaderTools() { />, ] - const [isDropdownOpen, setDropdownOpen] = useState(false) - const userDropdownItems = [ - <DropdownGroup key="group 2"> - <DropdownItem - key="group 2 logout" - isDisabled={!isAuthenticated} - onClick={() => logout({ returnTo: window.location.origin })} - > - Logout - </DropdownItem> - </DropdownGroup>, - ] - return ( - <PageHeaderTools> - <PageHeaderToolsGroup visibility={{ default: 'hidden', lg: 'visible' }}> - <PageHeaderToolsItem> + <ToolbarGroup + variant="icon-button-group" + alignment={{ default: 'alignRight' }} + spacer={{ default: 'spacerNone', md: 'spacerMd' }} + > + <ToolbarGroup variant="icon-button-group" visibility={{ default: 'hidden', lg: 'visible' }}> + <ToolbarItem> <Button component="a" href="https://github.com/atlarge-research/opendc" @@ -81,8 +62,8 @@ function AppHeaderTools() { > <GithubIcon /> </Button> - </PageHeaderToolsItem> - <PageHeaderToolsItem> + </ToolbarItem> + <ToolbarItem> <Button component="a" href="https://opendc.org/" @@ -92,45 +73,18 @@ function AppHeaderTools() { > <HelpIcon /> </Button> - </PageHeaderToolsItem> - </PageHeaderToolsGroup> - <PageHeaderToolsGroup> - <PageHeaderToolsItem visibility={{ lg: 'hidden' }}> - <Dropdown - isPlain - position="right" - toggle={<KebabToggle onToggle={() => setKebabDropdownOpen(!isKebabDropdownOpen)} />} - isOpen={isKebabDropdownOpen} - dropdownItems={kebabDropdownItems} - /> - </PageHeaderToolsItem> - <PageHeaderToolsItem visibility={{ default: 'hidden', md: 'visible' }}> - <Dropdown - isPlain - position="right" - isOpen={isDropdownOpen} - toggle={ - <DropdownToggle onToggle={() => setDropdownOpen(!isDropdownOpen)}> - {username ?? ( - <Skeleton - fontSize="xs" - width="150px" - className="pf-u-display-inline-flex" - screenreaderText="Loading username" - /> - )} - </DropdownToggle> - } - dropdownItems={userDropdownItems} - /> - </PageHeaderToolsItem> - </PageHeaderToolsGroup> - {avatar ? ( - <Avatar src={avatar} alt="Avatar image" /> - ) : ( - <Skeleton className="pf-c-avatar" shape="circle" width="2.25rem" screenreaderText="Loading avatar" /> - )} - </PageHeaderTools> + </ToolbarItem> + </ToolbarGroup> + <ToolbarItem visibility={{ lg: 'hidden' }}> + <Dropdown + isPlain + position="right" + toggle={<KebabToggle onToggle={toggleKebabDropdown} />} + isOpen={isKebabDropdownOpen} + dropdownItems={kebabDropdownItems} + /> + </ToolbarItem> + </ToolbarGroup> ) } diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderUser.js b/opendc-web/opendc-web-ui/src/components/AppHeaderUser.js new file mode 100644 index 00000000..809f3ac3 --- /dev/null +++ b/opendc-web/opendc-web-ui/src/components/AppHeaderUser.js @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2022 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { + Dropdown, + DropdownToggle, + Skeleton, + ToolbarItem, + DropdownItem, + DropdownGroup, + Avatar, +} from '@patternfly/react-core' +import { useReducer } from 'react' +import { useAuth } from '../auth' + +export default function AppHeaderUser() { + const { logout, user, isAuthenticated, isLoading } = useAuth() + const username = isAuthenticated || isLoading ? user?.name : 'Anonymous' + const avatar = isAuthenticated || isLoading ? user?.picture : '/img/avatar.svg' + + const [isDropdownOpen, toggleDropdown] = useReducer((t) => !t, false) + const userDropdownItems = [ + <DropdownGroup key="group 2"> + <DropdownItem + key="group 2 logout" + isDisabled={!isAuthenticated} + onClick={() => logout({ returnTo: window.location.origin })} + > + Logout + </DropdownItem> + </DropdownGroup>, + ] + + const avatarComponent = avatar ? ( + <Avatar src={avatar} alt="Avatar image" /> + ) : ( + <Skeleton className="pf-c-avatar" shape="circle" width="2.25rem" screenreaderText="Loading avatar" /> + ) + + return ( + <ToolbarItem visibility={{ default: 'hidden', md: 'visible' }}> + <Dropdown + isFullHeight + position="right" + isOpen={isDropdownOpen} + toggle={ + <DropdownToggle onToggle={toggleDropdown} icon={avatarComponent}> + {username ?? ( + <Skeleton + fontSize="xs" + width="150px" + className="pf-u-display-inline-flex" + screenreaderText="Loading username" + /> + )} + </DropdownToggle> + } + dropdownItems={userDropdownItems} + /> + </ToolbarItem> + ) +} diff --git a/opendc-web/opendc-web-ui/src/components/AppLogo.js b/opendc-web/opendc-web-ui/src/components/AppLogo.js deleted file mode 100644 index 92663295..00000000 --- a/opendc-web/opendc-web-ui/src/components/AppLogo.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import Link from 'next/link' -import { appLogo } from './AppLogo.module.scss' - -function AppLogo({ href, children, className, ...props }) { - return ( - <> - <Link href={href}> - <a {...props} className={`${className ?? ''} ${appLogo}`}> - {children} - <span>OpenDC</span> - </a> - </Link> - </> - ) -} - -AppLogo.propTypes = { - href: PropTypes.string.isRequired, - children: PropTypes.node, - className: PropTypes.string, -} - -export default AppLogo diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js index a99b60c0..436c179b 100644 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js @@ -23,9 +23,9 @@ import PropTypes from 'prop-types' import { ContextSelector as PFContextSelector, ContextSelectorItem } from '@patternfly/react-core' import { useMemo, useState } from 'react' -import { contextSelector } from './ContextSelector.module.scss' +import styles from './ContextSelector.module.scss' -function ContextSelector({ activeItem, items, onSelect, onToggle, isOpen, label }) { +function ContextSelector({ activeItem, items, onSelect, onToggle, isOpen, label, isFullHeight, type = 'page' }) { const [searchValue, setSearchValue] = useState('') const filteredItems = useMemo( () => items.filter(({ name }) => name.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1) || items, @@ -34,7 +34,7 @@ function ContextSelector({ activeItem, items, onSelect, onToggle, isOpen, label return ( <PFContextSelector - className={contextSelector} + className={type === 'page' && styles.pageSelector} toggleText={activeItem ? `${label}: ${activeItem.name}` : label} onSearchInputChange={(value) => setSearchValue(value)} searchInputValue={searchValue} @@ -47,6 +47,7 @@ function ContextSelector({ activeItem, items, onSelect, onToggle, isOpen, label onSelect(target) onToggle(!isOpen) }} + isFullHeight={isFullHeight} > {filteredItems.map((item) => ( <ContextSelectorItem key={item.id} value={item.id}> @@ -69,6 +70,8 @@ ContextSelector.propTypes = { onToggle: PropTypes.func.isRequired, isOpen: PropTypes.bool, label: PropTypes.string, + isFullHeight: PropTypes.bool, + type: PropTypes.oneOf(['app', 'page']), } export default ContextSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss index c4b89503..4f86ac64 100644 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss +++ b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss @@ -20,7 +20,7 @@ * SOFTWARE. */ -.contextSelector.contextSelector { +.pageSelector.pageSelector { // Ensure this selector has precedence over the default one margin-right: 20px; diff --git a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js index 7721e04c..5f47c798 100644 --- a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js +++ b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js @@ -22,14 +22,16 @@ import { useRouter } from 'next/router' import { useState } from 'react' -import { useProjects } from '../../data/project' +import { useProjects, useProject } from '../../data/project' import { Project } from '../../shapes' import ContextSelector from './ContextSelector' -function ProjectSelector({ activeProject }) { +function ProjectSelector() { const router = useRouter() + const projectId = +router.query['project'] const [isOpen, setOpen] = useState(false) + const { data: activeProject } = useProject(+projectId) const { data: projects = [] } = useProjects({ enabled: isOpen }) return ( @@ -40,6 +42,8 @@ function ProjectSelector({ activeProject }) { onSelect={(project) => router.push(`/projects/${project.id}`)} onToggle={setOpen} isOpen={isOpen} + isFullHeight + type="app" /> ) } diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js index 39fcb4f3..e4e2156b 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js @@ -21,9 +21,7 @@ */ import { useRouter } from 'next/router' -import ContextSelectionSection from '../../../components/context/ContextSelectionSection' import ProjectOverview from '../../../components/projects/ProjectOverview' -import ProjectSelector from '../../../components/context/ProjectSelector' import { useProject } from '../../../data/project' import { AppPage } from '../../../components/AppPage' import Head from 'next/head' @@ -55,14 +53,8 @@ function Project() { </Breadcrumb> ) - const contextSelectors = ( - <ContextSelectionSection> - <ProjectSelector activeProject={project} /> - </ContextSelectionSection> - ) - return ( - <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> + <AppPage breadcrumb={breadcrumb}> <Head> <title>{project?.name ?? 'Project'} - OpenDC</title> </Head> diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js index 4b9aa437..460785c1 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js @@ -40,7 +40,6 @@ import { import { AppPage } from '../../../../components/AppPage' import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' import PortfolioSelector from '../../../../components/context/PortfolioSelector' -import ProjectSelector from '../../../../components/context/ProjectSelector' import BreadcrumbLink from '../../../../components/util/BreadcrumbLink' import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview' import { usePortfolio } from '../../../../data/project' @@ -81,7 +80,6 @@ function Portfolio() { const contextSelectors = ( <ContextSelectionSection> - <ProjectSelector activeProject={project} /> <PortfolioSelector activePortfolio={portfolio} /> </ContextSelectionSection> ) diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js index c331761b..d3892710 100644 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js @@ -23,7 +23,6 @@ import dynamic from 'next/dynamic' import { useRouter } from 'next/router' import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' -import ProjectSelector from '../../../../components/context/ProjectSelector' import TopologySelector from '../../../../components/context/TopologySelector' import TopologyOverview from '../../../../components/topologies/TopologyOverview' import { useDispatch } from 'react-redux' @@ -90,7 +89,6 @@ function Topology() { const contextSelectors = ( <ContextSelectionSection> - <ProjectSelector activeProject={project} /> <TopologySelector activeTopology={topology} /> </ContextSelectionSection> ) |
