diff --git a/src/components/RadioElement.js b/src/components/RadioElement.js
index 8bb0f2c..1c18342 100644
--- a/src/components/RadioElement.js
+++ b/src/components/RadioElement.js
@@ -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 (
-
-

-
-
-
- {!this.props.condensed && (
-
- {radio.name}
-
- )}
- {this.props.position && (
-
- )}
-
-
-
-
-
-
-
-
-
+ const isDesktop = useBreakpoint("l");
+
+ return (
+ <>
+

+
+
- );
- }
+
+ {isDesktop && (
+
+ {radio.name}
+
+ )}
+
+ {position && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
}
+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
};
diff --git a/src/components/RadiosCarousel.js b/src/components/RadiosCarousel.js
index a9bb872..941b6e4 100644
--- a/src/components/RadiosCarousel.js
+++ b/src/components/RadiosCarousel.js
@@ -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 (
-
-
- {radios.map(function(radio, i) {
- let ti = translate.indexOf(i);
- return (
- self.handleRadioClick(ti)}
- />
- );
- })}
-
- );
+ 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 (
+
+
+
+ {radios.map(function(radio, i) {
+ let ti = translate.indexOf(i);
+ return (
+ handleRadioClick(ti)}
+ />
+ );
+ })}
+
+ );
}
RadiosCarousel.propTypes = {