<commit_before>function ScrubberView() {
  this.makeAccessors();
  this.createDOM();
  this.attachListeners();
  this.onValueChanged = function () {};
}

ScrubberView.prototype.makeAccessors = function () {
  var value = 0;
  var min = 0;
  var max = 1;

  this.value = function (_value) {
    if (_value === undefined) return value;
    if (value === _value) return this;
    value = _value;
    this.redraw();
    this.onValueChanged(_value);
    return this;
  };

  this.min = function (_min) {
    if (_min === undefined) return min;
    if (min === _min) return this;
    min = _min;
    this.redraw();
    return this;
  };

  this.max = function (_max) {
    if (_max === undefined) return max;
    if (max === _max) return this;
    max = _max;
    this.redraw();
    return this;
  };
};

ScrubberView.prototype.createDOM = function () {
  this.elt = document.createElement('div');
  this.track = document.createElement('div');
  this.thumb = document.createElement('div');

  this.elt.className = 'scrubber';
  this.track.className = 'track';
  this.thumb.className = 'thumb';

  this.elt.appendChild(this.track);
  this.elt.appendChild(this.thumb);
};

ScrubberView.prototype.redraw = function () {
  var frac = (this.value() - this.min())/(this.max() - this.min());
  this.thumb.style.left = frac*100 + '%';
};

ScrubberView.prototype.attachListeners = function ()  {
  var self = this;
  var mousedown = false;
  var cachedLeft;
  var cachedWidth;

  var start = function () {
    mousedown = true;
    cachedLeft = self.elt.offsetLeft;
    cachedWidth = self.elt.offsetWidth;
  };

  var stop = function () {
    mousedown = false;
    cachedLeft = undefined;
    cachedWidth = undefined;
  };

  var setValueFromPageX = function (pageX) {
    var frac = Math.min(1, Math.max((pageX - cachedLeft)/cachedWidth, 0));
    self.value((1-frac)*self.min() + frac*self.max());
  };

  this.elt.addEventListener('mousedown', start);
  this.elt.addEventListener('touchstart', start);

  document.addEventListener('mousemove', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.pageX);
  });

  document.addEventListener('touchmove', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.changedTouches[0].pageX);
  });

  this.elt.addEventListener('mouseup', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.pageX);
  });

  this.elt.addEventListener('touchend', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.changedTouches[0].pageX);
  });

  document.addEventListener('mouseup', stop);
  document.addEventListener('touchend', stop);
};
<commit_msg>Add ability to set step value; set drag opacity<commit_after>function ScrubberView() {
  this.makeAccessors();
  this.createDOM();
  this.attachListeners();
  this.onValueChanged = function () {};
}

ScrubberView.prototype.makeAccessors = function () {
  var value = 0;
  var min = 0;
  var max = 1;
	var step = 0;

  this.value = function (_value) {
    if (_value === undefined) return value;
    if (value === _value) return this;

		if (step > 0) {
			var remainder = (_value - min) % step;
			value = _value - remainder;
			if (Math.abs(remainder) * 2 >= step) {
				value += (remainder > 0) ? step : (-step);
			}
		} else {
			value = _value;
		}

    this.redraw();
    this.onValueChanged(value);
    return this;
  };

  this.min = function (_min) {
    if (_min === undefined) return min;
    if (min === _min) return this;
    min = _min;
    this.redraw();
    return this;
  };

  this.max = function (_max) {
    if (_max === undefined) return max;
    if (max === _max) return this;
    max = _max;
    this.redraw();
    return this;
  };

	this.step = function (_step) {
		if (_step === undefined) return step;
		if (step === _step) return this;
		step = _step;
		this.redraw();
		return this;
	};

};

ScrubberView.prototype.createDOM = function () {
  this.elt = document.createElement('div');
  this.track = document.createElement('div');
  this.thumb = document.createElement('div');

  this.elt.className = 'scrubber';
  this.track.className = 'track';
  this.thumb.className = 'thumb';

  this.elt.appendChild(this.track);
  this.elt.appendChild(this.thumb);
};

ScrubberView.prototype.redraw = function () {
  var frac = (this.value() - this.min())/(this.max() - this.min());
  this.thumb.style.left = frac*100 + '%';
};

ScrubberView.prototype.attachListeners = function ()  {
  var self = this;
  var mousedown = false;
  var cachedLeft;
  var cachedWidth;

  var start = function () {
    mousedown = true;
    cachedLeft = self.elt.offsetLeft;
    cachedWidth = self.elt.offsetWidth;
		self.thumb.style.opacity = 1;
		self.thumb.style.borderWidth = 0;
  };

  var stop = function () {
    mousedown = false;
    cachedLeft = undefined;
    cachedWidth = undefined;
		self.thumb.style.opacity = 0.7;
		self.thumb.style.borderWidth = '8px';
  };

  var setValueFromPageX = function (pageX) {
    var frac = Math.min(1, Math.max((pageX - cachedLeft)/cachedWidth, 0));
    self.value((1-frac)*self.min() + frac*self.max());
  };

  this.elt.addEventListener('mousedown', start);
  this.elt.addEventListener('touchstart', start);

  document.addEventListener('mousemove', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.pageX);
  });

  document.addEventListener('touchmove', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.changedTouches[0].pageX);
  });

  this.elt.addEventListener('mouseup', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.pageX);
  });

  this.elt.addEventListener('touchend', function (evt) {
    if (!mousedown) return;
    evt.preventDefault();
    setValueFromPageX(evt.changedTouches[0].pageX);
  });

  document.addEventListener('mouseup', stop);
  document.addEventListener('touchend', stop);
};
<|endoftext|>


<commit_before>/* =============================================================
 * bootstrap-formcontrol.js v0.1
 * https://github.com/pickypg/bootstrap-formvalidation/edit/master/js/bootstrap-formcontrol.js
 * =============================================================
 * Copyright 2013 Chris Earle
 *
 * 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.
 * ============================================================ */
!(function($) {

  "use strict"; // jshint ;_;

  /* FORMCONTROL PUBLIC CLASS DEFINITION
   * =================================== */
  var FormControl = function (element, options) {
    this.invalid = []
    this.$element = $(element)
    this.options = $.extend({}, $.fn.formControl.defaults, options)
    this.name = this.options.name || this.$element.data('name') || this.$element.attr('id')
    this.ready = this.options.ready || this.ready
    this.add = this.options.add || this.add
    this.remove = this.options.remove || this.remove
    this.validated = this.options.validated || this.validated
    this.updater = this.options.updater || this.updater
    this.namer = this.options.namer || this.namer
    this.add($('[data-control="' + this.name + '"]'))
  }

  FormControl.prototype = {

    constructor: FormControl

  , add: function ($controls) {
      $controls.on('validated.formControl', $.proxy(this.validated, this))
    }

  , remove: function ($controls) {
      var control = this
      $controls.off('validated.formControl').each(function() {
        var name = control.namer($(this))
          , index = $.inArray(name, control.invalid)

        if (index > -1) control.invalid.splice(index, 1)
      })
    }

  , validated: function (e) {
      var ready = this.ready()
        , name = this.namer(e.validation.$element)
        , index = $.inArray(name, this.invalid)
        , invalid = index > -1

      if (e.valid === invalid) {
        if (e.valid && invalid) this.invalid.splice(index, 1)
        else if (!e.valid && !invalid) this.invalid.push(name)

        if (ready !== this.ready()) this.updater(!ready)
      }
    }

  , updater: function (ready) {
      // setting both attr and data because JQuery does not pick up 'data'
      //
      this.$element
        .toggleClass('success', ready)
        .toggleClass('error', !ready)
        .attr('data-ready', ready).data('ready', ready)
        .trigger('validated.ready', {control: this, ready: ready})
    }

  , namer: function ($element) {
      return $element.attr('name') || $element.attr('id')
    }

  , ready: function () {
      return this.invalid.length === 0
    }
  }

  /* FORMCONTROL PLUGIN DEFINITION
   * ============================= */
  var old = $.fn.formControl

  $.fn.formControl = function (option) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('formControl')
        , options = typeof option == 'object' && option
      if (!data) {
        $this.data('formControl', (data = new FormControl(this, options)))
      }
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.formControl.defaults = {}

  $.fn.formControl.Constructor = FormControl

  /* FORMCONTROL NO CONFLICT
   * ======================= */
  $.fn.formControl.noConflict = function () {
    $.fn.formControl = old
    return this
  }

  /* FORMCONTROL DATA-API
   * ==================== */
  $(document).on('focus.formControl.data-api', '[data-provide="formControl"]', function (e) {
    var $this = $(this)
    if ($this.data('formControl')) return
    $this.formControl($this.data())
  })

})(window.jQuery);
<commit_msg>bringing back comment<commit_after>/* =============================================================
 * bootstrap-formcontrol.js v0.1
 * https://github.com/pickypg/bootstrap-formvalidation/edit/master/js/bootstrap-formcontrol.js
 * =============================================================
 * Copyright 2013 Chris Earle
 *
 * 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.
 * ============================================================ */
!(function($) {

  "use strict"; // jshint ;_;

  /* FORMCONTROL PUBLIC CLASS DEFINITION
   * =================================== */
  var FormControl = function (element, options) {
    this.invalid = []
    this.$element = $(element)
    this.options = $.extend({}, $.fn.formControl.defaults, options)
    this.name = this.options.name || this.$element.data('name') || this.$element.attr('id')
    this.ready = this.options.ready || this.ready
    this.add = this.options.add || this.add
    this.remove = this.options.remove || this.remove
    this.validated = this.options.validated || this.validated
    this.updater = this.options.updater || this.updater
    this.namer = this.options.namer || this.namer
    this.add($('[data-control="' + this.name + '"]'))
  }

  FormControl.prototype = {

    constructor: FormControl

  , add: function ($controls) {
      $controls.on('validated.formControl', $.proxy(this.validated, this))
    }

  , remove: function ($controls) {
      var that = this
      $controls.off('validated.formControl').each(function() {
        var name = that.namer($(this))
          , index = $.inArray(name, that.invalid)

        if (index > -1) that.invalid.splice(index, 1)
      })
    }

  , validated: function (e) {
      var ready = this.ready()
        , name = this.namer(e.validation.$element)
        , index = $.inArray(name, this.invalid)
        , invalid = index > -1

      if (e.valid === invalid) {
        if (e.valid && invalid) this.invalid.splice(index, 1)
        else if (!e.valid && !invalid) this.invalid.push(name)

        if (ready !== this.ready()) this.updater(!ready)
      }
    }

  , updater: function (ready) {
      // setting both attr and data because JQuery does not pick up 'data'
      //  from attributes that get set by attr after adding to the DOM
      this.$element
        .toggleClass('success', ready)
        .toggleClass('error', !ready)
        .attr('data-ready', ready).data('ready', ready)
        .trigger('validated.ready', {control: this, ready: ready})
    }

  , namer: function ($element) {
      return $element.attr('name') || $element.attr('id')
    }

  , ready: function () {
      return this.invalid.length === 0
    }
  }

  /* FORMCONTROL PLUGIN DEFINITION
   * ============================= */
  var old = $.fn.formControl

  $.fn.formControl = function (option) {
    return this.each(function () {
      var $this = $(this)
        , data = $this.data('formControl')
        , options = typeof option == 'object' && option
      if (!data) {
        $this.data('formControl', (data = new FormControl(this, options)))
      }
      if (typeof option == 'string') data[option]()
    })
  }

  $.fn.formControl.defaults = {}

  $.fn.formControl.Constructor = FormControl

  /* FORMCONTROL NO CONFLICT
   * ======================= */
  $.fn.formControl.noConflict = function () {
    $.fn.formControl = old
    return this
  }

  /* FORMCONTROL DATA-API
   * ==================== */
  $(document).on('focus.formControl.data-api', '[data-provide="formControl"]', function (e) {
    var $this = $(this)
    if ($this.data('formControl')) return
    $this.formControl($this.data())
  })

})(window.jQuery);
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();

var paintings =- {
  paintings: [
 { paintingId: 1, creatorId: 1, ownerId: 1, title: 'Pixel', image: 'www.google.com/paintings', price: 55 }
 {paintingId: 2, creatorId: 2, ownerId: 2, title: 'Pixel v Vector', image: 'www.google.com/paintings2', price: 5}
 {paintingId: 3, creatorId: 3, ownerId: 3, title: 'Vector', image: 'www.google.com/paintings3', price: 10}
  ]
}
/* GET all paintings. */
router.get('/', function(req, res, next) {
  //res.send('great art at affordable prices');
  res.send(paintings.paintings);
});

module.exports = router;

<commit_msg>Cheock for typos<commit_after>var express = require('express');
var router = express.Router();

var paintings = {
  paintings: [
 { paintingId: 1, creatorId: 1, ownerId: 1, title: 'Pixel', image: 'www.google.com/paintings', price: 55 },
 { paintingId: 2, creatorId: 2, ownerId: 2, title: 'Pixel v Vector', image: 'www.google.com/paintings2', price: 5 }
 { paintingId: 3, creatorId: 3, ownerId: 3, title: 'Vector', image: 'www.google.com/paintings3', price: 10}
  ]
}
/* GET all paintings. */
router.get('/', function(req, res, next) {
  //res.send('great art at affordable prices');
  res.send(paintings.paintings);
});

module.exports = router;

<|endoftext|>


<commit_before>/* Copyright 2013-2014 Extended Mind Technologies Oy
 *
 * 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.
 */
 'use strict';

 function EntryController($location, $rootScope, $scope, $timeout, $window,
                          AnalyticsService, AuthenticationService,
                          BackendClientService, SwiperService,
                          UserSessionService) {

  AnalyticsService.visitEntry('entry');

  // DEBUG //
  $scope.DEBUG_openKeyboard = function(){
    $rootScope.packaging = 'devel-cordova';
    $rootScope.softKeyboard.height = 300;
    if (!$scope.$$phase){
      $scope.$apply();
    }
  };
  $scope.DEBUG_closeKeyboard = function(){
    $rootScope.packaging = 'devel-cordova';
    $rootScope.softKeyboard.height = 0;
    if (!$scope.$$phase) $scope.$apply();
  };
  // DEBUG //

  $scope.swipeToSignup = function swipeToSignup() {
    $scope.entryState = 'signup';
    $scope.user = {};
    SwiperService.swipeTo('entry/main');
    AnalyticsService.visitEntry('signup');

    // TODO: Use AngularJS
    var entryBannerLogoElement = document.getElementById('entryBannerLogo');
    // entryBannerLogoElement.style['transform'] = 'translate3d(0, -50px, 0)';
    entryBannerLogoElement.style.webkitTransform = 'translate3d(0, -50px, 0)';
  };

  $scope.swipeToLogin = function swipeToLogin() {
    $scope.entryState = 'login';
    $scope.user = {};
    SwiperService.swipeTo('entry/main');
    AnalyticsService.visitEntry('login');
  };

  $scope.gotoTermsOfService = function gotoTermsOfService() {
    AnalyticsService.visit('terms');
    $window.open('http://ext.md/terms.html', '_system');
  };

  $scope.gotoPrivacyPolicy = function gotoPrivacyPolicy() {
    AnalyticsService.visit('privacy');
    $window.open('http://ext.md/privacy.html', '_system');
  };

  $scope.swipeToHome = function() {
    SwiperService.swipeTo('entry/home');

    // TODO: Use AngularJS
    var entryBannerLogoElement = document.getElementById('entryBannerLogo');
    // entryBannerLogoElement.style['transform'] = 'translate3d(0, 0, 0)';
    entryBannerLogoElement.style.webkitTransform = 'translate3d(0, 0, 0)';
  };

  $scope.swipeToMain = function() {
    SwiperService.swipeTo('entry/main');
  };

  $scope.swipeToForgot = function() {
    SwiperService.swipeTo('entry/details');
  };

  // ACTIONS

  $scope.logIn = function() {
    if ($scope.rememberByDefault()) {
      $scope.user.remember = true;
    }
    $scope.loginFailed = false;
    $scope.loginOffline = false;
    AuthenticationService.login($scope.user).then(function() {
      AnalyticsService.do('login');
      $location.path('/my');
    }, function(authenticateResponse) {
      if (BackendClientService.isOffline(authenticateResponse.status)) {
        AnalyticsService.error('login', 'offline');
        $scope.loginOffline = true;
      } else if (authenticateResponse.status === 403) {
        AnalyticsService.error('login', 'failed');
        $scope.loginFailed = true;
      }
    });
  };

  $scope.rememberByDefault = function rememberByDefault() {
    return UserSessionService.getRememberByDefault();
  };

}

EntryController['$inject'] = ['$location', '$rootScope', '$scope', '$timeout', '$window',
                              'AnalyticsService', 'AuthenticationService',
                              'BackendClientService', 'SwiperService',
                              'UserSessionService'];
angular.module('em.entry').controller('EntryController', EntryController);
<commit_msg>Removed banner logo animation from entry page.<commit_after>/* Copyright 2013-2014 Extended Mind Technologies Oy
 *
 * 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.
 */
 'use strict';

 function EntryController($location, $rootScope, $scope, $timeout, $window,
                          AnalyticsService, AuthenticationService,
                          BackendClientService, SwiperService,
                          UserSessionService) {

  AnalyticsService.visitEntry('entry');

  // DEBUG //
  $scope.DEBUG_openKeyboard = function(){
    $rootScope.packaging = 'devel-cordova';
    $rootScope.softKeyboard.height = 300;
    if (!$scope.$$phase){
      $scope.$apply();
    }
  };
  $scope.DEBUG_closeKeyboard = function(){
    $rootScope.packaging = 'devel-cordova';
    $rootScope.softKeyboard.height = 0;
    if (!$scope.$$phase) $scope.$apply();
  };
  // DEBUG //

  $scope.swipeToSignup = function swipeToSignup() {
    $scope.entryState = 'signup';
    $scope.user = {};
    SwiperService.swipeTo('entry/main');
    AnalyticsService.visitEntry('signup');
  };

  $scope.swipeToLogin = function swipeToLogin() {
    $scope.entryState = 'login';
    $scope.user = {};
    SwiperService.swipeTo('entry/main');
    AnalyticsService.visitEntry('login');
  };

  $scope.gotoTermsOfService = function gotoTermsOfService() {
    AnalyticsService.visit('terms');
    $window.open('http://ext.md/terms.html', '_system');
  };

  $scope.gotoPrivacyPolicy = function gotoPrivacyPolicy() {
    AnalyticsService.visit('privacy');
    $window.open('http://ext.md/privacy.html', '_system');
  };

  $scope.swipeToHome = function() {
    SwiperService.swipeTo('entry/home');
  };

  $scope.swipeToMain = function() {
    SwiperService.swipeTo('entry/main');
  };

  $scope.swipeToForgot = function() {
    SwiperService.swipeTo('entry/details');
  };

  // ACTIONS

  $scope.logIn = function() {
    if ($scope.rememberByDefault()) {
      $scope.user.remember = true;
    }
    $scope.loginFailed = false;
    $scope.loginOffline = false;
    AuthenticationService.login($scope.user).then(function() {
      AnalyticsService.do('login');
      $location.path('/my');
    }, function(authenticateResponse) {
      if (BackendClientService.isOffline(authenticateResponse.status)) {
        AnalyticsService.error('login', 'offline');
        $scope.loginOffline = true;
      } else if (authenticateResponse.status === 403) {
        AnalyticsService.error('login', 'failed');
        $scope.loginFailed = true;
      }
    });
  };

  $scope.rememberByDefault = function rememberByDefault() {
    return UserSessionService.getRememberByDefault();
  };

}

EntryController['$inject'] = ['$location', '$rootScope', '$scope', '$timeout', '$window',
                              'AnalyticsService', 'AuthenticationService',
                              'BackendClientService', 'SwiperService',
                              'UserSessionService'];
angular.module('em.entry').controller('EntryController', EntryController);
<|endoftext|>


<commit_before>const glsl = require('glslify')
const createQuad = require('primitive-quad')

const SKYBOX_VERT = glsl(__dirname + '/glsl/Skybox.vert')
const SKYBOX_FRAG = glsl(__dirname + '/glsl/Skybox.frag')

const SKYTEXTURE_VERT = glsl(__dirname + '/glsl/SkyEnvMap.vert')
const SKYTEXTURE_FRAG = glsl(__dirname + '/glsl/SkyEnvMap.frag')

const Signal = require('signals')

function Skybox (opts) {
  this.type = 'Skybox'
  this.changed = new Signal()
  this.rgbm = false

  const ctx = this._ctx = opts.ctx

  this.texture = null
  this._dirtySunPosition = true

  this.set(opts)

  const skyboxPositions = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
  const skyboxFaces = [[0, 1, 2], [0, 2, 3]]

  this._drawCommand = {
    name: 'Skybox.draw',
    pipeline: ctx.pipeline({
      vert: SKYBOX_VERT,
      frag: SKYBOX_FRAG,
      depthTest: true
    }),
    attributes: {
      aPosition: ctx.vertexBuffer(skyboxPositions)
    },
    indices: ctx.indexBuffer(skyboxFaces)
  }

  var quad = createQuad()

  this._skyTexture = ctx.texture2D({
    width: 512,
    height: 256,
    pixelFormat: this.rgbm ? ctx.PixelFormat.RGBA8 : ctx.PixelFormat.RGBA32F,
    encoding: this.rgbm ? ctx.Encoding.RGBM : ctx.Encoding.Linear
  })

  this._updateSkyTexture = {
    name: 'Skybox.updateSkyTexture',
    pass: ctx.pass({
      name: 'Skybox.updateSkyTexture',
      color: [ this._skyTexture ],
      clearColor: [0, 0, 0, 0]
    }),
    pipeline: ctx.pipeline({
      vert: SKYTEXTURE_VERT,
      frag: SKYTEXTURE_FRAG
    }),
    uniforms: {
      uSunPosition: [0, 0, 0]
    },
    attributes: {
      aPosition: ctx.vertexBuffer(quad.positions),
      aTexCoord0: ctx.vertexBuffer(quad.uvs)
    },
    indices: ctx.indexBuffer(quad.cells)
  }
}

Skybox.prototype.init = function (entity) {
  this.entity = entity
}

Skybox.prototype.set = function (opts) {
  Object.assign(this, opts)

  if (opts.sunPosition) {
    this._dirtySunPosition = true
  }

  Object.keys(opts).forEach((prop) => this.changed.dispatch(prop))
}

Skybox.prototype.draw = function (camera, opts) {
  var ctx = this._ctx

  if (!this.texture && this._dirtySunPosition) {
    this._dirtySunPosition = false
    ctx.submit(this._updateSkyTexture, {
      uniforms: {
        uSunPosition: this.sunPosition
      }
    })
  }

  const texture = this.texture || this._skyTexture
  // TODO: can we somehow avoid creating an object every frame here?
  ctx.submit(this._drawCommand, {
    uniforms: {
      uProjectionMatrix: camera.projectionMatrix,
      uViewMatrix: camera.viewMatrix,
      uEnvMap: texture,
      uEnvMapEncoding: texture.encoding,
      uOutputEncoding: opts.outputEncoding
    }
  })
}

module.exports = function createSkybox (opts) {
  if (!opts.sunPosition && !opts.texture) {
    throw new Error('Skybox requires either a sunPosition or a texture')
  }
  return new Skybox(opts)
}
<commit_msg>Add support for diffuse texture to skybox<commit_after>const glsl = require('glslify')
const createQuad = require('primitive-quad')

const SKYBOX_VERT = glsl(__dirname + '/glsl/Skybox.vert')
const SKYBOX_FRAG = glsl(__dirname + '/glsl/Skybox.frag')

const SKYTEXTURE_VERT = glsl(__dirname + '/glsl/SkyEnvMap.vert')
const SKYTEXTURE_FRAG = glsl(__dirname + '/glsl/SkyEnvMap.frag')

const Signal = require('signals')

function Skybox (opts) {
  this.type = 'Skybox'
  this.changed = new Signal()
  this.rgbm = false

  const ctx = this._ctx = opts.ctx

  this.texture = null
  this.diffuseTexture = null
  this._dirtySunPosition = true

  this.set(opts)

  const skyboxPositions = [[-1, -1], [1, -1], [1, 1], [-1, 1]]
  const skyboxFaces = [[0, 1, 2], [0, 2, 3]]

  this._drawCommand = {
    name: 'Skybox.draw',
    pipeline: ctx.pipeline({
      vert: SKYBOX_VERT,
      frag: SKYBOX_FRAG,
      depthTest: true
    }),
    attributes: {
      aPosition: ctx.vertexBuffer(skyboxPositions)
    },
    indices: ctx.indexBuffer(skyboxFaces)
  }

  var quad = createQuad()

  this._skyTexture = ctx.texture2D({
    width: 512,
    height: 256,
    pixelFormat: this.rgbm ? ctx.PixelFormat.RGBA8 : ctx.PixelFormat.RGBA32F,
    encoding: this.rgbm ? ctx.Encoding.RGBM : ctx.Encoding.Linear
  })

  this._updateSkyTexture = {
    name: 'Skybox.updateSkyTexture',
    pass: ctx.pass({
      name: 'Skybox.updateSkyTexture',
      color: [ this._skyTexture ],
      clearColor: [0, 0, 0, 0]
    }),
    pipeline: ctx.pipeline({
      vert: SKYTEXTURE_VERT,
      frag: SKYTEXTURE_FRAG
    }),
    uniforms: {
      uSunPosition: [0, 0, 0]
    },
    attributes: {
      aPosition: ctx.vertexBuffer(quad.positions),
      aTexCoord0: ctx.vertexBuffer(quad.uvs)
    },
    indices: ctx.indexBuffer(quad.cells)
  }
}

Skybox.prototype.init = function (entity) {
  this.entity = entity
}

Skybox.prototype.set = function (opts) {
  Object.assign(this, opts)

  if (opts.sunPosition) {
    this._dirtySunPosition = true
  }

  Object.keys(opts).forEach((prop) => this.changed.dispatch(prop))
}

Skybox.prototype.draw = function (camera, opts) {
  var ctx = this._ctx

  if (!this.texture && this._dirtySunPosition) {
    this._dirtySunPosition = false
    ctx.submit(this._updateSkyTexture, {
      uniforms: {
        uSunPosition: this.sunPosition
      }
    })
  }

  let texture = this.texture || this._skyTexture
  if (opts.diffuse && this.diffuseTexture) {
    texture = this.diffuseTexture
  }
  // TODO: can we somehow avoid creating an object every frame here?
  ctx.submit(this._drawCommand, {
    uniforms: {
      uProjectionMatrix: camera.projectionMatrix,
      uViewMatrix: camera.viewMatrix,
      uEnvMap: texture,
      uEnvMapEncoding: texture.encoding,
      uOutputEncoding: opts.outputEncoding
    }
  })
}

module.exports = function createSkybox (opts) {
  if (!opts.sunPosition && !opts.texture) {
    throw new Error('Skybox requires either a sunPosition or a texture')
  }
  return new Skybox(opts)
}
<|endoftext|>


<commit_before>;(function() {

	// Based on jQuery.extend
	var extend = function() {
		// copy reference to target object
		var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, toString = Object.prototype.toString;

		var isFunction = function( obj ) {
			return toString.call(obj) === "[object Function]";
		};

		// Handle a deep copy situation
		if ( typeof target === "boolean" ) {
			deep = target;
			target = arguments[1] || {};
			// skip the boolean and the target
			i = 2;
		}

		// Handle case when target is a string or something (possible in deep copy)
		if ( typeof target !== "object" && !isFunction(target) ) {
			target = {};
		}

		// extend Object itself if only one argument is passed
		if ( length == i ) {
			target = this;
			--i;
		}

		for ( ; i < length; i++ ) {
			// Only deal with non-null/undefined values
			if ( (options = arguments[ i ]) != null ) {
				// Extend the base object
				for ( var name in options ) {
					var src = target[ name ], copy = options[ name ];

					// Prevent never-ending loop
					if ( target === copy ) {
						continue;
					}

					// Recurse if we're merging object values
					if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
						target[ name ] = extend( deep,
							// Never move original objects, clone them
							src || ( copy.length != null ? [ ] : { } )
						, copy );
					}
					// Don't bring in undefined values
					else if ( copy !== undefined ) {
						target[ name ] = copy;
					}

				}
			}
		}

		// Return the modified object
		return target;
	};

	// temporary crappy method to get the base path
	var getBasePath = function () {
		var nodes = document.getElementsByTagName('script'), i, base;

		for (i = 0; i < nodes.length; i = i + 1) {
			var src = nodes[i].src;
			var match = src.match(/^(.*)yui[\.-].*js(\?.*)?$/);

			if (match && (base = match[1])) {
				base = base.substring(0, base.length - 4);
				break;
			}
		}

		return base;
	};

	/*!
	 * Alloy JavaScript Library v@VERSION
	 * http://alloyui.com/
	 *
	 * Copyright (c) 2009 Liferay Inc.
	 * Licensed under the MIT license.
	 * http://alloyui.com/License
	 *
	 * Nate Cavanaugh (nate.cavanaugh@liferay.com)
	 * Eduardo Lundgren (eduardo.lundgren@liferay.com)
	 *
	 * Date: @DATE
	 * Revision: @REVISION
	 */

	window.Alloy = window.Alloy || {};

	var defaults = {};
	var defaultModules = [ 'event', 'oop', 'widget' ];

	var errorCallback = function(instance, result) {
		if (!result.success) {
			throw result.msg;
		}
	};

	if ('defaults' in Alloy) {
		defaults = Alloy.defaults;
	}

	// get the base path in case the user has not specified
	if (!('base' in defaults)) {
		defaults.base = getBasePath();
	}

	var ALLOY = YUI( extend({}, defaults) );

	var originalConfig = ALLOY.config;

	// adding callback for .use()
	defaultModules.push(errorCallback);

	// loading default modules
	ALLOY.use.apply(ALLOY, defaultModules);

	Alloy = function(o) {
		var instance = this;

		ALLOY.config = ALLOY.merge(originalConfig, Alloy.defaults);

		if (o || instance instanceof Alloy) {
			// new Alloy() creates a new YUI sandbox
			return YUI( ALLOY.merge(ALLOY.config, o) );
		}

		// returns the cached YUI sandbox
		return ALLOY;
	};

	extend(Alloy, YUI, {
		__version: '@VERSION',

		extend: extend,

		defaults: defaults
	});

	Alloy.extend(Alloy.prototype, {
		ready: function() {
			var instance = this;
			var slice = Array.prototype.slice;
			var args = slice.call(arguments, 0), index = args.length - 1;

			// user callback
			var fn = args[index];

			// array with YUI modules to be loaded
			var modules = slice.call(arguments, 0, index);

			if (!modules.length) {
				modules.push('event');
			}

			// adding Alloy().use() callback
			modules.push(
				function(instance) {
					var args = arguments;
					instance.on('domready', function() {
					   fn.apply(this, args);
					});
				}
			);

			instance.use.apply(instance, modules);
		}
	});

})();<commit_msg>Change the getBasePath method to remove the last path level instead of calculate the substring<commit_after>;(function() {

	// Based on jQuery.extend
	var extend = function() {
		// copy reference to target object
		var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, toString = Object.prototype.toString;

		var isFunction = function( obj ) {
			return toString.call(obj) === "[object Function]";
		};

		// Handle a deep copy situation
		if ( typeof target === "boolean" ) {
			deep = target;
			target = arguments[1] || {};
			// skip the boolean and the target
			i = 2;
		}

		// Handle case when target is a string or something (possible in deep copy)
		if ( typeof target !== "object" && !isFunction(target) ) {
			target = {};
		}

		// extend Object itself if only one argument is passed
		if ( length == i ) {
			target = this;
			--i;
		}

		for ( ; i < length; i++ ) {
			// Only deal with non-null/undefined values
			if ( (options = arguments[ i ]) != null ) {
				// Extend the base object
				for ( var name in options ) {
					var src = target[ name ], copy = options[ name ];

					// Prevent never-ending loop
					if ( target === copy ) {
						continue;
					}

					// Recurse if we're merging object values
					if ( deep && copy && typeof copy === "object" && !copy.nodeType ) {
						target[ name ] = extend( deep,
							// Never move original objects, clone them
							src || ( copy.length != null ? [ ] : { } )
						, copy );
					}
					// Don't bring in undefined values
					else if ( copy !== undefined ) {
						target[ name ] = copy;
					}

				}
			}
		}

		// Return the modified object
		return target;
	};

	// temporary crappy method to get the base path
	var getBasePath = function () {
		var nodes = document.getElementsByTagName('script'), i, base;

		for (i = 0; i < nodes.length; i = i + 1) {
			var src = nodes[i].src;
			var match = src.match(/^(.*)yui[\.-].*js(\?.*)?$/);

			if (match && (base = match[1])) {
				base = base.split(/[\/\\]/);

				// removing the last path level /yui/../
				base = base.splice(0, base.length - 2).concat(
					base.splice(base.length - 1, base.length)
				);

				break;
			}
		}

		return base.join('/');
	};

	/*!
	 * Alloy JavaScript Library v@VERSION
	 * http://alloyui.com/
	 *
	 * Copyright (c) 2009 Liferay Inc.
	 * Licensed under the MIT license.
	 * http://alloyui.com/License
	 *
	 * Nate Cavanaugh (nate.cavanaugh@liferay.com)
	 * Eduardo Lundgren (eduardo.lundgren@liferay.com)
	 *
	 * Date: @DATE
	 * Revision: @REVISION
	 */

	window.Alloy = window.Alloy || {};

	var defaults = {};
	var defaultModules = [ 'event', 'oop', 'widget' ];

	var errorCallback = function(instance, result) {
		if (!result.success) {
			throw result.msg;
		}
	};

	if ('defaults' in Alloy) {
		defaults = Alloy.defaults;
	}

	// get the base path in case the user has not specified
	if (!('base' in defaults)) {
		defaults.base = getBasePath();
	}

	var ALLOY = YUI( extend({}, defaults) );

	var originalConfig = ALLOY.config;

	// adding callback for .use()
	defaultModules.push(errorCallback);

	// loading default modules
	ALLOY.use.apply(ALLOY, defaultModules);

	Alloy = function(o) {
		var instance = this;

		ALLOY.config = ALLOY.merge(originalConfig, Alloy.defaults);

		if (o || instance instanceof Alloy) {
			// new Alloy() creates a new YUI sandbox
			return YUI( ALLOY.merge(ALLOY.config, o) );
		}

		// returns the cached YUI sandbox
		return ALLOY;
	};

	extend(Alloy, YUI, {
		__version: '@VERSION',

		extend: extend,

		defaults: defaults
	});

	Alloy.extend(Alloy.prototype, {
		ready: function() {
			var instance = this;
			var slice = Array.prototype.slice;
			var args = slice.call(arguments, 0), index = args.length - 1;

			// user callback
			var fn = args[index];

			// array with YUI modules to be loaded
			var modules = slice.call(arguments, 0, index);

			if (!modules.length) {
				modules.push('event');
			}

			// adding Alloy().use() callback
			modules.push(
				function(instance) {
					var args = arguments;
					instance.on('domready', function() {
					   fn.apply(this, args);
					});
				}
			);

			instance.use.apply(instance, modules);
		}
	});

})();<|endoftext|>


<commit_before>/**
 * Loading
 *
 * @type {{config, count, getSpinner, start, stop, startInline, startDialog}}
 */
var pmUtilLoading = function () {
    "use strict";

    return {
        config: {
            spinner: {
                id: "pm-util-loading-modal",
                color: "#fff",
                icon: "fa fa-spinner fa-spin fa-4x",
                style: "display:block;overflow: visible!important;"
            },
            showModal: true,
            inline: {
                color: "#3bc9d7",
                init: false
            }
        },
        count: 0,
        /**
         * Get Spinner
         * @returns {*|Mixed|jQuery|HTMLElement}
         */
        getSpinner: function () {
            return $('#' + this.config.spinner.id);
        },
        /**
         * Start
         */
        start: function () {
            var _self = this;

            if (0 === _self.count) {
                var body = $('body');

                var spinner = $('

');
                var icon = $('').attr('class', _self.config.spinner.icon).css('color', _self.config.spinner.color);

                spinner.css("top", ($(window).height() / 2) + "px");

                spinner.append(icon);

                body.append(spinner);

                if (true === _self.config.showModal) {
                    body.append($('

'));
                }
            }

            _self.count++;
        },
        /**
         * Stop
         */
        stop: function () {
            var _self = this;

            if (0 < _self.count) {
                _self.count--;
            }

            if (0 === _self.count) {
                if (true === _self.config.showModal) {
                    $("#" + _self.config.spinner.id + "-modal").remove();
                }

                _self.getSpinner().remove();
            }
        },
        /**
         * Start inline
         * @param target
         * @param size
         */
        startInline: function (target, size) {
            if (false === this.config.inline.init) {
                this.config.inline.init = true;

                $('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', '/bundles/pmtool/module/pm-util/css/loading.css?v=1.0.1'));
            }

            var divClass = 'pm-util-loading-inline';
            var text = 'Lädt...';
            var style = 'width:80px;margin: 60px auto;height:80px;';

            if ('small' === size) {
                text = '';
                divClass += ' small';
                style = 'width:40px;margin: 5px auto;height:40px;';
            }

            target.html('
<svg class="circular" viewBox="25 25 50 50"><circle class="path" cx="50" cy="50" r="20" fill="none" stroke="' + this.config.inline.color + '" stroke-width="2" stroke-miterlimit="10" /></svg>' + text + '
');
        },
        /**
         * Start Dialog
         */
        startDialog: function () {
            var element = $('

');

            bootbox.dialog({
                message: element
            });

            this.startInline(element);
        }
    }
}();

/**
 * pmUtil
 * @type {{config, debug, initBootbox, initAjax, init}}
 */
var pmUtil = function () {
    "use strict";

    return {
        config: {
            debugging: false,
            module: {
                select2: {
                    enabled: false,
                    callback: function () {
                        $('.pm-select2').pmSelect2();
                    },
                    version: '4.0.3',
                    theme: '0.1.0-beta.9'
                },
                bootbox: {
                    enabled: false,
                    cache: '201701171444',
                    callback: function () {
                        $('.pm-bootbox').pmBootbox({
                            version: 170117
                        });
                    }
                },
                bootbox_delete: {
                    enabled: false,
                    cache: '201701171458',
                    callback: function () {
                        $('.pm-bootbox-delete').pmBootboxDelete({
                            version: 170117
                        });
                    }
                },
                bootstrapSwitch: {
                    enabled: false,
                    version: '3.3.2',
                    callback: function () {
                        $('.pm-bootstrap-switch').pmBootstrapSwitch({
                            version: 161201
                        });
                    }
                },
                simpleMde: {
                    enabled: false,
                    version: '1.11.2',
                    callback: function () {
                        $('.pm-markdown').pmMarkdown({
                            version: 170320
                        });
                    }
                }
            }
        },
        /**
         * Debug
         */
        debug: function () {
            if (true === this.config.debugging) {
                for (var i = 0; i < arguments.length; i++) {
                    console.log(arguments[i]);
                }
            }
        },
        /**
         * Init Bootbox
         */
        initBootbox: function () {
            this.debug('pmUtil.initBootbox()');
            this.debug(' -- pmUtil.initBootbox is deprecated! --');
            var _self = this;

            $('a.delete').on('click', function () {
                pmUtil.debug('pmUtil.initBootbox() a.delete_click()');

                var elem = $(this);

                var title = "Den Eintrag wirklich löschen?";
                if (elem.attr('title')) {
                    title = elem.attr('title');
                }

                bootbox.confirm(title, function (result) {
                    if (true === result) {
                        pmUtilLoading.start();
                        window.location.href = elem.attr('href');
                    }
                });

                return false;
            }).removeClass('disabled');
        },
        /**
         * Init Ajax
         */
        initAjax: function () {
            $.ajaxSetup({
                cache: true
            });

            $(document).ajaxComplete(function (event, xhr) {
                if (undefined !== xhr.status && 500 === xhr.status) {
                    var message = 'Es ist ein schwerwiegender Fehler aufgetreten. Bitte lade die Seite erneut.';
                    if (undefined !== bootbox) {
                        bootbox.hideAll();
                        bootbox.alert(message);
                    } else {
                        alert(message);
                    }

                }
            });
        },
        /**
         * Init
         */
        init: function () {
            this.debug('pmUtil.init()');

            if (false === pmUtil.config.module.bootbox_delete.enabled) {
                this.initBootbox();
            }

            this.initAjax();

            var path_parcels = '/bundles/pmtool/module/pm-util/parcels';

            if (true === this.config.module.select2.enabled) {
                $.getScript(path_parcels + '/jquery.pm-select2.js', function () {
                    pmUtil.config.module.select2.callback();
                });
            }

            if (true === this.config.module.bootbox.enabled) {
                $.getScript(path_parcels + '/jquery.pm-bootbox.js?v=' + pmUtil.config.module.bootbox.cache, function () {
                    pmUtil.config.module.bootbox.callback();
                });
            }

            if (true === this.config.module.bootbox_delete.enabled) {
                $.getScript(path_parcels + '/jquery.pm-bootbox-delete.js?v=' + pmUtil.config.module.bootbox_delete.cache, function () {
                    pmUtil.config.module.bootbox_delete.callback();
                });
            }

            if (true === this.config.module.bootstrapSwitch.enabled) {
                $.getScript(path_parcels + '/jquery.pm-bootstrap-switch.js', function () {
                    pmUtil.config.module.bootstrapSwitch.callback();
                });
            }

            if (true === this.config.module.simpleMde.enabled) {
                $.getScript(path_parcels + '/jquery.pm-markdown.js', function () {
                    pmUtil.config.module.simpleMde.callback();
                });
            }
        }
    };
}();

$(document).ready(function () {
    pmUtil.init();
});<commit_msg>settings for util ajax<commit_after>/**
 * Loading
 *
 * @type {{config, count, getSpinner, start, stop, startInline, startDialog}}
 */
var pmUtilLoading = function () {
    "use strict";

    return {
        config: {
            spinner: {
                id: "pm-util-loading-modal",
                color: "#fff",
                icon: "fa fa-spinner fa-spin fa-4x",
                style: "display:block;overflow: visible!important;"
            },
            showModal: true,
            inline: {
                color: "#3bc9d7",
                init: false
            }
        },
        count: 0,
        /**
         * Get Spinner
         * @returns {*|Mixed|jQuery|HTMLElement}
         */
        getSpinner: function () {
            return $('#' + this.config.spinner.id);
        },
        /**
         * Start
         */
        start: function () {
            var _self = this;

            if (0 === _self.count) {
                var body = $('body');

                var spinner = $('

');
                var icon = $('').attr('class', _self.config.spinner.icon).css('color', _self.config.spinner.color);

                spinner.css("top", ($(window).height() / 2) + "px");

                spinner.append(icon);

                body.append(spinner);

                if (true === _self.config.showModal) {
                    body.append($('

'));
                }
            }

            _self.count++;
        },
        /**
         * Stop
         */
        stop: function () {
            var _self = this;

            if (0 < _self.count) {
                _self.count--;
            }

            if (0 === _self.count) {
                if (true === _self.config.showModal) {
                    $("#" + _self.config.spinner.id + "-modal").remove();
                }

                _self.getSpinner().remove();
            }
        },
        /**
         * Start inline
         * @param target
         * @param size
         */
        startInline: function (target, size) {
            if (false === this.config.inline.init) {
                this.config.inline.init = true;

                $('head').append($('<link rel="stylesheet" type="text/css" />').attr('href', '/bundles/pmtool/module/pm-util/css/loading.css?v=1.0.1'));
            }

            var divClass = 'pm-util-loading-inline';
            var text = 'Lädt...';
            var style = 'width:80px;margin: 60px auto;height:80px;';

            if ('small' === size) {
                text = '';
                divClass += ' small';
                style = 'width:40px;margin: 5px auto;height:40px;';
            }

            target.html('
<svg class="circular" viewBox="25 25 50 50"><circle class="path" cx="50" cy="50" r="20" fill="none" stroke="' + this.config.inline.color + '" stroke-width="2" stroke-miterlimit="10" /></svg>' + text + '
');
        },
        /**
         * Start Dialog
         */
        startDialog: function () {
            var element = $('

');

            bootbox.dialog({
                message: element
            });

            this.startInline(element);
        }
    }
}();

/**
 * pmUtil
 * @type {{config, debug, initBootbox, initAjax, init}}
 */
var pmUtil = function () {
    "use strict";

    return {
        config: {
            debugging: false,
            ajax: {
                cache: true,
                error: 'Es ist ein schwerwiegender Fehler aufgetreten. Bitte lade die Seite erneut.'
            },
            module: {
                select2: {
                    enabled: false,
                    callback: function () {
                        $('.pm-select2').pmSelect2();
                    },
                    version: '4.0.3',
                    theme: '0.1.0-beta.9'
                },
                bootbox: {
                    enabled: false,
                    cache: '201701171444',
                    callback: function () {
                        $('.pm-bootbox').pmBootbox({
                            version: 170117
                        });
                    }
                },
                bootbox_delete: {
                    enabled: false,
                    cache: '201701171458',
                    callback: function () {
                        $('.pm-bootbox-delete').pmBootboxDelete({
                            version: 170117
                        });
                    }
                },
                bootstrapSwitch: {
                    enabled: false,
                    version: '3.3.2',
                    callback: function () {
                        $('.pm-bootstrap-switch').pmBootstrapSwitch({
                            version: 161201
                        });
                    }
                },
                simpleMde: {
                    enabled: false,
                    version: '1.11.2',
                    callback: function () {
                        $('.pm-markdown').pmMarkdown({
                            version: 170320
                        });
                    }
                }
            }
        },
        /**
         * Debug
         */
        debug: function () {
            if (true === this.config.debugging) {
                for (var i = 0; i < arguments.length; i++) {
                    console.log(arguments[i]);
                }
            }
        },
        /**
         * Init Bootbox
         */
        initBootbox: function () {
            this.debug('pmUtil.initBootbox()');
            this.debug(' -- pmUtil.initBootbox is deprecated! --');
            var _self = this;

            $('a.delete').on('click', function () {
                pmUtil.debug('pmUtil.initBootbox() a.delete_click()');

                var elem = $(this);

                var title = "Den Eintrag wirklich löschen?";
                if (elem.attr('title')) {
                    title = elem.attr('title');
                }

                bootbox.confirm(title, function (result) {
                    if (true === result) {
                        pmUtilLoading.start();
                        window.location.href = elem.attr('href');
                    }
                });

                return false;
            }).removeClass('disabled');
        },
        /**
         * Init Ajax
         */
        initAjax: function () {
            $.ajaxSetup({
                cache: pmUtil.config.ajax.cache
            });

            if (false !== this.config.ajax.error) {
                $(document).ajaxComplete(function (event, xhr) {
                    if (undefined !== xhr.status && 500 === xhr.status) {
                        if (undefined !== bootbox) {
                            bootbox.hideAll();
                            bootbox.alert(pmUtil.config.ajax.error);
                        } else {
                            alert(pmUtil.config.ajax.error);
                        }
                    }
                });
            }
        },
        /**
         * Init
         */
        init: function () {
            this.debug('pmUtil.init()');

            if (false === pmUtil.config.module.bootbox_delete.enabled) {
                this.initBootbox();
            }

            this.initAjax();

            var path_parcels = '/bundles/pmtool/module/pm-util/parcels';

            if (true === this.config.module.select2.enabled) {
                $.getScript(path_parcels + '/jquery.pm-select2.js', function () {
                    pmUtil.config.module.select2.callback();
                });
            }

            if (true === this.config.module.bootbox.enabled) {
                $.getScript(path_parcels + '/jquery.pm-bootbox.js?v=' + pmUtil.config.module.bootbox.cache, function () {
                    pmUtil.config.module.bootbox.callback();
                });
            }

            if (true === this.config.module.bootbox_delete.enabled) {
                $.getScript(path_parcels + '/jquery.pm-bootbox-delete.js?v=' + pmUtil.config.module.bootbox_delete.cache, function () {
                    pmUtil.config.module.bootbox_delete.callback();
                });
            }

            if (true === this.config.module.bootstrapSwitch.enabled) {
                $.getScript(path_parcels + '/jquery.pm-bootstrap-switch.js', function () {
                    pmUtil.config.module.bootstrapSwitch.callback();
                });
            }

            if (true === this.config.module.simpleMde.enabled) {
                $.getScript(path_parcels + '/jquery.pm-markdown.js', function () {
                    pmUtil.config.module.simpleMde.callback();
                });
            }
        }
    };
}();

$(document).ready(function () {
    pmUtil.init();
});<|endoftext|>


<commit_before>import React, { Component } from 'react';
import * as Actions from '../actions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import Tag from './Tag';
import { RECORDS_LIST, RECORD_FORM } from '../constants/WidgetNames';
import { EXP, INC } from '../constants/TransactionTypes';

import RecordModel from '../models/Record';

class RecordForm extends Component {
    static propTypes = {
        isVisible: PropTypes.bool.isRequired,
        tags: PropTypes.array.isRequired,
    };

    constructor(props) {
        super(props);

        this.calculateButton = React.createRef();
        this.amountInput = React.createRef();
    }

    get record() {
        return this.props.recordForm.record;
    }

    calculate() {
        this.props.actions.calculateRecordFormAmount();
        this.focus();
    }

    get tags() {
        return this.props.tags.map(name => (
            <Tag
                name={name}
                key={name}
                toggle={this.props.actions.toggleRecordFormTag}
                isSelected={this.record.selectedTags.has(name)}
            />
        ));
    }

    async submit(returnTo) {
        const success = await this.props.actions.submitRecordForm(returnTo);
        // TODO: show valdation errors if any

        if (success) {
            this.reset();
        }
    }

    save() {
        return this.submit(RECORDS_LIST);
    }

    saveAddAnother() {
        return this.submit(RECORD_FORM);
    }

    reset() {
        this.setState(RecordModel.default());
        this.focus();
    }

    handleAmountChange(event) {
        this.props.actions.setRecordFormAmount(event.target.value);
    }

    handleTypeChange(event) {
        this.props.actions.setRecordFormTransactionType(event.target.value);
    }

    componentDidUpdate(prevProps) {
        const justShowedUp = prevProps.isVisible === false && this.props.isVisible === true;
        const amountChanged =
            prevProps.recordForm.record.amount.amount !== this.record.amount.amount;

        if (justShowedUp || amountChanged) {
            this.focus();
        }
    }

    componentDidMount() {
        this.focus();
    }

    focus() {
        this.amountInput.current.focus();
    }

    cancel() {
        this.props.actions.selectWidget(RECORDS_LIST);
    }

    get headerText() {
        return this.record && this.record.isPersisted ? 'Edit Record' : 'Add New Record';
    }

    render() {
        return (

{this.headerText}

                <form>

                            Amount

                            <input
                                value={this.record.amount.amount}
                                onChange={this.handleAmountChange.bind(this)}
                                ref={this.amountInput}
                                type="tel"
                                className="form-control"
                                autoComplete="off"
                                autoFocus
                            />

                                <button
                                    ref={this.calculateButton}
                                    onClick={this.props.actions.calculateRecordFormAmount}
                                    className="btn btn-default"
                                    type="button">
                                    calculate
                                </button>

                        Tags

                            {this.tags}

                            Transaction type

                            <select
                                name="transaction_type"
                                className="form-control"
                                value={this.record.transaction_type}
                                onChange={this.handleTypeChange.bind(this)}>
                                <option value={EXP}>Expences</option>
                                <option value={INC}>Income</option>
                            </select>

                </form>

                    <button onClick={this.save.bind(this)} className="btn btn-success btn-default">
                        Save
                    </button>
                    <button
                        onClick={this.saveAddAnother.bind(this)}
                        className="btn btn-default"
                        hidden={!this.record.isPersisted}>
                        Save & Add
                    </button>
                    {/* <button className="btn btn-danger" hidden={!this.isPersisted}> */}
                    {/*     Delete */}
                    {/* </button> */}
                    <button onClick={this.cancel.bind(this)} className="btn btn-info">
                        Cancel
                    </button>

        );
    }
}

const mapStateToProps = state => ({ recordForm: state.recordForm });

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(Actions, dispatch),
});

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(RecordForm);
<commit_msg>temporary hide "save & add" button<commit_after>import React, { Component } from 'react';
import * as Actions from '../actions';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

import Tag from './Tag';
import { RECORDS_LIST, RECORD_FORM } from '../constants/WidgetNames';
import { EXP, INC } from '../constants/TransactionTypes';

import RecordModel from '../models/Record';

class RecordForm extends Component {
    static propTypes = {
        isVisible: PropTypes.bool.isRequired,
        tags: PropTypes.array.isRequired,
    };

    constructor(props) {
        super(props);

        this.calculateButton = React.createRef();
        this.amountInput = React.createRef();
    }

    get record() {
        return this.props.recordForm.record;
    }

    calculate() {
        this.props.actions.calculateRecordFormAmount();
        this.focus();
    }

    get tags() {
        return this.props.tags.map(name => (
            <Tag
                name={name}
                key={name}
                toggle={this.props.actions.toggleRecordFormTag}
                isSelected={this.record.selectedTags.has(name)}
            />
        ));
    }

    async submit(returnTo) {
        const success = await this.props.actions.submitRecordForm(returnTo);
        // TODO: show valdation errors if any

        if (success) {
            this.reset();
        }
    }

    save() {
        return this.submit(RECORDS_LIST);
    }

    saveAddAnother() {
        return this.submit(RECORD_FORM);
    }

    reset() {
        this.setState(RecordModel.default());
        this.focus();
    }

    handleAmountChange(event) {
        this.props.actions.setRecordFormAmount(event.target.value);
    }

    handleTypeChange(event) {
        this.props.actions.setRecordFormTransactionType(event.target.value);
    }

    componentDidUpdate(prevProps) {
        const justShowedUp = prevProps.isVisible === false && this.props.isVisible === true;
        const amountChanged =
            prevProps.recordForm.record.amount.amount !== this.record.amount.amount;

        if (justShowedUp || amountChanged) {
            this.focus();
        }
    }

    componentDidMount() {
        this.focus();
    }

    focus() {
        this.amountInput.current.focus();
    }

    cancel() {
        this.props.actions.selectWidget(RECORDS_LIST);
    }

    get headerText() {
        return this.record && this.record.isPersisted ? 'Edit Record' : 'Add New Record';
    }

    render() {
        return (

{this.headerText}

                <form>

                            Amount

                            <input
                                value={this.record.amount.amount}
                                onChange={this.handleAmountChange.bind(this)}
                                ref={this.amountInput}
                                type="tel"
                                className="form-control"
                                autoComplete="off"
                                autoFocus
                            />

                                <button
                                    ref={this.calculateButton}
                                    onClick={this.props.actions.calculateRecordFormAmount}
                                    className="btn btn-default"
                                    type="button">
                                    calculate
                                </button>

                        Tags

                            {this.tags}

                            Transaction type

                            <select
                                name="transaction_type"
                                className="form-control"
                                value={this.record.transaction_type}
                                onChange={this.handleTypeChange.bind(this)}>
                                <option value={EXP}>Expences</option>
                                <option value={INC}>Income</option>
                            </select>

                </form>

                    <button onClick={this.save.bind(this)} className="btn btn-success btn-default">
                        Save
                    </button>
                    {/* <button onClick={this.saveAddAnother.bind(this)} className="btn btn-default"> */}
                    {/*     Save & Add */}
                    {/* </button> */}
                    {/* <button className="btn btn-danger" hidden={!this.isPersisted}> */}
                    {/*     Delete */}
                    {/* </button> */}
                    <button onClick={this.cancel.bind(this)} className="btn btn-info">
                        Cancel
                    </button>

        );
    }
}

const mapStateToProps = state => ({ recordForm: state.recordForm });

const mapDispatchToProps = dispatch => ({
    actions: bindActionCreators(Actions, dispatch),
});

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(RecordForm);
<|endoftext|>


<commit_before>import { h, Component } from 'preact'
const config = require('../../appconfig')

import AppConstants from '../constants/AppConstants'
import ActionType from '../constants/ActionType'
import DaySort from '../constants/DaySort'
import SettingType from '../constants/SettingType'

import AppWrapper from '../components/AppWrapper'
import Nav from '../components/Nav'
import ImageWrapper from '../components/ImageWrapper'
import ImageOverlay from '../components/ImageOverlay'
import Overlay from '../components/Overlay'
import Description from '../components/Description'
import About from '../components/About'
import Footer from '../components/Footer'

import disableHoverEffectsOnMobile from '../helpers/disable-hover-effects-on-mobile'
import getByDate from '../helpers/get-by-date-bridge'
import shuffleDate from '../helpers/shuffle-date'
import tomorrow from '../helpers/tomorrow'
import yesterday from '../helpers/yesterday'
import isDateSafe from '../helpers/is-date-safe'
import updateImageWrapper from '../helpers/update-image-wrapper'

const downloadImage = new Image()

const defaultSettings = {
  isAsync: false,
  isHd: false
}

class AppContainer extends Component {
  constructor(props) {
    super(props)
    this.state = {
      showOverlay: false,
      showAbout: false,
      showImageOverlay: false,
      showSettings: false,
      showDescription: false,
      showTitle: true,
      settings: defaultSettings,
      tries: 0,
      isFailure: false,
      isLoadingData: true,
      isLoadingImage: true
    }

    this.setSetting = this.setSetting.bind(this)
    this.handleActionClick = this.handleActionClick.bind(this)
    this.handleDescriptionClick = this.handleDescriptionClick.bind(this)
    this.handleTitleClick = this.handleTitleClick.bind(this)
    this.handleImageClick = this.handleImageClick.bind(this)
    this.handleOverlayClick = this.handleOverlayClick.bind(this)
    this.handleSettingsClick = this.handleSettingsClick.bind(this)
    this.handleAboutClick = this.handleAboutClick.bind(this)
    this.loadImage = this.loadImage.bind(this)
    this.updateImage =
      config.mode === AppConstants.DEV_MODE
        ? updateImageWrapper.bind(this, this.updateImage)
        : this.updateImage.bind(this)
  }

  makeRequest(currentDate, type) {
    let date

    switch (type) {
      case ActionType.NEWEST:
        date = currentDate
        type = ActionType.LATEST
        DaySort.LATEST = date
        break
      case ActionType.LATEST:
        date = yesterday(currentDate)
        DaySort.LATEST = date
        break
      case ActionType.PREVIOUS:
        date = yesterday(currentDate)
        break
      case ActionType.NEXT:
        date = tomorrow(currentDate)
        break
      case ActionType.SHUFFLE:
        date = shuffleDate()
        break
    }

    this.setState({}, () => {
      getByDate(date)
        .then(this.receive.bind(this, date))
        .catch(err => this.handleRejection.bind(this, err, date, type)())
    })
  }

  receive(date, data) {
    this.setState(
      {
        title: data.title,
        explanation: data.explanation,
        date: date,
        url: data.url,
        hdurl: data.hdurl,
        tries: 0,
        isFailure: false,
        isLoadingData: false
      },
      this.loadImage
    )
  }

  loadImage() {
    this.setState(
      {
        isLoadingImage: true
      },
      () => {
        let imageUrl = this.state.settings.isHd
          ? this.state.hdurl
          : this.state.url
        if (this.state.settings.isHd) {
          imageUrl = isDateSafe(this.state.date)
            ? (imageUrl = this.state.hdurl)
            : (imageUrl = this.state.url)
        }

        if (this.state.settings.isAsync) {
          this.updateImage(imageUrl)
        } else {
          downloadImage.onload = this.updateImage.bind(this, imageUrl)
          downloadImage.src = imageUrl
        }
      }
    )
  }

  updateImage(imageUrl) {
    this.setState({
      imageUrl: imageUrl,
      isLoadingImage: false
    })
  }

  handleRejection(err, date, type) {
    if (this.state.tries >= AppConstants.MAX_TRY) {
      this.setState({
        date,
        isFailure: true,
        isLoadingData: false,
        isLoadingImage: false
      })
    } else {
      this.setState(
        {
          tries: this.state.tries + 1
        },
        this.makeRequest(date, type)
      )
    }
  }

  handleActionClick(actionType) {
    const date =
      actionType === ActionType.NEWEST ? DaySort.NEWEST : this.state.date
    this.setState(
      {
        isLoadingData: true,
        isLoadingImage: true,
        showSettings: false,
        imageUrl: ''
      },
      this.makeRequest(date, actionType)
    )
  }

  handleDescriptionClick() {
    this.setState({
      showDescription: !this.state.showDescription
    })
  }

  handleTitleClick() {
    this.setState({
      showTitle: !this.state.showTitle
    })
  }

  handleSettingsClick(e) {
    this.setState({
      showSettings: !this.state.showSettings
    })
  }

  handleAboutClick(e) {
    this.setState({
      showAbout: !this.state.showAbout
    })
  }

  handleImageClick(e) {
    e.preventDefault()

    this.setState({
      showImageOverlay: true
    })
  }

  handleOverlayClick(property, e) {
    const currentOverlay = property.replace('show', '').toLowerCase()
    if (e.target.closest(`#${currentOverlay}`)) return

    this.setState({
      [property]: false,
      showSettings: false
    })
  }

  render() {
    return (

        <Description
          showDescription={this.state.showDescription}
          isLoadingData={this.state.isLoadingData}
          isLoadingImage={this.state.isLoadingImage}
          description={this.state.explanation}
          title={this.state.title}
        />
        <Overlay
          showOverlay={this.state.showAbout}
          onOverlayClick={this.handleOverlayClick.bind(null, 'showAbout')}
        >
          <About />
        </Overlay>

        <AppWrapper
          isAsync={this.state.settings.isAsync}
          isFailure={this.state.isFailure}
          isLoadingImage={this.state.isLoadingImage}
          tries={this.state.tries}
        >
          <ImageWrapper
            imageUrl={this.state.imageUrl}
            onImageClick={this.handleImageClick}
          />

          <Overlay
            showOverlay={this.state.showImageOverlay}
            onOverlayClick={this.handleOverlayClick.bind(
              null,
              'showImageOverlay'
            )}
          >
            <ImageOverlay imageUrl={this.state.imageUrl} />
          </Overlay>
        </AppWrapper>

    )
  }

  setSetting(settingType, value) {
    localStorage.setItem(settingType.toString(), value)
    const newSettings = this.state.settings
    newSettings[settingType] = value

    this.setState(
      {
        settings: newSettings
      },
      () => {
        if (!this.state.isLoadingData) this.loadImage()
      }
    )
  }

  loadSettings() {
    const isAsync =
      localStorage.getItem(SettingType.IS_ASYNC.toString()) !== null
        ? JSON.parse(localStorage.getItem(SettingType.IS_ASYNC.toString()))
        : defaultSettings.isAsync
    const isHd =
      localStorage.getItem(SettingType.IS_HD.toString()) !== null
        ? JSON.parse(localStorage.getItem(SettingType.IS_HD.toString()))
        : defaultSettings.isHd

    const newSettings = {
      isAsync,
      isHd
    }

    this.setState({
      settings: newSettings
    })
  }

  componentWillMount() {
    this.loadSettings()
    disableHoverEffectsOnMobile(window)
    this.makeRequest(DaySort.NEWEST, ActionType.NEWEST)
  }
}

export default AppContainer
<commit_msg>[AppContainer] Refactor toggle to a single method<commit_after>import { h, Component } from 'preact'
const config = require('../../appconfig')

import AppConstants from '../constants/AppConstants'
import ActionType from '../constants/ActionType'
import DaySort from '../constants/DaySort'
import SettingType from '../constants/SettingType'
import ToggleType from '../constants/ToggleType'

import AppWrapper from '../components/AppWrapper'
import Nav from '../components/Nav'
import ImageWrapper from '../components/ImageWrapper'
import ImageOverlay from '../components/ImageOverlay'
import Overlay from '../components/Overlay'
import Description from '../components/Description'
import About from '../components/About'
import Footer from '../components/Footer'

import disableHoverEffectsOnMobile from '../helpers/disable-hover-effects-on-mobile'
import getByDate from '../helpers/get-by-date-bridge'
import shuffleDate from '../helpers/shuffle-date'
import tomorrow from '../helpers/tomorrow'
import yesterday from '../helpers/yesterday'
import isDateSafe from '../helpers/is-date-safe'
import updateImageWrapper from '../helpers/update-image-wrapper'
import capitalize from '../helpers/capitalize'

const downloadImage = new Image()

const defaultSettings = {
  isAsync: false,
  isHd: false
}

class AppContainer extends Component {
  constructor(props) {
    super(props)
    this.state = {
      showOverlay: false,
      showAbout: false,
      showImageOverlay: false,
      showSettings: false,
      showDescription: false,
      showTitle: true,
      settings: defaultSettings,
      tries: 0,
      isFailure: false,
      isLoadingData: true,
      isLoadingImage: true
    }

    this.setSetting = this.setSetting.bind(this)
    this.handleActionClick = this.handleActionClick.bind(this)
    this.handleImageClick = this.handleImageClick.bind(this)
    this.handleOverlayClick = this.handleOverlayClick.bind(this)
    this.handleToggleClick = this.handleToggleClick.bind(this)
    this.loadImage = this.loadImage.bind(this)
    this.updateImage =
      config.mode === AppConstants.DEV_MODE
        ? updateImageWrapper.bind(this, this.updateImage)
        : this.updateImage.bind(this)
  }

  makeRequest(currentDate, type) {
    let date

    switch (type) {
      case ActionType.NEWEST:
        date = currentDate
        type = ActionType.LATEST
        DaySort.LATEST = date
        break
      case ActionType.LATEST:
        date = yesterday(currentDate)
        DaySort.LATEST = date
        break
      case ActionType.PREVIOUS:
        date = yesterday(currentDate)
        break
      case ActionType.NEXT:
        date = tomorrow(currentDate)
        break
      case ActionType.SHUFFLE:
        date = shuffleDate()
        break
    }

    this.setState({}, () => {
      getByDate(date)
        .then(this.receive.bind(this, date))
        .catch(err => this.handleRejection.bind(this, err, date, type)())
    })
  }

  receive(date, data) {
    this.setState(
      {
        title: data.title,
        explanation: data.explanation,
        date: date,
        url: data.url,
        hdurl: data.hdurl,
        tries: 0,
        isFailure: false,
        isLoadingData: false
      },
      this.loadImage
    )
  }

  loadImage() {
    this.setState(
      {
        isLoadingImage: true
      },
      () => {
        let imageUrl = this.state.settings.isHd
          ? this.state.hdurl
          : this.state.url
        if (this.state.settings.isHd) {
          imageUrl = isDateSafe(this.state.date)
            ? (imageUrl = this.state.hdurl)
            : (imageUrl = this.state.url)
        }

        if (this.state.settings.isAsync) {
          this.updateImage(imageUrl)
        } else {
          downloadImage.onload = this.updateImage.bind(this, imageUrl)
          downloadImage.src = imageUrl
        }
      }
    )
  }

  updateImage(imageUrl) {
    this.setState({
      imageUrl: imageUrl,
      isLoadingImage: false
    })
  }

  handleRejection(err, date, type) {
    if (this.state.tries >= AppConstants.MAX_TRY) {
      this.setState({
        date,
        isFailure: true,
        isLoadingData: false,
        isLoadingImage: false
      })
    } else {
      this.setState(
        {
          tries: this.state.tries + 1
        },
        this.makeRequest(date, type)
      )
    }
  }

  handleActionClick(actionType) {
    const date =
      actionType === ActionType.NEWEST ? DaySort.NEWEST : this.state.date
    this.setState(
      {
        isLoadingData: true,
        isLoadingImage: true,
        showSettings: false,
        imageUrl: ''
      },
      this.makeRequest(date, actionType)
    )
  }

  handleToggleClick(type) {
    const property = `show${capitalize(type)}`

    this.setState({
      [property]: !this.state[property]
    })
  }

  handleImageClick(e) {
    e.preventDefault()

    this.setState({
      showImageOverlay: true
    })
  }

  handleOverlayClick(type, e) {
    const property = `show${capitalize(type)}`
    if (e.target.closest(`#${type.toLowerCase()}`)) return

    this.setState({
      [property]: false,
      showSettings: false
    })
  }

  render() {
    return (

        <Description
          showDescription={this.state.showDescription}
          isLoadingData={this.state.isLoadingData}
          isLoadingImage={this.state.isLoadingImage}
          description={this.state.explanation}
          title={this.state.title}
        />
        <Overlay
          showOverlay={this.state.showAbout}
          onOverlayClick={this.handleOverlayClick.bind(null, ToggleType.ABOUT)}
        >
          <About />
        </Overlay>

        <AppWrapper
          isAsync={this.state.settings.isAsync}
          isFailure={this.state.isFailure}
          isLoadingImage={this.state.isLoadingImage}
          tries={this.state.tries}
        >
          <ImageWrapper
            imageUrl={this.state.imageUrl}
            onImageClick={this.handleImageClick}
          />

          <Overlay
            showOverlay={this.state.showImageOverlay}
            onOverlayClick={this.handleOverlayClick.bind(
              null,
              ToggleType.IMAGE_OVERLAY
            )}
          >
            <ImageOverlay imageUrl={this.state.imageUrl} />
          </Overlay>
        </AppWrapper>

    )
  }

  setSetting(settingType, value) {
    localStorage.setItem(settingType.toString(), value)
    const newSettings = this.state.settings
    newSettings[settingType] = value

    this.setState(
      {
        settings: newSettings
      },
      () => {
        if (!this.state.isLoadingData) this.loadImage()
      }
    )
  }

  loadSettings() {
    const isAsync =
      localStorage.getItem(SettingType.IS_ASYNC.toString()) !== null
        ? JSON.parse(localStorage.getItem(SettingType.IS_ASYNC.toString()))
        : defaultSettings.isAsync
    const isHd =
      localStorage.getItem(SettingType.IS_HD.toString()) !== null
        ? JSON.parse(localStorage.getItem(SettingType.IS_HD.toString()))
        : defaultSettings.isHd

    const newSettings = {
      isAsync,
      isHd
    }

    this.setState({
      settings: newSettings
    })
  }

  componentWillMount() {
    this.loadSettings()
    disableHoverEffectsOnMobile(window)
    this.makeRequest(DaySort.NEWEST, ActionType.NEWEST)
  }
}

export default AppContainer
<|endoftext|>


<commit_before>(function() {
	var $varTab         = $('#variables-tab');
	var $varTabButton   = $('#variables-tab-button');
	var $funcTab        = $('#functions-tab');
	var $funcTabButton  = $('#functions-tab-button');
	var $classTab       = $('#classes-tab');
	var $classTabButton = $('#classes-tab-button');

	function clearTabSettings() {
		$varTab.hide();
		$funcTab.hide();
		$classTab.hide();
		$varTabButton.removeClass('active');
		$funcTabButton.removeClass('active');
		$classTabButton.removeClass('active');
	}

	// The variables, functions, and classes tabs are all identical, but need to
	// hold independent data that we can switch between them using the tab UI.
	$varTab.children().clone().appendTo($funcTab);
	$varTab.children().clone().appendTo($classTab);

	// Tab switching functionality
	$varTabButton.click(function(e) {
		e.preventDefault();
		clearTabSettings();
		$varTab.show();
		$varTabButton.addClass('active');
	});
	$funcTabButton.click(function(e) {
		e.preventDefault();
		clearTabSettings();
		$funcTab.show();
		$funcTabButton.addClass('active');
	});
	$classTabButton.click(function(e) {
		e.preventDefault();
		clearTabSettings();
		$classTab.show();
		$classTabButton.addClass('active');
	});
})();

var updateSearch;
(function() {
	var $searchError         = $('#search-error');
	var $searchInput         = $('#search-input');
	var $searchResults       = $('#search-results');
	var $normalizedQuery     = $('#normalized-query');
	var $varTab              = $('#variables-tab');
	var $funcTab             = $('#functions-tab');
	var $classTab            = $('#classes-tab');
	var $alternatives        = $('#alternatives');
	var $relatedTerms        = $('#related-terms');
	var $commonUses          = $('#common-uses');
	var $specificityCategory = $('#specificity-category');
	var $specificityValue    = $('#specificity-value');
	var $specificityBrief    = $('#specificity-brief');
	var $examples            = $('#examples');
	var currentSearchTerm    = null;
	var currentSearchResults = null;

	function getSpecificityCategory(value) {
		if (value < -0.4) {
			return 'generic';
		}
		if (value > 0.4) {
			return 'specific';
		}
		return 'neutral';
	}

	function getSpecificityBrief(category) {
		var briefs = {
			'generic': 'lifetime/scope should be kept as small as possible',
			'neutral': 'could be better, but if you\'re stuck, this name should suffice',
			'specific': 'leaves little room for ambiguity; good choice'
		};
		return briefs[category];
	}

	function loadSearchResults(results) {
		currentSearchResults = results;

		// Set the noramlized query text, so the user know what results they're
		// looking at (not always immediately clear, due to the normalization
		// process).
		$normalizedQuery.text(results.searchTerm);

		// Clear all previous results
		[$varTab, $funcTab, $classTab].forEach(function($tab) {
			$tab.find('.alternatives').empty();
			$tab.find('.related-terms').empty();
			$tab.find('.common-uses').empty();
			$tab.find('.specificity-value').html('');
			$tab.find('.specificity-category').html('');
			$tab.find('.specificity-brief').html('');
			$tab.find('.examples').empty();
		});

		// Load the results
		results.resultTerms.forEach(function(termData) {
			var $tab = null;
			if (termData.type === 'variable') {
				$tab = $varTab;
			} else if (termData.type === 'function') {
				$tab = $funcTab;
			} else if (termData.type === 'class') {
				$tab = $classTab;
			} else {
				console.log('unknown term type:', termData);
				return;
			}

			termData.alternatives.forEach(function(altTerm) {
				$tab.find('.alternatives').append(
					$('<li/>').append(
						$('<button/>', {
							'class': 'term',
							text: altTerm
						})
					)
				);
			});

			termData.relatedTerms.forEach(function(relTerm) {
				$tab.find('.related-terms').append(
					$('<li/>').append(
						$('<button/>', {
							'class': 'term',
							text: relTerm
						})
					)
				);
			});

			termData.commonUses.forEach(function(use) {
				$tab.find('.common-uses').append(
					$('<li/>', { text: use })
				);
			});

			var sValue = termData.specificity;
			var sCategory = getSpecificityCategory(sValue);
			var sBrief = getSpecificityBrief(sCategory);
			$tab.find('.specificity-value').html(sValue);
			$tab.find('.specificity-category').html(sCategory);
			$tab.find('.specificity-brief').html(sBrief);

			termData.examples.forEach(function(ex) {
				$tab.find('.examples').append(
					$('<li/>')
						.append(
							$('<a/>', {
								href: ex.projectUrl,
								text: ex.projectName
							})
						)
						.append(
							$('<span/>', { html: ' – ' })
						)
						.append(
							$('<a/>', {
								href: ex.exampleUrl,
								text: ex.exampleName
							})
						)
				);
			});
		});

		// Make 'term' links initiate a new search
		[$varTab, $funcTab, $classTab].forEach(function($tab) {
			$tab.find('.term').click(function(e) {
				e.preventDefault();
				var t = $(e.target).text();
				$searchInput.val(t);
				updateSearch(t);
			});
		});
	}

	updateSearch = function(term) {
		if (currentSearchTerm === term) {
			return;
		}
		currentSearchTerm = term;

		$searchError.hide();
		$.getJSON(apiUrl + '/search', {q: term})
			.done(function(results) {
				// If there was a long delay in our request for some reason, and
				// another search started before we finished, we don't want these
				// results; let the other search go through.
				if (currentSearchTerm !== term) {
					return;
				}
				loadSearchResults(results);
				$searchResults.removeClass('hidden');
			})
			.fail(function(jqXHR, textStatus, error) {
				if (currentSearchTerm !== term) {
					return;
				}

				console.log('search failed:', jqXHR, textStatus, error);
				$searchError.html('uh-oh, there was a problem with the search');
				$searchError.show();

				// Reset the search term, so if the user tries the same search
				// again in a couple minutes, the search will go through.
				currentSearchTerm = null;
			});

	}
})();

(function() {
	var $searchError         = $('#search-error');
	var $searchResults       = $('#search-results');
	var $searchInput         = $('#search-input');
	var $main                = $('main');
	var instantSearchDelay   = 500; // ms
	var instantSearchTimeout = null;

	// When the user starts typing, enable search mode
	$searchInput.on('input', function() {
		var term = $searchInput.val();

		// Every time the user types, we fade the previous results. This is to
		// cleanly transition between searches (which is especially important
		// with instant search).
		$searchResults.addClass('hidden');
		$searchError.hide();

		// If the term is empty, we have nothing to search
		if (term.length) {

			// If this is the users first search since page load, we need to hide
			// the banner header
			$main.addClass('search-mode');

			// We want to implement instant search so users can get their results
			// asap. However, we don't want to make an API request for each and
			// every keypress, so instead we use a timeout. If the user stops
			// typing for X milliseconds, we're allowed to make a request.
			clearTimeout(instantSearchTimeout);
			instantSearchTimeout = setTimeout(function() {
				updateSearch(term);
			}, instantSearchDelay);
		}
	});

	// When the search box loses focus, the search results should be updated. If
	// there is no search term (the user has cleared the input), disable search
	// mode.
	$searchInput.on('focusout', function() {
		if (!$searchInput.val().length) {
			$main.removeClass('search-mode');
			$searchError.hide();
		} else {
			updateSearch($searchInput.val());
		}
	});

	// Allow the user to press enter to complete the search
	$searchInput.on('keydown', function(e) {
		if (e.which === 13) {
			this.blur();
		}
	});
})();
<commit_msg>Fixed subtle search bugs (primarily race conditions).<commit_after>(function() {
	var $varTab         = $('#variables-tab');
	var $varTabButton   = $('#variables-tab-button');
	var $funcTab        = $('#functions-tab');
	var $funcTabButton  = $('#functions-tab-button');
	var $classTab       = $('#classes-tab');
	var $classTabButton = $('#classes-tab-button');

	function clearTabSettings() {
		$varTab.hide();
		$funcTab.hide();
		$classTab.hide();
		$varTabButton.removeClass('active');
		$funcTabButton.removeClass('active');
		$classTabButton.removeClass('active');
	}

	// The variables, functions, and classes tabs are all identical, but need to
	// hold independent data that we can switch between them using the tab UI.
	$varTab.children().clone().appendTo($funcTab);
	$varTab.children().clone().appendTo($classTab);

	// Tab switching functionality
	$varTabButton.click(function(e) {
		e.preventDefault();
		clearTabSettings();
		$varTab.show();
		$varTabButton.addClass('active');
	});
	$funcTabButton.click(function(e) {
		e.preventDefault();
		clearTabSettings();
		$funcTab.show();
		$funcTabButton.addClass('active');
	});
	$classTabButton.click(function(e) {
		e.preventDefault();
		clearTabSettings();
		$classTab.show();
		$classTabButton.addClass('active');
	});
})();

var clearSearch;
var updateSearch;
(function() {
	var $searchError         = $('#search-error');
	var $searchInput         = $('#search-input');
	var $searchResults       = $('#search-results');
	var $normalizedQuery     = $('#normalized-query');
	var $varTab              = $('#variables-tab');
	var $funcTab             = $('#functions-tab');
	var $classTab            = $('#classes-tab');
	var $alternatives        = $('#alternatives');
	var $relatedTerms        = $('#related-terms');
	var $commonUses          = $('#common-uses');
	var $specificityCategory = $('#specificity-category');
	var $specificityValue    = $('#specificity-value');
	var $specificityBrief    = $('#specificity-brief');
	var $examples            = $('#examples');
	var currentSearchTerm    = null;
	var currentSearchResults = null;

	function getSpecificityCategory(value) {
		if (value < -0.4) {
			return 'generic';
		}
		if (value > 0.4) {
			return 'specific';
		}
		return 'neutral';
	}

	function getSpecificityBrief(category) {
		var briefs = {
			'generic': 'lifetime/scope should be kept as small as possible',
			'neutral': 'could be better, but if you\'re stuck, this name should suffice',
			'specific': 'leaves little room for ambiguity; good choice'
		};
		return briefs[category];
	}

	function clearSearchResults() {
		currentSearchResults = null;
		$normalizedQuery.text('');
		[$varTab, $funcTab, $classTab].forEach(function($tab) {
			$tab.find('.alternatives').empty();
			$tab.find('.related-terms').empty();
			$tab.find('.common-uses').empty();
			$tab.find('.specificity-value').text('');
			$tab.find('.specificity-category').text('');
			$tab.find('.specificity-brief').text('');
			$tab.find('.examples').empty();
		});
	}

	function loadSearchResults(results) {
		clearSearchResults();

		currentSearchResults = results;

		// Set the noramlized query text, so the user know what results they're
		// looking at (not always immediately clear, due to the normalization
		// process).
		$normalizedQuery.text(results.searchTerm);

		// Load the results
		results.resultTerms.forEach(function(termData) {
			var $tab = null;
			if (termData.type === 'variable') {
				$tab = $varTab;
			} else if (termData.type === 'function') {
				$tab = $funcTab;
			} else if (termData.type === 'class') {
				$tab = $classTab;
			} else {
				console.log('unknown term type:', termData);
				return;
			}

			termData.alternatives.forEach(function(altTerm) {
				$tab.find('.alternatives').append(
					$('<li/>').append(
						$('<button/>', {
							'class': 'term',
							text: altTerm
						})
					)
				);
			});

			termData.relatedTerms.forEach(function(relTerm) {
				$tab.find('.related-terms').append(
					$('<li/>').append(
						$('<button/>', {
							'class': 'term',
							text: relTerm
						})
					)
				);
			});

			termData.commonUses.forEach(function(use) {
				$tab.find('.common-uses').append(
					$('<li/>', { text: use })
				);
			});

			var sValue = termData.specificity;
			var sCategory = getSpecificityCategory(sValue);
			var sBrief = getSpecificityBrief(sCategory);
			$tab.find('.specificity-value').html(sValue);
			$tab.find('.specificity-category').html(sCategory);
			$tab.find('.specificity-brief').html(sBrief);

			termData.examples.forEach(function(ex) {
				$tab.find('.examples').append(
					$('<li/>')
						.append(
							$('<a/>', {
								href: ex.projectUrl,
								text: ex.projectName
							})
						)
						.append(
							$('<span/>', { html: ' – ' })
						)
						.append(
							$('<a/>', {
								href: ex.exampleUrl,
								text: ex.exampleName
							})
						)
				);
			});
		});

		// Make 'term' links initiate a new search
		[$varTab, $funcTab, $classTab].forEach(function($tab) {
			$tab.find('.term').click(function(e) {
				e.preventDefault();
				var t = $(e.target).text();
				$searchInput.val(t);
				updateSearch(t);
			});
		});
	}

	clearSearch = function() {
		currentSearchTerm = null;
		clearSearchResults();
	}

	updateSearch = function(term) {
		currentSearchTerm = term;

		$searchError.hide();
		$.getJSON(apiUrl + '/search', {q: term})
			.done(function(results) {
				// If there was a long delay in our request for some reason, and
				// another search started before we finished, we don't want these
				// results; let the other search go through.
				if (currentSearchTerm !== term) {
					return;
				}
				loadSearchResults(results);
				$searchResults.removeClass('hidden');
			})
			.fail(function(jqXHR, textStatus, error) {
				if (currentSearchTerm !== term) {
					return;
				}

				console.log('search failed:', jqXHR, textStatus, error);
				$searchError.html('uh-oh, there was a problem with the search');
				$searchError.show();

				// Reset the search term, so if the user tries the same search
				// again in a couple minutes, the search will go through.
				currentSearchTerm = null;
			});

	}
})();

(function() {
	var $searchError         = $('#search-error');
	var $searchResults       = $('#search-results');
	var $searchInput         = $('#search-input');
	var $main                = $('main');
	var instantSearchDelay   = 500; // ms
	var instantSearchTimeout = null;

	// When the user starts typing, enable search mode
	$searchInput.on('input', function() {
		var term = $searchInput.val();

		// Every time the user types, we fade the previous results. This is to
		// cleanly transition between searches (which is especially important
		// with instant search).
		$searchResults.addClass('hidden');
		$searchError.hide();

		// If the term is empty, we have nothing to search
		if (term.length) {

			// If this is the users first search since page load, we need to hide
			// the banner header
			$main.addClass('search-mode');

			// We want to implement instant search so users can get their results
			// asap. However, we don't want to make an API request for each and
			// every keypress, so instead we use a timeout. If the user stops
			// typing for X milliseconds, we're allowed to make a request.
			clearTimeout(instantSearchTimeout);
			instantSearchTimeout = setTimeout(function() {
				updateSearch(term);
			}, instantSearchDelay);
		}
	});

	// When the search box loses focus, the search results should be updated. If
	// there is no search term (the user has cleared the input), disable search
	// mode.
	$searchInput.on('focusout', function() {
		if (!$searchInput.val().length) {
			$main.removeClass('search-mode');
			$searchError.hide();
			clearSearch();
		} else {
			clearTimeout(instantSearchTimeout);
			updateSearch($searchInput.val());
		}
	});

	// Allow the user to press enter to complete the search
	$searchInput.on('keydown', function(e) {
		if (e.which === 13) {
			this.blur();
		}
	});
})();
<|endoftext|>