Skip to content

Commit

Permalink
feat(popup): adds optional navbar shortcut buttons. #102
Browse files Browse the repository at this point in the history
  • Loading branch information
dvcol committed Apr 13, 2022
1 parent b9621ef commit 6ccc39c
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 56 deletions.
125 changes: 88 additions & 37 deletions src/components/navbar/navbar-menu.tsx
Expand Up @@ -17,25 +17,27 @@ import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';

import { ConfirmationDialog } from '@src/components';
import { AppLinks, AppRoute, ErrorType, LoginError } from '@src/models';
import type { NavbarButton } from '@src/models';
import { AppLinks, AppRoute, ErrorType, LoginError, NavbarButtonType } from '@src/models';
import { NotificationService, QueryService } from '@src/services';
import { setNavbar } from '@src/store/actions';
import { getUrl } from '@src/store/selectors';
import { getGlobalNavbarButton, getUrl } from '@src/store/selectors';
import { createTab, useI18n } from '@src/utils';

import NavbarMenuIcon from './navbar-menu-icon';

type NavbarMenuProps = { label: React.ReactNode };
type NavbarMenuProps = { menuIcon: React.ReactNode };

export const NavbarMenu = ({ label }: NavbarMenuProps) => {
export const NavbarMenu = ({ menuIcon }: NavbarMenuProps) => {
const i18n = useI18n('navbar', 'menu');
const dispatch = useDispatch();
const url = useSelector(getUrl) + AppLinks.DownloadStation;
const navbarButtons = useSelector(getGlobalNavbarButton);

const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [anchorEl, setAnchorEl] = React.useState<null | Element>(null);
const open = Boolean(anchorEl);

const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget);
const handleClick = <T extends Element>(event: React.MouseEvent<T>) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleClearTab = () => dispatch(setNavbar());
const handleUrl = () => createTab({ url });
Expand All @@ -55,57 +57,106 @@ export const NavbarMenu = ({ label }: NavbarMenuProps) => {
// Dialog
const [prompt, setPrompt] = React.useState(false);

// Button list
const buttons: NavbarButton[] = [
{
type: NavbarButtonType.Add,
label: i18n('menu_add'),
icon: <AddLinkIcon />,
component: Link,
to: AppRoute.Add,
onClick: handleClearTab,
},
{
type: NavbarButtonType.Refresh,
label: i18n('menu_refresh'),
icon: <RefreshIcon />,
onClick: () => QueryService.listTasks().subscribe(handleError('refresh')),
},
{
type: NavbarButtonType.Resume,
label: i18n('menu_resume'),
icon: <PlayArrowIcon />,
onClick: () => QueryService.resumeAllTasks().subscribe(handleError('resume')),
},
{
type: NavbarButtonType.Pause,
label: i18n('menu_pause'),
icon: <PauseIcon />,
onClick: () => QueryService.pauseAllTasks().subscribe(handleError('pause')),
},
{ type: NavbarButtonType.Remove, label: i18n('menu_remove'), icon: <DeleteSweepIcon />, onClick: () => setPrompt(true) },
{
type: NavbarButtonType.Clear,
label: i18n('menu_clear'),
icon: <ClearAllIcon />,
onClick: () => QueryService.deleteFinishedTasks().subscribe(handleError('clear')),
},
{
type: NavbarButtonType.Configs,
label: i18n(`menu_configs`),
icon: <SettingsIcon />,
component: Link,
to: AppRoute.Config,
onClick: handleClearTab,
},
{
type: NavbarButtonType.Settings,
label: i18n('menu_settings'),
icon: <TuneIcon />,
component: Link,
to: AppRoute.Settings,
onClick: handleClearTab,
},
{ type: NavbarButtonType.Open, label: i18n('menu_open'), icon: <LaunchIcon />, onClick: handleUrl },
{ type: NavbarButtonType.About, label: i18n('menu_about'), icon: <InfoIcon />, component: Link, to: AppRoute.About, onClick: handleClearTab },
];

return (
<React.Fragment>
{buttons
?.filter(({ type }) => navbarButtons?.includes(type))
?.map(({ type, icon, label, ..._props }) => (
<Tooltip title={label} key={type}>
<IconButton id={`${type}-pinned`} aria-controls={`${type}-pinned-button`} {..._props}>
{icon}
</IconButton>
</Tooltip>
))}

<Tooltip title="Actions and Settings">
<IconButton
sx={{ m: '0 0.25rem' }}
id="basic-button"
aria-controls="basic-menu"
id="dropdown-menu-button"
aria-controls="dropdown-menu-button"
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
>
{label}
{menuIcon}
</IconButton>
</Tooltip>
<Menu
id="basic-menu"
id="dropdown-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
onClick={handleClose}
MenuListProps={{ 'aria-labelledby': 'basic-button' }}
MenuListProps={{ 'aria-labelledby': 'dropdown-menu' }}
>
<NavbarMenuIcon label={i18n('menu_add')} icon={<AddLinkIcon />} component={Link} to={AppRoute.Add} onClick={handleClearTab} />
<NavbarMenuIcon {...buttons[0]} />
<Divider />
<NavbarMenuIcon
label={i18n('menu_refresh')}
icon={<RefreshIcon />}
onClick={() => QueryService.listTasks().subscribe(handleError('refresh'))}
/>
<NavbarMenuIcon
label={i18n('menu_resume')}
icon={<PlayArrowIcon />}
onClick={() => QueryService.resumeAllTasks().subscribe(handleError('resume'))}
/>
<NavbarMenuIcon
label={i18n('menu_pause')}
icon={<PauseIcon />}
onClick={() => QueryService.pauseAllTasks().subscribe(handleError('pause'))}
/>
<NavbarMenuIcon label={i18n('menu_remove')} icon={<DeleteSweepIcon />} onClick={() => setPrompt(true)} />
<NavbarMenuIcon
label={i18n('menu_clear')}
icon={<ClearAllIcon />}
onClick={() => QueryService.deleteFinishedTasks().subscribe(handleError('clear'))}
/>
<NavbarMenuIcon {...buttons[1]} />
<NavbarMenuIcon {...buttons[2]} />
<NavbarMenuIcon {...buttons[3]} />
<NavbarMenuIcon {...buttons[4]} />
<NavbarMenuIcon {...buttons[5]} />
<Divider />
<NavbarMenuIcon label={i18n('menu_configs')} icon={<SettingsIcon />} component={Link} to={AppRoute.Config} onClick={handleClearTab} />
<NavbarMenuIcon label={i18n('menu_settings')} icon={<TuneIcon />} component={Link} to={AppRoute.Settings} onClick={handleClearTab} />
<NavbarMenuIcon label={i18n('menu_open')} icon={<LaunchIcon />} onClick={handleUrl} />
<NavbarMenuIcon {...buttons[6]} />
<NavbarMenuIcon {...buttons[7]} />
<NavbarMenuIcon {...buttons[8]} />
<Divider />
<NavbarMenuIcon label={i18n('menu_about')} icon={<InfoIcon />} component={Link} to={AppRoute.About} onClick={handleClearTab} />
<NavbarMenuIcon {...buttons[9]} />
</Menu>

<ConfirmationDialog
Expand Down
4 changes: 2 additions & 2 deletions src/components/navbar/navbar.tsx
Expand Up @@ -40,12 +40,12 @@ export const Navbar = () => {
indicatorColor="primary"
variant="scrollable"
value={getValue()}
sx={{ height: '100%' }}
sx={{ height: '100%', flex: '1 1 auto' }}
TabIndicatorProps={{ style: tab ? undefined : { display: 'none' } }}
>
{tabComponents}
</Tabs>
<NavbarMenu label={<MenuIcon />} />
<NavbarMenu menuIcon={<MenuIcon />} />
</Toolbar>
</AppBar>
);
Expand Down
32 changes: 29 additions & 3 deletions src/components/panel/settings/settings-global.tsx
@@ -1,14 +1,14 @@
import { Button, Card, CardActions, CardContent, CardHeader, Collapse, InputAdornment, MenuItem, Stack } from '@mui/material';
import { Button, Card, CardActions, CardContent, CardHeader, Collapse, Grid, InputAdornment, MenuItem, Stack } from '@mui/material';

import React from 'react';

import { useForm } from 'react-hook-form';

import { useDispatch, useSelector } from 'react-redux';

import { FormInput, FormSwitch } from '@src/components';
import { FormCheckbox, FormInput, FormSwitch } from '@src/components';
import type { Global } from '@src/models';
import { ActionScope, defaultGlobal, InterfaceHeader, ThemeMode } from '@src/models';
import { ActionScope, defaultGlobal, InterfaceHeader, NavbarButtonType, ThemeMode } from '@src/models';
import type { StoreState } from '@src/store';
import { syncInterface } from '@src/store/actions';
import { getGlobal } from '@src/store/selectors';
Expand Down Expand Up @@ -36,6 +36,7 @@ export const SettingsGlobal = () => {
progressBar: true,
background: true,
},
navbar: state.navbar ?? defaultGlobal.navbar,
},
});

Expand Down Expand Up @@ -153,6 +154,31 @@ export const SettingsGlobal = () => {
action={<FormSwitch controllerProps={{ name: 'task.background', control }} formControlLabelProps={{ label: '' }} />}
sx={{ p: '0.5rem 0' }}
/>
<CardHeader
title={i18n('navbar__buttons_title')}
titleTypographyProps={{ variant: 'subtitle2' }}
subheader={i18n('navbar__buttons_subheader')}
subheaderTypographyProps={{ variant: 'subtitle2' }}
sx={{ p: '0.5rem 0' }}
/>
<Card sx={{ p: '0.5rem 1rem', m: '0.5rem 0' }}>
<Grid container spacing={1} columnSpacing={1}>
{Object.values(NavbarButtonType).map(t => (
<Grid item xs={4} lg={2} key={t}>
<Button disableTouchRipple={true} sx={{ p: '0 0 0 0.5rem' }}>
<FormCheckbox
controllerProps={{ name: 'navbar.buttons', control }}
formControlLabelProps={{ label: i18n(t, 'common', 'model', 'navbar_button_type'), sx: { textTransform: 'capitalize' } }}
checkboxProps={{
multiple: true,
value: t,
}}
/>
</Button>
</Grid>
))}
</Grid>
</Card>
</CardContent>

<CardActions sx={{ justifyContent: 'flex-end', padding: '0 1.5rem 1.5rem' }}>
Expand Down
32 changes: 32 additions & 0 deletions src/i18n/en/common/model/navbar-button-type.json
@@ -0,0 +1,32 @@
{
"common__model__navbar_button_type__add": {
"message": "add"
},
"common__model__navbar_button_type__refresh": {
"message": "refresh"
},
"common__model__navbar_button_type__resume": {
"message": "resume"
},
"common__model__navbar_button_type__pause": {
"message": "pause"
},
"common__model__navbar_button_type__remove": {
"message": "remove"
},
"common__model__navbar_button_type__clear": {
"message": "clear"
},
"common__model__navbar_button_type__configs": {
"message": "configs"
},
"common__model__navbar_button_type__settings": {
"message": "settings"
},
"common__model__navbar_button_type__open": {
"message": "open"
},
"common__model__navbar_button_type__about": {
"message": "about"
}
}
12 changes: 6 additions & 6 deletions src/i18n/en/panel/settings/settings-global.json
Expand Up @@ -5,7 +5,6 @@
"panel__settings__global__subheader": {
"message": "Global settings for various interface elements."
},

"panel__settings__global__theme_title": {
"message": "Theme"
},
Expand All @@ -15,7 +14,6 @@
"panel__settings__global__theme_label": {
"message": "Theme"
},

"panel__settings__global__actions_title": {
"message": "Menu actions"
},
Expand All @@ -25,14 +23,12 @@
"panel__settings__global__actions_label": {
"message": "Scope"
},

"panel__settings__global__loading_title": {
"message": "Loading bar"
},
"panel__settings__global__loading_subheader": {
"message": "Display a loading bar indicator while fetching data."
},

"panel__settings__global__loading_threshold_title": {
"message": "Loading bar threshold"
},
Expand All @@ -42,18 +38,22 @@
"panel__settings__global__loading_threshold_label": {
"message": "Threshold"
},

"panel__settings__global__task__progress_bar_title": {
"message": "Task progress bar"
},
"panel__settings__global__task__progress_bar_subheader": {
"message": "Display a progress bar indicator inside task cards."
},

"panel__settings__global__task__background_title": {
"message": "Task background progress"
},
"panel__settings__global__task__background_subheader": {
"message": "Display the progression of active download task in card background."
},
"panel__settings__global__navbar__buttons_title": {
"message": "Navbar buttons"
},
"panel__settings__global__navbar__buttons_subheader": {
"message": "Shortcut buttons to display in the navbar."
}
}
1 change: 1 addition & 0 deletions src/models/index.ts
Expand Up @@ -9,6 +9,7 @@ export * from './material-ui.model';
export * from './menu.model';
export * from './message.model';
export * from './modal-instance.model';
export * from './navbar.model';
export * from './notification.model';
export * from './routes.model';
export * from './settings.model';
Expand Down
23 changes: 23 additions & 0 deletions src/models/navbar.model.ts
@@ -0,0 +1,23 @@
import type { ForwardRefExoticComponent, MouseEventHandler } from 'react';

export enum NavbarButtonType {
Add = 'add',
Refresh = 'refresh',
Resume = 'resume',
Pause = 'pause',
Remove = 'remove',
Clear = 'clear',
Configs = 'configs',
Settings = 'settings',
Open = 'open',
About = 'about',
}

export interface NavbarButton {
type: NavbarButtonType;
label: string;
icon: JSX.Element;
onClick?: MouseEventHandler;
component?: ForwardRefExoticComponent<any>;
to?: string;
}
4 changes: 4 additions & 0 deletions src/models/settings.model.ts
@@ -1,3 +1,5 @@
import { NavbarButtonType } from '@src/models/navbar.model';

import { defaultContextMenu, defaultQuickMenu } from './menu.model';

import { NotificationLevel } from './notification.model';
Expand Down Expand Up @@ -168,6 +170,7 @@ export interface Global {
progressBar: boolean;
background: boolean;
};
navbar: { buttons: NavbarButtonType[] };
}

export const defaultGlobal = {
Expand All @@ -178,6 +181,7 @@ export const defaultGlobal = {
progressBar: true,
background: true,
},
navbar: { buttons: [NavbarButtonType.Refresh, NavbarButtonType.Clear] },
};

export const defaultSettings: SettingsSlice = {
Expand Down

0 comments on commit 6ccc39c

Please sign in to comment.