sudara/alonetone

View on GitHub
app/javascript/controllers/normal_playback_controller.js

Summary

Maintainability
A
0 mins
Test Coverage
import { gsap } from 'gsap'
import PlaybackController from './playback_controller'
import PlayAnimation from '../animation/play_animation'

let currentlyOpen

export default class extends PlaybackController {
  // these are added to the targets defined in PlaybackController
  static targets = ['playButton', 'details', 'time', 'seekBarPlayed', 'title']
  static values = {
    unopenable: Boolean
  }

  playing() {
    if (!this.loaded) {
      this.animation.pausingAnimation()
    } else this.animation.showPauseButton()
    this.loaded = true
  }

  playCallback() {
    this.setupAnimation()
    if (!this.loaded) {
      this.animation.loadingAnimation()
    } else this.animation.showPauseButton()
    if (currentlyOpen && (currentlyOpen !== this)) {
      currentlyOpen.closeDetails()
      currentlyOpen = undefined
    }
    if (!this.hasUnopenableValue) {
      this.openDetails()
    }
    this.showSeekBar()
    this.registeredListen = true
    this.alreadyPlayed = true
  }

  pauseCallback() {
    this.animation.showPlayButton()
  }

  stopCallback() {
    this.animation.showPlayButton()
  }

  toggleDetails(e) {
    if (!e.target.classList.contains('artist')) {
      // if the link in the track top is the artist link, go to that URL,
      // otherwise open the track reveal section
      e.preventDefault()

      const isAlreadyOpen = this.element.classList.contains('open')
      // if another track details is open, close it
      if (currentlyOpen) {
        currentlyOpen.closeDetails()
      }
      if (!this.hasUnopenableValue && !isAlreadyOpen ) {
        this.openDetails()
      }
    }
  }

  closeDetails() {
    currentlyOpen = undefined
    this.element.classList.remove('open')
    this.seekBarContainerTarget.classList.remove('show')
    // Height of the details could have changed (for example private banner showing)
    // So the margin offset for animating needs to be recalculated here
    gsap
      .to(this.detailsTarget, {
        duration: 0.25,
        marginTop: -this.detailsTarget.offsetHeight,
        ease: 'power4.inOut',
        display: 'none',
      })
  }

  openDetails() {
    if (currentlyOpen !== this) {
      this.element.classList.add('open')

      // can't animate "display" attribute as offsetHeight depends on it
      this.detailsTarget.style.display = 'block'
      gsap.set(this.detailsTarget, { marginTop: -this.detailsTarget.offsetHeight })
      gsap.to(this.detailsTarget, {
        duration: 0.25,
        marginTop: 0,
        ease: 'power4.inOut',
        display: 'block',
      })
      if (this.alreadyPlayed) {
        this.seekBarContainerTarget.classList.add('show')
      }
    }
    currentlyOpen = this
  }

  // We have one single #playAnimationSVG element to move around and animate
  // Until this point, our play button has been a placeholder icon SVG
  // After this point, our play button is an animatable SVG
  // (Until play is pressed elsewhere)
  //
  // Note: Because our svg has a mask with an id, we can't have multiple copies of it in the DOM
  // Without refactoring how the svg and animation work
  setupAnimation() {
    if (!this.animation) {
      this.animation = new PlayAnimation(this.playButtonTarget)
    }
  }

  showSeekBar() {
    this.seekBarContainerTarget.classList.add('show');
  }

  // turbolinks caches pages, so let's make sure things are sane when we return
  disconnect() {
    super.disconnect()

    if (this.animation) {
      this.animation.reset()
    }
    if (this.element.classList.contains('open')) {
      this.element.classList.remove('open')
    }
  }
}