Update player/shared components

This commit is contained in:
jeffvli
2022-10-24 22:30:16 -07:00
parent 8973571147
commit dd3de66232
21 changed files with 164 additions and 139 deletions
+28 -34
View File
@@ -1,20 +1,14 @@
import { ReactNode, useEffect } from 'react';
import {
DndContext,
MouseSensor,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import { MantineProvider } from '@mantine/core';
import { useLocalStorage } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
import { NotificationsProvider } from '@mantine/notifications';
import isElectron from 'is-electron';
import { BrowserRouter, HashRouter } from 'react-router-dom';
import { useDefaultSettings } from './features/settings';
import { AppRouter } from './router/AppRouter';
import { AppRouter } from './router/app-router';
import './styles/global.scss';
import 'ag-grid-community/styles/ag-grid.css';
import './styles/ag-grid.scss';
const SelectRouter = ({ children }: { children: ReactNode }) => {
if (isElectron()) {
@@ -36,27 +30,16 @@ export const App = () => {
document.body.setAttribute('data-theme', theme);
}, [theme]);
const sensors = useSensors(
useSensor(MouseSensor, {
activationConstraint: {
delay: 200,
tolerance: 100,
},
}),
useSensor(TouchSensor, {
activationConstraint: {
delay: 500,
tolerance: 10,
},
})
);
return (
<MantineProvider
withGlobalStyles
withNormalizeCSS
theme={{
colorScheme: 'dark',
defaultRadius: 'xs',
focusRing: 'auto',
dir: 'ltr',
focusRing: 'never',
fontFamily: 'Poppins, sans-serif',
fontSizes: {
lg: 16,
md: 14,
@@ -64,22 +47,33 @@ export const App = () => {
xl: 18,
xs: 10,
},
other: {},
spacing: {
lg: 12,
md: 8,
sm: 4,
xl: 16,
xs: 2,
},
}}
>
<DndContext
sensors={sensors}
onDragEnd={() => console.log('drag end')}
onDragStart={() => console.log('drag start')}
<NotificationsProvider
autoClose={1500}
position="bottom-right"
style={{
marginBottom: '90px',
opacity: '.8',
userSelect: 'none',
width: '250px',
}}
transitionDuration={200}
>
<SelectRouter>
<AppRouter />
</SelectRouter>
</DndContext>
<ModalsProvider>
<SelectRouter>
<AppRouter />
</SelectRouter>
</ModalsProvider>
</NotificationsProvider>
</MantineProvider>
);
};
@@ -1,7 +1,7 @@
import { Ref, useMemo } from 'react';
import { FixedSizeList, FixedSizeListProps } from 'react-window';
import { GridCard } from '@/renderer/components/virtual-grid/grid-card';
import { usePlayQueueHandler } from '@/renderer/features/player/hooks/usePlayQueueHandler';
import { usePlayQueueHandler } from '@/renderer/features/player/hooks/use-playqueue-handler';
import { CardRow } from '@/renderer/types';
export const VirtualGridWrapper = ({
@@ -1,11 +1,11 @@
import { useRef } from 'react';
import styled from 'styled-components';
import styled from '@emotion/styled';
import { PlaybackType } from '../../../../types';
import { AudioPlayer } from '../../../components';
import { usePlayerStore } from '../../../store';
import { CenterControls } from './CenterControls';
import { LeftControls } from './LeftControls';
import { RightControls } from './RightControls';
import { CenterControls } from './center-controls';
import { LeftControls } from './left-controls';
import { RightControls } from './right-controls';
const PlayerbarContainer = styled.div`
width: 100%;
@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react';
import styled from '@emotion/styled';
import format from 'format-duration';
import ReactSlider, { ReactSliderProps } from 'react-slider';
import styled from 'styled-components';
interface SliderProps extends ReactSliderProps {
hasTooltip?: boolean;
@@ -1,22 +1,22 @@
import { useEffect, useMemo, useState } from 'react';
import styled from '@emotion/styled';
import format from 'format-duration';
import { useTranslation } from 'react-i18next';
import {
RiPauseLine,
RiPlayFill,
RiRewindFill,
RiRepeat2Fill,
RiShuffleFill,
RiSkipBackFill,
RiSkipForwardFill,
RiSpeedFill,
} from 'react-icons/ri';
import styled from 'styled-components';
import { PlaybackType, PlayerStatus } from '../../../../types';
import { Text } from '../../../components';
import { usePlayerStore } from '../../../store';
import { Font } from '../../../styles';
import { useCenterControls } from '../hooks/useCenterControls';
import { PlayerButton } from './PlayerButton';
import { Slider } from './Slider';
import { useCenterControls } from '../hooks/use-center-controls';
import { PlayerButton } from './player-button';
import { Slider } from './slider';
interface CenterControlsProps {
playersRef: any;
@@ -67,8 +67,6 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
handlePlayPause,
handlePrevTrack,
handleSeekSlider,
handleSkipBackward,
handleSkipForward,
} = useCenterControls({ playersRef });
const currentTime = usePlayerStore((state) => state.current.time);
@@ -106,16 +104,16 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
<ControlsContainer onScroll={(e) => console.log(e)}>
<ButtonsContainer>
<PlayerButton
icon={<RiSkipBackFill size={15} />}
tooltip={{ label: `${t('player.prev')}` }}
icon={<RiShuffleFill size={15} />}
tooltip={{ label: `${t('player.shuffle')}` }}
variant="secondary"
onClick={handlePrevTrack}
/>
<PlayerButton
icon={<RiRewindFill size={15} />}
tooltip={{ label: `${t('player.skipBack')}` }}
icon={<RiSkipBackFill size={15} />}
tooltip={{ label: `${t('player.previous')}` }}
variant="secondary"
onClick={handleSkipBackward}
onClick={handlePrevTrack}
/>
<PlayerButton
icon={
@@ -134,18 +132,18 @@ export const CenterControls = ({ playersRef }: CenterControlsProps) => {
variant="main"
onClick={handlePlayPause}
/>
<PlayerButton
icon={<RiSpeedFill size={15} />}
tooltip={{ label: `${t('player.skipForward')}` }}
variant="secondary"
onClick={handleSkipForward}
/>
<PlayerButton
icon={<RiSkipForwardFill size={15} />}
tooltip={{ label: `${t('player.next')}` }}
variant="secondary"
onClick={handleNextTrack}
/>
<PlayerButton
icon={<RiRepeat2Fill size={15} />}
tooltip={{ label: `${t('player.repeat')}` }}
variant="secondary"
onClick={handleNextTrack}
/>
</ButtonsContainer>
</ControlsContainer>
<SliderContainer>
@@ -1,4 +1,4 @@
import styled from 'styled-components';
import styled from '@emotion/styled';
import { Text } from '../../../components';
import { usePlayerStore } from '../../../store';
import { Font } from '../../../styles';
@@ -34,7 +34,9 @@ export const LeftControls = () => {
return (
<LeftControlsContainer>
<ImageWrapper>
<img alt="img" height={60} src={song?.imageUrl} width={60} />
{song?.imageUrl && (
<img alt="img" height={60} src={song?.imageUrl} width={60} />
)}
</ImageWrapper>
<MetadataStack>
<Text
@@ -1,11 +1,12 @@
import { ComponentPropsWithoutRef, ReactNode } from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import {
TooltipProps,
UnstyledButton,
UnstyledButtonProps,
} from '@mantine/core';
import { motion } from 'framer-motion';
import styled, { css } from 'styled-components';
import { Tooltip } from '../../../components';
type MantineButtonProps = UnstyledButtonProps &
@@ -32,17 +33,11 @@ const MotionWrapper = styled(motion.div)<MotionWrapperProps>`
const ButtonMainVariant = css`
padding: 0.5rem;
background: var(--playerbar-btn-color);
border: 2px solid var(--playerbar-btn-color);
border-radius: 50%;
svg {
display: flex;
fill: black;
stroke: black;
}
&:hover {
background: var(--playerbar-btn-color-hover);
}
&:focus-visible {
@@ -109,13 +104,17 @@ export const PlayerButton = ({
}: PlayerButtonProps) => {
if (tooltip) {
return (
<MotionWrapper variant={variant}>
<Tooltip {...tooltip}>
<Tooltip {...tooltip}>
<MotionWrapper
variant={variant}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<StyledPlayerButton variant={variant} {...rest}>
{icon}
</StyledPlayerButton>
</Tooltip>
</MotionWrapper>
</MotionWrapper>
</Tooltip>
);
}
@@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { RiVolumeUpFill, RiVolumeMuteFill } from 'react-icons/ri';
import styled from 'styled-components';
import { IconButton } from '../../../components';
import { usePlayerStore } from '../../../store';
import { useRightControls } from '../hooks/useRightControls';
import { Slider } from './Slider';
import { useRightControls } from '../hooks/use-right-controls';
import { PlayerButton } from './player-button';
import { Slider } from './slider';
const RightControlsContainer = styled.div`
display: flex;
@@ -39,23 +39,21 @@ export const RightControls = () => {
<RightControlsContainer>
<MetadataStack>
<VolumeSliderWrapper>
<IconButton
<PlayerButton
icon={
muted ? (
<RiVolumeMuteFill size={20} />
<RiVolumeMuteFill size={15} />
) : (
<RiVolumeUpFill size={20} />
<RiVolumeUpFill size={15} />
)
}
size={20}
tooltip={{ label: muted ? 'Muted' : volume }}
variant="transparent"
variant="secondary"
onClick={handleMute}
/>
<Slider
hasTooltip
height="100%"
height="60%"
max={100}
min={0}
value={volume}
@@ -42,7 +42,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
};
const handlePlay = useCallback(() => {
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.play();
} else {
currentPlayerRef.getInternalPlayer().play();
@@ -52,7 +52,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
}, [currentPlayerRef, play, settings]);
const handlePause = useCallback(() => {
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.pause();
}
@@ -60,7 +60,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
}, [pause, settings]);
const handleStop = () => {
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.stop();
} else {
stopPlayback();
@@ -73,7 +73,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleNextTrack = useCallback(() => {
const playerData = next();
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.setQueue(playerData);
mpvPlayer.next();
} else {
@@ -86,7 +86,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handlePrevTrack = useCallback(() => {
const playerData = prev();
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.setQueue(playerData);
mpvPlayer.previous();
} else {
@@ -98,7 +98,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handlePlayPause = useCallback(() => {
if (queue) {
if (playerStatus === PlayerStatus.Paused) {
if (playerStatus === PlayerStatus.PAUSED) {
return handlePlay();
}
@@ -111,7 +111,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleSkipBackward = () => {
const skipBackwardSec = 5;
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
const newTime = currentTime - skipBackwardSec;
mpvPlayer.seek(-skipBackwardSec);
setCurrentTime(newTime < 0 ? 0 : newTime);
@@ -126,7 +126,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
const handleSkipForward = () => {
const skipForwardSec = 5;
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
const newTime = currentTime + skipForwardSec;
mpvPlayer.seek(skipForwardSec);
setCurrentTime(newTime);
@@ -147,7 +147,7 @@ export const useCenterControls = (args: { playersRef: any }) => {
(e: number | any) => {
setCurrentTime(e);
if (settings.type === PlaybackType.Local) {
if (settings.type === PlaybackType.LOCAL) {
mpvPlayer.seekTo(e);
} else {
currentPlayerRef.seekTo(e);
@@ -1,23 +1,19 @@
import { api } from '@/renderer/api';
import { Item, Play } from '../../../../types';
import { albumsApi } from '../../../api/albumsApi';
import { usePlayerStore } from '../../../store';
import {
getJellyfinStreamUrl,
getServerFolderAuth,
getSubsonicStreamUrl,
} from '../../../utils';
import { useAuthStore, usePlayerStore } from '../../../store';
import { mpvPlayer } from '../utils/mpvPlayer';
const getEndpointByItemType = (item: Item) => {
switch (item) {
case Item.ALBUM:
return albumsApi.getAlbum;
return api.albums.getAlbumDetail;
default:
return albumsApi.getAlbum;
return api.albums.getAlbumDetail;
}
};
export const usePlayQueueHandler = () => {
const serverId = useAuthStore((state) => state.currentServer?.id) || '';
const addToQueue = usePlayerStore((state) => state.addToQueue);
const handlePlayQueueAdd = async (options: {
@@ -35,36 +31,42 @@ export const usePlayQueueHandler = () => {
if (options.byItemType) {
const deviceId = localStorage.getItem('device_id');
const { serverUrl } = JSON.parse(
localStorage.getItem('authentication') || '{}'
);
// const { state } = JSON.parse(
// localStorage.getItem('authentication') || '{}'
// );
if (deviceId) {
const endpoint = getEndpointByItemType(options.byItemType.type);
const { data } = await endpoint({
id: options.byItemType.id,
albumId: options.byItemType.id,
serverId,
});
const songs = data.songs.map((song) => {
const auth = getServerFolderAuth(serverUrl, song.serverFolderId);
const songs = data.songs?.map((song) => {
// const auth = getServerFolderAuth(
// state.serverUrl,
// song.serverFolderId
// );
if (auth) {
const streamUrl =
auth.type === 'jellyfin'
? getJellyfinStreamUrl(auth, song, deviceId)
: getSubsonicStreamUrl(auth, song, deviceId);
// if (auth) {
// const streamUrl =
// auth.type === 'jellyfin'
// ? getJellyfinStreamUrl(auth, song, deviceId)
// : getSubsonicStreamUrl(auth, song, deviceId);
return {
...song,
streamUrl,
};
}
// return {
// ...song,
// streamUrl,
// };
// }
return song;
});
const playerData = addToQueue(songs, options.play);
const playerData = addToQueue(songs || [], options.play);
console.log('playerData', playerData);
if (options.play === Play.NEXT || options.play === Play.LAST) {
mpvPlayer.setQueueNext(playerData);
+4 -4
View File
@@ -1,4 +1,4 @@
export * from './components/CenterControls';
export * from './components/LeftControls';
export * from './components/Playerbar';
export * from './components/Slider';
export * from './components/center-controls';
export * from './components/left-controls';
export * from './components/playerbar';
export * from './components/slider';
@@ -1,6 +1,6 @@
import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import styled from 'styled-components';
interface AnimatedPageProps {
children: ReactNode;
@@ -0,0 +1,22 @@
import { useMemo } from 'react';
import { useAuthStore } from '@/renderer/store';
export const usePermissions = () => {
const permissions = useAuthStore((state) => state.permissions);
const permissionSet = useMemo(() => {
const set = {
createServer: permissions.isAdmin,
createServerCredential: true,
createServerUrl: permissions.isAdmin,
deleteServer: permissions.isAdmin,
deleteServerCredential: true,
deleteServerUrl: permissions.isAdmin,
editServer: permissions.isAdmin,
};
return set;
}, [permissions]);
return permissionSet;
};
+2
View File
@@ -0,0 +1,2 @@
export * from './components/animated-page';
export * from './hooks/use-permissions';
@@ -1,11 +1,11 @@
import styled from '@emotion/styled';
import {
RiDashboardFill,
RiFileList2Fill,
RiSearch2Fill,
RiSearch2Line,
} from 'react-icons/ri';
import styled from 'styled-components';
import { AppRoute } from '../../../router/utils/routes';
import { ListItem } from './ListItem';
import { AppRoute } from '../../../router/routes';
import { ListItem } from './list-item';
const StyledSidebar = styled.div``;
@@ -19,15 +19,15 @@ export const Sidebar = () => {
</ListItem.Link>
</ListItem>
<ListItem>
<ListItem.Link to={AppRoute.SEARCH}>
<RiSearch2Fill size={20} />
Search
<ListItem.Link to={AppRoute.LIBRARY}>
<RiFileList2Fill size={20} />
Library
</ListItem.Link>
</ListItem>
<ListItem>
<ListItem.Link to={AppRoute.LIBRARY}>
<RiFileList2Fill size={20} />
Your Library
<ListItem.Link to={AppRoute.SEARCH}>
<RiSearch2Line size={20} />
Search
</ListItem.Link>
</ListItem>
</StyledSidebar>
@@ -1,7 +1,8 @@
import { ReactNode } from 'react';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { motion } from 'framer-motion';
import { Link, LinkProps } from 'react-router-dom';
import styled, { css } from 'styled-components';
import { fontInter } from '../../../styles';
interface ListItemProps {
+1 -1
View File
@@ -1 +1 @@
export * from './components/Sidebar';
export * from './components/sidebar';
+2 -2
View File
@@ -1,9 +1,9 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { createRoot } from 'react-dom/client';
import { I18nextProvider } from 'react-i18next';
import { QueryClientProvider } from 'react-query';
import { queryClient } from '@/renderer/lib/react-query';
import i18n from '../i18n/i18n';
import { App } from './app';
import { queryClient } from './lib';
const container = document.getElementById('root')!;
const root = createRoot(container);
+1 -1
View File
@@ -1,4 +1,4 @@
import { AppRoute } from './router/utils/routes';
import { AppRoute } from './router/routes';
export interface CardRow {
align?: 'left' | 'center' | 'right';
+7
View File
@@ -0,0 +1,7 @@
import '@emotion/react';
import type { MantineTheme } from '@mantine/core';
declare module '@emotion/react' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface GREY extends MantineTheme {}
}