Split up main content route component

This commit is contained in:
jeffvli 2023-05-31 01:13:54 -07:00
parent fa0a21a021
commit e7c7eb3ec0
7 changed files with 409 additions and 286 deletions

View 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: '';
}
`;

View file

@ -8,3 +8,4 @@ export * from './mutations/create-favorite-mutation';
export * from './mutations/delete-favorite-mutation';
export * from './mutations/set-rating-mutation';
export * from './components/filter-bar';
export * from './components/resize-handle';

View 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>
</>
);
};

View 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>
);
};

View file

@ -1,19 +1,20 @@
import { useDisclosure, useTimeout } from '@mantine/hooks';
import { motion, AnimatePresence, Variants } from 'framer-motion';
import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
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 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 { useAppStore, useAppStoreActions, useFullScreenPlayerStore } from '/@/renderer/store';
import { useWindowSettings, useGeneralSettings } from '/@/renderer/store/settings.store';
import { Platform } from '/@/renderer/types';
import { useAppStoreActions, useSidebarStore } from '/@/renderer/store';
import { useGeneralSettings } from '/@/renderer/store/settings.store';
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;
@ -37,171 +38,17 @@ const MainContentContainer = styled.div<{
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 }) => {
const sidebar = useAppStore((state) => state.sidebar);
const { setSideBar } = useAppStoreActions();
const [drawer, drawerHandler] = useDisclosure(false);
const location = useLocation();
const { sideQueueType, showQueueDrawerButton } = useGeneralSettings();
const { windowBarStyle } = useWindowSettings();
const sidebarRef = useRef<HTMLDivElement | null>(null);
const rightSidebarRef = useRef<HTMLDivElement | null>(null);
const { collapsed, leftWidth, rightWidth, rightExpanded } = useSidebarStore();
const { setSideBar } = useAppStoreActions();
const { sideQueueType } = useGeneralSettings();
const [isResizing, setIsResizing] = useState(false);
const [isResizingRight, setIsResizingRight] = useState(false);
const { showQueueDrawerButton } = useGeneralSettings();
const drawerTimeout = useTimeout(() => drawerHandler.open(), 500);
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 showSideQueue = rightExpanded && location.pathname !== AppRoute.NOW_PLAYING;
const rightSidebarRef = useRef<HTMLDivElement | null>(null);
const startResizing = useCallback((position: 'left' | 'right') => {
if (position === 'left') return setIsResizing(true);
@ -225,13 +72,13 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
setSideBar({ collapsed: false, leftWidth: constrainedWidth });
}
} else if (isResizingRight) {
const start = Number(sidebar.rightWidth.split('px')[0]);
const start = Number(rightWidth.split('px')[0]);
const { left } = rightSidebarRef!.current!.getBoundingClientRect();
const width = `${constrainRightSidebarWidth(start + left - mouseMoveEvent.clientX)}px`;
setSideBar({ rightWidth: width });
}
},
[isResizing, isResizingRight, setSideBar, sidebar.rightWidth],
[isResizing, isResizingRight, setSideBar, rightWidth],
);
const throttledResize = useMemo(() => throttle(resize, 50), [resize]);
@ -248,123 +95,26 @@ export const MainContent = ({ shell }: { shell?: boolean }) => {
return (
<MainContentContainer
id="main-content"
leftSidebarWidth={sidebar.leftWidth}
leftSidebarWidth={leftWidth}
rightExpanded={showSideQueue && sideQueueType === 'sideQueue'}
rightSidebarWidth={sidebar.rightWidth}
rightSidebarWidth={rightWidth}
shell={shell}
sidebarCollapsed={sidebar.collapsed}
sidebarCollapsed={collapsed}
>
{!shell && (
<>
<AnimatePresence
initial={false}
mode="wait"
>
{isFullScreenPlayerExpanded && <FullScreenPlayer />}
</AnimatePresence>
<SidebarContainer id="sidebar">
<ResizeHandle
ref={sidebarRef}
isResizing={isResizing}
placement="right"
onMouseDown={(e) => {
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={<></>}>
{showQueueDrawerButton && <SideDrawerQueue />}
<FullScreenOverlay />
<LeftSidebar
isResizing={isResizing}
startResizing={startResizing}
/>
<RightSidebar
ref={rightSidebarRef}
isResizing={isResizingRight}
startResizing={startResizing}
/>
</Suspense>
)}
<Suspense fallback={<></>}>
<Outlet />

View 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>
);
},
);

View 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>
</>
);
};