nypublicradio/nypr-audio-services

View on GitHub
addon/components/listen-button.js

Summary

Maintainability
A
55 mins
Test Coverage
C
74%
import { A } from '@ember/array';
import { getWithDefault } from '@ember/object';
import Component from '@ember/component';
import {
  readOnly,
  not,
  match,
  and
} from '@ember/object/computed';
import { get, set, getProperties, computed } from '@ember/object';
import { htmlSafe } from '@ember/string';
import { schedule } from '@ember/runloop';
import { inject as service } from '@ember/service';
import layout from '../templates/components/listen-button';
import diffAttrs from 'ember-diff-attrs';

const STATES = {
  PLAYING:  'is-playing',
  PAUSED:   'is-paused',
  LOADING:  'is-loading'
};

const EXPANDABLE_BUTTONS = /(blue|gray|red)-(minion|boss)|white-hollow-block/;

export default Component.extend({
  layout,
  dj:                   service(),
  disabled:             not('dj.isReady'),
  playButtonClickedAction: () => {},

  isCurrentSound:       computed('dj.currentContentId', 'itemPK', function() {
    return get(this, 'itemPK') === get(this, 'dj.currentContentId');
  }),

  isPlaying:            and('dj.isPlaying', 'isCurrentSound'),
  _hifiPaused:          not('dj.isPlaying'),
  isPaused:             and('_hifiPaused', 'isCurrentSound'),
  isLoading:            computed('isCurrentSound', 'buttonLoading', 'dj.{currentSound.isLoading,currentlyLoadingIds}', function() {
    let currentlyLoadingIds = A(getWithDefault(this, 'dj.currentlyLoadingIds', []));

    return get(this, 'buttonLoading') ||
           currentlyLoadingIds.includes(String(get(this, 'itemPK'))) ||
           (get(this, 'isCurrentSound') && get(this, 'dj.currentSound.isLoading'));
  }),

  isExpandable:         match('type', EXPANDABLE_BUTTONS),
  'aria-label':         readOnly('title'),
  'data-test-selector': 'listen-button',

  tagName:              'button',
  classNames:           ['listen-button', 'gtm__click-tracking'],
  classNameBindings:    ['isHovering', 'type', 'isCurrentSound', 'isErrored', 'playState', 'isCurrentSound', 'isLive'],
  attributeBindings:    ['aria-label', 'title', 'disabled', 'data-test-selector', 'style', 'data-action', 'data-label'],

  // override in the template for streams and other action types
  'data-action': computed('playContext', function() {
    return `Clicked Play/Pause On Demand: ${this.get('playContext')}`;
  }),
  'data-label': computed('itemTitle', 'itemShow', function() {
    return `${this.get('itemTitle')} | ${this.get('itemShow')}`
  }),

  title: computed('itemTitle', function() {
    return `Listen to ${get(this, 'itemTitle')}`;
  }),

  style: computed('width', function() {
    let width = get(this, 'width');
    return width ? htmlSafe(`width: ${width}px;`) : null;
  }),

  playState: computed('isPlaying', 'isPaused', 'isLoading', 'wasMeasured', 'isExpandable', function() {
    let { wasMeasured, isExpandable } = getProperties(this, 'wasMeasured', 'isExpandable');
    if (isExpandable && !wasMeasured) {
      return STATES.PAUSED; // consider it paused until we measure so we get full width of natural state
    }

    if (get(this, 'isLoading')) {
      return STATES.LOADING;
    }
    else if (get(this, 'isPlaying')) {
      return STATES.PLAYING;
    }
    else if (get(this, 'isPaused')){
      return STATES.PAUSED;
    } else {
      return STATES.PAUSED; // JIC
    }
  }),

  width: computed('playState', 'contentWidth', 'isExpandable', function() {
    if (typeof FastBoot !== 'undefined' || !this.element || !get(this, 'isExpandable')) {
      return false;
    }

    let state = get(this, 'playState');
    if (state === STATES.PLAYING || state === STATES.LOADING) {
      return Math.ceil(this.element.getBoundingClientRect().height); // make it a circle, set width = height
    } else {
      return get(this, 'contentWidth');
    }
  }),

  didUpdateAttrs: diffAttrs('isLive', function(changedAttrs, ...args) {
    this._super(...args);
    if (typeof FastBoot !== 'undefined' || !this.element || !get(this, 'isExpandable')) {
      return false;
    }
    let updateSize = this.get('isLive') || changedAttrs && changedAttrs.isLive && changedAttrs.isLive[0];

    if (updateSize) {
      schedule('afterRender', this, () => {
        let contentWidth = this.element.scrollWidth + parseInt(this.$().css('paddingLeft'), 10) + parseInt(this.$().css('paddingRight'), 10);
        set(this, 'contentWidth', contentWidth);
      });
    }
  }),

  didRender() {
    let { wasMeasured, isExpandable } = getProperties(this, 'wasMeasured', 'isExpandable');
    if (isExpandable && !wasMeasured) {
      schedule('afterRender', this, () => {
        let contentWidth = Math.ceil(this.element.getBoundingClientRect().width);
        set(this, 'wasMeasured', true);
        set(this, 'contentWidth', contentWidth);
      });
    }
  },

  play() {
    let playContext     = get(this, 'playContext');
    let itemPk          = get(this, 'itemPK');
    let metadata        = {fromClick: true};
    set(this, 'buttonLoading', true);
    get(this,'dj').play(itemPk, {playContext, metadata}).then(() => {
      set(this, 'buttonLoading', false);
    }).catch(() => {
      set(this, 'buttonLoading', false);
      set(this, 'isErrored', true);
    });
  },

  click() {
    let dj = get(this, 'dj');
    if (get(this, 'isPlaying')) {
      dj.pause();
    } else {
      this.play();
      this.playButtonClickedAction(event);
    }
  },

  mouseLeave() {
    set(this, 'isHovering', false);
  },
  mouseEnter() {
    set(this, 'isHovering', true);
  },
});