Skip to content

Commit

Permalink
App: introduce HamburgerMenu + user image (#311)
Browse files Browse the repository at this point in the history
  • Loading branch information
zbycz authored Apr 17, 2024
1 parent 589fb65 commit 606ef08
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 64 deletions.
23 changes: 4 additions & 19 deletions src/components/LayerSwitcher/LayerSwitcherButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,6 @@ import React from 'react';
import styled from 'styled-components';
import LayersIcon from './LayersIcon';
import { t } from '../../services/intl';
import { isDesktop } from '../helpers';

const TopRight = styled.div`
position: absolute;
z-index: 1000;
padding: 10px;
right: 0;
top: 72px;
@media ${isDesktop} {
top: 0;
}
`;

const StyledLayerSwitcher = styled.button`
margin: 0;
Expand All @@ -41,10 +28,8 @@ const StyledLayerSwitcher = styled.button`
`;

export const LayerSwitcherButton = ({ onClick }: { onClick?: any }) => (
<TopRight>
<StyledLayerSwitcher onClick={onClick}>
<LayersIcon />
{t('layerswitcher.button')}
</StyledLayerSwitcher>
</TopRight>
<StyledLayerSwitcher onClick={onClick}>
<LayersIcon />
{t('layerswitcher.button')}
</StyledLayerSwitcher>
);
31 changes: 25 additions & 6 deletions src/components/Map/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import dynamic from 'next/dynamic';
import BugReport from '@material-ui/icons/BugReport';
import Button from '@material-ui/core/Button';
import CircularProgress from '@material-ui/core/CircularProgress';
import { useBoolState } from '../helpers';
import { isDesktop, useBoolState } from '../helpers';
import { MapFooter } from './MapFooter/MapFooter';
import { SHOW_PROTOTYPE_UI } from '../../config';
import { LayerSwitcherButton } from '../LayerSwitcher/LayerSwitcherButton';
import { MaptilerLogo } from './MapFooter/MaptilerLogo';
import { TopMenu } from './TopMenu/TopMenu';

const BrowserMap = dynamic(() => import('./BrowserMap'), {
ssr: false,
loading: () => <div />,
});

const LayerSwitcher = dynamic(() => import('../LayerSwitcher/LayerSwitcher'), {
ssr: false,
loading: () => <LayerSwitcherButton />,
});
const LayerSwitcherDynamic = dynamic(
() => import('../LayerSwitcher/LayerSwitcher'),
{
ssr: false,
loading: () => <LayerSwitcherButton />,
},
);

const Spinner = styled(CircularProgress)`
position: absolute;
Expand All @@ -28,6 +32,18 @@ const Spinner = styled(CircularProgress)`
margin: -20px 0 0 -20px;
`;

const TopRight = styled.div`
position: absolute;
z-index: 1000;
padding: 10px;
right: 0;
top: 72px;
@media ${isDesktop} {
top: 0;
}
`;

const BottomRight = styled.div`
position: absolute;
right: 0;
Expand Down Expand Up @@ -60,7 +76,10 @@ const Map = () => {
<BrowserMap onMapLoaded={setLoaded} />
{!mapLoaded && <Spinner color="secondary" />}
<NoscriptMessage />
<LayerSwitcher />
<TopRight>
<TopMenu />
<LayerSwitcherDynamic />
</TopRight>
<BottomRight>
{SHOW_PROTOTYPE_UI && <BugReportButton />}
<MapFooter />
Expand Down
3 changes: 0 additions & 3 deletions src/components/Map/MapFooter/MapFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { Tooltip, useMediaQuery } from '@material-ui/core';
import uniq from 'lodash/uniq';
import { t, Translation } from '../../../services/intl';
import GithubIcon from '../../../assets/GithubIcon';
import { MoreMenu } from './MoreMenu';
import { LangSwitcher } from './LangSwitcher';
import { useMapStateContext } from '../../utils/MapStateContext';
import { osmappLayers } from '../../LayerSwitcher/osmappLayers';
Expand Down Expand Up @@ -128,8 +127,6 @@ export const MapFooter = () => (
<LangSwitcher />
{' | '}
<MapDataLink />
{' | '}
<MoreMenu />
</Wrapper>
</ClientOnly>
);
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import BrightnessAutoIcon from '@material-ui/icons/BrightnessAuto';
import Brightness4Icon from '@material-ui/icons/Brightness4';
import BrightnessHighIcon from '@material-ui/icons/BrightnessHigh';
import React, { useEffect, useState } from 'react';
import React, { forwardRef, useEffect, useState } from 'react';
import { Menu, MenuItem } from '@material-ui/core';
import CreateIcon from '@material-ui/icons/Create';
import HelpIcon from '@material-ui/icons/Help';
import styled from 'styled-components';
import GetAppIcon from '@material-ui/icons/GetApp';
import Router from 'next/router';
import IconButton from '@material-ui/core/IconButton';
import MenuIcon from '@material-ui/icons/Menu';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import { useBoolState } from '../../helpers';
import { t } from '../../../services/intl';
import { useFeatureContext } from '../../utils/FeatureContext';
import { useMapStateContext } from '../../utils/MapStateContext';
import { getIdEditorLink } from '../../../utils';
import { useUserThemeContext } from '../../../helpers/theme';

const StyledChevronRightIcon = styled(ChevronRightIcon)`
color: ${({ theme }) => theme.palette.tertiary.main};
margin: -2px 0px -2px -1px !important;
font-size: 15px !important;
`;
import { useOsmAuthContext } from '../../utils/OsmAuthContext';
import { LoginIcon } from './LoginIcon';

const PencilIcon = styled(CreateIcon)`
color: ${({ theme }) => theme.palette.action.active};
Expand Down Expand Up @@ -58,6 +56,16 @@ const StyledBrightnessHighIcon = styled(BrightnessHighIcon)`
font-size: 17px !important;
`;

const StyledAccountCircleIcon = styled(AccountCircleIcon)`
color: ${({ theme }) => theme.palette.action.active};
margin: -2px 6px 0 0;
font-size: 17px !important;
`;

const StyledDivider = styled.hr`
border-top: 1px solid ${({ theme }) => theme.palette.divider};
`;

const useIsBrowser = () => {
// fixes hydration error - server and browser have different view (cookies and window.hash)
// throwed "Warning: Prop `href` did not match."
Expand Down Expand Up @@ -146,6 +154,36 @@ const ThemeSelection = () => {
);
};

const UserLogin = forwardRef<HTMLLIElement, any>(({ closeMenu }, ref) => {
const { osmUser, handleLogin, handleLogout } = useOsmAuthContext();

if (!osmUser) {
return (
<MenuItem ref={ref} onClick={handleLogin}>
<StyledAccountCircleIcon />
{t('user.login')}
</MenuItem>
);
}

return (
<>
<MenuItem
component="a"
href={`https://www.openstreetmap.org/user/${osmUser}`}
target="_blank"
rel="noopener"
onClick={closeMenu}
>
<StyledAccountCircleIcon ref={ref} />
<strong>{osmUser}</strong>
</MenuItem>

<MenuItem onClick={handleLogout}>{t('user.logout')}</MenuItem>
</>
);
});

// TODO maybe
// <ListItemIcon>
// <InboxIcon fontSize="small" />
Expand All @@ -157,35 +195,41 @@ const ThemeSelection = () => {
// https://github.com/mui-org/material-ui/issues/22912
// https://github.com/mui-org/material-ui/issues?q=is%3Aissue+is%3Aopen+menuitem+keyboard

export const MoreMenu = () => {
export const HamburgerMenu = () => {
const anchorRef = React.useRef();
const [opened, open, close] = useBoolState(false);

return (
<>
<Menu
id="more-menu"
id="hamburger-menu"
anchorEl={anchorRef.current}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
getContentAnchorEl={null}
open={opened}
onClose={close}
>
<UserLogin closeMenu={close} />
<StyledDivider />
<ThemeSelection />
<EditLink closeMenu={close} />
<AboutLink closeMenu={close} />
<InstallLink closeMenu={close} />
<ThemeSelection />
<AboutLink closeMenu={close} />
</Menu>
<button
type="button"
className="linkLikeButton"
aria-controls="more-menu"
aria-haspopup="true"
onClick={open}
<LoginIcon onClick={open} />
<IconButton
ref={anchorRef}
aria-controls="hamburger-menu"
aria-haspopup="true"
title={t('map.more_button_title')}
color="secondary"
onClick={open}
>
{t('map.more_button')}
<StyledChevronRightIcon />
</button>
<MenuIcon />
</IconButton>
</>
);
};
24 changes: 24 additions & 0 deletions src/components/Map/TopMenu/LoginIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import IconButton from '@material-ui/core/IconButton';
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
import React from 'react';
import styled from 'styled-components';
import { useOsmAuthContext } from '../../utils/OsmAuthContext';

const StyledUserImg = styled.img`
width: 24px;
height: 24px;
border-radius: 50%;
`;
export const LoginIcon = ({ onClick }) => {
const { osmUser, userImage } = useOsmAuthContext();

return (
<IconButton color="secondary" onClick={onClick}>
{osmUser ? (
<StyledUserImg src={userImage} alt={osmUser} onClick={onClick} />
) : (
<AccountCircleIcon />
)}
</IconButton>
);
};
29 changes: 29 additions & 0 deletions src/components/Map/TopMenu/TopMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import styled from 'styled-components';
import { isDesktop } from '../../helpers';
import { HamburgerMenu } from './HamburgerMenu';

const Wrapper = styled.span`
vertical-align: top;
display: inline-block;
margin-top: -10px;
@media ${isDesktop} {
margin-top: -5px;
}
button:last-child {
margin-left: -10px;
}
svg {
filter: drop-shadow(0 0 2px #ffffff);
}
`;

export const TopMenu = () => (
<Wrapper>
<HamburgerMenu />
</Wrapper>
);
26 changes: 20 additions & 6 deletions src/components/utils/OsmAuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
import React, { createContext, useContext, useState } from 'react';
import {
fetchOsmUsername,
getOsmUsername,
fetchOsmUser,
getOsmUser,
osmLogout,
OsmUser,
} from '../../services/osmApiAuth';
import { useMapStateContext } from './MapStateContext';

interface OsmAuthType {
loggedIn: boolean;
osmUser: string;
userImage: string;
handleLogin: () => void;
handleLogout: () => void;
}

export const OsmAuthContext = createContext<OsmAuthType>(undefined);

export const OsmAuthProvider = ({ children }) => {
const [osmUser, setOsmUser] = useState<string>(getOsmUsername() ?? '');
const handleLogin = () => fetchOsmUsername().then(setOsmUser);
const handleLogout = () => osmLogout().then(() => setOsmUser(''));
const mapStateContext = useMapStateContext();
const [osmUser, setOsmUser] = useState<OsmUser | undefined>(getOsmUser());

const successfulLogin = (user: OsmUser) => {
setOsmUser(user);
mapStateContext.showToast({
content: `Logged in as ${user.name}`,
type: 'success',
});
};

const handleLogin = () => fetchOsmUser().then(successfulLogin);
const handleLogout = () => osmLogout().then(() => setOsmUser(undefined));

const value = {
loggedIn: !!osmUser,
osmUser,
osmUser: osmUser?.name || '', // TODO rename
userImage: osmUser?.imageUrl || '',
handleLogin,
handleLogout,
};
Expand Down
5 changes: 4 additions & 1 deletion src/locales/cs.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export default {
show_more: 'Zobrazit více',
show_less: 'Zobrazit méně',

'user.login': 'Přihlásit se',
'user.logout': 'Odhlásit se',

'project.osmapp.description': 'Univerzální appka pro OpenStreetMap',
'project.osmapp.serpDescription': 'Otevřená mapa světa nad OpenStreetMap databází. Hledání, klikatelné POIs, editace a více!',

Expand Down Expand Up @@ -95,7 +98,7 @@ export default {
'(c) MapTiler.com ❤️ <br> – vektorové dlaždice, hosting, turistická mapa<br>Velký dík za podporu tohoto projektu! 🙂 ',
'map.more_button': 'více',
'map.more_button_title': 'Další možnosti…',
'map.edit_link': 'Otevřít oblast v editoru iD',
'map.edit_link': 'Otevřít mapu v editoru iD',
'map.about_link': 'O aplikaci',

'editdialog.add_heading': 'Přidat do OpenStreetMap',
Expand Down
3 changes: 3 additions & 0 deletions src/locales/vocabulary.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export default {
show_more: 'Show more',
show_less: 'Show less',

'user.login': 'Login',
'user.logout': 'Logout',

'project.osmapp.description': 'A universal app for OpenStreetMap',
'project.osmapp.serpDescription':
'An open-source map of the world based on the OpenStreetMap database. Features a search, clickable points of interest, in-app map edits, and more!',
Expand Down
Loading

0 comments on commit 606ef08

Please sign in to comment.