<commit_before>CMS.Views.ValidatingView = Backbone.View.extend({
    // Intended as an abstract class which catches validation errors on the model and
    // decorates the fields. Needs wiring per class, but this initialization shows how
    // either have your init call this one or copy the contents
    initialize : function() {
        this.listenTo(this.model, 'error', CMS.ServerError);
        this.listenTo(this.model, 'invalid', this.handleValidationError);
        this.selectorToField = _.invert(this.fieldToSelectorMap);
    },

    errorTemplate : _.template('<%= message %>'),

    events : {
        "change input" : "clearValidationErrors",
        "change textarea" : "clearValidationErrors"
    },
    fieldToSelectorMap : {
        // Your subclass must populate this w/ all of the model keys and dom selectors
        // which may be the subjects of validation errors
    },
    _cacheValidationErrors : [],

    handleValidationError : function(model, error) {
        // error is object w/ fields and error strings
        for (var field in error) {
            var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
            this._cacheValidationErrors.push(ele);
            if ($(ele).is('div')) {
                // put error on the contained inputs
                $(ele).find('input, textarea').addClass('error');
            }
            else $(ele).addClass('error');
            $(ele).parent().append(this.errorTemplate({message : error[field]}));
        }
    },

    clearValidationErrors : function() {
        // error is object w/ fields and error strings
        while (this._cacheValidationErrors.length > 0) {
            var ele = this._cacheValidationErrors.pop();
            if ($(ele).is('div')) {
                // put error on the contained inputs
                $(ele).find('input, textarea').removeClass('error');
            }
            else $(ele).removeClass('error');
            $(ele).nextAll('.message-error').remove();
        }
    },

    saveIfChanged : function(event) {
        // returns true if the value changed and was thus sent to server
        var field = this.selectorToField[event.currentTarget.id];
        var currentVal = this.model.get(field);
        var newVal = $(event.currentTarget).val();
        this.clearValidationErrors(); // curr = new if user reverts manually
        if (currentVal != newVal) {
            this.model.save(field, newVal);
            return true;
        }
        else return false;
    },
    // these should perhaps go into a superclass but lack of event hash inheritance demotivates me
    inputFocus : function(event) {
        $("label[for='" + event.currentTarget.id + "']").addClass("is-focused");
    },
    inputUnfocus : function(event) {
        $("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
    }
});
<commit_msg>Don't assume input fields will be within a div. https://edx.lighthouseapp.com/projects/102637/tickets/232<commit_after>CMS.Views.ValidatingView = Backbone.View.extend({
    // Intended as an abstract class which catches validation errors on the model and
    // decorates the fields. Needs wiring per class, but this initialization shows how
    // either have your init call this one or copy the contents
    initialize : function() {
        this.listenTo(this.model, 'error', CMS.ServerError);
        this.listenTo(this.model, 'invalid', this.handleValidationError);
        this.selectorToField = _.invert(this.fieldToSelectorMap);
    },

    errorTemplate : _.template('<%= message %>'),

    events : {
        "change input" : "clearValidationErrors",
        "change textarea" : "clearValidationErrors"
    },
    fieldToSelectorMap : {
        // Your subclass must populate this w/ all of the model keys and dom selectors
        // which may be the subjects of validation errors
    },
    _cacheValidationErrors : [],

    handleValidationError : function(model, error) {
        // error is object w/ fields and error strings
        for (var field in error) {
            var ele = this.$el.find('#' + this.fieldToSelectorMap[field]);
            this._cacheValidationErrors.push(ele);
            var inputElements = 'input, textarea';
            if ($(ele).is(inputElements)) {
                $(ele).addClass('error');
            }
            else {
                // put error on the contained inputs
                $(ele).find(inputElements).addClass('error');
            }
            $(ele).parent().append(this.errorTemplate({message : error[field]}));
        }
    },

    clearValidationErrors : function() {
        // error is object w/ fields and error strings
        while (this._cacheValidationErrors.length > 0) {
            var ele = this._cacheValidationErrors.pop();
            if ($(ele).is('div')) {
                // put error on the contained inputs
                $(ele).find('input, textarea').removeClass('error');
            }
            else $(ele).removeClass('error');
            $(ele).nextAll('.message-error').remove();
        }
    },

    saveIfChanged : function(event) {
        // returns true if the value changed and was thus sent to server
        var field = this.selectorToField[event.currentTarget.id];
        var currentVal = this.model.get(field);
        var newVal = $(event.currentTarget).val();
        this.clearValidationErrors(); // curr = new if user reverts manually
        if (currentVal != newVal) {
            this.model.save(field, newVal);
            return true;
        }
        else return false;
    },
    // these should perhaps go into a superclass but lack of event hash inheritance demotivates me
    inputFocus : function(event) {
        $("label[for='" + event.currentTarget.id + "']").addClass("is-focused");
    },
    inputUnfocus : function(event) {
        $("label[for='" + event.currentTarget.id + "']").removeClass("is-focused");
    }
});
<|endoftext|>


<commit_before>class SomaPlayerPopup {
  constructor() {
    this.stationFilter = ''
    this.findElements()
    this.handleLinks()
    this.hookupMenu()
    this.applyTheme()
    this.fetchSomaStations()
    this.listenForPlayback()
    this.displayTip()
  }

  hookupMenu() {
    this.stationMenuToggle.addEventListener('click', () => {
      if (this.stationMenuToggle.classList.contains('disabled')) {
        return
      }

      this.toggleStationMenu()
    })

    window.addEventListener('keydown', event => this.onKeydown(event))

    window.addEventListener('click', event => {
      const dropdown = event.target.closest('.dropdown')

      if (!dropdown) {
        this.closeStationMenu()
      }
    })
  }

  onKeydown(event) {
    switch (event.key) {
      case 'ArrowDown':
        this.focusStationListItem(1)
        this.openStationMenu()
        break
      case 'ArrowUp':
        this.focusStationListItem(-1)
        this.openStationMenu()
        break
      case 'Enter':
        this.handleEnter(event)
        break
      case ' ':
        this.handleSpace(event)
        break
      case 'Backspace':
        this.handleStationFilterTyping(null)
        break
      default:
        const input = String.fromCharCode(event.keyCode)
        if (/[a-zA-Z0-9]/.test(input)) {
          this.handleStationFilterTyping(event.key)
        }
    }
  }

  updateStationFilter(newValue) {
    this.stationFilter = newValue
    this.stationFilterEl.textContent = this.stationFilter
    this.stationFilterEl.classList.toggle('d-none', this.stationFilter === '')
  }

  handleStationFilterTyping(character) {
    if (!this.isStationMenuOpen()) {
      this.openStationMenu()
    }
    if (character) {
      this.updateStationFilter(this.stationFilter + character)
    } else {
      this.updateStationFilter(this.stationFilter.slice(0, this.stationFilter.length - 1))
    }
    this.filterStations()
  }

  handleSpace(event) {
    event.preventDefault()

    chrome.runtime.sendMessage({ action: 'info' }, info => {
      if (info.paused) {
        this.play()
      } else {
        this.pause()
      }
    })
  }

  handleEnter(event) {
    if (!this.isStationMenuOpen()) {
      return
    }
    event.preventDefault()

    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    if (focusedItem) {
      const button = focusedItem.querySelector('.station-button')
      this.updateStationFilter('')
      this.playStationFromButton(button)
    } else {
      this.toggleStationMenu()
    }
  }

  filterStations() {
    const listItems = Array.from(this.stationListEl.querySelectorAll('.station-list-item'))
    const showAllStations = this.stationFilter === ''

    for (const listItem of listItems) {
      const value = listItem.getAttribute('data-station-filter')
      const filterMatchesStation = value.indexOf(this.stationFilter) === 0
      const isVisible = showAllStations || filterMatchesStation
      listItem.classList.toggle('d-none', !isVisible)
    }
  }

  focusStationListItem(offset) {
    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    const listItems = Array.from(this.stationListEl.querySelectorAll('.station-list-item'))
    const firstListItem = listItems[0]
    const lastListItem = listItems[listItems.length - 1]
    let newFocusedItem

    if (focusedItem) {
      const index = listItems.indexOf(focusedItem)
      focusedItem.classList.remove('focused')

      if (offset > 0) {
        newFocusedItem = listItems[index + 1] || firstListItem
      } else {
        newFocusedItem = listItems[index - 1] || lastListItem
      }
    } else if (offset > 0) {
      newFocusedItem = firstListItem
    } else {
      newFocusedItem = lastListItem
    }

    if (newFocusedItem) {
      newFocusedItem.classList.add('focused')
      newFocusedItem.scrollIntoView()
    }
  }

  openStationMenu() {
    const container = this.stationMenuToggle.closest('.dropdown')
    container.classList.add('active')
    this.stationMenuToggle.classList.add('selected')

    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    if (focusedItem) {
      focusedItem.scrollIntoView()
    }
  }

  closeStationMenu() {
    this.updateStationFilter('')
    const container = this.stationMenuToggle.closest('.dropdown')
    container.classList.remove('active')
    this.stationMenuToggle.classList.remove('selected')
  }

  isStationMenuOpen() {
    return this.stationMenuToggle.classList.contains('selected')
  }

  toggleStationMenu() {
    if (this.isStationMenuOpen()) {
      this.closeStationMenu()
    } else {
      this.openStationMenu()
    }
  }

  listenForPlayback() {
    this.playButton.addEventListener('click', () => this.play())
    this.pauseButton.addEventListener('click', () => this.pause())
  }

  displayTip() {
    SomaPlayerUtil.getOptions().then(opts => {
      const tipsSetting = opts.tips || 'on'
      if (tipsSetting !== 'on') {
        return
      }

      const tips = Array.from(this.tipsList.querySelectorAll('.js-tip'))
      const index = Math.floor(Math.random() * tips.length)
      const tip = tips[index]
      tip.classList.remove('d-none')
      this.tipsList.classList.remove('d-none')
    })
  }

  findElements() {
    this.stationMenuToggle = document.getElementById('station-menu-toggle')
    this.stationFilterEl = document.getElementById('station-filter')
    this.stationListEl = document.getElementById('station-list')
    this.stationListItemTpl = document.getElementById('station-list-item-template')
    this.playButton = document.getElementById('play')
    this.pauseButton = document.getElementById('pause')
    this.currentInfoEl = document.getElementById('currently-playing')
    this.trackListEl = document.getElementById('track-list')
    this.trackListItemTpl = document.getElementById('track-list-item-template')
    this.stationImg = document.getElementById('station-image')
    this.tipsList = document.getElementById('tips-list')
  }

  insertStationOptions(stations) {
    const activeStationID = SomaLocalStorage.getCurrentStation()

    for (const data of stations) {
      const listItem = this.createStationListItem(data, data.id === activeStationID)
      this.stationListEl.appendChild(listItem)
    }

    this.stationMenuToggle.classList.remove('disabled')
    setTimeout(() => this.stationMenuToggle.focus(), 100)
    this.loadCurrentInfo()
  }

  loadDefaultStations() {
    console.debug('loading default station list')
    const url = chrome.extension.getURL('defaultStations.json')
    SomaPlayerUtil.getJSON(url).
      then(defaultStations => this.insertStationOptions(defaultStations))
  }

  fetchSomaStations() {
    chrome.runtime.sendMessage({ action: 'get_stations' }, stations => {
      if (!stations || stations.length < 1) {
        const msg = { action: 'refresh_stations' }

        chrome.runtime.sendMessage(msg, (stations, error) => {
          if (error) {
            console.error('failed to fetch stations, using defaults')
            this.loadDefaultStations()
          } else {
            console.debug('fetched stations', stations)
            this.insertStationOptions(stations)
          }
        })
      } else {
        console.debug('stations already stored', stations)
        this.insertStationOptions(stations)
      }
    })
  }

  emptyTrackList() {
    while (this.trackListEl.hasChildNodes()) {
      this.trackListEl.removeChild(this.trackListEl.lastChild);
    }
  }

  listTracks(tracks) {
    setTimeout(() => {
      this.emptyTrackList()

      for (const track of tracks) {
        const listItem = this.createTrackListItem(track)
        this.trackListEl.appendChild(listItem)
      }
    }, 70)
  }

  onStationButtonClick(event) {
    const button = event.currentTarget
    this.playStationFromButton(button)
  }

  playStationFromButton(button) {
    const stationID = button.value
    const stationName = button.querySelector('.station-name').textContent
    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    const listItem = button.closest('.station-list-item')

    if (focusedItem) {
      focusedItem.classList.remove('focused')
    }
    button.blur()
    this.toggleStationMenu()
    this.stationChanged(stationID, stationName)
    listItem.classList.add('focused')
  }

  filterValueForStation(stationTitle) {
    return stationTitle.toLowerCase().replace(/\s+/g, '')
  }

  createStationListItem(data, isActive) {
    const el = this.stationListItemTpl.content.cloneNode(true)
    const button = el.querySelector('.station-button')
    const nameEl = el.querySelector('.station-name')
    const imageEl = el.querySelector('.station-image')
    const listItem = el.querySelector('.station-list-item')

    if (isActive) {
      listItem.classList.add('focused')
    }

    listItem.setAttribute('data-station-filter', this.filterValueForStation(data.title))
    imageEl.src = `station-images/${data.id}.png`
    button.value = data.id
    nameEl.textContent = data.title
    button.addEventListener('click', e => this.onStationButtonClick(e))

    return el
  }

  createTrackListItem(track) {
    const el = this.trackListItemTpl.content.cloneNode(true)
    const nameEl = el.querySelector('.track-name')
    const artistEl = el.querySelector('.artist')
    const timeEl = el.querySelector('.track-date')
    const date = new Date(track.date)

    nameEl.textContent = track.title
    artistEl.textContent = track.artist
    timeEl.textContent = this.prettyDate(date)

    return el
  }

  prettyDate(date) {
    const fullHours = date.getHours()
    const hours = fullHours - 12
    let minutes = date.getMinutes()
    if (minutes < 10) {
      minutes = `0${minutes}`
    }
    let amPM = 'am'
    if (fullHours >= 12) {
      amPM = 'pm'
    }
    return `${hours}:${minutes} ${amPM}`
  }

  hideTrackInfo() {
    this.emptyTrackList()
    this.trackListEl.classList.add('d-none')
  }

  loadCurrentInfo() {
    this.stationMenuToggle.classList.add('disabled')

    chrome.runtime.sendMessage({ action: 'info' }, info => {
      console.debug('finished info request, info', info);
      const currentStationButton = document.querySelector(`.station-button[value="${info.station}"]`)
      if (currentStationButton) {
        this.stationMenuToggle.value = info.station
        this.stationMenuToggle.textContent = currentStationButton.textContent
      }

      if (info.paused) {
        this.stationIsPaused()
      } else {
        this.play()
      }

      this.updateStationImage(info.station)
      this.stationMenuToggle.classList.remove('disabled')
      this.listTracks(info.tracks)
    })
  }

  stationIsPlaying() {
    this.pauseButton.classList.remove('d-none')
    this.playButton.classList.add('d-none')
    this.stationMenuToggle.focus()
  }

  stationIsPaused() {
    this.pauseButton.classList.add('d-none')
    this.playButton.classList.remove('d-none')
    this.playButton.disabled = false
    this.stationMenuToggle.focus()
  }

  play() {
    const station = this.stationMenuToggle.value
    if (!station || station.length < 1) {
      return
    }
    console.debug('play', station)
    this.updateStationImage(station)

    chrome.runtime.sendMessage({ action: 'play', station }, () => {
      this.stationIsPlaying()

      chrome.runtime.sendMessage({ action: 'info' }, info => {
        if (info.tracks && info.tracks.length > 0) {
          this.listTracks(info.tracks)
        } else {
          const api = new SomaAPI()
          api.getStationTracks(station).
            then(tracks => this.listTracks(tracks)).
            catch(error => console.error('failed to get tracks', error))
        }
      })
    })
  }

  pause() {
    const station = this.stationMenuToggle.value
    console.debug('pausing station', station)

    return new Promise(resolve => {
      chrome.runtime.sendMessage({ action: 'pause', station }, () => {
        this.stationIsPaused()
        this.stationMenuToggle.focus()
        resolve()
      })
    })
  }

  updateStationImage(station) {
    if (station && station.length > 0) {
      this.stationImg.src = `station-images/${station}.png`
      this.stationImg.classList.remove('d-none')
    } else {
      this.stationImg.classList.add('d-none')
    }
  }

  stationChanged(stationID, stationName) {
    if (stationID === '') {
      chrome.runtime.sendMessage({ action: 'clear' }, () => {
        this.playButton.disabled = true
        this.hideTrackInfo()
        this.pause()
      })
      return
    }

    this.stationMenuToggle.value = stationID
    this.stationMenuToggle.textContent = stationName
    this.closeStationMenu()

    chrome.runtime.sendMessage({ action: 'info' }, info => {
      const currentStation = info.station

      if (stationID !== currentStation) {
        console.debug(`station changed to ${stationID}`)
        this.playButton.disabled = false
        this.pause().then(() => this.play())
      }
    })
  }

  handleLinks() {
    const links = Array.from(document.querySelectorAll('a'));
    for (let i = 0; i < links.length; i++) {
      this.handleLink(links[i]);
    }
  }

  handleLink(link) {
    link.addEventListener('click', event => {
      event.preventDefault();
      let url;
      const href = link.href;
      const optionsSuffix = '#options';
      if (href.indexOf(optionsSuffix) === href.length - optionsSuffix.length) {
        url = chrome.extension.getURL('options.html');
      } else {
        url = href;
      }
      chrome.tabs.create({ url });
      return false;
    });
  }

  applyTheme() {
    SomaPlayerUtil.getOptions().then(opts => {
      const theme = opts.theme || 'light'
      document.body.classList.add(`theme-${theme}`)
    })
  }
}

document.addEventListener('DOMContentLoaded', () => {
  new SomaPlayerPopup();
});
<commit_msg>Defocus focused station when filtering<commit_after>class SomaPlayerPopup {
  constructor() {
    this.stationFilter = ''
    this.findElements()
    this.handleLinks()
    this.hookupMenu()
    this.applyTheme()
    this.fetchSomaStations()
    this.listenForPlayback()
    this.displayTip()
  }

  hookupMenu() {
    this.stationMenuToggle.addEventListener('click', () => {
      if (this.stationMenuToggle.classList.contains('disabled')) {
        return
      }

      this.toggleStationMenu()
    })

    window.addEventListener('keydown', event => this.onKeydown(event))

    window.addEventListener('click', event => {
      const dropdown = event.target.closest('.dropdown')

      if (!dropdown) {
        this.closeStationMenu()
      }
    })
  }

  onKeydown(event) {
    switch (event.key) {
      case 'ArrowDown':
        this.focusStationListItem(1)
        this.openStationMenu()
        break
      case 'ArrowUp':
        this.focusStationListItem(-1)
        this.openStationMenu()
        break
      case 'Enter':
        this.handleEnter(event)
        break
      case ' ':
        this.handleSpace(event)
        break
      case 'Backspace':
        this.handleStationFilterTyping(null)
        break
      default:
        const input = String.fromCharCode(event.keyCode)
        if (/[a-zA-Z0-9]/.test(input)) {
          this.handleStationFilterTyping(event.key)
        }
    }
  }

  updateStationFilter(newValue) {
    this.stationFilter = newValue
    this.stationFilterEl.textContent = this.stationFilter
    this.stationFilterEl.classList.toggle('d-none', this.stationFilter === '')
  }

  handleStationFilterTyping(character) {
    if (!this.isStationMenuOpen()) {
      this.openStationMenu()
    }
    if (character) {
      this.updateStationFilter(this.stationFilter + character)
    } else {
      this.updateStationFilter(this.stationFilter.slice(0, this.stationFilter.length - 1))
    }
    this.filterStations()
  }

  handleSpace(event) {
    event.preventDefault()

    chrome.runtime.sendMessage({ action: 'info' }, info => {
      if (info.paused) {
        this.play()
      } else {
        this.pause()
      }
    })
  }

  handleEnter(event) {
    if (!this.isStationMenuOpen()) {
      return
    }
    event.preventDefault()

    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    if (focusedItem) {
      const button = focusedItem.querySelector('.station-button')
      this.updateStationFilter('')
      this.playStationFromButton(button)
    } else {
      this.toggleStationMenu()
    }
  }

  filterStations() {
    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    if (focusedItem) {
      focusedItem.classList.remove('focused')
    }

    const listItems = Array.from(this.stationListEl.querySelectorAll('.station-list-item'))
    const showAllStations = this.stationFilter === ''

    for (const listItem of listItems) {
      const value = listItem.getAttribute('data-station-filter')
      const filterMatchesStation = value.indexOf(this.stationFilter) === 0
      const isVisible = showAllStations || filterMatchesStation
      listItem.classList.toggle('d-none', !isVisible)
    }
  }

  focusStationListItem(offset) {
    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    const listItems = Array.from(this.stationListEl.querySelectorAll('.station-list-item'))
    const firstListItem = listItems[0]
    const lastListItem = listItems[listItems.length - 1]
    let newFocusedItem

    if (focusedItem) {
      const index = listItems.indexOf(focusedItem)
      focusedItem.classList.remove('focused')

      if (offset > 0) {
        newFocusedItem = listItems[index + 1] || firstListItem
      } else {
        newFocusedItem = listItems[index - 1] || lastListItem
      }
    } else if (offset > 0) {
      newFocusedItem = firstListItem
    } else {
      newFocusedItem = lastListItem
    }

    if (newFocusedItem) {
      newFocusedItem.classList.add('focused')
      newFocusedItem.scrollIntoView()
    }
  }

  openStationMenu() {
    const container = this.stationMenuToggle.closest('.dropdown')
    container.classList.add('active')
    this.stationMenuToggle.classList.add('selected')

    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    if (focusedItem) {
      focusedItem.scrollIntoView()
    }
  }

  closeStationMenu() {
    this.updateStationFilter('')
    const container = this.stationMenuToggle.closest('.dropdown')
    container.classList.remove('active')
    this.stationMenuToggle.classList.remove('selected')
  }

  isStationMenuOpen() {
    return this.stationMenuToggle.classList.contains('selected')
  }

  toggleStationMenu() {
    if (this.isStationMenuOpen()) {
      this.closeStationMenu()
    } else {
      this.openStationMenu()
    }
  }

  listenForPlayback() {
    this.playButton.addEventListener('click', () => this.play())
    this.pauseButton.addEventListener('click', () => this.pause())
  }

  displayTip() {
    SomaPlayerUtil.getOptions().then(opts => {
      const tipsSetting = opts.tips || 'on'
      if (tipsSetting !== 'on') {
        return
      }

      const tips = Array.from(this.tipsList.querySelectorAll('.js-tip'))
      const index = Math.floor(Math.random() * tips.length)
      const tip = tips[index]
      tip.classList.remove('d-none')
      this.tipsList.classList.remove('d-none')
    })
  }

  findElements() {
    this.stationMenuToggle = document.getElementById('station-menu-toggle')
    this.stationFilterEl = document.getElementById('station-filter')
    this.stationListEl = document.getElementById('station-list')
    this.stationListItemTpl = document.getElementById('station-list-item-template')
    this.playButton = document.getElementById('play')
    this.pauseButton = document.getElementById('pause')
    this.currentInfoEl = document.getElementById('currently-playing')
    this.trackListEl = document.getElementById('track-list')
    this.trackListItemTpl = document.getElementById('track-list-item-template')
    this.stationImg = document.getElementById('station-image')
    this.tipsList = document.getElementById('tips-list')
  }

  insertStationOptions(stations) {
    const activeStationID = SomaLocalStorage.getCurrentStation()

    for (const data of stations) {
      const listItem = this.createStationListItem(data, data.id === activeStationID)
      this.stationListEl.appendChild(listItem)
    }

    this.stationMenuToggle.classList.remove('disabled')
    setTimeout(() => this.stationMenuToggle.focus(), 100)
    this.loadCurrentInfo()
  }

  loadDefaultStations() {
    console.debug('loading default station list')
    const url = chrome.extension.getURL('defaultStations.json')
    SomaPlayerUtil.getJSON(url).
      then(defaultStations => this.insertStationOptions(defaultStations))
  }

  fetchSomaStations() {
    chrome.runtime.sendMessage({ action: 'get_stations' }, stations => {
      if (!stations || stations.length < 1) {
        const msg = { action: 'refresh_stations' }

        chrome.runtime.sendMessage(msg, (stations, error) => {
          if (error) {
            console.error('failed to fetch stations, using defaults')
            this.loadDefaultStations()
          } else {
            console.debug('fetched stations', stations)
            this.insertStationOptions(stations)
          }
        })
      } else {
        console.debug('stations already stored', stations)
        this.insertStationOptions(stations)
      }
    })
  }

  emptyTrackList() {
    while (this.trackListEl.hasChildNodes()) {
      this.trackListEl.removeChild(this.trackListEl.lastChild);
    }
  }

  listTracks(tracks) {
    setTimeout(() => {
      this.emptyTrackList()

      for (const track of tracks) {
        const listItem = this.createTrackListItem(track)
        this.trackListEl.appendChild(listItem)
      }
    }, 70)
  }

  onStationButtonClick(event) {
    const button = event.currentTarget
    this.playStationFromButton(button)
  }

  playStationFromButton(button) {
    const stationID = button.value
    const stationName = button.querySelector('.station-name').textContent
    const focusedItem = this.stationListEl.querySelector('.station-list-item.focused')
    const listItem = button.closest('.station-list-item')

    if (focusedItem) {
      focusedItem.classList.remove('focused')
    }
    button.blur()
    this.toggleStationMenu()
    this.stationChanged(stationID, stationName)
    listItem.classList.add('focused')
  }

  filterValueForStation(stationTitle) {
    return stationTitle.toLowerCase().replace(/\s+/g, '')
  }

  createStationListItem(data, isActive) {
    const el = this.stationListItemTpl.content.cloneNode(true)
    const button = el.querySelector('.station-button')
    const nameEl = el.querySelector('.station-name')
    const imageEl = el.querySelector('.station-image')
    const listItem = el.querySelector('.station-list-item')

    if (isActive) {
      listItem.classList.add('focused')
    }

    listItem.setAttribute('data-station-filter', this.filterValueForStation(data.title))
    imageEl.src = `station-images/${data.id}.png`
    button.value = data.id
    nameEl.textContent = data.title
    button.addEventListener('click', e => this.onStationButtonClick(e))

    return el
  }

  createTrackListItem(track) {
    const el = this.trackListItemTpl.content.cloneNode(true)
    const nameEl = el.querySelector('.track-name')
    const artistEl = el.querySelector('.artist')
    const timeEl = el.querySelector('.track-date')
    const date = new Date(track.date)

    nameEl.textContent = track.title
    artistEl.textContent = track.artist
    timeEl.textContent = this.prettyDate(date)

    return el
  }

  prettyDate(date) {
    const fullHours = date.getHours()
    const hours = fullHours - 12
    let minutes = date.getMinutes()
    if (minutes < 10) {
      minutes = `0${minutes}`
    }
    let amPM = 'am'
    if (fullHours >= 12) {
      amPM = 'pm'
    }
    return `${hours}:${minutes} ${amPM}`
  }

  hideTrackInfo() {
    this.emptyTrackList()
    this.trackListEl.classList.add('d-none')
  }

  loadCurrentInfo() {
    this.stationMenuToggle.classList.add('disabled')

    chrome.runtime.sendMessage({ action: 'info' }, info => {
      console.debug('finished info request, info', info);
      const currentStationButton = document.querySelector(`.station-button[value="${info.station}"]`)
      if (currentStationButton) {
        this.stationMenuToggle.value = info.station
        this.stationMenuToggle.textContent = currentStationButton.textContent
      }

      if (info.paused) {
        this.stationIsPaused()
      } else {
        this.play()
      }

      this.updateStationImage(info.station)
      this.stationMenuToggle.classList.remove('disabled')
      this.listTracks(info.tracks)
    })
  }

  stationIsPlaying() {
    this.pauseButton.classList.remove('d-none')
    this.playButton.classList.add('d-none')
    this.stationMenuToggle.focus()
  }

  stationIsPaused() {
    this.pauseButton.classList.add('d-none')
    this.playButton.classList.remove('d-none')
    this.playButton.disabled = false
    this.stationMenuToggle.focus()
  }

  play() {
    const station = this.stationMenuToggle.value
    if (!station || station.length < 1) {
      return
    }
    console.debug('play', station)
    this.updateStationImage(station)

    chrome.runtime.sendMessage({ action: 'play', station }, () => {
      this.stationIsPlaying()

      chrome.runtime.sendMessage({ action: 'info' }, info => {
        if (info.tracks && info.tracks.length > 0) {
          this.listTracks(info.tracks)
        } else {
          const api = new SomaAPI()
          api.getStationTracks(station).
            then(tracks => this.listTracks(tracks)).
            catch(error => console.error('failed to get tracks', error))
        }
      })
    })
  }

  pause() {
    const station = this.stationMenuToggle.value
    console.debug('pausing station', station)

    return new Promise(resolve => {
      chrome.runtime.sendMessage({ action: 'pause', station }, () => {
        this.stationIsPaused()
        this.stationMenuToggle.focus()
        resolve()
      })
    })
  }

  updateStationImage(station) {
    if (station && station.length > 0) {
      this.stationImg.src = `station-images/${station}.png`
      this.stationImg.classList.remove('d-none')
    } else {
      this.stationImg.classList.add('d-none')
    }
  }

  stationChanged(stationID, stationName) {
    if (stationID === '') {
      chrome.runtime.sendMessage({ action: 'clear' }, () => {
        this.playButton.disabled = true
        this.hideTrackInfo()
        this.pause()
      })
      return
    }

    this.stationMenuToggle.value = stationID
    this.stationMenuToggle.textContent = stationName
    this.closeStationMenu()

    chrome.runtime.sendMessage({ action: 'info' }, info => {
      const currentStation = info.station

      if (stationID !== currentStation) {
        console.debug(`station changed to ${stationID}`)
        this.playButton.disabled = false
        this.pause().then(() => this.play())
      }
    })
  }

  handleLinks() {
    const links = Array.from(document.querySelectorAll('a'));
    for (let i = 0; i < links.length; i++) {
      this.handleLink(links[i]);
    }
  }

  handleLink(link) {
    link.addEventListener('click', event => {
      event.preventDefault();
      let url;
      const href = link.href;
      const optionsSuffix = '#options';
      if (href.indexOf(optionsSuffix) === href.length - optionsSuffix.length) {
        url = chrome.extension.getURL('options.html');
      } else {
        url = href;
      }
      chrome.tabs.create({ url });
      return false;
    });
  }

  applyTheme() {
    SomaPlayerUtil.getOptions().then(opts => {
      const theme = opts.theme || 'light'
      document.body.classList.add(`theme-${theme}`)
    })
  }
}

document.addEventListener('DOMContentLoaded', () => {
  new SomaPlayerPopup();
});
<|endoftext|>


<commit_before>const nPath = require('path');
const fs = require('fs-promise');
const glob = require('glob-promise');
const babel = require('babel-core');

const babelConfig = {
  plugins: [['transform-es2015-modules-commonjs', { strict: true, loose: false }]]
};

glob('./*.js')
  .then((files) =>
    Promise.all(
      files.map((file) =>
        fs.readFile(file)
          .then((code) =>
            fs.outputFile(
              nPath.resolve(`cjs/${nPath.basename(file)}`),
              babel.transform(code, babelConfig).code
            )
          )
          .then(() => process.stdout.write('.'))
      )
    )
  )
  .then(() => { console.log('\nAll Done'); })
  .catch((err) => {
    console.error(err.messsage);
    console.error(err.stack);
  });
<commit_msg>Taks CJS: Remove cjs folder before build<commit_after>const nPath = require('path');
const fs = require('fs-promise');
const glob = require('glob-promise');
const babel = require('babel-core');

const babelConfig = {
  plugins: [['transform-es2015-modules-commonjs', { strict: true, loose: false }]]
};

fs.remove(nPath.resolve('cjs'))
  .then(() => glob('./*.js'))
  .then((files) =>
    Promise.all(
      files.map((file) =>
        fs.readFile(file)
          .then((code) =>
            fs.outputFile(
              nPath.resolve(`cjs/${nPath.basename(file)}`),
              babel.transform(code, babelConfig).code
            )
          )
          .then(() => process.stdout.write('.'))
      )
    )
  )
  .then(() => { console.log('\nFiles sucessfully converted to CJS'); })
  .catch((err) => {
    console.error(err.messsage);
    console.error(err.stack);
  });
<|endoftext|>


<commit_before>const mongoose = require('mongoose');
const logger = require('../../logger');
const errorHandlers = require('./errors');

class LdapConfigService {
	setup(app) {
		this.app = app;
	}

	async create(config, params) {
		return this.verifyAndSaveLdapConfig(config, this.getOptions(params));
	}

	async patch(id, config, params) {
		return this.verifyAndSaveLdapConfig(config, {
			...this.getOptions(params),
			systemId: id,
		});
	}

	getOptions(params) {
		const { verifyOnly, activate } = params.query;

		const saveSystem = verifyOnly !== true && verifyOnly !== 'true';
		const activateSystem = activate !== false && activate !== 'false';

		return {
			schoolId: params.account.schoolId,
			activateSystem,
			saveSystem,
		};
	}

	async verifyAndSaveLdapConfig(config, options) {
		const verificationResult = await this.verifyConfig(config);
		if (verificationResult.ok && options.saveSystem) {
			await this.saveConfig(config, options.schoolId, options.systemId, options.activateSystem);
		}
		return verificationResult;
	}

	/**
	 * Verifies a given config by connecting to the server and retrieving sample
	 * objects. Errors are caught and categorized.
	 * @param {Object} config LDAP config object
	 * @returns {Object} verification result object
	 * `{ ok: Boolean, errors: [], users: {}, classes: {} }`
	 */
	async verifyConfig(config) {
		const ldap = this.app.service('ldap');
		const result = {
			ok: false,
			errors: [],
			users: {},
			classes: {},
		};
		try {
			const users = await ldap.getUsers(config);
			result.users = LdapConfigService.generateUserStats(users);
			if (LdapConfigService.shouldVerifyClasses(config)) {
				const classes = await ldap.getClasses(config);
				result.classes = LdapConfigService.generateClassStats(classes);
			}
			result.ok = true;
		} catch (error) {
			for (const { match, message, code } of errorHandlers) {
				if (match(error)) {
					result.errors.push({ message, code });
				}
			}
			if (result.errors.length === 0) {
				logger.warning('Uncaught error during LDAP config verification', { error });
			}
		} finally {
			ldap.disconnect(config);
		}
		return result;
	}

	/**
	 * Saves (and optionally activates) an LDAP config for a given school.
	 * If no systemId is given, a new system will be created and assigned.
	 * @param {Object} config LDAP config object
	 * @param {ObjectId} schoolId id of the school
	 * @param {Object} [systemId] optional id of the system to update
	 * @param {Boolean} [activate=true] optional value for `ldapConfig.active`
	 */
	async saveConfig(config, schoolId, systemId = undefined, activate = true) {
		const systemService = await this.app.service('systems');
		const schoolsService = await this.app.service('schools');

		const school = await schoolsService.get(schoolId);
		const system = LdapConfigService.constructSystem(config, school, activate);

		const session = await mongoose.startSession();
		await session.withTransaction(async () => {
			if (systemId) {
				// update existing system (already assigned to school)
				await Promise.all([
					systemService.patch(systemId, system),
					schoolsService.patch(schoolId, {
						ldapSchoolIdentifier: config.rootPath,
					}),
				]);
			} else {
				// create a new system and assign it to the school
				const { _id: newSystemId } = await systemService.create(system);
				await schoolsService.patch(schoolId, {
					ldapSchoolIdentifier: config.rootPath,
					$addToSet: {
						systems: newSystemId,
					},
				});
			}
		});
		await session.endSession();
	}

	/**
	 * Returns a system collection object based on an abbreviated LDAP config
	 * object and a school object. The school's name will be used for both system
	 * `alias` and `ldapConfig.providerOptions.schoolName`. Provider is always
	 * `general`. Type is always `ldap`.
	 * @static
	 * @param {Object} config LDAP config object
	 * @param {School} school the school at which to activate the system
	 * @param {boolean} [activate=true] optional value for `ldapConfig.active`
	 * @returns {System} system object to be used for creating or updating
	 */
	static constructSystem(config, school, activate = true) {
		return {
			type: 'ldap',
			alias: school.name,
			ldapConfig: {
				...config,
				provider: 'general',
				providerOptions: {
					...config.providerOptions,
					schoolName: school.name,
				},
				active: activate,
			},
		};
	}

	/**
	 * Generates statistics about LDAP verification result for users.
	 * @static
	 * @param {Array} [users=[]] array of users returned from LDAP provider strategy #getUsers
	 * @returns {Object} statistics
	 * `{ total: Number, admin: Number, teacher: Number, student: Number, sample: {} }`
	 */
	static generateUserStats(users = []) {
		const result = {
			total: users.length,
			admin: 0,
			teacher: 0,
			student: 0,
			sample: users.length > 0 ? users[0] : {},
		};
		for (const user of users) {
			if (user.roles.includes('administrator')) result.admin += 1;
			if (user.roles.includes('teacher')) result.teacher += 1;
			if (user.roles.includes('student')) result.student += 1;
		}
		return result;
	}

	/**
	 * Generates statistics about LDAP verification result for classes.
	 * @static
	 * @param {Array} [classes=[]] array of classes returned from LDAP provider strategy #getClasses
	 * @returns {Object} statistics
	 * `{ total: Number, sample: {} }`
	 */
	static generateClassStats(classes = []) {
		return {
			total: classes.length,
			sample: classes.length > 0 ? classes[0] : {},
		};
	}

	/**
	 * Determines whether a given config needs to sync classes.
	 * @static
	 * @param {Object} config LDAP config object
	 * @returns {Boolean} true/false
	 */
	static shouldVerifyClasses(config) {
		return (
			config.providerOptions &&
			config.providerOptions.classPathAdditions &&
			config.providerOptions.classPathAdditions !== ''
			// return false if classPathAdditions are undefined or empty
		);
	}
}

module.exports = LdapConfigService;
<commit_msg>Add inline-docs<commit_after>const mongoose = require('mongoose');
const logger = require('../../logger');
const errorHandlers = require('./errors');

class LdapConfigService {
	setup(app) {
		this.app = app;
	}

	async create(config, params) {
		return this.verifyAndSaveLdapConfig(config, this.getOptions(params));
	}

	async patch(id, config, params) {
		return this.verifyAndSaveLdapConfig(config, {
			...this.getOptions(params),
			systemId: id,
		});
	}

	/**
	 * Retrieves and returns options object to be used for #verifyAndSaveLdapConfig
	 * @param {Object} params feathers request params
	 * @returns {Object} options `{ schoolId: ObjectId, activateSystem: Boolean, saveSystem: Boolean}`
	 */
	getOptions(params) {
		const { verifyOnly, activate } = params.query;

		const saveSystem = verifyOnly !== true && verifyOnly !== 'true';
		const activateSystem = activate !== false && activate !== 'false';

		return {
			schoolId: params.account.schoolId,
			activateSystem,
			saveSystem,
		};
	}

	/**
	 * Actual business logic to validate and save LDAP configs
	 * @param {Object} config config data
	 * @param {Object} options options object
	 * @returns {Object} verification result
	 */
	async verifyAndSaveLdapConfig(config, options) {
		const verificationResult = await this.verifyConfig(config);
		if (verificationResult.ok && options.saveSystem) {
			await this.saveConfig(config, options.schoolId, options.systemId, options.activateSystem);
		}
		return verificationResult;
	}

	/**
	 * Verifies a given config by connecting to the server and retrieving sample
	 * objects. Errors are caught and categorized.
	 * @param {Object} config LDAP config object
	 * @returns {Object} verification result object
	 * `{ ok: Boolean, errors: [], users: {}, classes: {} }`
	 */
	async verifyConfig(config) {
		const ldap = this.app.service('ldap');
		const result = {
			ok: false,
			errors: [],
			users: {},
			classes: {},
		};
		try {
			const users = await ldap.getUsers(config);
			result.users = LdapConfigService.generateUserStats(users);
			if (LdapConfigService.shouldVerifyClasses(config)) {
				const classes = await ldap.getClasses(config);
				result.classes = LdapConfigService.generateClassStats(classes);
			}
			result.ok = true;
		} catch (error) {
			for (const { match, message, code } of errorHandlers) {
				if (match(error)) {
					result.errors.push({ message, code });
				}
			}
			if (result.errors.length === 0) {
				logger.warning('Uncaught error during LDAP config verification', { error });
			}
		} finally {
			ldap.disconnect(config);
		}
		return result;
	}

	/**
	 * Saves (and optionally activates) an LDAP config for a given school.
	 * If no systemId is given, a new system will be created and assigned.
	 * @param {Object} config LDAP config object
	 * @param {ObjectId} schoolId id of the school
	 * @param {Object} [systemId] optional id of the system to update
	 * @param {Boolean} [activate=true] optional value for `ldapConfig.active`
	 */
	async saveConfig(config, schoolId, systemId = undefined, activate = true) {
		const systemService = await this.app.service('systems');
		const schoolsService = await this.app.service('schools');

		const school = await schoolsService.get(schoolId);
		const system = LdapConfigService.constructSystem(config, school, activate);

		const session = await mongoose.startSession();
		await session.withTransaction(async () => {
			if (systemId) {
				// update existing system (already assigned to school)
				await Promise.all([
					systemService.patch(systemId, system),
					schoolsService.patch(schoolId, {
						ldapSchoolIdentifier: config.rootPath,
					}),
				]);
			} else {
				// create a new system and assign it to the school
				const { _id: newSystemId } = await systemService.create(system);
				await schoolsService.patch(schoolId, {
					ldapSchoolIdentifier: config.rootPath,
					$addToSet: {
						systems: newSystemId,
					},
				});
			}
		});
		await session.endSession();
	}

	/**
	 * Returns a system collection object based on an abbreviated LDAP config
	 * object and a school object. The school's name will be used for both system
	 * `alias` and `ldapConfig.providerOptions.schoolName`. Provider is always
	 * `general`. Type is always `ldap`.
	 * @static
	 * @param {Object} config LDAP config object
	 * @param {School} school the school at which to activate the system
	 * @param {boolean} [activate=true] optional value for `ldapConfig.active`
	 * @returns {System} system object to be used for creating or updating
	 */
	static constructSystem(config, school, activate = true) {
		return {
			type: 'ldap',
			alias: school.name,
			ldapConfig: {
				...config,
				provider: 'general',
				providerOptions: {
					...config.providerOptions,
					schoolName: school.name,
				},
				active: activate,
			},
		};
	}

	/**
	 * Generates statistics about LDAP verification result for users.
	 * @static
	 * @param {Array} [users=[]] array of users returned from LDAP provider strategy #getUsers
	 * @returns {Object} statistics
	 * `{ total: Number, admin: Number, teacher: Number, student: Number, sample: {} }`
	 */
	static generateUserStats(users = []) {
		const result = {
			total: users.length,
			admin: 0,
			teacher: 0,
			student: 0,
			sample: users.length > 0 ? users[0] : {},
		};
		for (const user of users) {
			if (user.roles.includes('administrator')) result.admin += 1;
			if (user.roles.includes('teacher')) result.teacher += 1;
			if (user.roles.includes('student')) result.student += 1;
		}
		return result;
	}

	/**
	 * Generates statistics about LDAP verification result for classes.
	 * @static
	 * @param {Array} [classes=[]] array of classes returned from LDAP provider strategy #getClasses
	 * @returns {Object} statistics
	 * `{ total: Number, sample: {} }`
	 */
	static generateClassStats(classes = []) {
		return {
			total: classes.length,
			sample: classes.length > 0 ? classes[0] : {},
		};
	}

	/**
	 * Determines whether a given config needs to sync classes.
	 * @static
	 * @param {Object} config LDAP config object
	 * @returns {Boolean} true/false
	 */
	static shouldVerifyClasses(config) {
		return (
			config.providerOptions &&
			config.providerOptions.classPathAdditions &&
			config.providerOptions.classPathAdditions !== ''
			// return false if classPathAdditions are undefined or empty
		);
	}
}

module.exports = LdapConfigService;
<|endoftext|>


<commit_before>var request = require('request');
var fs = require('fs');

// you need to pass all 4 of these variables via ENV vars or in a private.json file in the
// root of the repo or this whole.  .gitignore is setup to ignore private.json
try {
  var private = require('../private.json');
  const CHROME_EXT_ITEM_ID =  process.env.CHROME_EXT_ITEM_ID || private.CHROME_EXT_ITEM_ID;
  const CLIENT_ID = process.env.CLIENT_ID || private.CLIENT_ID;
  const CLIENT_SECRET = process.env.CLIENT_SECRET || private.CLIENT_SECRET;
  const CODE = process.env.CODE || private.CODE;
} catch(ex) {
  console.log("missing environment variables");
  console.error(ex);
  process.exit(1);
}

/*
  Note:
  Currently, there is no API for setting an item’s metadata, such as description. This has to be done manually in the Chrome Web Store Developer Dashboard.
 */

function getChromeOauthToken(cb){
  var options = {
    url: 'https://accounts.google.com/o/oauth2/token',
    formData: {
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code: CODE,
      grant_type: 'authorization_code',
      redirect_uri: 'urn:ietf:wg:oauth:2.0:oob'
    }
  };

  request.post(options,
    function(err,httpResponse,body){
      if (err) {
        console.log(err);
        process.exit(1);
      }
      cb(body);
    }
  );

}

// Uploading a package to update an existing store item
function updateExtension(TOKEN, cb) {

  var options = {
    url: `https://www.googleapis.com/upload/chromewebstore/v1.1/items/${CHROME_EXT_ITEM_ID}`,
    headers: {
      'x-goog-api-version': '2',
      'Authorization': 'Bearer ${TOKEN}'
    }
  };

  fs.createReadStream(process.cwd() + '/extensions/Chrome.zip')
    .pipe(request.put(options,
      function(err, httpResponse, body) {
        if (err) {
          console.error('upload failed:', err);
          process.exit(1);
        }
        cb(body);
      }
    ));
}

module.exports = function(){

  // step 1 zip the folder
  // step 2 get access token
  // step 3 send update to the api
  getChromeOauthToken(function(result){
    console.log(result);
  });
}; <commit_msg>adding a zip function<commit_after>var request = require('request');
var fs = require('fs');

// you need to pass all 4 of these variables via ENV vars or in a private.json file in the
// root of the repo or this whole.  .gitignore is setup to ignore private.json
try {
  var private = require('../private.json');
  const CHROME_EXT_ITEM_ID =  process.env.CHROME_EXT_ITEM_ID || private.CHROME_EXT_ITEM_ID;
  const CLIENT_ID = process.env.CLIENT_ID || private.CLIENT_ID;
  const CLIENT_SECRET = process.env.CLIENT_SECRET || private.CLIENT_SECRET;
  const CODE = process.env.CODE || private.CODE;
} catch(ex) {
  console.log("missing environment variables");
  console.error(ex);
  process.exit(1);
}

/*
  Note:
  Currently, there is no API for setting an item’s metadata, such as description. This has to be done manually in the Chrome Web Store Developer Dashboard.
 */

function getChromeOauthToken(cb){
  var options = {
    url: 'https://accounts.google.com/o/oauth2/token',
    formData: {
      client_id: CLIENT_ID,
      client_secret: CLIENT_SECRET,
      code: CODE,
      grant_type: 'authorization_code',
      redirect_uri: 'urn:ietf:wg:oauth:2.0:oob'
    }
  };

  request.post(options,
    function(err,httpResponse,body){
      if (err) {
        console.log(err);
        process.exit(1);
      }
      cb(body);
    }
  );

}

// Uploading a package to update an existing store item
function updateExtension(TOKEN, cb) {

  var options = {
    url: `https://www.googleapis.com/upload/chromewebstore/v1.1/items/${CHROME_EXT_ITEM_ID}`,
    headers: {
      'x-goog-api-version': '2',
      'Authorization': 'Bearer ${TOKEN}'
    }
  };

  fs.createReadStream(process.cwd() + '/extensions/Chrome.zip')
    .pipe(request.put(options,
      function(err, httpResponse, body) {
        if (err) {
          console.error('upload failed:', err);
          process.exit(1);
        }
        cb(body);
      }
    ));
}

/**
 * Zips up the folders inside /slides
 * Requires the command line "zip" program.
 * Mac OSX should already come with that
 */
function doZip(filename, dir){
  var exec = require('child_process').exec;
  var zip = exec(`zip -vr ${name} ${name} -x "*.DS_Store"`,
    {cwd: dir},
    function(error, stdout, stderr){
      //console.log('stdout: ' + stdout);
      if (stderr !== "") {
        console.log('stderr: ' + stderr);
      }
      if (error !== null) {
        console.log('exec error: ' + error);
      }
  });
}

module.exports = function(){

  // step 1 zip the folder
  // step 2 get access token
  // step 3 send update to the api
  getChromeOauthToken(function(result){
    console.log(result.access_token, function(result){
      console.log(result);
    });
  });
}; <|endoftext|>


<commit_before>/*-----------------------------------------------------------------

SudojQuery
https://github.com/esr360/sudojQuery
http://writing.colin-gourlay.com/safely-using-ready-before-including-jquery/

-----------------------------------------------------------------*/

(function (w, d, u) {

    // Define two queues for handlers
    w.readyQ = [];
    w.bindReadyQ = [];

    // Push a handler into the correct queue
    function pushToQ(x, y) {
        if (x == "ready") {
            w.bindReadyQ.push(y);
        } else {
            w.readyQ.push(x);
        }
    }

    // Define an alias object (for use later)
    var alias = {
        ready: pushToQ,
        bind: pushToQ
    }

    // Define the fake jQuery function to capture handlers
    w.$ = w.jQuery = function (handler) {
        if (handler === d || handler === u) {
            // Queue $(document).ready(handler), $().ready(handler)
            // and $(document).bind("ready", handler) by returning
            // an object with alias methods for pushToQ
            return alias;
        } else {
            // Queue $(handler)
            pushToQ(handler);
        }
    }

})(window, document);
<commit_msg>Update pseudojQuery-start.js<commit_after>/*-----------------------------------------------------------------

PseudojQuery
https://github.com/esr360/pseudojQuery
http://writing.colin-gourlay.com/safely-using-ready-before-including-jquery/

-----------------------------------------------------------------*/

(function (w, d, u) {

    // Define two queues for handlers
    w.readyQ = [];
    w.bindReadyQ = [];

    // Push a handler into the correct queue
    function pushToQ(x, y) {
        if (x == "ready") {
            w.bindReadyQ.push(y);
        } else {
            w.readyQ.push(x);
        }
    }

    // Define an alias object (for use later)
    var alias = {
        ready: pushToQ,
        bind: pushToQ
    }

    // Define the fake jQuery function to capture handlers
    w.$ = w.jQuery = function (handler) {
        if (handler === d || handler === u) {
            // Queue $(document).ready(handler), $().ready(handler)
            // and $(document).bind("ready", handler) by returning
            // an object with alias methods for pushToQ
            return alias;
        } else {
            // Queue $(handler)
            pushToQ(handler);
        }
    }

})(window, document);
<|endoftext|>


<commit_before>'use strict'

const tap = require('tap')
const isFara = require('../index')
var options = {
  skoleid: 3825,
  postnummer: '',
  gatenavn: 'Djevelgaten',
  husnummer: '666'
}

options.postnummer = 3960
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3960 Stathelle')

options.postnummer = 3803
options.gatenavn = 'Rokkemakervegen'
options.husnummer = '81'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803')

// Hovedregel for 3801, 3803, 3810 og 3830 er SANN FARA - Samme på Specials
<commit_msg>More tests for specials<commit_after>'use strict'

const tap = require('tap')
const isFara = require('../index')
var options = {
  skoleid: 3825,
  postnummer: '',
  gatenavn: 'Djevelgaten',
  husnummer: '666'
}

options.postnummer = 3960
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3960 Stathelle')

options.postnummer = 3803
options.gatenavn = 'Rokkemakervegen'
options.husnummer = '70'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Rokkemakervegen')

options.gatenavn = 'Tjønnåsvegen'
options.husnummer = '60'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Tjønnåsvegen')

options.gatenavn = 'Myhrevegen'
options.husnummer = '151'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Myhrevegen')

options.gatenavn = 'Roevegen'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Roevegen')

options.gatenavn = 'Nyvollvegen'
options.husnummer = '12'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Nyvollvegen')

options.gatenavn = 'Lundevegen'
options.husnummer = '126'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Lundevegen')

options.gatenavn = 'Uvdalvegen'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Uvdalvegen')

options.gatenavn = 'Dagstjønnvegen'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Dagstjønnvegen')

options.gatenavn = 'Breskehaugbakken'
options.husnummer = '60'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Breskehaugbakken')

options.gatenavn = 'Tjønnåsen'
options.husnummer = '6'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Tjønnåsen')

options.gatenavn = 'Stadskleivvegen'
options.husnummer = '15'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Stadskleivvegen')

options.gatenavn = 'Skyttarvegen'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Skyttarvegen')

options.gatenavn = 'Kyrkjevegen'
options.husnummer = '169'
tap.equal(isFara(options), true, 'Nome vgs, avdeling Lunde returns true from 3803 Kyrkjevegen')

// Hovedregel for 3801, 3803, 3810 og 3830 er SANN FARA - Samme på Specials
<|endoftext|>


<commit_before>const watcher = require('./index.js');
const StateMachine = require('./state-machine.js')
const State = require('./state.js')
const sounder = watcher.getActual("Sounder")
const bell = watcher.getActual("Bell")

const ARMING_BEEP = 50
const ARMING_PERIOD = 30000
const WARNING_PERIOD = 60000
const LAST_WARNING_PERIOD = 15000

onQuiet = function(){
	console.log("->Quiet",new Date())
	sounder.stop(true);
	bell.stop(true);
}

onArming = function(){
	console.log("->Arming",new Date())
	sounder.short(ARMING_BEEP,"arming")
	this.armingTimeout = setTimeout(()=>{
		this.emit("timeup")
	},ARMING_PERIOD)
}

exArming = function(){
	console.log("Arming->",new Date())
	this.armingTimeout && clearTimeout(this.armingTimeout)
}

onGuarding = function(){
	console.log("->Guarding",new Date())
}

onWarning = function(){
	console.log("->Warning",new Date())
	sounder.startWarning();
	this.lastWarningTimeout = setTimeout(()=>{
		sounder.lastWarning();
	},WARNING_PERIOD-LAST_WARNING_PERIOD)
	this.warningTimeout = setTimeout(()=>{
		this.emit("timeup")
	},WARNING_PERIOD)
}

exWarning = function(){
	console.log("Warning->",new Date())
	this.lastWarningTimeout && clearTimeout(this.lastWarningTimeout)
	this.warningTimeout && clearTimeout(this.warningTimeout)
	sounder.stopWarning();
}

onSounding = function(){
	console.log("->Sounding",new Date())
	//bell.start()
	//sounder.start()
}

exSounding = function(){
	console.log("Sounding->",new Date())
	sounder.stop()
	bell.stop()
}

const stateMachine = new StateMachine();

const quiet = new State('quiet')
const arming = new State('arming')
const guarding = new State('guarding')
const warning = new State('warning')
const sounding = new State('sounding')

quiet.addTransition('armed',arming)
quiet.onEntry = onQuiet

arming.addTransition('disarmed',quiet)
arming.addTransition('timeup',guarding)
arming.onEntry = onArming
arming.onExit = exArming

guarding.addTransition('disarmed',quiet)
guarding.addTransition('intruder',warning)
guarding.onEntry = onGuarding

warning.addTransition('disarmed',quiet)
warning.addTransition('timeup',sounding)
warning.onEntry = onWarning
warning.onExit = exWarning

sounding.addTransition('disarmed',quiet)
sounding.onEntry = onSounding
sounding.onExit = exSounding

stateMachine.setInitial(quiet)

//register for the right events
//External
watcher.on('armed',stateMachine.getHandler('armed'))
watcher.on('disarmed',stateMachine.getHandler('disarmed'))
watcher.on('intruder',stateMachine.getHandler('intruder'))

//Internal
warning.on('timeup',stateMachine.getHandler('timeup'))
arming.on('timeup',stateMachine.getHandler('timeup'))

module.exports = stateMachine
<commit_msg>whitespace<commit_after>const watcher = require('./index.js');
const StateMachine = require('./state-machine.js')
const State = require('./state.js')
const sounder = watcher.getActual("Sounder")
const bell = watcher.getActual("Bell")

const ARMING_BEEP = 50
const ARMING_PERIOD = 30000
const WARNING_PERIOD = 60000
const LAST_WARNING_PERIOD = 15000

onQuiet = function(){
	console.log("->Quiet",new Date())
	sounder.stop(true);
	bell.stop(true);
}

onArming = function(){
	console.log("->Arming",new Date())
	sounder.short(ARMING_BEEP,"arming")
	this.armingTimeout = setTimeout(()=>{
		this.emit("timeup")
	},ARMING_PERIOD)
}

exArming = function(){
	console.log("Arming->",new Date())
	this.armingTimeout && clearTimeout(this.armingTimeout)
}

onGuarding = function(){
	console.log("->Guarding",new Date())
}

onWarning = function(){
	console.log("->Warning",new Date())
	sounder.startWarning();
	this.lastWarningTimeout = setTimeout(()=>{
		sounder.lastWarning();
	},WARNING_PERIOD-LAST_WARNING_PERIOD)
	this.warningTimeout = setTimeout(()=>{
		this.emit("timeup")
	},WARNING_PERIOD)
}

exWarning = function(){
	console.log("Warning->",new Date())
	this.lastWarningTimeout && clearTimeout(this.lastWarningTimeout)
	this.warningTimeout && clearTimeout(this.warningTimeout)
	sounder.stopWarning();
}

onSounding = function(){
	console.log("->Sounding",new Date())
	//bell.start()
	//sounder.start()
}

exSounding = function(){
	console.log("Sounding->",new Date())
	sounder.stop()
	bell.stop()
}

const stateMachine = new StateMachine();

const quiet = new State('quiet')
const arming = new State('arming')
const guarding = new State('guarding')
const warning = new State('warning')
const sounding = new State('sounding')

quiet.addTransition('armed',arming)
quiet.onEntry = onQuiet

arming.addTransition('disarmed',quiet)
arming.addTransition('timeup',guarding)
arming.onEntry = onArming
arming.onExit = exArming

guarding.addTransition('disarmed',quiet)
guarding.addTransition('intruder',warning)
guarding.onEntry = onGuarding

warning.addTransition('disarmed',quiet)
warning.addTransition('timeup',sounding)
warning.onEntry = onWarning
warning.onExit = exWarning

sounding.addTransition('disarmed',quiet)
sounding.onEntry = onSounding
sounding.onExit = exSounding

stateMachine.setInitial(quiet)

//register for the right events
//External
watcher.on('armed',stateMachine.getHandler('armed'))
watcher.on('disarmed',stateMachine.getHandler('disarmed'))
watcher.on('intruder',stateMachine.getHandler('intruder'))

//Internal
warning.on('timeup',stateMachine.getHandler('timeup'))
arming.on('timeup',stateMachine.getHandler('timeup'))

module.exports = stateMachine
<|endoftext|>


<commit_before>/*!
* Copyright (c) 2015 Carrot Creative
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

(function (W, D) {

  function __() {

    var RegexpValue = /[\-+]?[\d]*\.?[\d]+/g
      , ParseWildcard = '{?}'
      , ParseWildcardRegexp = /\{\?\}/g;

    function ParseValue (value) {
      var nums = [];
      value = new String(value).replace(RegexpValue, function (num) {
        nums.push(parseFloat(num));
        return ParseWildcard;
      });
      return [value, nums]
    }

    function CompileValues (fromValue, toValue, pct) {
      var i = 0
        , fromNum
        , toNum;
      return fromValue[0].replace(ParseWildcardRegexp, function () {
        from = fromValue[1][i];
        to = toValue[1][i];
        i++;
        return (from + ((to - from) * pct));
      });
    }

    var Tweener = {};

    Tweener.default = function (fromValue, toValue) {
      this.fromValue = ParseValue(fromValue);
      this.toValue = ParseValue(toValue);
    }

    Tweener.default.prototype = {
      tween: function (pct) {
        var fromValue = this.fromValue
          , toValue = this.toValue;
        return CompileValues(fromValue, toValue, pct);
      }
    }

    var Tween = function (property, fromPoint, toPoint) {
      if (!toPoint) toPoint = fromPoint;
      this.tweener = new (Tweener[property] || Tweener.default)(fromPoint.value, toPoint.value);
      this.fromY = fromPoint.Y;
      this.toY = toPoint.Y;
    }

    Tween.prototype = {
      current: function (Y) {
        var pct = 0
          , fromY = this.fromY
          , toY = this.toY;
        if (Y >= fromY && Y <= toY) {
          pct = (Y - fromY) / (toY - fromY);
        } else if (Y > toY) {
          pct = 1;
        }
        return this.tweener.tween(pct);
      }
    }

    var Point = function (Y, value) {
      this.Y = Y;
      this.value = value;
    }

    var Points = function (object) {
      var keys = [], _points = [];
      for (var key in object) keys.push(parseInt(key));
      keys.sort(function(a, b){return a-b;});
      for (var i=0; i<keys.length; i++) _points.push(new Point(keys[i], object[keys[i]]));
      return _points;
    }

    var Component = function ($el, Klass, property, points) {
      this.$el = $el;
      this.property = property;
      this.points = Points(points);
      this.child = new Klass(this);
    }

    Component.prototype = {
      set: function (Y) {
        var $el = this.$el;
        if ($el.nodeName) {
          $el = [$el];
        } else if ('string' === typeof $el) {
          $el = D.querySelectorAll($el);
        }
        for (var i=0; i<$el.length; i++) {
          $el[i].style[this.property] = this.child.current(Y);
        }
      }
    }

    var Animation = function (component) {
      var tweens = []
        , points = component.points
        , current, next;
      for (var i=0; i<points.length; i++) {
        current = points[i];
        next = points[i+1];
        tweens.push(new Tween(property, current, next));
      }
      this.tweens = tweens;
    }

    Animation.prototype = {
      current: function (Y) {
        var tweens = this.tweens
          , len = tweens.length
          , tween
          , current;
        for (var i=0; i<len; i++) {
          tween = tweens[i];
          if ( (Y >= tween.fromY && Y <= tween.toY)
            || (Y <= tween.fromY && i == 0)
            || (Y >= tween.toY && i == len - 1) ) {
            break;
          }
        }
        return tween.current(Y);
      }
    }

    var Style = function (component) {
      this.points = component.points;
    }

    Style.prototype = {
      current: function (Y) {
        var points = this.points
          , current = new String();
        for (var i=0; i<points.length; i++) {
          point = points[i];
          if (Y >= point.Y) current = point.value;
        }
        return current;
      }
    }

    var Roll = function (options) {
      this.options = options || {};
      this.components = [];
      this.max = 0;
    }

    Roll.prototype = {
      animate: function ($el, property, points) {
        return CreateComponent(this, Animation, arguments);
      },
      style: function ($el, property, points) {
        return CreateComponent(this, Style, arguments);
      },
      bind: function () {
        var scrollFn = OnScrollFunction(this)
          , resizeFn = OnResizeFunction(this);
        W.addEventListener('scroll', scrollFn);
        W.addEventListener('resize', resizeFn);
        scrollFn();
        resizeFn();
      }
    }

    function CreateComponent (R, fn, args) {
      var $el = args[0]
        , properties, component, points, last;
      if (args.length == 2) {
        properties = args[1];
      } else {
        (properties = {})[args[1]] = args[2];
      }
      for (property in properties) {
        component = new Component($el, fn, property, properties[property]);
        R.components.push(component);
        points = component.points;
        last = points[points.length - 1];
        if (last.Y > R.max) R.max = last.Y;
      }
      return R;
    }

    function OnScrollFunction (R) {
      return function () {
        var Y = W.pageYOffset
          , component;
        for (var i=0; i<R.components.length; i++) {
          component = R.components[i];
          component.set(Y);
        }
      }
    }

    function OnResizeFunction (R) {
      return function () {
        D.body.style.minHeight = (R.max + W.innerHeight) + 'px';
      }
    }

    return Roll;

  }

  if ('function' === typeof define && define.amd) {
    define(__);
  } else {
    W.Roll = __();
  };

}(window, document));
<commit_msg>Implement refactor. - Roll.Scene   - Create modular scenes by using "new Roll.Scene()." - Roll is now a controller.   - Add scenes and set points to play scenes with .at method. - New structure.   - Collections   - Storyboards   - Elements<commit_after>/*!
* Copyright (c) 2015 Carrot Creative
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

(function (windowObject, documentObject) {

  function __() {

    var RegexpValue = /[\-+]?[\d]*\.?[\d]+/g
      , ParseWildcard = '{?}'
      , ParseWildcardRegexp = /\{\?\}/g;

    var Animation = function (component) {
      var tweens = []
        , points = component.points
        , current, next;
      for (var i=0; i<points.length; i++) {
        current = points[i];
        next = points[i+1];
        tweens.push(new Tween(component.property, current, next));
      }
      this.tweens = tweens;
    }

    Animation.prototype = {
      current: function (Y) {
        var tweens = this.tweens
          , len = tweens.length
          , tween
          , current;
        for (var i=0; i<len; i++) {
          tween = tweens[i];
          if (Y >= tween.fromY || (Y <= tween.fromY && i == 0)){
            current = tween.current(Y);
          }
        }
        return current;
      }
    }

    var Tweener = {};

    Tweener.default = function (fromValue, toValue) {
      this.fromValue = ParseValue(fromValue);
      this.toValue = ParseValue(toValue);
    }

    Tweener.default.prototype = {
      tween: function (pct) {
        var fromValue = this.fromValue
          , toValue = this.toValue;
        return CompileValues(fromValue, toValue, pct);
      }
    }

    var Tween = function (property, fromPoint, toPoint) {
      if (!toPoint) toPoint = fromPoint;
      this.tweener = new (Tweener[property] || Tweener.default)(fromPoint.value, toPoint.value);
      this.fromY = fromPoint.Y;
      this.toY = toPoint.Y;
    }

    Tween.prototype = {
      current: function (Y) {
        var pct = 0
          , fromY = this.fromY
          , toY = this.toY;
        if (Y >= fromY && Y <= toY) {
          pct = (Y - fromY) / (toY - fromY);
        } else if (Y > toY) {
          pct = 1;
        }
        return this.tweener.tween(pct);
      }
    }

    function ParseValue (value) {
      var nums = [];
      value = new String(value).replace(RegexpValue, function (num) {
        nums.push(parseFloat(num));
        return ParseWildcard;
      });
      return [value, nums]
    }

    function CompileValues (fromValue, toValue, pct) {
      var i = 0
        , fromNum
        , toNum;
      return fromValue[0].replace(ParseWildcardRegexp, function () {
        from = fromValue[1][i];
        to = toValue[1][i];
        i++;
        return (from + ((to - from) * pct));
      });
    }

    var Style = function (component) {
      this.points = component.points;
    }

    Style.prototype = {
      current: function (Y) {
        var points = this.points
          , current = new String();
        for (var i=0; i<points.length; i++) {
          point = points[i];
          if (Y >= point.Y) current = point.value;
        }
        return current;
      }
    }

    var Point = function (Y, value) {
      this.Y = Y;
      this.value = value;
    }

    var Collection = function (object) {
      object = object || {};
      this.points = [];
      for (var Y in object) {
        this.add(Y, object[Y]);
      }
    }

    Collection.prototype = {
      add: function (Y, value) {
        var points = this.points.slice(0)
          , point = new Point(parseInt(Y), value)
          , isAdded = false;
        for (var i=0; i<this.points.length; i++) {
          if (this.points[i].Y > point.Y) {
            points.splice(i, 0, point);
            isAdded = true;
          }
        }
        if (!isAdded) points.push(point);
        this.points = points;
        return this;
      },
      merge: function (at, collection) {
        var points = collection.points
          , value;
        for (var i=0; i<points.length; i++) {
          action = points[i];
          this.add((at + action.Y), action.value);
        }
        return this;
      }
    }

    var Action = function (Klass, property, points) {
      var points = this.points = (new Collection(points)).points;
      this.max = points[points.length - 1].Y;
      this.property = property;
      this.klass = new Klass(this);
    }

    Action.prototype = {
      current: function (Y) {
        return this.klass.current(Y);
      }
    }

    var Element = function (selector) {
      this.selector = selector;
      this.collection = new Collection();
    }

    Element.prototype = {
      add: function (Y, action) {
        this.collection.add(Y, action);
        return this;
      },
      set: function (wY) {
        var $el = this.selector
          , collection = this.collection
          , points = collection.points
          , point
          , action
          , Y;
        if ($el.nodeName) {
          $el = [$el]
        } else if ('string' === typeof $el) {
          $el = documentObject.querySelectorAll($el);
        }
        for (var x=0; x<points.length; x++) {
          point = points[x];
          action = point.value;
          Y = (wY - point.Y);
          if (Y > 0
            || (Y < 0 && x == 0) ) {
            for (var y=0; y<$el.length; y++) {
              $el[y].style[action.property] = action.current(Y);
            }
          }
        }
        return this;
      }
    }

    var Storyboard = function () {
      this.elements = [];
      this.max = 0;
    }

    Storyboard.prototype = {
      add: function (selector, Y, action) {
        var elements = this.elements
          , element
          , max;
        for (var i=0; i<elements.length; i++) {
          if (elements[i].selector == selector) {
            element = elements[i];
            break;
          }
        }
        if (!element) {
          element = new Element(selector);
          elements.push(element);
        }
        if (action) {
          element.add(Y, action);
          max = Y + action.max;
          if (max > this.max) this.max = max;
        }
        this.elements = elements;
        return this;
      },
      merge: function (at, storyboard) {
        var elements = storyboard.elements
          , element, collection, points;
        for (var x=0; x<elements.length; x++) {
          element = elements[x];
          collection = element.collection;
          points = collection.points;
          for (var y=0; y<points.length; y++) {
            point = points[y];
            this.add(element.selector, (point.Y + parseInt(at)), point.value);
          }
        }
        return this;
      }
    }

    var OnScrollFunction = function (roll) {
      return function () {
        var wY = windowObject.pageYOffset
          , storyboard = roll.storyboard
          , elements = storyboard.elements
          , element;
        for (var i=0; i<elements.length; i++) {
          element = elements[i];
          element.set(wY);
        }
      }
    }

    var OnResizeFunction = function (roll) {
      return function () {
        documentObject.body.style.minHeight = (roll.storyboard.max + windowObject.innerHeight) + 'px';
      }
    }

    var Roll = function () {
      this.storyboard = new Storyboard();
      this.scenes = {};
      this.ats = {};
    }

    Roll.prototype = {
      add: function (name, scene) {
        if ('function' === typeof scene) {
          var fn = scene;
          scene = new Scene();
          fn(scene);
        }
        this.scenes[name] = scene;
        return this;
      },
      at: function (Y, name) {
        var ats = this.ats[Y] || [];
        ats.push(name);
        this.ats[Y] = ats;
        return this;
      },
      bind: function () {
        var scene;
        for (var Y in this.ats) {
          scene = this.scenes[this.ats[Y]];
          this.storyboard.merge(Y, scene.storyboard);
        }
        var OnScrollFn = OnScrollFunction(this)
          , OnResizeFn = OnResizeFunction(this);
        windowObject.addEventListener('scroll', OnScrollFn);
        windowObject.addEventListener('resize', OnResizeFn);
        OnScrollFn();
        OnResizeFn();
      }
    }

    var Scene = Roll.Scene = function () {
      this.storyboard = new Storyboard();
    }

    Scene.prototype = {
      animate: function ($el, property, points) {
        return this.action(Animation, $el, property, points);
      },
      style: function ($el, property, points) {
        return this.action(Style, $el, property, points);
      },
      action: function (Klass, $el, property, points) {
        var properties;
        if ('undefined' === typeof points) {
          properties = property;
        } else {
          (properties = {})[property] = points;
        }
        for (property in properties) {
          action = new Action(Klass, property, properties[property]);
          this.storyboard.add($el, 0, action);
        }
        return this;
      }
    }

    return Roll;

  }

  if ('function' === typeof define && define.amd) {
    define(__);
  } else {
    windowObject.Roll = __();
  };

}(window, document));
<|endoftext|>


<commit_before>#!/usr/bin/env node
var program = require('commander');
var path = require('path');
var fs = require('fs');
var tsnodemon = require('../lib/tsnodemon');

program
  .version('0.1.0')
  .option('-t, --tsc [value]', 'Tsc arguments. Default: --watch)', '--watch')
  .option('-x, --exec [value]', 'Exec command. Default: node ["main" from package.json])')
  .parse(process.argv);

if (typeof program.exec === 'undefined') {
  program.exec = 'node ' + (require(path.resolve(process.cwd(), 'package.json')) || {}).main || 'index.js';
}

var execArr = program.exec.split(' ');
tsnodemon.compileAndStart(program.tsc.split(' '), execArr[0], execArr.slice(1).concat(program.args));
<commit_msg>Improve default exec value<commit_after>#!/usr/bin/env node
var program = require('commander');
var path = require('path');
var fs = require('fs');
var tsnodemon = require('../lib/tsnodemon');

program
  .version('0.1.0')
  .option('-t, --tsc [value]', 'Tsc arguments. Default: --watch)', '--watch')
  .option('-x, --exec [value]', 'Exec command. Default: node ["main" from package.json])')
  .parse(process.argv);

if (typeof program.exec === 'undefined') {
  var pathPackageJson = path.resolve(process.cwd(), 'package.json');
  var packageJson = {};
  var script = 'dist/index.js';

  try {
    packageJson = require(pathPackageJson);
  } catch (e) {
    // No package.json found
  }

  if (packageJson && packageJson.main) {
    script = packageJson.main;
  } else {
    var pathTsconfigJson = path.resolve(process.cwd(), 'tsconfig.json');
    var tsconfigJson = {};

    try {
      tsconfigJson = require(pathTsconfigJson);
    } catch (e) {
      // No tsconfig.json found
    }

    if (tsconfigJson && tsconfigJson.compilerOptions) {
      if (tsconfigJson.compilerOptions.outFile) {
        script = tsconfigJson.compilerOptions.outFile;
      } else if (tsconfigJson.compilerOptions.outDir) {
        script = path.join(tsconfigJson.compilerOptions.outDir, 'index.js');
      }
    }
  }

  program.exec = 'node ' + script;
}

var execArr = program.exec.split(' ');
tsnodemon.compileAndStart(program.tsc.split(' '), execArr[0], execArr.slice(1).concat(program.args));
<|endoftext|>


<commit_before>// initialize websocket
var ws = new WebSocket("ws://192.168.56.101:8080/");

console.log('starting meteorite websockets. . .');

ws.onmessage = function(msg) {
  // parse the json message
  // todo: handle non-json messages
  var json = JSON.parse(msg.data);

  // log the data
  console.log(json);

  // if this is a delete
  if (json.bind_data === 'delete') {
    // remove the item
    $('#' + json.bind_key).remove();
    // halt further processing
    return false;
  }

  // for each meteorite class
  $('.meteorite').each(function() {
    // if the bind keys match
    if ($(this).data('bind-key') === json.bind_key) {
      console.log('bind found');

      // if there is no bind-attr present, this is a collection
      if ($(this).data('bind-attr') === undefined) {
        add(json, $(this));
      // else update the element
      } else {
        update(json, $(this));
      }

    }
  });
}

// add an item
function add(json, $element) {
  // add to the dom
  $element.append(json.bind_data);
  // listen to subscribe events
  subscribe($(json.bind_data).find('.meteorite').data('bind-key'));
}

// update a single attribute
function update(json, $element) {
  // get the bind data
  var bind_data = JSON.parse(json.bind_data);
  // desired attribute
  var bind_attr = $element.data('bind-attr');
  // get the attribute data
  var attr_data = bind_data[bind_attr];

  // update the property
  $element.prop('checked', attr_data);
  // update the text
  $element.text(attr_data);
  // notify event handlers that a change has occurred
  $element.trigger('change');
}

// when the document is ready
$(document).on('page:load ready', function() {
  subscribeAfterWebsocket();
});

// subscribe to all bind keys of meteorite classes
function subscribeAll() {
  // for each meteorite class
  $('.meteorite').each(function() {
    // if there is a bind key
    if ($(this).data('bind-key') !== undefined) {
      // subscribe
      subscribe($(this).data('bind-key'));
    }
  });
}

// wait for websocket to be ready, then subscribe all
function subscribeAfterWebsocket() {
  // set a 5ms timeout
  setTimeout(function() {
    // if the websocket is ready
    if (ws.readyState === 1) {
      // send the subscribe notices
      subscribeAll();
    // try again if not ready
    } else { subscribeAfterWebsocket(); }
  }, 5);
}

// subscribe to events when the websocket is ready
function subscribe(bind_key) {
  // send the subscribe notice
  ws.send(JSON.stringify({ action: 'subscribe', key: bind_key }));
}
<commit_msg>add Meteorite namespace<commit_after>// meteorite namespace
var Meteorite = Meteorite || {};

// initialize websocket
Meteorite.websocket = new WebSocket("ws://192.168.56.101:8080/");

console.log('starting meteorite websockets. . .');

// when a new websocket message is received
Meteorite.websocket.onmessage = function(msg) {
  // parse the json message
  // todo: handle non-json messages
  var json = JSON.parse(msg.data);

  // log the data
  console.log(json);

  // if this is a delete
  if (json.bind_data === 'delete') {
    // remove the item
    $('#' + json.bind_key).remove();
    // halt further processing
    return false;
  }

  // for each meteorite class
  $('.meteorite').each(function() {
    // if the bind keys match
    if ($(this).data('bind-key') === json.bind_key) {
      console.log('bind found');

      // if there is no bind-attr present, this is a collection
      if ($(this).data('bind-attr') === undefined) {
        Meteorite.add(json, $(this));
      // else update the element
      } else {
        Meteorite.update(json, $(this));
      }

    }
  });
}

// add an item
Meteorite.add = function(json, $element) {
  // add to the dom
  $element.append(json.bind_data);
  // listen to subscribe events
  Meteorite.subscribe($(json.bind_data).find('.meteorite').data('bind-key'));
}

// update a single attribute
Meteorite.update = function(json, $element) {
  // get the bind data
  var bind_data = JSON.parse(json.bind_data);
  // desired attribute
  var bind_attr = $element.data('bind-attr');
  // get the attribute data
  var attr_data = bind_data[bind_attr];

  // update the property
  $element.prop('checked', attr_data);
  // update the text
  $element.text(attr_data);
  // notify event handlers that a change has occurred
  $element.trigger('change');
}

// subscribe to all bind keys of meteorite classes
Meteorite.subscribeAll = function() {
  // for each meteorite class
  $('.meteorite').each(function() {
    // if there is a bind key
    if ($(this).data('bind-key') !== undefined) {
      // subscribe
      Meteorite.subscribe($(this).data('bind-key'));
    }
  });
}

// wait for websocket to be ready, then subscribe all
Meteorite.subscribeAfterWebsocket = function() {
  // set a 5ms timeout
  setTimeout(function() {
    // if the websocket is ready
    if (Meteorite.websocket.readyState === 1) {
      // send the subscribe notices
      Meteorite.subscribeAll();
    // try again if not ready
    } else { subscribeAfterWebsocket(); }
  }, 5);
}

// subscribe to events when the websocket is ready
Meteorite.subscribe = function(bind_key) {
  // send the subscribe notice
  Meteorite.websocket.send(JSON.stringify({ action: 'subscribe', key: bind_key }));
}

// when the document is ready
$(document).on('page:load ready', function() {
  Meteorite.subscribeAfterWebsocket();
});
<|endoftext|>


<commit_before><commit_msg>Fix plugin loading<commit_after><|endoftext|>