Browse Source

Initial sidebar improvements

- Add playlist list to sidebar
- Add cover art toggle to sidebar
- Drag resizable
master
jeffvli 3 years ago
committed by Jeff
parent
commit
f632301102
  1. 1
      package.json
  2. 6
      src/__tests__/App.test.tsx
  3. 11
      src/components/layout/Layout.tsx
  4. 502
      src/components/layout/Sidebar.tsx
  5. 73
      src/components/layout/SidebarPlaylists.tsx
  6. 97
      src/components/layout/styled.tsx
  7. 5
      src/components/library/AlbumView.tsx
  8. 3
      src/components/library/ArtistView.tsx
  9. 58
      src/components/player/PlayerBar.tsx
  10. 18
      src/components/player/styled.tsx
  11. 12
      src/components/shared/setDefaultSettings.ts
  12. 25
      src/redux/miscSlice.ts
  13. 4
      src/shared/mockSettings.ts
  14. 183
      yarn.lock

1
package.json

@ -290,6 +290,7 @@
"react-query": "^3.19.1",
"react-redux": "^7.2.4",
"react-router-dom": "^5.2.0",
"react-use": "^17.3.2",
"react-virtualized-auto-sizer": "^1.0.6",
"react-visibility-sensor": "^5.1.1",
"react-window": "^1.8.6",

6
src/__tests__/App.test.tsx

@ -73,7 +73,11 @@ const miscState: General = {
contextMenu: {
show: false,
},
expandSidebar: false,
sidebar: {
expand: false,
coverArt: true,
width: '225px',
},
modal: {
currentPageIndex: undefined,
show: false,

11
src/components/layout/Layout.tsx

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import settings from 'electron-settings';
import { useHotkeys } from 'react-hotkeys-hook';
import { useHistory } from 'react-router-dom';
import { ButtonToolbar, Content, FlexboxGrid, Icon, Nav, Whisper } from 'rsuite';
@ -6,7 +7,7 @@ import { useTranslation } from 'react-i18next';
import Sidebar from './Sidebar';
import Titlebar from './Titlebar';
import { RootContainer, RootFooter, MainContainer } from './styled';
import { setContextMenu, setExpandSidebar, setSearchQuery } from '../../redux/miscSlice';
import { setContextMenu, setSearchQuery, setSidebar } from '../../redux/miscSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { clearSelected } from '../../redux/multiSelectSlice';
import {
@ -58,7 +59,8 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
});
const handleToggle = () => {
dispatch(setExpandSidebar(!misc.expandSidebar));
settings.setSync('sidebar.expand', !misc.sidebar.expand);
dispatch(setSidebar({ expand: !misc.sidebar.expand }));
};
const handleSidebarSelect = (e: string) => {
@ -111,7 +113,7 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
<>
<Titlebar font={font} />
<Sidebar
expand={misc.expandSidebar}
expand={misc.sidebar.expand}
handleToggle={handleToggle}
handleSidebarSelect={handleSidebarSelect}
disableSidebar={disableSidebar}
@ -145,7 +147,8 @@ const Layout = ({ footer, children, disableSidebar, font }: any) => {
>
<MainContainer
id="container-main"
expanded={misc.expandSidebar}
expanded={misc.sidebar.expand}
sidebarwidth={misc.sidebar.width}
$titleBar={misc.titleBar} // transient prop to determine margin
>
<FlexboxGrid

502
src/components/layout/Sidebar.tsx

@ -1,10 +1,25 @@
import React from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import settings from 'electron-settings';
import useMeasure from 'react-use/lib/useMeasure';
import { LazyLoadImage } from 'react-lazy-load-image-component';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import { Sidenav, Nav, Icon } from 'rsuite';
import { useAppSelector } from '../../redux/hooks';
import _ from 'lodash';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { Server } from '../../types';
import { FixedSidebar, SidebarNavItem } from './styled';
import {
FixedSidebar,
PlaylistDivider,
SidebarCoverArtContainer,
SidebarDragContainer,
SidebarNavItem,
} from './styled';
import { setSidebar } from '../../redux/miscSlice';
import { StyledButton } from '../shared/styled';
import { InfoModal } from '../modal/PageModal';
import placeholderImg from '../../img/placeholder.png';
import SidebarPlaylists from './SidebarPlaylists';
const Sidebar = ({
expand,
@ -16,193 +31,320 @@ const Sidebar = ({
...rest
}: any) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const history = useHistory();
const playQueue = useAppSelector((state) => state.playQueue);
const config = useAppSelector((state) => state.config);
const misc = useAppSelector((state) => state.misc);
const [width, setWidth] = useState(Number(misc.sidebar.width.replace('px', '')));
const [isResizing, setIsResizing] = useState(false);
const [showCoverArtModal, setShowCoverArtModal] = useState(false);
const [throttledWidth, setThrottledWidth] = useState(
Number(misc.sidebar.width.replace('px', ''))
);
const [mainNavRef, { height: mainNavHeight }] = useMeasure<HTMLDivElement>();
const [sidebarBodyRef, { height: sidebarBodyHeight }] = useMeasure<HTMLDivElement>();
const getSidebarWidth = useCallback((num: number) => {
if (num < 160) {
return 160;
}
if (num > 400) {
return 400;
}
return num;
}, []);
const handleResizeMove = useMemo(() => {
const throttled = _.throttle((e: MouseEvent) => setThrottledWidth(e.clientX), 25);
return (e: MouseEvent) => throttled(e);
}, []);
const handleResizeEnd = useCallback(
(e: MouseEvent) => {
if (isResizing) {
dispatch(setSidebar({ width: `${getSidebarWidth(e?.clientX)}px` }));
setIsResizing(false);
document.body.style.cursor = 'default';
}
},
[dispatch, getSidebarWidth, isResizing]
);
useEffect(() => {
if (isResizing) {
document.addEventListener('mousemove', handleResizeMove);
document.addEventListener('mouseup', handleResizeEnd);
}
return () => {
document.removeEventListener('mousemove', handleResizeMove);
document.removeEventListener('mouseup', handleResizeEnd);
};
}, [handleResizeEnd, isResizing, handleResizeMove]);
useEffect(() => {
setWidth(getSidebarWidth(throttledWidth));
}, [dispatch, getSidebarWidth, throttledWidth]);
return (
<FixedSidebar
id="sidebar"
width={expand ? 165 : 56}
collapsible
font={font}
$titleBar={titleBar} // transient prop to determine position
onClick={rest.onClick}
>
<Sidenav
style={{
height: '100%',
}}
expanded={expand}
appearance="default"
<>
<FixedSidebar
id="sidebar"
width={expand ? `${width}px` : 56}
font={font}
$titleBar={titleBar} // transient prop to determine position
onClick={rest.onClick}
>
<Sidenav.Header />
<Sidenav.Body>
<Nav>
<SidebarNavItem
tabIndex={0}
eventKey="discover"
icon={<Icon icon="dashboard" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/');
}
}}
>
{t('Dashboard')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="nowplaying"
icon={<Icon icon="headphones" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/nowplaying');
<Sidenav style={{ height: '100%' }} expanded={expand} appearance="default">
{expand && misc.sidebar.coverArt && (
<SidebarCoverArtContainer height={`${width}px`}>
<LazyLoadImage
onClick={() => setShowCoverArtModal(true)}
src={
playQueue.current?.image.replace(
/&size=\d+|width=\d+&height=\d+&quality=\d+/,
''
) || placeholderImg
}
}}
>
{t('Now Playing')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="playlists"
icon={<Icon icon="list-ul" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/playlist');
}
}}
>
{t('Playlists')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="starred"
icon={<Icon icon="heart" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/starred');
}
}}
>
{t('Favorites')}
</SidebarNavItem>
{config.serverType === Server.Jellyfin && (
<SidebarNavItem
tabIndex={0}
eventKey="music"
icon={<Icon icon="music" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/music');
}
/>
<StyledButton
size="xs"
onClick={() => {
dispatch(setSidebar({ coverArt: false }));
settings.setSync('sidebar.coverArt', false);
}}
>
Songs
</SidebarNavItem>
)}
<SidebarNavItem
tabIndex={0}
eventKey="albums"
icon={<Icon icon="book2" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/album');
}
}}
>
{t('Albums')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="artists"
icon={<Icon icon="people-group" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/artist');
}
}}
>
{t('Artists')}{' '}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="genres"
icon={<Icon icon="globe2" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/genre');
}
}}
>
{t('Genres')}{' '}
</SidebarNavItem>
{useAppSelector((state) => state.config).serverType !== 'funkwhale' && (
<>
<SidebarNavItem
tabIndex={0}
eventKey="folders"
icon={<Icon icon="folder-open" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/folder');
}
<Icon icon="down" />
</StyledButton>
</SidebarCoverArtContainer>
)}
<Sidenav.Body
style={{
height: expand ? `calc(100% - ${misc.sidebar.coverArt ? width : 0}px)` : '100%',
overflowY: 'auto',
}}
>
<div ref={sidebarBodyRef} style={{ height: '100%' }}>
{expand && (
<SidebarDragContainer
$resizing={isResizing}
onMouseDown={() => {
setIsResizing(true);
document.body.style.cursor = 'w-resize';
}}
/>
)}
<Nav>
<div ref={mainNavRef}>
<SidebarNavItem
tabIndex={0}
eventKey="discover"
icon={<Icon icon="dashboard" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/');
}
}}
>
{t('Dashboard')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="nowplaying"
icon={<Icon icon="headphones" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/nowplaying');
}
}}
>
{t('Now Playing')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="starred"
icon={<Icon icon="heart" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/starred');
}
}}
>
{t('Favorites')}
</SidebarNavItem>
{config.serverType === Server.Jellyfin && (
<SidebarNavItem
tabIndex={0}
eventKey="music"
icon={<Icon icon="music" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/music');
}
}}
>
Songs
</SidebarNavItem>
)}
<SidebarNavItem
tabIndex={0}
eventKey="albums"
icon={<Icon icon="book2" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/album');
}
}}
>
{t('Albums')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="artists"
icon={<Icon icon="people-group" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/artist');
}
}}
>
{t('Artists')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
eventKey="genres"
icon={<Icon icon="globe2" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/genre');
}
}}
>
{t('Genres')}
</SidebarNavItem>
{useAppSelector((state) => state.config).serverType !== 'funkwhale' && (
<>
<SidebarNavItem
tabIndex={0}
eventKey="folders"
icon={<Icon icon="folder-open" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/library/folder');
}
}}
>
{t('Folders')}
</SidebarNavItem>
</>
)}
<SidebarNavItem
tabIndex={0}
eventKey="config"
icon={<Icon icon="gear-circle" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/config');
}
}}
>
{t('Config')}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
icon={<Icon icon={expand ? 'arrow-left' : 'arrow-right'} />}
onSelect={handleToggle}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
handleToggle();
}
}}
>
{expand ? t('Collapse') : t('Expand')}
</SidebarNavItem>
{!expand && (
<SidebarNavItem
tabIndex={0}
eventKey="playlists"
icon={<Icon icon="list-ul" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/playlist');
}
}}
>
{t('Playlists')}
</SidebarNavItem>
)}
</div>
</Nav>
{expand && !disableSidebar && (
<div
style={{
height: `${
sidebarBodyHeight - mainNavHeight < 170
? 170
: sidebarBodyHeight - mainNavHeight
}px`,
overflow: 'hidden',
overflowY: 'auto',
}}
>
{t('Folders')}{' '}
</SidebarNavItem>
</>
)}
</Nav>
<Nav>
<SidebarNavItem
tabIndex={0}
eventKey="config"
icon={<Icon icon="gear-circle" />}
onSelect={handleSidebarSelect}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
history.push('/config');
}
}}
>
{t('Config')}{' '}
</SidebarNavItem>
<SidebarNavItem
tabIndex={0}
icon={<Icon icon={expand ? 'arrow-left' : 'arrow-right'} />}
onSelect={handleToggle}
disabled={disableSidebar}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
handleToggle();
}
}}
>
{expand ? t('Collapse') : t('Expand')}
</SidebarNavItem>
</Nav>
</Sidenav.Body>
</Sidenav>
</FixedSidebar>
<>
<PlaylistDivider onClick={() => history.push('/playlist')}>
{t('Playlists')}
</PlaylistDivider>
<SidebarPlaylists width={width} />
</>
</div>
)}
</div>
</Sidenav.Body>
</Sidenav>
</FixedSidebar>
<InfoModal show={showCoverArtModal} handleHide={() => setShowCoverArtModal(false)}>
<LazyLoadImage
src={
playQueue.current?.image.replace(/&size=\d+|width=\d+&height=\d+&quality=\d+/, '') ||
placeholderImg
}
style={{
width: 'auto',
height: 'auto',
minHeight: '50vh',
maxHeight: '70vh',
maxWidth: '95vw',
}}
/>
</InfoModal>
</>
);
};

73
src/components/layout/SidebarPlaylists.tsx

@ -0,0 +1,73 @@
import React from 'react';
import { useQuery } from 'react-query';
import { useHistory } from 'react-router-dom';
import { AutoSizer } from 'react-virtualized';
import { FixedSizeList as List } from 'react-window';
import styled from 'styled-components';
import { apiController } from '../../api/controller';
import { useAppSelector } from '../../redux/hooks';
import CenterLoader from '../loader/CenterLoader';
import { StyledButton } from '../shared/styled';
const ListItemContainer = styled.div`
.rs-btn {
text-align: left;
color: ${(props) => props.theme.colors.layout.sideBar.button.color} !important;
&:hover {
color: ${(props) => props.theme.colors.layout.sideBar.button.colorHover} !important;
}
&:focus-visible {
color: ${(props) => props.theme.colors.layout.sideBar.button.colorHover} !important;
}
}
`;
const PlaylistRow = ({ data, index, style }: any) => {
const history = useHistory();
return (
<ListItemContainer style={style}>
<StyledButton
block
appearance="subtle"
onClick={() => history.push(`/playlist/${data[index].id}`)}
>
{data[index].title} {`(${data[index].songCount})`}
</StyledButton>
</ListItemContainer>
);
};
const SidebarPlaylists = ({ width }: any) => {
const config = useAppSelector((state) => state.config);
const { isLoading, data: playlists }: any = useQuery(['playlists'], () =>
apiController({ serverType: config.serverType, endpoint: 'getPlaylists' })
);
return (
<AutoSizer>
{({ height }: any) => (
<>
{isLoading ? (
<CenterLoader />
) : (
<List
height={height - 25}
itemCount={playlists?.length}
itemSize={25}
width={width}
itemData={playlists}
>
{PlaylistRow}
</List>
)}
</>
)}
</AutoSizer>
);
};
export default SidebarPlaylists;

97
src/components/layout/styled.tsx

@ -1,6 +1,6 @@
import React from 'react';
import styled from 'styled-components';
import { Container, Content, Footer, Header, Nav, Sidebar } from 'rsuite';
import { Container, Content, Divider, Footer, Header, Nav, Sidebar } from 'rsuite';
// Layout.tsx
export const RootContainer = styled(Container)<{ font: string }>`
@ -23,8 +23,8 @@ const StyledContainer = ({ id, expanded, children, ...props }: ContainerProps) =
<Container {...props}>{children}</Container>
);
export const MainContainer = styled(StyledContainer)<{ $titleBar: string }>`
padding-left: ${(props) => (props.expanded ? '165px' : '56px')};
export const MainContainer = styled(StyledContainer)<{ $titleBar: string; sidebarwidth: string }>`
padding-left: ${(props) => (props.expanded ? props.sidebarwidth : '56px')};
height: calc(100% - 32px);
margin-top: ${(props) => (props.$titleBar === 'native' ? '0px' : '32px')};
overflow-y: auto;
@ -178,13 +178,23 @@ export const FixedSidebar = styled(Sidebar)<{ font: string; $titleBar: string }>
props.font?.match('Light') ? 300 : props.font?.match('Medium') ? 500 : 400};
overflow-y: auto;
overflow-x: hidden;
user-select: none;
::-webkit-scrollbar {
display: none;
}
.rs-sidenav-body {
::-webkit-scrollbar {
display: none;
}
}
`;
export const SidebarNavItem = styled(Nav.Item)`
padding-right: 5px;
user-select: none;
a {
color: ${(props) => props.theme.colors.layout.sideBar.button.color} !important;
@ -193,7 +203,7 @@ export const SidebarNavItem = styled(Nav.Item)`
}
&:focus-visible {
color: ${(props) => props.theme.colors.primary} !important;
color: ${(props) => props.theme.colors.layout.sideBar.button.colorHover} !important;
}
}
`;
@ -241,6 +251,7 @@ export const PageHeaderWrapper = styled.div<{
margin-left: ${(props) => (props.hasImage ? '15px' : '0px')};
vertical-align: top;
color: ${(props) => (props.isDark ? '#D8D8D8' : props.theme.colors.layout.page.color)};
user-select: none;
`;
export const PageHeaderSubtitleWrapper = styled.span`
@ -265,13 +276,19 @@ export const PageHeaderSubtitleDataLine = styled.div<{
scroll-behavior: smooth;
`;
export const FlatBackground = styled.div<{ $expanded: boolean; $color: string; $titleBar: string }>`
export const FlatBackground = styled.div<{
$expanded: boolean;
$color: string;
$titleBar: string;
sidebarwidth: string;
}>`
background: ${(props) => props.$color};
top: ${(props) => (props.$titleBar === 'native' ? '0px' : '32px')};
left: ${(props) => (props.$expanded ? '165px' : '56px')};
left: ${(props) => (props.$expanded ? props.sidebarwidth : '56px')};
height: 200px;
position: absolute;
width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')};
width: ${(props) =>
props.$expanded ? `calc(100% - ${props.sidebarwidth})` : 'calc(100% - 56px)'};
user-select: none;
pointer-events: none;
`;
@ -280,13 +297,15 @@ export const BlurredBackgroundWrapper = styled.div<{
expanded: boolean;
hasImage: boolean;
$titleBar: string;
sidebarwidth: string;
}>`
clip: rect(0, auto, auto, 0);
-webkit-clip-path: inset(0 0);
clip-path: inset(0 0);
position: absolute;
left: ${(props) => (props.expanded ? '165px' : '56px')};
width: ${(props) => (props.expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')};
left: ${(props) => (props.expanded ? props.sidebarwidth : '56px')};
width: ${(props) =>
props.expanded ? `calc(100% - ${props.sidebarwidth})` : 'calc(100% - 56px)'};
top: ${(props) => (props.$titleBar === 'native' ? '0px' : '32px')};
z-index: 1;
background: ${(props) => (props.hasImage ? '#0b0908' : '#00395A')};
@ -315,6 +334,7 @@ export const GradientBackground = styled.div<{
$expanded: boolean;
$color: string;
$titleBar: string;
sidebarwidth: string;
}>`
background: ${(props) =>
`linear-gradient(0deg, transparent 10%, ${props.$color.replace(
@ -322,10 +342,11 @@ export const GradientBackground = styled.div<{
`${props.theme.type === 'dark' ? ',0.2' : ',0.5'})`
)} 100%)`};
top: ${(props) => (props.$titleBar === 'native' ? '0px' : '32px')};
left: ${(props) => (props.$expanded ? '165px' : '56px')};
left: ${(props) => (props.$expanded ? props.sidebarwidth : '56px')};
height: calc(100% - 130px);
position: absolute;
width: ${(props) => (props.$expanded ? `calc(100% - 165px)` : 'calc(100% - 56px)')};
width: ${(props) =>
props.$expanded ? `calc(100% - ${props.sidebarwidth})` : 'calc(100% - 56px)'};
z-index: 1;
user-select: none;
pointer-events: none;
@ -342,3 +363,57 @@ export const CustomImageGridWrapper = styled.div`
export const CustomImageGrid = styled.div<{ $gridArea: string }>`
grid-area: ${(props) => props.$gridArea};
`;
export const SidebarDragContainer = styled.div<{ $resizing: boolean }>`
position: absolute;
right: 0;
width: 3px;
height: 100%;
z-index: 1;
background-color: ${(props) => props.$resizing && '#323232'};
&:hover {
cursor: w-resize !important;
}
`;
export const SidebarCoverArtContainer = styled.div<{ height: string }>`
position: absolute;
bottom: 0;
height: ${(props) => props.height};
width: 100%;
z-index: 100;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background: rgba(50, 50, 50, 0.2);
img {
max-height: ${(props) => props.height};
max-width: 100%;
height: auto;
}
.rs-btn {
display: none;
}
&:hover {
.rs-btn {
display: block;
position: absolute;
bottom: 0;
left: 0;
z-index: 99;
border-radius: 0px !important;
}
}
`;
export const PlaylistDivider = styled(Divider)`
margin: 10px 0 !important;
cursor: pointer;
`;

5
src/components/library/AlbumView.tsx

@ -267,13 +267,14 @@ const AlbumView = ({ ...rest }: any) => {
{!rest.isModal && (
<BlurredBackgroundWrapper
hasImage={!data?.image.match('placeholder')}
expanded={misc.expandSidebar}
expanded={misc.sidebar.expand}
$titleBar={misc.titleBar} // transient prop to determine margin
sidebarwidth={misc.sidebar.width}
>
<BlurredBackground
// We have to use an inline style here due to the context menu forcing a component rerender
// which causes the background-image to flicker
expanded={misc.expandSidebar}
expanded={misc.sidebar.expand}
style={{
backgroundImage: `url(${
!data?.image.match('placeholder')

3
src/components/library/ArtistView.tsx

@ -521,9 +521,10 @@ const ArtistView = ({ ...rest }: any) => {
<>
{!rest.isModal && (
<GradientBackground
$expanded={misc.expandSidebar}
$expanded={misc.sidebar.expand}
$color={imageAverageColor.color}
$titleBar={misc.titleBar}
sidebarwidth={misc.sidebar.width}
/>
)}
<GenericPage

58
src/components/player/PlayerBar.tsx

@ -16,6 +16,7 @@ import {
DurationSpan,
VolumeIcon,
LinkButton,
CoverArtContainer,
} from './styled';
import { setVolume, setStar, setRate } from '../../redux/playQueueSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
@ -23,7 +24,6 @@ import Player from './Player';
import CustomTooltip from '../shared/CustomTooltip';
import placeholderImg from '../../img/placeholder.png';
import DebugWindow from '../debug/DebugWindow';
import { CoverArtWrapper } from '../layout/styled';
import { getCurrentEntryList, writeOBSFiles } from '../../shared/utils';
import {
LinkWrapper,
@ -39,6 +39,7 @@ import { InfoModal } from '../modal/PageModal';
import { setPlaylistRate } from '../../redux/playlistSlice';
import useGetLyrics from '../../hooks/useGetLyrics';
import usePlayerControls from '../../hooks/usePlayerControls';
import { setSidebar } from '../../redux/miscSlice';
const DiscordRPC = require('discord-rpc');
@ -48,6 +49,7 @@ const PlayerBar = () => {
const playQueue = useAppSelector((state) => state.playQueue);
const player = useAppSelector((state) => state.player);
const config = useAppSelector((state) => state.config);
const misc = useAppSelector((state) => state.misc);
const dispatch = useAppDispatch();
const [seek, setSeek] = useState(0);
const [isDragging, setIsDragging] = useState(false);
@ -363,28 +365,40 @@ const PlayerBar = () => {
alignItems: 'flex-start',
}}
>
<Col xs={2} style={{ height: '100%', width: '80px', paddingRight: '10px' }}>
<CoverArtWrapper size={65}>
<LazyLoadImage
src={
playQueue[currentEntryList][playQueue.currentIndex]?.image ||
placeholderImg
}
tabIndex={0}
onClick={() => setShowCoverArtModal(true)}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
setShowCoverArtModal(true);
{(!misc.sidebar.coverArt || !misc.sidebar.expand) && (
<Col xs={2} style={{ height: '100%', width: '80px', paddingRight: '10px' }}>
<CoverArtContainer expand={misc.sidebar.expand}>
<LazyLoadImage
src={
playQueue[currentEntryList][playQueue.currentIndex]?.image ||
placeholderImg
}
}}
alt="trackImg"
effect="opacity"
width="65"
height="65"
style={{ cursor: 'pointer' }}
/>
</CoverArtWrapper>
</Col>
tabIndex={0}
onClick={() => setShowCoverArtModal(true)}
onKeyDown={(e: any) => {
if (e.key === ' ' || e.key === 'Enter') {
setShowCoverArtModal(true);
}
}}
alt="trackImg"
effect="opacity"
width="65"
height="65"
style={{ cursor: 'pointer' }}
/>
<StyledButton
size="xs"
onClick={() => {
dispatch(setSidebar({ coverArt: true }));
settings.setSync('sidebar.coverArt', true);
}}
>
<Icon icon="up" />
</StyledButton>
</CoverArtContainer>
</Col>
)}
<Col xs={2} style={{ minWidth: '120px', maxWidth: '450px', width: '100%' }}>
<Row
style={{

18
src/components/player/styled.tsx

@ -47,6 +47,24 @@ export const PlayerControlIcon = styled(Icon)`
}
`;
export const CoverArtContainer = styled.div<{ expand: boolean }>`
height: 65px;
width: 65px;
.rs-btn {
display: none;
}
&:hover {
.rs-btn {
display: ${(props) => (props.expand ? 'block' : 'none')};
position: absolute;
top: 0;
left: 0;
border-radius: 0px !important;
}
}
`;
export const LinkButton = styled(Button)<{ subtitle?: string }>`
border-radius: 0px;
background: transparent;

12
src/components/shared/setDefaultSettings.ts

@ -139,6 +139,18 @@ const setDefaultSettings = (force: boolean) => {
settings.setSync('musicFolder.music', true);
}
if (force || !settings.hasSync('sidebar.expand')) {
settings.setSync('sidebar.expand', true);
}
if (force || !settings.hasSync('sidebar.width')) {
settings.setSync('sidebar.width', '225px');
}
if (force || !settings.hasSync('sidebar.coverArt')) {
settings.setSync('sidebar.coverArt', true);
}
if (force || !settings.hasSync('pagination.music.recordsPerPage')) {
settings.setSync('pagination.music.recordsPerPage', 50);
}

25
src/redux/miscSlice.ts

@ -4,7 +4,7 @@ import { mockSettings } from '../shared/mockSettings';
import { getImageCachePath, getSongCachePath } from '../shared/utils';
import { Entry } from './playQueueSlice';
const parsedSettings = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
const parsedSettings: any = process.env.NODE_ENV === 'test' ? mockSettings : settings.getSync();
export interface ModalPage {
pageType: string;
@ -44,13 +44,19 @@ export interface ContextMenu {
details?: Entry;
disabledOptions?: ContextMenuOptions[];
}
export interface Sidebar {
expand: boolean;
width: string;
coverArt: boolean;
}
export interface General {
theme: string;
font: string;
modal: Modal;
modalPages: ModalPage[];
imgModal: ImgModal;
expandSidebar: boolean;
sidebar: Sidebar;
isProcessingPlaylist: string[];
contextMenu: ContextMenu;
dynamicBackground: boolean;
@ -73,7 +79,11 @@ const initialState: General = {
show: false,
src: undefined,
},
expandSidebar: false,
sidebar: {
expand: Boolean(parsedSettings.sidebar.expand) || true,
width: String(parsedSettings.sidebar.width) || '225px',
coverArt: Boolean(parsedSettings.sidebar.coverArt) || true,
},
isProcessingPlaylist: [],
contextMenu: {
show: false,
@ -94,8 +104,11 @@ const miscSlice = createSlice({
state.dynamicBackground = action.payload;
},
setExpandSidebar: (state, action: PayloadAction<boolean>) => {
state.expandSidebar = action.payload;
setSidebar: (state, action: PayloadAction<any>) => {
state.sidebar = {
...state.sidebar,
...action.payload,
};
},
setMiscSetting: (state, action: PayloadAction<{ setting: string; value: any }>) => {
@ -208,7 +221,7 @@ export const {
addProcessingPlaylist,
removeProcessingPlaylist,
setContextMenu,
setExpandSidebar,
setSidebar,
setDynamicBackground,
setMiscSetting,
setImgModal,

4
src/shared/mockSettings.ts

@ -24,6 +24,10 @@ export const mockSettings = {
fadeType: 'equalPower',
scrobble: false,
transcode: false,
sidebar: {
expand: false,
width: '165px',
},
pagination: {
music: {
recordsPerPage: 50,

183
yarn.lock

@ -1702,6 +1702,11 @@
jest-diff "^26.0.0"
pretty-format "^26.0.0"
"@types/js-cookie@^2.2.6":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3"
integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==
"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
version "7.0.6"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0"
@ -2236,6 +2241,11 @@
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.1.0.tgz#13ad38f89b6e53d1133bac0006a128217a6ebf92"
integrity sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==
"@xobotyi/scrollbar-width@^1.9.5":
version "1.9.5"
resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d"
integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@ -4052,6 +4062,13 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
copy-to-clipboard@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae"
integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==
dependencies:
toggle-selection "^1.0.6"
core-js-compat@^3.7.0:
version "3.8.0"
resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.8.0.tgz#3248c6826f4006793bd637db608bca6e4cd688b1"
@ -4224,6 +4241,14 @@ css-declaration-sorter@^4.0.1:
postcss "^7.0.1"
timsort "^0.3.0"
css-in-js-utils@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/css-in-js-utils/-/css-in-js-utils-2.0.1.tgz#3b472b398787291b47cfe3e44fecfdd9e914ba99"
integrity sha512-PJF0SpJT+WdbVVt0AOYp9C8GnuruRlL/UFW7932nLWmFLQTaWEzTBQEx7/hn4BuV+WON75iAViSUJLiU3PKbpA==
dependencies:
hyphenate-style-name "^1.0.2"
isobject "^3.0.1"
css-loader@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.0.1.tgz#9e4de0d6636a6266a585bd0900b422c85539d25f"
@ -4300,10 +4325,10 @@ css-tree@1.0.0-alpha.37:
mdn-data "2.0.4"
source-map "^0.6.1"
css-tree@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.1.tgz#30b8c0161d9fb4e9e2141d762589b6ec2faebd2e"
integrity sha512-NVN42M2fjszcUNpDbdkvutgQSlFYsr1z7kqeuCagHnNLBfYor6uP1WL1KrkmdYZ5Y1vTBCIOI/C/+8T98fJ71w==
css-tree@^1.0.0, css-tree@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
dependencies:
mdn-data "2.0.14"
source-map "^0.6.1"
@ -4429,10 +4454,10 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
csstype@^3.0.2:
version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
csstype@^3.0.2, csstype@^3.0.6:
version "3.0.10"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
damerau-levenshtein@^1.0.6:
version "1.0.6"
@ -5934,6 +5959,16 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
fast-shallow-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-shallow-equal/-/fast-shallow-equal-1.0.0.tgz#d4dcaf6472440dcefa6f88b98e3251e27f25628b"
integrity sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==
fastest-stable-stringify@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/fastest-stable-stringify/-/fastest-stable-stringify-2.0.2.tgz#3757a6774f6ec8de40c4e86ec28ea02417214c76"
integrity sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==
fastq@^1.6.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.9.0.tgz#e16a72f338eaca48e91b5c23593bcc2ef66b7947"
@ -6946,6 +6981,11 @@ husky@^4.2.5:
slash "^3.0.0"
which-pm-runs "^1.0.0"
hyphenate-style-name@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
i18next-parser@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/i18next-parser/-/i18next-parser-5.4.0.tgz#ba69a41a2b636f15a1bdfd50aadabc9ec397b335"
@ -7143,6 +7183,13 @@ ini@^1.3.4, ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
inline-style-prefixer@^6.0.0:
version "6.0.1"
resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-6.0.1.tgz#c5c0e43ba8831707afc5f5bbfd97edf45c1fa7ae"
integrity sha512-AsqazZ8KcRzJ9YPN1wMH2aNM7lkWQ8tSPrW5uDk1ziYwiAPWSZnUsC7lfZq+BDqLqz0B4Pho5wscWcJzVvRzDQ==
dependencies:
css-in-js-utils "^2.0.0"
internal-ip@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
@ -8073,6 +8120,11 @@ js-base64@^2.1.8:
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4"
integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==
js-cookie@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8"
integrity sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==
js-sha3@0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
@ -9074,6 +9126,20 @@ nan@^2.12.1, nan@^2.13.2:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
nano-css@^5.3.1:
version "5.3.4"
resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.4.tgz#40af6a83a76f84204f346e8ccaa9169cdae9167b"
integrity sha512-wfcviJB6NOxDIDfr7RFn/GlaN7I/Bhe4d39ZRCJ3xvZX60LVe2qZ+rDqM49nm4YT81gAjzS+ZklhKP/Gnfnubg==
dependencies:
css-tree "^1.1.2"
csstype "^3.0.6"
fastest-stable-stringify "^2.0.2"
inline-style-prefixer "^6.0.0"
rtl-css-js "^1.14.0"
sourcemap-codec "^1.4.8"
stacktrace-js "^2.0.2"
stylis "^4.0.6"
nano-time@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
@ -10837,6 +10903,31 @@ react-test-renderer@^17.0.1:
react-shallow-renderer "^16.13.1"
scheduler "^0.20.1"
react-universal-interface@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"
integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==
react-use@^17.3.2:
version "17.3.2"
resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.3.2.tgz#448abf515f47c41c32455024db28167cb6e53be8"
integrity sha512-bj7OD0/1wL03KyWmzFXAFe425zziuTf7q8olwCYBfOeFHY1qfO1FAMjROQLsLZYwG4Rx63xAfb7XAbBrJsZmEw==
dependencies:
"@types/js-cookie" "^2.2.6"
"@xobotyi/scrollbar-width" "^1.9.5"
copy-to-clipboard "^3.3.1"
fast-deep-equal "^3.1.3"
fast-shallow-equal "^1.0.0"
js-cookie "^2.2.1"
nano-css "^5.3.1"
react-universal-interface "^0.6.2"
resize-observer-polyfill "^1.5.1"
screenfull "^5.1.0"
set-harmonic-interval "^1.0.1"
throttle-debounce "^3.0.1"
ts-easing "^0.2.0"
tslib "^2.1.0"
react-virtualized-auto-sizer@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.6.tgz#66c5b1c9278064c5ef1699ed40a29c11518f97ca"
@ -11235,6 +11326,11 @@ reselect@^4.0.0:
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-alpn@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c"
@ -11429,6 +11525,13 @@ rsvp@~3.2.1:
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a"
integrity sha1-B8tKXfJa3Z6Cbrxn3Mn9idsn2Eo=
rtl-css-js@^1.14.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.15.0.tgz#680ed816e570a9ebccba9e1cd0f202c6a8bb2dc0"
integrity sha512-99Cu4wNNIhrI10xxUaABHsdDqzalrSRTie4GeCmbGVuehm4oj+fIy8fTzB+16pmKe8Bv9rl+hxIBez6KxExTew==
dependencies:
"@babel/runtime" "^7.1.2"
run-parallel@^1.1.9:
version "1.1.10"
resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.10.tgz#60a51b2ae836636c81377df16cb107351bcd13ef"
@ -11570,6 +11673,11 @@ schema-utils@^3.0.0:
ajv "^6.12.5"
ajv-keywords "^3.5.2"
screenfull@^5.1.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.2.0.tgz#6533d524d30621fc1283b9692146f3f13a93d1ba"
integrity sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
@ -11695,6 +11803,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0:
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
set-harmonic-interval@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249"
integrity sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==
set-immediate-shim@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
@ -11938,6 +12051,11 @@ source-map-url@^0.4.0:
resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
source-map@0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
source-map@^0.4.2:
version "0.4.4"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
@ -11960,6 +12078,11 @@ source-map@^0.7.3, source-map@~0.7.2:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
spawn-command@^0.0.2-1:
version "0.0.2-1"
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0"
@ -12058,6 +12181,13 @@ stable@^0.1.8:
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
stack-generator@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36"
integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==
dependencies:
stackframe "^1.1.1"
stack-utils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz#cd5f030126ff116b78ccb3c027fe302713b61277"
@ -12070,6 +12200,23 @@ stackframe@^1.1.1:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303"
integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA==
stacktrace-gps@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a"
integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==
dependencies:
source-map "0.5.6"
stackframe "^1.1.1"
stacktrace-js@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b"
integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==
dependencies:
error-stack-parser "^2.0.6"
stack-generator "^2.0.5"
stacktrace-gps "^3.0.4"
stat-mode@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/stat-mode/-/stat-mode-1.0.0.tgz#68b55cb61ea639ff57136f36b216a291800d1465"
@ -12329,6 +12476,11 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
stylis@^4.0.6:
version "4.0.13"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
sumchecker@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42"
@ -12520,6 +12672,11 @@ throat@^5.0.0:
resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
throttle-debounce@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-3.0.1.tgz#32f94d84dfa894f786c9a1f290e7a645b6a19abb"
integrity sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==
through2-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
@ -12642,6 +12799,11 @@ to-through@^2.0.0:
dependencies:
through2 "^2.0.3"
toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
@ -12700,6 +12862,11 @@ truncate-utf8-bytes@^1.0.0:
dependencies:
utf8-byte-length "^1.0.1"
ts-easing@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ts-easing/-/ts-easing-0.2.0.tgz#c8a8a35025105566588d87dbda05dd7fbfa5a4ec"
integrity sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==
tsc-silent@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/tsc-silent/-/tsc-silent-1.2.1.tgz#c219789b7b1ab8e475fe3069d061d5ab49167d3f"

Loading…
Cancel
Save