<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  name: 'Person',

  tableColumns: [ 'id', 'firstName', 'lastName', 'hello', 'remove' ],

  properties: [
    { class: 'Int',    name: 'id', hidden: true },
    { class: 'String', name: 'firstName' },
    { class: 'String', name: 'lastName' },
    { class: 'Int',    name: 'age' }
  ],

  actions: [
    {
      name: 'hello',
      code: function hello() {
        console.log('Hello', this.firstName + ' ' + this.lastName);
      }
    },
    {
      name: 'remove',
      code: function hello(X) {
        X.dao.remove(this);
      }
    }
  ]
});

foam.CLASS({
  name: 'Main',

  requires: [
    'foam.u2.TableView',
    'foam.dao.EasyDAO'
  ],

  exports: [ 'dao' ],

  properties: [
    {
      name: 'dao',
      factory: function() {
        return this.EasyDAO.create({
          of: Person,
          daoType: 'MDAO',
          seqNo: true,
          testData: [
            { firstName: 'John',  lastName: 'Davis' },
            { firstName: 'Steve', lastName: 'Howe' },
            { firstName: 'Andy',  lastName: 'Smith' },
            { firstName: 'Gary',  lastName: 'Russell' },
            { firstName: 'Janet', lastName: 'Jones' },
            { firstName: 'Linda', lastName: 'Fisher' },
            { firstName: 'Kim',   lastName: 'Erwin' }
          ]
        });
      }
    },
    {
      name: 'table',
      factory: function() {
        return this.TableView.create({
          of: Person,
          data: this.dao
        });
      }
    }
  ],

  methods: [
    function init() {
      this.table.write();
    }
  ]
});

var m = Main.create();
<commit_msg>Added selection listener example to u2/Table.js demo.<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  name: 'Person',

  tableColumns: [ 'id', 'firstName', 'lastName', 'hello', 'remove' ],

  properties: [
    { class: 'Int',    name: 'id', hidden: true },
    { class: 'String', name: 'firstName' },
    { class: 'String', name: 'lastName' },
    { class: 'Int',    name: 'age' }
  ],

  actions: [
    {
      name: 'hello',
      code: function hello() {
        console.log('Hello', this.firstName + ' ' + this.lastName);
      }
    },
    {
      name: 'remove',
      code: function hello(X) {
        X.dao.remove(this);
      }
    }
  ]
});

foam.CLASS({
  name: 'Main',

  requires: [
    'foam.u2.TableView',
    'foam.dao.EasyDAO'
  ],

  exports: [ 'dao' ],

  properties: [
    {
      name: 'dao',
      factory: function() {
        return this.EasyDAO.create({
          of: Person,
          daoType: 'MDAO',
          seqNo: true,
          testData: [
            { firstName: 'John',  lastName: 'Davis' },
            { firstName: 'Steve', lastName: 'Howe' },
            { firstName: 'Andy',  lastName: 'Smith' },
            { firstName: 'Gary',  lastName: 'Russell' },
            { firstName: 'Janet', lastName: 'Jones' },
            { firstName: 'Linda', lastName: 'Fisher' },
            { firstName: 'Kim',   lastName: 'Erwin' }
          ]
        });
      }
    },
    {
      name: 'table',
      factory: function() {
        return this.TableView.create({
          of: Person,
          data: this.dao
        });
      }
    }
  ],

  methods: [
    function init() {
      var table = this.table;
      table.write();
      table.selection$.sub(function() { console.log('selection: ', arguments, table.selection); });
    }
  ]
});

var m = Main.create();
<|endoftext|>


<commit_before>import Ember from 'ember';

var computed = Ember.computed;
var alias = computed.alias;

export default Ember.Component.extend({
  tagName: 'ul',
  classNameBindings: ['pager:pager:pagination', 'isHidden:hidden', 'paginationSizeClass'],
  pager: false,
  hide: false,
  pagerNext: 'Next',
  pagerPrevious: 'Previous',
  paginationPrevious: '«',
  paginationNext: '»',
  seperator: '…',
  countOut: 2,
  countIn: 2,
  firstPage: 1,
  current: 1,
  urlTemplate: '#',
  lastPage: alias('count'),

  previousUrl: computed('urlTemplate', 'current', 'firstPage', function () {
    var urlTemplate = this.get('urlTemplate');
    var current = this.get('current');
    var firstPage = this.get('firstPage');

    urlTemplate = urlTemplate.replace('{current}', current > firstPage ? current - 1 : current);

    return urlTemplate;
  }),

  nextUrl: computed('urlTemplate', 'current', 'count', function () {
    var urlTemplate = this.get('urlTemplate');
    var current = this.get('current');
    var count = this.get('count');

    urlTemplate = urlTemplate.replace('{current}', current < count ? current + 1 : current);

    return urlTemplate;
  }),

  currentPage: computed('current', function () {
    return Number(this.get('current'));
  }),

  paginationSizeClass: computed('paginationSize', function () {
    var size = this.get('size');
    var pager = this.get('pager');

    return !pager && size && (size === 'lg' || size === 'sm') ? 'pagination-' + size : '';
  }),

  isFirst: computed('firstPage', 'current', function () {
    return this.get('currentPage') === this.get('firstPage');
  }),

  isLast: computed('lastPage', 'current', function () {
    return this.get('currentPage') === this.get('lastPage');
  }),

  isHidden: computed('hide', 'count', function () {
    if (this.get('hide')) {
      return (this.get('count') === 1);
    }
    return false;
  }),

  pages: computed('count', 'current', 'countOut', 'countIn', function () {
    var seperator = this.get('seperator');
    var current = this.get('current');
    var count = this.get('count');
    var countOut = this.get('countOut');
    var countIn = this.get('countIn');
    var result = [];
    var i;

    // Beginning group of pages: n1...n2
    var n1 = 1;
    var n2 = Math.min(countOut, count);

    // Ending group of pages: n7...n8
    var n7 = Math.max(1, (count - countOut + 1));
    var n8 = count;

    // Middle group of pages: n4...n5
    var n4 = Math.max(n2 + 1, current - countIn);
    var n5 = Math.min(n7 - 1, current + countIn);
    var useMiddle = (n5 >= n4);

    // Point n3 between n2 and n4
    var n3 = Math.floor((n2 + n4) / 2);
    var useN3 = (useMiddle && ((n4 - n2) > 1));

    // Point $n6 between $n5 and $n7
    var n6 = Math.floor((n5 + n7) / 2);
    var useN6 = (useMiddle && ((n7 - n5) > 1));

    var links = [];

    // Generate links data in accordance with calculated numbers
    for (var n = n1; n <= n2; n++) {
      links[n] = n;
    }

    if (useN3) {
      links[n3] = seperator;
    }

    for (i = n4; i <= n5; i++) {
      links[i] = i;
    }

    if (useN6) {
      links[n6] = seperator;
    }

    for (i = n7; i <= n8; i++) {
      links[i] = i;
    }

    links.forEach(function (content, number) {
      result.push(content);
    });

    return result;
  }),

  click: function (event) {
    // stop `#` from jumping to top of page
    event.preventDefault();
  },

  actions: {
    next: function () {
      if (!this.get('isLast')) {
        var previous = parseInt(this.get('current'), 10);
        var current = previous + 1;

        this.set('current', current);
        this.send('pageChanged', current, previous);
      }
    },

    previous: function () {
      if (!this.get('isFirst')) {
        var previous = parseInt(this.get('current'), 10);
        var current = previous - 1;

        this.set('current', current);
        this.send('pageChanged', current, previous);
      }
    },

    pageChanged: function (page, previous) {
      this.sendAction('change', page, previous);
    }
  }
});
<commit_msg>pagination-pager component: remove unused "number" argument<commit_after>import Ember from 'ember';

var computed = Ember.computed;
var alias = computed.alias;

export default Ember.Component.extend({
  tagName: 'ul',
  classNameBindings: ['pager:pager:pagination', 'isHidden:hidden', 'paginationSizeClass'],
  pager: false,
  hide: false,
  pagerNext: 'Next',
  pagerPrevious: 'Previous',
  paginationPrevious: '«',
  paginationNext: '»',
  seperator: '…',
  countOut: 2,
  countIn: 2,
  firstPage: 1,
  current: 1,
  urlTemplate: '#',
  lastPage: alias('count'),

  previousUrl: computed('urlTemplate', 'current', 'firstPage', function () {
    var urlTemplate = this.get('urlTemplate');
    var current = this.get('current');
    var firstPage = this.get('firstPage');

    urlTemplate = urlTemplate.replace('{current}', current > firstPage ? current - 1 : current);

    return urlTemplate;
  }),

  nextUrl: computed('urlTemplate', 'current', 'count', function () {
    var urlTemplate = this.get('urlTemplate');
    var current = this.get('current');
    var count = this.get('count');

    urlTemplate = urlTemplate.replace('{current}', current < count ? current + 1 : current);

    return urlTemplate;
  }),

  currentPage: computed('current', function () {
    return Number(this.get('current'));
  }),

  paginationSizeClass: computed('paginationSize', function () {
    var size = this.get('size');
    var pager = this.get('pager');

    return !pager && size && (size === 'lg' || size === 'sm') ? 'pagination-' + size : '';
  }),

  isFirst: computed('firstPage', 'current', function () {
    return this.get('currentPage') === this.get('firstPage');
  }),

  isLast: computed('lastPage', 'current', function () {
    return this.get('currentPage') === this.get('lastPage');
  }),

  isHidden: computed('hide', 'count', function () {
    if (this.get('hide')) {
      return (this.get('count') === 1);
    }
    return false;
  }),

  pages: computed('count', 'current', 'countOut', 'countIn', function () {
    var seperator = this.get('seperator');
    var current = this.get('current');
    var count = this.get('count');
    var countOut = this.get('countOut');
    var countIn = this.get('countIn');
    var result = [];
    var i;

    // Beginning group of pages: n1...n2
    var n1 = 1;
    var n2 = Math.min(countOut, count);

    // Ending group of pages: n7...n8
    var n7 = Math.max(1, (count - countOut + 1));
    var n8 = count;

    // Middle group of pages: n4...n5
    var n4 = Math.max(n2 + 1, current - countIn);
    var n5 = Math.min(n7 - 1, current + countIn);
    var useMiddle = (n5 >= n4);

    // Point n3 between n2 and n4
    var n3 = Math.floor((n2 + n4) / 2);
    var useN3 = (useMiddle && ((n4 - n2) > 1));

    // Point $n6 between $n5 and $n7
    var n6 = Math.floor((n5 + n7) / 2);
    var useN6 = (useMiddle && ((n7 - n5) > 1));

    var links = [];

    // Generate links data in accordance with calculated numbers
    for (var n = n1; n <= n2; n++) {
      links[n] = n;
    }

    if (useN3) {
      links[n3] = seperator;
    }

    for (i = n4; i <= n5; i++) {
      links[i] = i;
    }

    if (useN6) {
      links[n6] = seperator;
    }

    for (i = n7; i <= n8; i++) {
      links[i] = i;
    }

    links.forEach(function (content) {
      result.push(content);
    });

    return result;
  }),

  click: function (event) {
    // stop `#` from jumping to top of page
    event.preventDefault();
  },

  actions: {
    next: function () {
      if (!this.get('isLast')) {
        var previous = parseInt(this.get('current'), 10);
        var current = previous + 1;

        this.set('current', current);
        this.send('pageChanged', current, previous);
      }
    },

    previous: function () {
      if (!this.get('isFirst')) {
        var previous = parseInt(this.get('current'), 10);
        var current = previous - 1;

        this.set('current', current);
        this.send('pageChanged', current, previous);
      }
    },

    pageChanged: function (page, previous) {
      this.sendAction('change', page, previous);
    }
  }
});
<|endoftext|>


<commit_before>function jpok(a,b){
  var dat = {pax:urb.term.pax, act:{}}
  dat.act[a] = b
  urb.send({data:dat}, function(e,dat){
    if(a === 'line' && dat.data.err){
      hist.unshift(prom.val())
      prom.val(b)
      hind = 0
    }
  })
}

var prom, prom_size, hist, hind, size, focus, pos, pax

$(function() {
  prom = $("#prom")
  prom_size = $("#prom-size")

  hist = []
  hind = 0

  var keys = ['l', 'x', 'r']
  for (i in keys) (function(k){
    Mousetrap.bind('ctrl+' + k, function(){
      jpok('cmd', k)
    })
  })(keys[i])

  prom.keydown(function(e){
    switch(e.which){
      default: return true
        break;
      case 13:  // %retn
        if(e.shiftKey) return true
        v = prom.val().replace(/\xa0/g, ' ')
        $(cont).append($('').html(prem.innerHTML),
                       $('').text(v))

        jpok('line', v)
        hist.unshift(v)
        prom.val('')
        return false
        break;
      case 38:  // %up
        if(hind == hist.length) return true
        if(pos().top === false) return true
        prom.val([hist[hind], hist[hind] = prom.val()][0]) // swap
        size()
        hind++
        return false
        break;
      case 40:  // %down
        if(hind == 0) return true
        if(pos().bot === false) return true
        size()
        hind--
        prom.val([hist[hind], hist[hind] = prom.val()][0]) // swap
        return false
    }
  })

  focus = function() { $(prom).focus(); }
  $('body').on('click', focus)
  focus()

  size = function() {
    prom_size.html(prom.val()+"
")
  }
  size()
  prom.on('input propertychange', size)

  pos = function() {
    v = prom.val()
    _top = v.slice(0,prom[0].selectionStart).indexOf("\n") === -1
    _bot = v.slice(prom[0].selectionStart).indexOf("\n") === -1
    return {top:_top,bot:_bot}
  }

  pax = '/lines'
  if(urb.term.pax != "/") pax += urb.term.pax
  urb.subscribe({path: pax}, function(e, dat){
    if(dat.data.ok) return;
    hist = dat.data.history
    hind = 0
    cont.innerHTML = ''
    for(var i in dat.data.lines){
      var lom = dat.data.lines[i]
      if(typeof lom == 'string')
        $(cont).append($('
').text(lom))
      else {
        $(cont).append($('').text(lom.prompt + '> '),
                       $('').text(lom.task))
      }
    }
    window.scrollTo(0,document.body.scrollHeight)
    prem.textContent = dat.data.prompt + '> '
  })
});
<commit_msg>Bound alt instead of ctrl on non-mac platforms<commit_after>function jpok(a,b){
  var dat = {pax:urb.term.pax, act:{}}
  dat.act[a] = b
  urb.send({data:dat}, function(e,dat){
    if(a === 'line' && dat.data.err){
      hist.unshift(prom.val())
      prom.val(b)
      hind = 0
    }
  })
}

var prom, prom_size, hist, hind, size, focus, pos, pax

$(function() {
  prom = $("#prom")
  prom_size = $("#prom-size")

  hist = []
  hind = 0

  var keys = ['l', 'x', 'r']
  var mod = /Mac|iPod|iPhone|iPad/.test(navigator.platform) ? 'ctrl' : 'alt'
  for (i in keys) (function(k){
    Mousetrap.bind(mod + '+' + k, function(){
      jpok('cmd', k)
    })
  })(keys[i])

  prom.keydown(function(e){
    switch(e.which){
      default: return true
        break;
      case 13:  // %retn
        if(e.shiftKey) return true
        v = prom.val().replace(/\xa0/g, ' ')
        $(cont).append($('').html(prem.innerHTML),
                       $('').text(v))

        jpok('line', v)
        hist.unshift(v)
        prom.val('')
        return false
        break;
      case 38:  // %up
        if(hind == hist.length) return true
        if(pos().top === false) return true
        prom.val([hist[hind], hist[hind] = prom.val()][0]) // swap
        size()
        hind++
        return false
        break;
      case 40:  // %down
        if(hind == 0) return true
        if(pos().bot === false) return true
        size()
        hind--
        prom.val([hist[hind], hist[hind] = prom.val()][0]) // swap
        return false
    }
  })

  focus = function() { $(prom).focus(); }
  $('body').on('click', focus)
  focus()

  size = function() {
    prom_size.html(prom.val()+"
")
  }
  size()
  prom.on('input propertychange', size)

  pos = function() {
    v = prom.val()
    _top = v.slice(0,prom[0].selectionStart).indexOf("\n") === -1
    _bot = v.slice(prom[0].selectionStart).indexOf("\n") === -1
    return {top:_top,bot:_bot}
  }

  pax = '/lines'
  if(urb.term.pax != "/") pax += urb.term.pax
  urb.subscribe({path: pax}, function(e, dat){
    if(dat.data.ok) return;
    hist = dat.data.history
    hind = 0
    cont.innerHTML = ''
    for(var i in dat.data.lines){
      var lom = dat.data.lines[i]
      if(typeof lom == 'string')
        $(cont).append($('
').text(lom))
      else {
        $(cont).append($('').text(lom.prompt + '> '),
                       $('').text(lom.task))
      }
    }
    window.scrollTo(0,document.body.scrollHeight)
    prem.textContent = dat.data.prompt + '> '
  })
});
<|endoftext|>


<commit_before>'use strict';

let map, latLon;
let mapMarkers = [];
let eventData = [];
const SEATTLE = {lat: 47.618347, lng: -122.351977};

function initMap(latLon = SEATTLE) {
  map = new google.maps.Map(document.getElementById('map'), {
    center: latLon,
    zoom: 12
  });
}

navigator.geolocation.getCurrentPosition(position =>
  latLon = [position.coords.latitude, position.coords.longitude])

const fetchMeetupData = callback => {
  $('#find-meetups').on('click', () =>
    $.ajax({
      url: '/venues/meetup',
      method: 'post',
      data: { latLon }
    }).then(callback));
}

const template = Handlebars.compile($('#eventlist-template').text());

const appendMapMarker = event => {
  let marker = new google.maps.Marker({
    position:{lat: event.group.lat, lng: event.group.lon},
    map,
    icon: { url: 'icons/panda.svg' }
  });
  let infoWindow = new google.maps.InfoWindow({
    content: `${event.description}`
  });
  marker.addListener('click', () => infoWindow.open(map, marker));
  mapMarkers.push(marker);
}

const renderMapData = data => {
  $('#icons').show();
  $('#map').show();
  initMap();
  data.forEach(event => {
    event.time = new Date(event.time)
    eventData.push(event);
    $('#list-events').append(template(event));
    appendMapMarker(event);
  });
  setTeasers();
  deleteEvents();
};

$(() => fetchMeetupData(renderMapData));
<commit_msg>hide the find meetup button<commit_after>'use strict';

let map, latLon;
let mapMarkers = [];
let eventData = [];
const SEATTLE = {lat: 47.618347, lng: -122.351977};

function initMap(latLon = SEATTLE) {
  map = new google.maps.Map(document.getElementById('map'), {
    center: latLon,
    zoom: 12
  });
}

navigator.geolocation.getCurrentPosition(position =>
  latLon = [position.coords.latitude, position.coords.longitude])

const fetchMeetupData = callback => {
  $('#find-meetups').on('click', () =>
    $.ajax({
      url: '/venues/meetup',
      method: 'post',
      data: { latLon }
    }).then(callback));
}

const template = Handlebars.compile($('#eventlist-template').text());

const appendMapMarker = event => {
  let marker = new google.maps.Marker({
    position:{lat: event.group.lat, lng: event.group.lon},
    map,
    icon: { url: 'icons/panda.svg' }
  });
  let infoWindow = new google.maps.InfoWindow({
    content: `${event.description}`
  });
  marker.addListener('click', () => infoWindow.open(map, marker));
  mapMarkers.push(marker);
}

const renderMapData = data => {
  $('#icons').show();
  $('#find-meetups').hide();
  $('#map').show();
  initMap();
  data.forEach(event => {
    event.time = new Date(event.time)
    eventData.push(event);
    $('#list-events').append(template(event));
    appendMapMarker(event);
  });
  setTeasers();
  deleteEvents();
};

$(() => fetchMeetupData(renderMapData));
<|endoftext|>


<commit_before>jest.dontMock('../MarathonTaskDetailsList');
/* eslint-disable no-unused-vars */
var React = require('react');
/* eslint-enable no-unused-vars */
var ReactDOM = require('react-dom');
var TestUtils = require('react-addons-test-utils');

var MarathonStore = require('../../stores/MarathonStore');
var MarathonTaskDetailsList = require('../MarathonTaskDetailsList');
var Service = require('../../structs/Service');

describe('MarathonTaskDetailsList', function () {

  beforeEach(function () {
    this.getServiceFromTaskID = MarathonStore.getServiceFromTaskID;
    MarathonStore.getServiceFromTaskID = function () {
      return new Service();
    };
    this.container = document.createElement('div');
  });

  afterEach(function () {
    MarathonStore.getServiceFromTaskID = this.getServiceFromTaskID;
    ReactDOM.unmountComponentAtNode(this.container);
  });

  describe('#getTaskEndpoints', function () {

    it('returns None if ipAddresses, ports and host is not set', function () {
      var instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({})).toEqual('None');
    });

    it('returns a list of ipAddresses if ports is not defined', function () {
      var instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({
        ipAddresses: [
          new Service({ipAddress: 'foo'}),
          new Service({ipAddress: 'bar'})
        ]
      })).toEqual('foo, bar');
    });

    it('returns host if ports and ipAddresses are not defined', function () {
      var instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({host: 'foo'}))
        .toEqual('foo');
    });

    it('returns host with ports if ipAddresses are not defined', function () {
      var instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      var result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:1');
    });

    it('uses service ports if available', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      var instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      var result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(1);
      expect(result[0].props.children).toEqual('foo:3');
    });

    it('defaults to ipAddresses and service ports', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      var instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      var result = instance.getTaskEndpoints({
        host: 'foo',
        ipAddresses: [
          new Service({ipAddress: 'foo'}),
          new Service({ipAddress: 'bar'})
        ],
        ports: [1, 2]
      });

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:3');
      expect(result[1].props.children).toEqual('bar:3');
    });

  })

});
<commit_msg>Using let's<commit_after>jest.dontMock('../MarathonTaskDetailsList');
/* eslint-disable no-unused-vars */
var React = require('react');
/* eslint-enable no-unused-vars */
var ReactDOM = require('react-dom');
var TestUtils = require('react-addons-test-utils');

let MarathonStore = require('../../stores/MarathonStore');
let MarathonTaskDetailsList = require('../MarathonTaskDetailsList');
let Service = require('../../structs/Service');

describe('MarathonTaskDetailsList', function () {

  beforeEach(function () {
    this.getServiceFromTaskID = MarathonStore.getServiceFromTaskID;
    MarathonStore.getServiceFromTaskID = function () {
      return new Service();
    };
    this.container = document.createElement('div');
  });

  afterEach(function () {
    MarathonStore.getServiceFromTaskID = this.getServiceFromTaskID;
    ReactDOM.unmountComponentAtNode(this.container);
  });

  describe('#getTaskEndpoints', function () {

    it('returns None if ipAddresses, ports and host is not set', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({})).toEqual('None');
    });

    it('returns a list of ipAddresses if ports is not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({
        ipAddresses: [
          new Service({ipAddress: 'foo'}),
          new Service({ipAddress: 'bar'})
        ]
      })).toEqual('foo, bar');
    });

    it('returns host if ports and ipAddresses are not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      expect(instance.getTaskEndpoints({host: 'foo'}))
        .toEqual('foo');
    });

    it('returns host with ports if ipAddresses are not defined', function () {
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:1');
    });

    it('uses service ports if available', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({host: 'foo', ports: [1, 2]});

      expect(result.length).toEqual(1);
      expect(result[0].props.children).toEqual('foo:3');
    });

    it('defaults to ipAddresses and service ports', function () {
      MarathonStore.getServiceFromTaskID = function () {
        return new Service({
          ipAddress: {discovery : {ports: [{number: 3}]}}
        });
      };
      let instance = ReactDOM.render(
        <MarathonTaskDetailsList taskID="foo" />,
        this.container
      );
      let result = instance.getTaskEndpoints({
        host: 'foo',
        ipAddresses: [
          new Service({ipAddress: 'foo'}),
          new Service({ipAddress: 'bar'})
        ],
        ports: [1, 2]
      });

      expect(result.length).toEqual(2);
      expect(result[0].props.children).toEqual('foo:3');
      expect(result[1].props.children).toEqual('bar:3');
    });

  })

});
<|endoftext|>


<commit_before> function showBatchWork(keyword){
    	window.clearInterval(inter);
		$("#main").load("remote/batch.jsp",{"tb":"room","keyword":keyword});
    }
 function doBatchWork(frm){
	 window.clearInterval(inter);
		var data=$(frm).serialize();

		$("#main").load("remote/batch.jsp#"+Math.random(),data);
		return false;
	}

 function viewroom(roomid){
 	window.clearInterval(inter);
		$("#main").load("view.jsp",{"tb":"room","id":roomid},reformatview);
 }
	function dbid(evt){
		var data=$(evt.target).parent().parent().attr("id");
		return parseInt(data);
	}
	function view(rowid){
		var tbname=$("#tbname").val();
		$("#main").load("view.jsp",{"tb":tbname,"id":rowid},reformatview);

		//window.open("view.jsp?tb="+tbname+"&id="+rowid);
	}
	function edit(rowid){
		var tbname=$("#tbname").val();
		$("#main").load("add.jsp",{"tb":tbname,"id":rowid},reformatform);
	}
	function del(rowid){
		var ans=confirm('确定删除编号'+rowid);
		var tbname=$("#tbname").val();
		if(ans){
			$.post("del.jsp",{"tbname":tbname,"id":rowid},new function(){
				 window.setTimeout("mainLoad('"+tbname+"')",1000);
			});
		}
	}
	function addButton(main){
		//$("#main tr :last-child").css("background","#eeeeee");
		$("#main tr").append(""+
				""+
				""
										+"	");
		$(".glyphicon-eye-open").bind("click",function(evt){
			view(dbid(evt));
		});
		$(".glyphicon-trash").bind("click",function(evt){
			del(dbid(evt));
		});
		$(".glyphicon-edit").bind("click",function(evt){
			edit(dbid(evt));
		});

	}
	function loadSelect(input){
		var input_name=input.attr("name");
		var default_value=input.val();
		if(input[0].nodeName=="SPAN")  //view use span for tag, text() for value
			 	default_value=input.text();
		var tbname=input_name.substring(0,input_name.length-3);
		var td=input.parent();
		td.load("select.jsp?tbname="+tbname+"&value="+default_value);

	}
	function reformatform(){
		$("input[name$=_id]").each(function(){
			loadSelect($(this));
		});
		$(".input_date").datepicker();
		$(".input_datetime").datetimepicker(
				{   showSecond: false,
		            timeFormat: 'hh:mm:ss',
		            stepHour: 1,
		            stepMinute: 1,
		            stepSecond: 1
		        });
		if(tableName!='config')
				$("textarea").ckeditor();
	}
	function reformatview(){
		$("span[name$=_id]").each(function(){
			loadSelect($(this));
		});
	}

	function loadReport(){
		window.clearInterval(inter);
		$("#main").load("report_select.jsp?"+Math.random());
	}
	function mainLoad(tbname,pageNum,keyword){
		tableName=tbname;
		if (typeof(pageNum)=='undefined') pageNum=thepage;
		if (typeof(keyword)=='undefined') keyword=searchKeyword;
		thepage=pageNum;
		searchKeyword=keyword;
		window.clearInterval(inter);
		$("#main").load("list.jsp",{"tb":tbname,"pageNum":pageNum,"keyword":keyword},function(text,status,http){

			if(status=="success"){
				addButton($("#main"));
				$("#tbname").after("");
				$("#addrow").click(function(){
					$("#main").load("add.jsp",{tb:tbname},reformatform);
				});
			}
		});

	}
	function pageUp(tbname,pageNum){
		mainLoad(tbname,pageNum-1,searchKeyword);
	}
	function pageDown(tbname,pageNum){
		mainLoad(tbname,pageNum+1,searchKeyword);
	}
	function showReport(frm){
		var data=$(frm).serialize();
		$("#main").load("jsp/report_select.jsp?"+Math.random(),data);
		return false;
	}
	function submitAdd(tbname){
		var data=$("#addForm").serialize();
		$.post("add.jsp",data,new function(){
			 mainLoad(tbname,0,searchKeyword);
		});
	}
	function submitTable(){
		var data=$("#frmAddTable").serialize();
		var tbname=$("#tb_name").val();

		$.post("addTable.jsp",data,new function(){
			 window.setTimeout("mainLoad('"+tbname+"',0,'"+searchKeyword+"')",1000);
		});
	}
	function addTable(){
		$("#main").empty();
		$("#main").append("<form method=post onSubmit='submitTable()' id='frmAddTable'>

</form>");
		$("#tb_adding").append("
表名<input type=text id=tb_name name=tb_name value='tb_'>	中文<input type=text name=tb_title value='信息'>
");
		$("#tb_adding").append("
列名	类型	中文
");
		$("#frmAddTable").append("<input type=button onclick='submitTable()' value='确定'><input type=reset value='重置'>");
		addColumn();

	}
	function addColumn(){
		var row="
";
		row+="<input name=fd_name type=text>	";
		row+="<input name=fd_type type=text value='varchar(32)'>	";
		row+="<input name=fd_title type=text>	";
		row+="
";
		$("#tb_adding").append(row);
	}
	function search(keyword){
		window.clearTimeout(stid);
		searchKeyword=keyword;
		stid=window.setTimeout("mainLoad(tableName,0,'"+keyword+"');",500);
	}
	var inter=null;
	var stid=null;
	var tableName="room";
	var searchKeyword="";
	var thepage=0;
<commit_msg>Update main.js<commit_after> function showBatchWork(keyword){
    	window.clearInterval(inter);
		$("#main").load("remote/batch.jsp",{"tb":"room","keyword":keyword});
    }
 function doBatchWork(frm){
	 window.clearInterval(inter);
		var data=$(frm).serialize();

		$("#main").load("remote/batch.jsp#"+Math.random(),data);
		return false;
	}

 function viewroom(roomid){
 	window.clearInterval(inter);
		$("#main").load("view.jsp",{"tb":"room","id":roomid},reformatview);
 }
	function dbid(evt){
		var data=$(evt.target).parent().parent().attr("id");
		return parseInt(data);
	}
	function view(rowid){
		var tbname=$("#tbname").val();
		$("#main").load("view.jsp",{"tb":tbname,"id":rowid},reformatview);

		//window.open("view.jsp?tb="+tbname+"&id="+rowid);
	}
	function edit(rowid){
		var tbname=$("#tbname").val();
		$("#main").load("add.jsp",{"tb":tbname,"id":rowid},reformatform);
	}
	function del(rowid){
		var ans=confirm('确定删除编号'+rowid);
		var tbname=$("#tbname").val();
		if(ans){
			$.post("del.jsp",{"tbname":tbname,"id":rowid},new function(){
				 window.setTimeout("mainLoad('"+tbname+"')",1000);
			});
		}
	}
	function addButton(main){
		//$("#main tr :last-child").css("background","#eeeeee");
		$("#main tr").append(""+
				""+
				""
										+"	");
		$(".glyphicon-eye-open").bind("click",function(evt){
			view(dbid(evt));
		});
		$(".glyphicon-trash").bind("click",function(evt){
			del(dbid(evt));
		});
		$(".glyphicon-edit").bind("click",function(evt){
			edit(dbid(evt));
		});

	}
	function loadSelect(input){
		var input_name=input.attr("name");
		var default_value=input.val();
		if(input[0].nodeName=="SPAN")  //view use span for tag, text() for value
			 	default_value=input.text();
		var tbname=input_name.substring(0,input_name.length-3);
		var td=input.parent();
		td.load("select.jsp?tbname="+tbname+"&value="+default_value);

	}
	function reformatform(){
		$("input[name$=_id]").each(function(){
			loadSelect($(this));
		});
		$(".input_date").datepicker();
		$(".input_datetime").datetimepicker(
				{   showSecond: false,
		            timeFormat: 'hh:mm:ss',
		            stepHour: 1,
		            stepMinute: 1,
		            stepSecond: 1
		        });
		if(tableName!='config')
				$("textarea").ckeditor();
	}
	function reformatview(){
		$("span[name$=_id]").each(function(){
			loadSelect($(this));
		});
	}

	function loadReport(){
		window.clearInterval(inter);
		$("#main").load("report_select.jsp?"+Math.random());
	}
	function mainLoad(tbname,pageNum,keyword){
		tableName=tbname;
		if (typeof(pageNum)=='undefined') pageNum=thepage;
		if (typeof(keyword)=='undefined') keyword=searchKeyword;
		thepage=pageNum;
		searchKeyword=keyword;
		window.clearInterval(inter);
		$("#main").load("list.jsp",{"tb":tbname,"pageNum":pageNum,"keyword":keyword},function(text,status,http){

			if(status=="success"){
				addButton($("#main"));
				$("#tbname").after("");
				$("#addrow").click(function(){
					$("#main").load("add.jsp",{tb:tbname},reformatform);
				});
			}
		});

	}
	function pageUp(tbname,pageNum){
		mainLoad(tbname,pageNum-1,searchKeyword);
	}
	function pageDown(tbname,pageNum){
		mainLoad(tbname,pageNum+1,searchKeyword);
	}
	function showReport(frm){
		var data=$(frm).serialize();
		$("#main").load("report_select.jsp?"+Math.random(),data);
		return false;
	}
	function submitAdd(tbname){
		var data=$("#addForm").serialize();
		$.post("add.jsp",data,new function(){
			 mainLoad(tbname,0,searchKeyword);
		});
	}
	function submitTable(){
		var data=$("#frmAddTable").serialize();
		var tbname=$("#tb_name").val();

		$.post("addTable.jsp",data,new function(){
			 window.setTimeout("mainLoad('"+tbname+"',0,'"+searchKeyword+"')",1000);
		});
	}
	function addTable(){
		$("#main").empty();
		$("#main").append("<form method=post onSubmit='submitTable()' id='frmAddTable'>

</form>");
		$("#tb_adding").append("
表名<input type=text id=tb_name name=tb_name value='tb_'>	中文<input type=text name=tb_title value='信息'>
");
		$("#tb_adding").append("
列名	类型	中文
");
		$("#frmAddTable").append("<input type=button onclick='submitTable()' value='确定'><input type=reset value='重置'>");
		addColumn();

	}
	function addColumn(){
		var row="
";
		row+="<input name=fd_name type=text>	";
		row+="<input name=fd_type type=text value='varchar(32)'>	";
		row+="<input name=fd_title type=text>	";
		row+="
";
		$("#tb_adding").append(row);
	}
	function search(keyword){
		window.clearTimeout(stid);
		searchKeyword=keyword;
		stid=window.setTimeout("mainLoad(tableName,0,'"+keyword+"');",500);
	}
	var inter=null;
	var stid=null;
	var tableName="room";
	var searchKeyword="";
	var thepage=0;
<|endoftext|>


<commit_before>// Copyright 2019-2021, University of Colorado Boulder

/**
 * An XYChartNode that includes a draggable cursor that allows the user to scrub or play back through the data.
 *
 * @author Jesse Greenberg
 *
 * @deprecated - please use bamboo
 */

import Utils from '../../dot/js/Utils.js';
import deprecationWarning from '../../phet-core/js/deprecationWarning.js';
import merge from '../../phet-core/js/merge.js';
import ArrowNode from '../../scenery-phet/js/ArrowNode.js';
import { DragListener } from '../../scenery/js/imports.js';
import { Circle } from '../../scenery/js/imports.js';
import { Node } from '../../scenery/js/imports.js';
import { Rectangle } from '../../scenery/js/imports.js';
import { Color } from '../../scenery/js/imports.js';
import Tandem from '../../tandem/js/Tandem.js';
import griddle from './griddle.js';
import XYChartNode from './XYChartNode.js';

// constants
const WIDTH_PROPORTION = 0.013; // empirically determined
const CURSOR_FILL_COLOR = new Color( 50, 50, 200, 0.2 );
const CURSOR_STROKE_COLOR = Color.DARK_GRAY;
const ARROW_CUE_FILL_COLOR = new Color( 180, 180, 230 );
const ARROW_CUE_STROKE_COLOR = Color.DARK_GRAY;

class XYCursorChartNode extends XYChartNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {
    assert && deprecationWarning( 'Please use bamboo' );

    options = merge( {

      // options passed on to the cursor, see ChartCursor
      cursorOptions: null,

      chartPanelOptions: {

        // so that the cursor is draggable
        pickable: true
      },

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    super( options );

    // @private {boolean|null} - if set with setCursorVisible, then this will indicate visibility of the cursor
    this._cursorVisibleOverride = null;

    // @private {number} - value for the cursor, determines cursor positioning
    this.cursorValue = 0;

    // @public (read-only) - minimum and maximum recorded value, required by the cursor to limit dragging
    this.minRecordedXValue = 0;
    this.maxRecordedXValue = 0;

    // @private Map.<DynamicSeries,function> Keep track of the listener for each series, so the listener can be removed
    // when a series is removed.
    this.dynamicSeriesListenerMap = new Map();

    // @protected (read-only) {DynamicSeries[]}
    this.dynamicSeriesArray = [];

    // @private {ChartCursor} - draggable Node that shows the cursor value
    this.chartCursor = new ChartCursor( this, this.modelViewTransformProperty, options.cursorOptions );
    this.chartPanel.addChild( this.chartCursor );

    // initialize position and visibility of the cursor
    this.updateCursor();
  }

  /**
   * Adds a DynamicSeries to the XYCursorChartNode.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries
   */
  addDynamicSeries( dynamicSeries ) {
    super.addDynamicSeries( dynamicSeries );

    this.dynamicSeriesArray.push( dynamicSeries );

    // when a point is added, update the min and max recorded values
    const dynamicSeriesListener = () => {

      // update the
      this.updateMinMaxRecordedValues();

      // if all data has been removed from the chart, update cursor visibility
      this.updateCursor();
    };

    // save to map so that listener can be found again for disposal
    this.dynamicSeriesListenerMap.set( dynamicSeries, dynamicSeriesListener );
    dynamicSeries.addDynamicSeriesListener( dynamicSeriesListener );
  }

  /**
   * Removes a DynamicSeries from the chart and disposes of its listener.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries - to remove
   */
  removeDynamicSeries( dynamicSeries ) {
    super.removeDynamicSeries( dynamicSeries );

    const seriesIndex = this.dynamicSeriesArray.indexOf( dynamicSeries );
    this.dynamicSeriesArray.splice( seriesIndex, 1 );

    dynamicSeries.removeDynamicSeriesListener( this.dynamicSeriesListenerMap.get( dynamicSeries ) );
    this.dynamicSeriesListenerMap.delete( dynamicSeries );
  }

  /**
   * Sets the cursor value. The value of the cursor is constrained to be within chart bounds.
   * @public
   *
   * @param {number} value
   */
  setCursorValue( value ) {
    const modelViewTransform = this.modelViewTransformProperty.get();
    const minX = modelViewTransform.viewToModelX( 0 );
    const maxX = modelViewTransform.viewToModelX( this.chartWidth + this.chartCursor.width / 2 );
    this.cursorValue = Utils.clamp( value, minX, maxX );
    this.updateCursor();
  }

  /**
   * Gets the value currently under the cursor.
   * @public
   *
   * @returns {number}
   */
  getCursorValue() {
    return this.cursorValue;
  }

  /**
   * Resets the cursor.
   * @public
   */
  resetCursor() {
    this.chartCursor.reset();
  }

  /**
   * Overrides the default behavior for setting cursor visibility. If set to null, cursor visibility will behave as
   * described in updateCursorVisibility. Otherwise, visibility will equal the boolean value set here.
   * @param {boolean|null} visible
   * @public
   */
  setCursorVisibleOverride( visible ) {
    assert && assert( typeof visible === 'boolean' || visible === null, 'visible must be boolean or null' );
    this._cursorVisibleOverride = visible;
    this.updateCursorVisibility();
  }

  /**
   * Updates the cursor visibility and position.
   * @private
   */
  updateCursor() {
    this.updateCursorVisibility();
    if ( this.chartCursor.isVisible() ) {
      this.updateCursorPosition();
    }
  }

  /**
   * Updates the cursor position.
   * @private
   */
  updateCursorPosition() {
    this.moveCursorToValue( this.cursorValue );
  }

  /**
   * Updates the cursor visibility. The cursor should be visible any time the cursor value is within
   * the recorded value range.
   * @private
   */
  updateCursorVisibility() {

    const wasVisible = this.chartCursor.visible;
    if ( typeof this._cursorVisibleOverride === 'boolean' ) {
      this.chartCursor.setVisible( this._cursorVisibleOverride );
    }
    else {

      const maxX = this.modelViewTransformProperty.get().viewToModelX( this.chartWidth + this.chartCursor.width / 2 );
      const minX = this.modelViewTransformProperty.get().viewToModelX( 0 );

      const isCurrentValueOnChart = ( this.cursorValue >= minX ) && ( this.cursorValue <= maxX );
      const hasData = this.hasData();
      const chartCursorVisible = isCurrentValueOnChart && hasData;

      this.chartCursor.setVisible( chartCursorVisible );
    }

    // if the cursor just became invisible, interrupt any active dragging
    if ( !this.chartCursor.visible && wasVisible ) {
      this.chartCursor.interruptDrag();
    }
  }

  /**
   * Returns true if any DynamicSeries associated with this chart has data.
   * @returns {boolean}
   * @public
   */
  hasData() {
    return _.some( this.dynamicSeriesArray, dynamicSeries => dynamicSeries.hasData() );
  }

  /**
   * Moves the cursor to the specified value.
   * @private
   *
   * @param {number} value
   */
  moveCursorToValue( value ) {
    const viewPosition = this.modelViewTransformProperty.get().modelToViewX( value );

    // keep the cursor within the grid bounds
    this.chartCursor.centerX = Utils.clamp( viewPosition, 0, this.chartWidth );
    this.chartCursor.centerY = this.gridNode.centerY;
  }

  /**
   * From the existing data, update the min and max recorded X values from all of the dynamicSeries of this chart,
   * so that the cursor can be limited to the recorded data.
   * @private
   */
  updateMinMaxRecordedValues() {
    let minValue = Number.POSITIVE_INFINITY;
    let maxValue = Number.NEGATIVE_INFINITY;
    this.dynamicSeriesArray.forEach( dynamicSeries => {
      if ( dynamicSeries.getLength() > 0 ) {
        const seriesMinValue = dynamicSeries.getDataPoint( 0 ).x;
        const seriesMaxValue = dynamicSeries.getDataPoint( dynamicSeries.getLength() - 1 ).x;
        if ( seriesMinValue < minValue ) {
          minValue = seriesMinValue;
        }
        if ( seriesMaxValue > maxValue ) {
          maxValue = seriesMaxValue;
        }
      }
    } );

    this.minRecordedXValue = minValue;
    this.maxRecordedXValue = maxValue;
  }
}

/**
 * ChartCursor is a rectangular cursor that indicates the current or selected value on the chart.
 */
class ChartCursor extends Rectangle {

  /**
   * @param {XYCursorChartNode} chart
   * @param {Property.<ModelViewTransform2>} modelViewTransformProperty
   * @param {Object} [options]
   */
  constructor( chart, modelViewTransformProperty, options ) {

    options = merge( {
      startDrag: () => {},
      endDrag: () => {},
      drag: () => {},

      // {boolean} - if true, a double headed arrow will be shown to indicate
      // that the cursor is draggable - becomes invisible after first
      // drag
      includeDragCue: false,

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    const width = chart.chartWidth * WIDTH_PROPORTION;
    const height = chart.chartHeight;

    // Set the shape. Origin is at the center top of the rectangle.
    super( 0, -height, width, height, 0, 0, {
      cursor: 'ew-resize',
      fill: CURSOR_FILL_COLOR,
      stroke: CURSOR_STROKE_COLOR,
      lineWidth: 0.4,
      lineDash: [ 4, 4 ]
    } );

    // @private
    this.chart = chart;

    // @public
    this.modelViewTransformProperty = modelViewTransformProperty;

    // Make it easier to grab this cursor by giving it expanded mouse and touch areas.
    this.mouseArea = this.localBounds.dilatedX( 12 );
    this.touchArea = this.localBounds.dilatedX( 12 );

    // @private
    this.includeDragCue = options.includeDragCue;

    if ( this.includeDragCue ) {

      // @private - indicates to the user that the cursor is draggable, only created
      // and added if necessary
      this.dragCueArrowNode = new ArrowNode( -width * 2, 0, width * 2, 0, {
        doubleHead: true,
        headWidth: 12,
        headHeight: 10,
        fill: ARROW_CUE_FILL_COLOR,
        stroke: ARROW_CUE_STROKE_COLOR,
        center: this.center.plusXY( 0, height * 0.4 )
      } );

      this.addChild( this.dragCueArrowNode );
    }

    // Add the indentations that are intended to convey the idea of "gripability".
    const grippyNode = new Node();
    const indentSpacing = height * 0.05;
    for ( let i = 0; i < 3; i++ ) {
      const indentNode = new GrippyIndentNode( width / 2, CURSOR_FILL_COLOR );
      indentNode.top = i * ( indentNode.height + indentSpacing );
      grippyNode.addChild( indentNode );
    }
    grippyNode.center = this.center;
    this.addChild( grippyNode );

    // @private - so that we can interrupt the DragListener if necessary
    this.dragListener = new DragListener( {
      start: ( event, listener ) => {
        assert && assert( this.chart.hasData(), 'chart should have data for the cursor to be draggable' );
        options.startDrag();
      },
      drag: ( event, listener ) => {
        const parentX = listener.parentPoint.x;
        let newValue = this.modelViewTransformProperty.get().viewToModelX( parentX );

        // limit cursor to the domain of recorded values
        newValue = Utils.clamp( newValue, this.chart.minRecordedXValue, this.chart.maxRecordedXValue );
        this.chart.setCursorValue( newValue );

        options.drag();
      },
      end: () => {
        options.endDrag();

        // no need to show arrow after user successfully drags the cursor
        if ( this.includeDragCue ) {
          this.dragCueArrowNode.visible = false;
        }
      },
      tandem: options.tandem.createTandem( 'dragListener' )
    } );
    this.addInputListener( this.dragListener );
  }

  /**
   * Interrupts dragging of the cursor, useful when ChartCursor visibility changes.
   * @public
   */
  interruptDrag() {
    this.dragListener.interrupt();
  }

  /**
   * Resets the ChartCursor to its initial state. Note that this does not modify data
   * or the cursor position (cursorValue), only aspects of the view for the cursor itself.
   * @public
   */
  reset() {
    if ( this.includeDragCue ) {
      this.dragCueArrowNode.visible = true;
    }
  }
}

/**
 * GrippyIndentNode is a small round indentation on a surface.  This is a modern user interface paradigm that
 * is intended to convey the concept of "gripability", i.e. something that the user can grab.  This is meant to
 * look somewhat 3D, much like etched borders do.
 */
class GrippyIndentNode extends Circle {

  /**
   * @param {number} diameter
   * @param {Color} baseColor
   * @param {Object} [options]
   */
  constructor( diameter, baseColor, options ) {

    options = merge( {
      lineWidth: 0.5
    }, options );

    const baseDarkerColor = baseColor.darkerColor( 0.9 );
    const translucentDarkerColor = new Color( baseDarkerColor.getRed(), baseDarkerColor.getGreen(),
      baseDarkerColor.getBlue(), baseColor.getAlpha() );
    const baseLighterColor = baseColor.brighterColor( 0.9 );
    const translucentBrighterColor = new Color( baseLighterColor.getRed(), baseLighterColor.getGreen(),
      baseLighterColor.getBlue(), baseColor.getAlpha() );
    const radius = diameter / 2 - options.lineWidth;

    super( radius, {
      fill: translucentDarkerColor,
      stroke: translucentBrighterColor,
      lineWidth: options.lineWidth
    } );
  }
}

griddle.register( 'XYCursorChartNode', XYCursorChartNode );
export default XYCursorChartNode;<commit_msg>update copyright dates from daily grunt work<commit_after>// Copyright 2019-2022, University of Colorado Boulder

/**
 * An XYChartNode that includes a draggable cursor that allows the user to scrub or play back through the data.
 *
 * @author Jesse Greenberg
 *
 * @deprecated - please use bamboo
 */

import Utils from '../../dot/js/Utils.js';
import deprecationWarning from '../../phet-core/js/deprecationWarning.js';
import merge from '../../phet-core/js/merge.js';
import ArrowNode from '../../scenery-phet/js/ArrowNode.js';
import { DragListener } from '../../scenery/js/imports.js';
import { Circle } from '../../scenery/js/imports.js';
import { Node } from '../../scenery/js/imports.js';
import { Rectangle } from '../../scenery/js/imports.js';
import { Color } from '../../scenery/js/imports.js';
import Tandem from '../../tandem/js/Tandem.js';
import griddle from './griddle.js';
import XYChartNode from './XYChartNode.js';

// constants
const WIDTH_PROPORTION = 0.013; // empirically determined
const CURSOR_FILL_COLOR = new Color( 50, 50, 200, 0.2 );
const CURSOR_STROKE_COLOR = Color.DARK_GRAY;
const ARROW_CUE_FILL_COLOR = new Color( 180, 180, 230 );
const ARROW_CUE_STROKE_COLOR = Color.DARK_GRAY;

class XYCursorChartNode extends XYChartNode {

  /**
   * @param {Object} [options]
   */
  constructor( options ) {
    assert && deprecationWarning( 'Please use bamboo' );

    options = merge( {

      // options passed on to the cursor, see ChartCursor
      cursorOptions: null,

      chartPanelOptions: {

        // so that the cursor is draggable
        pickable: true
      },

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    super( options );

    // @private {boolean|null} - if set with setCursorVisible, then this will indicate visibility of the cursor
    this._cursorVisibleOverride = null;

    // @private {number} - value for the cursor, determines cursor positioning
    this.cursorValue = 0;

    // @public (read-only) - minimum and maximum recorded value, required by the cursor to limit dragging
    this.minRecordedXValue = 0;
    this.maxRecordedXValue = 0;

    // @private Map.<DynamicSeries,function> Keep track of the listener for each series, so the listener can be removed
    // when a series is removed.
    this.dynamicSeriesListenerMap = new Map();

    // @protected (read-only) {DynamicSeries[]}
    this.dynamicSeriesArray = [];

    // @private {ChartCursor} - draggable Node that shows the cursor value
    this.chartCursor = new ChartCursor( this, this.modelViewTransformProperty, options.cursorOptions );
    this.chartPanel.addChild( this.chartCursor );

    // initialize position and visibility of the cursor
    this.updateCursor();
  }

  /**
   * Adds a DynamicSeries to the XYCursorChartNode.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries
   */
  addDynamicSeries( dynamicSeries ) {
    super.addDynamicSeries( dynamicSeries );

    this.dynamicSeriesArray.push( dynamicSeries );

    // when a point is added, update the min and max recorded values
    const dynamicSeriesListener = () => {

      // update the
      this.updateMinMaxRecordedValues();

      // if all data has been removed from the chart, update cursor visibility
      this.updateCursor();
    };

    // save to map so that listener can be found again for disposal
    this.dynamicSeriesListenerMap.set( dynamicSeries, dynamicSeriesListener );
    dynamicSeries.addDynamicSeriesListener( dynamicSeriesListener );
  }

  /**
   * Removes a DynamicSeries from the chart and disposes of its listener.
   * @override
   * @public
   *
   * @param {DynamicSeries} dynamicSeries - to remove
   */
  removeDynamicSeries( dynamicSeries ) {
    super.removeDynamicSeries( dynamicSeries );

    const seriesIndex = this.dynamicSeriesArray.indexOf( dynamicSeries );
    this.dynamicSeriesArray.splice( seriesIndex, 1 );

    dynamicSeries.removeDynamicSeriesListener( this.dynamicSeriesListenerMap.get( dynamicSeries ) );
    this.dynamicSeriesListenerMap.delete( dynamicSeries );
  }

  /**
   * Sets the cursor value. The value of the cursor is constrained to be within chart bounds.
   * @public
   *
   * @param {number} value
   */
  setCursorValue( value ) {
    const modelViewTransform = this.modelViewTransformProperty.get();
    const minX = modelViewTransform.viewToModelX( 0 );
    const maxX = modelViewTransform.viewToModelX( this.chartWidth + this.chartCursor.width / 2 );
    this.cursorValue = Utils.clamp( value, minX, maxX );
    this.updateCursor();
  }

  /**
   * Gets the value currently under the cursor.
   * @public
   *
   * @returns {number}
   */
  getCursorValue() {
    return this.cursorValue;
  }

  /**
   * Resets the cursor.
   * @public
   */
  resetCursor() {
    this.chartCursor.reset();
  }

  /**
   * Overrides the default behavior for setting cursor visibility. If set to null, cursor visibility will behave as
   * described in updateCursorVisibility. Otherwise, visibility will equal the boolean value set here.
   * @param {boolean|null} visible
   * @public
   */
  setCursorVisibleOverride( visible ) {
    assert && assert( typeof visible === 'boolean' || visible === null, 'visible must be boolean or null' );
    this._cursorVisibleOverride = visible;
    this.updateCursorVisibility();
  }

  /**
   * Updates the cursor visibility and position.
   * @private
   */
  updateCursor() {
    this.updateCursorVisibility();
    if ( this.chartCursor.isVisible() ) {
      this.updateCursorPosition();
    }
  }

  /**
   * Updates the cursor position.
   * @private
   */
  updateCursorPosition() {
    this.moveCursorToValue( this.cursorValue );
  }

  /**
   * Updates the cursor visibility. The cursor should be visible any time the cursor value is within
   * the recorded value range.
   * @private
   */
  updateCursorVisibility() {

    const wasVisible = this.chartCursor.visible;
    if ( typeof this._cursorVisibleOverride === 'boolean' ) {
      this.chartCursor.setVisible( this._cursorVisibleOverride );
    }
    else {

      const maxX = this.modelViewTransformProperty.get().viewToModelX( this.chartWidth + this.chartCursor.width / 2 );
      const minX = this.modelViewTransformProperty.get().viewToModelX( 0 );

      const isCurrentValueOnChart = ( this.cursorValue >= minX ) && ( this.cursorValue <= maxX );
      const hasData = this.hasData();
      const chartCursorVisible = isCurrentValueOnChart && hasData;

      this.chartCursor.setVisible( chartCursorVisible );
    }

    // if the cursor just became invisible, interrupt any active dragging
    if ( !this.chartCursor.visible && wasVisible ) {
      this.chartCursor.interruptDrag();
    }
  }

  /**
   * Returns true if any DynamicSeries associated with this chart has data.
   * @returns {boolean}
   * @public
   */
  hasData() {
    return _.some( this.dynamicSeriesArray, dynamicSeries => dynamicSeries.hasData() );
  }

  /**
   * Moves the cursor to the specified value.
   * @private
   *
   * @param {number} value
   */
  moveCursorToValue( value ) {
    const viewPosition = this.modelViewTransformProperty.get().modelToViewX( value );

    // keep the cursor within the grid bounds
    this.chartCursor.centerX = Utils.clamp( viewPosition, 0, this.chartWidth );
    this.chartCursor.centerY = this.gridNode.centerY;
  }

  /**
   * From the existing data, update the min and max recorded X values from all of the dynamicSeries of this chart,
   * so that the cursor can be limited to the recorded data.
   * @private
   */
  updateMinMaxRecordedValues() {
    let minValue = Number.POSITIVE_INFINITY;
    let maxValue = Number.NEGATIVE_INFINITY;
    this.dynamicSeriesArray.forEach( dynamicSeries => {
      if ( dynamicSeries.getLength() > 0 ) {
        const seriesMinValue = dynamicSeries.getDataPoint( 0 ).x;
        const seriesMaxValue = dynamicSeries.getDataPoint( dynamicSeries.getLength() - 1 ).x;
        if ( seriesMinValue < minValue ) {
          minValue = seriesMinValue;
        }
        if ( seriesMaxValue > maxValue ) {
          maxValue = seriesMaxValue;
        }
      }
    } );

    this.minRecordedXValue = minValue;
    this.maxRecordedXValue = maxValue;
  }
}

/**
 * ChartCursor is a rectangular cursor that indicates the current or selected value on the chart.
 */
class ChartCursor extends Rectangle {

  /**
   * @param {XYCursorChartNode} chart
   * @param {Property.<ModelViewTransform2>} modelViewTransformProperty
   * @param {Object} [options]
   */
  constructor( chart, modelViewTransformProperty, options ) {

    options = merge( {
      startDrag: () => {},
      endDrag: () => {},
      drag: () => {},

      // {boolean} - if true, a double headed arrow will be shown to indicate
      // that the cursor is draggable - becomes invisible after first
      // drag
      includeDragCue: false,

      // phet-io
      tandem: Tandem.OPTIONAL
    }, options );

    const width = chart.chartWidth * WIDTH_PROPORTION;
    const height = chart.chartHeight;

    // Set the shape. Origin is at the center top of the rectangle.
    super( 0, -height, width, height, 0, 0, {
      cursor: 'ew-resize',
      fill: CURSOR_FILL_COLOR,
      stroke: CURSOR_STROKE_COLOR,
      lineWidth: 0.4,
      lineDash: [ 4, 4 ]
    } );

    // @private
    this.chart = chart;

    // @public
    this.modelViewTransformProperty = modelViewTransformProperty;

    // Make it easier to grab this cursor by giving it expanded mouse and touch areas.
    this.mouseArea = this.localBounds.dilatedX( 12 );
    this.touchArea = this.localBounds.dilatedX( 12 );

    // @private
    this.includeDragCue = options.includeDragCue;

    if ( this.includeDragCue ) {

      // @private - indicates to the user that the cursor is draggable, only created
      // and added if necessary
      this.dragCueArrowNode = new ArrowNode( -width * 2, 0, width * 2, 0, {
        doubleHead: true,
        headWidth: 12,
        headHeight: 10,
        fill: ARROW_CUE_FILL_COLOR,
        stroke: ARROW_CUE_STROKE_COLOR,
        center: this.center.plusXY( 0, height * 0.4 )
      } );

      this.addChild( this.dragCueArrowNode );
    }

    // Add the indentations that are intended to convey the idea of "gripability".
    const grippyNode = new Node();
    const indentSpacing = height * 0.05;
    for ( let i = 0; i < 3; i++ ) {
      const indentNode = new GrippyIndentNode( width / 2, CURSOR_FILL_COLOR );
      indentNode.top = i * ( indentNode.height + indentSpacing );
      grippyNode.addChild( indentNode );
    }
    grippyNode.center = this.center;
    this.addChild( grippyNode );

    // @private - so that we can interrupt the DragListener if necessary
    this.dragListener = new DragListener( {
      start: ( event, listener ) => {
        assert && assert( this.chart.hasData(), 'chart should have data for the cursor to be draggable' );
        options.startDrag();
      },
      drag: ( event, listener ) => {
        const parentX = listener.parentPoint.x;
        let newValue = this.modelViewTransformProperty.get().viewToModelX( parentX );

        // limit cursor to the domain of recorded values
        newValue = Utils.clamp( newValue, this.chart.minRecordedXValue, this.chart.maxRecordedXValue );
        this.chart.setCursorValue( newValue );

        options.drag();
      },
      end: () => {
        options.endDrag();

        // no need to show arrow after user successfully drags the cursor
        if ( this.includeDragCue ) {
          this.dragCueArrowNode.visible = false;
        }
      },
      tandem: options.tandem.createTandem( 'dragListener' )
    } );
    this.addInputListener( this.dragListener );
  }

  /**
   * Interrupts dragging of the cursor, useful when ChartCursor visibility changes.
   * @public
   */
  interruptDrag() {
    this.dragListener.interrupt();
  }

  /**
   * Resets the ChartCursor to its initial state. Note that this does not modify data
   * or the cursor position (cursorValue), only aspects of the view for the cursor itself.
   * @public
   */
  reset() {
    if ( this.includeDragCue ) {
      this.dragCueArrowNode.visible = true;
    }
  }
}

/**
 * GrippyIndentNode is a small round indentation on a surface.  This is a modern user interface paradigm that
 * is intended to convey the concept of "gripability", i.e. something that the user can grab.  This is meant to
 * look somewhat 3D, much like etched borders do.
 */
class GrippyIndentNode extends Circle {

  /**
   * @param {number} diameter
   * @param {Color} baseColor
   * @param {Object} [options]
   */
  constructor( diameter, baseColor, options ) {

    options = merge( {
      lineWidth: 0.5
    }, options );

    const baseDarkerColor = baseColor.darkerColor( 0.9 );
    const translucentDarkerColor = new Color( baseDarkerColor.getRed(), baseDarkerColor.getGreen(),
      baseDarkerColor.getBlue(), baseColor.getAlpha() );
    const baseLighterColor = baseColor.brighterColor( 0.9 );
    const translucentBrighterColor = new Color( baseLighterColor.getRed(), baseLighterColor.getGreen(),
      baseLighterColor.getBlue(), baseColor.getAlpha() );
    const radius = diameter / 2 - options.lineWidth;

    super( radius, {
      fill: translucentDarkerColor,
      stroke: translucentBrighterColor,
      lineWidth: options.lineWidth
    } );
  }
}

griddle.register( 'XYCursorChartNode', XYCursorChartNode );
export default XYCursorChartNode;<|endoftext|>


<commit_before>// WARNING:
// The following global constants must be set before instantiation:
//             DEEZER_APP_ID and DEEZER_CHANNEL_URL

window.showMessage = window.showMessage || function(msg) {
  console.warn("[showMessage]", msg);
};

window.$ = window.$ || function(){return window.$};
$.getScript = $.getScript || function(js,cb){loader.includeJS(js,cb);};
$.append = $.append || function(html){document.write(html);};

function DeezerPlayer(){
  return DeezerPlayer.super_.apply(this, arguments);
}

(function(){

  // CONSTANTS
  var SDK_URL = 'https://cdns-files.deezer.com/js/min/dz.js',
      SDK_LOADED = false,
      IS_LOGGED = false,
      URL_REG = /(deezer\.com\/track|\/dz)\/(\d+)/,
      EVENT_MAP = {
        player_play: 'onPlaying',
        player_paused: 'onPaused',
        player_position: 'onTrackInfo'
      };

  //============================================================================
  function Player(eventHandlers) {

    var self = this;

    this.label = 'Deezer';
    this.eventHandlers = eventHandlers || {};
    this.currentTrack = {position: 0, duration: 0};

    loadSDK(function() {
      init(function() {
        //console.log('DeezerPlayer ready');
        //DZ.getLoginStatus = function(cb) {cb && cb({userID: null})}
        DZ.getLoginStatus(function(response) {
          DZ.player.setRepeat(0);
          IS_LOGGED = response.userID;
          self.isReady = true;
          hookHandlers(self);
        });
      });
    });

  }

  //============================================================================
  Player.prototype.isLogged = function() {
    return IS_LOGGED;
  }

  //============================================================================
  Player.prototype.getEid = function(url) {
    return URL_REG.test(url) && RegExp.lastParen;
  }

  //============================================================================
  Player.prototype.play = function(id) {
    var self = this;
    if (IS_LOGGED) {
      DZ.player.playTracks([id], 0);
    } else {
      DZ.api('/track/' + id, function(data) {
        showMessage(
          'This is a 30 secs preview. ' +
          '' +
          'Connect to Deezer to listen to the full track.'
        );
        self.sound = createSound(self, data.preview)
      });
    }
  }

  //============================================================================
  Player.prototype.pause = function() {
    if (this.sound) {
      this.sound.pause();
    } else {
      DZ.player.pause();
    }
  }

  //============================================================================
  Player.prototype.stop = function() {
    if (this.sound) {
      this.sound.stop();
      this.sound.destruct();
      this.sound = null;
    } else {
      DZ.player.pause();
    }
  }

  //============================================================================
  Player.prototype.resume = function() {
    if (this.sound) {
      this.sound.resume();
    } else {
      DZ.player.play();
    }
  }

  //============================================================================
  // pos: seconds
  Player.prototype.setTrackPosition = function(pos) {
    if (this.sound)
      this.sound.setPosition(Math.round(pos * 1000));
    else
      DZ.player.seek(Math.round(100 * pos / this.currentTrack.duration));
  }

  //============================================================================
  // vol: float between 0 and 1
  Player.prototype.setVolume = function(vol) {
    if (this.sound)
      this.sound.setVolume(Math.round(vol * 100));
    else
      DZ.player.setVolume(Math.round(vol * 100));
  }

  //============================================================================
  function loadSDK(cb) {
    if (!SDK_LOADED) {
      //$('body').append('

');
      var dz = document.createElement('div'); dz.id = 'dz-root';
      document.getElementsByTagName("body")[0].appendChild(dz);
      $.getScript(SDK_URL, function() {
        SDK_LOADED = true;
        cb();
      });
    } else {
      cb();
    }
  }

  //============================================================================
  function init(onload) {
    DZ.init({
      appId: DEEZER_APP_ID,
      channelUrl: DEEZER_CHANNEL_URL,
      player: {
        onload: onload
      }
    });
  }

  //============================================================================
  function hookHandlers(self) {

    function createHandler(e) {
      if (e === 'player_position') {
        return function(eventObject) {
          var onTrackInfoHandler = self.eventHandlers.onTrackInfo,
              onEndedHandler = self.eventHandlers.onEnded,
              position = eventObject[0],
              duration = eventObject[1];
          if (onTrackInfoHandler) {
            self.currentTrack = {position: position, duration: duration};
            onTrackInfoHandler(self.currentTrack);
          }
          if ((duration - position <= 1.5) && onEndedHandler)
            onEndedHandler(self);
        };
      }
      return function() {
        var handler = self.eventHandlers[EVENT_MAP[e]];
        handler && handler(self);
      };
    }

    for (var e in EVENT_MAP)
      DZ.Event.suscribe(e, createHandler(e));
    self.eventHandlers.onApiReady && self.eventHandlers.onApiReady(self);
  }

  //============================================================================
  function createSound(self, url) {
    return soundManager.createSound({
      id: 'deezerSound' + Date.now(),
      url: url,
      autoLoad: true,
      autoPlay: true,
      whileplaying: function() {
        self.currentTrack = {
          position: self.sound.position / 1000,
          duration: self.sound.duration / 1000
        };
        if (self.eventHandlers.onTrackInfo)
          self.eventHandlers.onTrackInfo(self.currentTrack);
      },
      onplay: function() {
        if (self.eventHandlers.onPlaying)
          self.eventHandlers.onPlaying(self);
      },
      onresume: function() {
        if (self.eventHandlers.onPlaying)
          self.eventHandlers.onPlaying(self);
      },
      onfinish: function() {
        if (self.eventHandlers.onEnded)
          self.eventHandlers.onEnded(self);
      }
    });
  }

  //============================================================================
  DeezerPlayer.login = function() {
    DZ.login(function(response) {
      if (response.userID) {
        IS_LOGGED = true;
        showMessage('Login successful. Your Deezer tracks will be full length from now on!');
      } else {
        showMessage('Deezer login unsuccesful.', true);
      }
    }, {perms: 'email'});
  }

  //============================================================================
  //return Player;
  //inherits(DeezerPlayer, Player);
  DeezerPlayer.prototype = Player.prototype;
  DeezerPlayer.super_ = Player;
})();
<commit_msg>deezer: enable full https mode<commit_after>// WARNING:
// The following global constants must be set before instantiation:
//             DEEZER_APP_ID and DEEZER_CHANNEL_URL

window.showMessage = window.showMessage || function(msg) {
  console.warn("[showMessage]", msg);
};

window.$ = window.$ || function(){return window.$};
$.getScript = $.getScript || function(js,cb){loader.includeJS(js,cb);};
$.append = $.append || function(html){document.write(html);};

function DeezerPlayer(){
  return DeezerPlayer.super_.apply(this, arguments);
}

(function(){

  // CONSTANTS
  var SDK_URL = 'https://cdns-files.deezer.com/js/min/dz.js',
      SDK_LOADED = false,
      IS_LOGGED = false,
      URL_REG = /(deezer\.com\/track|\/dz)\/(\d+)/,
      EVENT_MAP = {
        player_play: 'onPlaying',
        player_paused: 'onPaused',
        player_position: 'onTrackInfo'
      };

  //============================================================================
  function Player(eventHandlers) {

    var self = this;

    this.label = 'Deezer';
    this.eventHandlers = eventHandlers || {};
    this.currentTrack = {position: 0, duration: 0};

    loadSDK(function() {
      init(function() {
        //console.log('DeezerPlayer ready');
        //DZ.getLoginStatus = function(cb) {cb && cb({userID: null})}
        DZ.getLoginStatus(function(response) {
          DZ.player.setRepeat(0);
          IS_LOGGED = response.userID;
          self.isReady = true;
          hookHandlers(self);
        });
      });
    });

  }

  //============================================================================
  Player.prototype.isLogged = function() {
    return IS_LOGGED;
  }

  //============================================================================
  Player.prototype.getEid = function(url) {
    return URL_REG.test(url) && RegExp.lastParen;
  }

  //============================================================================
  Player.prototype.play = function(id) {
    var self = this;
    if (IS_LOGGED) {
      DZ.player.playTracks([id], 0);
    } else {
      DZ.api('/track/' + id, function(data) {
        showMessage(
          'This is a 30 secs preview. ' +
          '' +
          'Connect to Deezer to listen to the full track.'
        );
        self.sound = createSound(self, data.preview)
      });
    }
  }

  //============================================================================
  Player.prototype.pause = function() {
    if (this.sound) {
      this.sound.pause();
    } else {
      DZ.player.pause();
    }
  }

  //============================================================================
  Player.prototype.stop = function() {
    if (this.sound) {
      this.sound.stop();
      this.sound.destruct();
      this.sound = null;
    } else {
      DZ.player.pause();
    }
  }

  //============================================================================
  Player.prototype.resume = function() {
    if (this.sound) {
      this.sound.resume();
    } else {
      DZ.player.play();
    }
  }

  //============================================================================
  // pos: seconds
  Player.prototype.setTrackPosition = function(pos) {
    if (this.sound)
      this.sound.setPosition(Math.round(pos * 1000));
    else
      DZ.player.seek(Math.round(100 * pos / this.currentTrack.duration));
  }

  //============================================================================
  // vol: float between 0 and 1
  Player.prototype.setVolume = function(vol) {
    if (this.sound)
      this.sound.setVolume(Math.round(vol * 100));
    else
      DZ.player.setVolume(Math.round(vol * 100));
  }

  //============================================================================
  function loadSDK(cb) {
    if (!SDK_LOADED) {
      //$('body').append('

');
      var dz = document.createElement('div'); dz.id = 'dz-root';
      document.getElementsByTagName("body")[0].appendChild(dz);
      $.getScript(SDK_URL, function() {
        SDK_LOADED = true;
        cb();
      });
    } else {
      cb();
    }
  }

  //============================================================================
  function init(onload) {
    DZ.init({
      appId: DEEZER_APP_ID,
      channelUrl: DEEZER_CHANNEL_URL,
      player: {
        onload: onload
      }
    });
    if (window.location.protocol === "https:")
      DZ.override_https();
  }

  //============================================================================
  function hookHandlers(self) {

    function createHandler(e) {
      if (e === 'player_position') {
        return function(eventObject) {
          var onTrackInfoHandler = self.eventHandlers.onTrackInfo,
              onEndedHandler = self.eventHandlers.onEnded,
              position = eventObject[0],
              duration = eventObject[1];
          if (onTrackInfoHandler) {
            self.currentTrack = {position: position, duration: duration};
            onTrackInfoHandler(self.currentTrack);
          }
          if ((duration - position <= 1.5) && onEndedHandler)
            onEndedHandler(self);
        };
      }
      return function() {
        var handler = self.eventHandlers[EVENT_MAP[e]];
        handler && handler(self);
      };
    }

    for (var e in EVENT_MAP)
      DZ.Event.suscribe(e, createHandler(e));
    self.eventHandlers.onApiReady && self.eventHandlers.onApiReady(self);
  }

  //============================================================================
  function createSound(self, url) {
    return soundManager.createSound({
      id: 'deezerSound' + Date.now(),
      url: url,
      autoLoad: true,
      autoPlay: true,
      whileplaying: function() {
        self.currentTrack = {
          position: self.sound.position / 1000,
          duration: self.sound.duration / 1000
        };
        if (self.eventHandlers.onTrackInfo)
          self.eventHandlers.onTrackInfo(self.currentTrack);
      },
      onplay: function() {
        if (self.eventHandlers.onPlaying)
          self.eventHandlers.onPlaying(self);
      },
      onresume: function() {
        if (self.eventHandlers.onPlaying)
          self.eventHandlers.onPlaying(self);
      },
      onfinish: function() {
        if (self.eventHandlers.onEnded)
          self.eventHandlers.onEnded(self);
      }
    });
  }

  //============================================================================
  DeezerPlayer.login = function() {
    DZ.login(function(response) {
      if (response.userID) {
        IS_LOGGED = true;
        showMessage('Login successful. Your Deezer tracks will be full length from now on!');
      } else {
        showMessage('Deezer login unsuccesful.', true);
      }
    }, {perms: 'email'});
  }

  //============================================================================
  //return Player;
  //inherits(DeezerPlayer, Player);
  DeezerPlayer.prototype = Player.prototype;
  DeezerPlayer.super_ = Player;
})();
<|endoftext|>