Added mobile-friendly menu (#5)

This commit is contained in:
Przemek Wiech
2020-03-31 23:42:31 +02:00
parent 46d0607707
commit d21858547b
2 changed files with 376 additions and 172 deletions

View File

@@ -23,6 +23,7 @@ import {
Search, Search,
SearchProps, SearchProps,
SearchResultProps, SearchResultProps,
Responsive,
} from 'semantic-ui-react'; } from 'semantic-ui-react';
enum WikiTreeLoginState { enum WikiTreeLoginState {
@@ -31,6 +32,11 @@ enum WikiTreeLoginState {
LOGGED_IN, LOGGED_IN,
} }
enum ScreenSize {
LARGE,
SMALL,
}
/** Menus and dialogs state. */ /** Menus and dialogs state. */
interface State { interface State {
loadUrlDialogOpen: boolean; loadUrlDialogOpen: boolean;
@@ -319,81 +325,8 @@ export class TopBar extends React.Component<
); );
} }
private chartMenus() { private search() {
if (!this.props.showingChart) {
return null;
}
return ( return (
<>
<Menu.Item as="a" onClick={() => this.props.eventHandlers.onPrint()}>
<Icon name="print" />
<FormattedMessage id="menu.print" defaultMessage="Print" />
</Menu.Item>
<Dropdown
trigger={
<div>
<Icon name="download" />
<FormattedMessage id="menu.download" defaultMessage="Download" />
</div>
}
className="item"
>
<Dropdown.Menu>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadPdf()}
>
<FormattedMessage id="menu.pdf_file" defaultMessage="PDF file" />
</Dropdown.Item>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadPng()}
>
<FormattedMessage id="menu.png_file" defaultMessage="PNG file" />
</Dropdown.Item>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadSvg()}
>
<FormattedMessage id="menu.svg_file" defaultMessage="SVG file" />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Dropdown
trigger={
<div>
<Icon name="eye" />
<FormattedMessage id="menu.view" defaultMessage="View" />
</div>
}
className="item"
>
<Dropdown.Menu>
<Dropdown.Item onClick={() => this.changeView('hourglass')}>
<Icon name="hourglass" />
<FormattedMessage
id="menu.hourglass"
defaultMessage="Hourglass chart"
/>
</Dropdown.Item>
{this.props.allowAllRelativesChart ? (
<Dropdown.Item onClick={() => this.changeView('relatives')}>
<Icon name="users" />
<FormattedMessage
id="menu.relatives"
defaultMessage="All relatives"
/>
</Dropdown.Item>
) : null}
<Dropdown.Item onClick={() => this.changeView('fancy')}>
<Icon name="users" />
<FormattedMessage
id="menu.fancy"
defaultMessage="Fancy tree (experimental)"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Search <Search
onSearchChange={debounce( onSearchChange={debounce(
(_: React.MouseEvent<HTMLElement>, data: SearchProps) => (_: React.MouseEvent<HTMLElement>, data: SearchProps) =>
@@ -417,28 +350,183 @@ export class TopBar extends React.Component<
}) })
} }
/> />
</>
); );
} }
private fileMenus() { private chartMenus(screenSize: ScreenSize) {
if (!this.props.standalone) { if (!this.props.showingChart) {
return null; return null;
} }
const chartTypeItems = (
<>
<Dropdown.Item onClick={() => this.changeView('hourglass')}>
<Icon name="hourglass" />
<FormattedMessage
id="menu.hourglass"
defaultMessage="Hourglass chart"
/>
</Dropdown.Item>
{this.props.allowAllRelativesChart ? (
<Dropdown.Item onClick={() => this.changeView('relatives')}>
<Icon name="users" />
<FormattedMessage
id="menu.relatives"
defaultMessage="All relatives"
/>
</Dropdown.Item>
) : null}
<Dropdown.Item onClick={() => this.changeView('fancy')}>
<Icon name="users" />
<FormattedMessage
id="menu.fancy"
defaultMessage="Fancy tree (experimental)"
/>
</Dropdown.Item>
</>
);
switch (screenSize) {
case ScreenSize.LARGE:
return ( return (
<> <>
<Link to="/"> <Menu.Item onClick={() => this.props.eventHandlers.onPrint()}>
<Icon name="print" />
<FormattedMessage id="menu.print" defaultMessage="Print" />
</Menu.Item>
<Dropdown
trigger={
<div>
<Icon name="download" />
<FormattedMessage
id="menu.download"
defaultMessage="Download"
/>
</div>
}
className="item"
>
<Dropdown.Menu>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadPdf()}
>
<FormattedMessage
id="menu.pdf_file"
defaultMessage="PDF file"
/>
</Dropdown.Item>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadPng()}
>
<FormattedMessage
id="menu.png_file"
defaultMessage="PNG file"
/>
</Dropdown.Item>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadSvg()}
>
<FormattedMessage
id="menu.svg_file"
defaultMessage="SVG file"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
<Dropdown
trigger={
<div>
<Icon name="eye" />
<FormattedMessage id="menu.view" defaultMessage="View" />
</div>
}
className="item"
>
<Dropdown.Menu>{chartTypeItems}</Dropdown.Menu>
</Dropdown>
{this.search()}
</>
);
case ScreenSize.SMALL:
return (
<>
<Dropdown.Item onClick={() => this.props.eventHandlers.onPrint()}>
<Icon name="print" />
<FormattedMessage id="menu.print" defaultMessage="Print" />
</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadPdf()}
>
<Icon name="download" />
<FormattedMessage
id="menu.download_pdf"
defaultMessage="Downlod PDF"
/>
</Dropdown.Item>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadPng()}
>
<Icon name="download" />
<FormattedMessage
id="menu.download_png"
defaultMessage="Download PNG"
/>
</Dropdown.Item>
<Dropdown.Item
onClick={() => this.props.eventHandlers.onDownloadSvg()}
>
<Icon name="download" />
<FormattedMessage
id="menu.download_svg"
defaultMessage="Download SVG"
/>
</Dropdown.Item>
<Dropdown.Divider />
{chartTypeItems}
<Dropdown.Divider />
</>
);
}
}
private title() {
return (
<Menu.Item> <Menu.Item>
<b>Topola Genealogy</b> <b>Topola Genealogy</b>
</Menu.Item> </Menu.Item>
</Link> );
<Menu.Item as="a" onClick={() => this.openLoadUrlDialog()}> }
private fileMenus(screenSize: ScreenSize) {
if (!this.props.standalone) {
return null;
}
const loadUrlItem = (
<>
<Icon name="cloud download" /> <Icon name="cloud download" />
<FormattedMessage <FormattedMessage
id="menu.load_from_url" id="menu.load_from_url"
defaultMessage="Load from URL" defaultMessage="Load from URL"
/> />
</Menu.Item> </>
);
const loadFileItem = (
<>
<Icon name="folder open" />
<FormattedMessage
id="menu.load_from_file"
defaultMessage="Load from file"
/>
</>
);
const commonElements = (
<>
{this.loadFromUrlModal()}
<input <input
className="hidden" className="hidden"
type="file" type="file"
@@ -447,38 +535,48 @@ export class TopBar extends React.Component<
multiple multiple
onChange={(e) => this.handleUpload(e)} onChange={(e) => this.handleUpload(e)}
/> />
<label htmlFor="fileInput"> </>
<Menu.Item as="a"> );
<Icon name="folder open" /> switch (screenSize) {
<FormattedMessage case ScreenSize.LARGE:
id="menu.load_from_file" return (
defaultMessage="Load from file" <>
/> <Menu.Item onClick={() => this.openLoadUrlDialog()}>
{loadUrlItem}
</Menu.Item> </Menu.Item>
<label htmlFor="fileInput">
<Menu.Item as="a">{loadFileItem}</Menu.Item>
</label> </label>
{commonElements}
</>
);
case ScreenSize.SMALL:
return (
<>
<Dropdown.Item onClick={() => this.openLoadUrlDialog()}>
{loadUrlItem}
</Dropdown.Item>
<Dropdown.Item as="label" htmlFor="fileInput">
{loadFileItem}
</Dropdown.Item>
<Dropdown.Divider />
{commonElements}
</> </>
); );
} }
}
private wikiTreeLoginMenu() { private wikiTreeLoginMenu(screenSize: ScreenSize) {
if (!this.props.showWikiTreeLogin) { if (!this.props.showWikiTreeLogin) {
return null; return null;
} }
const wikiTreeLogoUrl = const wikiTreeLogoUrl =
'https://www.wikitree.com/photo.php/a/a5/WikiTree_Images.png'; 'https://www.wikitree.com/photo.php/a/a5/WikiTree_Images.png';
switch (this.state.wikiTreeLoginState) { switch (this.state.wikiTreeLoginState) {
case WikiTreeLoginState.NOT_LOGGED_IN: case WikiTreeLoginState.NOT_LOGGED_IN:
return ( const loginForm = (
<Menu.Item as="a" onClick={() => this.wikiTreeLogin()}>
<img
src={wikiTreeLogoUrl}
alt="WikiTree logo"
style={{width: '24px', height: '24px'}}
/>
<FormattedMessage
id="menu.wikitree_login"
defaultMessage="Log in to WikiTree"
/>
<form <form
action="https://apps.wikitree.com/api.php" action="https://apps.wikitree.com/api.php"
method="POST" method="POST"
@@ -492,8 +590,45 @@ export class TopBar extends React.Component<
ref={this.wikiTreeReturnUrlRef} ref={this.wikiTreeReturnUrlRef}
/> />
</form> </form>
);
switch (screenSize) {
case ScreenSize.LARGE:
return (
<Menu.Item onClick={() => this.wikiTreeLogin()}>
<img
src={wikiTreeLogoUrl}
alt="WikiTree logo"
style={{width: '24px', height: '24px'}}
/>
<FormattedMessage
id="menu.wikitree_login"
defaultMessage="Log in to WikiTree"
/>
{loginForm}
</Menu.Item> </Menu.Item>
); );
case ScreenSize.SMALL:
return (
<>
<Dropdown.Item onClick={() => this.wikiTreeLogin()}>
<img
src={wikiTreeLogoUrl}
alt="WikiTree logo"
style={{width: '24px', height: '24px'}}
/>
<FormattedMessage
id="menu.wikitree_login"
defaultMessage="Log in to WikiTree"
/>
{loginForm}
</Dropdown.Item>
<Dropdown.Divider />
</>
);
}
break;
case WikiTreeLoginState.LOGGED_IN: case WikiTreeLoginState.LOGGED_IN:
const tooltip = this.state.wikiTreeLoginUsername const tooltip = this.state.wikiTreeLoginUsername
? this.context.intl.formatMessage( ? this.context.intl.formatMessage(
@@ -507,6 +642,8 @@ export class TopBar extends React.Component<
id: 'menu.wikitree_popup', id: 'menu.wikitree_popup',
defaultMessage: 'Logged in to WikiTree', defaultMessage: 'Logged in to WikiTree',
}); });
switch (screenSize) {
case ScreenSize.LARGE:
return ( return (
<Menu.Item title={tooltip}> <Menu.Item title={tooltip}>
<img <img
@@ -520,49 +657,114 @@ export class TopBar extends React.Component<
/> />
</Menu.Item> </Menu.Item>
); );
case ScreenSize.SMALL:
return (
<>
<Menu.Item title={tooltip}>
<img
src={wikiTreeLogoUrl}
alt="WikiTree logo"
style={{width: '24px', height: '24px'}}
/>
<FormattedMessage
id="menu.wikitree_logged_in"
defaultMessage="Logged in"
/>
</Menu.Item>
<Dropdown.Divider />
</>
);
default: default:
return null; return null;
} }
} }
}
private sourceLink() { private mobileMenus() {
return ( return (
<Menu.Item <>
as="a" <Dropdown
trigger={
<div>
<Icon name="sidebar" />
</div>
}
className="item"
icon={null}
>
<Dropdown.Menu>
{this.fileMenus(ScreenSize.SMALL)}
{this.chartMenus(ScreenSize.SMALL)}
{this.wikiTreeLoginMenu(ScreenSize.SMALL)}
<Dropdown.Item
href="https://github.com/PeWu/topola-viewer" href="https://github.com/PeWu/topola-viewer"
target="_blank" target="_blank"
> >
<FormattedMessage id="menu.github" defaultMessage="Source on GitHub" /> <FormattedMessage
</Menu.Item> id="menu.github"
defaultMessage="Source on GitHub"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
{this.props.standalone ? (
<Link to="/">{this.title()}</Link>
) : (
this.title()
)}
</>
); );
} }
private poweredByLink() { private desktopMenus() {
return ( return (
<>
{this.props.standalone ? <Link to="/">{this.title()}</Link> : null}
{this.fileMenus(ScreenSize.LARGE)}
{this.chartMenus(ScreenSize.LARGE)}
<Menu.Menu position="right">
{this.wikiTreeLoginMenu(ScreenSize.LARGE)}
<Menu.Item <Menu.Item
as="a" href="https://github.com/PeWu/topola-viewer"
href="https://pewu.github.com/topola-viewer"
target="_blank" target="_blank"
> >
<FormattedMessage <FormattedMessage
id="menu.powered_by" id="menu.github"
defaultMessage="Powered by Topola" defaultMessage="GitHub project"
/> />
</Menu.Item> </Menu.Item>
</Menu.Menu>
</>
); );
} }
render() { render() {
return ( return (
<Menu attached="top" inverted color="blue" size="large"> <>
{this.fileMenus()} <Responsive
{this.chartMenus()} as={Menu}
<Menu.Menu position="right"> attached="top"
{this.wikiTreeLoginMenu()} inverted
{this.props.standalone ? this.sourceLink() : this.poweredByLink()} color="blue"
</Menu.Menu> size="large"
{this.loadFromUrlModal()} minWidth={768}
</Menu> >
{this.desktopMenus()}
</Responsive>
<Responsive
as={Menu}
attached="top"
inverted
color="blue"
size="large"
maxWidth={767}
>
{this.mobileMenus()}
</Responsive>
</>
); );
} }
} }

View File

@@ -6,6 +6,9 @@
"menu.pdf_file": "Plik PDF", "menu.pdf_file": "Plik PDF",
"menu.png_file": "Plik PNG", "menu.png_file": "Plik PNG",
"menu.svg_file": "Plik SVG", "menu.svg_file": "Plik SVG",
"menu.download_pdf": "Pobierz PDF",
"menu.download_png": "Pobierz PNG",
"menu.download_svg": "Pobierz SVG",
"menu.view": "Widok", "menu.view": "Widok",
"menu.hourglass": "Wykres klepsydrowy", "menu.hourglass": "Wykres klepsydrowy",
"menu.relatives": "Wszyscy krewni", "menu.relatives": "Wszyscy krewni",
@@ -14,8 +17,7 @@
"menu.wikitree_logged_in": "Zalogowano", "menu.wikitree_logged_in": "Zalogowano",
"menu.wikitree_popup_username": "Zalogowano do WikiTree jako {username}", "menu.wikitree_popup_username": "Zalogowano do WikiTree jako {username}",
"menu.wikitree_popup": "Zalogowano do WikiTree", "menu.wikitree_popup": "Zalogowano do WikiTree",
"menu.github": "Źródła na GitHub", "menu.github": "Projekt na stronie GitHub",
"menu.powered_by": "Topola Genealogy",
"menu.search.placeholder": "Szukaj osoby", "menu.search.placeholder": "Szukaj osoby",
"menu.search.no_results": "Brak wyników", "menu.search.no_results": "Brak wyników",
"intro.title": "Topola Genealogy", "intro.title": "Topola Genealogy",