Split up main content route component
This commit is contained in:
parent
fa0a21a021
commit
e7c7eb3ec0
7 changed files with 409 additions and 286 deletions
33
src/renderer/features/shared/components/resize-handle.tsx
Normal file
33
src/renderer/features/shared/components/resize-handle.tsx
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const ResizeHandle = styled.div<{
|
||||||
|
isResizing: boolean;
|
||||||
|
placement: 'top' | 'left' | 'bottom' | 'right';
|
||||||
|
}>`
|
||||||
|
position: absolute;
|
||||||
|
top: ${(props) => props.placement === 'top' && 0};
|
||||||
|
right: ${(props) => props.placement === 'right' && 0};
|
||||||
|
bottom: ${(props) => props.placement === 'bottom' && 0};
|
||||||
|
left: ${(props) => props.placement === 'left' && 0};
|
||||||
|
z-index: 90;
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: ew-resize;
|
||||||
|
opacity: ${(props) => (props.isResizing ? 1 : 0)};
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
top: ${(props) => props.placement === 'top' && 0};
|
||||||
|
right: ${(props) => props.placement === 'right' && 0};
|
||||||
|
bottom: ${(props) => props.placement === 'bottom' && 0};
|
||||||
|
left: ${(props) => props.placement === 'left' && 0};
|
||||||
|
width: 1px;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--sidebar-handle-bg);
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
`;
|
|
@ -8,3 +8,4 @@ export * from './mutations/create-favorite-mutation';
|
||||||
export * from './mutations/delete-favorite-mutation';
|
export * from './mutations/delete-favorite-mutation';
|
||||||
export * from './mutations/set-rating-mutation';
|
export * from './mutations/set-rating-mutation';
|
||||||
export * from './components/filter-bar';
|
export * from './components/filter-bar';
|
||||||
|
export * from './components/resize-handle';
|
||||||
|
|
18
src/renderer/layouts/default-layout/full-screen-overlay.tsx
Normal file
18
src/renderer/layouts/default-layout/full-screen-overlay.tsx
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { AnimatePresence } from 'framer-motion';
|
||||||
|
import { FullScreenPlayer } from '/@/renderer/features/player/components/full-screen-player';
|
||||||
|
import { useFullScreenPlayerStore } from '/@/renderer/store';
|
||||||
|
|
||||||
|
export const FullScreenOverlay = () => {
|
||||||
|
const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AnimatePresence
|
||||||
|
initial={false}
|
||||||
|
mode="wait"
|
||||||
|
>
|
||||||
|
{isFullScreenPlayerExpanded && <FullScreenPlayer />}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
38
src/renderer/layouts/default-layout/left-sidebar.tsx
Normal file
38
src/renderer/layouts/default-layout/left-sidebar.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ResizeHandle } from '/@/renderer/features/shared';
|
||||||
|
import { CollapsedSidebar } from '/@/renderer/features/sidebar/components/collapsed-sidebar';
|
||||||
|
import { Sidebar } from '/@/renderer/features/sidebar/components/sidebar';
|
||||||
|
import { useSidebarStore } from '/@/renderer/store';
|
||||||
|
|
||||||
|
const SidebarContainer = styled.aside`
|
||||||
|
position: relative;
|
||||||
|
grid-area: sidebar;
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
border-right: var(--sidebar-border);
|
||||||
|
`;
|
||||||
|
|
||||||
|
interface LeftSidebarProps {
|
||||||
|
isResizing: boolean;
|
||||||
|
startResizing: (direction: 'left' | 'right') => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LeftSidebar = ({ isResizing, startResizing }: LeftSidebarProps) => {
|
||||||
|
const sidebarRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const { collapsed } = useSidebarStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarContainer id="sidebar">
|
||||||
|
<ResizeHandle
|
||||||
|
ref={sidebarRef}
|
||||||
|
isResizing={isResizing}
|
||||||
|
placement="right"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
startResizing('left');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{collapsed ? <CollapsedSidebar /> : <Sidebar />}
|
||||||
|
</SidebarContainer>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,19 +1,20 @@
|
||||||
import { useDisclosure, useTimeout } from '@mantine/hooks';
|
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { motion, AnimatePresence, Variants } from 'framer-motion';
|
|
||||||
import { throttle } from 'lodash';
|
import { throttle } from 'lodash';
|
||||||
import { Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
||||||
import { TbArrowBarLeft } from 'react-icons/tb';
|
|
||||||
import { Outlet, useLocation } from 'react-router';
|
import { Outlet, useLocation } from 'react-router';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { DrawerPlayQueue, SidebarPlayQueue } from '/@/renderer/features/now-playing';
|
|
||||||
import { FullScreenPlayer } from '/@/renderer/features/player/components/full-screen-player';
|
|
||||||
import { Sidebar } from '/@/renderer/features/sidebar/components/sidebar';
|
|
||||||
import { CollapsedSidebar } from '../../features/sidebar/components/collapsed-sidebar';
|
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useAppStore, useAppStoreActions, useFullScreenPlayerStore } from '/@/renderer/store';
|
import { useAppStoreActions, useSidebarStore } from '/@/renderer/store';
|
||||||
import { useWindowSettings, useGeneralSettings } from '/@/renderer/store/settings.store';
|
import { useGeneralSettings } from '/@/renderer/store/settings.store';
|
||||||
import { Platform } from '/@/renderer/types';
|
|
||||||
import { constrainSidebarWidth, constrainRightSidebarWidth } from '/@/renderer/utils';
|
import { constrainSidebarWidth, constrainRightSidebarWidth } from '/@/renderer/utils';
|
||||||
|
import { LeftSidebar } from '/@/renderer/layouts/default-layout/left-sidebar';
|
||||||
|
import { FullScreenOverlay } from '/@/renderer/layouts/default-layout/full-screen-overlay';
|
||||||
|
import { RightSidebar } from '/@/renderer/layouts/default-layout/right-sidebar';
|
||||||
|
|
||||||
|
const SideDrawerQueue = lazy(() =>
|
||||||
|
import('/@/renderer/layouts/default-layout/side-drawer-queue').then((module) => ({
|
||||||
|
default: module.SideDrawerQueue,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
const MINIMUM_SIDEBAR_WIDTH = 260;
|
const MINIMUM_SIDEBAR_WIDTH = 260;
|
||||||
|
|
||||||
|
@ -37,171 +38,17 @@ const MainContentContainer = styled.div<{
|
||||||
background: var(--main-bg);
|
background: var(--main-bg);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SidebarContainer = styled.aside`
|
|
||||||
position: relative;
|
|
||||||
grid-area: sidebar;
|
|
||||||
background: var(--sidebar-bg);
|
|
||||||
border-right: var(--sidebar-border);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const RightSidebarContainer = styled(motion.aside)`
|
|
||||||
position: relative;
|
|
||||||
grid-area: right-sidebar;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--sidebar-bg);
|
|
||||||
border-left: var(--sidebar-border);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ResizeHandle = styled.div<{
|
|
||||||
isResizing: boolean;
|
|
||||||
placement: 'top' | 'left' | 'bottom' | 'right';
|
|
||||||
}>`
|
|
||||||
position: absolute;
|
|
||||||
top: ${(props) => props.placement === 'top' && 0};
|
|
||||||
right: ${(props) => props.placement === 'right' && 0};
|
|
||||||
bottom: ${(props) => props.placement === 'bottom' && 0};
|
|
||||||
left: ${(props) => props.placement === 'left' && 0};
|
|
||||||
z-index: 90;
|
|
||||||
width: 4px;
|
|
||||||
height: 100%;
|
|
||||||
cursor: ew-resize;
|
|
||||||
opacity: ${(props) => (props.isResizing ? 1 : 0)};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
position: absolute;
|
|
||||||
top: ${(props) => props.placement === 'top' && 0};
|
|
||||||
right: ${(props) => props.placement === 'right' && 0};
|
|
||||||
bottom: ${(props) => props.placement === 'bottom' && 0};
|
|
||||||
left: ${(props) => props.placement === 'left' && 0};
|
|
||||||
width: 1px;
|
|
||||||
height: 100%;
|
|
||||||
background-color: var(--sidebar-handle-bg);
|
|
||||||
content: '';
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QueueDrawer = styled(motion.div)`
|
|
||||||
background: var(--main-bg);
|
|
||||||
border: 3px solid var(--generic-border-color);
|
|
||||||
border-radius: 10px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const QueueDrawerArea = styled(motion.div)`
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
right: 25px;
|
|
||||||
z-index: 100;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 20px;
|
|
||||||
height: 30px;
|
|
||||||
user-select: none;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const queueDrawerVariants: Variants = {
|
|
||||||
closed: (windowBarStyle) => ({
|
|
||||||
height:
|
|
||||||
windowBarStyle === Platform.WINDOWS || Platform.MACOS
|
|
||||||
? 'calc(100vh - 205px)'
|
|
||||||
: 'calc(100vh - 175px)',
|
|
||||||
position: 'absolute',
|
|
||||||
right: 0,
|
|
||||||
top: '75px',
|
|
||||||
transition: {
|
|
||||||
duration: 0.4,
|
|
||||||
ease: 'anticipate',
|
|
||||||
},
|
|
||||||
width: '450px',
|
|
||||||
x: '50vw',
|
|
||||||
}),
|
|
||||||
open: (windowBarStyle) => ({
|
|
||||||
boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.8)',
|
|
||||||
height:
|
|
||||||
windowBarStyle === Platform.WINDOWS || Platform.MACOS
|
|
||||||
? 'calc(100vh - 205px)'
|
|
||||||
: 'calc(100vh - 175px)',
|
|
||||||
position: 'absolute',
|
|
||||||
right: '20px',
|
|
||||||
top: '75px',
|
|
||||||
transition: {
|
|
||||||
damping: 10,
|
|
||||||
delay: 0,
|
|
||||||
duration: 0.4,
|
|
||||||
ease: 'anticipate',
|
|
||||||
mass: 0.5,
|
|
||||||
},
|
|
||||||
width: '450px',
|
|
||||||
x: 0,
|
|
||||||
zIndex: 120,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const queueDrawerButtonVariants: Variants = {
|
|
||||||
hidden: {
|
|
||||||
opacity: 0,
|
|
||||||
transition: { duration: 0.2 },
|
|
||||||
x: 100,
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
opacity: 0.5,
|
|
||||||
transition: { duration: 0.1, ease: 'anticipate' },
|
|
||||||
x: 0,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const queueSidebarVariants: Variants = {
|
|
||||||
closed: (rightWidth) => ({
|
|
||||||
transition: { duration: 0.5 },
|
|
||||||
width: rightWidth,
|
|
||||||
x: 1000,
|
|
||||||
zIndex: 120,
|
|
||||||
}),
|
|
||||||
open: (rightWidth) => ({
|
|
||||||
transition: {
|
|
||||||
duration: 0.5,
|
|
||||||
ease: 'anticipate',
|
|
||||||
},
|
|
||||||
width: rightWidth,
|
|
||||||
x: 0,
|
|
||||||
zIndex: 120,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MainContent = ({ shell }: { shell?: boolean }) => {
|
export const MainContent = ({ shell }: { shell?: boolean }) => {
|
||||||
const sidebar = useAppStore((state) => state.sidebar);
|
|
||||||
const { setSideBar } = useAppStoreActions();
|
|
||||||
const [drawer, drawerHandler] = useDisclosure(false);
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { sideQueueType, showQueueDrawerButton } = useGeneralSettings();
|
const { collapsed, leftWidth, rightWidth, rightExpanded } = useSidebarStore();
|
||||||
const { windowBarStyle } = useWindowSettings();
|
const { setSideBar } = useAppStoreActions();
|
||||||
|
const { sideQueueType } = useGeneralSettings();
|
||||||
const sidebarRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const rightSidebarRef = useRef<HTMLDivElement | null>(null);
|
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const [isResizingRight, setIsResizingRight] = useState(false);
|
const [isResizingRight, setIsResizingRight] = useState(false);
|
||||||
|
const { showQueueDrawerButton } = useGeneralSettings();
|
||||||
|
|
||||||
const drawerTimeout = useTimeout(() => drawerHandler.open(), 500);
|
const showSideQueue = rightExpanded && location.pathname !== AppRoute.NOW_PLAYING;
|
||||||
|
const rightSidebarRef = useRef<HTMLDivElement | null>(null);
|
||||||
const handleEnterDrawerButton = useCallback(() => {
|
|
||||||
drawerTimeout.start();
|
|
||||||
}, [drawerTimeout]);
|
|
||||||
|
|
||||||
const handleLeaveDrawerButton = useCallback(() => {
|
|
||||||
drawerTimeout.clear();
|
|
||||||
}, [drawerTimeout]);
|
|
||||||
|
|
||||||
const isQueueDrawerButtonVisible =
|
|
||||||
showQueueDrawerButton &&
|
|
||||||
!sidebar.rightExpanded &&
|
|
||||||
!drawer &&
|
|
||||||
location.pathname !== AppRoute.NOW_PLAYING;
|
|
||||||
|
|
||||||
const showSideQueue = sidebar.rightExpanded && location.pathname !== AppRoute.NOW_PLAYING;
|
|
||||||
const { expanded: isFullScreenPlayerExpanded } = useFullScreenPlayerStore();
|
|
||||||
|
|
||||||
const startResizing = useCallback((position: 'left' | 'right') => {
|
const startResizing = useCallback((position: 'left' | 'right') => {
|
||||||
if (position === 'left') return setIsResizing(true);
|
if (position === 'left') return setIsResizing(true);
|
||||||
|
@ -225,13 +72,13 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
|
||||||
setSideBar({ collapsed: false, leftWidth: constrainedWidth });
|
setSideBar({ collapsed: false, leftWidth: constrainedWidth });
|
||||||
}
|
}
|
||||||
} else if (isResizingRight) {
|
} else if (isResizingRight) {
|
||||||
const start = Number(sidebar.rightWidth.split('px')[0]);
|
const start = Number(rightWidth.split('px')[0]);
|
||||||
const { left } = rightSidebarRef!.current!.getBoundingClientRect();
|
const { left } = rightSidebarRef!.current!.getBoundingClientRect();
|
||||||
const width = `${constrainRightSidebarWidth(start + left - mouseMoveEvent.clientX)}px`;
|
const width = `${constrainRightSidebarWidth(start + left - mouseMoveEvent.clientX)}px`;
|
||||||
setSideBar({ rightWidth: width });
|
setSideBar({ rightWidth: width });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[isResizing, isResizingRight, setSideBar, sidebar.rightWidth],
|
[isResizing, isResizingRight, setSideBar, rightWidth],
|
||||||
);
|
);
|
||||||
|
|
||||||
const throttledResize = useMemo(() => throttle(resize, 50), [resize]);
|
const throttledResize = useMemo(() => throttle(resize, 50), [resize]);
|
||||||
|
@ -248,123 +95,26 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
|
||||||
return (
|
return (
|
||||||
<MainContentContainer
|
<MainContentContainer
|
||||||
id="main-content"
|
id="main-content"
|
||||||
leftSidebarWidth={sidebar.leftWidth}
|
leftSidebarWidth={leftWidth}
|
||||||
rightExpanded={showSideQueue && sideQueueType === 'sideQueue'}
|
rightExpanded={showSideQueue && sideQueueType === 'sideQueue'}
|
||||||
rightSidebarWidth={sidebar.rightWidth}
|
rightSidebarWidth={rightWidth}
|
||||||
shell={shell}
|
shell={shell}
|
||||||
sidebarCollapsed={sidebar.collapsed}
|
sidebarCollapsed={collapsed}
|
||||||
>
|
>
|
||||||
{!shell && (
|
{!shell && (
|
||||||
<>
|
<Suspense fallback={<></>}>
|
||||||
<AnimatePresence
|
{showQueueDrawerButton && <SideDrawerQueue />}
|
||||||
initial={false}
|
<FullScreenOverlay />
|
||||||
mode="wait"
|
<LeftSidebar
|
||||||
>
|
isResizing={isResizing}
|
||||||
{isFullScreenPlayerExpanded && <FullScreenPlayer />}
|
startResizing={startResizing}
|
||||||
</AnimatePresence>
|
/>
|
||||||
<SidebarContainer id="sidebar">
|
<RightSidebar
|
||||||
<ResizeHandle
|
ref={rightSidebarRef}
|
||||||
ref={sidebarRef}
|
isResizing={isResizingRight}
|
||||||
isResizing={isResizing}
|
startResizing={startResizing}
|
||||||
placement="right"
|
/>
|
||||||
onMouseDown={(e) => {
|
</Suspense>
|
||||||
e.preventDefault();
|
|
||||||
startResizing('left');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{sidebar.collapsed ? <CollapsedSidebar /> : <Sidebar />}
|
|
||||||
</SidebarContainer>
|
|
||||||
<AnimatePresence
|
|
||||||
initial={false}
|
|
||||||
mode="wait"
|
|
||||||
>
|
|
||||||
{isQueueDrawerButtonVisible && (
|
|
||||||
<QueueDrawerArea
|
|
||||||
key="queue-drawer-button"
|
|
||||||
animate="visible"
|
|
||||||
exit="hidden"
|
|
||||||
initial="hidden"
|
|
||||||
variants={queueDrawerButtonVariants}
|
|
||||||
whileHover={{ opacity: 1, scale: 2, transition: { duration: 0.5 } }}
|
|
||||||
onMouseEnter={handleEnterDrawerButton}
|
|
||||||
onMouseLeave={handleLeaveDrawerButton}
|
|
||||||
>
|
|
||||||
<TbArrowBarLeft size={12} />
|
|
||||||
</QueueDrawerArea>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{drawer && (
|
|
||||||
<QueueDrawer
|
|
||||||
key="queue-drawer"
|
|
||||||
animate="open"
|
|
||||||
exit="closed"
|
|
||||||
initial="closed"
|
|
||||||
variants={queueDrawerVariants}
|
|
||||||
onMouseLeave={() => {
|
|
||||||
// The drawer will close due to the delay when setting isReorderingQueue
|
|
||||||
setTimeout(() => {
|
|
||||||
if (useAppStore.getState().isReorderingQueue) return;
|
|
||||||
drawerHandler.close();
|
|
||||||
}, 50);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DrawerPlayQueue />
|
|
||||||
</QueueDrawer>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
<AnimatePresence
|
|
||||||
key="queue-sidebar"
|
|
||||||
presenceAffectsLayout
|
|
||||||
initial={false}
|
|
||||||
mode="wait"
|
|
||||||
>
|
|
||||||
{showSideQueue && (
|
|
||||||
<>
|
|
||||||
{sideQueueType === 'sideQueue' ? (
|
|
||||||
<RightSidebarContainer
|
|
||||||
key="queue-sidebar"
|
|
||||||
animate="open"
|
|
||||||
custom={sidebar.rightWidth}
|
|
||||||
exit="closed"
|
|
||||||
id="sidebar-queue"
|
|
||||||
initial="closed"
|
|
||||||
variants={queueSidebarVariants}
|
|
||||||
>
|
|
||||||
<ResizeHandle
|
|
||||||
ref={rightSidebarRef}
|
|
||||||
isResizing={isResizingRight}
|
|
||||||
placement="left"
|
|
||||||
onMouseDown={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
startResizing('right');
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<SidebarPlayQueue />
|
|
||||||
</RightSidebarContainer>
|
|
||||||
) : (
|
|
||||||
<QueueDrawer
|
|
||||||
key="queue-drawer"
|
|
||||||
animate="open"
|
|
||||||
custom={windowBarStyle}
|
|
||||||
exit="closed"
|
|
||||||
id="drawer-queue"
|
|
||||||
initial="closed"
|
|
||||||
variants={queueDrawerVariants}
|
|
||||||
onMouseLeave={() => {
|
|
||||||
// The drawer will close due to the delay when setting isReorderingQueue
|
|
||||||
setTimeout(() => {
|
|
||||||
if (useAppStore.getState().isReorderingQueue) return;
|
|
||||||
drawerHandler.close();
|
|
||||||
}, 50);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DrawerPlayQueue />
|
|
||||||
</QueueDrawer>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
<Suspense fallback={<></>}>
|
<Suspense fallback={<></>}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|
142
src/renderer/layouts/default-layout/right-sidebar.tsx
Normal file
142
src/renderer/layouts/default-layout/right-sidebar.tsx
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
||||||
|
import { forwardRef, Ref } from 'react';
|
||||||
|
import { useLocation } from 'react-router';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { SidebarPlayQueue, DrawerPlayQueue } from '/@/renderer/features/now-playing';
|
||||||
|
import { ResizeHandle } from '/@/renderer/features/shared';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useGeneralSettings, useSidebarStore, useWindowSettings } from '/@/renderer/store';
|
||||||
|
import { Platform } from '/@/renderer/types';
|
||||||
|
|
||||||
|
const RightSidebarContainer = styled(motion.aside)`
|
||||||
|
position: relative;
|
||||||
|
grid-area: right-sidebar;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--sidebar-bg);
|
||||||
|
border-left: var(--sidebar-border);
|
||||||
|
`;
|
||||||
|
|
||||||
|
const queueSidebarVariants: Variants = {
|
||||||
|
closed: (rightWidth) => ({
|
||||||
|
transition: { duration: 0.5 },
|
||||||
|
width: rightWidth,
|
||||||
|
x: 1000,
|
||||||
|
zIndex: 120,
|
||||||
|
}),
|
||||||
|
open: (rightWidth) => ({
|
||||||
|
transition: {
|
||||||
|
duration: 0.5,
|
||||||
|
ease: 'anticipate',
|
||||||
|
},
|
||||||
|
width: rightWidth,
|
||||||
|
x: 0,
|
||||||
|
zIndex: 120,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const QueueDrawer = styled(motion.div)`
|
||||||
|
background: var(--main-bg);
|
||||||
|
border: 3px solid var(--generic-border-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const queueDrawerVariants: Variants = {
|
||||||
|
closed: (windowBarStyle) => ({
|
||||||
|
height:
|
||||||
|
windowBarStyle === Platform.WINDOWS || Platform.MACOS
|
||||||
|
? 'calc(100vh - 205px)'
|
||||||
|
: 'calc(100vh - 175px)',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: '75px',
|
||||||
|
transition: {
|
||||||
|
duration: 0.4,
|
||||||
|
ease: 'anticipate',
|
||||||
|
},
|
||||||
|
width: '450px',
|
||||||
|
x: '50vw',
|
||||||
|
}),
|
||||||
|
open: (windowBarStyle) => ({
|
||||||
|
boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.8)',
|
||||||
|
height:
|
||||||
|
windowBarStyle === Platform.WINDOWS || Platform.MACOS
|
||||||
|
? 'calc(100vh - 205px)'
|
||||||
|
: 'calc(100vh - 175px)',
|
||||||
|
position: 'absolute',
|
||||||
|
right: '20px',
|
||||||
|
top: '75px',
|
||||||
|
transition: {
|
||||||
|
damping: 10,
|
||||||
|
delay: 0,
|
||||||
|
duration: 0.4,
|
||||||
|
ease: 'anticipate',
|
||||||
|
mass: 0.5,
|
||||||
|
},
|
||||||
|
width: '450px',
|
||||||
|
x: 0,
|
||||||
|
zIndex: 120,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
interface RightSidebarProps {
|
||||||
|
isResizing: boolean;
|
||||||
|
startResizing: (direction: 'left' | 'right') => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RightSidebar = forwardRef(
|
||||||
|
({ isResizing: isResizingRight, startResizing }: RightSidebarProps, ref: Ref<HTMLDivElement>) => {
|
||||||
|
const { windowBarStyle } = useWindowSettings();
|
||||||
|
const { rightWidth, rightExpanded } = useSidebarStore();
|
||||||
|
const { sideQueueType } = useGeneralSettings();
|
||||||
|
const location = useLocation();
|
||||||
|
const showSideQueue = rightExpanded && location.pathname !== AppRoute.NOW_PLAYING;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence
|
||||||
|
key="queue-sidebar"
|
||||||
|
presenceAffectsLayout
|
||||||
|
initial={false}
|
||||||
|
mode="sync"
|
||||||
|
>
|
||||||
|
{showSideQueue && (
|
||||||
|
<>
|
||||||
|
{sideQueueType === 'sideQueue' ? (
|
||||||
|
<RightSidebarContainer
|
||||||
|
key="queue-sidebar"
|
||||||
|
animate="open"
|
||||||
|
custom={rightWidth}
|
||||||
|
exit="closed"
|
||||||
|
id="sidebar-queue"
|
||||||
|
initial="closed"
|
||||||
|
variants={queueSidebarVariants}
|
||||||
|
>
|
||||||
|
<ResizeHandle
|
||||||
|
ref={ref}
|
||||||
|
isResizing={isResizingRight}
|
||||||
|
placement="left"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
startResizing('right');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SidebarPlayQueue />
|
||||||
|
</RightSidebarContainer>
|
||||||
|
) : (
|
||||||
|
<QueueDrawer
|
||||||
|
key="queue-drawer"
|
||||||
|
animate="open"
|
||||||
|
custom={windowBarStyle}
|
||||||
|
exit="closed"
|
||||||
|
id="drawer-queue"
|
||||||
|
initial="closed"
|
||||||
|
variants={queueDrawerVariants}
|
||||||
|
>
|
||||||
|
<DrawerPlayQueue />
|
||||||
|
</QueueDrawer>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
141
src/renderer/layouts/default-layout/side-drawer-queue.tsx
Normal file
141
src/renderer/layouts/default-layout/side-drawer-queue.tsx
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import { useDisclosure, useTimeout } from '@mantine/hooks';
|
||||||
|
import { AnimatePresence, motion, Variants } from 'framer-motion';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { TbArrowBarLeft } from 'react-icons/tb';
|
||||||
|
import { useLocation } from 'react-router';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { DrawerPlayQueue } from '/@/renderer/features/now-playing';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { useAppStore, useSidebarStore } from '/@/renderer/store';
|
||||||
|
import { Platform } from '/@/renderer/types';
|
||||||
|
|
||||||
|
const QueueDrawerArea = styled(motion.div)`
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 25px;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 30px;
|
||||||
|
user-select: none;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const QueueDrawer = styled(motion.div)`
|
||||||
|
background: var(--main-bg);
|
||||||
|
border: 3px solid var(--generic-border-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const queueDrawerVariants: Variants = {
|
||||||
|
closed: (windowBarStyle) => ({
|
||||||
|
height:
|
||||||
|
windowBarStyle === Platform.WINDOWS || Platform.MACOS
|
||||||
|
? 'calc(100vh - 205px)'
|
||||||
|
: 'calc(100vh - 175px)',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: '75px',
|
||||||
|
transition: {
|
||||||
|
duration: 0.4,
|
||||||
|
ease: 'anticipate',
|
||||||
|
},
|
||||||
|
width: '450px',
|
||||||
|
x: '50vw',
|
||||||
|
}),
|
||||||
|
open: (windowBarStyle) => ({
|
||||||
|
boxShadow: '0px 0px 10px 0px rgba(0, 0, 0, 0.8)',
|
||||||
|
height:
|
||||||
|
windowBarStyle === Platform.WINDOWS || Platform.MACOS
|
||||||
|
? 'calc(100vh - 205px)'
|
||||||
|
: 'calc(100vh - 175px)',
|
||||||
|
position: 'absolute',
|
||||||
|
right: '20px',
|
||||||
|
top: '75px',
|
||||||
|
transition: {
|
||||||
|
damping: 10,
|
||||||
|
delay: 0,
|
||||||
|
duration: 0.4,
|
||||||
|
ease: 'anticipate',
|
||||||
|
mass: 0.5,
|
||||||
|
},
|
||||||
|
width: '450px',
|
||||||
|
x: 0,
|
||||||
|
zIndex: 120,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const queueDrawerButtonVariants: Variants = {
|
||||||
|
hidden: {
|
||||||
|
opacity: 0,
|
||||||
|
transition: { duration: 0.2 },
|
||||||
|
x: 100,
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
opacity: 0.5,
|
||||||
|
transition: { duration: 0.1, ease: 'anticipate' },
|
||||||
|
x: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SideDrawerQueue = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const [drawer, drawerHandler] = useDisclosure(false);
|
||||||
|
const { rightExpanded } = useSidebarStore();
|
||||||
|
|
||||||
|
const drawerTimeout = useTimeout(() => drawerHandler.open(), 500);
|
||||||
|
|
||||||
|
const handleEnterDrawerButton = useCallback(() => {
|
||||||
|
drawerTimeout.start();
|
||||||
|
}, [drawerTimeout]);
|
||||||
|
|
||||||
|
const handleLeaveDrawerButton = useCallback(() => {
|
||||||
|
drawerTimeout.clear();
|
||||||
|
}, [drawerTimeout]);
|
||||||
|
|
||||||
|
const isQueueDrawerButtonVisible =
|
||||||
|
!rightExpanded && !drawer && location.pathname !== AppRoute.NOW_PLAYING;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AnimatePresence
|
||||||
|
initial={false}
|
||||||
|
mode="wait"
|
||||||
|
>
|
||||||
|
{isQueueDrawerButtonVisible && (
|
||||||
|
<QueueDrawerArea
|
||||||
|
key="queue-drawer-button"
|
||||||
|
animate="visible"
|
||||||
|
exit="hidden"
|
||||||
|
initial="hidden"
|
||||||
|
variants={queueDrawerButtonVariants}
|
||||||
|
whileHover={{ opacity: 1, scale: 2, transition: { duration: 0.5 } }}
|
||||||
|
onMouseEnter={handleEnterDrawerButton}
|
||||||
|
onMouseLeave={handleLeaveDrawerButton}
|
||||||
|
>
|
||||||
|
<TbArrowBarLeft size={12} />
|
||||||
|
</QueueDrawerArea>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{drawer && (
|
||||||
|
<QueueDrawer
|
||||||
|
key="queue-drawer"
|
||||||
|
animate="open"
|
||||||
|
exit="closed"
|
||||||
|
initial="closed"
|
||||||
|
variants={queueDrawerVariants}
|
||||||
|
onMouseLeave={() => {
|
||||||
|
// The drawer will close due to the delay when setting isReorderingQueue
|
||||||
|
setTimeout(() => {
|
||||||
|
if (useAppStore.getState().isReorderingQueue) return;
|
||||||
|
drawerHandler.close();
|
||||||
|
}, 50);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DrawerPlayQueue />
|
||||||
|
</QueueDrawer>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
Reference in a new issue