mirror of
https://github.com/jeffvli/feishin.git
synced 2024-11-20 14:37:06 +01:00
Move settings to route instead of modal
This commit is contained in:
parent
0c13b09029
commit
bc5f1f13f0
@ -1,5 +1,5 @@
|
||||
import type { TabsProps as MantineTabsProps } from '@mantine/core';
|
||||
import { Tabs as MantineTabs } from '@mantine/core';
|
||||
import { Suspense } from 'react';
|
||||
import { TabsPanelProps, TabsProps as MantineTabsProps, Tabs as MantineTabs } from '@mantine/core';
|
||||
import styled from 'styled-components';
|
||||
|
||||
type TabsProps = MantineTabsProps;
|
||||
@ -12,11 +12,14 @@ const StyledTabs = styled(MantineTabs)`
|
||||
}
|
||||
|
||||
&.mantine-Tabs-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
background-color: var(--main-bg);
|
||||
}
|
||||
|
||||
& .mantine-Tabs-panel {
|
||||
padding: 0 1rem;
|
||||
padding: 1.5rem 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
@ -35,27 +38,26 @@ const StyledTabs = styled(MantineTabs)`
|
||||
button[data-active] {
|
||||
color: var(--btn-subtle-fg);
|
||||
background: none;
|
||||
box-shadow: 2px 0 0 var(--primary-color) inset;
|
||||
border-color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
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) => {
|
||||
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.Panel = StyledTabs.Panel;
|
||||
Tabs.Panel = Panel;
|
||||
Tabs.Tab = StyledTabs.Tab;
|
||||
|
@ -27,7 +27,7 @@ const SIDE_QUEUE_OPTIONS = [
|
||||
{ label: 'Floating', value: 'sideDrawerQueue' },
|
||||
];
|
||||
|
||||
const WINDOWBAR_OPTIONS = [
|
||||
const WINDOW_BAR_OPTIONS = [
|
||||
{ label: 'Web (hidden)', value: Platform.WEB },
|
||||
{ label: 'Windows', value: Platform.WINDOWS },
|
||||
{ label: 'macOS', value: Platform.MACOS },
|
||||
@ -41,7 +41,7 @@ export const GeneralTab = () => {
|
||||
{
|
||||
control: (
|
||||
<Select
|
||||
data={WINDOWBAR_OPTIONS}
|
||||
data={WINDOW_BAR_OPTIONS}
|
||||
disabled={!isElectron()}
|
||||
value={settings.windowBarStyle}
|
||||
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(),
|
||||
title: 'Windowbar style',
|
||||
title: 'Window bar style',
|
||||
},
|
||||
{
|
||||
control: (
|
||||
|
@ -3,7 +3,6 @@ import { Divider, Group, SelectItem, Stack } from '@mantine/core';
|
||||
import {
|
||||
FileInput,
|
||||
NumberInput,
|
||||
SegmentedControl,
|
||||
Select,
|
||||
Slider,
|
||||
Switch,
|
||||
@ -69,7 +68,7 @@ export const PlaybackTab = () => {
|
||||
const playerOptions = [
|
||||
{
|
||||
control: (
|
||||
<SegmentedControl
|
||||
<Select
|
||||
data={[
|
||||
{
|
||||
disabled: !isElectron(),
|
||||
@ -98,7 +97,6 @@ export const PlaybackTab = () => {
|
||||
control: (
|
||||
<FileInput
|
||||
placeholder={mpvPath}
|
||||
size="sm"
|
||||
width={225}
|
||||
onChange={handleSetMpvPath}
|
||||
/>
|
||||
@ -115,9 +113,7 @@ export const PlaybackTab = () => {
|
||||
autosize
|
||||
defaultValue={mpvParameters}
|
||||
minRows={4}
|
||||
placeholder={
|
||||
'Default parameters (one per line):\n--gapless-audio=weak\n--prefetch-playlist=yes'
|
||||
}
|
||||
placeholder={'(Add one per line):\n--gapless-audio=weak\n--prefetch-playlist=yes'}
|
||||
width={225}
|
||||
onBlur={(e) => {
|
||||
if (isElectron()) {
|
||||
@ -128,20 +124,23 @@ export const PlaybackTab = () => {
|
||||
</Stack>
|
||||
),
|
||||
description: (
|
||||
<Text
|
||||
$noSelect
|
||||
$secondary
|
||||
size="sm"
|
||||
>
|
||||
Options to pass to the player{' '}
|
||||
<a
|
||||
href="https://mpv.io/manual/stable/#audio"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
<Stack spacing={0}>
|
||||
<Text
|
||||
$noSelect
|
||||
$secondary
|
||||
>
|
||||
https://mpv.io/manual/stable/#audio
|
||||
</a>
|
||||
</Text>
|
||||
Options to pass to the player
|
||||
</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,
|
||||
note: 'Restart required.',
|
||||
@ -163,7 +162,7 @@ export const PlaybackTab = () => {
|
||||
},
|
||||
{
|
||||
control: (
|
||||
<SegmentedControl
|
||||
<Select
|
||||
data={[
|
||||
{ label: 'Normal', value: PlaybackStyle.GAPLESS },
|
||||
{ label: 'Crossfade', value: PlaybackStyle.CROSSFADE },
|
||||
@ -346,7 +345,7 @@ export const PlaybackTab = () => {
|
||||
const otherOptions = [
|
||||
{
|
||||
control: (
|
||||
<SegmentedControl
|
||||
<Select
|
||||
data={[
|
||||
{ label: 'Now', value: Play.NOW },
|
||||
{ label: 'Next', value: Play.NEXT },
|
||||
@ -367,28 +366,6 @@ export const PlaybackTab = () => {
|
||||
isHidden: false,
|
||||
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: (
|
||||
<Group>
|
||||
@ -437,6 +414,28 @@ export const PlaybackTab = () => {
|
||||
isHidden: false,
|
||||
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 (
|
||||
@ -461,7 +460,7 @@ export const PlaybackTab = () => {
|
||||
))}
|
||||
<Text
|
||||
$secondary
|
||||
size="xs"
|
||||
size="sm"
|
||||
>
|
||||
*The scrobble will be submitted if one or more of the above conditions is met
|
||||
</Text>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -29,7 +29,7 @@ export const SettingsOptions = ({ title, description, control, note }: SettingsO
|
||||
<Group>
|
||||
<Text
|
||||
$noSelect
|
||||
size="sm"
|
||||
size="md"
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
21
src/renderer/features/settings/routes/settings-route.tsx
Normal file
21
src/renderer/features/settings/routes/settings-route.tsx
Normal 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;
|
@ -9,10 +9,10 @@ import {
|
||||
RiWindowFill,
|
||||
} from 'react-icons/ri';
|
||||
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 { EditServerForm } from '/@/renderer/features/servers/components/edit-server-form';
|
||||
import { Settings } from '/@/renderer/features/settings';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer, useServerList, useAuthStoreActions } from '/@/renderer/store';
|
||||
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 = () => {
|
||||
browser?.devtools();
|
||||
};
|
||||
@ -100,8 +87,9 @@ export const AppMenu = () => {
|
||||
Manage servers
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item
|
||||
component={Link}
|
||||
icon={<RiSettings3Fill />}
|
||||
onClick={handleSettingsModal}
|
||||
to={AppRoute.SETTINGS}
|
||||
>
|
||||
Settings
|
||||
</DropdownMenu.Item>
|
||||
|
@ -58,6 +58,8 @@ const AlbumDetailRoute = lazy(
|
||||
() => import('/@/renderer/features/albums/routes/album-detail-route'),
|
||||
);
|
||||
|
||||
const SettingsRoute = lazy(() => import('/@/renderer/features/settings/routes/settings-route'));
|
||||
|
||||
const RouteErrorBoundary = lazy(
|
||||
() => import('/@/renderer/features/action-required/components/route-error-boundary'),
|
||||
);
|
||||
@ -84,6 +86,11 @@ export const AppRouter = () => {
|
||||
errorElement={<RouteErrorBoundary />}
|
||||
path={AppRoute.HOME}
|
||||
/>
|
||||
<Route
|
||||
element={<SettingsRoute />}
|
||||
errorElement={<RouteErrorBoundary />}
|
||||
path={AppRoute.SETTINGS}
|
||||
/>
|
||||
<Route
|
||||
element={<NowPlayingRoute />}
|
||||
errorElement={<RouteErrorBoundary />}
|
||||
|
@ -20,4 +20,5 @@ export enum AppRoute {
|
||||
PLAYLISTS_DETAIL_SONGS = '/playlists/:playlistId/songs',
|
||||
SEARCH = '/search',
|
||||
SERVERS = '/servers',
|
||||
SETTINGS = '/settings',
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user