From d8adb83293f969db49f4f9e94041d1cddaa86407 Mon Sep 17 00:00:00 2001 From: mmahalwy Date: Sun, 10 Jul 2016 02:03:04 -0400 Subject: [PATCH 1/4] Audioplayer repeat --- .../Audioplayer/RepeatButton/index.js | 213 +++++++++++++++--- src/components/Audioplayer/index.js | 26 ++- src/components/Audioplayer/style.scss | 23 ++ src/redux/modules/audioplayer.js | 16 +- 4 files changed, 229 insertions(+), 49 deletions(-) diff --git a/src/components/Audioplayer/RepeatButton/index.js b/src/components/Audioplayer/RepeatButton/index.js index 423e0a0c5..1f8419a4d 100644 --- a/src/components/Audioplayer/RepeatButton/index.js +++ b/src/components/Audioplayer/RepeatButton/index.js @@ -1,39 +1,186 @@ -import React, { PropTypes } from 'react'; +import React, { Component, PropTypes } from 'react'; import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; -import Tooltip from 'react-bootstrap/lib/Tooltip'; +import Popover from 'react-bootstrap/lib/Popover'; +import Nav from 'react-bootstrap/lib/Nav'; +import NavItem from 'react-bootstrap/lib/NavItem'; +import Row from 'react-bootstrap/lib/Row'; +import Col from 'react-bootstrap/lib/Col'; + +import SwitchToggle from 'components/SwitchToggle'; const style = require('../style.scss'); -const RepeatButton = ({ shouldRepeat, onRepeatToggle }) => { - const tooltip = ( - - Repeats the current ayah on end... - - ); - - return ( -
- - { + const { repeat, setRepeat, current } = this.props; + + if (repeat.from) { + return setRepeat({}); + } + + return setRepeat({ + from: current, + to: current + }); + } + + handleNavChange = (nav) => { + const { setRepeat, current } = this.props; + + this.setState({ nav }); + + if (nav === 1) { + // Should set single ayah + return setRepeat({ + from: current, + to: current + }); + } + + return setRepeat({ + from: current, + to: current + 3 + }); + } + + renderRangeAyahs() { + const { surah, repeat, setRepeat } = this.props; + const array = Array(surah.ayat).join().split(','); + + return ( + +
    +
  • + +
  • +
  • -
  • +
  • + +
  • +
+ + ); + } + + renderSingleAyah() { + const { current } = this.props; + + return ( + +
+ Repeating Ayah: {current} +
+ + ); + } + + renderOptions() { + const { repeat } = this.props; + + return ( + + + + + + ); + } + + render() { + const { repeat } = this.props; + + const popover = ( + + + Toggle repeat{' '} + + + + } > - + ); + + return ( +
+ - - - -
- ); -}; - -RepeatButton.propTypes = { - shouldRepeat: PropTypes.bool.isRequired, - onRepeatToggle: PropTypes.func.isRequired -}; - -export default RepeatButton; + +
+
+ ); + } +} diff --git a/src/components/Audioplayer/index.js b/src/components/Audioplayer/index.js index e62ca62d4..1f220d9f7 100644 --- a/src/components/Audioplayer/index.js +++ b/src/components/Audioplayer/index.js @@ -8,7 +8,7 @@ import { stop, next, previous, - toggleRepeat, + setRepeat, toggleScroll, buildOnClient, update @@ -37,7 +37,7 @@ const style = require('./style.scss'); isPlaying: state.audioplayer.isPlaying, isLoadedOnClient: state.audioplayer.isLoadedOnClient, isLoading: state.audioplayer.isLoading, - shouldRepeat: state.audioplayer.shouldRepeat, + repeat: state.audioplayer.repeat, shouldScroll: state.audioplayer.shouldScroll, duration: state.audioplayer.duration, currentTime: state.audioplayer.currentTime, @@ -47,7 +47,7 @@ const style = require('./style.scss'); stop, next, previous, - toggleRepeat, + setRepeat, toggleScroll, buildOnClient, update @@ -70,9 +70,9 @@ export default class Audioplayer extends Component { next: PropTypes.func.isRequired, previous: PropTypes.func.isRequired, update: PropTypes.func.isRequired, - shouldRepeat: PropTypes.bool.isRequired, + repeat: PropTypes.object.isRequired, shouldScroll: PropTypes.bool.isRequired, - toggleRepeat: PropTypes.func.isRequired, + setRepeat: PropTypes.func.isRequired, toggleScroll: PropTypes.func.isRequired, isPlaying: PropTypes.bool, currentTime: PropTypes.number, @@ -249,9 +249,9 @@ export default class Audioplayer extends Component { }); const onEnded = () => { - const { shouldRepeat } = this.props; + const { repeat } = this.props; - if (shouldRepeat) { + if (repeat) { file.pause(); file.currentTime = 0; // eslint-disable-line no-param-reassign return file.play(); @@ -361,10 +361,11 @@ export default class Audioplayer extends Component { currentTime, isSupported, duration, + surah, isLoadedOnClient, - shouldRepeat, // eslint-disable-line no-shadow + repeat, // eslint-disable-line no-shadow shouldScroll, // eslint-disable-line no-shadow - toggleRepeat // eslint-disable-line no-shadow + setRepeat // eslint-disable-line no-shadow } = this.props; if (!isSupported) { @@ -399,7 +400,12 @@ export default class Audioplayer extends Component { {this.renderNextButton()}
  • - +
  • diff --git a/src/components/Audioplayer/style.scss b/src/components/Audioplayer/style.scss index 4c30b6689..bd422bbac 100644 --- a/src/components/Audioplayer/style.scss +++ b/src/components/Audioplayer/style.scss @@ -68,9 +68,32 @@ :local .disabled{ opacity: 0.5; cursor: not-allowed !important; + pointer-events: none; } .verse{ padding: 0px; color: $olive; } + +:local .popover{ + :global(.popover-title){ + font-family: $font-montserrat; + text-transform: uppercase; + color: $cream; + padding-top: 15px; + padding-bottom: 15px; + font-size: 0.75em; + } + + :global(.popover-content){ + :global(a){ + font-size: 0.8em; + } + } + .pill{ + :global(a){ + padding: 10px 15px; + } + } +} diff --git a/src/redux/modules/audioplayer.js b/src/redux/modules/audioplayer.js index 6ef7d598b..744a4245e 100644 --- a/src/redux/modules/audioplayer.js +++ b/src/redux/modules/audioplayer.js @@ -18,7 +18,7 @@ const STOP = '@@quran/audioplayer/STOP'; export const NEXT = '@@quran/audioplayer/NEXT'; export const SET_AYAH = '@@quran/audioplayer/SET'; const PREVIOUS = '@@quran/audioplayer/PREVIOUS'; -const TOGGLE_REPEAT = '@@quran/audioplayer/TOGGLE_REPEAT'; +const SET_REPEAT = '@@quran/audioplayer/SET_REPEAT'; const TOGGLE_SCROLL = '@@quran/audioplayer/TOGGLE_SCROLL'; const BUILD_ON_CLIENT = '@@quran/audioplayer/BUILD_ON_CLIENT'; const UPDATE = '@@quran/audioplayer/UPDATE'; @@ -32,7 +32,10 @@ const initialState = { currentTime: 0, isSupported: true, isPlaying: false, - shouldRepeat: false, + repeat: { + from: undefined, + to: undefined + }, shouldScroll: false, isLoadedOnClient: false, isLoading: true @@ -204,10 +207,10 @@ export default function reducer(state = initialState, action = {}) { currentTime: 0 }; } - case TOGGLE_REPEAT: + case SET_REPEAT: return { ...state, - shouldRepeat: !state.shouldRepeat + repeat: action.repeat }; case TOGGLE_SCROLL: return { @@ -325,9 +328,10 @@ export function previous(currentAyah) { }; } -export function toggleRepeat() { +export function setRepeat(repeat) { return { - type: TOGGLE_REPEAT + type: SET_REPEAT, + repeat }; } From c78bbafba193728f81fad3d2eb4a6044abe59a72 Mon Sep 17 00:00:00 2001 From: mmahalwy Date: Sun, 10 Jul 2016 14:28:36 -0400 Subject: [PATCH 2/4] ui --- package.json | 2 +- .../{RepeatButton => RepeatDropdown}/index.js | 73 +++++++++++++++---- .../{RepeatButton => RepeatDropdown}/spec.js | 26 ++++--- src/components/Audioplayer/index.js | 4 +- 4 files changed, 78 insertions(+), 27 deletions(-) rename src/components/Audioplayer/{RepeatButton => RepeatDropdown}/index.js (68%) rename src/components/Audioplayer/{RepeatButton => RepeatDropdown}/spec.js (54%) diff --git a/package.json b/package.json index f2dabac83..46366c901 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "raven": "^0.11.0", "raw-loader": "^0.5.1", "react": "^0.14.8", - "react-bootstrap": "^0.28.4", + "react-bootstrap": "^0.29.5", "react-cookie": "^0.3.4", "react-dom": "^0.14.0", "react-helmet": "^3.1.0", diff --git a/src/components/Audioplayer/RepeatButton/index.js b/src/components/Audioplayer/RepeatDropdown/index.js similarity index 68% rename from src/components/Audioplayer/RepeatButton/index.js rename to src/components/Audioplayer/RepeatDropdown/index.js index 1f8419a4d..6760dd5af 100644 --- a/src/components/Audioplayer/RepeatButton/index.js +++ b/src/components/Audioplayer/RepeatDropdown/index.js @@ -3,6 +3,7 @@ import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger'; import Popover from 'react-bootstrap/lib/Popover'; import Nav from 'react-bootstrap/lib/Nav'; import NavItem from 'react-bootstrap/lib/NavItem'; +import FormControl from 'react-bootstrap/lib/FormControl'; import Row from 'react-bootstrap/lib/Row'; import Col from 'react-bootstrap/lib/Col'; @@ -64,10 +65,11 @@ export default class RepeatButton extends Component { return ( -
      + From - To:
      +
      • - +
      • -
      • - +
      @@ -106,13 +108,29 @@ export default class RepeatButton extends Component { } renderSingleAyah() { - const { current } = this.props; + const { repeat, setRepeat, surah } = this.props; + const array = Array(surah.ayat).join().split(','); return ( - -
      - Repeating Ayah: {current} -
      + + Ayah:
      + setRepeat({ + ...repeat, + from: parseInt(event.target.value, 10), + to: parseInt(event.target.value, 10) + })} + > + { + array.map((ayah, index) => ( + + )) + } + ); } @@ -129,7 +147,7 @@ export default class RepeatButton extends Component { onSelect={this.handleNavChange} > - Single Ayah + Single Range @@ -141,7 +159,8 @@ export default class RepeatButton extends Component { } render() { - const { repeat } = this.props; + const { repeat, setRepeat } = this.props; + const times = Array(10).join().split(','); const popover = ( {this.renderOptions()} - + {this.state.nav === 1 ? this.renderSingleAyah() : this.renderRangeAyahs()} + + + Times:
      + setRepeat({ + ...repeat, + times: parseInt(event.target.value, 10) + })} + > + + { + times.map((ayah, index) => ( + + )) + } + + +
      ); diff --git a/src/components/Audioplayer/RepeatButton/spec.js b/src/components/Audioplayer/RepeatDropdown/spec.js similarity index 54% rename from src/components/Audioplayer/RepeatButton/spec.js rename to src/components/Audioplayer/RepeatDropdown/spec.js index a31ba8f56..25616b1c9 100644 --- a/src/components/Audioplayer/RepeatButton/spec.js +++ b/src/components/Audioplayer/RepeatDropdown/spec.js @@ -1,29 +1,37 @@ import React from 'react'; import { mount } from 'enzyme'; -import RepeatButton from './index'; +import RepeatDropdown from './index'; -let makeComponent, component, onRepeatToggle; +let makeComponent, component, setRepeat; +const surah = { + ayat: 10 +}; -describe('', () => { +describe('', () => { beforeEach(() => { - makeComponent = (shouldRepeat) => { - onRepeatToggle = sinon.stub(); + makeComponent = (repeat) => { + setRepeat = sinon.stub(); component = mount( - + ); } }); - it('should indicate that shouldRepeat', () => { - makeComponent(true); + it('should indicate repeating', () => { + makeComponent({from: 1, to: 10}); expect(component.find('label').first().props().className).to.contain('repeat'); }); it('should not indicate that shouldRepeat', () => { - makeComponent(false); + makeComponent({}); expect(component.find('label').first().props().className).not.to.contain('repeat'); }); diff --git a/src/components/Audioplayer/index.js b/src/components/Audioplayer/index.js index 1f220d9f7..abe8f0a23 100644 --- a/src/components/Audioplayer/index.js +++ b/src/components/Audioplayer/index.js @@ -18,7 +18,7 @@ import { import Track from './Track'; import Segments from './Segments'; import ScrollButton from './ScrollButton'; -import RepeatButton from './RepeatButton'; +import RepeatDropdown from './RepeatDropdown'; // Helpers import debug from '../../helpers/debug'; @@ -400,7 +400,7 @@ export default class Audioplayer extends Component { {this.renderNextButton()}
    • - Date: Sun, 10 Jul 2016 14:44:28 -0400 Subject: [PATCH 3/4] wip --- .../Audioplayer/RepeatDropdown/index.js | 2 +- src/components/Audioplayer/index.js | 54 +++++++++++++++++-- src/redux/modules/audioplayer.js | 3 +- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/components/Audioplayer/RepeatDropdown/index.js b/src/components/Audioplayer/RepeatDropdown/index.js index 6760dd5af..d6bbe8f9e 100644 --- a/src/components/Audioplayer/RepeatDropdown/index.js +++ b/src/components/Audioplayer/RepeatDropdown/index.js @@ -195,7 +195,7 @@ export default class RepeatButton extends Component { times: parseInt(event.target.value, 10) })} > - { diff --git a/src/components/Audioplayer/index.js b/src/components/Audioplayer/index.js index abe8f0a23..21c384a44 100644 --- a/src/components/Audioplayer/index.js +++ b/src/components/Audioplayer/index.js @@ -205,6 +205,54 @@ export default class Audioplayer extends Component { } } + handleRepeat = (file) => { + const { repeat, currentAyah, setRepeat } = this.props; + const ayah = parseInt(currentAyah.split(':')[1], 10); + + file.pause(); + + if (repeat.from > ayah && repeat.to < ayah) { + // user selected a range where current ayah is outside + return this.handleAyahChange(); + } + + if (repeat.from === repeat.to) { + // user selected single ayah repeat + if (repeat.times === 1) { + // end of times + setRepeat({}); + + return this.handleAyahChange(); + } + + setRepeat({ ...repeat, times: repeat.times - 1 }); + file.currentTime = 0; // eslint-disable-line no-param-reassign + + return file.play(); + } + + if (repeat.from !== repeat.to) { + // user selected a range + if (ayah < repeat.to) { + // still in range + return this.handleAyahChange(); + } + + if (ayah === repeat.to) { + // end of range + if (repeat.times === 1) { + // end of times + setRepeat({}); + + return this.handleAyahChange(); + } + + setRepeat({ ...repeat, times: repeat.times - 1 }); + + } + } + } + handleScrollToggle = (event) => { event.preventDefault(); @@ -251,10 +299,8 @@ export default class Audioplayer extends Component { const onEnded = () => { const { repeat } = this.props; - if (repeat) { - file.pause(); - file.currentTime = 0; // eslint-disable-line no-param-reassign - return file.play(); + if (repeat.from) { + return this.handleRepeat(file); } if (file.readyState >= 3 && file.paused) { diff --git a/src/redux/modules/audioplayer.js b/src/redux/modules/audioplayer.js index 744a4245e..45feed88e 100644 --- a/src/redux/modules/audioplayer.js +++ b/src/redux/modules/audioplayer.js @@ -34,7 +34,8 @@ const initialState = { isPlaying: false, repeat: { from: undefined, - to: undefined + to: undefined, + times: Infinity }, shouldScroll: false, isLoadedOnClient: false, From f959d87ec2ce8620f9f44e6bffbb68e573648560 Mon Sep 17 00:00:00 2001 From: mmahalwy Date: Sun, 10 Jul 2016 16:22:00 -0400 Subject: [PATCH 4/4] tests --- .../Audioplayer/RepeatDropdown/index.js | 86 ++++++----- .../Audioplayer/RepeatDropdown/spec.js | 81 ++++++++--- src/components/Audioplayer/index.js | 94 ++++++------ src/components/Audioplayer/spec.js | 136 ++++++++++++++++++ src/containers/Surah/index.js | 2 +- src/redux/modules/audioplayer.js | 17 ++- 6 files changed, 296 insertions(+), 120 deletions(-) create mode 100644 src/components/Audioplayer/spec.js diff --git a/src/components/Audioplayer/RepeatDropdown/index.js b/src/components/Audioplayer/RepeatDropdown/index.js index d6bbe8f9e..60fd8b52d 100644 --- a/src/components/Audioplayer/RepeatDropdown/index.js +++ b/src/components/Audioplayer/RepeatDropdown/index.js @@ -15,18 +15,14 @@ export default class RepeatButton extends Component { static propTypes = { surah: PropTypes.object.isRequired, repeat: PropTypes.shape({ - from: PropTypes.number.isRequired, - to: PropTypes.number.isRequired, + from: PropTypes.number, + to: PropTypes.number, times: PropTypes.number }).isRequired, setRepeat: PropTypes.func.isRequired, current: PropTypes.number.isRequired }; - state = { - nav: 1 - }; - handleToggle = () => { const { repeat, setRepeat, current } = this.props; @@ -43,8 +39,6 @@ export default class RepeatButton extends Component { handleNavChange = (nav) => { const { setRepeat, current } = this.props; - this.setState({ nav }); - if (nav === 1) { // Should set single ayah return setRepeat({ @@ -135,7 +129,7 @@ export default class RepeatButton extends Component { ); } - renderOptions() { + renderNav() { const { repeat } = this.props; return ( @@ -143,7 +137,7 @@ export default class RepeatButton extends Component {