diff --git a/frontend/svalyn-studio-app/src/domains/DomainsShell.tsx b/frontend/svalyn-studio-app/src/domains/DomainsShell.tsx
index c74c1a6..3759ae6 100644
--- a/frontend/svalyn-studio-app/src/domains/DomainsShell.tsx
+++ b/frontend/svalyn-studio-app/src/domains/DomainsShell.tsx
@@ -18,9 +18,7 @@
*/
import Container from '@mui/material/Container';
-import Link from '@mui/material/Link';
import Toolbar from '@mui/material/Toolbar';
-import { Link as RouterLink } from 'react-router-dom';
import { Navbar } from '../navbars/Navbar';
import { DomainsShellProps } from './DomainsShell.types';
@@ -28,11 +26,7 @@ export const DomainsShell = ({ children }: DomainsShellProps) => {
return (
<>
-
-
- Domains
-
-
+
diff --git a/frontend/svalyn-studio-app/src/navbars/Navbar.tsx b/frontend/svalyn-studio-app/src/navbars/Navbar.tsx
index 9563ba5..97f925f 100644
--- a/frontend/svalyn-studio-app/src/navbars/Navbar.tsx
+++ b/frontend/svalyn-studio-app/src/navbars/Navbar.tsx
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Stéphane Bégaudeau.
+ * Copyright (c) 2022, 2023 Stéphane Bégaudeau.
*
* 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,
@@ -18,36 +18,24 @@
*/
import { gql, useQuery } from '@apollo/client';
-import HelpIcon from '@mui/icons-material/Help';
-import HomeIcon from '@mui/icons-material/Home';
-import LogoutIcon from '@mui/icons-material/Logout';
-import MailOutlineIcon from '@mui/icons-material/MailOutline';
+import HubOutlinedIcon from '@mui/icons-material/HubOutlined';
import NotificationsNoneIcon from '@mui/icons-material/NotificationsNone';
-import PersonIcon from '@mui/icons-material/Person';
-import SearchIcon from '@mui/icons-material/Search';
-import SettingsIcon from '@mui/icons-material/Settings';
import AppBar from '@mui/material/AppBar';
import Avatar from '@mui/material/Avatar';
import Badge from '@mui/material/Badge';
import Box from '@mui/material/Box';
-import Button from '@mui/material/Button';
-import Divider from '@mui/material/Divider';
import IconButton from '@mui/material/IconButton';
-import ListItem from '@mui/material/ListItem';
-import ListItemIcon from '@mui/material/ListItemIcon';
-import ListItemText from '@mui/material/ListItemText';
-import Menu from '@mui/material/Menu';
-import MenuItem from '@mui/material/MenuItem';
+import Link from '@mui/material/Link';
import Toolbar from '@mui/material/Toolbar';
import { useContext, useEffect, useState } from 'react';
-import { Navigate, Link as RouterLink } from 'react-router-dom';
-import { getCookie } from '../cookies/getCookie';
+import { Link as RouterLink } from 'react-router-dom';
import { Svalyn } from '../icons/Svalyn';
import { PaletteContext } from '../palette/PaletteContext';
import { PaletteContextValue } from '../palette/PaletteContext.types';
import { ErrorSnackbar } from '../snackbar/ErrorSnackbar';
import { GetViewerData, GetViewerVariables, NavbarProps, NavbarState } from './Navbar.types';
-const { VITE_BACKEND_URL } = import.meta.env;
+import { SearchButton } from './SearchButton';
+import { UserMenu } from './UserMenu';
const getViewerQuery = gql`
query getViewer {
@@ -64,7 +52,6 @@ export const Navbar = ({ children }: NavbarProps) => {
const [state, setState] = useState({
viewer: null,
anchorElement: null,
- redirectToLogin: false,
message: null,
});
@@ -93,27 +80,6 @@ export const Navbar = ({ children }: NavbarProps) => {
};
const handleCloseUserMenu = () => setState((prevState) => ({ ...prevState, anchorElement: null }));
- const handleLogout: React.MouseEventHandler = () => {
- const csrfToken = getCookie('XSRF-TOKEN');
-
- fetch(`${VITE_BACKEND_URL}/api/logout`, {
- method: 'POST',
- credentials: 'include',
- mode: 'cors',
- headers: {
- 'X-XSRF-TOKEN': csrfToken,
- },
- }).then(() => {
- setState((prevState) => ({ ...prevState, redirectToLogin: true }));
- });
- };
-
- if (state.redirectToLogin) {
- return ;
- }
-
- var isApple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
-
return (
<>
@@ -133,32 +99,23 @@ export const Navbar = ({ children }: NavbarProps) => {
marginLeft: 'auto',
}}
>
-
+
+ Domains
+
+
@@ -167,59 +124,16 @@ export const Navbar = ({ children }: NavbarProps) => {
-
+ />
>
) : null}
diff --git a/frontend/svalyn-studio-app/src/navbars/Navbar.types.ts b/frontend/svalyn-studio-app/src/navbars/Navbar.types.ts
index 8ad5299..50c360f 100644
--- a/frontend/svalyn-studio-app/src/navbars/Navbar.types.ts
+++ b/frontend/svalyn-studio-app/src/navbars/Navbar.types.ts
@@ -24,7 +24,6 @@ export interface NavbarProps {
export interface NavbarState {
viewer: Viewer | null;
anchorElement: HTMLElement | null;
- redirectToLogin: boolean;
message: string | null;
}
diff --git a/frontend/svalyn-studio-app/src/navbars/SearchButton.tsx b/frontend/svalyn-studio-app/src/navbars/SearchButton.tsx
new file mode 100644
index 0000000..6a92da7
--- /dev/null
+++ b/frontend/svalyn-studio-app/src/navbars/SearchButton.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2023 Stéphane Bégaudeau.
+ *
+ * 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 SearchIcon from '@mui/icons-material/Search';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import { SearchButtonProps } from './SearchButton.types';
+
+export const SearchButton = ({ onClick }: SearchButtonProps) => {
+ var isApple = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
+ return (
+
+ );
+};
diff --git a/frontend/svalyn-studio-app/src/navbars/SearchButton.types.ts b/frontend/svalyn-studio-app/src/navbars/SearchButton.types.ts
new file mode 100644
index 0000000..2dfb02f
--- /dev/null
+++ b/frontend/svalyn-studio-app/src/navbars/SearchButton.types.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2023 Stéphane Bégaudeau.
+ *
+ * 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.
+ */
+
+export interface SearchButtonProps {
+ onClick?: React.MouseEventHandler | undefined;
+}
diff --git a/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx b/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx
new file mode 100644
index 0000000..b8e6c47
--- /dev/null
+++ b/frontend/svalyn-studio-app/src/navbars/UserMenu.tsx
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2023 Stéphane Bégaudeau.
+ *
+ * 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 HelpIcon from '@mui/icons-material/Help';
+import HomeIcon from '@mui/icons-material/Home';
+import LogoutIcon from '@mui/icons-material/Logout';
+import MailOutlineIcon from '@mui/icons-material/MailOutline';
+import PersonIcon from '@mui/icons-material/Person';
+import SettingsIcon from '@mui/icons-material/Settings';
+import Divider from '@mui/material/Divider';
+import ListItem from '@mui/material/ListItem';
+import ListItemIcon from '@mui/material/ListItemIcon';
+import ListItemText from '@mui/material/ListItemText';
+import Menu from '@mui/material/Menu';
+import MenuItem from '@mui/material/MenuItem';
+import { useState } from 'react';
+import { Navigate, Link as RouterLink } from 'react-router-dom';
+import { getCookie } from '../cookies/getCookie';
+import { UserMenuProps, UserMenuState } from './UserMenu.types';
+const { VITE_BACKEND_URL } = import.meta.env;
+
+export const UserMenu = ({ name, username, onClose, ...props }: UserMenuProps) => {
+ const [state, setState] = useState({
+ redirectToLogin: false,
+ });
+
+ const handleCloseUserMenu: React.MouseEventHandler = (event) => {
+ if (onClose) {
+ onClose(event, 'backdropClick');
+ }
+ };
+
+ const handleLogout: React.MouseEventHandler = () => {
+ const csrfToken = getCookie('XSRF-TOKEN');
+
+ fetch(`${VITE_BACKEND_URL}/api/logout`, {
+ method: 'POST',
+ credentials: 'include',
+ mode: 'cors',
+ headers: {
+ 'X-XSRF-TOKEN': csrfToken,
+ },
+ }).then(() => {
+ setState((prevState) => ({ ...prevState, redirectToLogin: true }));
+ });
+ };
+
+ if (state.redirectToLogin) {
+ return ;
+ }
+
+ return (
+
+ );
+};
diff --git a/frontend/svalyn-studio-app/src/navbars/UserMenu.types.ts b/frontend/svalyn-studio-app/src/navbars/UserMenu.types.ts
new file mode 100644
index 0000000..f45ade6
--- /dev/null
+++ b/frontend/svalyn-studio-app/src/navbars/UserMenu.types.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2023 Stéphane Bégaudeau.
+ *
+ * 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 { MenuProps } from '@mui/material/Menu';
+
+export interface UserMenuProps extends MenuProps {
+ name: string;
+ username: string;
+}
+
+export interface UserMenuState {
+ redirectToLogin: boolean;
+}
diff --git a/frontend/svalyn-studio-app/src/projects/ProjectBreadcrumbs.tsx b/frontend/svalyn-studio-app/src/projects/ProjectBreadcrumbs.tsx
new file mode 100644
index 0000000..c819f2c
--- /dev/null
+++ b/frontend/svalyn-studio-app/src/projects/ProjectBreadcrumbs.tsx
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2023 Stéphane Bégaudeau.
+ *
+ * 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 ClassIcon from '@mui/icons-material/Class';
+import CorporateFareIcon from '@mui/icons-material/CorporateFare';
+import DifferenceIcon from '@mui/icons-material/Difference';
+import SettingsIcon from '@mui/icons-material/Settings';
+import TagIcon from '@mui/icons-material/Tag';
+import TimelineIcon from '@mui/icons-material/Timeline';
+import Breadcrumbs from '@mui/material/Breadcrumbs';
+import Link from '@mui/material/Link';
+import Typography from '@mui/material/Typography';
+import { Link as RouterLink } from 'react-router-dom';
+import { useRouteMatch } from '../hooks/useRouteMatch';
+import { useProject } from './useProject';
+
+const patterns = [
+ '/projects/:projectIdentifier',
+ '/projects/:projectIdentifier/activity',
+ '/projects/:projectIdentifier/changeproposals',
+ '/projects/:projectIdentifier/new/changeproposal',
+ '/projects/:projectIdentifier/tags',
+ '/projects/:projectIdentifier/settings',
+ '/projects/:projectIdentifier/changes/:changeId/resources/*',
+];
+
+export const ProjectBreadcrumbs = () => {
+ const routeMatch = useRouteMatch(patterns);
+ const currentTab = routeMatch?.pattern?.path;
+ return (
+
+
+
+ {currentTab !== '/projects/:projectIdentifier' &&
+ currentTab !== '/projects/:projectIdentifier/changes/:changeId/resources/*' ? (
+
+ ) : null}
+
+ );
+};
+
+const OrganizationBreadcrumbEntry = () => {
+ const project = useProject();
+
+ return (
+ theme.spacing(0.5) }}
+ >
+
+ {project.organization.name}
+
+ );
+};
+
+const ProjectBreadcrumbEntry = () => {
+ const project = useProject();
+ const routeMatch = useRouteMatch(patterns);
+ const currentTab = routeMatch?.pattern?.path;
+
+ if (currentTab === '/projects/:projectIdentifier') {
+ return (
+ theme.spacing(0.5) }}
+ >
+
+ {project.name}
+
+ );
+ }
+
+ return (
+ theme.spacing(0.5) }}
+ >
+
+ {project.name}
+
+ );
+};
+
+const AdditionalBreadcrumbEntry = () => {
+ const routeMatch = useRouteMatch(patterns);
+ const currentTab = routeMatch?.pattern?.path;
+
+ let tabBreadcrumbEntry: { label: string; icon: JSX.Element } | null = null;
+ if (currentTab === '/projects/:projectIdentifier/activity') {
+ tabBreadcrumbEntry = {
+ label: 'Activity',
+ icon: ,
+ };
+ } else if (currentTab === '/projects/:projectIdentifier/changeproposals') {
+ tabBreadcrumbEntry = {
+ label: 'Change Proposals',
+ icon: ,
+ };
+ } else if (currentTab === '/projects/:projectIdentifier/new/changeproposal') {
+ tabBreadcrumbEntry = {
+ label: 'New Change Proposal',
+ icon: ,
+ };
+ } else if (currentTab === '/projects/:projectIdentifier/tags') {
+ tabBreadcrumbEntry = {
+ label: 'Tags',
+ icon: ,
+ };
+ } else if (currentTab === '/projects/:projectIdentifier/settings') {
+ tabBreadcrumbEntry = {
+ label: 'Settings',
+ icon: ,
+ };
+ }
+
+ if (tabBreadcrumbEntry) {
+ const { label, icon } = tabBreadcrumbEntry;
+ return (
+ theme.spacing(0.5) }}
+ >
+ {icon}
+ {label}
+
+ );
+ }
+
+ return null;
+};
diff --git a/frontend/svalyn-studio-app/src/projects/ProjectShell.tsx b/frontend/svalyn-studio-app/src/projects/ProjectShell.tsx
index 79eedc8..048bc12 100644
--- a/frontend/svalyn-studio-app/src/projects/ProjectShell.tsx
+++ b/frontend/svalyn-studio-app/src/projects/ProjectShell.tsx
@@ -20,15 +20,15 @@
import { gql, useQuery } from '@apollo/client';
import CorporateFareIcon from '@mui/icons-material/CorporateFare';
import Box from '@mui/material/Box';
-import Link from '@mui/material/Link';
import { useEffect, useState } from 'react';
-import { Link as RouterLink, useParams } from 'react-router-dom';
+import { useParams } from 'react-router-dom';
import { Navbar } from '../navbars/Navbar';
import { NotFoundView } from '../notfound/NotFoundView';
import { goToDomains, goToHelp, goToHome, goToNotifications, goToSettings } from '../palette/DefaultPaletteActions';
import { PaletteNavigationAction } from '../palette/Palette.types';
import { usePalette } from '../palette/usePalette';
import { ErrorSnackbar } from '../snackbar/ErrorSnackbar';
+import { ProjectBreadcrumbs } from './ProjectBreadcrumbs';
import { ProjectDrawer } from './ProjectDrawer';
import { GetProjectData, GetProjectVariables, ProjectShellProps, ProjectShellState } from './ProjectShell.types';
import { ProjectContext } from './useProject';
@@ -93,12 +93,10 @@ export const ProjectShell = ({ children }: ProjectShellProps) => {
}
return (
- <>
+
-
- Domains
-
+ {
flexGrow: '1',
}}
>
-
-
- {children}
-
+
+ {children}
- >
+
);
};