mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-02 10:53:33 +00:00
Add fullscreen player view (#27)
* Add store controls for fullscreen player * Normalize styles for playback config * Add fullscreen player component * Add option component * Update player controls to use option/popover components * Add esc hotkey to close player * Add usePlayerData hook
This commit is contained in:
parent
6cfdb8ff84
commit
e47fcfc62e
18 changed files with 780 additions and 62 deletions
194
src/renderer/features/player/components/full-screen-player.tsx
Normal file
194
src/renderer/features/player/components/full-screen-player.tsx
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
import { useLayoutEffect, useRef } from 'react';
|
||||
import { Group } from '@mantine/core';
|
||||
import { useHotkeys } from '@mantine/hooks';
|
||||
import { Variants, motion } from 'framer-motion';
|
||||
import { RiArrowDownSLine, RiSettings3Line } from 'react-icons/ri';
|
||||
import { useLocation } from 'react-router';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Option, Popover, Switch, TableConfigDropdown } from '/@/renderer/components';
|
||||
import {
|
||||
useCurrentSong,
|
||||
useFullScreenPlayerStore,
|
||||
useFullScreenPlayerStoreActions,
|
||||
} from '/@/renderer/store';
|
||||
import { useFastAverageColor } from '../../../hooks/use-fast-average-color';
|
||||
import { FullScreenPlayerImage } from '/@/renderer/features/player/components/full-screen-player-image';
|
||||
import { FullScreenPlayerQueue } from '/@/renderer/features/player/components/full-screen-player-queue';
|
||||
|
||||
const Container = styled(motion.div)`
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
`;
|
||||
|
||||
const ResponsiveContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
max-width: 2560px;
|
||||
margin-top: 70px;
|
||||
|
||||
.full-screen-player-image {
|
||||
max-height: calc(35vh - 90px);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1080px) {
|
||||
flex-direction: row;
|
||||
|
||||
.full-screen-player-image {
|
||||
max-height: calc(70vh - 90px);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-height: 800px) and (min-width: 1080px) {
|
||||
.full-screen-player-image {
|
||||
max-height: calc(50vh - 90px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const BackgroundImageOverlay = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(20, 21, 23, 40%), var(--main-bg));
|
||||
`;
|
||||
|
||||
const Controls = () => {
|
||||
const { dynamicBackground, expanded } = useFullScreenPlayerStore();
|
||||
const { setStore } = useFullScreenPlayerStoreActions();
|
||||
|
||||
const handleToggleFullScreenPlayer = () => {
|
||||
setStore({ expanded: !expanded });
|
||||
};
|
||||
|
||||
useHotkeys([['Escape', handleToggleFullScreenPlayer]]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group
|
||||
p="1rem"
|
||||
pos="absolute"
|
||||
spacing="sm"
|
||||
sx={{
|
||||
left: 0,
|
||||
top: 10,
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
compact
|
||||
size="sm"
|
||||
tooltip={{ label: 'Minimize' }}
|
||||
variant="subtle"
|
||||
onClick={handleToggleFullScreenPlayer}
|
||||
>
|
||||
<RiArrowDownSLine size="2rem" />
|
||||
</Button>
|
||||
<Popover position="bottom-start">
|
||||
<Popover.Target>
|
||||
<Button
|
||||
compact
|
||||
size="sm"
|
||||
tooltip={{ label: 'Configure' }}
|
||||
variant="subtle"
|
||||
>
|
||||
<RiSettings3Line size="1.5rem" />
|
||||
</Button>
|
||||
</Popover.Target>
|
||||
<Popover.Dropdown>
|
||||
<Option>
|
||||
<Option.Label>Dynamic Background</Option.Label>
|
||||
<Option.Control>
|
||||
<Switch
|
||||
defaultChecked={dynamicBackground}
|
||||
onChange={(e) =>
|
||||
setStore({
|
||||
dynamicBackground: e.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Option.Control>
|
||||
</Option>
|
||||
<TableConfigDropdown type="fullScreen" />
|
||||
</Popover.Dropdown>
|
||||
</Popover>
|
||||
</Group>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const containerVariants: Variants = {
|
||||
closed: {
|
||||
height: 'calc(100vh - 90px)',
|
||||
position: 'absolute',
|
||||
top: '100vh',
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
width: '100vw',
|
||||
y: -100,
|
||||
},
|
||||
open: (custom) => {
|
||||
const { dynamicBackground, background } = custom;
|
||||
return {
|
||||
background: dynamicBackground ? background : 'var(--main-bg)',
|
||||
height: 'calc(100vh - 90px)',
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
transition: {
|
||||
background: {
|
||||
duration: 1,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
delay: 0.1,
|
||||
duration: 0.5,
|
||||
ease: 'easeInOut',
|
||||
},
|
||||
width: '100vw',
|
||||
y: 0,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const FullScreenPlayer = () => {
|
||||
const { dynamicBackground } = useFullScreenPlayerStore();
|
||||
const { setStore } = useFullScreenPlayerStoreActions();
|
||||
|
||||
const location = useLocation();
|
||||
const isOpenedRef = useRef<boolean | null>(null);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (isOpenedRef.current !== null) {
|
||||
setStore({ expanded: false });
|
||||
}
|
||||
|
||||
isOpenedRef.current = true;
|
||||
}, [location, setStore]);
|
||||
|
||||
const currentSong = useCurrentSong();
|
||||
const background = useFastAverageColor(currentSong?.imageUrl, true, 'dominant');
|
||||
|
||||
return (
|
||||
<Container
|
||||
animate="open"
|
||||
custom={{ background, dynamicBackground }}
|
||||
exit="closed"
|
||||
initial="closed"
|
||||
variants={containerVariants}
|
||||
>
|
||||
<Controls />
|
||||
{dynamicBackground && <BackgroundImageOverlay />}
|
||||
<ResponsiveContainer>
|
||||
<FullScreenPlayerImage />
|
||||
<FullScreenPlayerQueue />
|
||||
</ResponsiveContainer>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue