Move settings to route instead of modal

This commit is contained in:
jeffvli 2023-03-30 03:01:31 -07:00
parent 0c13b09029
commit bc5f1f13f0
11 changed files with 159 additions and 151 deletions

View File

@ -1,5 +1,5 @@
import type { TabsProps as MantineTabsProps } from '@mantine/core'; import { Suspense } from 'react';
import { Tabs as MantineTabs } from '@mantine/core'; import { TabsPanelProps, TabsProps as MantineTabsProps, Tabs as MantineTabs } from '@mantine/core';
import styled from 'styled-components'; import styled from 'styled-components';
type TabsProps = MantineTabsProps; type TabsProps = MantineTabsProps;
@ -12,11 +12,14 @@ const StyledTabs = styled(MantineTabs)`
} }
&.mantine-Tabs-tab { &.mantine-Tabs-tab {
padding: 0.5rem 1rem;
font-weight: 600;
font-size: 1rem;
background-color: var(--main-bg); background-color: var(--main-bg);
} }
& .mantine-Tabs-panel { & .mantine-Tabs-panel {
padding: 0 1rem; padding: 1.5rem 0.5rem;
} }
button { button {
@ -35,27 +38,26 @@ const StyledTabs = styled(MantineTabs)`
button[data-active] { button[data-active] {
color: var(--btn-subtle-fg); color: var(--btn-subtle-fg);
background: none; background: none;
box-shadow: 2px 0 0 var(--primary-color) inset; border-color: var(--primary-color);
&:hover { &:hover {
background: none; background: none;
} }
} }
/* button[data-active]::before {
content: '';
border-left: 2px solid var(--primary-color);
position: absolute;
left: 0;
right: 0;
bottom: 0;
} */
`; `;
export const Tabs = ({ children, ...props }: TabsProps) => { export const Tabs = ({ children, ...props }: TabsProps) => {
return <StyledTabs {...props}>{children}</StyledTabs>; return <StyledTabs {...props}>{children}</StyledTabs>;
}; };
const Panel = ({ children, ...props }: TabsPanelProps) => {
return (
<StyledTabs.Panel {...props}>
<Suspense fallback={<></>}>{children}</Suspense>
</StyledTabs.Panel>
);
};
Tabs.List = StyledTabs.List; Tabs.List = StyledTabs.List;
Tabs.Panel = StyledTabs.Panel; Tabs.Panel = Panel;
Tabs.Tab = StyledTabs.Tab; Tabs.Tab = StyledTabs.Tab;

View File

@ -27,7 +27,7 @@ const SIDE_QUEUE_OPTIONS = [
{ label: 'Floating', value: 'sideDrawerQueue' }, { label: 'Floating', value: 'sideDrawerQueue' },
]; ];
const WINDOWBAR_OPTIONS = [ const WINDOW_BAR_OPTIONS = [
{ label: 'Web (hidden)', value: Platform.WEB }, { label: 'Web (hidden)', value: Platform.WEB },
{ label: 'Windows', value: Platform.WINDOWS }, { label: 'Windows', value: Platform.WINDOWS },
{ label: 'macOS', value: Platform.MACOS }, { label: 'macOS', value: Platform.MACOS },
@ -41,7 +41,7 @@ export const GeneralTab = () => {
{ {
control: ( control: (
<Select <Select
data={WINDOWBAR_OPTIONS} data={WINDOW_BAR_OPTIONS}
disabled={!isElectron()} disabled={!isElectron()}
value={settings.windowBarStyle} value={settings.windowBarStyle}
onChange={(e) => { onChange={(e) => {
@ -55,9 +55,9 @@ export const GeneralTab = () => {
}} }}
/> />
), ),
description: 'Adjust the style of the windowbar', description: 'Adjust the style of the application window bar',
isHidden: !isElectron(), isHidden: !isElectron(),
title: 'Windowbar style', title: 'Window bar style',
}, },
{ {
control: ( control: (

View File

@ -3,7 +3,6 @@ import { Divider, Group, SelectItem, Stack } from '@mantine/core';
import { import {
FileInput, FileInput,
NumberInput, NumberInput,
SegmentedControl,
Select, Select,
Slider, Slider,
Switch, Switch,
@ -69,7 +68,7 @@ export const PlaybackTab = () => {
const playerOptions = [ const playerOptions = [
{ {
control: ( control: (
<SegmentedControl <Select
data={[ data={[
{ {
disabled: !isElectron(), disabled: !isElectron(),
@ -98,7 +97,6 @@ export const PlaybackTab = () => {
control: ( control: (
<FileInput <FileInput
placeholder={mpvPath} placeholder={mpvPath}
size="sm"
width={225} width={225}
onChange={handleSetMpvPath} onChange={handleSetMpvPath}
/> />
@ -115,9 +113,7 @@ export const PlaybackTab = () => {
autosize autosize
defaultValue={mpvParameters} defaultValue={mpvParameters}
minRows={4} minRows={4}
placeholder={ placeholder={'(Add one per line):\n--gapless-audio=weak\n--prefetch-playlist=yes'}
'Default parameters (one per line):\n--gapless-audio=weak\n--prefetch-playlist=yes'
}
width={225} width={225}
onBlur={(e) => { onBlur={(e) => {
if (isElectron()) { if (isElectron()) {
@ -128,20 +124,23 @@ export const PlaybackTab = () => {
</Stack> </Stack>
), ),
description: ( description: (
<Text <Stack spacing={0}>
$noSelect <Text
$secondary $noSelect
size="sm" $secondary
>
Options to pass to the player{' '}
<a
href="https://mpv.io/manual/stable/#audio"
rel="noreferrer"
target="_blank"
> >
https://mpv.io/manual/stable/#audio Options to pass to the player
</a> </Text>
</Text> <Text>
<a
href="https://mpv.io/manual/stable/#audio"
rel="noreferrer"
target="_blank"
>
https://mpv.io/manual/stable/#audio
</a>
</Text>
</Stack>
), ),
isHidden: settings.type !== PlaybackType.LOCAL, isHidden: settings.type !== PlaybackType.LOCAL,
note: 'Restart required.', note: 'Restart required.',
@ -163,7 +162,7 @@ export const PlaybackTab = () => {
}, },
{ {
control: ( control: (
<SegmentedControl <Select
data={[ data={[
{ label: 'Normal', value: PlaybackStyle.GAPLESS }, { label: 'Normal', value: PlaybackStyle.GAPLESS },
{ label: 'Crossfade', value: PlaybackStyle.CROSSFADE }, { label: 'Crossfade', value: PlaybackStyle.CROSSFADE },
@ -346,7 +345,7 @@ export const PlaybackTab = () => {
const otherOptions = [ const otherOptions = [
{ {
control: ( control: (
<SegmentedControl <Select
data={[ data={[
{ label: 'Now', value: Play.NOW }, { label: 'Now', value: Play.NOW },
{ label: 'Next', value: Play.NEXT }, { label: 'Next', value: Play.NEXT },
@ -367,28 +366,6 @@ export const PlaybackTab = () => {
isHidden: false, isHidden: false,
title: 'Play button behavior', title: 'Play button behavior',
}, },
{
control: (
<Switch
aria-label="Toggle skip buttons"
defaultChecked={settings.skipButtons?.enabled}
onChange={(e) =>
setSettings({
player: {
...settings,
skipButtons: {
...settings.skipButtons,
enabled: e.currentTarget.checked,
},
},
})
}
/>
),
description: 'Show or hide the skip buttons on the playerbar',
isHidden: false,
title: 'Show skip buttons',
},
{ {
control: ( control: (
<Group> <Group>
@ -437,6 +414,28 @@ export const PlaybackTab = () => {
isHidden: false, isHidden: false,
title: 'Skip duration', title: 'Skip duration',
}, },
{
control: (
<Switch
aria-label="Toggle skip buttons"
defaultChecked={settings.skipButtons?.enabled}
onChange={(e) =>
setSettings({
player: {
...settings,
skipButtons: {
...settings.skipButtons,
enabled: e.currentTarget.checked,
},
},
})
}
/>
),
description: 'Show or hide the skip buttons on the playerbar',
isHidden: false,
title: 'Show skip buttons',
},
]; ];
return ( return (
@ -461,7 +460,7 @@ export const PlaybackTab = () => {
))} ))}
<Text <Text
$secondary $secondary
size="xs" size="sm"
> >
*The scrobble will be submitted if one or more of the above conditions is met *The scrobble will be submitted if one or more of the above conditions is met
</Text> </Text>

View File

@ -0,0 +1,48 @@
import { lazy } from 'react';
import { Box } from '@mantine/core';
import { Tabs } from '/@/renderer/components';
import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store';
const GeneralTab = lazy(() =>
import('/@/renderer/features/settings/components/general-tab').then((module) => ({
default: module.GeneralTab,
})),
);
const PlaybackTab = lazy(() =>
import('/@/renderer/features/settings/components/playback-tab').then((module) => ({
default: module.PlaybackTab,
})),
);
export const SettingsContent = () => {
const currentTab = useSettingsStore((state) => state.tab);
const { setSettings } = useSettingsStoreActions();
return (
<Box
h="100%"
p="1rem"
sx={{ overflow: 'scroll' }}
>
<Tabs
keepMounted={false}
orientation="horizontal"
value={currentTab}
variant="default"
onTabChange={(e) => e && setSettings({ tab: e })}
>
<Tabs.List>
<Tabs.Tab value="general">General</Tabs.Tab>
<Tabs.Tab value="playback">Playback</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="general">
<GeneralTab />
</Tabs.Panel>
<Tabs.Panel value="playback">
<PlaybackTab />
</Tabs.Panel>
</Tabs>
</Box>
);
};

View File

@ -0,0 +1,14 @@
import { RiSettings2Fill } from 'react-icons/ri';
import { PageHeader } from '/@/renderer/components';
import { LibraryHeaderBar } from '/@/renderer/features/shared';
export const SettingsHeader = () => {
return (
<PageHeader>
<LibraryHeaderBar>
<RiSettings2Fill size="2rem" />
<LibraryHeaderBar.Title>Settings</LibraryHeaderBar.Title>
</LibraryHeaderBar>
</PageHeader>
);
};

View File

@ -29,7 +29,7 @@ export const SettingsOptions = ({ title, description, control, note }: SettingsO
<Group> <Group>
<Text <Text
$noSelect $noSelect
size="sm" size="md"
> >
{title} {title}
</Text> </Text>

View File

@ -1,72 +0,0 @@
import { Box } from '@mantine/core';
import { Tabs } from '/@/renderer/components';
import type { Variants } from 'framer-motion';
import { AnimatePresence, motion } from 'framer-motion';
import { GeneralTab } from '/@/renderer/features/settings/components/general-tab';
import { PlaybackTab } from '/@/renderer/features/settings/components/playback-tab';
import { useSettingsStore, useSettingsStoreActions } from '/@/renderer/store/settings.store';
export const Settings = () => {
const currentTab = useSettingsStore((state) => state.tab);
const { setSettings } = useSettingsStoreActions();
const tabVariants: Variants = {
in: {
opacity: 1,
transition: {
duration: 0.3,
},
x: 0,
},
out: {
opacity: 0,
x: 50,
},
};
return (
<Box
m={5}
sx={{ height: '50vh', maxHeight: '500px', overflowX: 'hidden' }}
>
<AnimatePresence initial={false}>
<Tabs
keepMounted={false}
orientation="vertical"
styles={{
tab: {
fontSize: '1rem',
padding: '0.5rem 1rem',
},
}}
value={currentTab}
variant="pills"
onTabChange={(e) => e && setSettings({ tab: e })}
>
<Tabs.List>
<Tabs.Tab value="general">General</Tabs.Tab>
<Tabs.Tab value="playback">Playback</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="general">
<motion.div
animate="in"
initial="out"
variants={tabVariants}
>
<GeneralTab />
</motion.div>
</Tabs.Panel>
<Tabs.Panel value="playback">
<motion.div
animate="in"
initial="out"
variants={tabVariants}
>
<PlaybackTab />
</motion.div>
</Tabs.Panel>
</Tabs>
</AnimatePresence>
</Box>
);
};

View File

@ -0,0 +1,21 @@
import { Flex } from '@mantine/core';
import { SettingsContent } from '/@/renderer/features/settings/components/settings-content';
import { SettingsHeader } from '/@/renderer/features/settings/components/settings-header';
import { AnimatedPage } from '/@/renderer/features/shared';
const SettingsRoute = () => {
return (
<AnimatedPage>
<Flex
direction="column"
h="100%"
w="100%"
>
<SettingsHeader />
<SettingsContent />
</Flex>
</AnimatedPage>
);
};
export default SettingsRoute;

View File

@ -9,10 +9,10 @@ import {
RiWindowFill, RiWindowFill,
} from 'react-icons/ri'; } from 'react-icons/ri';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { DropdownMenu, Text } from '/@/renderer/components'; import { Link } from 'react-router-dom';
import { DropdownMenu } from '/@/renderer/components';
import { ServerList } from '/@/renderer/features/servers'; import { ServerList } from '/@/renderer/features/servers';
import { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form'; import { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form';
import { Settings } from '/@/renderer/features/settings';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useServerList, useAuthStoreActions } from '/@/renderer/store'; import { useCurrentServer, useServerList, useAuthStoreActions } from '/@/renderer/store';
import { ServerListItem, ServerType } from '/@/renderer/types'; import { ServerListItem, ServerType } from '/@/renderer/types';
@ -51,19 +51,6 @@ export const AppMenu = () => {
}); });
}; };
const handleSettingsModal = () => {
openModal({
children: <Settings />,
size: 'xl',
title: (
<Group position="center">
<RiSettings3Fill size={20} />
<Text>Settings</Text>
</Group>
),
});
};
const handleBrowserDevTools = () => { const handleBrowserDevTools = () => {
browser?.devtools(); browser?.devtools();
}; };
@ -100,8 +87,9 @@ export const AppMenu = () => {
Manage servers Manage servers
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Item <DropdownMenu.Item
component={Link}
icon={<RiSettings3Fill />} icon={<RiSettings3Fill />}
onClick={handleSettingsModal} to={AppRoute.SETTINGS}
> >
Settings Settings
</DropdownMenu.Item> </DropdownMenu.Item>

View File

@ -58,6 +58,8 @@ const AlbumDetailRoute = lazy(
() => import('/@/renderer/features/albums/routes/album-detail-route'), () => import('/@/renderer/features/albums/routes/album-detail-route'),
); );
const SettingsRoute = lazy(() => import('/@/renderer/features/settings/routes/settings-route'));
const RouteErrorBoundary = lazy( const RouteErrorBoundary = lazy(
() => import('/@/renderer/features/action-required/components/route-error-boundary'), () => import('/@/renderer/features/action-required/components/route-error-boundary'),
); );
@ -84,6 +86,11 @@ export const AppRouter = () => {
errorElement={<RouteErrorBoundary />} errorElement={<RouteErrorBoundary />}
path={AppRoute.HOME} path={AppRoute.HOME}
/> />
<Route
element={<SettingsRoute />}
errorElement={<RouteErrorBoundary />}
path={AppRoute.SETTINGS}
/>
<Route <Route
element={<NowPlayingRoute />} element={<NowPlayingRoute />}
errorElement={<RouteErrorBoundary />} errorElement={<RouteErrorBoundary />}

View File

@ -20,4 +20,5 @@ export enum AppRoute {
PLAYLISTS_DETAIL_SONGS = '/playlists/:playlistId/songs', PLAYLISTS_DETAIL_SONGS = '/playlists/:playlistId/songs',
SEARCH = '/search', SEARCH = '/search',
SERVERS = '/servers', SERVERS = '/servers',
SETTINGS = '/settings',
} }