diff --git a/src/store/reducers/settings.ts b/src/store/reducers/settings.ts index bd4122d5..8c6940b9 100644 --- a/src/store/reducers/settings.ts +++ b/src/store/reducers/settings.ts @@ -1,5 +1,11 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { HomeView, Settings, VideosSeniority, ViewFilters } from 'types'; +import { + HomeDisplayOptions, + HomeView, + Settings, + VideosSeniority, + ViewFilters, +} from 'types'; const { REACT_APP_YOUTUBE_API_KEY } = process.env; @@ -19,9 +25,14 @@ export const defaultSettings = { viewed: true, archived: true, }, + homeDisplayOptions: { + hiddenViews: [], + }, enableNotifications: true, }; +const views = Object.values(HomeView); + interface SettingsState extends Settings {} const initialState: SettingsState = { @@ -73,10 +84,31 @@ export const settingsSlice = createSlice({ return state; } }, + setHomeDisplayOptions: ( + state, + action: PayloadAction> + ) => { + const { hiddenViews } = action.payload; + return { + ...state, + defaultView: + !state.defaultView || hiddenViews?.includes(state.defaultView) + ? views.find((view) => !hiddenViews?.includes(view)) || null + : state.defaultView, + homeDisplayOptions: { + ...state.homeDisplayOptions, + ...action.payload, + }, + }; + }, }, }); -export const { setSettings, resetSettings, setViewFilters } = - settingsSlice.actions; +export const { + setSettings, + resetSettings, + setViewFilters, + setHomeDisplayOptions, +} = settingsSlice.actions; export default settingsSlice.reducer; diff --git a/src/store/selectors/settings.ts b/src/store/selectors/settings.ts index 7404134b..4e62af4b 100644 --- a/src/store/selectors/settings.ts +++ b/src/store/selectors/settings.ts @@ -19,3 +19,8 @@ export const selectViewFilters = (view: HomeView) => return {} as ViewFilters; } }); + +export const selectHomeDisplayOptions = createSelector( + selectSettings, + (settings) => settings.homeDisplayOptions +); diff --git a/src/types/Settings.ts b/src/types/Settings.ts index 51eb1dbe..929f1ea7 100644 --- a/src/types/Settings.ts +++ b/src/types/Settings.ts @@ -1,16 +1,21 @@ import { Either } from './common'; export interface Settings { - defaultView: HomeView; + defaultView: HomeView | null; apiKey: string; darkMode: boolean; autoPlayVideos: boolean; recentVideosSeniority: VideosSeniority; recentViewFilters: RecentViewFilters; watchLaterViewFilters: WatchLaterViewFilters; + homeDisplayOptions: HomeDisplayOptions; enableNotifications: boolean; } +export interface HomeDisplayOptions { + hiddenViews: HomeView[]; +} + export interface RecentViewFilters { uncategorised: boolean; viewed: boolean; diff --git a/src/ui/components/pages/Channels/ChannelCard/ChannelDialogs/ChannelFiltersDialog/index.tsx b/src/ui/components/pages/Channels/ChannelCard/ChannelDialogs/ChannelFiltersDialog/index.tsx index 4a15df0f..bf29dadd 100644 --- a/src/ui/components/pages/Channels/ChannelCard/ChannelDialogs/ChannelFiltersDialog/index.tsx +++ b/src/ui/components/pages/Channels/ChannelCard/ChannelDialogs/ChannelFiltersDialog/index.tsx @@ -90,6 +90,7 @@ export default function ChannelFiltersDialog(props: ChannelFiltersDialogProps) { {filters.map((filter, index) => ( ) => handleChange(changes, index) } diff --git a/src/ui/components/pages/Home/DisplayOptionsDialog/index.tsx b/src/ui/components/pages/Home/DisplayOptionsDialog/index.tsx new file mode 100644 index 00000000..3d33be76 --- /dev/null +++ b/src/ui/components/pages/Home/DisplayOptionsDialog/index.tsx @@ -0,0 +1,129 @@ +import React, { useState } from 'react'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + FormLabel, + FormControl, + FormGroup, + FormControlLabel, + Switch, +} from '@mui/material'; +import { HomeView } from 'types'; +import { useDidMountEffect } from 'hooks/useDidMountEffect'; +import { useAppDispatch, useAppSelector } from 'store'; +import { selectHomeDisplayOptions } from 'store/selectors/settings'; +import DisplaySettingsIcon from '@mui/icons-material/DisplaySettings'; +import { setHomeDisplayOptions } from 'store/reducers/settings'; + +interface DisplayOptionsDialogProps { + open: boolean; + onClose: () => void; +} + +interface View { + label: string; + value: HomeView; + hidden: boolean; +} + +export default function DisplayOptionsDialog(props: DisplayOptionsDialogProps) { + const { open, onClose } = props; + const displayOptions = useAppSelector(selectHomeDisplayOptions); + const dispatch = useAppDispatch(); + const initialViews: View[] = [ + { + label: 'All', + value: HomeView.All, + hidden: displayOptions.hiddenViews.includes(HomeView.All), + }, + { + label: 'Recent', + value: HomeView.Recent, + hidden: displayOptions.hiddenViews.includes(HomeView.Recent), + }, + { + label: 'Watch Later', + value: HomeView.WatchLater, + hidden: displayOptions.hiddenViews.includes(HomeView.WatchLater), + }, + ]; + const [views, setViews] = useState(initialViews); + + useDidMountEffect(() => { + if (open) { + setViews(initialViews); + } + }, [open]); + + const handleViewToggle = (target: View) => { + setViews((state) => + state.map((view) => + view.value === target.value + ? { + ...view, + hidden: !view.hidden, + } + : view + ) + ); + }; + + const handleSave = () => { + dispatch( + setHomeDisplayOptions({ + hiddenViews: views + .filter(({ hidden }) => hidden) + .map(({ value }) => value), + }) + ); + onClose(); + }; + + const handleClose = () => { + onClose(); + }; + + return ( + + + + + Display options + + + + Active views + + {views.map((view) => ( + handleViewToggle(view)} + key={view.value} + control={ + + } + label={view.label} + /> + ))} + + + + + + + + + + ); +} diff --git a/src/ui/components/pages/Home/TabActions/index.tsx b/src/ui/components/pages/Home/TabActions/index.tsx index d4243a25..3c761449 100644 --- a/src/ui/components/pages/Home/TabActions/index.tsx +++ b/src/ui/components/pages/Home/TabActions/index.tsx @@ -6,7 +6,7 @@ import { useAppSelector } from 'store'; import { selectActiveChannelsCount } from 'store/selectors/channels'; interface TabActionsProps { - tab: HomeView; + tab: HomeView | null; recentVideosCount: number; watchLaterVideosCount: number; } diff --git a/src/ui/components/pages/Home/index.tsx b/src/ui/components/pages/Home/index.tsx index 3fccd5d7..bbb7031c 100644 --- a/src/ui/components/pages/Home/index.tsx +++ b/src/ui/components/pages/Home/index.tsx @@ -21,6 +21,7 @@ export function Home(props: HomeProps) { } ); const watchLaterVideosCount = useAppSelector(selectWatchLaterVideosCount); + const { hiddenViews } = settings.homeDisplayOptions; useEffect(() => { if (activeTab !== settings.defaultView) { @@ -57,17 +58,23 @@ export function Home(props: HomeProps) { onChange={handleTabChange} aria-label="tabs" > - - - + {!hiddenViews.includes(HomeView.All) && ( + + )} + {!hiddenViews.includes(HomeView.Recent) && ( + + )} + {!hiddenViews.includes(HomeView.WatchLater) && ( + + )} - + {activeTab && ( + + )} ); } diff --git a/src/ui/components/pages/Settings/Field/index.tsx b/src/ui/components/pages/Settings/Field/index.tsx index 44b4dbbc..3b4b9f2a 100644 --- a/src/ui/components/pages/Settings/Field/index.tsx +++ b/src/ui/components/pages/Settings/Field/index.tsx @@ -7,7 +7,7 @@ import Select from './Select'; import MenuItem from './MenuItem'; import Secret from './Secret'; -type ValueType = string | number | boolean; +type ValueType = string | number | boolean | null; type OptionType = { label: string; diff --git a/src/ui/components/pages/Settings/index.tsx b/src/ui/components/pages/Settings/index.tsx index 39d0b882..525ea185 100644 --- a/src/ui/components/pages/Settings/index.tsx +++ b/src/ui/components/pages/Settings/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { Stack, Divider, Link } from '@mui/material'; import { Layout } from 'ui/components/shared'; import { HomeView, SettingType, VideosSeniority } from 'types'; @@ -20,6 +20,21 @@ import ClearVideosData from './ClearVideosData'; interface SettingsProps {} +const views = [ + { + label: 'All', + value: HomeView.All, + }, + { + label: 'Recent', + value: HomeView.Recent, + }, + { + label: 'Watch later', + value: HomeView.WatchLater, + }, +]; + export function Settings(props: SettingsProps) { const [lastCheckTime, setLastCheckTime] = useState(null); const settings = useAppSelector(selectSettings); @@ -27,6 +42,14 @@ export function Settings(props: SettingsProps) { const savedVideosSize = memorySizeOf(videos); const dispatch = useAppDispatch(); + const activeViews = useMemo( + () => + views.filter( + ({ value }) => !settings.homeDisplayOptions.hiddenViews.includes(value) + ), + [settings.homeDisplayOptions.hiddenViews] + ); + useEffect(() => { if (isWebExtension) { sendMessage('getLastCheckDate').then((date: string) => { @@ -48,20 +71,7 @@ export function Settings(props: SettingsProps) { onChange={(defaultView: HomeView) => { dispatch(setSettings({ defaultView })); }} - options={[ - { - label: 'All', - value: HomeView.All, - }, - { - label: 'Recent', - value: HomeView.Recent, - }, - { - label: 'Watch later', - value: HomeView.WatchLater, - }, - ]} + options={activeViews} type={SettingType.List} /> ({ minWidth: 24, height: 24, fontSize: '0.75rem', - marginLeft: theme.spacing(3), + // marginLeft: theme.spacing(3), borderRadius: theme.spacing(1.5), }, })); diff --git a/src/ui/components/shared/Sidebar/ListItemLink/index.tsx b/src/ui/components/shared/Sidebar/ListItemLink/index.tsx index cdc5f9f7..726d03e0 100644 --- a/src/ui/components/shared/Sidebar/ListItemLink/index.tsx +++ b/src/ui/components/shared/Sidebar/ListItemLink/index.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Box } from '@mui/material'; import { Link, LinkProps, useLocation } from 'react-router-dom'; import ListItemIcon from '@mui/material/ListItemIcon'; import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; @@ -10,12 +11,13 @@ interface ListItemLinkProps { icon: React.ReactElement; text: string; badge?: React.ReactNode; + actions?: (selected: boolean) => React.ReactNode; hasWarning?: boolean; to: string; } export default function ListItemLink(props: ListItemLinkProps) { - const { icon, text, badge, hasWarning, to } = props; + const { icon, text, badge, actions, hasWarning, to } = props; const location = useLocation(); const selected = location.pathname === to; @@ -32,13 +34,23 @@ export default function ListItemLink(props: ListItemLinkProps) { {icon ? {icon} : null} + theme.spacing(3), + }} + > {text} {badge && selected ? : null} + {actions && actions(selected)} {hasWarning && !selected ? ( ) : null} - + } /> diff --git a/src/ui/components/shared/Sidebar/index.tsx b/src/ui/components/shared/Sidebar/index.tsx index 41f6c707..87a93fd2 100644 --- a/src/ui/components/shared/Sidebar/index.tsx +++ b/src/ui/components/shared/Sidebar/index.tsx @@ -1,16 +1,19 @@ -import React from 'react'; -import { Box, Typography, Link, List } from '@mui/material'; +import React, { useState } from 'react'; +import { Box, Typography, Link, List, IconButton } from '@mui/material'; import ExploreIcon from '@mui/icons-material/Explore'; import SubscriptionsIcon from '@mui/icons-material/Subscriptions'; import SettingsIcon from '@mui/icons-material/Settings'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import ContactSupportIcon from '@mui/icons-material/ContactSupport'; +import DisplaySettingsIcon from '@mui/icons-material/DisplaySettings'; import ListItemLink from './ListItemLink'; import Header from './Header'; import { useAppSelector } from 'store'; import { selectChannelsCount } from 'store/selectors/channels'; import { selectSettings } from 'store/selectors/settings'; import { selectApp } from 'store/selectors/app'; +import { Nullable } from 'types'; +import HomeDisplayOptionsDialog from 'ui/components/pages/Home/DisplayOptionsDialog'; interface SidebarProps {} @@ -18,6 +21,15 @@ export function Sidebar(props: SidebarProps) { const app = useAppSelector(selectApp); const channelsCount = useAppSelector(selectChannelsCount); const settings = useAppSelector(selectSettings); + const [openedDialog, setOpenedDialog] = useState>(null); + + const openDialog = (dialog: string) => { + setOpenedDialog(dialog); + }; + + const closeDialog = () => { + setOpenedDialog(null); + }; return ( - } text="Home" to="/" /> + } + text="Home" + to="/" + actions={(selected) => + selected && ( + <> + theme.palette.primary.dark, + color: (theme) => theme.palette.common.white, + }} + onClick={() => openDialog('display-options')} + > + + + + + ) + } + /> } text="Channels"