summaryrefslogtreecommitdiff
path: root/opendc-web/opendc-web-ui/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'opendc-web/opendc-web-ui/src/components')
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppHeader.js46
-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.js96
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppHeaderUser.js81
-rw-r--r--opendc-web/opendc-web-ui/src/components/AppLogo.js46
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/ContextSelector.js9
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.scss2
-rw-r--r--opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js8
8 files changed, 156 insertions, 138 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"
/>
)
}