// @flow import React, { Component } from 'react'; import { Audio } from '../../media'; import type { AudioElement } from '../../media'; import { connect } from '../../redux'; import { _addAudioElement, _removeAudioElement } from '../actions'; import type { Sound } from '../reducer'; /** * {@link SoundCollection}'s properties. */ type Props = { /** * Dispatches {@link _ADD_AUDIO_ELEMENT} Redux action which will store the * {@link AudioElement} for a sound in the Redux store. */ _addAudioElement: Function, /** * Dispatches {@link _REMOVE_AUDIO_ELEMENT} Redux action which will remove * the sound's {@link AudioElement} from the Redux store. */ _removeAudioElement: Function, /** * It's the 'base/sounds' reducer's state mapped to a property. It's used to * render audio elements for every registered sound. */ _sounds: Map } /** * Collections of all global sounds used by the app for playing audio * notifications in response to various events. It renders Audio * element for each sound registered in the base/sounds feature. When the audio * resource is loaded it will emit add/remove audio element actions which will * attach the element to the corresponding {@link Sound} instance in the Redux * state. When that happens the sound can be played using the {@link playSound} * action. */ class SoundCollection extends Component { /** * Implements React's {@link Component#render()}. * * @inheritdoc * @returns {ReactElement} */ render() { let key = 0; const sounds = []; for (const [ soundId, sound ] of this.props._sounds.entries()) { const { options, src } = sound; sounds.push( React.createElement( Audio, { key, setRef: this._setRef.bind(this, soundId), src, loop: options.loop })); key += 1; } return sounds; } /** * Set the (reference to the) {@link AudioElement} object which implements * the audio playback functionality. * * @param {string} soundId - The sound Id for the audio element for which * the callback is being executed. * @param {AudioElement} element - The {@link AudioElement} instance * which implements the audio playback functionality. * @protected * @returns {void} */ _setRef(soundId: string, element: ?AudioElement) { if (element) { this.props._addAudioElement(soundId, element); } else { this.props._removeAudioElement(soundId); } } } /** * Maps (parts of) the Redux state to {@code SoundCollection}'s props. * * @param {Object} state - The redux state. * @private * @returns {{ * _sounds: Map * }} */ function _mapStateToProps(state) { return { _sounds: state['features/base/sounds'] }; } /** * Maps dispatching of some actions to React component props. * * @param {Function} dispatch - Redux action dispatcher. * @private * @returns {{ * _addAudioElement: void, * _removeAudioElement: void * }} */ export function _mapDispatchToProps(dispatch: Function) { return { /** * Dispatches action to store the {@link AudioElement} for * a {@link Sound} identified by given soundId in the Redux * store, so that the playback can be controlled through the Redux * actions. * * @param {string} soundId - A global identifier which will be used to * identify the {@link Sound} instance for which an audio element will * be added. * @param {AudioElement} audioElement - The {@link AudioElement} * instance that will be stored in the Redux state of the base/sounds * feature, as part of the {@link Sound} object. At that point the sound * will be ready for playback. * @private * @returns {void} */ _addAudioElement(soundId: string, audioElement: AudioElement) { dispatch(_addAudioElement(soundId, audioElement)); }, /** * Dispatches action to remove {@link AudioElement} from the Redux * store for specific {@link Sound}, because it is no longer part of * the DOM tree and the audio resource will be released. * * @param {string} soundId - The id of the {@link Sound} instance for * which an {@link AudioElement} will be removed from the Redux store. * @private * @returns {void} */ _removeAudioElement(soundId: string) { dispatch(_removeAudioElement(soundId)); } }; } export default connect(_mapStateToProps, _mapDispatchToProps)(SoundCollection);