feat(app): beatconnect client revamp (#188)

This commit is contained in:
Yannis Petitot
2020-09-20 22:28:41 +02:00
committed by GitHub
parent 53dcbe80e0
commit efb7a29174
31 changed files with 288 additions and 179 deletions
+1 -1
View File
@@ -1 +1 @@
4632
71847
+4 -1
View File
@@ -5,5 +5,8 @@
},
"editor.formatOnSave": true,
"editor.tabSize": 2,
"files.eol": "\n"
"files.eol": "\n",
"extensions.ignoreRecommendations": true,
"files.autoSave": "off",
"debug.allowBreakpointsEverywhere": true
}
+1 -1
View File
@@ -17,7 +17,7 @@ The official client for [Beatconnect](https://beatconnect.io) which is a mirror
## Quick Tour
- This App gives you access to all the beatmaps mirrored on [Beatconnect](https://beatconnect.io). You can downloads multiple beatmaps that will be automaticaly imported into osu! </br>
<img src="https://cdn.discordapp.com/attachments/414474227710820352/663024989984915467/unknown_2.jpg">
<img src="https://user-images.githubusercontent.com/46972108/90391453-75d31600-e08d-11ea-9ad0-bdb881219fdd.png">
- You can launch an <b>IRC bot</b> from the app that will make all [available commands](./docs/commands.md) usable to peoples pming you and from all the matches chats that the bot is connected to. (how to connect docs soon..)</br>
Comming with the <b>autobeat</b> feature that send the Beatconnect download link in the #multuplayer channel each time host change the beatmap
+10 -8
View File
@@ -60,12 +60,14 @@ let make = (~children) => {
},
);
Audio.onplay(
audio,
_e => {
Js.log("PAUSEEEE");
setPlayingState(oldState => {...oldState, isPlaying: true});
},
Audio.onplay(audio, e => {
setPlayingState(oldState =>
{...oldState, isPlaying: e.target.readyState === 4}
)
});
Audio.oncanplay(audio, _e =>
setPlayingState(oldState => {...oldState, isPlaying: true})
);
let setPreviewAudio = (beatmapSetId: int) => {
@@ -82,7 +84,7 @@ let make = (~children) => {
);
setPreviewAudio(beatmapSetId);
Audio.play(audio);
setPlayingState(_oldState => {isPlaying: true, beatmapSetId, songTitle});
setPlayingState(_oldState => {isPlaying: false, beatmapSetId, songTitle});
};
let pause = () => {
@@ -96,4 +98,4 @@ let make = (~children) => {
let value = {playingState, pause, setAudio, setVolume};
<Provider value> children </Provider>;
};
};
+50
View File
@@ -0,0 +1,50 @@
export const getFadeIn = () => ({
'@keyframes fadeIn': {
from: {
opacity: 0,
},
to: {
opacity: 1,
},
},
});
export const getAppear = () => ({
'@keyframes appear': {
from: {
filter: 'blur(25px)',
opacity: 0,
},
to: {
filter: 'blur(0px)',
opacity: 1,
},
},
});
export const sectionSwitchAnimation = () => ({
opacity: 0,
animation: '140ms ease-in forwards $fadeIn',
});
export const getDragRegion = (override, header) => ({
WebkitAppRegion: `drag ${override ? '!important' : ''}`,
position: 'relative',
...(header
? {
'&::after': {
content: "''",
height: '24px',
display: 'block',
position: 'absolute',
width: '80px',
right: 0,
top: 0,
WebkitAppRegion: `no-drag !important`,
},
}
: null),
'& > *': {
WebkitAppRegion: `no-drag ${override ? '!important' : ''}`,
},
});
+4 -1
View File
@@ -4,8 +4,10 @@ import { createUseStyles } from 'react-jss';
import Nav from './modules/Nav';
import TitleBar from './modules/common/TitleBar.bs';
import config from '../shared/config';
import { getAppear } from './helpers/css.utils';
const useStyles = createUseStyles({
...getAppear(),
App: {
'-webkit-font-smoothing': 'antialiased',
userSelect: 'none',
@@ -15,7 +17,8 @@ const useStyles = createUseStyles({
sans-serif`,
},
appContentWrapper: {
paddingTop: config.display.titleBarHeight,
animation: '500ms ease-in forwards $appear',
overflow: 'hidden',
},
});
@@ -10,11 +10,13 @@ import DropDown from '../../common/DropDown';
import renderIcons from '../../../helpers/renderIcons';
import config from '../../../../shared/config';
import Button from '../../common/Button';
import { getDragRegion } from '../../../helpers/css.utils';
const { trackEvent } = remote.getGlobal('tracking');
const useStyle = createUseStyles({
Search: {
...getDragRegion(true),
width: '100%',
display: 'inline-flex',
'& div, select, input, label': {
@@ -27,6 +29,7 @@ const useStyle = createUseStyles({
},
hideDownloaded: {
cursor: 'pointer',
margin: 'auto 5px',
},
searchButtonWrapper: {
margin: 8,
@@ -98,14 +101,13 @@ const Search = ({ lastSearch, isBusy, beatmapCount, skeletonBeatmaps }) => {
onKeyDown={searchOnEnter}
onBlur={() => execSearch()}
/>
<div className={classes.right} />
<div
className={classes.hideDownloaded}
onClick={togleHideDownloaded}
title="Hide downloaded beatmaps"
role="button"
tabIndex={0}
>
<div className={classes.right} />
{renderIcons({ name: 'Verified', color: search.hideDownloaded && theme.palette.primary.accent })}
</div>
</div>
+26 -17
View File
@@ -1,9 +1,9 @@
/* eslint-disable camelcase */
import React, { useEffect, useContext, useState, useRef } from 'react';
import React, { useEffect, useContext, useState, useRef, useCallback } from 'react';
import _ from 'underscore';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { FixedSizeGrid } from 'react-window';
import { VariableSizeGrid } from 'react-window';
import injectSheet, { useTheme } from 'react-jss';
import Beatmap from '../common/Beatmap';
import Search from './components/Search';
@@ -13,14 +13,17 @@ import { HistoryContext } from '../../Providers/HistoryProvider';
import BeatmapSkeleton from '../common/Beatmap/beatmap.skeleton';
import config from '../../../shared/config';
import { saveLastScrollPosition } from './reducer/actions';
import { getFadeIn, sectionSwitchAnimation } from '../../helpers/css.utils';
const styles = {
...getFadeIn(),
Beatmaps: {
outline: 'none',
...sectionSwitchAnimation(),
},
};
const Beatmaps = ({ searchResults, classes, setHeaderContent, window, panelExpended }) => {
const Beatmaps = ({ searchResults, classes, setHeaderContent, window }) => {
const theme = useTheme();
const gridContainer = useRef(null);
const [autoDl, setAutoDl] = useState(false);
@@ -35,11 +38,10 @@ const Beatmaps = ({ searchResults, classes, setHeaderContent, window, panelExpen
lastSearch.current = search;
if (gridContainer.current) gridContainer.current.childNodes[0].scrollTop = lastScrollPosition.current;
}
const gridWidth =
window.width - (panelExpended ? config.display.sidePanelExpandedLength : config.display.sidePanelCompactedLength);
const gridHeight = window.height - (config.display.titleBarHeight + config.display.topBarHeight);
const displayGrid = gridWidth >= 1200;
const rowCount = displayGrid ? Math.ceil(beatmaps.length / 2) : beatmaps.length;
const gridWidth = window.width - config.display.sidePanelCompactedLength - 1;
const gridHeight = window.height - 1;
const displayGrid = gridWidth >= 1000;
const rowCount = (displayGrid ? Math.ceil(beatmaps.length / 2) : beatmaps.length) + 1; // Add one for the invisible top placeholder
const canLoadMore = hideDownloaded ? !lastPage : beatmaps.length % 50 === 0;
const onScroll = ({ scrollTop }) => {
lastScrollPosition.current = scrollTop;
@@ -65,10 +67,15 @@ const Beatmaps = ({ searchResults, classes, setHeaderContent, window, panelExpen
return () => saveLastScrollPosition(lastScrollPosition.current);
}, []);
const getColumnWidth = useCallback(() => (displayGrid ? gridWidth / 2 - 9 : gridWidth - 18), [
displayGrid,
gridWidth,
]);
const renderBeatmaps = ({ columnIndex, rowIndex, style }) => {
const beatmap =
(displayGrid && beatmaps[(rowIndex === 0 ? 0 : rowIndex + rowIndex) + columnIndex]) || beatmaps[rowIndex];
if (beatmap === 0) return <BeatmapSkeleton style={style} />;
if (rowIndex === 0) return <div style={{ height: `${config.display.topBarHeight}px` }} />;
const beatmap = displayGrid ? beatmaps[rowIndex * 2 + columnIndex - 2] : beatmaps[rowIndex - 1];
if (beatmap === 0) return <BeatmapSkeleton style={style} rowIndex={rowIndex} />;
if (!beatmap) return <div style={style} className="NoBeatmap" />;
return (
<div style={style}>
@@ -91,28 +98,30 @@ const Beatmaps = ({ searchResults, classes, setHeaderContent, window, panelExpen
role="button"
ref={gridContainer}
>
<FixedSizeGrid
<VariableSizeGrid
columnCount={displayGrid ? 2 : 1}
columnWidth={displayGrid ? gridWidth / 2 - 9 : gridWidth - 18}
columnWidth={getColumnWidth}
estimatedColumnWidth={getColumnWidth()}
estimatedRowHeight={250}
rowCount={rowCount}
rowHeight={250}
rowHeight={index => (index === 0 ? config.display.topBarHeight : 250)}
overscanRowCount={7}
height={gridHeight}
width={gridWidth}
onItemsRendered={newItemsRendered}
onScroll={onScroll}
initialScrollTop={lastScrollPosition.current}
key={getColumnWidth()}
>
{renderBeatmaps}
</FixedSizeGrid>
</VariableSizeGrid>
</div>
);
};
const mapStateToProps = ({ app, settings, beatmaps }) => ({
const mapStateToProps = ({ app, beatmaps }) => ({
searchResults: beatmaps.searchResults,
window: app.window,
panelExpended: settings.userPreferences.sidePanelExpended,
});
export default compose(
connect(mapStateToProps),
+13 -1
View File
@@ -1,15 +1,27 @@
import React, { useEffect } from 'react';
import { createUseStyles } from 'react-jss';
import Start from './components/Start';
import Matchs from './Matchs';
import config from '../../../shared/config';
import { getFadeIn, sectionSwitchAnimation } from '../../helpers/css.utils';
const useStyles = createUseStyles({
...getFadeIn(),
Bot: {
paddingTop: `${config.display.topBarHeight}px`,
...sectionSwitchAnimation(),
},
});
const Bot = ({ connected, bot, theme, setHeaderContent }) => {
const classes = useStyles();
useEffect(() => {
setHeaderContent(<Start connected={connected} />);
return () => setHeaderContent(null);
}, [setHeaderContent, connected, theme]);
return (
<div className="menuContainer">
<div className={`menuContainer ${classes.Bot}`}>
<Matchs bot={bot} connected={connected} />
</div>
);
@@ -1,5 +1,5 @@
import React, { useContext } from 'react';
import { FixedSizeList } from 'react-window';
import { VariableSizeList } from 'react-window';
import { connect } from 'react-redux';
import { HistoryContext } from '../../../Providers/HistoryProvider';
import DownloadsItem from './Item';
@@ -7,26 +7,35 @@ import config from '../../../../shared/config';
const DownloadedItems = ({ window }) => {
const { history } = useContext(HistoryContext);
const items = [];
Object.values(history).forEach(item => {
const sortedHistory = Object.values(history).sort((itemA, itemB) => itemB.date - itemA.date);
const items = sortedHistory.map(item => {
const { id, date, name } = item;
items.push(<DownloadsItem id={id} date={date} name={name} status="downloaded" key={id} />);
return isScrolling => (
<DownloadsItem id={id} date={date} name={name} status="downloaded" key={id} isScrolling={isScrolling} />
);
});
items.sort((a, b) => b.props.date - a.props.date);
const renderItems = ({ index, style }) => <div style={style}>{items[index]}</div>;
const renderItems = ({ index, style, isScrolling }) =>
index === 0 ? <div /> : <div style={style}>{items[index - 1](isScrolling)}</div>;
return (
<div className="downloadMenu DownloadsItem" style={{ width: '100%' }}>
<FixedSizeList
height={window.height - (config.display.titleBarHeight + config.display.topBarHeight)}
itemCount={items.length}
itemSize={130}
overscanCount={2}
width="100%"
className="downloadMenu customScroll"
>
{items.length > 0 ? renderItems : 'The beatmaps you download will go here'}
</FixedSizeList>
{items.length ? (
<VariableSizeList
height={window.height}
itemCount={items.length + 1}
itemSize={index => (index === 0 ? config.display.topBarHeight : 130)}
overscanCount={5}
width="100%"
className="downloadMenu customScroll"
useIsScrolling
>
{renderItems}
</VariableSizeList>
) : (
<span style={{ marginTop: `calc(${config.display.topBarHeight}px + 1rem)`, display: 'block' }}>
The beatmaps you download will go here
</span>
)}
</div>
);
};
@@ -1,4 +1,4 @@
import React, { useState, memo } from 'react';
import React, { useState } from 'react';
import injectSheet, { useTheme } from 'react-jss';
import { Text } from 'react-desktop';
import { shell } from 'electron';
@@ -10,19 +10,20 @@ import PreviewBeatmapBtn from '../../../common/Beatmap/PreviewBeatmapBtn';
import styles from './ItemStyles';
import Button from '../../../common/Button';
const DownloadsItem = ({ id, name, item, date, status, progress, speed, classes, cancel }) => {
const DownloadsItem = ({ id, name, item, date, status, progress, speed, classes, cancel, isScrolling }) => {
const [isPaused, setIsPaused] = useState(false);
const theme = useTheme();
const toggleDownload = () => {
item.isPaused() ? item.resume() : item.pause();
if (item.isPaused()) item.resume();
else item.pause();
setIsPaused(item.isPaused());
};
return (
<div className={classes.DownloadsItem}>
<div className={classes.fade}>
<Cover url={`https://assets.ppy.sh/beatmaps/${id}/covers/cover.jpg`} height={130} />
<Cover url={`https://assets.ppy.sh/beatmaps/${id}/covers/cover.jpg`} height={130} canLoad={!isScrolling} />
</div>
{progress ? (
<div className={classes.downloadInfos}>
@@ -62,4 +63,4 @@ const areEqual = (prevProps, nextProps) => {
}
return prevProps.status === nextProps.status;
};
export default memo(injectSheet(styles)(DownloadsItem), areEqual);
export default injectSheet(styles)(DownloadsItem);
+6 -1
View File
@@ -33,7 +33,12 @@ const Downloads = ({ setHeaderContent }) => {
return (
<div className="menuContainer Downloads" style={{ transition: 'background 0ms' }}>
<NavPanel paneExpandedLength={150} defaultIsPanelExpanded sidePanelBackground={theme.palette.secondary.dark}>
<NavPanel
paneExpandedLength={150}
defaultIsPanelExpanded
sidePanelBackground={theme.palette.secondary.dark}
subPanel
>
{renderItem(`Queued`, <DownloadsInQueue queue={queue} removeItemfromQueue={removeItemfromQueue} />)}
{renderItem('Downloaded', <DownloadedItems theme={theme} />)}
</NavPanel>
@@ -105,14 +105,20 @@ const BeatmapPackDetail = ({ classes, windowSize, panelExpended, pack, select })
)
: pack.beatmapsets;
const listWidth =
windowSize.width -
(panelExpended ? config.display.sidePanelExpandedLength : config.display.sidePanelCompactedLength);
const listHeight = windowSize.height - (config.display.titleBarHeight + config.display.topBarHeight);
const itemCount = filteredBeatmapsets.length + 1; // +1 for top spacer
const listWidth = windowSize.width - config.display.sidePanelCompactedLength;
const listHeight = windowSize.height;
// optimization needed (useCallback or memo ?) k
const renderRow = ({ index, style }) => {
const isPlaying = audioPlayer.playingState.beatmapSetId === filteredBeatmapsets[index].id;
if (index === 0) {
return <div style={style} key="PackListSpacer" />;
}
const offsetedIndex = index - 1;
const item = filteredBeatmapsets[offsetedIndex];
const { id, title, artist } = item;
const isPlaying = audioPlayer.playingState.beatmapSetId === id;
const wrapperStyle = {
backgroundColor: isPlaying && 'rgba(255,255,255,.05)',
};
@@ -120,36 +126,28 @@ const BeatmapPackDetail = ({ classes, windowSize, panelExpended, pack, select })
opacity: isPlaying && 0.9,
backgroundImage: `url(${reqImgAssets(isPlaying ? './pause-button.png' : './play-button.png')})`,
};
return (
<div style={style} key={filteredBeatmapsets[index].id}>
<div style={style} key={id}>
<div className={classes.listItem} style={wrapperStyle}>
<div
className={`${classes.thumbnail} thumbnail`}
style={{ backgroundImage: `url(${getThumbUrl(filteredBeatmapsets[index].id)})` }}
>
<div className={`${classes.thumbnail} thumbnail`} style={{ backgroundImage: `url(${getThumbUrl(id)})` }}>
<div
className="playIco clickable"
style={playIcoStyle}
role="button"
onClick={() =>
playPreview(
filteredBeatmapsets[index].id,
isPlaying,
`${filteredBeatmapsets[index].title} - ${filteredBeatmapsets[index].artist}`,
)
}
onClick={() => playPreview(id, isPlaying, `${title} - ${artist}`)}
/>
</div>
<div className={classes.title}>{filteredBeatmapsets[index].title}</div>
<div className={classes.artist}>{filteredBeatmapsets[index].artist}</div>
<div className={classes.title}>{title}</div>
<div className={classes.artist}>{artist}</div>
<DownloadBeatmapBtn
beatmapSet={filteredBeatmapsets[index]}
beatmapSet={item}
title="Download"
noStyle
className={`${classes.downloadButton} clickable`}
/>
<div
onClick={() => shell.openExternal(getBeatmapInfosUrl(filteredBeatmapsets[index]))}
onClick={() => shell.openExternal(getBeatmapInfosUrl(item))}
role="button"
title="See beatmap page"
className={`${classes.beatmapPageButton} clickable`}
@@ -163,7 +161,7 @@ const BeatmapPackDetail = ({ classes, windowSize, panelExpended, pack, select })
return (
<div className={classes.wrapper}>
<List height={listHeight} itemCount={filteredBeatmapsets.length} itemSize={50} width={listWidth}>
<List height={listHeight} itemCount={itemCount} itemSize={50} width={listWidth}>
{renderRow}
</List>
</div>
+4
View File
@@ -7,9 +7,13 @@ import BeatmapPackDetail from './BeatmapPackDetail';
import Header from './Header';
import config from '../../../shared/config';
import Disabled from './Disabled';
import { getFadeIn, sectionSwitchAnimation } from '../../helpers/css.utils';
const styles = {
...getFadeIn(),
Packs: {
...sectionSwitchAnimation(),
paddingTop: `${config.display.topBarHeight}px`,
textAlign: 'auto',
margin: 'auto 5rem',
display: 'grid',
+6 -1
View File
@@ -9,7 +9,7 @@ import { make as CheckBox } from '../common/Checkbox.bs';
const useStyles = createUseStyles({
settingWrapper: {
margin: '0 4vmin',
paddingTop: '20px',
paddingTop: '70px',
justifyContent: 'space-evenly',
'& p': {
fontSize: '1.05rem',
@@ -65,6 +65,11 @@ const useStyles = createUseStyles({
clickable: {
cursor: 'pointer',
},
description: {
fontWeight: 100,
fontSize: '0.8rem',
color: 'rgba(255, 255, 255, 0.75)',
},
});
const Setting = ({ settingCategory }) => {
+6 -1
View File
@@ -204,7 +204,12 @@ const Settings = ({ userPreferences }) => {
return (
<div className="menuContainer Settings" style={{ transition: 'background 0ms', textAlign: 'center' }}>
<NavPanel paneExpandedLength={150} defaultIsPanelExpanded sidePanelBackground={theme.palette.secondary.dark}>
<NavPanel
paneExpandedLength={150}
defaultIsPanelExpanded
sidePanelBackground={theme.palette.secondary.dark}
subPanel
>
{renderItems()}
</NavPanel>
</div>
+13 -12
View File
@@ -1,14 +1,18 @@
import React, { useState, useEffect, memo } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import config from '../../../../shared/config';
const Cover = ({ url, width, height, paddingBottom, noFade }) => {
const Cover = ({ url, width, height, paddingBottom, noFade, canLoad = true }) => {
const [loaded, isLoaded] = useState(false);
const cover = new Image();
cover.onload = () => isLoaded(url);
const coverRef = useRef();
useEffect(() => {
if (loaded !== url) cover.setAttribute('src', url);
}, [url, loaded]);
if (!coverRef.current && canLoad) {
coverRef.current = new Image();
coverRef.current.onload = () => isLoaded(true);
}
if (canLoad && !loaded) {
coverRef.current.setAttribute('src', url);
}
}, [canLoad, coverRef.current]);
const style = {
opacity: noFade ? 1 : loaded ? 1 : 0,
@@ -20,13 +24,10 @@ const Cover = ({ url, width, height, paddingBottom, noFade }) => {
backgroundPosition: 'center center',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
backgroundImage: `url('${url}')`,
backgroundImage: loaded && `url('${url}')`,
backgroundColor: 'rgba(255, 255, 255, 0.02)',
};
return <div className="cover" style={style} />;
};
const areEqual = (prevProps, nextProps) => {
return JSON.stringify(prevProps) === JSON.stringify(nextProps);
};
export default memo(Cover, areEqual);
export default Cover;
@@ -24,16 +24,16 @@ const useStyle = createUseStyles({
},
});
const BeatmapSkeleton = ({ style }) => {
const BeatmapSkeleton = ({ style, rowIndex }) => {
const theme = useTheme();
const classes = useStyle({ theme });
return (
<div style={style}>
<div className={`${classes.beatmap} Beatmap`}>
<Skeleton className={classes.cover} />
<Skeleton className={classes.text} style={{ width: '9%' }} />
<Skeleton className={classes.text} style={{ width: '16%' }} />
<Skeleton className={classes.buttons} />
<Skeleton className={classes.cover} delay={rowIndex} />
<Skeleton className={classes.text} style={{ width: '9%' }} delay={rowIndex} />
<Skeleton className={classes.text} style={{ width: '16%' }} delay={rowIndex} />
<Skeleton className={classes.buttons} delay={rowIndex} />
</div>
</div>
);
+4 -3
View File
@@ -23,6 +23,7 @@ const useStyles = createUseStyles({
background: ({ theme }) => theme.palette.primary.main,
margin: ({ margin }) => margin || '1.3vh auto',
paddingBottom: '10px',
borderRadius: '5px',
transition: ({ bpm }) => `opacity ${bpmToBps(bpm)}s`,
'&:hover': {
opacity: 1.1,
@@ -55,14 +56,14 @@ const useStyles = createUseStyles({
'@keyframes pulse': {
'0%': {
opacity: 0.1,
transform: 'scale(.995)',
transform: 'scale(.990)',
},
'60%': {
transform: 'scale(1.000)',
transform: 'scale(0.995)',
opacity: 0.65,
},
'100%': {
transform: 'scale(1.015)',
transform: 'scale(1.010)',
opacity: 0,
},
},
@@ -1,18 +1,27 @@
import React from 'react';
import { createUseStyles, useTheme } from 'react-jss';
import config from '../../../../shared/config';
import { getDragRegion } from '../../../helpers/css.utils';
const useStyle = createUseStyles({
header: {
position: 'relative',
...getDragRegion(false, true),
position: 'absolute',
height: '48px',
display: 'flex',
alignItems: 'center',
fontSize: '15px',
textTransform: 'uppercase',
padding: '0px 24px',
marginLeft: '48px',
overflow: 'hidden',
cursor: 'default',
userSelect: 'none',
backdropFilter: 'saturate(180%) blur(5px)',
backgroundColor: 'rgba(0,0,0,0.5)',
borderBottom: 'inset 1px hsla(0,0%,100%,0.1)',
zIndex: 300,
width: `calc(100% - ${config.display.sidePanelCompactedLength * 2}px)`,
color: ({ theme }) => (theme.dark ? '#fff' : '#000'),
},
divider: {
@@ -5,7 +5,7 @@ import config from '../../../../shared/config';
const useStyle = createUseStyles({
contentContainer: {
height: `calc(100vh - ${config.display.titleBarHeight}px)`,
height: `100vh`,
position: 'relative',
flexGrow: 1,
flexShrink: 1,
@@ -14,10 +14,12 @@ const useStyle = createUseStyles({
flexDirection: 'column',
},
contentWrapper: {
height: '100vh',
display: 'flex',
flex: '1 1 0%',
flexDirection: 'column',
padding: '0px',
paddingLeft: `${config.display.sidePanelCompactedLength}px`,
background: ({ theme }) => theme.palette.primary.dark,
textAlign: 'center',
fontSize: 'calc(10px + 2vmin)',
@@ -25,15 +27,14 @@ const useStyle = createUseStyles({
backgroundColor: ({ theme }) => theme.palette.primary.dark,
textRendering: 'optimizelegibility',
fontFamily: 'Open Sans, sans - serif',
height: `calc(100vh - ${config.display.titleBarHeight + config.display.topBarHeight}px)`,
width: `calc(100vw - ${config.display.sidePanelCompactedLength}px)`, // TODO Ugly when sidePanel expends but will be fixed in next revamp
width: `calc(100vw - ${config.display.sidePanelCompactedLength}px)`,
overflow: 'auto',
'&, & *': {
'&::-webkit-scrollbar': {
width: '8px',
},
'&::-webkit-scrollbar-track': {
background: ({ theme }) => theme.palette.primary.main,
background: 'transparent',
},
'&::-webkit-scrollbar-thumb': {
background: ({ theme }) => theme.palette.primary.accent,
@@ -45,7 +46,7 @@ const useStyle = createUseStyles({
const Item = ({ title, children, header }) => {
const [headerContent, setHeaderContent] = useState(null);
const theme = useTheme();
const classes = useStyle({ theme });
const classes = useStyle({ theme, hasHeader: !!header });
return header ? (
<div className={classes.contentContainer}>
@@ -99,7 +99,7 @@ const PlayOsu = ({ onSelect, osuGamePath, ...otherProps }) => {
return (
<a data-radium="true" className={classes.a} onClick={launchOsu} role="tab">
<span className={`${classes.tooltiptext} tooltiptext`}>{osuIsRunning ? 'Playing Osu!' : 'Play Osu!'}</span>
<span className={`${classes.tooltiptext} tooltiptext`}>{osuIsRunning ? 'Playing !' : 'Play!'}</span>
<span data-radium="true" className={classes.span}>
<div className={`${classes.indicator} indicator`} />
<div data-radium="true" className={classes.i}>
@@ -110,7 +110,7 @@ const PlayOsu = ({ onSelect, osuGamePath, ...otherProps }) => {
})}
</div>
<span data-radium="true" className={classes.title}>
{osuIsRunning ? 'Playing Osu!' : 'Play Osu!'}
{osuIsRunning ? 'Playing !' : 'Play !'}
</span>
</span>
</a>
@@ -9,6 +9,10 @@ const useStyle = createUseStyles({
height: '44px',
backgroundColor: 'transparent',
cursor: 'pointer',
color: 'rgb(255, 255, 255)',
letterSpacing: '0.4pt',
fontSize: '15px',
padding: '0px 16px',
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)',
},
@@ -19,16 +23,6 @@ const useStyle = createUseStyles({
visibility: props => (props.expended ? 'hidden' : 'visible'),
},
},
span: {
display: 'flex',
alignItems: 'center',
color: 'rgb(255, 255, 255)',
fontSize: '15px',
letterSpacing: '0.4pt',
padding: '0px 16px',
transition: 'transform 0.1s ease-in 0s',
userSelect: 'none',
},
i: {
marginRight: '8px',
height: '44px',
@@ -47,18 +41,6 @@ const useStyle = createUseStyles({
title: {
visibility: props => (props.expended ? 'visible' : 'hidden'),
},
tooltiptext: {
visibility: 'hidden',
width: '120px',
backgroundColor: ({ theme }) => theme.palette.primary.main,
color: '#fff',
textAlign: 'center',
padding: '5px 0',
borderRadius: '6px',
position: 'absolute',
left: '105%',
zIndex: 1,
},
});
const Tab = ({ icon, title, onSelect, ...otherProps }) => {
@@ -66,15 +48,12 @@ const Tab = ({ icon, title, onSelect, ...otherProps }) => {
const classes = useStyle({ ...otherProps, theme });
return (
<a data-radium="true" className={classes.a} onClick={onSelect} role="tab">
<span className={`${classes.tooltiptext} tooltiptext`}>{title}</span>
<span data-radium="true" className={classes.span}>
<div className={`${classes.indicator} indicator`} />
<i data-radium="true" className={classes.i}>
{icon}
</i>
<span data-radium="true" className={classes.title}>
{title}
</span>
<div className={`${classes.indicator} indicator`} />
<i data-radium="true" className={classes.i}>
{icon}
</i>
<span data-radium="true" className={classes.title}>
{title}
</span>
</a>
);
@@ -5,20 +5,29 @@ import VolumeControl from './VolumeControl';
import TasksControl from './TasksControl';
import PlayOsu from './PlayOsu';
import config from '../../../../../shared/config';
import { getDragRegion } from '../../../../helpers/css.utils';
const styles = {
SidePanel: {
...getDragRegion(),
cursor: 'default',
userSelect: 'none',
display: 'flex',
position: 'relative',
position: ({ subPanel }) => (subPanel ? 'relative' : 'absolute'),
marginTop: ({ subPanel }) => subPanel && '48px',
flexGrow: 0,
flexShrink: 0,
flexDirection: 'column',
overflow: 'visible',
width: props => (props.expended ? props.panelExpandedLength : props.panelCompactedLength),
backgroundColor: props => props.background,
height: ({ subPanel }) => (subPanel ? `calc(100vh - ${config.display.topBarHeight}px)` : '100%'),
transition: `width ${config.display.defaultTransitionDuration}`,
left: 0,
top: 0,
backgroundColor: 'rgba(0,0,0,0.5)',
zIndex: ({ subPanel }) => (subPanel ? 2000 : 3000),
backdropFilter: 'saturate(180%) blur(5px)',
borderRight: 'inset 1px hsla(0,0%,100%,0.1)',
},
head: {
height: config.display.topBarHeight,
@@ -34,15 +43,19 @@ const styles = {
},
};
const SidePanel = ({ classes, items, expended, expendable, volume, tasks, setExpended }) => {
const SidePanel = ({ classes, items, expended, expendable, tasks, setExpended, subPanel }) => {
const itemTab = () =>
items.map((item, i) => {
if (items.length - i === 1) {
return (
<>
{tasks && <TasksControl expended={expended} tasks={tasks} />}
{volume && <PlayOsu expended={expended} />}
{volume && <VolumeControl expended={expended} />}
{!subPanel && (
<>
<TasksControl expended={expended} tasks={tasks} />
<PlayOsu expended={expended} />
<VolumeControl expended={expended} />
</>
)}
<Tab {...item.props} expended={expended} />
</>
);
@@ -23,11 +23,13 @@ const NavPanel = ({
sidePanelBackground,
children,
onExpended,
subPanel,
}) => {
const [isExpended, setIsExpended] = useState(defaultIsPanelExpanded);
return (
<div className={classes.NavPanel}>
<SidePanel
subPanel={subPanel}
items={children}
expended={isExpended}
setExpended={expended => {
+4 -3
View File
@@ -3,13 +3,13 @@ import injectSheet from 'react-jss';
const styles = {
'@keyframes color': {
'0%': {
'10%': {
backgroundColor: '#5c5c5c',
},
'50%': {
backgroundColor: '#7d7d7d',
},
'100': {
'90': {
backgroundColor: '#5c5c5c',
},
},
@@ -19,8 +19,9 @@ const styles = {
position: 'relative',
overflow: 'hidden',
animationName: '$color',
animationDuration: '2s',
animationDuration: '2.4s',
animationIterationCount: 'infinite',
animationDelay: props => `0.${props.delay}s`,
},
};
+9 -25
View File
@@ -1,28 +1,13 @@
open Css;
let makeWrapperStyle = (~height as h) =>
let makeWrapperStyle = () =>
style([
unsafe("WebkitAppRegion", "drag"),
backgroundColor(hex("171717")),
zIndex(100),
zIndex(500),
position(fixed),
height(px(h)),
width(pct(100.)),
right(zero),
display(`flex),
alignItems(center),
]);
let makeTitleStyle = () =>
style([
textOverflow(`ellipsis),
whiteSpace(`nowrap),
overflow(`hidden),
maxWidth(pct(40.)),
paddingLeft(px(12)),
fontSize(px(12)),
color(white),
flex3(~grow=1., ~shrink=1., ~basis=zero),
]);
let makeControlStyle = (~bgColor=?, ~spacer=false, ()) =>
style([
unsafe("WebkitAppRegion", "no-drag"),
@@ -32,7 +17,7 @@ let makeControlStyle = (~bgColor=?, ~spacer=false, ()) =>
[
backgroundColor(
switch (bgColor) {
| None => rgba(255, 255, 255, 0.15)
| None => rgba(255, 255, 255, 0.25)
| Some(c) => c
},
),
@@ -59,19 +44,18 @@ let make = (~height: int) => {
BrowserWindow.isMaximized(window)
? BrowserWindow.unmaximize(window) : BrowserWindow.maximize(window);
<div className={makeWrapperStyle(~height)}>
<div className={makeTitleStyle()}> title->React.string </div>
<div className={makeWrapperStyle()}>
<div
className={makeControlStyle(~spacer=true, ())} onClick=onMinimizeClick>
{Icon.make(~name="Dash", ~width=12, ~height=12, ())}
{Icon.make(~name="Dash", ~width=height, ~height, ())}
</div>
<div className={makeControlStyle()} onClick=onMaximizeClick>
{Icon.make(~name="Square", ~width=12, ~height=12, ())}
{Icon.make(~name="Square", ~width=height, ~height, ())}
</div>
<div className={makeControlStyle(~bgColor=red, ())} onClick=onCloseClick>
{Icon.make(~name="Cancel", ~width=12, ~height=12, ())}
{Icon.make(~name="Cancel", ~width=height, ~height, ())}
</div>
</div>;
};
let default = make;
let default = make;
+1 -1
View File
@@ -26,7 +26,7 @@ const makeMainWindowSettings = () => {
darkTheme: true,
// eslint-disable-next-line no-unneeded-ternary
frame: process.env.ELECTRON_START_URL ? true : false,
backgroundColor: '#121212',
backgroundColor: '#000',
webPreferences: {
nodeIntegration: true,
webSecurity: false,
+14 -4
View File
@@ -1,6 +1,10 @@
type t;
type callback('a) = 'a => unit;
type target = {readyState: int};
type partialAudioEvent = {target};
[@bs.new] external make: unit => t = "Audio";
[@bs.new] external makeWithSrc: (~src: string) => t = "Audio";
@@ -9,7 +13,13 @@ type callback('a) = 'a => unit;
[@bs.set] external setVolume: (t, float) => unit = "volume";
[@bs.send] external pause: t => unit = "pause";
[@bs.send] external play: t => unit = "play";
[@bs.set] external onended: (t, callback(Dom.event)) => unit = "onended";
[@bs.set] external onerror: (t, callback(Dom.event)) => unit = "onerror";
[@bs.set] external onpause: (t, callback(Dom.event)) => unit = "onpause";
[@bs.set] external onplay: (t, callback(Dom.event)) => unit = "onplay";
[@bs.set]
external onended: (t, callback(partialAudioEvent)) => unit = "onended";
[@bs.set]
external onerror: (t, callback(partialAudioEvent)) => unit = "onerror";
[@bs.set]
external onpause: (t, callback(partialAudioEvent)) => unit = "onpause";
[@bs.set]
external onplay: (t, callback(partialAudioEvent)) => unit = "onplay";
[@bs.set]
external oncanplay: (t, callback(partialAudioEvent)) => unit = "oncanplay";
+1 -1
View File
@@ -12,7 +12,7 @@ export default {
topBarHeight: 48,
sidePanelCompactedLength: 48,
sidePanelExpandedLength: 150,
titleBarHeight: 23,
titleBarHeight: 12,
defaultTransitionDuration: '200ms',
defaultAccentColor: '#17b04c',
},
+1 -1
View File
@@ -1,6 +1,6 @@
import { createStore, combineReducers } from 'redux';
import bot from '../Bot/reducer';
import settings from '../App/modules/Settings/reducer/reducer';
import settings from '../App/modules/Settings/reducer/reducer'; // TODO Dependency cycle
import beatmaps from '../App/modules/Beatmaps/reducer/reducer';
import navigation from '../App/modules/reducer';
import packs from '../App/modules/Packs/reducer/reducer';