Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App: introduce HamburgerMenu + user image #311

Merged
merged 8 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading