Convert RadioElement & RadiosCarousel to functional component

This commit is contained in:
TeChn4K
2019-10-20 20:22:32 +02:00
parent ec65af9625
commit 890a4e033e
2 changed files with 269 additions and 278 deletions
+55 -58
View File
@@ -1,38 +1,39 @@
import React, { Component } from "react";
import React from "react";
import PropTypes from "prop-types";
import styled from "styled-components";
import classNames from "classnames";
import RoundNumber from "./RoundNumber";
import { useBreakpoint } from "../helpers/hooks";
import breakpoint from "../helpers/breakpoint";
import consts from "../consts.js";
import music from "../img/type/1music.png";
import speech from "../img/type/2speech.png";
import ads from "../img/type/3ads.png";
const TRANSITION = "0.2s ease-out";
const Shadow = styled.div`
opacity: 0.5;
width: 100%;
height: 100%;
border-radius: 50%;
box-shadow: 0px 0px 15px 0px rgb(0, 0, 0);
border: 1px solid black;
&.playing {
opacity: 0.2;
${props =>
props.playing && breakpoint.min.l`
opacity: 0.2;
box-shadow: 0px 0px 60px 20px rgb(0, 0, 0);
animation-name: shadow;
animation-duration: 3s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
`}
&.condensed {
box-shadow: unset;
border: 1px solid black;
}
${breakpoint.min.l`
box-shadow: 0px 0px 15px 0px rgb(0, 0, 0);
border: none;
`}
@keyframes shadow {
0% {
@@ -55,67 +56,63 @@ const RadioName = styled.span`
const RadioStatus = styled.div`
position: absolute;
transition: ${TRANSITION};
transition: 0.2s ease-out;
border-radius: 50%;
`;
const RadioStatusLogo = styled.img`
filter: invert(100%);
width: 80%;
margin: 10%;
height: 80%;
margin: 10%;
filter: invert(100%);
`;
class RadioElement extends Component {
constructor(props) {
super(props);
this.onClick = this.props.onClick.bind(this);
}
function RadioElement(props) {
const { radio, style, onClick, position } = props;
render() {
let radio = this.props.radio;
return (
<div>
<img
src={radio.logo || "https://static.adblockradio.com/assets/default_radio_logo.png"}
alt={radio.name}
style={this.props.style.logo}
/>
<div style={this.props.style.overlay} onClick={this.onClick}>
<Shadow className={classNames({ playing: radio.playing, condensed: this.props.condensed })}></Shadow>
</div>
{!this.props.condensed && (
<RadioName style={this.props.style.name} onClick={this.onClick}>
{radio.name}
</RadioName>
)}
{this.props.position && (
<RoundNumber
active={false}
disabled={false}
done={true}
number={this.props.position.number}
style={this.props.position.style}
/>
)}
<RadioStatus style={this.props.style.status(consts.STATUS_AD, this.props.condensed)}>
<RadioStatusLogo src={ads} />
</RadioStatus>
<RadioStatus style={this.props.style.status(consts.STATUS_SPEECH, this.props.condensed)}>
<RadioStatusLogo src={speech} />
</RadioStatus>
<RadioStatus style={this.props.style.status(consts.STATUS_MUSIC, this.props.condensed)}>
<RadioStatusLogo src={music} />
</RadioStatus>
const isDesktop = useBreakpoint("l");
return (
<>
<img
src={radio.logo || "https://static.adblockradio.com/assets/default_radio_logo.png"}
alt={radio.name}
style={style.logo}
/>
<div style={style.overlay} onClick={onClick}>
<Shadow playing={radio.playing} />
</div>
);
}
{isDesktop && (
<RadioName style={style.name} onClick={onClick}>
{radio.name}
</RadioName>
)}
{position && (
<RoundNumber active={false} disabled={false} done={true} number={position.number} style={position.style} />
)}
<RadioStatus style={style.status(consts.STATUS_AD, !isDesktop)}>
<RadioStatusLogo src={ads} />
</RadioStatus>
<RadioStatus style={style.status(consts.STATUS_SPEECH, !isDesktop)}>
<RadioStatusLogo src={speech} />
</RadioStatus>
<RadioStatus style={style.status(consts.STATUS_MUSIC, !isDesktop)}>
<RadioStatusLogo src={music} />
</RadioStatus>
</>
);
}
RadioElement.defaultProps = {
position: PropTypes.object,
};
RadioElement.propTypes = {
radio: PropTypes.object.isRequired,
onClick: PropTypes.func.isRequired,
condensed: PropTypes.bool.isRequired,
position: PropTypes.object,
style: PropTypes.object.isRequired
};
+214 -220
View File
@@ -1,9 +1,10 @@
import React, { Component } from "react";
import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import RadioElement from "./RadioElement";
import Controls from "./Controls/Controls";
import { throttle } from "../helpers/functions";
import consts from "../consts.js";
const LOGO_SIZE = 200;
@@ -19,233 +20,226 @@ const styleDivRadios = {
height: "100%"
};
class RadiosCarousel extends Component {
componentDidMount() {
document.addEventListener("keydown", this.handleNav);
function RadiosCarousel(props) {
const { bsw, settings, condensed } = props;
let self = this;
this.lastScroll = new Date();
this.radiolistRef.current.addEventListener("wheel", function(event) {
let now = new Date(); // avoid too fast scrolling
if (+now - self.lastScroll < 300) return;
self.lastScroll = now;
self.props.bsw.manualTuning(event.deltaY > 0 ? 1 : -1);
const carouselRef = useRef(null);
let radios = settings.radios;
const handleArrowKeys = evt => {
if (evt.key === "ArrowDown") {
bsw.manualTuning(1);
} else if (evt.key === "ArrowUp") {
bsw.manualTuning(-1);
}
};
const handleRadioClick = i => {
let targetVolume = bsw.getFilteredVolume(settings.radios[i].getStatus());
if (targetVolume === consts.VOLUME_NORMAL || settings.config.actionType === consts.ACTION_MUTE) {
bsw.togglePlay(settings.radios[i].name);
}
};
const handleScroll = throttle(evt => {
bsw.manualTuning(evt.deltaY > 0 ? 1 : -1);
}, 300);
useEffect(() => {
// TODO: This code have to move in a service or something
for (let i = 0; i < radios.length; i++) {
bsw.getRadio(radios[i].country, radios[i].name, result => {
result.logo = result.favicon;
settings.addRadio(result);
});
}
}, []); // No deps, original code was fired once
useEffect(() => {
const carouselRefCopy = carouselRef.current;
document.addEventListener("keydown", handleArrowKeys);
carouselRefCopy.addEventListener("wheel", handleScroll);
return () => {
document.removeEventListener("keydown", handleArrowKeys);
carouselRefCopy.removeEventListener("wheel", handleScroll);
};
}, []);
let indexRadio = bsw.getActiveIndex();
let noFilter = settings.config.filterType === consts.FILTER_OFF;
let isPodium = settings.config.actionType === consts.ACTION_PODIUM;
let styleLogo = [],
styleOverlay = [],
styleName = [],
styleStatus = [],
positionData = [];
let translate = [];
for (let i = 0; i < radios.length; i++) {
translate.push(i);
}
for (let i = 0; i < radios.length; i++) {
let delta = translate[i] - indexRadio;
let scalef, angleRotate, logoL, logoT, logoS, zIndex, shadow, fontS, statusR, statusS, controlShift;
if (isNaN(delta)) {
delta = i - (radios.length - 1) / 2;
}
let acceptableContent = 1;
let targetVolume = bsw.getFilteredVolume(settings.radios[translate[i]].getStatus());
if (targetVolume === consts.VOLUME_MUTED && !noFilter) {
acceptableContent = 0;
}
let scalef_base = 1 - 0.5 * condensed;
if (isNaN(indexRadio)) {
logoT = 5 + ((i + 1.0) / (radios.length + 1)) * 95;
scalef = scalef_base;
logoL = 50;
zIndex = i * 100;
} else if (settings.config.actionType === consts.ACTION_PODIUM) {
logoT = 5 + ((i + 1.0) / (radios.length + 1)) * 95;
logoL = 50;
scalef = scalef_base * (1 + 0.5 * (translate[i] === indexRadio));
zIndex = (radios.length - Math.abs(delta)) * 100;
} else {
logoT = 5 + ((i + 1.0 + 0.5 * (i === indexRadio) + 1 * (i > indexRadio)) / (radios.length + 2)) * 95;
logoL = 50 + Math.abs(i - indexRadio) * 3;
scalef = scalef_base * (1 + 0.5 * (translate[i] === indexRadio));
zIndex = (radios.length - Math.abs(delta)) * 100;
}
shadow = noFilter ? 0 : 0.8 * (1 - acceptableContent);
fontS = Math.round(18 + 6 * acceptableContent) * scalef;
angleRotate = 0;
statusR = LOGO_SIZE * scalef; // radius for position of status dots
logoS = LOGO_SIZE * scalef; // diameter of radio logo + shadow
statusS = STATUS_SIZE * (condensed ? 1 : scalef); // diameter of status dots
controlShift = Math.round((logoT * CONTROLS_HEIGHT) / 100);
let styleLogoBase = {
left: "" + logoL + "%",
top: "calc(" + logoT + "% - " + controlShift + "px)",
borderRadius: "50%",
transition: TRANSITION,
position: "absolute"
};
styleLogo.push(
Object.assign({}, styleLogoBase, {
width: "" + logoS + "px",
height: "" + logoS + "px",
marginLeft: "" + -logoS / 2 + "px",
marginTop: "" + -logoS / 2 + "px",
background: "white",
zIndex: "" + zIndex,
transform: "rotate(" + (-angleRotate * 180) / Math.PI + "deg)"
})
);
styleOverlay.push(
Object.assign({}, styleLogoBase, {
width: "" + logoS + "px",
height: "" + logoS + "px",
marginLeft: "" + -logoS / 2 + "px",
marginTop: "" + -logoS / 2 + "px",
background: "rgba(187,187,187," + shadow + ")",
zIndex: "" + (zIndex + 1),
cursor:
acceptableContent || settings.config.actionType === consts.ACTION_MUTE ? "pointer" : "not-allowed"
})
);
styleName.push({
left: "calc(" + logoL + "% + " + (logoS * (0.5 + 1 / 8) + 30) * Math.cos(-angleRotate) + "px)",
top: "calc(" + logoT + "% + " + ((logoS * (0.5 + 1 / 8) + 30) * Math.sin(-angleRotate) - controlShift) + "px)",
fontSize: "" + fontS + "px",
fontWeight: "bold",
color: "rgba(0,0,0," + (1 - shadow) + ")",
marginTop: "-0.5em",
zIndex: "" + zIndex,
transformOrigin: "left",
transform: "rotate(" + (-angleRotate * 180) / Math.PI + "deg)",
transition: TRANSITION,
cursor: acceptableContent ? "pointer" : "not-allowed"
});
let radios = this.props.settings.radios;
for (let i = 0; i < radios.length; i++) {
this.props.bsw.getRadio(radios[i].country, radios[i].name, result => {
result.logo = result.favicon;
this.props.settings.addRadio(result);
});
}
}
componentWillUnmount() {
document.removeEventListener("keydown", this.handleNav);
}
constructor(props) {
super(props);
this.handleRadioClick = this.handleRadioClick.bind(this);
this.handleNav = this.handleNav.bind(this);
this.radiolistRef = React.createRef();
}
handleRadioClick(i) {
let targetVolume = this.props.bsw.getFilteredVolume(this.props.settings.radios[i].getStatus());
if (targetVolume === consts.VOLUME_NORMAL || this.props.settings.config.actionType === consts.ACTION_MUTE) {
this.props.bsw.togglePlay(this.props.settings.radios[i].name);
}
}
handleNav(evt) {
let delta = 0;
if (evt.key === "ArrowDown") {
delta = 1;
} else if (evt.key === "ArrowUp") {
delta = -1;
}
this.props.bsw.manualTuning(delta);
}
render() {
let radios = this.props.settings.radios;
let self = this;
let indexRadio = this.props.bsw.getActiveIndex();
let noFilter = this.props.settings.config.filterType === consts.FILTER_OFF;
let isPodium = this.props.settings.config.actionType === consts.ACTION_PODIUM;
let styleLogo = [],
styleOverlay = [],
styleName = [],
styleStatus = [],
positionData = [];
let translate = [];
for (let i = 0; i < radios.length; i++) {
translate.push(i);
}
for (let i = 0; i < radios.length; i++) {
let delta = translate[i] - indexRadio;
let scalef, angleRotate, logoL, logoT, logoS, zIndex, shadow, fontS, statusR, statusS, controlShift;
if (isNaN(delta)) delta = i - (radios.length - 1) / 2;
let acceptableContent = 1;
let targetVolume = this.props.bsw.getFilteredVolume(this.props.settings.radios[translate[i]].getStatus());
if (targetVolume === consts.VOLUME_MUTED && !noFilter) {
acceptableContent = 0;
styleStatus.push(function(status, condensed) {
let statusAngle = 0,
statusBgColor = consts.getStatusColor(status);
switch (status) {
case consts.STATUS_AD:
statusAngle = -STATUS_ANGLE * (1 + 0.5 * condensed);
break;
case consts.STATUS_MUSIC:
statusAngle = +STATUS_ANGLE * (1 + 0.5 * condensed);
break;
default:
}
let scalef_base = 1 - 0.5 * this.props.condensed;
if (isNaN(indexRadio)) {
logoT = 5 + ((i + 1.0) / (radios.length + 1)) * 95;
scalef = scalef_base;
logoL = 50;
zIndex = i * 100;
} else if (this.props.settings.config.actionType === consts.ACTION_PODIUM) {
//angle = 0;
logoT = 5 + ((i + 1.0) / (radios.length + 1)) * 95;
logoL = 50;
scalef = scalef_base * (1 + 0.5 * (translate[i] === indexRadio));
zIndex = (radios.length - Math.abs(delta)) * 100;
} else {
logoT = 5 + ((i + 1.0 + 0.5 * (i === indexRadio) + 1 * (i > indexRadio)) / (radios.length + 2)) * 95;
logoL = 50 + Math.abs(i - indexRadio) * 3;
scalef = scalef_base * (1 + 0.5 * (translate[i] === indexRadio));
zIndex = (radios.length - Math.abs(delta)) * 100;
}
shadow = noFilter ? 0 : 0.8 * (1 - acceptableContent);
fontS = Math.round(18 + 6 * acceptableContent) * scalef;
angleRotate = 0;
statusR = LOGO_SIZE * scalef; // radius for position of status dots
logoS = LOGO_SIZE * scalef; // diameter of radio logo + shadow
statusS = STATUS_SIZE * (this.props.condensed ? 1 : scalef); // diameter of status dots
controlShift = Math.round((logoT * CONTROLS_HEIGHT) / 100);
let styleLogoBase = {
left: "" + logoL + "%",
top: "calc(" + logoT + "% - " + controlShift + "px)",
borderRadius: "50%",
transition: TRANSITION,
position: "absolute"
};
styleLogo.push(
Object.assign({}, styleLogoBase, {
width: "" + logoS + "px",
height: "" + logoS + "px",
marginLeft: "" + -logoS / 2 + "px",
marginTop: "" + -logoS / 2 + "px",
background: "white",
zIndex: "" + zIndex,
transform: "rotate(" + (-angleRotate * 180) / Math.PI + "deg)"
})
);
styleOverlay.push(
Object.assign({}, styleLogoBase, {
width: "" + logoS + "px",
height: "" + logoS + "px",
marginLeft: "" + -logoS / 2 + "px",
marginTop: "" + -logoS / 2 + "px",
background: "rgba(187,187,187," + shadow + ")",
zIndex: "" + (zIndex + 1),
cursor:
acceptableContent || this.props.settings.config.actionType === consts.ACTION_MUTE
? "pointer"
: "not-allowed"
})
);
styleName.push({
left: "calc(" + logoL + "% + " + (logoS * (0.5 + 1 / 8) + 30) * Math.cos(-angleRotate) + "px)",
top: "calc(" + logoT + "% + " + ((logoS * (0.5 + 1 / 8) + 30) * Math.sin(-angleRotate) - controlShift) + "px)",
fontSize: "" + fontS + "px",
fontWeight: "bold",
color: "rgba(0,0,0," + (1 - shadow) + ")",
marginTop: "-0.5em",
zIndex: "" + zIndex,
transformOrigin: "left",
return {
left: "calc(" + logoL + "% - " + (statusR / 2) * 1.1 * Math.cos(statusAngle + angleRotate) + "px)",
top:
"calc(" + logoT + "% + " + ((statusR / 2) * 1.1 * Math.sin(statusAngle + angleRotate) - controlShift) + "px)",
width: statusS + "px",
height: statusS + "px",
transform: "rotate(" + (-angleRotate * 180) / Math.PI + "deg)",
transition: TRANSITION,
cursor: acceptableContent ? "pointer" : "not-allowed"
});
styleStatus.push(function(status, condensed) {
let statusAngle = 0,
statusBgColor = consts.getStatusColor(status);
switch (status) {
case consts.STATUS_AD:
statusAngle = -STATUS_ANGLE * (1 + 0.5 * condensed);
break;
case consts.STATUS_MUSIC:
statusAngle = +STATUS_ANGLE * (1 + 0.5 * condensed);
break;
default:
}
return {
left: "calc(" + logoL + "% - " + (statusR / 2) * 1.1 * Math.cos(statusAngle + angleRotate) + "px)",
top:
"calc(" +
logoT +
"% + " +
((statusR / 2) * 1.1 * Math.sin(statusAngle + angleRotate) - controlShift) +
"px)",
width: statusS + "px",
height: statusS + "px",
transform: "rotate(" + (-angleRotate * 180) / Math.PI + "deg)",
zIndex: "" + (zIndex + 1),
marginLeft: -statusS / 2 + "px",
marginTop: -statusS / 2 + "px",
transition: TRANSITION,
background: radios[i].getStatus() === status ? statusBgColor : "#bbbbbb"
};
});
positionData.push({
left: "calc(" + logoL + "% + " + logoS * 0.3 + "px)",
top: "calc(" + logoT + "% - " + (logoS * 0.5 - controlShift) + "px)",
position: "absolute",
zIndex: "" + (zIndex + 1),
fontSize: "" + fontS + "px",
fontWeight: "bold",
opacity: isPodium ? "1" : "0",
transition: TRANSITION
});
}
marginLeft: -statusS / 2 + "px",
marginTop: -statusS / 2 + "px",
transition: TRANSITION,
background: radios[i].getStatus() === status ? statusBgColor : "#bbbbbb"
};
});
return (
<div ref={this.radiolistRef} onKeyPress={this.handleNav} onScroll={this.handleScroll} style={styleDivRadios}>
<Controls settings={this.props.settings} bsw={this.props.bsw} condensed={this.props.condensed} />
{radios.map(function(radio, i) {
let ti = translate.indexOf(i);
return (
<RadioElement
radio={radio}
key={"radio" + i}
style={{
logo: styleLogo[ti],
overlay: styleOverlay[ti],
name: styleName[ti],
status: styleStatus[ti]
}}
position={{
number: i + 1,
style: positionData[ti]
}}
condensed={self.props.condensed}
onClick={() => self.handleRadioClick(ti)}
/>
);
})}
</div>
);
positionData.push({
left: "calc(" + logoL + "% + " + logoS * 0.3 + "px)",
top: "calc(" + logoT + "% - " + (logoS * 0.5 - controlShift) + "px)",
position: "absolute",
zIndex: "" + (zIndex + 1),
fontSize: "" + fontS + "px",
fontWeight: "bold",
opacity: isPodium ? "1" : "0",
transition: TRANSITION
});
}
return (
<div ref={carouselRef} style={styleDivRadios}>
<Controls settings={settings} bsw={bsw} condensed={condensed} />
{radios.map(function(radio, i) {
let ti = translate.indexOf(i);
return (
<RadioElement
radio={radio}
key={"radio" + i}
style={{
logo: styleLogo[ti],
overlay: styleOverlay[ti],
name: styleName[ti],
status: styleStatus[ti]
}}
position={{
number: i + 1,
style: positionData[ti]
}}
condensed={condensed}
onClick={() => handleRadioClick(ti)}
/>
);
})}
</div>
);
}
RadiosCarousel.propTypes = {