feat(Beatmaps): Add more info on beatmap card about each difficulty (#455)

This commit is contained in:
Yannis Petitot
2021-07-03 15:51:48 +02:00
committed by GitHub
parent a0570609d6
commit 550cdd8428
10 changed files with 562 additions and 97 deletions
+62
View File
@@ -680,6 +680,68 @@ export default function({ name, color, width, height, style, secColor }) {
</g>
</svg>
);
case 'Circle':
return (
<svg viewBox="0 0 512 512" width={width} height={height}>
<g>
<path
fill="rgb(255 255 255 / 28%)"
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 424c-97.06 0-176-79-176-176S158.94 80 256 80s176 79 176 176-78.94 176-176 176z"
/>
<path
fill="rgb(255 255 255 / 75%)"
d="M256 432c-97.06 0-176-79-176-176S158.94 80 256 80s176 79 176 176-78.94 176-176 176z"
/>
</g>
</svg>
);
case 'Clock':
return (
<svg width={width} height={height} viewBox="0 0 512 512">
<g>
<path
fill={fill}
d="M347.216,301.211l-71.387-53.54V138.609c0-10.966-8.864-19.83-19.83-19.83c-10.966,0-19.83,8.864-19.83,19.83v118.978
c0,6.246,2.935,12.136,7.932,15.864l79.318,59.489c3.569,2.677,7.734,3.966,11.878,3.966c6.048,0,11.997-2.717,15.884-7.952
C357.766,320.208,355.981,307.775,347.216,301.211z"
/>
</g>
<g>
<path
fill={fill}
d="M256,0C114.833,0,0,114.833,0,256s114.833,256,256,256s256-114.833,256-256S397.167,0,256,0z M256,472.341
c-119.275,0-216.341-97.066-216.341-216.341S136.725,39.659,256,39.659c119.295,0,216.341,97.066,216.341,216.341
S375.275,472.341,256,472.341z"
/>
</g>
</svg>
);
case 'Slider':
return (
<svg viewBox="0 0 368 368" width={width} height={height}>
<g>
<path
fill={fill}
d="M280,96H88c-48.52,0-88,39.48-88,88s39.48,88,88,88h192c48.52,0,88-39.48,88-88C368,135.48,328.52,96,280,96z M16,184
c0-39.704,32.304-72,72-72s72,32.296,72,72s-32.304,72-72,72S16,223.704,16,184z M280,256H138.44
c22.672-15.936,37.56-42.24,37.56-72s-14.888-56.064-37.56-72H280c39.696,0,72,32.296,72,72S319.696,256,280,256z"
/>
</g>
</svg>
);
case 'Spinner':
return (
<svg height={height} viewBox="-16 -18 533.33331 533" width={width}>
<path
fill={fill}
d="m248.429688 25.082031c39.289062 0 78.640624 11.152344 113.800781 32.253907 22.300781 13.417968 42.339843 30.28125 59.363281 49.972656l-58.738281-1.9375c-6.898438-.230469-12.675781 5.179687-12.902344 12.082031-.226563 6.898437 5.179687 12.679687 12.082031 12.902344l81.269532 2.683593c4.667968 1.921876 10.035156.847657 13.605468-2.726562 3.566406-3.574219 4.632813-8.941406 2.703125-13.609375l-2.675781-81.25c-.15625-6.902344-5.878906-12.371094-12.78125-12.21875-6.898438.15625-12.375 5.878906-12.21875 12.777344 0 .09375 0 .179687.007812.269531l1.546876 46.875c-17.195313-18.375-36.847657-34.285156-58.398438-47.265625-39.042969-23.425781-82.84375-35.8085938-126.664062-35.8085938-70 0-134.539063 28.8632808-181.703126 81.2695308-41.78125 46.425782-66.726562 108.914063-66.726562 167.160157 0 6.902343 5.59375 12.5 12.5 12.5s12.5-5.597657 12.5-12.5c0-52.238281 22.542969-108.476563 60.308594-150.433594 42.367187-47.066406 100.292968-72.996094 163.121094-72.996094zm0 0"
/>
<path
fill={fill}
d="m487.5 236.011719c-6.90625 0-12.5 5.59375-12.5 12.5 0 52.234375-22.542969 108.476562-60.308594 150.4375-42.367187 47.066406-100.292968 72.988281-163.121094 72.988281-39.289062 0-78.640624-11.144531-113.800781-32.246094-22.300781-13.421875-42.339843-30.285156-59.363281-49.972656l58.738281 1.9375c6.898438.222656 12.675781-5.1875 12.902344-12.085938.226563-6.898437-5.179687-12.675781-12.082031-12.90625l-81.269532-2.679687c-4.667968-1.921875-10.035156-.84375-13.605468 2.726563-3.566406 3.574218-4.632813 8.9375-2.703125 13.605468l2.675781 81.25c.15625 6.90625 5.878906 12.378906 12.78125 12.222656 6.898438-.152343 12.375-5.875 12.21875-12.777343 0-.089844 0-.175781-.007812-.269531l-1.546876-46.875c17.195313 18.378906 36.847657 34.28125 58.398438 47.269531 39.042969 23.429687 82.84375 35.808593 126.664062 35.808593 70 0 134.539063-28.859374 181.703126-81.265624 41.78125-46.429688 66.726562-108.917969 66.726562-167.167969 0-6.90625-5.59375-12.5-12.5-12.5zm0 0"
/>
</svg>
);
default:
return null;
}
+26 -7
View File
@@ -1,20 +1,39 @@
export default timeStamp => {
const timeSince = date => {
const now = new Date();
const secondsPast = (now.getTime() - timeStamp.getTime()) / 1000;
const secondsPast = (now.getTime() - date.getTime()) / 1000;
if (secondsPast < 60) {
return `${parseInt(secondsPast, 10)} sec ago`;
}
if (secondsPast < 3600) {
return `${parseInt(secondsPast / 60, 10)} min ago`;
}
if (secondsPast <= 86400) {
return `${parseInt(secondsPast / 3600, 10)} h ago'`;
if (secondsPast < 86400) {
return `${parseInt(secondsPast / 3600, 10)} hours ago`;
}
const day = timeStamp.getDate();
const month = timeStamp
if (secondsPast < 2628336.2137829) {
return `${parseInt(secondsPast / 86400, 10)} days ago`;
}
if (secondsPast < 31536000) {
return `${parseInt(secondsPast / 2628336.2137829, 10)} months ago`;
}
const day = date.getDate();
const month = date
.toDateString()
.match(/ [a-zA-Z]*/)[0]
.replace(' ', '');
const year = timeStamp.getFullYear() === now.getFullYear() ? '' : ` ${timeStamp.getFullYear()}`;
const year = date.getFullYear() === now.getFullYear() ? '' : ` ${date.getFullYear()}`;
return `${day} ${month + year}`;
};
export default timeSince;
export const secToMinSec = sec => {
const minutes = Math.floor(sec / 60);
const seconds = sec - minutes * 60;
function str_pad_left(string, pad, length) {
return (new Array(length + 1).join(pad) + string).slice(-length);
}
return `${str_pad_left(minutes, '0', 2)}:${str_pad_left(seconds, '0', 2)}`;
};
+88 -49
View File
@@ -10,89 +10,128 @@ let makeBaseStyle = () =>
alignItems(center),
]);
let makeStatusStyle = status =>
let makeStatusStyle = (status, difficulty) =>
switch (status) {
| "info" => style([backgroundColor(hex("3498db"))])
| "pending" =>
style([
backgroundColor(rgba(241, 196, 15, 0.50)),
borderColor(rgba(241, 196, 15, 0.8)),
selector(
"&:hover",
[
backgroundColor(rgba(241, 196, 15, 0.7)),
borderColor(rgb(241, 196, 15)),
],
backgroundColor(
difficulty->Belt.Option.mapWithDefault(
rgba(241, 196, 15),
PpyHelpers.getBeatmapDifficultyColorRGBA,
0.5,
),
),
borderColor(
difficulty->Belt.Option.mapWithDefault(
rgba(241, 196, 15),
PpyHelpers.getBeatmapDifficultyColorRGBA,
0.8,
),
),
hover([
backgroundColor(rgba(241, 196, 15, 0.7)),
borderColor(rgb(241, 196, 15)),
]),
])
| "qualified" =>
style([
backgroundColor(rgba(241, 196, 15, 0.50)),
borderColor(rgba(241, 196, 15, 0.8)),
selector(
"&:hover",
[
backgroundColor(rgba(241, 196, 15, 0.7)),
borderColor(rgb(241, 196, 15)),
],
),
hover([
backgroundColor(rgba(241, 196, 15, 0.7)),
borderColor(rgb(241, 196, 15)),
]),
])
| "graveyard" =>
style([
backgroundColor(rgba(231, 76, 60, 0.50)),
borderColor(rgba(231, 76, 60, 0.8)),
selector(
"&:hover",
[
backgroundColor(rgba(231, 76, 60, 0.7)),
borderColor(rgb(231, 76, 60)),
],
),
hover([
backgroundColor(rgba(231, 76, 60, 0.7)),
borderColor(rgb(231, 76, 60)),
]),
])
| "WIP" =>
style([
backgroundColor(rgba(231, 76, 60, 0.50)),
borderColor(rgba(231, 76, 60, 0.8)),
selector(
"&:hover",
[
backgroundColor(rgba(231, 76, 60, 0.7)),
borderColor(rgb(231, 76, 60)),
],
),
hover([
backgroundColor(rgba(231, 76, 60, 0.7)),
borderColor(rgb(231, 76, 60)),
]),
])
| "loved" =>
style([
backgroundColor(rgba(222, 90, 148, 0.50)),
borderColor(rgba(222, 90, 148, 0.8)),
selector(
"&:hover",
[
backgroundColor(rgba(222, 90, 148, 0.7)),
borderColor(rgb(222, 90, 148)),
],
),
hover([
backgroundColor(rgba(222, 90, 148, 0.7)),
borderColor(rgb(222, 90, 148)),
]),
])
| "ranked" =>
style([
backgroundColor(rgba(46, 204, 113, 0.50)),
borderColor(rgba(46, 204, 113, 0.8)),
selector(
"&:hover",
[
backgroundColor(rgba(46, 204, 113, 0.7)),
borderColor(rgb(46, 204, 113)),
],
backgroundColor(
difficulty->Belt.Option.mapWithDefault(
rgba(46, 204, 113),
PpyHelpers.getBeatmapDifficultyColorRGBA,
0.5,
),
),
borderColor(
difficulty->Belt.Option.mapWithDefault(
rgba(46, 204, 113),
PpyHelpers.getBeatmapDifficultyColorRGBA,
0.8,
),
),
hover([
backgroundColor(rgba(46, 204, 113, 0.7)),
borderColor(rgb(46, 204, 113)),
]),
])
| _ => style([backgroundColor(hex("2c3e50"))])
};
let makeStyle = status => [makeBaseStyle(), makeStatusStyle(status)]->merge;
let makeStyle = (status, difficulty) =>
[makeBaseStyle(), makeStatusStyle(status, difficulty)]->merge;
[@react.component]
let make = (~status, ~style=?) => {
<span className={makeStyle(status)} ?style>
{status->String.capitalize_ascii->React.string}
let make =
(
~status,
~difficulty: option(float),
~difficultyText: option(string),
~style=?,
) => {
let (text, setText) = React.useState(() => status);
React.useEffect1(
() => {
setText(_ =>
switch (difficultyText) {
| Some(difficultyText) => difficultyText
| None => status
}
);
None;
},
[|difficultyText|],
);
<span
className={makeStyle(status, difficulty)}
?style
onMouseEnter={_ => setText(_ => status)}
onMouseLeave={_ =>
setText(_ =>
switch (difficultyText) {
| Some(difficultyText) => difficultyText
| None => status
}
)
}>
<span> text->React.string </span>
</span>;
};
@@ -0,0 +1,203 @@
import React, { useState, useEffect, useCallback, useLayoutEffect } from 'react';
import { createUseStyles } from 'react-jss';
import renderIcons from '../../../helpers/renderIcons';
import timeSince, { secToMinSec } from '../../../helpers/timeSince';
import { make as Badge } from '../Badge.bs';
import DifficultiesSelector from './DifficultiesSelector';
const totalPlayCount = beatmaps => beatmaps.reduce((playcount, beatmap) => playcount + beatmap.playcount, 0);
const modes = mode => {
const modesMap = Object.freeze({
fruits: 'ctb',
mania: 'mania',
osu: 'std',
taiko: 'taiko',
});
return modesMap[mode];
};
const useStyle = createUseStyles({
wrapper: {
backdropFilter: 'saturate(150%) blur(5px) brightness(0.5)',
height: '100%',
opacity: ({ isCardhovered }) => (isCardhovered ? 1 : 0),
transition: 'opacity 150ms',
borderTopLeftRadius: '5px',
borderTopRightRadius: '5px',
},
topContainer: {
display: 'inline-flex',
width: '-webkit-fill-available',
justifyContent: 'space-between',
alignItems: 'baseline',
gap: '0.5rem',
backgroundColor: 'rgba(0,0,0,0.1)',
padding: '.5rem',
height: ({ isCardhovered }) => (isCardhovered ? '26px' : '0'),
transitionDelay: '50ms',
transition: 'height 200ms',
},
leftContainer: {
position: 'absolute',
top: '45px',
left: '1rem',
display: 'inline-flex',
flexDirection: 'column',
textAlign: 'left',
opacity: ({ isCardhovered }) => (isCardhovered ? 1 : 0),
},
rightContainer: {
position: 'absolute',
top: '45px',
right: '1rem',
display: 'inline-flex',
flexDirection: 'column',
textAlign: 'right',
opacity: ({ noDiffSelectd }) => (noDiffSelectd ? 0 : 1),
},
versionTitle: {
fontWeight: 600,
fontSize: 'medium',
opacity: ({ noDiffSelectd }) => (noDiffSelectd ? 0 : 1),
},
details: {
display: 'inline-flex',
alignItems: 'center',
gap: '4px',
opacity: ({ noDiffSelectd }) => (noDiffSelectd ? 0 : 1),
},
one: {
transitionDelay: '250ms',
transitionDuration: '200ms',
transitionProperty: 'opacity',
},
two: {
transitionDelay: '50ms',
transitionDuration: '200ms',
transitionProperty: 'opacity',
},
three: {
transitionDelay: '100ms',
transitionDuration: '200ms',
transitionProperty: 'opacity',
},
foor: {
transitionDelay: '150ms',
transitionDuration: '200ms',
transitionProperty: 'opacity',
},
bold: {
fontWeight: 600,
},
});
const BeatmapDetails = ({ beatmapSet, cardRef }) => {
const [selectedDiff, setSelectedDiff] = useState('none');
const [isCardhovered, setIsCardhovered] = useState(false);
const noDiffSelectd = selectedDiff === 'none';
const classes = useStyle({ noDiffSelectd, isCardhovered });
const onDiffSelect = useCallback(diffIndex => setSelectedDiff(beatmapSet.beatmaps[diffIndex] ?? 'none'), []);
useEffect(() => {
if (cardRef.current) {
const leaveHandler = () => {
setSelectedDiff('none');
setIsCardhovered(false);
};
const enterHandler = () => {
setIsCardhovered(true);
};
cardRef.current.addEventListener('mouseleave', leaveHandler);
cardRef.current.addEventListener('mouseenter', enterHandler);
}
}, [cardRef.current]);
useLayoutEffect(() => {
if (cardRef.current && selectedDiff !== 'none') {
const matchingModePill = cardRef.current.querySelector(`img.pill.${modes(selectedDiff.mode)}`);
matchingModePill.classList.add('highlight');
cardRef.current.querySelector('div.availableModes').classList.add('hasHighlight');
return () => matchingModePill.classList.remove('highlight');
}
if (cardRef.current && selectedDiff === 'none') {
cardRef.current.querySelector('div.availableModes').classList.remove('hasHighlight');
}
return () => {};
}, [selectedDiff, cardRef.current]);
return (
<div className={classes.wrapper}>
<div className={classes.topContainer}>
<Badge
status={beatmapSet.status}
difficulty={selectedDiff.difficulty}
difficultyText={selectedDiff.difficulty && `${selectedDiff.difficulty}`}
/>
<p className={`${classes.versionTitle} ${classes.two}`}>{selectedDiff.version ?? 'Version'}</p>
<p style={{ flexGrow: 1 }} />
<p className={`${classes.details} ${classes.foor}`} title="Circle count">
{renderIcons({ name: 'Circle', width: '15px', height: '15px' })}
<span>{selectedDiff.count_circles}</span>
</p>
<p className={`${classes.details} ${classes.three}`} title="Slider count">
{renderIcons({ name: 'Slider', width: '15px', height: '15px' })}
<span>{selectedDiff.count_sliders}</span>
</p>
<p className={`${classes.details} ${classes.two}`} title="Spinner count">
{renderIcons({ name: 'Spinner', width: '14px', height: '14px' })}
<span>{selectedDiff.count_spinners}</span>
</p>
<p
className={`${classes.details} ${classes.one}`}
style={{ opacity: isCardhovered ? 1 : 0 }}
title="Duration in minutes"
>
{renderIcons({ name: 'Clock', width: '15px', height: '15px' })}
<span>{secToMinSec(selectedDiff.total_length ?? beatmapSet.average_length)}</span>
</p>
</div>
<div className={`${classes.leftContainer} ${classes.one}`}>
<p title={new Date(beatmapSet.submitted_date).toLocaleString()}>
<span>Submited </span>
<span className={classes.bold}>{timeSince(new Date(beatmapSet.submitted_date))}</span>
</p>
<p title={new Date(beatmapSet.ranked_date).toLocaleString()}>
<span>Ranked </span>
<span className={classes.bold}>{timeSince(new Date(beatmapSet.ranked_date))}</span>
</p>
<p>
<span>Play count </span>
<span className={classes.bold}>
{noDiffSelectd ? totalPlayCount(beatmapSet.beatmaps) : selectedDiff.playcount}
</span>
</p>
<p>
<span>Favorite count </span>
<span className={classes.bold}>{beatmapSet.favourite_count}</span>
</p>
</div>
<div className={`${classes.rightContainer} ${classes.two}`}>
<p>
<span className={classes.bold}>CS: </span>
<span>{selectedDiff.cs}</span>
</p>
<p>
<span className={classes.bold}>AR: </span>
<span>{selectedDiff.ar}</span>
</p>
<p>
<span className={classes.bold}>Drain: </span>
<span>{selectedDiff.drain}</span>
</p>
<p>
<span className={classes.bold}>Acc: </span>
<span>{selectedDiff.accuracy}</span>
</p>
</div>
<DifficultiesSelector beatmaps={beatmapSet.beatmaps} onSelect={onDiffSelect} />
</div>
);
};
export default BeatmapDetails;
+7 -2
View File
@@ -1,7 +1,8 @@
import React, { useState, useEffect, useRef } from 'react';
import config from '../../../../shared/config';
import BeatmapDetails from './BeatmapDetails';
const Cover = ({ url, width, height, paddingBottom, roundedTop, noFade, canLoad = true }) => {
const Cover = ({ url, width, height, paddingBottom, roundedTop, noFade, canLoad = true, beatmapSet, parentRef }) => {
const [loaded, isLoaded] = useState(false);
const coverRef = useRef();
useEffect(() => {
@@ -29,7 +30,11 @@ const Cover = ({ url, width, height, paddingBottom, roundedTop, noFade, canLoad
backgroundImage: loaded && `url('${url}')`,
backgroundColor: 'rgba(255, 255, 255, 0.02)',
};
return <div className="cover" style={style} />;
return (
<div className="cover" style={style}>
<BeatmapDetails beatmapSet={beatmapSet} cardRef={parentRef} />
</div>
);
};
export default Cover;
@@ -0,0 +1,87 @@
import React, { useEffect, useState, useCallback } from 'react';
import { createUseStyles } from 'react-jss';
import { debounce } from 'underscore';
import { getBeatmapDifficultyColorHex } from '../../../../shared/PpyHelpers.bs';
const DIFFICULTY_TRANSITION_DURATION = 160;
const VERSION_TRANSITION_DURATION = 100;
const useStyle = createUseStyles({
wrapper: {
position: 'absolute',
bottom: 0,
display: 'flex',
width: '100%',
height: '25px',
'&:hover': {
'& > .diff': {
height: '12px',
},
},
transition: 'height 200ms',
'& .prev': {
height: '18px !important',
},
},
difficulty: {
height: '4px',
flexBasis: '100%',
boxSizing: 'border-box',
alignSelf: 'flex-end',
flexShrink: '2',
minWidth: 0,
'& > .version': {
pointerEvents: 'none',
overflow: 'hidden',
height: '100%',
color: 'transparent',
},
'&:hover': {
'& + .diff': {
height: '18px',
},
flexShrink: '1',
height: '22px !important',
'& > .version': {
transitionDelay: `${DIFFICULTY_TRANSITION_DURATION}ms`,
transitionDuration: `${VERSION_TRANSITION_DURATION}ms`,
transitionProperty: 'color',
color: 'white',
backdropFilter: 'brightness(0.9)',
},
},
transition: `all ${DIFFICULTY_TRANSITION_DURATION}ms`,
},
});
const DifficultiesSelector = ({ beatmaps, onSelect }) => {
const [selectedDiff, setSelectedDiff] = useState(-1);
const debounceOnSelect = useCallback(debounce(onSelect, DIFFICULTY_TRANSITION_DURATION), []);
useEffect(() => {
if (selectedDiff > -1) debounceOnSelect(selectedDiff);
else debounceOnSelect.cancel();
}, [selectedDiff]);
const classes = useStyle();
return (
<div className={classes.wrapper}>
{beatmaps
.sort((a, b) => a.difficulty - b.difficulty)
.map((beatmap, i) => (
<div
onMouseLeave={() => {
setSelectedDiff(-1);
}}
className={`${classes.difficulty} diff ${i === selectedDiff - 1 ? 'prev' : ''}`}
style={{ backgroundColor: getBeatmapDifficultyColorHex(beatmap.difficulty) }}
onMouseEnter={() => {
setSelectedDiff(i);
}}
>
<div className="version">{beatmap.version}</div>
</div>
))}
</div>
);
};
export default DifficultiesSelector;
+54
View File
@@ -0,0 +1,54 @@
import React from 'react';
import { createUseStyles } from 'react-jss';
import reqImgAssets from '../../../helpers/reqImgAssets';
const modePillsStyle = mode => ({
width: 20,
height: 20,
margin: '3px',
backgroundSize: 'contain',
filter: 'brightness(0.85)',
content: `url(${reqImgAssets(`./${mode}.png`)})`,
});
const useStyle = createUseStyles({
availableModes: {
padding: '0 3px',
display: 'inline-flex',
'& > .pill': {
transition: 'opacity 200ms ease',
},
'&.hasHighlight > .pill': {
opacity: 0.15,
},
'&.hasHighlight > .pill.highlight': {
opacity: 1,
},
},
});
const Modes = ({ std, mania, taiko, ctb }) => {
const classes = useStyle();
return (
<div
className="rightContainer"
style={{ position: 'absolute', right: '1%', bottom: '4%', display: 'inline-flex', margin: '0.2vw' }}
>
<div className={`${classes.availableModes} availableModes`} style={{ padding: '0 3px', display: 'inline-flex' }}>
{std && <img alt="std" title="Standard" className="pill std" style={modePillsStyle('std')} />}
{mania && <img alt="mania" title="Mania" className="pill mania" style={modePillsStyle('mania')} />}
{taiko && <img alt="taiko" title="Taiko" className="pill taiko" style={modePillsStyle('taiko')} />}
{ctb && <img alt="ctb" title="Catch The Beat" className="pill ctb" style={modePillsStyle('ctb')} />}
</div>
</div>
);
};
Modes.defaultProps = {
std: false,
mania: false,
taiko: false,
ctb: false,
};
export default Modes;
+7 -33
View File
@@ -1,5 +1,5 @@
/* eslint-disable camelcase */
import React, { useState, memo } from 'react';
import React, { useState, memo, useRef } from 'react';
import { shell } from 'electron';
import { createUseStyles, useTheme } from 'react-jss';
import Cover from './Cover';
@@ -7,10 +7,9 @@ import DownloadBeatmapBtn from './DownloadBeatmapBtn';
import PreviewBeatmapBtn from './PreviewBeatmapBtn';
import renderIcons from '../../../helpers/renderIcons';
import getBeatmapInfosUrl from '../../../helpers/getBeatmapInfosUrl';
import { make as Badge } from '../Badge.bs';
import reqImgAssets from '../../../helpers/reqImgAssets';
import Button from '../Button';
import SetWallpaperButton from './SetWallpaperButton';
import Modes from './Modes';
const bpmToBps = bpm => 60 / bpm;
@@ -90,31 +89,24 @@ const useStyles = createUseStyles({
export const getDownloadUrl = ({ id, unique_id }) => `https://beatconnect.io/b/${id}/${unique_id}`;
const Beatmap = ({ beatmap, noFade, autoDl, width, ...otherProps }) => {
const ref = useRef();
const theme = useTheme();
const [isPlaying, setIsPLaying] = useState(false);
const { beatmapset_id, id, title, artist, creator, version, bpm, beatconnectDlLink, beatmaps } = beatmap;
const wallpaperBeatmapId = beatmaps[Math.max(beatmaps.length - 2, 0)].id;
const modePillsStyle = mode => ({
width: 20,
height: 20,
margin: '3px',
backgroundSize: 'contain',
filter: 'brightness(0.85)',
content: `url(${reqImgAssets(`./${mode}.png`)})`,
});
const classes = useStyles({ width, theme, isPlaying, bpm: beatmap.bpm, ...otherProps });
return (
<div className={classes.Beatmap}>
<div className={classes.Beatmap} ref={ref}>
{beatmap && (
<>
{beatmap.status && <Badge style={{ position: 'absolute', top: '4%', right: '1%' }} status={beatmap.status} />}
<Cover
url={`https://assets.ppy.sh/beatmaps/${beatmapset_id || id}/covers/cover.jpg`}
height={130}
noFade={noFade}
roundedTop
beatmapSet={beatmap}
parentRef={ref}
/>
<div className="leftContainer" style={{ position: 'absolute', left: '2%', bottom: '3%' }}>
<p className={classes.Row}>
@@ -153,25 +145,7 @@ const Beatmap = ({ beatmap, noFade, autoDl, width, ...otherProps }) => {
{renderIcons({ name: 'Search', style: theme.accentContrast })}
</Button>
<SetWallpaperButton beatmapSetId={beatmapset_id || id} beatmapId={wallpaperBeatmapId} />
<div
className="rightContainer"
style={{ position: 'absolute', right: '1%', bottom: '4%', display: 'inline-flex', margin: '0.2vw' }}
>
<div className="availableModes" style={{ padding: '0 3px', display: 'inline-flex' }}>
{beatmap.mode_std && (
<img alt="std" title="Standard" className="pill std" style={modePillsStyle('std')} />
)}
{beatmap.mode_mania && (
<img alt="mania" title="Mania" className="pill mania" style={modePillsStyle('mania')} />
)}
{beatmap.mode_taiko && (
<img alt="taiko" title="Taiko" className="pill taiko" style={modePillsStyle('taiko')} />
)}
{beatmap.mode_ctb && (
<img alt="ctb" title="Catch The Beat" className="pill ctb" style={modePillsStyle('ctb')} />
)}
</div>
</div>
<Modes ctb={beatmap.mode_ctb} std={beatmap.mode_std} mania={beatmap.mode_mania} taiko={beatmap.mode_taiko} />
</>
)}
</div>
+6 -6
View File
@@ -145,7 +145,7 @@
"beatmaps": [
{
"id": 1650657,
"mode": "osu",
"mode": "fruits",
"mode_int": 0,
"difficulty": 5.04,
"version": "Mofu Loli",
@@ -201,7 +201,7 @@
},
{
"id": 1808794,
"mode": "osu",
"mode": "mania",
"mode_int": 0,
"difficulty": 2.32,
"version": "Fresh Normal",
@@ -229,7 +229,7 @@
},
{
"id": 2977441,
"mode": "osu",
"mode": "taiko",
"mode_int": 0,
"difficulty": 2.05,
"version": "Fresh Easy",
@@ -306,10 +306,10 @@
"hype_required": null,
"last_updated": "2021-06-02T16:45:11+00:00",
"legacy_thread_url": "https://osu.ppy.sh/community/forums/topics/750701",
"mode_ctb": false,
"mode_mania": false,
"mode_ctb": true,
"mode_mania": true,
"mode_std": true,
"mode_taiko": false,
"mode_taiko": true,
"nominations_current": null,
"nominations_required": null,
"preview_url": "//b.ppy.sh/preview/786320.mp3",
+22
View File
@@ -21,3 +21,25 @@ let resolveThumbnail = (beatmapId, osuPath, fallbackUrl) => {
let resolveThumbURL = (beatmapId, osuPath) =>
resolveThumbnail(beatmapId, osuPath, getListCoverUrl(beatmapId));
let getBeatmapDifficultyColorHex = (difficulty: float) => {
switch (difficulty) {
| difficulty when difficulty >= 6.5 => "#000"
| difficulty when difficulty >= 5.3 => "#8866ee"
| difficulty when difficulty >= 4.0 => "#ff66aa"
| difficulty when difficulty >= 2.7 => "#ffcc22"
| difficulty when difficulty >= 2.0 => "#66ccff"
| _ => "#88b300"
};
};
let getBeatmapDifficultyColorRGBA = (difficulty: float) => {
switch (difficulty) {
| difficulty when difficulty >= 6.5 => Css.rgba(0, 0, 0)
| difficulty when difficulty >= 5.3 => Css.rgba(136, 102, 238)
| difficulty when difficulty >= 4.0 => Css.rgba(255, 102, 170)
| difficulty when difficulty >= 2.7 => Css.rgba(255, 204, 34)
| difficulty when difficulty >= 2.0 => Css.rgba(102, 204, 255)
| _ => Css.rgba(136, 179, 0)
};
};