diff --git a/src/App.tsx b/src/App.tsx index 1311cf4..0004f20 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,7 +27,11 @@ function App() { const { data: allThemes, isFetched: isAllThemesFetched } = useQueryAPI({ endpoint: 'api/themes', }); - const { theme, setTheme, themeName, handleThemeChange } = useCommonHooks(); + const { data: themePlaylists, isFetched: isThemePlaylistsFetched } = useQueryAPI({ + endpoint: 'api/themePlaylists', + }); + const { theme, setTheme, themeName, handleThemeChange, currentThemePlaylist } = + useCommonHooks(); const overrides = createTheme({ white: theme?.backgroundColor, fontFamily: '-apple-system,Jost,BlinkMacSystemFont,Helvetica Neue,Roboto,sans-serif', @@ -54,14 +58,32 @@ function App() { useEffect(() => { if (allThemes && themeName) { - if (themeName !== 'random') { - const temp = allThemes.find((t: any) => t.name === themeName); - setTheme(temp); + if (currentThemePlaylist === 'All') { + if (themeName !== 'random') { + const temp = allThemes.find((t: any) => t.name === themeName); + setTheme(temp); + } else { + handleThemeChange(allThemes, false); + } + } else if (currentThemePlaylist === 'Time of Day') { + const themeList = JSON.parse( + themePlaylists.find((t: any) => t.name === currentThemePlaylist).themeList, + ); + setTheme( + allThemes.find( + (t: any) => t.id === Number(themeList[Math.floor(new Date().getHours() / 6)]), + ), + ); } else { - handleThemeChange(allThemes, false); + const themeList = JSON.parse( + themePlaylists.find((t: any) => t.name === currentThemePlaylist).themeList, + ); + const filteredThemes = allThemes.filter((t: any) => themeList.includes(t.id)); + setTheme('random'); + handleThemeChange(filteredThemes, false); } } - }, [isAllThemesFetched, themeName]); + }, [isAllThemesFetched, themeName, currentThemePlaylist, isThemePlaylistsFetched]); return ( <> diff --git a/src/hooks/useCommonHooks.ts b/src/hooks/useCommonHooks.ts index f2fd240..181e953 100644 --- a/src/hooks/useCommonHooks.ts +++ b/src/hooks/useCommonHooks.ts @@ -22,6 +22,12 @@ export function useCommonHooks() { key: 'theme', defaultValue: 'blackboard', }); + + const [currentThemePlaylist, setCurrentThemePlaylist] = useLocalStorage({ + key: 'themePlaylist', + defaultValue: 'All', + }); + const [searchValue, setSearchValue] = useState(''); const [providerLabel, setProviderLabel] = useState('Google Search'); const [providerURL, setProviderURL] = useState('https://www.google.com/search?q='); @@ -52,6 +58,10 @@ export function useCommonHooks() { if (enabled.length === 0) { setTheme(themes[0]); save && setThemeName(themes[0].name); + } else if (currentThemePlaylist !== 'All') { + const index = Math.floor(Math.random() * themes.length); + setTheme(themes[index]); + save && setThemeName(themes[index].name); } else { const index = Math.floor(Math.random() * enabled.length); setTheme(enabled[index]); @@ -72,6 +82,8 @@ export function useCommonHooks() { setBookmarkFilter, currentTheme, setCurrentTheme, + currentThemePlaylist, + setCurrentThemePlaylist, searchValue, setSearchValue, providerLabel, diff --git a/src/pages/settings/Settings.tsx b/src/pages/settings/Settings.tsx index 68971b7..c14f227 100644 --- a/src/pages/settings/Settings.tsx +++ b/src/pages/settings/Settings.tsx @@ -19,6 +19,7 @@ import Categories from './components/Categories'; import Messages from './components/Messages'; import Personalization from './components/Personalization'; import SearchProviders from './components/SearchProviders'; +import ThemePlaylists from './components/ThemePlaylists'; import Themes from './components/Themes'; import WolClients from './components/WolClients'; @@ -66,6 +67,9 @@ export function Settings() { {isMobile ? : 'Themes'} + + {isMobile ? : 'Theme Playlists'} + {isMobile ? : 'Search Providers'} @@ -91,6 +95,9 @@ export function Settings() { + + + diff --git a/src/pages/settings/components/ThemeMiniCard.tsx b/src/pages/settings/components/ThemeMiniCard.tsx new file mode 100644 index 0000000..51738b4 --- /dev/null +++ b/src/pages/settings/components/ThemeMiniCard.tsx @@ -0,0 +1,36 @@ +import { Group, Text } from '@mantine/core'; +function ThemeMiniCard({ allThemes, item }: any) { + const theme = allThemes.find((theme: any) => theme.id === item); + + return ( + + + {allThemes.find((theme: any) => theme.id === item)?.name} + + + ); +} + +export default ThemeMiniCard; diff --git a/src/pages/settings/components/ThemePlaylistCard.tsx b/src/pages/settings/components/ThemePlaylistCard.tsx new file mode 100644 index 0000000..5d7aecf --- /dev/null +++ b/src/pages/settings/components/ThemePlaylistCard.tsx @@ -0,0 +1,208 @@ +import { + ActionIcon, + Button, + Group, + Overlay, + ScrollArea, + Stack, + Text, +} from '@mantine/core'; +import { useCommonHooks } from 'hooks'; +import { useState } from 'react'; +import { FiEdit, FiRefreshCcw, FiTrash } from 'react-icons/fi'; +import ThemeMiniCard from './ThemeMiniCard'; + +function ThemePlaylistCard({ + cardPlaylist, + handleOpen, + handleDeletePlaylist, + createPlaylist, + allThemes, +}: any) { + const [hover, setHover] = useState(false); + const { currentThemePlaylist, setCurrentThemePlaylist, theme } = useCommonHooks(); + const syncableLists = ['All', 'Backgrounds', 'No Backgrounds']; + const themeList = JSON.parse( + cardPlaylist.themeList.name === 'Time of Day' ? '[]' : cardPlaylist.themeList, + ); + + return ( + setHover(true)} + onMouseLeave={() => setHover(false)} + > + + {cardPlaylist.name} + + {syncableLists.includes(cardPlaylist.name) ? ( + { + event.stopPropagation(); + if (cardPlaylist.name === 'All') { + createPlaylist({ + method: 'PATCH', + endpoint: 'api/themePlaylists', + body: { + id: cardPlaylist.id, + name: 'All', + themeList: JSON.stringify(allThemes.map((theme: any) => theme.id)), + }, + }); + } + if (cardPlaylist.name === 'Backgrounds') { + createPlaylist({ + method: 'PATCH', + endpoint: 'api/themePlaylists', + body: { + id: cardPlaylist.id, + name: 'Backgrounds', + themeList: JSON.stringify( + allThemes + .filter((theme: any) => theme.backgroundImage !== 'none') + .map((theme: any) => theme.id), + ), + }, + }); + } + if (cardPlaylist.name === 'No Backgrounds') { + createPlaylist({ + method: 'PATCH', + endpoint: 'api/themePlaylists', + body: { + id: cardPlaylist.id, + name: 'No Backgrounds', + themeList: JSON.stringify( + allThemes + .filter((theme: any) => theme.backgroundImage === 'none') + .map((theme: any) => theme.id), + ), + }, + }); + } + }} + > + + + ) : ( + <> + { + handleOpen(cardPlaylist); + }} + > + + + {cardPlaylist.default === 0 && ( + handleDeletePlaylist(cardPlaylist)} + > + + + )} + + )} + + + ); +} + +export default ThemePlaylistCard; diff --git a/src/pages/settings/components/ThemePlaylistModal.tsx b/src/pages/settings/components/ThemePlaylistModal.tsx new file mode 100644 index 0000000..dc06bd4 --- /dev/null +++ b/src/pages/settings/components/ThemePlaylistModal.tsx @@ -0,0 +1,268 @@ +import { + Button, + Divider, + Group, + Modal, + Select, + SimpleGrid, + Stack, + Text, + TextInput, +} from '@mantine/core'; +import { useCommonHooks } from 'hooks'; +import { useEffect, useState } from 'react'; +import { modalClasses, selectClasses, textInputClasses } from 'styles'; +import ThemeMiniCard from './ThemeMiniCard'; + +function ThemePlaylistModal({ + opened, + handleClose, + createPlaylist, + selectedPlaylist, + setSelectedPlaylist, + handleSavePlaylist, + allThemes, +}: any) { + const { theme } = useCommonHooks(); + const [themeList, setThemeList] = useState('[]'); + + useEffect(() => { + if (selectedPlaylist.themeList) { + setThemeList(JSON.parse(selectedPlaylist?.themeList)); + } + }, [selectedPlaylist]); + return ( + { + handleClose(); + setThemeList('[]'); + }} + style={{ + '--color-text-pri': theme.primaryColor, + '--color-text-acc': theme.accentColor, + '--color-background': theme.backgroundColor, + '--color-border': theme.borderColor, + '--color-search-background': theme.searchColor, + '--image-background': `url("${theme.backgroundImage}")`, + }} + > + + + + + Name: + + setSelectedPlaylist({ ...selectedPlaylist, name: e.target.value }) + } + styles={{ + label: { + color: theme.primaryColor, + }, + input: { + color: theme.primaryColor, + border: 'none', + borderBottom: `1px solid ${theme.primaryColor}`, + }, + }} + /> + + + + + {selectedPlaylist.name === 'Time of Day' ? ( + + + 00:00-05:59 + { + return { + value: `${theme.id}`, + label: theme.name, + }; + })} + classNames={{ + input: selectClasses.menuSelectInput, + label: selectClasses.menuSelectLabel, + options: selectClasses.menuSelectOptions, + option: selectClasses.menuSelectOption, + }} + onChange={(data) => { + const newThemeList = themeList; + newThemeList['1'] = data; + createPlaylist({ + method: 'PATCH', + endpoint: 'api/themePlaylists', + body: { + id: selectedPlaylist.id, + name: selectedPlaylist.name, + themeList: JSON.stringify(newThemeList), + }, + }); + }} + /> + + + 12:00-17:59 + { + return { + value: `${theme.id}`, + label: theme.name, + }; + })} + classNames={{ + input: selectClasses.menuSelectInput, + label: selectClasses.menuSelectLabel, + options: selectClasses.menuSelectOptions, + option: selectClasses.menuSelectOption, + }} + onChange={(data) => { + const newThemeList = themeList; + newThemeList['3'] = data; + createPlaylist({ + method: 'PATCH', + endpoint: 'api/themePlaylists', + body: { + id: selectedPlaylist.id, + name: selectedPlaylist.name, + themeList: JSON.stringify(newThemeList), + }, + }); + }} + /> + + + ) : ( + + {selectedPlaylist.name !== 'Time of Day' && + allThemes?.map((miniTheme: any) => { + const newThemeList = themeList; + return ( + { + if (themeList?.includes(miniTheme.id)) { + newThemeList.splice(themeList.indexOf(miniTheme.id), 1); + } else { + newThemeList.push(miniTheme.id); + } + selectedPlaylist.themeList = JSON.stringify(newThemeList); + }} + > + + + ); + })} + + )} + + + + + + + + + ); +} + +export default ThemePlaylistModal; diff --git a/src/pages/settings/components/ThemePlaylists.tsx b/src/pages/settings/components/ThemePlaylists.tsx new file mode 100644 index 0000000..d71ace8 --- /dev/null +++ b/src/pages/settings/components/ThemePlaylists.tsx @@ -0,0 +1,117 @@ +import { Box, Button, Group, Stack, Text } from '@mantine/core'; +import { useDisclosure } from '@mantine/hooks'; +import { modals } from '@mantine/modals'; +import { useCommonHooks, useMutationAPI, useQueryAPI } from 'hooks'; +import { useState } from 'react'; +import { FiPlus } from 'react-icons/fi'; +import { buttonClasses, textClasses } from 'styles'; +import ThemePlaylistCard from './ThemePlaylistCard'; +import ThemePlaylistModal from './ThemePlaylistModal'; + +function ThemePlaylists() { + const { data: allThemes } = useQueryAPI({ + endpoint: 'api/themes', + }); + const { data: themePlaylists } = useQueryAPI({ + endpoint: 'api/themePlaylists', + }); + const { mutateAsync: createPlaylist } = useMutationAPI(); + const { mutateAsync: deletePlaylist } = useMutationAPI(); + const [opened, { open, close }] = useDisclosure(false); + const [selectedPlaylist, setSelectedPlaylist] = useState({}); + const { currentThemePlaylist, setCurrentThemePlaylist } = useCommonHooks(); + + const handleOpen = (playlist: any) => { + setSelectedPlaylist(playlist); + open(); + }; + + const handleClose = () => { + close(); + }; + + const handleSavePlaylist = () => { + createPlaylist({ + method: selectedPlaylist.id === undefined ? 'POST' : 'PATCH', + endpoint: 'api/themePlaylists', + body: selectedPlaylist, + }); + close(); + }; + + const openDeleteConfirmModal = (playlist: any) => + modals.openConfirmModal({ + title: 'Are you sure you want to delete this theme playlist?', + labels: { confirm: 'Delete', cancel: 'Cancel' }, + confirmProps: { color: 'red' }, + onConfirm: () => + deletePlaylist({ + method: 'DELETE', + endpoint: 'api/themePlaylists', + body: playlist, + }), + }); + + const handleDeletePlaylist = (playlist: any) => { + openDeleteConfirmModal(playlist); + close(); + }; + + return ( + <> + + + + + + Theme Playlists + + + + + + {themePlaylists?.map((cardPlaylist: any) => { + return ( + + + + + + ); + })} + + + + ); +} + +export default ThemePlaylists;