<commit_before>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import HTML5VideoPlayback from 'playbacks/html5_video'
import HLSJS from 'hls.js'
import Events from 'base/events'
import Playback from 'base/playback'
import Browser from 'components/browser'
import Log from 'plugins/log'

const AUTO = -1

export default class HLS extends HTML5VideoPlayback {
  get name() { return 'hls' }

  get levels() { return this._levels || [] }
  get currentLevel() {
    if (this._currentLevel === null || this._currentLevel === undefined) {
      return AUTO;
    } else {
      return this._currentLevel; //0 is a valid level ID
    }
  }
  set currentLevel(id) {
    this._currentLevel = id
    this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
    this.hls.currentLevel = this._currentLevel
  }

  constructor(options) {
    super(options)
    this.minDvrSize = (options.hlsMinimumDvrSize === undefined) ? 60 : options.hlsMinimumDvrSize
    this.playbackType = Playback.VOD
    // for hls streams which have dvr with a sliding window,
    // the content at the start of the playlist is removed as new
    // content is appended at the end.
    // this means the actual playable start time will increase as the
    // start content is deleted
    // For streams with dvr where the entire recording is kept from the
    // beginning this should stay as 0
    this.playableRegionStartTime = 0
    // if content is removed from the beginning then this empty area should
    // be ignored. "playableRegionDuration" does not consider this
    this.playableRegionDuration = 0
    // true when the actual duration is longer than hlsjs's live sync point
    // when this is false playableRegionDuration will be the actual duration
    // when this is true playableRegionDuration will exclude the time after the sync point
    this.durationExcludesAfterLiveSyncPoint = false
    options.autoPlay && this.setupHls()
    this.recoverAttemptsRemaining = options.hlsRecoverAttempts || 16
  }

  setupHls() {
    this.hls = new HLSJS(this.options.hlsjsConfig || {})
    this.hls.on(HLSJS.Events.MEDIA_ATTACHED, () => this.hls.loadSource(this.options.src))
    this.hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this.updatePlaybackType(evt, data))
    this.hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this.onLevelUpdated(evt, data))
    this.hls.on(HLSJS.Events.LEVEL_SWITCH, (evt,data) => this.onLevelSwitch(evt, data))
    this.hls.on(HLSJS.Events.FRAG_LOADED, (evt, data) => this.onFragmentLoaded(evt, data))
    this.hls.on(HLSJS.Events.ERROR, (evt, data) => this.onError(evt, data))
    this.hls.attachMedia(this.el)
  }

  recover() {
    if (!this._recoveredDecodingError) {
      this._recoveredDecodingError = true
      this.hls.recoverMediaError()
    } else if (!this._recoveredAudioCodecError) {
      this._recoveredAudioCodecError = true
      this.hls.swapAudioCodec()
      this.hls.recoverMediaError()
    } else {
      Log.error("hlsjs: failed to recover")
      this.trigger(Events.PLAYBACK_ERROR, `hlsjs: could not recover from error, evt ${evt}, data ${data} `, this.name)
    }
  }

  // override
  setupSrc(srcUrl) {
    // this playback manages the src on the video element itself
  }

  // the duration on the video element itself should not be used
  // as this does not necesarily represent the duration of the stream
  // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
  getDuration() {
    return this.playableRegionDuration
  }

  getCurrentTime() {
    return this.el.currentTime - this.playableRegionStartTime
  }

  // the time that "0" now represents relative to when playback started
  // for a stream with a sliding window this will increase as content is
  // removed from the beginning
  getStartTimeOffset() {
    return this.playableRegionStartTime
  }

  seekPercentage(percentage) {
    var seekTo = this.playableRegionDuration
    if (percentage > 0) {
      seekTo = this.playableRegionDuration * (percentage / 100)
    }
    this.seek(seekTo)
  }

  seek(time) {
    if (time < 0) {
      Log.warn("Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.")
      time = this.getDuration()
    }
    // assume live if time within 3 seconds of end of stream
    this.dvrEnabled && this.updateDvr(time < this.getDuration()-3)
    time += this.playableRegionStartTime
    super.seek(time)
  }

  seekToLivePoint() {
    this.seek(this.getDuration())
  }

  updateDvr(status) {
    this.trigger(Events.PLAYBACK_DVR, status)
    this.trigger(Events.PLAYBACK_STATS_ADD, {'dvr': status})
  }

  updateSettings() {
    if (this.playbackType === Playback.VOD) {
      this.settings.left = ["playpause", "position", "duration"]
    } else if (this.dvrEnabled) {
      this.settings.left = ["playpause"]
    } else {
      this.settings.left = ["playstop"]
    }
    this.settings.seekEnabled = this.isSeekEnabled()
    this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
  }

  onError(evt, data) {
    if (data && data.fatal && this.recoverAttemptsRemaining > 0) {
      this.recoverAttemptsRemaining -= 1
      switch (data.type) {
        case HLSJS.ErrorTypes.NETWORK_ERROR:
          Log.warn(`hlsjs: trying to recover from network error, evt ${evt}, data ${data} `)
          this.hls.startLoad()
          break
        case HLSJS.ErrorTypes.MEDIA_ERROR:
          Log.warn(`hlsjs: trying to recover from media error, evt ${evt}, data ${data} `)
          this.recover()
          break
        default:
          Log.error(`hlsjs: trying to recover from media error, evt ${evt}, data ${data} `)
          this.trigger(Events.PLAYBACK_ERROR, `hlsjs: could not recover from error, evt ${evt}, data ${data} `, this.name)
          break
      }
    }
  }

  onTimeUpdate() {
    this.trigger(Events.PLAYBACK_TIMEUPDATE, {current: this.getCurrentTime(), total: this.getDuration()}, this.name)
  }

  onProgress() {
    if (!this.el.buffered.length) {
      return
    }
    var bufferedPos = 0
    for (var i = 0; i < this.el.buffered.length; i++) {
      if (this.el.currentTime >= this.el.buffered.start(i) && this.el.currentTime <= this.el.buffered.end(i)) {
        bufferedPos = i
        break
      }
    }
    this.trigger(Events.PLAYBACK_PROGRESS, {
      // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
      start: Math.max(0, this.el.buffered.start(bufferedPos) - this.playableRegionStartTime),
      current: Math.max(0, this.el.buffered.end(bufferedPos) - this.playableRegionStartTime),
      total: this.getDuration()
    })
  }

  play() {
    if (!this.hls) {
      this.setupHls()
    }
    super.play()
  }

  pause() {
    super.pause()
    if (this.dvrEnabled) {
      this.updateDvr(true)
    }
  }

  stop() {
    super.stop()
    if (this.hls) {
      this.hls.destroy()
      delete this.hls
    }
  }

  updatePlaybackType(evt, data) {
    this.playbackType = data.details.live ? Playback.LIVE : Playback.VOD
    this.fillLevels()
    this.onLevelUpdated(evt, data)
  }

  fillLevels() {
    this._levels = this.hls.levels.map((level, index) => {
      return {id: index, level: level, label: `${level.bitrate/1000}Kbps`
    }})
    this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
  }

  onLevelUpdated(evt, data) {
    var startTimeChanged = false
    var durationChanged = false
    var fragments = data.details.fragments
    if (fragments.length > 0 && this.playableRegionStartTime !== fragments[0].start) {
      startTimeChanged = true
      this.playableRegionStartTime = fragments[0].start
    }
    var newDuration = data.details.totalduration
    // if it's a live stream then shorten the duration to remove access
    // to the area after hlsjs's live sync point
    // seeks to areas after this point sometimes have issues
    if (this.playbackType === Playback.LIVE) {
      let currentLevel = this.hls.levels[data.level]
      let fragmentTargetDuration = currentLevel.details.targetduration
      let hlsjsConfig = this.options.hlsjsConfig || {}
      let liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount
      let hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
      if (hiddenAreaDuration <= newDuration) {
        newDuration -= hiddenAreaDuration
        this.durationExcludesAfterLiveSyncPoint = true
      }
      else {
        this.durationExcludesAfterLiveSyncPoint = false
      }
    }

    if (newDuration !== this.playableRegionDuration) {
      durationChanged = true
      this.playableRegionDuration = newDuration
    }

    // now that the values have been updated call any methods that use on them so they get the updated values
    // immediately
    durationChanged && this.onDurationChange()
    startTimeChanged && this.onProgress()
  }

  onFragmentLoaded(evt, data) {
    this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
  }

  onLevelSwitch(evt, data) {
    if (!this.levels.length) {
      this.fillLevels()
    }
    this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
    this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
    var currentLevel = this.hls.levels[data.level]
    if (currentLevel) {
      this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
      this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
      this.trigger(Events.PLAYBACK_BITRATE, {
        height: currentLevel.height,
        width: currentLevel.width,
        bandwidth: currentLevel.bandwidth,
        bitrate: currentLevel.bitrate,
        level: data.level
      })
    }
  }

  get dvrEnabled() {
    // enabled when:
    // - the duration does not include content after hlsjs's live sync point
    // - the playable region duration is longer than the configured duration to enable dvr after
    // - the playback type is LIVE.
    return (this.durationExcludesAfterLiveSyncPoint && this.playableRegionDuration >= this.minDvrSize && this.getPlaybackType() === Playback.LIVE)
  }

  getPlaybackType() {
    return this.playbackType
  }

  isSeekEnabled() {
    return (this.playbackType === Playback.VOD || this.dvrEnabled)
  }
}

HLS.canPlay = function(resource, mimeType) {
  var resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
  var isHls = ((resourceParts.length > 1 && resourceParts[1] === "m3u8") ||
        mimeType === 'application/x-mpegURL' || mimeType === 'application/vnd.apple.mpegurl')

  return !!(HLSJS.isSupported() && isHls && !Browser.isSafari)
}
<commit_msg>hls: trigger error event properly after reaching maximum number of retries<commit_after>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

import HTML5VideoPlayback from 'playbacks/html5_video'
import HLSJS from 'hls.js'
import Events from 'base/events'
import Playback from 'base/playback'
import Browser from 'components/browser'
import Log from 'plugins/log'

const AUTO = -1

export default class HLS extends HTML5VideoPlayback {
  get name() { return 'hls' }

  get levels() { return this._levels || [] }
  get currentLevel() {
    if (this._currentLevel === null || this._currentLevel === undefined) {
      return AUTO;
    } else {
      return this._currentLevel; //0 is a valid level ID
    }
  }
  set currentLevel(id) {
    this._currentLevel = id
    this.trigger(Events.PLAYBACK_LEVEL_SWITCH_START)
    this.hls.currentLevel = this._currentLevel
  }

  constructor(options) {
    super(options)
    this.minDvrSize = (options.hlsMinimumDvrSize === undefined) ? 60 : options.hlsMinimumDvrSize
    this.playbackType = Playback.VOD
    // for hls streams which have dvr with a sliding window,
    // the content at the start of the playlist is removed as new
    // content is appended at the end.
    // this means the actual playable start time will increase as the
    // start content is deleted
    // For streams with dvr where the entire recording is kept from the
    // beginning this should stay as 0
    this.playableRegionStartTime = 0
    // if content is removed from the beginning then this empty area should
    // be ignored. "playableRegionDuration" does not consider this
    this.playableRegionDuration = 0
    // true when the actual duration is longer than hlsjs's live sync point
    // when this is false playableRegionDuration will be the actual duration
    // when this is true playableRegionDuration will exclude the time after the sync point
    this.durationExcludesAfterLiveSyncPoint = false
    options.autoPlay && this.setupHls()
    this.recoverAttemptsRemaining = options.hlsRecoverAttempts || 16
  }

  setupHls() {
    this.hls = new HLSJS(this.options.hlsjsConfig || {})
    this.hls.on(HLSJS.Events.MEDIA_ATTACHED, () => this.hls.loadSource(this.options.src))
    this.hls.on(HLSJS.Events.LEVEL_LOADED, (evt, data) => this.updatePlaybackType(evt, data))
    this.hls.on(HLSJS.Events.LEVEL_UPDATED, (evt, data) => this.onLevelUpdated(evt, data))
    this.hls.on(HLSJS.Events.LEVEL_SWITCH, (evt,data) => this.onLevelSwitch(evt, data))
    this.hls.on(HLSJS.Events.FRAG_LOADED, (evt, data) => this.onFragmentLoaded(evt, data))
    this.hls.on(HLSJS.Events.ERROR, (evt, data) => this.onError(evt, data))
    this.hls.attachMedia(this.el)
  }

  recover() {
    if (!this._recoveredDecodingError) {
      this._recoveredDecodingError = true
      this.hls.recoverMediaError()
    } else if (!this._recoveredAudioCodecError) {
      this._recoveredAudioCodecError = true
      this.hls.swapAudioCodec()
      this.hls.recoverMediaError()
    } else {
      Log.error("hlsjs: failed to recover")
      this.trigger(Events.PLAYBACK_ERROR, `hlsjs: could not recover from error, evt ${evt}, data ${data} `, this.name)
    }
  }

  // override
  setupSrc(srcUrl) {
    // this playback manages the src on the video element itself
  }

  // the duration on the video element itself should not be used
  // as this does not necesarily represent the duration of the stream
  // https://github.com/clappr/clappr/issues/668#issuecomment-157036678
  getDuration() {
    return this.playableRegionDuration
  }

  getCurrentTime() {
    return this.el.currentTime - this.playableRegionStartTime
  }

  // the time that "0" now represents relative to when playback started
  // for a stream with a sliding window this will increase as content is
  // removed from the beginning
  getStartTimeOffset() {
    return this.playableRegionStartTime
  }

  seekPercentage(percentage) {
    var seekTo = this.playableRegionDuration
    if (percentage > 0) {
      seekTo = this.playableRegionDuration * (percentage / 100)
    }
    this.seek(seekTo)
  }

  seek(time) {
    if (time < 0) {
      Log.warn("Attempt to seek to a negative time. Resetting to live point. Use seekToLivePoint() to seek to the live point.")
      time = this.getDuration()
    }
    // assume live if time within 3 seconds of end of stream
    this.dvrEnabled && this.updateDvr(time < this.getDuration()-3)
    time += this.playableRegionStartTime
    super.seek(time)
  }

  seekToLivePoint() {
    this.seek(this.getDuration())
  }

  updateDvr(status) {
    this.trigger(Events.PLAYBACK_DVR, status)
    this.trigger(Events.PLAYBACK_STATS_ADD, {'dvr': status})
  }

  updateSettings() {
    if (this.playbackType === Playback.VOD) {
      this.settings.left = ["playpause", "position", "duration"]
    } else if (this.dvrEnabled) {
      this.settings.left = ["playpause"]
    } else {
      this.settings.left = ["playstop"]
    }
    this.settings.seekEnabled = this.isSeekEnabled()
    this.trigger(Events.PLAYBACK_SETTINGSUPDATE)
  }

  onError(evt, data) {
    if (data && data.fatal && this.recoverAttemptsRemaining > 0) {
      this.recoverAttemptsRemaining -= 1
      switch (data.type) {
        case HLSJS.ErrorTypes.NETWORK_ERROR:
          Log.warn(`hlsjs: trying to recover from network error, evt ${evt}, data ${data} `)
          this.hls.startLoad()
          break
        case HLSJS.ErrorTypes.MEDIA_ERROR:
          Log.warn(`hlsjs: trying to recover from media error, evt ${evt}, data ${data} `)
          this.recover()
          break
        default:
          Log.error(`hlsjs: trying to recover from media error, evt ${evt}, data ${data} `)
          this.trigger(Events.PLAYBACK_ERROR, `hlsjs: could not recover from error, evt ${evt}, data ${data} `, this.name)
          break
      }
    } else {
      Log.error(`hlsjs: could not recover from error after maximum number of attempts, evt ${evt}, data ${data} `)
      this.trigger(Events.PLAYBACK_ERROR, {evt, data}, this.name)
    }
  }

  onTimeUpdate() {
    this.trigger(Events.PLAYBACK_TIMEUPDATE, {current: this.getCurrentTime(), total: this.getDuration()}, this.name)
  }

  onProgress() {
    if (!this.el.buffered.length) {
      return
    }
    var bufferedPos = 0
    for (var i = 0; i < this.el.buffered.length; i++) {
      if (this.el.currentTime >= this.el.buffered.start(i) && this.el.currentTime <= this.el.buffered.end(i)) {
        bufferedPos = i
        break
      }
    }
    this.trigger(Events.PLAYBACK_PROGRESS, {
      // for a stream with sliding window dvr something that is buffered my slide off the start of the timeline
      start: Math.max(0, this.el.buffered.start(bufferedPos) - this.playableRegionStartTime),
      current: Math.max(0, this.el.buffered.end(bufferedPos) - this.playableRegionStartTime),
      total: this.getDuration()
    })
  }

  play() {
    if (!this.hls) {
      this.setupHls()
    }
    super.play()
  }

  pause() {
    super.pause()
    if (this.dvrEnabled) {
      this.updateDvr(true)
    }
  }

  stop() {
    super.stop()
    if (this.hls) {
      this.hls.destroy()
      delete this.hls
    }
  }

  updatePlaybackType(evt, data) {
    this.playbackType = data.details.live ? Playback.LIVE : Playback.VOD
    this.fillLevels()
    this.onLevelUpdated(evt, data)
  }

  fillLevels() {
    this._levels = this.hls.levels.map((level, index) => {
      return {id: index, level: level, label: `${level.bitrate/1000}Kbps`
    }})
    this.trigger(Events.PLAYBACK_LEVELS_AVAILABLE, this._levels)
  }

  onLevelUpdated(evt, data) {
    var startTimeChanged = false
    var durationChanged = false
    var fragments = data.details.fragments
    if (fragments.length > 0 && this.playableRegionStartTime !== fragments[0].start) {
      startTimeChanged = true
      this.playableRegionStartTime = fragments[0].start
    }
    var newDuration = data.details.totalduration
    // if it's a live stream then shorten the duration to remove access
    // to the area after hlsjs's live sync point
    // seeks to areas after this point sometimes have issues
    if (this.playbackType === Playback.LIVE) {
      let currentLevel = this.hls.levels[data.level]
      let fragmentTargetDuration = currentLevel.details.targetduration
      let hlsjsConfig = this.options.hlsjsConfig || {}
      let liveSyncDurationCount = hlsjsConfig.liveSyncDurationCount || HLSJS.DefaultConfig.liveSyncDurationCount
      let hiddenAreaDuration = fragmentTargetDuration * liveSyncDurationCount
      if (hiddenAreaDuration <= newDuration) {
        newDuration -= hiddenAreaDuration
        this.durationExcludesAfterLiveSyncPoint = true
      }
      else {
        this.durationExcludesAfterLiveSyncPoint = false
      }
    }

    if (newDuration !== this.playableRegionDuration) {
      durationChanged = true
      this.playableRegionDuration = newDuration
    }

    // now that the values have been updated call any methods that use on them so they get the updated values
    // immediately
    durationChanged && this.onDurationChange()
    startTimeChanged && this.onProgress()
  }

  onFragmentLoaded(evt, data) {
    this.trigger(Events.PLAYBACK_FRAGMENT_LOADED, data)
  }

  onLevelSwitch(evt, data) {
    if (!this.levels.length) {
      this.fillLevels()
    }
    this.trigger(Events.PLAYBACK_LEVEL_SWITCH_END)
    this.trigger(Events.PLAYBACK_LEVEL_SWITCH, data)
    var currentLevel = this.hls.levels[data.level]
    if (currentLevel) {
      this.highDefinition = (currentLevel.height >= 720 || (currentLevel.bitrate / 1000) >= 2000);
      this.trigger(Events.PLAYBACK_HIGHDEFINITIONUPDATE, this.highDefinition)
      this.trigger(Events.PLAYBACK_BITRATE, {
        height: currentLevel.height,
        width: currentLevel.width,
        bandwidth: currentLevel.bandwidth,
        bitrate: currentLevel.bitrate,
        level: data.level
      })
    }
  }

  get dvrEnabled() {
    // enabled when:
    // - the duration does not include content after hlsjs's live sync point
    // - the playable region duration is longer than the configured duration to enable dvr after
    // - the playback type is LIVE.
    return (this.durationExcludesAfterLiveSyncPoint && this.playableRegionDuration >= this.minDvrSize && this.getPlaybackType() === Playback.LIVE)
  }

  getPlaybackType() {
    return this.playbackType
  }

  isSeekEnabled() {
    return (this.playbackType === Playback.VOD || this.dvrEnabled)
  }
}

HLS.canPlay = function(resource, mimeType) {
  var resourceParts = resource.split('?')[0].match(/.*\.(.*)$/) || []
  var isHls = ((resourceParts.length > 1 && resourceParts[1] === "m3u8") ||
        mimeType === 'application/x-mpegURL' || mimeType === 'application/vnd.apple.mpegurl')

  return !!(HLSJS.isSupported() && isHls && !Browser.isSafari)
}
<|endoftext|>


<commit_before>/*
 * View model for OctoPrint-Slicer
 *
 * Author: Kenneth Jiang
 * License: AGPLv3
 */
$(function() {
    ko.bindingHandlers.numericValue = {
        init : function(element, valueAccessor, allBindings, data, context) {
            var interceptor = ko.computed({
                read: function() {
                    return ko.unwrap(valueAccessor());
                },
                write: function(value) {
                    if (!isNaN(value)) {
                        valueAccessor()(parseFloat(value));
                    }
                },
                disposeWhenNodeIsRemoved: element
            });

            ko.applyBindingsToNode(element, { value: interceptor }, context);
        }
    };

    function BasicOverridesViewModel(parameters) {
        var ARRAY_KEYS = ["print_temperature"],
            ENUM_KEYS = { "support" : ko.observableArray(["none", "buildplate", "everywhere"])},
            ITEM_KEYS = ["layer_height",
                        "print_bed_temperature",
                        "fill_density",
                        "wall_thickness",
                        "print_speed",
                        "solid_layer_thickness",
                        "support"];

        var ALL_KEYS = ITEM_KEYS.concat(ARRAY_KEYS).concat(Object.keys(ENUM_KEYS));

        var self = this;

        self.slicingViewModel = parameters[0];

        // initialize all observables
        _.forEach(ALL_KEYS, function(k) { self["profile." + k] = ko.observable(); });

        self.optionsForKey = function(key) {
            return ENUM_KEYS[key];
        };

        self.updateOverridesFromProfile = function(profile) {
            _.forEach(ITEM_KEYS, function(k) { self["profile." + k]( profile[k] ); });
            _.forEach(ENUM_KEYS, function(v, k) { self["profile." + k]( profile[k] ); });
            _.forEach(ARRAY_KEYS, function(k) { self["profile." + k]( profile[k][0] ); });
        };

        self.updateOverrides = function(newValue) {
            var slicing = self.slicingViewModel;

            if ( slicing.profile() && slicing.slicer() ) {
				$.ajax({
					url: API_BASEURL + "slicing/" + slicing.slicer() + "/profiles/" + slicing.profile(),
					type: "GET",
					// On success
					success: function(data) {
                        self.updateOverridesFromProfile(data.data);
                    }
                });
            }
        };

        self.slicingViewModel.profile.subscribe( self.updateOverrides );
        self.slicingViewModel.slicer.subscribe( self.updateOverrides );

        self.toJS = function() {
            var result = ko.mapping.toJS(self, {
                ignore: ["slicingViewModel",
                         "updateOverridesFromProfile",
                         "updateOverrides",
                         "toJS"]
            });

            for (var key in result) {
                if (_.contains(ARRAY_KEYS, key.replace("profile.", ""))) {
                    result[key] = [result[key]];
                }
            }
            return result;
        }
    }

    // view model class, parameters for constructor, container to bind to
    OCTOPRINT_VIEWMODELS.push([
        BasicOverridesViewModel,
        [ "slicingViewModel" ],
        [ "#basic_overrides" ]
    ]);
});
<commit_msg>Extract superclass for slicing overrides<commit_after>/*
 * View model for OctoPrint-Slicer
 *
 * Author: Kenneth Jiang
 * License: AGPLv3
 */
$(function() {
    ko.bindingHandlers.numericValue = {
        init : function(element, valueAccessor, allBindings, data, context) {
            var interceptor = ko.computed({
                read: function() {
                    return ko.unwrap(valueAccessor());
                },
                write: function(value) {
                    if (!isNaN(value)) {
                        valueAccessor()(parseFloat(value));
                    }
                },
                disposeWhenNodeIsRemoved: element
            });

            ko.applyBindingsToNode(element, { value: interceptor }, context);
        }
    };

    function OverridesViewModel(parameters, array_keys, enum_keys, item_keys) {
        var self = this;
        self.slicingViewModel = parameters[0];

        var ARRAY_KEYS = (typeof array_keys === 'undefined') ? [] : array_keys,
            ENUM_KEYS = (typeof enum_keys === 'undefined') ? {} : enum_keys,
            ITEM_KEYS = (typeof item_keys === 'undefined') ? [] : item_keys;
        var ALL_KEYS = ITEM_KEYS.concat(ARRAY_KEYS).concat(Object.keys(ENUM_KEYS));

        // initialize all observables
        _.forEach(ALL_KEYS, function(k) { self["profile." + k] = ko.observable(); });

        self.optionsForKey = function(key) {
            return ENUM_KEYS[key];
        };

        self.updateOverridesFromProfile = function(profile) {
            _.forEach(ITEM_KEYS, function(k) { self["profile." + k]( profile[k] ); });
            _.forEach(ENUM_KEYS, function(v, k) { self["profile." + k]( profile[k] ); });
            _.forEach(ARRAY_KEYS, function(k) { self["profile." + k]( profile[k][0] ); });
        };

        self.updateOverrides = function(newValue) {
            var slicing = self.slicingViewModel;

            if ( slicing.profile() && slicing.slicer() ) {
				$.ajax({
					url: API_BASEURL + "slicing/" + slicing.slicer() + "/profiles/" + slicing.profile(),
					type: "GET",
					// On success
					success: function(data) {
                        self.updateOverridesFromProfile(data.data);
                    }
                });
            }
        };

        self.slicingViewModel.profile.subscribe( self.updateOverrides );
        self.slicingViewModel.slicer.subscribe( self.updateOverrides );

        self.toJS = function() {
            var result = ko.mapping.toJS(self, {
                ignore: ["slicingViewModel",
                         "updateOverridesFromProfile",
                         "updateOverrides",
                         "toJS",
                         "optionsForKey"]
            });

            for (var key in result) {
                if (_.contains(ARRAY_KEYS, key.replace("profile.", ""))) {
                    result[key] = [result[key]];
                }
            }
            return result;
        };
    }

    function BasicOverridesViewModel(parameters) {
        OverridesViewModel.call(this, parameters,
                            ["print_temperature"],
                            { "support" : ko.observableArray(["none", "buildplate", "everywhere"])},
                            ["layer_height",
                            "print_bed_temperature",
                            "fill_density",
                            "wall_thickness",
                            "print_speed",
                            "solid_layer_thickness",
                            "support"]);
    }

    // view model class, parameters for constructor, container to bind to
    OCTOPRINT_VIEWMODELS.push([
        BasicOverridesViewModel,
        [ "slicingViewModel" ],
        [ "#basic_overrides" ]
    ]);
});
<|endoftext|>


<commit_before>import load from 'load-script';

export default () => {
  /**
   * A promise that is resolved when window.onYouTubeIframeAPIReady is called.
   * The promise is resolved with a reference to window.YT object.
   *
   * @param {Function} resolve
   * @member {Object} iframeAPIReady
   */
  const iframeAPIReady = new Promise((resolve) => {
    const previous = window.onYouTubeIframeAPIReady;

    // The API will call this function when page has finished downloading
    // the JavaScript for the player API.
    window.onYouTubeIframeAPIReady = () => {
      if (previous) {
        previous();
      }

      resolve(window.YT);
    };
  });
  const protocol = window.location.protocol === 'https:' ? 'https:' : 'http:';

  load(protocol + '//www.youtube.com/iframe_api');

  return iframeAPIReady;
};
<commit_msg>fix: (fixes issue #15) (#27)<commit_after>import load from 'load-script';

export default () => {
  /**
   * A promise that is resolved when window.onYouTubeIframeAPIReady is called.
   * The promise is resolved with a reference to window.YT object.
   *
   * @param {Function} resolve
   * @member {Object} iframeAPIReady
   */
  const iframeAPIReady = new Promise((resolve) => {
    const previous = window.onYouTubeIframeAPIReady;

    // The API will call this function when page has finished downloading
    // the JavaScript for the player API.
    window.onYouTubeIframeAPIReady = () => {
      if (previous) {
        previous();
      }

      resolve(window.YT);
    };
  });
  const protocol = window.location.protocol === 'http:' ? 'http:' : 'https:';

  load(protocol + '//www.youtube.com/iframe_api');

  return iframeAPIReady;
};
<|endoftext|>


<commit_before>/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var mouseChange = require('mouse-change');
var mouseWheel = require('mouse-wheel');
var mouseOffset = require('mouse-event-offset');
var cartesianConstants = require('../cartesian/constants');

module.exports = createCamera;

function Camera2D(element, plot) {
    this.element = element;
    this.plot = plot;
    this.mouseListener = null;
    this.wheelListener = null;
    this.lastInputTime = Date.now();
    this.lastPos = [0, 0];
    this.boxEnabled = false;
    this.boxInited = false;
    this.boxStart = [0, 0];
    this.boxEnd = [0, 0];
    this.dragStart = [0, 0];
}

function createCamera(scene) {
    var element = scene.mouseContainer,
        plot = scene.glplot,
        result = new Camera2D(element, plot);

    function unSetAutoRange() {
        scene.xaxis.autorange = false;
        scene.yaxis.autorange = false;
    }

    function getSubplotConstraint() {
        // note: this assumes we only have one x and one y axis on this subplot
        // when this constraint is lifted this block won't make sense
        var constraints = scene.graphDiv._fullLayout._axisConstraintGroups;
        var xaId = scene.xaxis._id;
        var yaId = scene.yaxis._id;
        for(var i = 0; i < constraints.length; i++) {
            if(constraints[i][xaId] !== -1) {
                if(constraints[i][yaId] !== -1) return true;
                break;
            }
        }
        return false;
    }

    result.mouseListener = mouseChange(element, handleInteraction);

    // enable simple touch interactions
    element.addEventListener('touchstart', function(ev) {
        var xy = mouseOffset(ev.changedTouches[0], element);
        handleInteraction(0, xy[0], xy[1]);
        handleInteraction(1, xy[0], xy[1]);
    });
    element.addEventListener('touchmove', function(ev) {
        ev.preventDefault()
        var xy = mouseOffset(ev.changedTouches[0], element);
        handleInteraction(1, xy[0], xy[1]);
    });
    element.addEventListener('touchend', function() {
        handleInteraction(0, result.lastPos[0], result.lastPos[1]);
    });

    function handleInteraction(buttons, x, y) {
        var dataBox = scene.calcDataBox(),
            viewBox = plot.viewBox;

        var lastX = result.lastPos[0],
            lastY = result.lastPos[1];

        var MINDRAG = cartesianConstants.MINDRAG * plot.pixelRatio;
        var MINZOOM = cartesianConstants.MINZOOM * plot.pixelRatio;

        var dx, dy;

        x *= plot.pixelRatio;
        y *= plot.pixelRatio;

        // mouseChange gives y about top; convert to about bottom
        y = (viewBox[3] - viewBox[1]) - y;

        function updateRange(i0, start, end) {
            var range0 = Math.min(start, end),
                range1 = Math.max(start, end);

            if(range0 !== range1) {
                dataBox[i0] = range0;
                dataBox[i0 + 2] = range1;
                result.dataBox = dataBox;
                scene.setRanges(dataBox);
            }
            else {
                scene.selectBox.selectBox = [0, 0, 1, 1];
                scene.glplot.setDirty();
            }
        }

        switch(scene.fullLayout.dragmode) {
            case 'zoom':
                if(buttons) {
                    var dataX = x /
                            (viewBox[2] - viewBox[0]) * (dataBox[2] - dataBox[0]) +
                        dataBox[0];
                    var dataY = y /
                            (viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) +
                        dataBox[1];

                    if(!result.boxInited) {
                        result.boxStart[0] = dataX;
                        result.boxStart[1] = dataY;
                        result.dragStart[0] = x;
                        result.dragStart[1] = y;
                    }

                    result.boxEnd[0] = dataX;
                    result.boxEnd[1] = dataY;

                    // we need to mark the box as initialized right away
                    // so that we can tell the start and end points apart
                    result.boxInited = true;

                    // but don't actually enable the box until the cursor moves
                    if(!result.boxEnabled && (
                        result.boxStart[0] !== result.boxEnd[0] ||
                        result.boxStart[1] !== result.boxEnd[1])
                    ) {
                        result.boxEnabled = true;
                    }

                    // constrain aspect ratio if the axes require it
                    var smallDx = Math.abs(result.dragStart[0] - x) < MINZOOM;
                    var smallDy = Math.abs(result.dragStart[1] - y) < MINZOOM;
                    if(getSubplotConstraint() && !(smallDx && smallDy)) {
                        dx = result.boxEnd[0] - result.boxStart[0];
                        dy = result.boxEnd[1] - result.boxStart[1];
                        var dydx = (dataBox[3] - dataBox[1]) / (dataBox[2] - dataBox[0]);

                        if(Math.abs(dx * dydx) > Math.abs(dy)) {
                            result.boxEnd[1] = result.boxStart[1] +
                                Math.abs(dx) * dydx * (dy >= 0 ? 1 : -1);

                            // gl-select-box clips to the plot area bounds,
                            // which breaks the axis constraint, so don't allow
                            // this box to go out of bounds
                            if(result.boxEnd[1] < dataBox[1]) {
                                result.boxEnd[1] = dataBox[1];
                                result.boxEnd[0] = result.boxStart[0] +
                                    (dataBox[1] - result.boxStart[1]) / Math.abs(dydx);
                            }
                            else if(result.boxEnd[1] > dataBox[3]) {
                                result.boxEnd[1] = dataBox[3];
                                result.boxEnd[0] = result.boxStart[0] +
                                    (dataBox[3] - result.boxStart[1]) / Math.abs(dydx);
                            }
                        }
                        else {
                            result.boxEnd[0] = result.boxStart[0] +
                                Math.abs(dy) / dydx * (dx >= 0 ? 1 : -1);

                            if(result.boxEnd[0] < dataBox[0]) {
                                result.boxEnd[0] = dataBox[0];
                                result.boxEnd[1] = result.boxStart[1] +
                                    (dataBox[0] - result.boxStart[0]) * Math.abs(dydx);
                            }
                            else if(result.boxEnd[0] > dataBox[2]) {
                                result.boxEnd[0] = dataBox[2];
                                result.boxEnd[1] = result.boxStart[1] +
                                    (dataBox[2] - result.boxStart[0]) * Math.abs(dydx);
                            }
                        }
                    }
                    // otherwise clamp small changes to the origin so we get 1D zoom
                    else {
                        if(smallDx) result.boxEnd[0] = result.boxStart[0];
                        if(smallDy) result.boxEnd[1] = result.boxStart[1];
                    }
                }
                else if(result.boxEnabled) {
                    dx = result.boxStart[0] !== result.boxEnd[0];
                    dy = result.boxStart[1] !== result.boxEnd[1];
                    if(dx || dy) {
                        if(dx) {
                            updateRange(0, result.boxStart[0], result.boxEnd[0]);
                            scene.xaxis.autorange = false;
                        }
                        if(dy) {
                            updateRange(1, result.boxStart[1], result.boxEnd[1]);
                            scene.yaxis.autorange = false;
                        }
                        scene.relayoutCallback();
                    }
                    else {
                        scene.glplot.setDirty();
                    }
                    result.boxEnabled = false;
                    result.boxInited = false;
                }
                // if box was inited but button released then - reset the box
                else if(result.boxInited) {
                    result.boxInited = false;
                }
                break;

            case 'pan':
                result.boxEnabled = false;
                result.boxInited = false;

                if(buttons) {
                    if(!result.panning) {
                        result.dragStart[0] = x;
                        result.dragStart[1] = y;
                    }

                    if(Math.abs(result.dragStart[0] - x) < MINDRAG) x = result.dragStart[0];
                    if(Math.abs(result.dragStart[1] - y) < MINDRAG) y = result.dragStart[1];

                    dx = (lastX - x) * (dataBox[2] - dataBox[0]) /
                        (plot.viewBox[2] - plot.viewBox[0]);
                    dy = (lastY - y) * (dataBox[3] - dataBox[1]) /
                        (plot.viewBox[3] - plot.viewBox[1]);

                    dataBox[0] += dx;
                    dataBox[2] += dx;
                    dataBox[1] += dy;
                    dataBox[3] += dy;

                    scene.setRanges(dataBox);

                    result.panning = true;
                    result.lastInputTime = Date.now();
                    unSetAutoRange();
                    scene.cameraChanged();
                    scene.handleAnnotations();
                }
                else if(result.panning) {
                    result.panning = false;
                    scene.relayoutCallback();
                }
                break;
        }

        result.lastPos[0] = x;
        result.lastPos[1] = y;
    }

    result.wheelListener = mouseWheel(element, function(dx, dy) {
        var dataBox = scene.calcDataBox(),
            viewBox = plot.viewBox;

        var lastX = result.lastPos[0],
            lastY = result.lastPos[1];

        switch(scene.fullLayout.dragmode) {
            case 'zoom':
                break;

            case 'pan':
                var scale = Math.exp(0.1 * dy / (viewBox[3] - viewBox[1]));

                var cx = lastX /
                        (viewBox[2] - viewBox[0]) * (dataBox[2] - dataBox[0]) +
                    dataBox[0];
                var cy = lastY /
                        (viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) +
                    dataBox[1];

                dataBox[0] = (dataBox[0] - cx) * scale + cx;
                dataBox[2] = (dataBox[2] - cx) * scale + cx;
                dataBox[1] = (dataBox[1] - cy) * scale + cy;
                dataBox[3] = (dataBox[3] - cy) * scale + cy;

                scene.setRanges(dataBox);

                result.lastInputTime = Date.now();
                unSetAutoRange();
                scene.cameraChanged();
                scene.handleAnnotations();
                scene.relayoutCallback();
                break;
        }

        return true;
    });

    return result;
}
<commit_msg>Add semicolon<commit_after>/**
* Copyright 2012-2017, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var mouseChange = require('mouse-change');
var mouseWheel = require('mouse-wheel');
var mouseOffset = require('mouse-event-offset');
var cartesianConstants = require('../cartesian/constants');

module.exports = createCamera;

function Camera2D(element, plot) {
    this.element = element;
    this.plot = plot;
    this.mouseListener = null;
    this.wheelListener = null;
    this.lastInputTime = Date.now();
    this.lastPos = [0, 0];
    this.boxEnabled = false;
    this.boxInited = false;
    this.boxStart = [0, 0];
    this.boxEnd = [0, 0];
    this.dragStart = [0, 0];
}

function createCamera(scene) {
    var element = scene.mouseContainer,
        plot = scene.glplot,
        result = new Camera2D(element, plot);

    function unSetAutoRange() {
        scene.xaxis.autorange = false;
        scene.yaxis.autorange = false;
    }

    function getSubplotConstraint() {
        // note: this assumes we only have one x and one y axis on this subplot
        // when this constraint is lifted this block won't make sense
        var constraints = scene.graphDiv._fullLayout._axisConstraintGroups;
        var xaId = scene.xaxis._id;
        var yaId = scene.yaxis._id;
        for(var i = 0; i < constraints.length; i++) {
            if(constraints[i][xaId] !== -1) {
                if(constraints[i][yaId] !== -1) return true;
                break;
            }
        }
        return false;
    }

    result.mouseListener = mouseChange(element, handleInteraction);

    // enable simple touch interactions
    element.addEventListener('touchstart', function(ev) {
        var xy = mouseOffset(ev.changedTouches[0], element);
        handleInteraction(0, xy[0], xy[1]);
        handleInteraction(1, xy[0], xy[1]);
    });
    element.addEventListener('touchmove', function(ev) {
        ev.preventDefault();
        var xy = mouseOffset(ev.changedTouches[0], element);
        handleInteraction(1, xy[0], xy[1]);
    });
    element.addEventListener('touchend', function() {
        handleInteraction(0, result.lastPos[0], result.lastPos[1]);
    });

    function handleInteraction(buttons, x, y) {
        var dataBox = scene.calcDataBox(),
            viewBox = plot.viewBox;

        var lastX = result.lastPos[0],
            lastY = result.lastPos[1];

        var MINDRAG = cartesianConstants.MINDRAG * plot.pixelRatio;
        var MINZOOM = cartesianConstants.MINZOOM * plot.pixelRatio;

        var dx, dy;

        x *= plot.pixelRatio;
        y *= plot.pixelRatio;

        // mouseChange gives y about top; convert to about bottom
        y = (viewBox[3] - viewBox[1]) - y;

        function updateRange(i0, start, end) {
            var range0 = Math.min(start, end),
                range1 = Math.max(start, end);

            if(range0 !== range1) {
                dataBox[i0] = range0;
                dataBox[i0 + 2] = range1;
                result.dataBox = dataBox;
                scene.setRanges(dataBox);
            }
            else {
                scene.selectBox.selectBox = [0, 0, 1, 1];
                scene.glplot.setDirty();
            }
        }

        switch(scene.fullLayout.dragmode) {
            case 'zoom':
                if(buttons) {
                    var dataX = x /
                            (viewBox[2] - viewBox[0]) * (dataBox[2] - dataBox[0]) +
                        dataBox[0];
                    var dataY = y /
                            (viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) +
                        dataBox[1];

                    if(!result.boxInited) {
                        result.boxStart[0] = dataX;
                        result.boxStart[1] = dataY;
                        result.dragStart[0] = x;
                        result.dragStart[1] = y;
                    }

                    result.boxEnd[0] = dataX;
                    result.boxEnd[1] = dataY;

                    // we need to mark the box as initialized right away
                    // so that we can tell the start and end points apart
                    result.boxInited = true;

                    // but don't actually enable the box until the cursor moves
                    if(!result.boxEnabled && (
                        result.boxStart[0] !== result.boxEnd[0] ||
                        result.boxStart[1] !== result.boxEnd[1])
                    ) {
                        result.boxEnabled = true;
                    }

                    // constrain aspect ratio if the axes require it
                    var smallDx = Math.abs(result.dragStart[0] - x) < MINZOOM;
                    var smallDy = Math.abs(result.dragStart[1] - y) < MINZOOM;
                    if(getSubplotConstraint() && !(smallDx && smallDy)) {
                        dx = result.boxEnd[0] - result.boxStart[0];
                        dy = result.boxEnd[1] - result.boxStart[1];
                        var dydx = (dataBox[3] - dataBox[1]) / (dataBox[2] - dataBox[0]);

                        if(Math.abs(dx * dydx) > Math.abs(dy)) {
                            result.boxEnd[1] = result.boxStart[1] +
                                Math.abs(dx) * dydx * (dy >= 0 ? 1 : -1);

                            // gl-select-box clips to the plot area bounds,
                            // which breaks the axis constraint, so don't allow
                            // this box to go out of bounds
                            if(result.boxEnd[1] < dataBox[1]) {
                                result.boxEnd[1] = dataBox[1];
                                result.boxEnd[0] = result.boxStart[0] +
                                    (dataBox[1] - result.boxStart[1]) / Math.abs(dydx);
                            }
                            else if(result.boxEnd[1] > dataBox[3]) {
                                result.boxEnd[1] = dataBox[3];
                                result.boxEnd[0] = result.boxStart[0] +
                                    (dataBox[3] - result.boxStart[1]) / Math.abs(dydx);
                            }
                        }
                        else {
                            result.boxEnd[0] = result.boxStart[0] +
                                Math.abs(dy) / dydx * (dx >= 0 ? 1 : -1);

                            if(result.boxEnd[0] < dataBox[0]) {
                                result.boxEnd[0] = dataBox[0];
                                result.boxEnd[1] = result.boxStart[1] +
                                    (dataBox[0] - result.boxStart[0]) * Math.abs(dydx);
                            }
                            else if(result.boxEnd[0] > dataBox[2]) {
                                result.boxEnd[0] = dataBox[2];
                                result.boxEnd[1] = result.boxStart[1] +
                                    (dataBox[2] - result.boxStart[0]) * Math.abs(dydx);
                            }
                        }
                    }
                    // otherwise clamp small changes to the origin so we get 1D zoom
                    else {
                        if(smallDx) result.boxEnd[0] = result.boxStart[0];
                        if(smallDy) result.boxEnd[1] = result.boxStart[1];
                    }
                }
                else if(result.boxEnabled) {
                    dx = result.boxStart[0] !== result.boxEnd[0];
                    dy = result.boxStart[1] !== result.boxEnd[1];
                    if(dx || dy) {
                        if(dx) {
                            updateRange(0, result.boxStart[0], result.boxEnd[0]);
                            scene.xaxis.autorange = false;
                        }
                        if(dy) {
                            updateRange(1, result.boxStart[1], result.boxEnd[1]);
                            scene.yaxis.autorange = false;
                        }
                        scene.relayoutCallback();
                    }
                    else {
                        scene.glplot.setDirty();
                    }
                    result.boxEnabled = false;
                    result.boxInited = false;
                }
                // if box was inited but button released then - reset the box
                else if(result.boxInited) {
                    result.boxInited = false;
                }
                break;

            case 'pan':
                result.boxEnabled = false;
                result.boxInited = false;

                if(buttons) {
                    if(!result.panning) {
                        result.dragStart[0] = x;
                        result.dragStart[1] = y;
                    }

                    if(Math.abs(result.dragStart[0] - x) < MINDRAG) x = result.dragStart[0];
                    if(Math.abs(result.dragStart[1] - y) < MINDRAG) y = result.dragStart[1];

                    dx = (lastX - x) * (dataBox[2] - dataBox[0]) /
                        (plot.viewBox[2] - plot.viewBox[0]);
                    dy = (lastY - y) * (dataBox[3] - dataBox[1]) /
                        (plot.viewBox[3] - plot.viewBox[1]);

                    dataBox[0] += dx;
                    dataBox[2] += dx;
                    dataBox[1] += dy;
                    dataBox[3] += dy;

                    scene.setRanges(dataBox);

                    result.panning = true;
                    result.lastInputTime = Date.now();
                    unSetAutoRange();
                    scene.cameraChanged();
                    scene.handleAnnotations();
                }
                else if(result.panning) {
                    result.panning = false;
                    scene.relayoutCallback();
                }
                break;
        }

        result.lastPos[0] = x;
        result.lastPos[1] = y;
    }

    result.wheelListener = mouseWheel(element, function(dx, dy) {
        var dataBox = scene.calcDataBox(),
            viewBox = plot.viewBox;

        var lastX = result.lastPos[0],
            lastY = result.lastPos[1];

        switch(scene.fullLayout.dragmode) {
            case 'zoom':
                break;

            case 'pan':
                var scale = Math.exp(0.1 * dy / (viewBox[3] - viewBox[1]));

                var cx = lastX /
                        (viewBox[2] - viewBox[0]) * (dataBox[2] - dataBox[0]) +
                    dataBox[0];
                var cy = lastY /
                        (viewBox[3] - viewBox[1]) * (dataBox[3] - dataBox[1]) +
                    dataBox[1];

                dataBox[0] = (dataBox[0] - cx) * scale + cx;
                dataBox[2] = (dataBox[2] - cx) * scale + cx;
                dataBox[1] = (dataBox[1] - cy) * scale + cy;
                dataBox[3] = (dataBox[3] - cy) * scale + cy;

                scene.setRanges(dataBox);

                result.lastInputTime = Date.now();
                unSetAutoRange();
                scene.cameraChanged();
                scene.handleAnnotations();
                scene.relayoutCallback();
                break;
        }

        return true;
    });

    return result;
}
<|endoftext|>


<commit_before>
// Setup depends on: -
// Runtime depends on: console.js, helpers.js, server.js

// API:
// st.communication.setup()
// st.communication.connect()
// st.communication.send(data)
// st.communication.sendSynchronously(data)
// st.communication.disconnect()
// st.communication.handleMessage(content, status)

// Settings:
// st.communication.PREFER_WS (boolean)
// st.communication.WEBSOCKET_PATH (string)
// st.communication.XHR_PATH (string)
// st.communication.MESSAGE_HANDLER (function(string))

(function() {

	// Set up the namespace
	if (!window.st) window.st = {};
	var home = st.communication ? st.communication : (st.communication = {});

	//
	// Settings
	//

	if (!home.PREFER_WS) home.PREFER_WS = false;
	if (!home.WEBSOCKET_PATH) home.WEBSOCKET_PATH = "ws";
	if (!home.XHR_PATH) home.XHR_PATH = "xhr";
	if (!home.MESSAGE_HANDLER) home.MESSAGE_HANDLER = function(message) { st.console.log("Received message: " + message); };

	//
	// Local variables
	//

	var connectionHandler;
	var synchronousRequest;
	var identifier = st.getRandomInt(0, 4294967296);

	//
	// API functions
	//

	// re-read the PREFER_WS-flag
	home.setup = function() {
		connectionHandler = useWs() ? createWsHandler() : createCometHandler();
	};

	home.connect = function() {
		if (!connectionHandler.isOpen())
			connectionHandler.open();
	};

	home.send = function(data) {
		if (connectionHandler.isOpen())
			return connectionHandler.send(data);
	};

	home.sendSynchronously = function(data, urlPath) {
		if (connectionHandler.isOpen())
			return connectionHandler.sendSynchronously(data, urlPath);
	};

	home.disconnect = function() {
		if (synchronousRequest)
			closeRequest(synchronousRequest);
		if (connectionHandler.isOpen())
			connectionHandler.close();
	};

	// Use the configured message-handler to evaluate and log the content
	home.handleMessage = function(content, status) {
		var result;
		try {
			result = home.MESSAGE_HANDLER(content);
		} catch (e) {
			st.console.log("Error handling the content of a server-message: " + e +
			"\rMessage was: " + content);
		}
		st.console.statusInfo(content, status);
		return result;
	};

	//
	// Private functions
	//

	var sendSynchronouslyImpl = function(data, url) {
		if (data) {
			synchronousRequest = createXmlRequest();
			synchronousRequest.open("POST", fullURL(url), false);
			synchronousRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			synchronousRequest.send(data);
			var result = synchronousRequest.responseText;
			synchronousRequest = null;
			return result;
		}
	}

	var useWs = function() {
		return home.PREFER_WS && ("WebSocket" in window);
	};

	var createXmlRequest = function() {
		return new XMLHttpRequest();
	};

	var closeRequest = function(request) {
		// TODO this is not pretty: this abort()-call always results in
		// a "Failed to load resource" in the browser...
		request.abort();
	};

	var fullURL = function(urlPath) {
		var baseUrl = document.location.href;
		if (!(/\/$/.test(baseUrl))){
			baseUrl = baseUrl + "/";
		}
		return baseUrl + urlPath + "?id=" + identifier;
	};

	var createCometHandler = function() {
		var request = null;

		return {
			open: function() {
				var optionalArgument = '';
				if (arguments[0] !== undefined) {
					optionalArgument = "&answer=" + home.realEscape(arguments[0]);
				}
				request = createXmlRequest();
				request.open("GET", fullURL(home.XHR_PATH) + optionalArgument , true);
				request.onreadystatechange = function() {
					if (request.readyState == 4) {
						if (request.status == 200) {
							var answer = home.handleMessage(request.responseText, request.status);
							connectionHandler.open(answer);
						} else {
							st.console.statusInfo("Disconnected Comet: " + request.responseText, request.status);
						}
					}
				};
				request.send(null);
			},
			send: function(data) {
				return connectionHandler.sendSynchronously(data, home.XHR_PATH);
			},
			sendSynchronously: function(data, url) {
				// connectionHandler.close();
				var result = sendSynchronouslyImpl(data, url);
				// connectionHandler.open();
				return result;
			},
			close: function() {
				closeRequest(request);
				request = null;
			},
			isOpen: function() {
				return request ? true : false;
			}
		};
	};

	var createWsHandler = function() {
		var webSocket = null;

		return {
			open: function() {
				webSocket = new WebSocket("ws://" + fullURL(home.WEBSOCKET_PATH).split("//")[1]);
				webSocket.onopen = function(event) {
					st.console.log("Successfully opened WebSocket: " + event);
				};
				webSocket.onerror = function(event) {
					st.console.log("WebSocket failed: " + event);
				};
				webSocket.onmessage = function(event) {
					home.handleMessage(event.data, 200);
				};
				webSocket.onclose = function() {
					st.console.log("WebSocket received close event.");
				};
			},
			send: function(data) {
				webSocket.send(message);
				st.console.statusInfo(message, 200);
			},
			sendSynchronously: sendSynchronouslyImpl,
			close: function() {
				webSocket.close();
				webSocket = null;
				st.console.log("WebSocket closed by client.");
			},
			isOpen: function() {
				return webSocket;
			}
		};
	};

	// Once setup the default connectionHandler-object
	home.setup();

})();
<commit_msg>WebSocket's answering on newOnClient works! :)<commit_after>
// Setup depends on: -
// Runtime depends on: console.js, helpers.js, server.js

// API:
// st.communication.setup()
// st.communication.connect()
// st.communication.send(data)
// st.communication.sendSynchronously(data)
// st.communication.disconnect()
// st.communication.handleMessage(content, status)

// Settings:
// st.communication.PREFER_WS (boolean)
// st.communication.WEBSOCKET_PATH (string)
// st.communication.XHR_PATH (string)
// st.communication.MESSAGE_HANDLER (function(string))

(function() {

	// Set up the namespace
	if (!window.st) window.st = {};
	var home = st.communication ? st.communication : (st.communication = {});

	//
	// Settings
	//

	if (!home.PREFER_WS) home.PREFER_WS = true;
	if (!home.WEBSOCKET_PATH) home.WEBSOCKET_PATH = "ws";
	if (!home.XHR_PATH) home.XHR_PATH = "xhr";
	if (!home.MESSAGE_HANDLER) home.MESSAGE_HANDLER = function(message) { st.console.log("Received message: " + message); };

	//
	// Local variables
	//

	var connectionHandler;
	var synchronousRequest;
	var identifier = st.getRandomInt(0, 4294967296);

	//
	// API functions
	//

	// re-read the PREFER_WS-flag
	home.setup = function() {
		connectionHandler = useWs() ? createWsHandler() : createCometHandler();
	};

	home.connect = function() {
		if (!connectionHandler.isOpen())
			connectionHandler.open();
	};

	home.send = function(data) {
		if (connectionHandler.isOpen())
			return connectionHandler.send(data);
	};

	home.sendSynchronously = function(data, urlPath) {
		if (connectionHandler.isOpen())
			return connectionHandler.sendSynchronously(data, urlPath);
	};

	home.disconnect = function() {
		if (synchronousRequest)
			closeRequest(synchronousRequest);
		if (connectionHandler.isOpen())
			connectionHandler.close();
	};

	// Use the configured message-handler to evaluate and log the content
	home.handleMessage = function(content, status) {
		var result;
		try {
			result = home.MESSAGE_HANDLER(content);
		} catch (e) {
			st.console.log("Error handling the content of a server-message: " + e +
			"\rMessage was: " + content);
		}
		st.console.statusInfo(content, status);
		return result;
	};

	//
	// Private functions
	//

	var sendSynchronouslyImpl = function(data, url) {
		if (data) {
			synchronousRequest = createXmlRequest();
			synchronousRequest.open("POST", fullURL(url), false);
			synchronousRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			synchronousRequest.send(data);
			var result = synchronousRequest.responseText;
			synchronousRequest = null;
			return result;
		}
	}

	var useWs = function() {
		return home.PREFER_WS && ("WebSocket" in window);
	};

	var createXmlRequest = function() {
		return new XMLHttpRequest();
	};

	var closeRequest = function(request) {
		// TODO this is not pretty: this abort()-call always results in
		// a "Failed to load resource" in the browser...
		request.abort();
	};

	var fullURL = function(urlPath) {
		var baseUrl = document.location.href;
		if (!(/\/$/.test(baseUrl))){
			baseUrl = baseUrl + "/";
		}
		return baseUrl + urlPath + "?id=" + identifier;
	};

	var createCometHandler = function() {
		var request = null;

		return {
			open: function() {
				var optionalArgument = '';
				if (arguments[0] !== undefined) {
					optionalArgument = "&answer=" + home.realEscape(arguments[0]);
				}
				request = createXmlRequest();
				request.open("GET", fullURL(home.XHR_PATH) + optionalArgument , true);
				request.onreadystatechange = function() {
					if (request.readyState == 4) {
						if (request.status == 200) {
							var answer = home.handleMessage(request.responseText, request.status);
							connectionHandler.open(answer);
						} else {
							st.console.statusInfo("Disconnected Comet: " + request.responseText, request.status);
						}
					}
				};
				request.send(null);
			},
			send: function(data) {
				return connectionHandler.sendSynchronously(data, home.XHR_PATH);
			},
			sendSynchronously: function(data, url) {
				var result = sendSynchronouslyImpl(data, url);
				return result;
			},
			close: function() {
				closeRequest(request);
				request = null;
			},
			isOpen: function() {
				return request ? true : false;
			}
		};
	};

	var createWsHandler = function() {
		var webSocket = null;

		return {
			open: function() {
				webSocket = new WebSocket("ws://" + fullURL(home.WEBSOCKET_PATH).split("//")[1]);
				webSocket.onopen = function(event) {
					st.console.log("Successfully opened WebSocket: " + event);
				};
				webSocket.onerror = function(event) {
					st.console.log("WebSocket failed: " + event);
				};
				webSocket.onmessage = function(event) {
					var answer = home.handleMessage(event.data, 200);
					if (answer !== undefined){
						webSocket.send(answer);
					}
				};
				webSocket.onclose = function() {
					st.console.log("WebSocket received close event.");
				};
			},
			send: function(data) {
				webSocket.send(message);
				st.console.statusInfo(message, 200);
			},
			sendSynchronously: sendSynchronouslyImpl,
			close: function() {
				webSocket.close();
				webSocket = null;
				st.console.log("WebSocket closed by client.");
			},
			isOpen: function() {
				return webSocket;
			}
		};
	};

	// Once setup the default connectionHandler-object
	home.setup();

})();
<|endoftext|>