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 { 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;
|
||||||
|
@ -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: (
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
<Group>
|
||||||
<Text
|
<Text
|
||||||
$noSelect
|
$noSelect
|
||||||
size="sm"
|
size="md"
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</Text>
|
</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,
|
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>
|
||||||
|
@ -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 />}
|
||||||
|
@ -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',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user