<commit_before>/* ========================================================================
 * Bootstrap: scrollspy.js v3.1.1
 * http://getbootstrap.com/javascript/#scrollspy
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

+function ($) {
  'use strict';

  // SCROLLSPY CLASS DEFINITION
  // ==========================

  function ScrollSpy(element, options) {
    var href
    var process  = $.proxy(this.process, this)

    this.$element       = $(element).is('body') ? $(window) : $(element)
    this.$body          = $('body')
    this.$scrollElement = this.$element.on('scroll.bs.scrollspy', process)
    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
    this.selector       = (this.options.target
      || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
      || '') + ' .nav li > a'
    this.offsets        = []
    this.targets        = []
    this.activeTarget   = null

    this.refresh()
    this.process()
  }

  ScrollSpy.VERSION  = '3.1.1'

  ScrollSpy.DEFAULTS = {
    offset: 10
  }

  ScrollSpy.prototype.refresh = function () {
    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'

    this.offsets = []
    this.targets = []

    var self     = this

    this.$body
      .find(this.selector)
      .filter(':visible')
      .map(function () {
        var $el   = $(this)
        var href  = $el.data('target') || $el.attr('href')
        var $href = /^#./.test(href) && $(href)

        return ($href
          && $href.length
          && $href.is(':visible')
          && [[$href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href]]) || null
      })
      .sort(function (a, b) { return a[0] - b[0] })
      .each(function () {
        self.offsets.push(this[0])
        self.targets.push(this[1])
      })
  }

  ScrollSpy.prototype.process = function () {
    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
    var scrollHeight = this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
    var offsets      = this.offsets
    var targets      = this.targets
    var activeTarget = this.activeTarget
    var i

    if (scrollTop >= maxScroll) {
      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
    }

    if (activeTarget && scrollTop <= offsets[0]) {
      return activeTarget != (i = targets[0]) && this.activate(i)
    }

    for (i = offsets.length; i--;) {
      activeTarget != targets[i]
        && scrollTop >= offsets[i]
        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
        && this.activate(targets[i])
    }
  }

  ScrollSpy.prototype.activate = function (target) {
    this.activeTarget = target

    $(this.selector)
      .parentsUntil(this.options.target, '.active')
      .removeClass('active')

    var selector = this.selector +
        '[data-target="' + target + '"],' +
        this.selector + '[href="' + target + '"]'

    var active = $(selector)
      .parents('li')
      .addClass('active')

    if (active.parent('.dropdown-menu').length) {
      active = active
        .closest('li.dropdown')
        .addClass('active')
    }

    active.trigger('activate.bs.scrollspy')
  }

  // SCROLLSPY PLUGIN DEFINITION
  // ===========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.scrollspy')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.scrollspy

  $.fn.scrollspy             = Plugin
  $.fn.scrollspy.Constructor = ScrollSpy

  // SCROLLSPY NO CONFLICT
  // =====================

  $.fn.scrollspy.noConflict = function () {
    $.fn.scrollspy = old
    return this
  }

  // SCROLLSPY DATA-API
  // ==================

  $(window).on('load.bs.scrollspy.data-api', function () {
    $('[data-spy="scroll"]').each(function () {
      var $spy = $(this)
      Plugin.call($spy, $spy.data())
    })
  })

}(jQuery);
<commit_msg>no need to check for href attribute on the scrollable element<commit_after>/* ========================================================================
 * Bootstrap: scrollspy.js v3.1.1
 * http://getbootstrap.com/javascript/#scrollspy
 * ========================================================================
 * Copyright 2011-2014 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

+function ($) {
  'use strict';

  // SCROLLSPY CLASS DEFINITION
  // ==========================

  function ScrollSpy(element, options) {
    var process  = $.proxy(this.process, this)

    this.$element       = $(element).is('body') ? $(window) : $(element)
    this.$body          = $('body')
    this.$scrollElement = this.$element.on('scroll.bs.scrollspy', process)
    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
    this.selector       = (this.options.target || '') + ' .nav li > a'
    this.offsets        = []
    this.targets        = []
    this.activeTarget   = null

    this.refresh()
    this.process()
  }

  ScrollSpy.VERSION  = '3.1.1'

  ScrollSpy.DEFAULTS = {
    offset: 10
  }

  ScrollSpy.prototype.refresh = function () {
    var offsetMethod = this.$element[0] == window ? 'offset' : 'position'

    this.offsets = []
    this.targets = []

    var self     = this

    this.$body
      .find(this.selector)
      .filter(':visible')
      .map(function () {
        var $el   = $(this)
        var href  = $el.data('target') || $el.attr('href')
        var $href = /^#./.test(href) && $(href)

        return ($href
          && $href.length
          && $href.is(':visible')
          && [[$href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href]]) || null
      })
      .sort(function (a, b) { return a[0] - b[0] })
      .each(function () {
        self.offsets.push(this[0])
        self.targets.push(this[1])
      })
  }

  ScrollSpy.prototype.process = function () {
    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
    var scrollHeight = this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
    var offsets      = this.offsets
    var targets      = this.targets
    var activeTarget = this.activeTarget
    var i

    if (scrollTop >= maxScroll) {
      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
    }

    if (activeTarget && scrollTop <= offsets[0]) {
      return activeTarget != (i = targets[0]) && this.activate(i)
    }

    for (i = offsets.length; i--;) {
      activeTarget != targets[i]
        && scrollTop >= offsets[i]
        && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
        && this.activate(targets[i])
    }
  }

  ScrollSpy.prototype.activate = function (target) {
    this.activeTarget = target

    $(this.selector)
      .parentsUntil(this.options.target, '.active')
      .removeClass('active')

    var selector = this.selector +
        '[data-target="' + target + '"],' +
        this.selector + '[href="' + target + '"]'

    var active = $(selector)
      .parents('li')
      .addClass('active')

    if (active.parent('.dropdown-menu').length) {
      active = active
        .closest('li.dropdown')
        .addClass('active')
    }

    active.trigger('activate.bs.scrollspy')
  }

  // SCROLLSPY PLUGIN DEFINITION
  // ===========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.scrollspy')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.scrollspy

  $.fn.scrollspy             = Plugin
  $.fn.scrollspy.Constructor = ScrollSpy

  // SCROLLSPY NO CONFLICT
  // =====================

  $.fn.scrollspy.noConflict = function () {
    $.fn.scrollspy = old
    return this
  }

  // SCROLLSPY DATA-API
  // ==================

  $(window).on('load.bs.scrollspy.data-api', function () {
    $('[data-spy="scroll"]').each(function () {
      var $spy = $(this)
      Plugin.call($spy, $spy.data())
    })
  })

}(jQuery);
<|endoftext|>


<commit_before>var Sequences;
(function (Sequences) {
    'use strict';

    const iterSymbol = self.Symbol ? self.Symbol.iterator : "@@iterator";

    const writable = value => ({ writable: true, value });

    const numericArg = (arg, def) => {
        const result = Number(arg);
        return isNaN(result) ? def : result;
    };

    Object.defineProperties(Sequences, {
        numbers: writable((initialValue, step) => {
            return function* (initialValue_, step_) {
                let n = initialValue_ || initialValue || 0;
                const s = step_ || step || 1;
                while (true) {
                    yield n;
                    n += s;
                }
            };
        }),
        toGenerator: writable((source, initialValue) => {
            if (source) {
                if (typeof source === 'object' && iterSymbol in source) {
                    return function* () {
                        yield* source[iterSymbol]();
                    };
                }
                if (typeof source === 'function') {
                    return source.isGenerator() ? source : function* (...args) {
                        while (true) {
                            yield source(...args);
                        }
                    };
                }
                if (typeof source.length === 'number') {
                    return function* () {
                        for (let i = initialValue || 0; i < source.length; ++i) {
                            yield source[i];
                        }
                    };
                }
            }
            return function* generatorFromValue() {
                yield source;
            };
        }),
        toFilter: writable(function toFilter(value) {
            if (typeof value === 'function' && !value.isGenerator()) {
                return value;
            }
            const gen = Sequences.toGenerator(value);
            return v => gen.filter(x => x === v).head(1).reduce(() => true, false);
        })
    });

    const proto = Object.getPrototypeOf(Sequences.numbers());
    if (!proto.isGenerator) {
        Object.defineProperties(proto, {
            isGenerator: writable(() => true)
        });
        Object.defineProperties(Function.prototype, {
            isGenerator: writable(() => false)
        });
    }

    const not = func => function (...args) {
        return !func.apply(this, args)
    };

    const bind = Function.prototype.bind;
    Object.defineProperties(proto, {
        bind: writable(function (...args) {
            const result = bind.apply(this, args);
            Object.setPrototypeOf(result, proto);
            return result;
        }),
        forEach: writable(function (callback, ...args) {
            const i = this(...args);
            let r;
            while (true) {
                const x = i.next(r);
                if (x.done) {
                    break;
                }
                r = callback(x.value);
            }
        }),
        until: writable(function (filter) {
            const gen = this;
            const callback = Sequences.toFilter(filter);
            return function* (...args) {
                const i = gen(...args);
                let r;
                for (let counter = 0; ; ++counter) {
                    const x = i.next(r);
                    if (x.done || callback(x.value, r, counter) === true) {
                        return x.value;
                    }
                    r = yield x.value;
                }
            };
        }),
        asLongAs: writable(function (filter) {
            return this.until(not(Sequences.toFilter(filter)));
        }),
        head: writable(function (length) {
            return this.until((v, r, counter) => counter >= length);
        }),
        filter: writable(function (filter) {
            const gen = this;
            const callback = Sequences.toFilter(filter);
            return function* (...args) {
                for (let i of gen(...args)) {
                    if (callback(i)) {
                        yield i;
                    }
                }
            };
        }),
        exclude: writable(function (filter) {
            return this.filter(not(Sequences.toFilter(filter)));
        }),
        skip: writable(function skip(filter) {
            const gen = this;
            if (typeof filter === 'number') {
                const limit = filter;
                filter = (x, counter) => counter <= limit;
            }
            return function* (...args) {
                const callback = Sequences.toFilter(filter);
                var iter = gen(...args);
                let counter = 0;
                for (var i of iter) {
                    if (!callback(i, ++counter)) {
                        yield i;
                        break;
                    }
                }
                yield* iter;
            };
        }),
        map: writable(function (callback) {
            const gen = this;
            return function* (...args) {
                const i = gen(...args);
                let r;
                while (true) {
                    const x = i.next(r);
                    const v = callback(x.value, r);
                    if (x.done) {
                        return v;
                    }
                    r = yield v;
                }
            };
        }),
        inverseMap: writable(function (callback) {
            const gen = this;
            return function* (...args) {
                const i = gen(...args);
                let r;
                while (true) {
                    const x = i.next(r);
                    if (x.done) {
                        break;
                    }
                    r = callback(yield x.value, x.value);
                }
            };
        }),
        flatten: writable(function (depth) {
            const gen = this;
            depth = numericArg(depth, 0);
            return function* (...args) {
                for (let i of gen(...args)) {
                    const generator = Sequences.toGenerator(i);
                    if (depth === 0 || generator().next().value === i) {
                        yield i;
                    } else {
                        yield* generator.flatten(depth - 1)(...args);
                    }
                }
            };
        }),
        loop: writable(function (times) {
            const gen = this;
            times = numericArg(times, Infinity);
            return function* (...args) {
                for (let i = 0; i < times; ++i) {
                    yield* gen(...args);
                }
            };
        }),
        concat: writable(function (...args) {
            const gens = Sequences.toGenerator([this, ...args]).map(Sequences.toGenerator);
            return function* (...args) {
                for (var i of gens()) {
                    yield* i(...args);
                }
            };
        }),
        reduce: writable(function (callback, r, ...args) {
            const i = this(...args);
            while (true) {
                const x = i.next(r);
                if (x.done) {
                    break;
                }
                r = callback(r, x.value);
            }
            return r;
        }),
        combine: writable(function (...gens) {
            gens = [this, ...gens.map(Sequences.toGenerator)];
            return function* (...args) {
                var iters = gens.map(gen => gen(...args));
                while (true) {
                    const nexts = iters.map(iter => iter.next());
                    const results = nexts.map(r => r.value);
                    if (nexts.some(r => r.done)) {
                        return results;
                    }
                    yield results;
                }
            };
        }),
        tee: writable(function (...args) {
            Sequences.toGenerator(args).filter(arg => typeof arg === 'function').forEach(arg => arg(this));
            return this;
        }),
        indexOf: writable(function (value, fromIndex, ...args) {
            const gen = args.length ? this.bind(null, ...args) : this;
            return gen
                .combine(Sequences.numbers())
                .skip(fromIndex)
                .filter(v => v[0] === value)
                .head()
                .reduce((r, v) => v[1], -1);
        }),
        use: writable(function (callback, ...args) {
            return callback(this(...args));
        })
    });
}(Sequences || (Sequences = {})));
<commit_msg>Delete sequences.js<commit_after><|endoftext|>


<commit_before>(function(OCA, OC) {
	'use strict';

	OCA.SpreedMe = OCA.SpreedMe || {};

	function SignalingBase() {
		this.sessionId = '';
		this.currentCallToken = null;
		this.handlers = {};
	}

	SignalingBase.prototype.on = function(ev, handler) {
		if (!this.handlers.hasOwnProperty(ev)) {
			this.handlers[ev] = [handler];
		} else {
			this.handlers[ev].push(handler);
		}
	};

	SignalingBase.prototype.emit = function(/*ev, data*/) {
		// Override in subclasses.
	};

	SignalingBase.prototype._trigger = function(ev, args) {
		var handlers = this.handlers[ev];
		if (!handlers) {
			return;
		}

		handlers = handlers.slice(0);
		for (var i = 0, len = handlers.length; i < len; i++) {
			var handler = handlers[i];
			handler.apply(handler, args);
		}
	};

	SignalingBase.prototype.getSessionid = function() {
		return this.sessionId;
	};

	SignalingBase.prototype.disconnect = function() {
		this.sessionId = '';
		this.currentCallToken = null;
	};

	SignalingBase.prototype.emit = function(ev, data) {
		switch (ev) {
			case 'join':
				var callback = arguments[2];
				var token = data;
				this.joinCall(token, callback);
				break;
			case 'leave':
				this.leaveCurrentCall();
				break;
			case 'message':
				this.sendCallMessage(data);
				break;
		}
	};

	SignalingBase.prototype.leaveCurrentCall = function() {
		if (this.currentCallToken) {
			this.leaveCall(this.currentCallToken);
			this.currentCallToken = null;
		}
	};

	SignalingBase.prototype.leaveAllCalls = function() {
		// Override if necessary.
	};

	SignalingBase.prototype.setRoomCollection = function(rooms) {
		this.roomCollection = rooms;
		return this.syncRooms();
	};

	SignalingBase.prototype.syncRooms = function() {
		var defer = $.Deferred();
		if (this.roomCollection && oc_current_user) {
			this.roomCollection.fetch({
				success: function(data) {
					defer.resolve(data);
				}
			});
		} else {
			defer.resolve([]);
		}
		return defer;
	};

	// Connection to the internal signaling server provided by the app.
	function InternalSignaling() {
		SignalingBase.prototype.constructor.apply(this, arguments);
		this.spreedArrayConnection = [];
		this._openEventSource();

		this.pingFails = 0;
		this.pingInterval = null;

		this.sendInterval = window.setInterval(function(){
			this.sendPendingMessages();
		}.bind(this), 500);
	}

	InternalSignaling.prototype = new SignalingBase();
	InternalSignaling.prototype.constructor = InternalSignaling;

	InternalSignaling.prototype.disconnect = function() {
		this.spreedArrayConnection = [];
		if (this.source) {
			this.source.close();
			this.source = null;
		}
		if (this.sendInterval) {
			window.clearInterval(this.sendInterval);
			this.sendInterval = null;
		}
		if (this.pingInterval) {
			window.clearInterval(this.pingInterval);
			this.pingInterval = null;
		}
		if (this.roomPoller) {
			window.clearInterval(this.roomPoller);
			this.roomPoller = null;
		}
		SignalingBase.prototype.disconnect.apply(this, arguments);
	};

	InternalSignaling.prototype.on = function(ev/*, handler*/) {
		SignalingBase.prototype.on.apply(this, arguments);

		switch (ev) {
			case 'connect':
				// A connection is established if we can perform a request
				// through it.
				this._sendMessageWithCallback(ev);
				break;

			case 'stunservers':
			case 'turnservers':
				// Values are not pushed by the server but have to be explicitly
				// requested.
				this._sendMessageWithCallback(ev);
				break;
		}
	};

	InternalSignaling.prototype._sendMessageWithCallback = function(ev) {
		var message = [{
			ev: ev
		}];
		$.post(OC.generateUrl('/apps/spreed/signalling'), {
			messages: JSON.stringify(message)
		}, function(data) {
			this._trigger(ev, [data]);
		}.bind(this));
	};

	InternalSignaling.prototype.joinCall = function(token, callback) {
		// The client is joining a new call, in this case we need
		// to do the following:
		//
		// 1. Join the call as participant.
		// 2. Get a list of other connected clients in the call.
		// 3. Pass information about other clients to the callback.
		//
		// The clients will then use the message command to exchange
		// their signalling information.
		$.ajax({
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
			type: 'POST',
			beforeSend: function (request) {
				request.setRequestHeader('Accept', 'application/json');
			},
			success: function (result) {
				console.log("Joined", result);
				this.sessionId = result.ocs.data.sessionId;
				this.currentCallToken = token;
				this._startPingCall();
				this._getCallPeers(token).then(function(peers) {
					var callDescription = {
						'clients': {}
					};

					peers.forEach(function(element) {
						if (this.sessionId !== element['sessionId']) {
							callDescription['clients'][element['sessionId']] = {
								'video': true
							};
						}
					}.bind(this));
					callback('', callDescription);
				}.bind(this));
			}.bind(this)
		});
	};

	InternalSignaling.prototype.leaveCall = function(token) {
		if (token === this.currentCallToken) {
			this._stopPingCall();
		}
		$.ajax({
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
			method: 'DELETE',
			async: false
		});
	};

	InternalSignaling.prototype.sendCallMessage = function(data) {
		if(data.type === 'answer') {
			console.log("ANSWER", data);
		} else if(data.type === 'offer') {
			console.log("OFFER", data);
		}
		this.spreedArrayConnection.push({
			ev: "message",
			fn: JSON.stringify(data),
			sessionId: this.sessionId
		});
	};

	InternalSignaling.prototype.setRoomCollection = function(rooms) {
		this._pollForRoomChanges();
		return SignalingBase.prototype.setRoomCollection.apply(this, arguments);
	};

	InternalSignaling.prototype._pollForRoomChanges = function() {
		if (this.roomPoller) {
			window.clearInterval(this.roomPoller);
		}
		this.roomPoller = window.setInterval(function() {
			this.syncRooms();
		}.bind(this), 10000);
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._getCallPeers = function(token) {
		var defer = $.Deferred();
		$.ajax({
			beforeSend: function (request) {
				request.setRequestHeader('Accept', 'application/json');
			},
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
			success: function (result) {
				var peers = result.ocs.data;
				defer.resolve(peers);
			}
		});
		return defer;
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._openEventSource = function() {
		// Connect to the messages endpoint and pull for new messages
		this.source = new OC.EventSource(OC.generateUrl('/apps/spreed/messages'));

		this.source.listen('usersInRoom', function(users) {
			this._trigger('usersInRoom', [users]);
		}.bind(this));
		this.source.listen('message', function(message) {
			if (typeof(message) === 'string') {
				message = JSON.parse(message);
			}
			this._trigger('message', [message]);
		}.bind(this));
		this.source.listen('__internal__', function(data) {
			if (data === 'close') {
				console.log('signaling connection closed - will reopen');
				setTimeout(function() {
					this._openEventSource();
				}.bind(this), 0);
			}
		}.bind(this));
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype.sendPendingMessages = function() {
		if (!this.spreedArrayConnection.length) {
			return;
		}

		$.post(OC.generateUrl('/apps/spreed/signalling'), {
			messages: JSON.stringify(this.spreedArrayConnection)
		});
		this.spreedArrayConnection = [];
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._startPingCall = function() {
		this._pingCall();
		// Send a ping to the server all 5 seconds to ensure that the connection
		// is still alive.
		this.pingInterval = window.setInterval(function() {
			this._pingCall();
		}.bind(this), 5000);
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._stopPingCall = function() {
		if (this.pingInterval) {
			window.clearInterval(this.pingInterval);
			this.pingInterval = null;
		}
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._pingCall = function() {
		if (!this.currentCallToken) {
			return;
		}

		$.ajax({
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + this.currentCallToken + '/ping',
			method: 'POST'
		).done(function() {
			this.pingFails = 0;
		}.bind(this)).fail(function(xhr) {
			// If there is an error when pinging, retry for 3 times.
			if (xhr.status !== 404 && this.pingFails < 3) {
				this.pingFails++;
				return;
			}
			OCA.SpreedMe.Calls.leaveCurrentCall(false);
		}.bind(this));
	};

	OCA.SpreedMe.createSignalingConnection = function() {
		// TODO(fancycode): Create different type of signaling connection
		// depending on configuration.
		return new InternalSignaling();
	};

})(OCA, OC);
<commit_msg>Fixed rebase error.<commit_after>(function(OCA, OC) {
	'use strict';

	OCA.SpreedMe = OCA.SpreedMe || {};

	function SignalingBase() {
		this.sessionId = '';
		this.currentCallToken = null;
		this.handlers = {};
	}

	SignalingBase.prototype.on = function(ev, handler) {
		if (!this.handlers.hasOwnProperty(ev)) {
			this.handlers[ev] = [handler];
		} else {
			this.handlers[ev].push(handler);
		}
	};

	SignalingBase.prototype.emit = function(/*ev, data*/) {
		// Override in subclasses.
	};

	SignalingBase.prototype._trigger = function(ev, args) {
		var handlers = this.handlers[ev];
		if (!handlers) {
			return;
		}

		handlers = handlers.slice(0);
		for (var i = 0, len = handlers.length; i < len; i++) {
			var handler = handlers[i];
			handler.apply(handler, args);
		}
	};

	SignalingBase.prototype.getSessionid = function() {
		return this.sessionId;
	};

	SignalingBase.prototype.disconnect = function() {
		this.sessionId = '';
		this.currentCallToken = null;
	};

	SignalingBase.prototype.emit = function(ev, data) {
		switch (ev) {
			case 'join':
				var callback = arguments[2];
				var token = data;
				this.joinCall(token, callback);
				break;
			case 'leave':
				this.leaveCurrentCall();
				break;
			case 'message':
				this.sendCallMessage(data);
				break;
		}
	};

	SignalingBase.prototype.leaveCurrentCall = function() {
		if (this.currentCallToken) {
			this.leaveCall(this.currentCallToken);
			this.currentCallToken = null;
		}
	};

	SignalingBase.prototype.leaveAllCalls = function() {
		// Override if necessary.
	};

	SignalingBase.prototype.setRoomCollection = function(rooms) {
		this.roomCollection = rooms;
		return this.syncRooms();
	};

	SignalingBase.prototype.syncRooms = function() {
		var defer = $.Deferred();
		if (this.roomCollection && oc_current_user) {
			this.roomCollection.fetch({
				success: function(data) {
					defer.resolve(data);
				}
			});
		} else {
			defer.resolve([]);
		}
		return defer;
	};

	// Connection to the internal signaling server provided by the app.
	function InternalSignaling() {
		SignalingBase.prototype.constructor.apply(this, arguments);
		this.spreedArrayConnection = [];
		this._openEventSource();

		this.pingFails = 0;
		this.pingInterval = null;

		this.sendInterval = window.setInterval(function(){
			this.sendPendingMessages();
		}.bind(this), 500);
	}

	InternalSignaling.prototype = new SignalingBase();
	InternalSignaling.prototype.constructor = InternalSignaling;

	InternalSignaling.prototype.disconnect = function() {
		this.spreedArrayConnection = [];
		if (this.source) {
			this.source.close();
			this.source = null;
		}
		if (this.sendInterval) {
			window.clearInterval(this.sendInterval);
			this.sendInterval = null;
		}
		if (this.pingInterval) {
			window.clearInterval(this.pingInterval);
			this.pingInterval = null;
		}
		if (this.roomPoller) {
			window.clearInterval(this.roomPoller);
			this.roomPoller = null;
		}
		SignalingBase.prototype.disconnect.apply(this, arguments);
	};

	InternalSignaling.prototype.on = function(ev/*, handler*/) {
		SignalingBase.prototype.on.apply(this, arguments);

		switch (ev) {
			case 'connect':
				// A connection is established if we can perform a request
				// through it.
				this._sendMessageWithCallback(ev);
				break;

			case 'stunservers':
			case 'turnservers':
				// Values are not pushed by the server but have to be explicitly
				// requested.
				this._sendMessageWithCallback(ev);
				break;
		}
	};

	InternalSignaling.prototype._sendMessageWithCallback = function(ev) {
		var message = [{
			ev: ev
		}];
		$.post(OC.generateUrl('/apps/spreed/signalling'), {
			messages: JSON.stringify(message)
		}, function(data) {
			this._trigger(ev, [data]);
		}.bind(this));
	};

	InternalSignaling.prototype.joinCall = function(token, callback) {
		// The client is joining a new call, in this case we need
		// to do the following:
		//
		// 1. Join the call as participant.
		// 2. Get a list of other connected clients in the call.
		// 3. Pass information about other clients to the callback.
		//
		// The clients will then use the message command to exchange
		// their signalling information.
		$.ajax({
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
			type: 'POST',
			beforeSend: function (request) {
				request.setRequestHeader('Accept', 'application/json');
			},
			success: function (result) {
				console.log("Joined", result);
				this.sessionId = result.ocs.data.sessionId;
				this.currentCallToken = token;
				this._startPingCall();
				this._getCallPeers(token).then(function(peers) {
					var callDescription = {
						'clients': {}
					};

					peers.forEach(function(element) {
						if (this.sessionId !== element['sessionId']) {
							callDescription['clients'][element['sessionId']] = {
								'video': true
							};
						}
					}.bind(this));
					callback('', callDescription);
				}.bind(this));
			}.bind(this)
		});
	};

	InternalSignaling.prototype.leaveCall = function(token) {
		if (token === this.currentCallToken) {
			this._stopPingCall();
		}
		$.ajax({
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
			method: 'DELETE',
			async: false
		});
	};

	InternalSignaling.prototype.sendCallMessage = function(data) {
		if(data.type === 'answer') {
			console.log("ANSWER", data);
		} else if(data.type === 'offer') {
			console.log("OFFER", data);
		}
		this.spreedArrayConnection.push({
			ev: "message",
			fn: JSON.stringify(data),
			sessionId: this.sessionId
		});
	};

	InternalSignaling.prototype.setRoomCollection = function(rooms) {
		this._pollForRoomChanges();
		return SignalingBase.prototype.setRoomCollection.apply(this, arguments);
	};

	InternalSignaling.prototype._pollForRoomChanges = function() {
		if (this.roomPoller) {
			window.clearInterval(this.roomPoller);
		}
		this.roomPoller = window.setInterval(function() {
			this.syncRooms();
		}.bind(this), 10000);
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._getCallPeers = function(token) {
		var defer = $.Deferred();
		$.ajax({
			beforeSend: function (request) {
				request.setRequestHeader('Accept', 'application/json');
			},
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
			success: function (result) {
				var peers = result.ocs.data;
				defer.resolve(peers);
			}
		});
		return defer;
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._openEventSource = function() {
		// Connect to the messages endpoint and pull for new messages
		this.source = new OC.EventSource(OC.generateUrl('/apps/spreed/messages'));

		this.source.listen('usersInRoom', function(users) {
			this._trigger('usersInRoom', [users]);
		}.bind(this));
		this.source.listen('message', function(message) {
			if (typeof(message) === 'string') {
				message = JSON.parse(message);
			}
			this._trigger('message', [message]);
		}.bind(this));
		this.source.listen('__internal__', function(data) {
			if (data === 'close') {
				console.log('signaling connection closed - will reopen');
				setTimeout(function() {
					this._openEventSource();
				}.bind(this), 0);
			}
		}.bind(this));
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype.sendPendingMessages = function() {
		if (!this.spreedArrayConnection.length) {
			return;
		}

		$.post(OC.generateUrl('/apps/spreed/signalling'), {
			messages: JSON.stringify(this.spreedArrayConnection)
		});
		this.spreedArrayConnection = [];
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._startPingCall = function() {
		this._pingCall();
		// Send a ping to the server all 5 seconds to ensure that the connection
		// is still alive.
		this.pingInterval = window.setInterval(function() {
			this._pingCall();
		}.bind(this), 5000);
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._stopPingCall = function() {
		if (this.pingInterval) {
			window.clearInterval(this.pingInterval);
			this.pingInterval = null;
		}
	};

	/**
	 * @private
	 */
	InternalSignaling.prototype._pingCall = function() {
		if (!this.currentCallToken) {
			return;
		}

		$.ajax({
			url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + this.currentCallToken + '/ping',
			method: 'POST'
		}).done(function() {
			this.pingFails = 0;
		}.bind(this)).fail(function(xhr) {
			// If there is an error when pinging, retry for 3 times.
			if (xhr.status !== 404 && this.pingFails < 3) {
				this.pingFails++;
				return;
			}
			OCA.SpreedMe.Calls.leaveCurrentCall(false);
		}.bind(this));
	};

	OCA.SpreedMe.createSignalingConnection = function() {
		// TODO(fancycode): Create different type of signaling connection
		// depending on configuration.
		return new InternalSignaling();
	};

})(OCA, OC);
<|endoftext|>


<commit_before>/**
 * @fileOverview A JavaScript/GLSL sculpting script for sculpting Three.js meshes
 * @author Skeel Lee <skeel@skeelogy.com>
 * @version 0.1.0
 */

/**
 * Creates a GpuSkulpt instance that manages sculpting
 * @constructor
 */
function GpuSkulpt(options) {

    if (typeof options.mesh === 'undefined') {
        throw new Error('mesh not specified');
    }
    this.mesh = options.mesh;
    if (typeof options.renderer === 'undefined') {
        throw new Error('renderer not specified');
    }
    this.renderer = options.renderer;
    if (typeof options.size === 'undefined') {
        throw new Error('size not specified');
    }
    this.size = options.size;
    this.halfSize = this.size / 2.0;
    if (typeof options.res === 'undefined') {
        throw new Error('res not specified');
    }
    this.res = options.res;
    this.gridSize = this.size / this.res;
    this.texelSize = 1.0 / this.res;

    this.imageProcessedData = new Float32Array(4 * this.res * this.res);
    this.imageDataTexture = new THREE.DataTexture(null, this.res, this.res, THREE.RGBAFormat, THREE.FloatType);

    this.isSculpting = false;
    this.sculptUvPos = new THREE.Vector2();

    this.cursorHoverColor = new THREE.Vector3(0.4, 0.4, 0.4);
    this.cursorAddColor = new THREE.Vector3(0.3, 0.5, 0.1);
    this.cursorRemoveColor = new THREE.Vector3(0.5, 0.2, 0.1);

    this.shouldClear = false;

    this.linearFloatParams = {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping,
        format: THREE.RGBFormat,
        stencilBuffer: false,
        depthBuffer: false,
        type: THREE.FloatType
    };

    this.init();
}
GpuSkulpt.prototype.init = function () {
    this.__checkExtensions();
    this.__setupShaders();
    this.__setupRttScene();
    this.__setupVtf();
};
GpuSkulpt.prototype.__checkExtensions = function (renderer) {
    var context = this.renderer.context;
    if (!context.getExtension('OES_texture_float_linear')) {
        throw new Error('Extension not available: OES_texture_float_linear');
    }
    if (!context.getParameter(context.MAX_VERTEX_TEXTURE_IMAGE_UNITS)) {
        throw new Error('Vertex textures not supported on your graphics card');
    }
};
GpuSkulpt.prototype.__setupShaders = function () {

    THREE.ShaderManager.addShader('/glsl/passUv.vert');

    THREE.ShaderManager.addShader('/glsl/skulpt.frag');
    this.skulptMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uBaseTexture: { type: 't', value: null },
            uSculptTexture1: { type: 't', value: null },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.texelSize, this.texelSize) },
            uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.size / this.res, this.size / this.res) },
            uIsSculpting: { type: 'i', value: 0 },
            uSculptType: { type: 'i', value: 0 },
            uSculptPos: { type: 'v2', value: new THREE.Vector2() },
            uSculptAmount: { type: 'f', value: 0.05 },
            uSculptRadius: { type: 'f', value: 0.0 }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/passUv.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/skulpt.frag')
    });

    THREE.ShaderManager.addShader('/glsl/combineTextures.frag');
    this.combineTexturesMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture1: { type: 't', value: null },
            uTexture2: { type: 't', value: null }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/passUv.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/combineTextures.frag')
    });

    THREE.ShaderManager.addShader('/glsl/setColor.frag');
    this.clearMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uColor: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/passUv.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/setColor.frag')
    });
};
/**
 * Sets up the render-to-texture scene (2 render targets for accumulative feedback)
 */
GpuSkulpt.prototype.__setupRttScene = function () {

    //create a RTT scene
    this.rttScene = new THREE.Scene();

    //create an orthographic RTT camera
    var far = 10000;
    var near = -far;
    this.rttCamera = new THREE.OrthographicCamera(-this.halfSize, this.halfSize, this.halfSize, -this.halfSize, near, far);

    //create a quad which we will use to invoke the shaders
    this.rttQuadGeom = new THREE.PlaneGeometry(this.size, this.size);
    this.rttQuadMesh = new THREE.Mesh(this.rttQuadGeom, this.skulptMaterial);
    this.rttScene.add(this.rttQuadMesh);

    //create RTT render targets (we need two to do feedback)
    this.rttRenderTarget1 = new THREE.WebGLRenderTarget(this.res, this.res, this.linearFloatParams);
    this.rttRenderTarget1.generateMipmaps = false;
    this.rttRenderTarget2 = this.rttRenderTarget1.clone();

    //create a RTT render target for storing the combine results of all layers
    this.rttCombinedLayer = this.rttRenderTarget1.clone();
};
/**
 * Sets up the vertex-texture-fetch for the given mesh
 */
GpuSkulpt.prototype.__setupVtf = function () {
    THREE.ShaderManager.addShader('/glsl/heightMap.vert');
    THREE.ShaderManager.addShader('/glsl/lambertCursor.frag');
    this.mesh.material = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: null },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(1.0 / this.res, 1.0 / this.res) },
            uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.gridSize, this.gridSize) },
            uHeightMultiplier: { type: 'f', value: 1.0 },
            uBaseColor: { type: 'v3', value: new THREE.Vector3(0.6, 0.8, 0.0) },
            uAmbientLightColor: { type: 'v3', value: new THREE.Vector3(1.0, 1.0, 1.0) },
            uAmbientLightIntensity: { type: 'f', value: 0.1 },
            uPointLightWorldPos: { type: 'v3v', value: [ new THREE.Vector3(5, 15, -15), new THREE.Vector3(5, 2, 15) ] },
            uPointLightColor: { type: 'v3v', value: [ new THREE.Vector3(1.0, 0.8, 0.8), new THREE.Vector3(0.0, 0.9, 1.0) ] },
            uPointLightIntensity: { type: 'fv1', value: [ 0.5, 0.3 ] },
            uPointLightFalloffStart: { type: 'fv1', value: [ 25.0, 25.0 ] },
            uPointLightFalloffEnd: { type: 'fv1', value: [ 30.0, 30.0 ] },
            uShowCursor: { type: 'i', value: 0 },
            uCursorPos: { type: 'v2', value: new THREE.Vector2() },
            uCursorRadius: { type: 'f', value: 0.0 },
            uCursorColor: { type: 'v3', value: new THREE.Vector3() }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/heightMap.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/lambertCursor.frag')
    });
};
GpuSkulpt.prototype.update = function () {

    //have to set flags from other places and then do all steps at once during update

    //clear sculpts if necessary
    if (this.shouldClear) {
        this.rttQuadMesh.material = this.clearMaterial;
        this.clearMaterial.uniforms.uColor.value.set(0.0, 0.0, 0.0, 0.0);
        this.renderer.render(this.rttScene, this.rttCamera, this.rttRenderTarget1, false);
        this.renderer.render(this.rttScene, this.rttCamera, this.rttRenderTarget2, false);
        this.shouldClear = false;
        this.updateCombinedLayers = true;
    }

    //do the main sculpting
    if (this.isSculpting) {
        this.rttQuadMesh.material = this.skulptMaterial;
        this.skulptMaterial.uniforms.uBaseTexture.value = this.imageDataTexture;
        this.skulptMaterial.uniforms.uSculptTexture1.value = this.rttRenderTarget2;
        this.skulptMaterial.uniforms.uIsSculpting.value = this.isSculpting;
        this.skulptMaterial.uniforms.uSculptPos.value.copy(this.sculptUvPos);
        this.renderer.render(this.rttScene, this.rttCamera, this.rttRenderTarget1, false);
        this.swapRenderTargets();
        this.isSculpting = false;
        this.updateCombinedLayers = true;
    }

    //combine layers into one
    if (this.updateCombinedLayers) {  //this can be triggered somewhere else without sculpting

        this.rttQuadMesh.material = this.combineTexturesMaterial;
        this.combineTexturesMaterial.uniforms.uTexture1.value = this.imageDataTexture;
        this.combineTexturesMaterial.uniforms.uTexture2.value = this.rttRenderTarget2;
        this.renderer.render(this.rttScene, this.rttCamera, this.rttCombinedLayer, false);
        this.updateCombinedLayers = false;

        //need to rebind rttCombinedLayer to uTexture
        this.mesh.material.uniforms.uTexture.value = this.rttCombinedLayer;
    }
};
GpuSkulpt.prototype.swapRenderTargets = function () {
    var temp = this.rttRenderTarget1;
    this.rttRenderTarget1 = this.rttRenderTarget2;
    this.rttRenderTarget2 = temp;
    // this.skulptMaterial.uniforms.uSculptTexture1.value = this.rttRenderTarget2;
};
GpuSkulpt.prototype.setBrushSize = function (size) {
    var normSize = size / (this.size * 2.0);
    this.skulptMaterial.uniforms.uSculptRadius.value = normSize;
    this.mesh.material.uniforms.uCursorRadius.value = normSize;
};
GpuSkulpt.prototype.setBrushAmount = function (amount) {
    this.skulptMaterial.uniforms.uSculptAmount.value = amount;
};
GpuSkulpt.prototype.loadFromImageData = function (data, amount, midGreyIsLowest)
{
    //convert data from Uint8ClampedArray to Float32Array so that DataTexture can use
    var normalizedHeight;
    var min = 99999;
    var i, len;
    for (i = 0, len = this.imageProcessedData.length; i < len; i++) {
        if (midGreyIsLowest) {
            normalizedHeight = Math.abs(data[i] / 255.0 - 0.5);
        } else {
            normalizedHeight = data[i] / 255.0;
        }
        this.imageProcessedData[i] = normalizedHeight * amount;

        //store min
        if (this.imageProcessedData[i] < min) {
            min = this.imageProcessedData[i];
        }
    }

    //shift down so that min is at 0
    for (i = 0, len = this.imageProcessedData.length; i < len; i++) {
        this.imageProcessedData[i] -= min;
    }

    //assign data to DataTexture
    this.imageDataTexture.image.data = this.imageProcessedData;
    this.imageDataTexture.needsUpdate = true;
    this.skulptMaterial.uniforms.uBaseTexture.value = this.imageDataTexture;
    this.combineTexturesMaterial.uniforms.uTexture1.value = this.imageDataTexture;
    // this.mesh.material.uniforms.uBaseTexture.value = this.imageDataTexture;
    this.updateCombinedLayers = true;
};
GpuSkulpt.prototype.sculpt = function (type, position, amount) {
    this.skulptMaterial.uniforms.uSculptType.value = type;
    this.isSculpting = true;
    this.sculptUvPos.x = (position.x + this.halfSize) / this.size;
    this.sculptUvPos.y = (position.z + this.halfSize) / this.size;
    if (type === 1) {
        this.mesh.material.uniforms.uCursorColor.value.copy(this.cursorAddColor);
    } else if (type === 2) {
        this.mesh.material.uniforms.uCursorColor.value.copy(this.cursorRemoveColor);
    }
};
GpuSkulpt.prototype.clear = function () {
    this.shouldClear = true;
};
GpuSkulpt.prototype.updateCursor = function (position) {
    this.sculptUvPos.x = (position.x + this.halfSize) / this.size;
    this.sculptUvPos.y = (position.z + this.halfSize) / this.size;
    this.mesh.material.uniforms.uCursorPos.value.set(this.sculptUvPos.x, this.sculptUvPos.y);
    this.mesh.material.uniforms.uCursorColor.value.copy(this.cursorHoverColor);
};
GpuSkulpt.prototype.showCursor = function () {
    this.mesh.material.uniforms.uShowCursor.value = 1;
};
GpuSkulpt.prototype.hideCursor = function () {
    this.mesh.material.uniforms.uShowCursor.value = 0;
};
/**
 * Returns the pixel data for the render target texture
 */
GpuSkulpt.prototype.getPixelData = function () {

    //I need to read in pixel data from WebGLRenderTarget but there seems to be no direct way.
    //Seems like I have to do some native WebGL stuff with readPixels().

    var pixelData = new Uint8Array(this.res * this.res * 4);

    gl = this.renderer.getContext();

    //bind texture to gl context
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.rttCombinedLayer.__webglFramebuffer);

    //attach texture
    // gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.rttCombinedLayer.__webglTexture, 0);

    //read pixels
    gl.readPixels(0, 0, this.res, this.res, gl.RGBA, gl.UNSIGNED_BYTE, pixelData);

    //unbind
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    return pixelData;
};

GpuSkulpt.ADD = 1;
GpuSkulpt.REMOVE = 2;
<commit_msg>Defining pixel data array once and reusing<commit_after>/**
 * @fileOverview A JavaScript/GLSL sculpting script for sculpting Three.js meshes
 * @author Skeel Lee <skeel@skeelogy.com>
 * @version 0.1.0
 */

/**
 * Creates a GpuSkulpt instance that manages sculpting
 * @constructor
 */
function GpuSkulpt(options) {

    if (typeof options.mesh === 'undefined') {
        throw new Error('mesh not specified');
    }
    this.mesh = options.mesh;
    if (typeof options.renderer === 'undefined') {
        throw new Error('renderer not specified');
    }
    this.renderer = options.renderer;
    if (typeof options.size === 'undefined') {
        throw new Error('size not specified');
    }
    this.size = options.size;
    this.halfSize = this.size / 2.0;
    if (typeof options.res === 'undefined') {
        throw new Error('res not specified');
    }
    this.res = options.res;
    this.gridSize = this.size / this.res;
    this.texelSize = 1.0 / this.res;

    this.imageProcessedData = new Float32Array(4 * this.res * this.res);
    this.imageDataTexture = new THREE.DataTexture(null, this.res, this.res, THREE.RGBAFormat, THREE.FloatType);

    this.isSculpting = false;
    this.sculptUvPos = new THREE.Vector2();

    this.cursorHoverColor = new THREE.Vector3(0.4, 0.4, 0.4);
    this.cursorAddColor = new THREE.Vector3(0.3, 0.5, 0.1);
    this.cursorRemoveColor = new THREE.Vector3(0.5, 0.2, 0.1);

    this.shouldClear = false;

    this.linearFloatParams = {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        wrapS: THREE.ClampToEdgeWrapping,
        wrapT: THREE.ClampToEdgeWrapping,
        format: THREE.RGBFormat,
        stencilBuffer: false,
        depthBuffer: false,
        type: THREE.FloatType
    };

    this.pixelData = new Uint8Array(this.res * this.res * 4);

    this.init();
}
GpuSkulpt.prototype.init = function () {
    this.__checkExtensions();
    this.__setupShaders();
    this.__setupRttScene();
    this.__setupVtf();
};
GpuSkulpt.prototype.__checkExtensions = function (renderer) {
    var context = this.renderer.context;
    if (!context.getExtension('OES_texture_float_linear')) {
        throw new Error('Extension not available: OES_texture_float_linear');
    }
    if (!context.getParameter(context.MAX_VERTEX_TEXTURE_IMAGE_UNITS)) {
        throw new Error('Vertex textures not supported on your graphics card');
    }
};
GpuSkulpt.prototype.__setupShaders = function () {

    THREE.ShaderManager.addShader('/glsl/passUv.vert');

    THREE.ShaderManager.addShader('/glsl/skulpt.frag');
    this.skulptMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uBaseTexture: { type: 't', value: null },
            uSculptTexture1: { type: 't', value: null },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(this.texelSize, this.texelSize) },
            uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.size / this.res, this.size / this.res) },
            uIsSculpting: { type: 'i', value: 0 },
            uSculptType: { type: 'i', value: 0 },
            uSculptPos: { type: 'v2', value: new THREE.Vector2() },
            uSculptAmount: { type: 'f', value: 0.05 },
            uSculptRadius: { type: 'f', value: 0.0 }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/passUv.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/skulpt.frag')
    });

    THREE.ShaderManager.addShader('/glsl/combineTextures.frag');
    this.combineTexturesMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uTexture1: { type: 't', value: null },
            uTexture2: { type: 't', value: null }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/passUv.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/combineTextures.frag')
    });

    THREE.ShaderManager.addShader('/glsl/setColor.frag');
    this.clearMaterial = new THREE.ShaderMaterial({
        uniforms: {
            uColor: { type: 'v4', value: new THREE.Vector4() }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/passUv.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/setColor.frag')
    });
};
/**
 * Sets up the render-to-texture scene (2 render targets for accumulative feedback)
 */
GpuSkulpt.prototype.__setupRttScene = function () {

    //create a RTT scene
    this.rttScene = new THREE.Scene();

    //create an orthographic RTT camera
    var far = 10000;
    var near = -far;
    this.rttCamera = new THREE.OrthographicCamera(-this.halfSize, this.halfSize, this.halfSize, -this.halfSize, near, far);

    //create a quad which we will use to invoke the shaders
    this.rttQuadGeom = new THREE.PlaneGeometry(this.size, this.size);
    this.rttQuadMesh = new THREE.Mesh(this.rttQuadGeom, this.skulptMaterial);
    this.rttScene.add(this.rttQuadMesh);

    //create RTT render targets (we need two to do feedback)
    this.rttRenderTarget1 = new THREE.WebGLRenderTarget(this.res, this.res, this.linearFloatParams);
    this.rttRenderTarget1.generateMipmaps = false;
    this.rttRenderTarget2 = this.rttRenderTarget1.clone();

    //create a RTT render target for storing the combine results of all layers
    this.rttCombinedLayer = this.rttRenderTarget1.clone();
};
/**
 * Sets up the vertex-texture-fetch for the given mesh
 */
GpuSkulpt.prototype.__setupVtf = function () {
    THREE.ShaderManager.addShader('/glsl/heightMap.vert');
    THREE.ShaderManager.addShader('/glsl/lambertCursor.frag');
    this.mesh.material = new THREE.ShaderMaterial({
        uniforms: {
            uTexture: { type: 't', value: null },
            uTexelSize: { type: 'v2', value: new THREE.Vector2(1.0 / this.res, 1.0 / this.res) },
            uTexelWorldSize: { type: 'v2', value: new THREE.Vector2(this.gridSize, this.gridSize) },
            uHeightMultiplier: { type: 'f', value: 1.0 },
            uBaseColor: { type: 'v3', value: new THREE.Vector3(0.6, 0.8, 0.0) },
            uAmbientLightColor: { type: 'v3', value: new THREE.Vector3(1.0, 1.0, 1.0) },
            uAmbientLightIntensity: { type: 'f', value: 0.1 },
            uPointLightWorldPos: { type: 'v3v', value: [ new THREE.Vector3(5, 15, -15), new THREE.Vector3(5, 2, 15) ] },
            uPointLightColor: { type: 'v3v', value: [ new THREE.Vector3(1.0, 0.8, 0.8), new THREE.Vector3(0.0, 0.9, 1.0) ] },
            uPointLightIntensity: { type: 'fv1', value: [ 0.5, 0.3 ] },
            uPointLightFalloffStart: { type: 'fv1', value: [ 25.0, 25.0 ] },
            uPointLightFalloffEnd: { type: 'fv1', value: [ 30.0, 30.0 ] },
            uShowCursor: { type: 'i', value: 0 },
            uCursorPos: { type: 'v2', value: new THREE.Vector2() },
            uCursorRadius: { type: 'f', value: 0.0 },
            uCursorColor: { type: 'v3', value: new THREE.Vector3() }
        },
        vertexShader: THREE.ShaderManager.getShaderContents('/glsl/heightMap.vert'),
        fragmentShader: THREE.ShaderManager.getShaderContents('/glsl/lambertCursor.frag')
    });
};
GpuSkulpt.prototype.update = function () {

    //have to set flags from other places and then do all steps at once during update

    //clear sculpts if necessary
    if (this.shouldClear) {
        this.rttQuadMesh.material = this.clearMaterial;
        this.clearMaterial.uniforms.uColor.value.set(0.0, 0.0, 0.0, 0.0);
        this.renderer.render(this.rttScene, this.rttCamera, this.rttRenderTarget1, false);
        this.renderer.render(this.rttScene, this.rttCamera, this.rttRenderTarget2, false);
        this.shouldClear = false;
        this.updateCombinedLayers = true;
    }

    //do the main sculpting
    if (this.isSculpting) {
        this.rttQuadMesh.material = this.skulptMaterial;
        this.skulptMaterial.uniforms.uBaseTexture.value = this.imageDataTexture;
        this.skulptMaterial.uniforms.uSculptTexture1.value = this.rttRenderTarget2;
        this.skulptMaterial.uniforms.uIsSculpting.value = this.isSculpting;
        this.skulptMaterial.uniforms.uSculptPos.value.copy(this.sculptUvPos);
        this.renderer.render(this.rttScene, this.rttCamera, this.rttRenderTarget1, false);
        this.swapRenderTargets();
        this.isSculpting = false;
        this.updateCombinedLayers = true;
    }

    //combine layers into one
    if (this.updateCombinedLayers) {  //this can be triggered somewhere else without sculpting

        this.rttQuadMesh.material = this.combineTexturesMaterial;
        this.combineTexturesMaterial.uniforms.uTexture1.value = this.imageDataTexture;
        this.combineTexturesMaterial.uniforms.uTexture2.value = this.rttRenderTarget2;
        this.renderer.render(this.rttScene, this.rttCamera, this.rttCombinedLayer, false);
        this.updateCombinedLayers = false;

        //need to rebind rttCombinedLayer to uTexture
        this.mesh.material.uniforms.uTexture.value = this.rttCombinedLayer;
    }
};
GpuSkulpt.prototype.swapRenderTargets = function () {
    var temp = this.rttRenderTarget1;
    this.rttRenderTarget1 = this.rttRenderTarget2;
    this.rttRenderTarget2 = temp;
    // this.skulptMaterial.uniforms.uSculptTexture1.value = this.rttRenderTarget2;
};
GpuSkulpt.prototype.setBrushSize = function (size) {
    var normSize = size / (this.size * 2.0);
    this.skulptMaterial.uniforms.uSculptRadius.value = normSize;
    this.mesh.material.uniforms.uCursorRadius.value = normSize;
};
GpuSkulpt.prototype.setBrushAmount = function (amount) {
    this.skulptMaterial.uniforms.uSculptAmount.value = amount;
};
GpuSkulpt.prototype.loadFromImageData = function (data, amount, midGreyIsLowest)
{
    //convert data from Uint8ClampedArray to Float32Array so that DataTexture can use
    var normalizedHeight;
    var min = 99999;
    var i, len;
    for (i = 0, len = this.imageProcessedData.length; i < len; i++) {
        if (midGreyIsLowest) {
            normalizedHeight = Math.abs(data[i] / 255.0 - 0.5);
        } else {
            normalizedHeight = data[i] / 255.0;
        }
        this.imageProcessedData[i] = normalizedHeight * amount;

        //store min
        if (this.imageProcessedData[i] < min) {
            min = this.imageProcessedData[i];
        }
    }

    //shift down so that min is at 0
    for (i = 0, len = this.imageProcessedData.length; i < len; i++) {
        this.imageProcessedData[i] -= min;
    }

    //assign data to DataTexture
    this.imageDataTexture.image.data = this.imageProcessedData;
    this.imageDataTexture.needsUpdate = true;
    this.skulptMaterial.uniforms.uBaseTexture.value = this.imageDataTexture;
    this.combineTexturesMaterial.uniforms.uTexture1.value = this.imageDataTexture;
    // this.mesh.material.uniforms.uBaseTexture.value = this.imageDataTexture;
    this.updateCombinedLayers = true;
};
GpuSkulpt.prototype.sculpt = function (type, position, amount) {
    this.skulptMaterial.uniforms.uSculptType.value = type;
    this.isSculpting = true;
    this.sculptUvPos.x = (position.x + this.halfSize) / this.size;
    this.sculptUvPos.y = (position.z + this.halfSize) / this.size;
    if (type === 1) {
        this.mesh.material.uniforms.uCursorColor.value.copy(this.cursorAddColor);
    } else if (type === 2) {
        this.mesh.material.uniforms.uCursorColor.value.copy(this.cursorRemoveColor);
    }
};
GpuSkulpt.prototype.clear = function () {
    this.shouldClear = true;
};
GpuSkulpt.prototype.updateCursor = function (position) {
    this.sculptUvPos.x = (position.x + this.halfSize) / this.size;
    this.sculptUvPos.y = (position.z + this.halfSize) / this.size;
    this.mesh.material.uniforms.uCursorPos.value.set(this.sculptUvPos.x, this.sculptUvPos.y);
    this.mesh.material.uniforms.uCursorColor.value.copy(this.cursorHoverColor);
};
GpuSkulpt.prototype.showCursor = function () {
    this.mesh.material.uniforms.uShowCursor.value = 1;
};
GpuSkulpt.prototype.hideCursor = function () {
    this.mesh.material.uniforms.uShowCursor.value = 0;
};
/**
 * Returns the pixel data for the render target texture
 */
GpuSkulpt.prototype.getPixelData = function () {

    //I need to read in pixel data from WebGLRenderTarget but there seems to be no direct way.
    //Seems like I have to do some native WebGL stuff with readPixels().

    gl = this.renderer.getContext();

    //bind texture to gl context
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.rttCombinedLayer.__webglFramebuffer);

    //attach texture
    // gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.rttCombinedLayer.__webglTexture, 0);

    //read pixels
    gl.readPixels(0, 0, this.res, this.res, gl.RGBA, gl.UNSIGNED_BYTE, this.pixelData);

    //unbind
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    return this.pixelData;
};

GpuSkulpt.ADD = 1;
GpuSkulpt.REMOVE = 2;
<|endoftext|>


<commit_before>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.1.3): modal.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index'
import EventHandler from './dom/event-handler'
import SelectorEngine from './dom/selector-engine'
import ScrollBarHelper from './util/scrollbar'
import BaseComponent from './base-component'
import Backdrop from './util/backdrop'
import FocusTrap from './util/focustrap'
import { enableDismissTrigger } from './util/component-functions'

/**
 * Constants
 */

const NAME = 'modal'
const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const ESCAPE_KEY = 'Escape'

const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_RESIZE = `resize${EVENT_KEY}`
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_OPEN = 'modal-open'
const CLASS_NAME_FADE = 'fade'
const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_STATIC = 'modal-static'

const OPEN_SELECTOR = '.modal.show'
const SELECTOR_DIALOG = '.modal-dialog'
const SELECTOR_MODAL_BODY = '.modal-body'
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]'

const Default = {
  backdrop: true,
  keyboard: true,
  focus: true
}

const DefaultType = {
  backdrop: '(boolean|string)',
  keyboard: 'boolean',
  focus: 'boolean'
}

/**
 * Class definition
 */

class Modal extends BaseComponent {
  constructor(element, config) {
    super(element, config)

    this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)
    this._backdrop = this._initializeBackDrop()
    this._focustrap = this._initializeFocusTrap()
    this._isShown = false
    this._isTransitioning = false
    this._scrollBar = new ScrollBarHelper()
  }

  // Getters
  static get Default() {
    return Default
  }

  static get DefaultType() {
    return DefaultType
  }

  static get NAME() {
    return NAME
  }

  // Public
  toggle(relatedTarget) {
    return this._isShown ? this.hide() : this.show(relatedTarget)
  }

  show(relatedTarget) {
    if (this._isShown || this._isTransitioning) {
      return
    }

    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
      relatedTarget
    })

    if (showEvent.defaultPrevented) {
      return
    }

    this._isShown = true
    this._isTransitioning = true

    this._scrollBar.hide()

    document.body.classList.add(CLASS_NAME_OPEN)

    this._adjustDialog()

    this._toggleEscapeEventListener(true)
    this._toggleResizeEventListener(true)

    this._showBackdrop(() => this._showElement(relatedTarget))
  }

  hide() {
    if (!this._isShown || this._isTransitioning) {
      return
    }

    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)

    if (hideEvent.defaultPrevented) {
      return
    }

    this._isShown = false
    this._isTransitioning = true

    this._toggleEscapeEventListener(false)
    this._toggleResizeEventListener(false)

    this._focustrap.deactivate()

    this._element.classList.remove(CLASS_NAME_SHOW)

    this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())
  }

  dispose() {
    for (const htmlElement of [window, this._dialog]) {
      EventHandler.off(htmlElement, EVENT_KEY)
    }

    this._backdrop.dispose()
    this._focustrap.deactivate()
    super.dispose()
  }

  handleUpdate() {
    this._adjustDialog()
  }

  // Private
  _initializeBackDrop() {
    return new Backdrop({
      isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value
      isAnimated: this._isAnimated()
    })
  }

  _initializeFocusTrap() {
    return new FocusTrap({
      trapElement: this._element
    })
  }

  _showElement(relatedTarget) {
    // try to append dynamic modal
    if (!document.body.contains(this._element)) {
      document.body.append(this._element)
    }

    this._element.style.display = 'block'
    this._element.removeAttribute('aria-hidden')
    this._element.setAttribute('aria-modal', true)
    this._element.setAttribute('role', 'dialog')
    this._element.scrollTop = 0

    const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
    if (modalBody) {
      modalBody.scrollTop = 0
    }

    reflow(this._element)

    this._element.classList.add(CLASS_NAME_SHOW)

    const transitionComplete = () => {
      if (this._config.focus) {
        this._focustrap.activate()
      }

      this._isTransitioning = false
      EventHandler.trigger(this._element, EVENT_SHOWN, {
        relatedTarget
      })
    }

    this._queueCallback(transitionComplete, this._dialog, this._isAnimated())
  }

  _toggleEscapeEventListener(enable) {
    if (!enable) {
      EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)
      return
    }

    EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
      if (event.key !== ESCAPE_KEY) {
        return
      }

      if (this._config.keyboard) {
        event.preventDefault()
        this.hide()
        return
      }

      this._triggerBackdropTransition()
    })
  }

  _toggleResizeEventListener(enable) {
    if (enable) {
      EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())
      return
    }

    EventHandler.off(window, EVENT_RESIZE)
  }

  _hideModal() {
    this._element.style.display = 'none'
    this._element.setAttribute('aria-hidden', true)
    this._element.removeAttribute('aria-modal')
    this._element.removeAttribute('role')
    this._isTransitioning = false

    this._backdrop.hide(() => {
      document.body.classList.remove(CLASS_NAME_OPEN)
      this._resetAdjustments()
      this._scrollBar.reset()
      EventHandler.trigger(this._element, EVENT_HIDDEN)
    })
  }

  _showBackdrop(callback) {
    EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
      if (event.target !== event.currentTarget) {
        return
      }

      if (this._config.backdrop === true) {
        this.hide()
        return
      }

      if (this._config.backdrop === 'static') {
        this._triggerBackdropTransition()
      }
    })

    this._backdrop.show(callback)
  }

  _isAnimated() {
    return this._element.classList.contains(CLASS_NAME_FADE)
  }

  _triggerBackdropTransition() {
    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
    if (hideEvent.defaultPrevented) {
      return
    }

    const { classList, scrollHeight, style } = this._element
    const isModalOverflowing = scrollHeight > document.documentElement.clientHeight
    const initialOverflowY = style.overflowY
    // return if the following background transition hasn't yet completed
    if (initialOverflowY === 'hidden' || classList.contains(CLASS_NAME_STATIC)) {
      return
    }

    if (!isModalOverflowing) {
      style.overflowY = 'hidden'
    }

    classList.add(CLASS_NAME_STATIC)
    this._queueCallback(() => {
      classList.remove(CLASS_NAME_STATIC)
      this._queueCallback(() => {
        style.overflowY = initialOverflowY
      }, this._dialog)
    }, this._dialog)

    this._element.focus()
  }

  /**
   * The following methods are used to handle overflowing modals
   */

  _adjustDialog() {
    const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
    const scrollbarWidth = this._scrollBar.getWidth()
    const isBodyOverflowing = scrollbarWidth > 0

    if (isBodyOverflowing && !isModalOverflowing) {
      const property = isRTL() ? 'paddingLeft' : 'paddingRight'
      this._element.style[property] = `${scrollbarWidth}px`
    }

    if (!isBodyOverflowing && isModalOverflowing) {
      const property = isRTL() ? 'paddingRight' : 'paddingLeft'
      this._element.style[property] = `${scrollbarWidth}px`
    }
  }

  _resetAdjustments() {
    this._element.style.paddingLeft = ''
    this._element.style.paddingRight = ''
  }

  // Static
  static jQueryInterface(config, relatedTarget) {
    return this.each(function () {
      const data = Modal.getOrCreateInstance(this, config)

      if (typeof config !== 'string') {
        return
      }

      if (typeof data[config] === 'undefined') {
        throw new TypeError(`No method named "${config}"`)
      }

      data[config](relatedTarget)
    })
  }
}

/**
 * Data API implementation
 */

EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  const target = getElementFromSelector(this)

  if (['A', 'AREA'].includes(this.tagName)) {
    event.preventDefault()
  }

  EventHandler.one(target, EVENT_SHOW, showEvent => {
    if (showEvent.defaultPrevented) {
      // only register focus restorer if modal will actually get shown
      return
    }

    EventHandler.one(target, EVENT_HIDDEN, () => {
      if (isVisible(this)) {
        this.focus()
      }
    })
  })

  // avoid conflict when clicking moddal toggler while another one is open
  const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)
  if (allReadyOpen) {
    Modal.getInstance(allReadyOpen).hide()
  }

  const data = Modal.getOrCreateInstance(target)

  data.toggle(this)
})

enableDismissTrigger(Modal)

/**
 * jQuery
 */

defineJQueryPlugin(Modal)

export default Modal
<commit_msg>Fix typo in comment (#35564)<commit_after>/**
 * --------------------------------------------------------------------------
 * Bootstrap (v5.1.3): modal.js
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
 * --------------------------------------------------------------------------
 */

import { defineJQueryPlugin, getElementFromSelector, isRTL, isVisible, reflow } from './util/index'
import EventHandler from './dom/event-handler'
import SelectorEngine from './dom/selector-engine'
import ScrollBarHelper from './util/scrollbar'
import BaseComponent from './base-component'
import Backdrop from './util/backdrop'
import FocusTrap from './util/focustrap'
import { enableDismissTrigger } from './util/component-functions'

/**
 * Constants
 */

const NAME = 'modal'
const DATA_KEY = 'bs.modal'
const EVENT_KEY = `.${DATA_KEY}`
const DATA_API_KEY = '.data-api'
const ESCAPE_KEY = 'Escape'

const EVENT_HIDE = `hide${EVENT_KEY}`
const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY}`
const EVENT_HIDDEN = `hidden${EVENT_KEY}`
const EVENT_SHOW = `show${EVENT_KEY}`
const EVENT_SHOWN = `shown${EVENT_KEY}`
const EVENT_RESIZE = `resize${EVENT_KEY}`
const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY}`
const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY}`
const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`

const CLASS_NAME_OPEN = 'modal-open'
const CLASS_NAME_FADE = 'fade'
const CLASS_NAME_SHOW = 'show'
const CLASS_NAME_STATIC = 'modal-static'

const OPEN_SELECTOR = '.modal.show'
const SELECTOR_DIALOG = '.modal-dialog'
const SELECTOR_MODAL_BODY = '.modal-body'
const SELECTOR_DATA_TOGGLE = '[data-bs-toggle="modal"]'

const Default = {
  backdrop: true,
  keyboard: true,
  focus: true
}

const DefaultType = {
  backdrop: '(boolean|string)',
  keyboard: 'boolean',
  focus: 'boolean'
}

/**
 * Class definition
 */

class Modal extends BaseComponent {
  constructor(element, config) {
    super(element, config)

    this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element)
    this._backdrop = this._initializeBackDrop()
    this._focustrap = this._initializeFocusTrap()
    this._isShown = false
    this._isTransitioning = false
    this._scrollBar = new ScrollBarHelper()
  }

  // Getters
  static get Default() {
    return Default
  }

  static get DefaultType() {
    return DefaultType
  }

  static get NAME() {
    return NAME
  }

  // Public
  toggle(relatedTarget) {
    return this._isShown ? this.hide() : this.show(relatedTarget)
  }

  show(relatedTarget) {
    if (this._isShown || this._isTransitioning) {
      return
    }

    const showEvent = EventHandler.trigger(this._element, EVENT_SHOW, {
      relatedTarget
    })

    if (showEvent.defaultPrevented) {
      return
    }

    this._isShown = true
    this._isTransitioning = true

    this._scrollBar.hide()

    document.body.classList.add(CLASS_NAME_OPEN)

    this._adjustDialog()

    this._toggleEscapeEventListener(true)
    this._toggleResizeEventListener(true)

    this._showBackdrop(() => this._showElement(relatedTarget))
  }

  hide() {
    if (!this._isShown || this._isTransitioning) {
      return
    }

    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE)

    if (hideEvent.defaultPrevented) {
      return
    }

    this._isShown = false
    this._isTransitioning = true

    this._toggleEscapeEventListener(false)
    this._toggleResizeEventListener(false)

    this._focustrap.deactivate()

    this._element.classList.remove(CLASS_NAME_SHOW)

    this._queueCallback(() => this._hideModal(), this._element, this._isAnimated())
  }

  dispose() {
    for (const htmlElement of [window, this._dialog]) {
      EventHandler.off(htmlElement, EVENT_KEY)
    }

    this._backdrop.dispose()
    this._focustrap.deactivate()
    super.dispose()
  }

  handleUpdate() {
    this._adjustDialog()
  }

  // Private
  _initializeBackDrop() {
    return new Backdrop({
      isVisible: Boolean(this._config.backdrop), // 'static' option will be translated to true, and booleans will keep their value
      isAnimated: this._isAnimated()
    })
  }

  _initializeFocusTrap() {
    return new FocusTrap({
      trapElement: this._element
    })
  }

  _showElement(relatedTarget) {
    // try to append dynamic modal
    if (!document.body.contains(this._element)) {
      document.body.append(this._element)
    }

    this._element.style.display = 'block'
    this._element.removeAttribute('aria-hidden')
    this._element.setAttribute('aria-modal', true)
    this._element.setAttribute('role', 'dialog')
    this._element.scrollTop = 0

    const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog)
    if (modalBody) {
      modalBody.scrollTop = 0
    }

    reflow(this._element)

    this._element.classList.add(CLASS_NAME_SHOW)

    const transitionComplete = () => {
      if (this._config.focus) {
        this._focustrap.activate()
      }

      this._isTransitioning = false
      EventHandler.trigger(this._element, EVENT_SHOWN, {
        relatedTarget
      })
    }

    this._queueCallback(transitionComplete, this._dialog, this._isAnimated())
  }

  _toggleEscapeEventListener(enable) {
    if (!enable) {
      EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS)
      return
    }

    EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => {
      if (event.key !== ESCAPE_KEY) {
        return
      }

      if (this._config.keyboard) {
        event.preventDefault()
        this.hide()
        return
      }

      this._triggerBackdropTransition()
    })
  }

  _toggleResizeEventListener(enable) {
    if (enable) {
      EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog())
      return
    }

    EventHandler.off(window, EVENT_RESIZE)
  }

  _hideModal() {
    this._element.style.display = 'none'
    this._element.setAttribute('aria-hidden', true)
    this._element.removeAttribute('aria-modal')
    this._element.removeAttribute('role')
    this._isTransitioning = false

    this._backdrop.hide(() => {
      document.body.classList.remove(CLASS_NAME_OPEN)
      this._resetAdjustments()
      this._scrollBar.reset()
      EventHandler.trigger(this._element, EVENT_HIDDEN)
    })
  }

  _showBackdrop(callback) {
    EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => {
      if (event.target !== event.currentTarget) {
        return
      }

      if (this._config.backdrop === true) {
        this.hide()
        return
      }

      if (this._config.backdrop === 'static') {
        this._triggerBackdropTransition()
      }
    })

    this._backdrop.show(callback)
  }

  _isAnimated() {
    return this._element.classList.contains(CLASS_NAME_FADE)
  }

  _triggerBackdropTransition() {
    const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED)
    if (hideEvent.defaultPrevented) {
      return
    }

    const { classList, scrollHeight, style } = this._element
    const isModalOverflowing = scrollHeight > document.documentElement.clientHeight
    const initialOverflowY = style.overflowY
    // return if the following background transition hasn't yet completed
    if (initialOverflowY === 'hidden' || classList.contains(CLASS_NAME_STATIC)) {
      return
    }

    if (!isModalOverflowing) {
      style.overflowY = 'hidden'
    }

    classList.add(CLASS_NAME_STATIC)
    this._queueCallback(() => {
      classList.remove(CLASS_NAME_STATIC)
      this._queueCallback(() => {
        style.overflowY = initialOverflowY
      }, this._dialog)
    }, this._dialog)

    this._element.focus()
  }

  /**
   * The following methods are used to handle overflowing modals
   */

  _adjustDialog() {
    const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight
    const scrollbarWidth = this._scrollBar.getWidth()
    const isBodyOverflowing = scrollbarWidth > 0

    if (isBodyOverflowing && !isModalOverflowing) {
      const property = isRTL() ? 'paddingLeft' : 'paddingRight'
      this._element.style[property] = `${scrollbarWidth}px`
    }

    if (!isBodyOverflowing && isModalOverflowing) {
      const property = isRTL() ? 'paddingRight' : 'paddingLeft'
      this._element.style[property] = `${scrollbarWidth}px`
    }
  }

  _resetAdjustments() {
    this._element.style.paddingLeft = ''
    this._element.style.paddingRight = ''
  }

  // Static
  static jQueryInterface(config, relatedTarget) {
    return this.each(function () {
      const data = Modal.getOrCreateInstance(this, config)

      if (typeof config !== 'string') {
        return
      }

      if (typeof data[config] === 'undefined') {
        throw new TypeError(`No method named "${config}"`)
      }

      data[config](relatedTarget)
    })
  }
}

/**
 * Data API implementation
 */

EventHandler.on(document, EVENT_CLICK_DATA_API, SELECTOR_DATA_TOGGLE, function (event) {
  const target = getElementFromSelector(this)

  if (['A', 'AREA'].includes(this.tagName)) {
    event.preventDefault()
  }

  EventHandler.one(target, EVENT_SHOW, showEvent => {
    if (showEvent.defaultPrevented) {
      // only register focus restorer if modal will actually get shown
      return
    }

    EventHandler.one(target, EVENT_HIDDEN, () => {
      if (isVisible(this)) {
        this.focus()
      }
    })
  })

  // avoid conflict when clicking modal toggler while another one is open
  const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR)
  if (allReadyOpen) {
    Modal.getInstance(allReadyOpen).hide()
  }

  const data = Modal.getOrCreateInstance(target)

  data.toggle(this)
})

enableDismissTrigger(Modal)

/**
 * jQuery
 */

defineJQueryPlugin(Modal)

export default Modal
<|endoftext|>


<commit_before>(function(stopwatch){
    var Observable = function(){
        this.observers = {};
    };
    Observable.prototype.on = function(event, callback){
        (this.observers[event] = this.observers[event] || []).push(callback);
    };
    Observable.prototype.emit = function(event){
        var data = Array.prototype.slice.call(arguments, 1);
        (this.observers[event] || []).forEach(function(observer){
            observer.apply(undefined, data);
        });
    };

    var Game = stopwatch.Game = function(seconds){
        Observable.call(this);
        this.target = 1000 * seconds; // in milliseconds
        this.reset();
    };
    Game.prototype = Object.create(Observable.prototype);
    Game.prototype.constructor = Game;
    Game.prototype.current = function(){
        if (this.started) {
            return (this.stopped ? this.stopTime: this.time) - this.startTime;
        }
        return 0;
    };
    Game.prototype.start = function(){
        if (!this.started) {
            this.started = true;
            this.startTime = new Date().getTime();
            this.time = this.startTime;
        }
    };
    Game.prototype.stop = function(){
        if (!this.stopped) {
            this.stopped = true;
            this.stopTime = new Date().getTime();
            this.emit('stopped', this.score());
        }
    };
    Game.prototype.tick = function(){
        this.time = new Date().getTime();
    };
    Game.prototype.score = function(){
        return this.stopped ? Math.abs(this.target - (this.stopTime - this.startTime)): Number.POSITIVE_INFINITY;
    };
    Game.prototype.reset = function(){
        if (this.stopped) {
            this.started = false;
            this.stopped = false;
        }
    };

    var GameView = stopwatch.GameView = function(game, container){
        this.game = game;
        this.container = container;
        this.update();
    };
    GameView.prototype.update = function(){
        var target = this.container.querySelector('#target');
        target.innerHTML = this.game.target;
        var current = this.container.querySelector('#current');
        current.innerHTML = this.game.current();
        var score = this.container.querySelector('#score');
        score.innerHTML = this.game.score();
    };

    var HighScore = stopwatch.HighScore = function(top){
        Observable.call(this);
        this.top = top || 10;
        this.best = [];
    };
    HighScore.prototype = Object.create(Observable.prototype);
    HighScore.prototype.constructor = HighScore;
    HighScore.prototype.registerScore = function(score){
        this.best.push(score);
        this.best.sort();
        this.best.splice(this.top, 1);
        this.emit('scored');
    };

    var HighScoreView = stopwatch.HighScoreView = function(highScore, container){
        this.highScore = highScore;
        this.container = container;
        this.highScore.on('scored', this.update.bind(this));
        this.update();
    };
    HighScoreView.prototype.update = function(){
        var container = this.container;
        container.innerHTML = '';
        this.highScore.best.forEach(function(score){
            var item = document.createElement('li');
            item.innerHTML = score;
            container.appendChild(item);
        });
    };
})(window.stopwatch = window.stopwatch || {});
<commit_msg>add to Create an HighScore<commit_after>(function(stopwatch){
    var Observable = function(){
        this.observers = {};
    };
    Observable.prototype.on = function(event, callback){
        (this.observers[event] = this.observers[event] || []).push(callback);
    };
    Observable.prototype.emit = function(event){
        var data = Array.prototype.slice.call(arguments, 1);
        (this.observers[event] || []).forEach(function(observer){
            observer.apply(undefined, data);
        });
    };

    var Game = stopwatch.Game = function(seconds){
        Observable.call(this);
        this.target = 1000 * seconds; // in milliseconds
        this.reset();
    };
    Game.prototype = Object.create(Observable.prototype);
    Game.prototype.constructor = Game;
    Game.prototype.current = function(){
        if (this.started) {
            return (this.stopped ? this.stopTime: this.time) - this.startTime;
        }
        return 0;
    };
    Game.prototype.start = function(){
        if (!this.started) {
            this.started = true;
            this.startTime = new Date().getTime();
            this.time = this.startTime;
        }
    };
    Game.prototype.stop = function(){
        if (!this.stopped) {
            this.stopped = true;
            this.stopTime = new Date().getTime();
            this.emit('stopped', this.score());
        }
    };
    Game.prototype.tick = function(){
        this.time = new Date().getTime();
    };
    Game.prototype.score = function(){
        return this.stopped ? Math.abs(this.target - (this.stopTime - this.startTime)): Number.POSITIVE_INFINITY;
    };
    Game.prototype.reset = function(){
        if (this.stopped) {
            this.started = false;
            this.stopped = false;
        }
    };

    var GameView = stopwatch.GameView = function(game, container){
        this.game = game;
        this.container = container;
        this.update();
    };
    GameView.prototype.update = function(){
        var target = this.container.querySelector('#target');
        target.innerHTML = this.game.target;
        var current = this.container.querySelector('#current');
        current.innerHTML = this.game.current();
        var score = this.container.querySelector('#score');
        score.innerHTML = this.game.score();
    };

    var HighScore = stopwatch.HighScore = function(top){
        Observable.call(this);
        this.top = top || 10;
        this.best = [];
    };
    HighScore.prototype = Object.create(Observable.prototype);
    HighScore.prototype.constructor = HighScore;
    HighScore.prototype.registerScore = function(score){
        this.best.push(score);
        this.best.sort(function(a, b){ return a - b; });
        this.best.splice(this.top, 1);
        this.emit('scored');
    };

    var HighScoreView = stopwatch.HighScoreView = function(highScore, container){
        this.highScore = highScore;
        this.container = container;
        this.highScore.on('scored', this.update.bind(this));
        this.update();
    };
    HighScoreView.prototype.update = function(){
        var container = this.container;
        container.innerHTML = '';
        this.highScore.best.forEach(function(score){
            var item = document.createElement('li');
            item.innerHTML = score;
            container.appendChild(item);
        });
    };
})(window.stopwatch = window.stopwatch || {});
<|endoftext|>