diff --git a/src/components/navbar/navbar-menu.tsx b/src/components/navbar/navbar-menu.tsx index e129eeaf..d5cec4d5 100644 --- a/src/components/navbar/navbar-menu.tsx +++ b/src/components/navbar/navbar-menu.tsx @@ -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); + const [anchorEl, setAnchorEl] = React.useState(null); const open = Boolean(anchorEl); - const handleClick = (event: React.MouseEvent) => setAnchorEl(event.currentTarget); + const handleClick = (event: React.MouseEvent) => setAnchorEl(event.currentTarget); const handleClose = () => setAnchorEl(null); const handleClearTab = () => dispatch(setNavbar()); const handleUrl = () => createTab({ url }); @@ -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: , + component: Link, + to: AppRoute.Add, + onClick: handleClearTab, + }, + { + type: NavbarButtonType.Refresh, + label: i18n('menu_refresh'), + icon: , + onClick: () => QueryService.listTasks().subscribe(handleError('refresh')), + }, + { + type: NavbarButtonType.Resume, + label: i18n('menu_resume'), + icon: , + onClick: () => QueryService.resumeAllTasks().subscribe(handleError('resume')), + }, + { + type: NavbarButtonType.Pause, + label: i18n('menu_pause'), + icon: , + onClick: () => QueryService.pauseAllTasks().subscribe(handleError('pause')), + }, + { type: NavbarButtonType.Remove, label: i18n('menu_remove'), icon: , onClick: () => setPrompt(true) }, + { + type: NavbarButtonType.Clear, + label: i18n('menu_clear'), + icon: , + onClick: () => QueryService.deleteFinishedTasks().subscribe(handleError('clear')), + }, + { + type: NavbarButtonType.Configs, + label: i18n(`menu_configs`), + icon: , + component: Link, + to: AppRoute.Config, + onClick: handleClearTab, + }, + { + type: NavbarButtonType.Settings, + label: i18n('menu_settings'), + icon: , + component: Link, + to: AppRoute.Settings, + onClick: handleClearTab, + }, + { type: NavbarButtonType.Open, label: i18n('menu_open'), icon: , onClick: handleUrl }, + { type: NavbarButtonType.About, label: i18n('menu_about'), icon: , component: Link, to: AppRoute.About, onClick: handleClearTab }, + ]; + return ( + {buttons + ?.filter(({ type }) => navbarButtons?.includes(type)) + ?.map(({ type, icon, label, ..._props }) => ( + + + {icon} + + + ))} + - {label} + {menuIcon} - } component={Link} to={AppRoute.Add} onClick={handleClearTab} /> + - } - onClick={() => QueryService.listTasks().subscribe(handleError('refresh'))} - /> - } - onClick={() => QueryService.resumeAllTasks().subscribe(handleError('resume'))} - /> - } - onClick={() => QueryService.pauseAllTasks().subscribe(handleError('pause'))} - /> - } onClick={() => setPrompt(true)} /> - } - onClick={() => QueryService.deleteFinishedTasks().subscribe(handleError('clear'))} - /> + + + + + - } component={Link} to={AppRoute.Config} onClick={handleClearTab} /> - } component={Link} to={AppRoute.Settings} onClick={handleClearTab} /> - } onClick={handleUrl} /> + + + - } component={Link} to={AppRoute.About} onClick={handleClearTab} /> + { indicatorColor="primary" variant="scrollable" value={getValue()} - sx={{ height: '100%' }} + sx={{ height: '100%', flex: '1 1 auto' }} TabIndicatorProps={{ style: tab ? undefined : { display: 'none' } }} > {tabComponents} - } /> + } /> ); diff --git a/src/components/panel/settings/settings-global.tsx b/src/components/panel/settings/settings-global.tsx index ec7d4784..0498f03a 100644 --- a/src/components/panel/settings/settings-global.tsx +++ b/src/components/panel/settings/settings-global.tsx @@ -1,4 +1,4 @@ -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'; @@ -6,9 +6,9 @@ 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'; @@ -36,6 +36,7 @@ export const SettingsGlobal = () => { progressBar: true, background: true, }, + navbar: state.navbar ?? defaultGlobal.navbar, }, }); @@ -153,6 +154,31 @@ export const SettingsGlobal = () => { action={} sx={{ p: '0.5rem 0' }} /> + + + + {Object.values(NavbarButtonType).map(t => ( + + + + ))} + + diff --git a/src/i18n/en/common/model/navbar-button-type.json b/src/i18n/en/common/model/navbar-button-type.json new file mode 100644 index 00000000..7162ed37 --- /dev/null +++ b/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" + } +} diff --git a/src/i18n/en/panel/settings/settings-global.json b/src/i18n/en/panel/settings/settings-global.json index 58581720..930cffe7 100644 --- a/src/i18n/en/panel/settings/settings-global.json +++ b/src/i18n/en/panel/settings/settings-global.json @@ -5,7 +5,6 @@ "panel__settings__global__subheader": { "message": "Global settings for various interface elements." }, - "panel__settings__global__theme_title": { "message": "Theme" }, @@ -15,7 +14,6 @@ "panel__settings__global__theme_label": { "message": "Theme" }, - "panel__settings__global__actions_title": { "message": "Menu actions" }, @@ -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" }, @@ -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." } } diff --git a/src/models/index.ts b/src/models/index.ts index 3282c6c1..a605ad10 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -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'; diff --git a/src/models/navbar.model.ts b/src/models/navbar.model.ts new file mode 100644 index 00000000..a55ce279 --- /dev/null +++ b/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; + to?: string; +} diff --git a/src/models/settings.model.ts b/src/models/settings.model.ts index 6934db39..29386662 100644 --- a/src/models/settings.model.ts +++ b/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'; @@ -168,6 +170,7 @@ export interface Global { progressBar: boolean; background: boolean; }; + navbar: { buttons: NavbarButtonType[] }; } export const defaultGlobal = { @@ -178,6 +181,7 @@ export const defaultGlobal = { progressBar: true, background: true, }, + navbar: { buttons: [NavbarButtonType.Refresh, NavbarButtonType.Clear] }, }; export const defaultSettings: SettingsSlice = { diff --git a/src/models/tab.model.ts b/src/models/tab.model.ts index 04ba762a..03717a53 100644 --- a/src/models/tab.model.ts +++ b/src/models/tab.model.ts @@ -83,12 +83,4 @@ export const defaultTabs: TaskTab[] = [ destination: { enabled: false }, color: ColorLevel.warning, }, - { - id: uuid(), - name: TabType.stopped, - template: TabType.stopped, - status: [TaskStatus.paused], - destination: { enabled: false }, - color: ColorLevel.error, - }, ]; diff --git a/src/store/selectors/settings.selector.ts b/src/store/selectors/settings.selector.ts index c05e72ce..20beaec5 100644 --- a/src/store/selectors/settings.selector.ts +++ b/src/store/selectors/settings.selector.ts @@ -72,3 +72,5 @@ export const getThemeMode = createSelector(getGlobal, global => { }); export const getGlobalTask = createSelector(getGlobal, global => global?.task ?? defaultGlobal.task); + +export const getGlobalNavbarButton = createSelector(getGlobal, global => global?.navbar?.buttons ?? defaultGlobal.navbar?.buttons);