<commit_before>(function($) {

	var evaluatePath, applyViewFn, updateViewBindEl, getFormElVal, updateVisibleBindEl;

	// Backbone.View Mixins
	// --------------------

	_.extend(Backbone.View.prototype, {

		// Collection of model event bindings.
		//   [{model,event,fn}, ...]
		_modelBindings: null,

		// Unbind the model bindings that are referenced in `this._modelBindings`. If
		// the optional `model` parameter is defined, then only delete bindings for
		// the given `model`.
		unstickModel: function(model) {
			_.each(this._modelBindings, _.bind(function(binding, i) {
				if (model && binding.model !== model) return false;
				binding.model.off(binding.event, binding.fn);
				delete this._modelBindings[i];
			}, this));
			this._modelBindings = _.compact(this._modelBindings);
		},

		// Using `this.bindings` configuration or the `optionalBindingsConfig`, binds `this.model`
		// or the `optionalModel` to elements in the view.
		stickit: function(optionalModel, optionalBindingsConfig) {
			var self = this, observeModelEvent,
				model = optionalModel || this.model,
				bindings = optionalBindingsConfig || this.bindings || {},
				props = ['autofocus', 'autoplay', 'async', 'checked', 'controls', 'defer', 'disabled', 'hidden', 'loop', 'multiple', 'open', 'readonly', 'required', 'scoped', 'selected'];

			this._modelBindings || (this._modelBindings = []);
			this.unstickModel(model);

			this.events || (this.events = {});

			// Setup a model event binding with the given function, and track the event in this._modelBindings.
			observeModelEvent = function(event, fn) {
				model.on(event, fn, self);
				self._modelBindings.push({model:model, event:event, fn:fn});
			};

			// Iterate through the selectors in the bindings configuration and configure
			// the various options for each field.
			_.each(_.keys(bindings), function(selector) {
				var $el, getVal, modelEvents, eventCallback, format, modelAttr, attributes, visibleCb,
					config = bindings[selector] || {},
					bindKey = _.uniqueId();

				// Support ':el' selector - special case selector for the view managed delegate.
				if (selector != ':el') $el = self.$(selector);
				else {
					$el = self.$el;
					selector = '';
				}

				// Fail fast if the selector didn't match an element.
				if (!$el.length) return false;

				// Allow shorthand setting of model attributes
				if (typeof config === 'string') config = {modelAttr:config};

				format = config.format;
				modelAttr = config.modelAttr;
				attributes = config.attributes || [];

				// Returns the given `field`'s value from the model, escaping and formatting if necessary.
				getVal = function(field) {
					var val = config.escape ? model.escape(field) : model.get(field);
					if (_.isUndefined(val)) val = '';
					return format ? applyViewFn(self, format, val, modelAttr) : val;
				};

				// Setup the attributes configuration - a list that maps an attribute or
				// property `name`, to an `observe`d model attribute, using an optional
				// `format`ter.
				//
				//   [{
				//     name: 'attributeOrPropertyName',
				//     observe: 'modelAttrName'
				//     format: function(modelAttrVal, modelAttrName) { ... }
				//   }, ...]
				//
				_.each(attributes, function(attrConfig) {
					var lastClass = '',
						observed = attrConfig.observe || modelAttr,
						updateAttr = function() {
							var val, updateType = _.indexOf(props, attrConfig.name, true) > -1 ? 'prop' : 'attr';
							if (attrConfig.format) val = applyViewFn(self, attrConfig.format, model.get(observed), observed);
							else val = model.get(observed);
							// If it is a class then we need to remove the last value and add the new.
							if (attrConfig.name == 'class') {
								$el.removeClass(lastClass).addClass(val);
								lastClass = val;
							}
							else $el[updateType](attrConfig.name, val);
						};
					observeModelEvent('bind:' + observed, updateAttr);
					updateAttr();
				});

				// If the `visible` config is configured, then the view element will be
				// shown/hidden based on the truthiness of the modelattr's value or the
				// result of the given callback. If a `visibleFn` is also supplied, then
				// that callback will be executed to manually handle showing/hiding the
				// view element.
				if (config.visible) {
					visibleCb = function() {
						updateVisibleBindEl($el, getVal(modelAttr), modelAttr, config, this);
					};
					observeModelEvent('bind:' + modelAttr, visibleCb);
					visibleCb();
					return false;
				}

				if (modelAttr) {
					// If the bind element is a form element, then configure `this.events` bindings
					// so that the model stays in sync with user input/changes.
					eventCallback = function() {
						var options = _.extend({bindKey:bindKey}, config.setOptions || {});
						model.set(modelAttr, getFormElVal($el), options);
					};
					if ($el.is('input[type=radio]') || $el.is('input[type=checkbox]') || $el.is('select'))
						self.events['change '+selector] =  eventCallback;
					else if ($el.is('input') || $el.is('textarea')) {
						self.events['keyup '+selector] =  eventCallback;
						self.events['change '+selector] =  eventCallback;
					}

					// Setup a `bind:modelAttr` observer for the model to keep the view element in sync.
					observeModelEvent('bind:'+modelAttr, function(val, options) {
						if (options && options.bindKey != bindKey)
							updateViewBindEl(self, $el, config, getVal(modelAttr), model);
					});

					updateViewBindEl(self, $el, config, getVal(modelAttr), model, true);
				}
			});

			// We added to `this.events` so we need to re-delegate.
			this.delegateEvents();

			// Wrap remove so that we can remove model events when this view is removed.
			this.remove = _.wrap(this.remove, function(oldRemove) {
				self.unstickModel();
				oldRemove && oldRemove.call(self);
			});
		}
	});

	// Backbone.Model.set Wrapper
	// --------------------------

	// Wrap set and fire a `bind:[attribute_name]` for each changed attribute,
	// unless there is a {bind:false} option.
	Backbone.Model.prototype.set = _.wrap(Backbone.Model.prototype.set, function(oldSet, key, value, oldOptions) {
		var attrs, attr, val, ret,
			options = oldOptions && _.clone(oldOptions) || {},
			now = this.attributes;

		// Use the parameters to get the `attrs` and `options` objects.
		if (_.isObject(key) || key == null) {
			attrs = key;
			options = value;
		} else {
			attrs = {};
			attrs[key] = value;
		}
		options || (options = {});

		// Delegating to Backbone's model.set().
		ret = oldSet.call(this, attrs, options);

		// Iterate through the attributes that were just set.
		_.each(_.keys(attrs || {}), _.bind(function(attr) {
			// Trigger a custom "bind" event for each attribute that has changed, unless {bind:false} option.
			if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr)))
				this.trigger('bind:' + attr, attrs[attr], options);
		}, this));

		return ret;
	});

	// Utilities
	// ---------

	// Evaluates the given `path` (in object/dot-notation) relative to the given `obj`.
	evaluatePath = function(obj, path) {
		var pathParts = (path || '').split('.');
		return _.reduce(pathParts, function(memo, i) { return memo[i]; }, obj) || obj;
	};

	// If the given `fn` is a string, then view[fn] is called, otherwise it is a function
	// that should be executed.
	applyViewFn = function(view, fn) {
		if (fn) return (_.isString(fn) ? view[fn] : fn).apply(view, _.toArray(arguments).slice(2));
	};

	// Gets the value from the given form element.
	getFormElVal = function($el) {
		var val;
		if ($el.is('input[type=checkbox]')) val = $el.prop('checked');
		else if ($el.is('select')) val = $el.find('option:selected').data('stickit_bind_val');
		else if ($el.is('input[type=number]')) val = Number($el.val());
		else if ($el.is('input[type="radio"]')) val = $el.filter(':checked').val();
		else val = $el.val();
		return val;
	};

	updateVisibleBindEl = function($el, val, attrName, config, context) {
		var visible = config.visible, visibleFn = config.visibleFn, isVisible = !!val;
		if (_.isFunction(visible))
			isVisible = applyViewFn(context, visible, val, attrName);
		if (visibleFn)
			applyViewFn(context, visibleFn, $el, isVisible, attrName);
		else {
			if (isVisible) $el.show();
			else $el.hide();
		}
	};

	// Update the value of `$el` in `view` using the given configuration.
	updateViewBindEl = function(view, $el, config, val, model, isInitializing) {
		var originalVal, radioEl,
			modelAttr = config.modelAttr,
			afterUpdate = config.afterUpdate,
			selectConfig = config.selectOptions,
			updateMethod = config.updateMethod || 'text';

		if ($el.is('input[type=radio]')) {
			radioEl = $el.filter('[value='+val+']');
			originalVal = radioEl.prop('checked');
			radioEl.prop('checked', true);
		} else if ($el.is('input[type=checkbox]')) {
			originalVal = $el.prop('checked');
			$el.prop('checked', val === true);
		} else if ($el.is('input') || $el.is('textarea')) {
			originalVal = $el.val();
			if (originalVal !== val) $el.val(val);
		} else if ($el.is('select')) {
			var optList, list = selectConfig.collection, fieldVal = model.get(modelAttr);

			// Get the current selected option value if the select options exist.
			if ($el[0].options.length && $el[0].selectedIndex >= 0)
				originalVal = $($el[0].options[$el[0].selectedIndex]).data('stickit_bind_val');

			$el.html('');

			// The `list` configuration is a function that returns the options list or a string
			// which represents the path to the list relative to `window`.
			optList = _.isFunction(list) ? list.call(view) : evaluatePath(window, list);

			// Add an empty default option if the current model attribute isn't defined.
			if (fieldVal == null)
				$el.append('<option/>').find('option').prop('selected', true).data('stickit_bind_val', fieldVal);

			_.each(optList, function(obj) {
				var option = $('<option/>'), optionVal = obj;

				// If the list contains a null/undefined value, then an empty option should
				// be appended in the list; otherwise, fill the option with text and value.
				if (obj != null) {
					option.text(obj[selectConfig.labelPath]);
					optionVal = evaluatePath(obj, selectConfig.valuePath);
				}

				// Save the option value so that we can reference it later.
				option.data('stickit_bind_val', optionVal);

				// Determine if this option is selected.
				if (optionVal != null && fieldVal != null && optionVal == fieldVal || (_.isObject(fieldVal) && _.isEqual(optionVal, fieldVal)))
					option.prop('selected', true);

				$el.append(option);
			});
		} else {
			originalVal = $el[updateMethod]();
			$el[updateMethod](val);
		}

		// Execute the `afterUpdate` callback from the `bindings` config.
		if (!isInitializing) applyViewFn(view, afterUpdate, $el, val, originalVal);
	};

})(window.jQuery || window.Zepto);
<commit_msg>Support oneWay bindings, suggested in issues #13 and #18<commit_after>(function($) {

	var evaluatePath, applyViewFn, updateViewBindEl, getFormElVal, updateVisibleBindEl;

	// Backbone.View Mixins
	// --------------------

	_.extend(Backbone.View.prototype, {

		// Collection of model event bindings.
		//   [{model,event,fn}, ...]
		_modelBindings: null,

		// Unbind the model bindings that are referenced in `this._modelBindings`. If
		// the optional `model` parameter is defined, then only delete bindings for
		// the given `model`.
		unstickModel: function(model) {
			_.each(this._modelBindings, _.bind(function(binding, i) {
				if (model && binding.model !== model) return false;
				binding.model.off(binding.event, binding.fn);
				delete this._modelBindings[i];
			}, this));
			this._modelBindings = _.compact(this._modelBindings);
		},

		// Using `this.bindings` configuration or the `optionalBindingsConfig`, binds `this.model`
		// or the `optionalModel` to elements in the view.
		stickit: function(optionalModel, optionalBindingsConfig) {
			var self = this, observeModelEvent,
				model = optionalModel || this.model,
				bindings = optionalBindingsConfig || this.bindings || {},
				props = ['autofocus', 'autoplay', 'async', 'checked', 'controls', 'defer', 'disabled', 'hidden', 'loop', 'multiple', 'open', 'readonly', 'required', 'scoped', 'selected'];

			this._modelBindings || (this._modelBindings = []);
			this.unstickModel(model);

			this.events || (this.events = {});

			// Setup a model event binding with the given function, and track the event in this._modelBindings.
			observeModelEvent = function(event, fn) {
				model.on(event, fn, self);
				self._modelBindings.push({model:model, event:event, fn:fn});
			};

			// Iterate through the selectors in the bindings configuration and configure
			// the various options for each field.
			_.each(_.keys(bindings), function(selector) {
				var $el, getVal, modelEvents, eventCallback, format, modelAttr, attributes, visibleCb,
					config = bindings[selector] || {},
					bindKey = _.uniqueId();

				// Support ':el' selector - special case selector for the view managed delegate.
				if (selector != ':el') $el = self.$(selector);
				else {
					$el = self.$el;
					selector = '';
				}

				// Fail fast if the selector didn't match an element.
				if (!$el.length) return false;

				// Allow shorthand setting of model attributes
				if (typeof config === 'string') config = {modelAttr:config};

				format = config.format;
				modelAttr = config.modelAttr;
				attributes = config.attributes || [];

				// Returns the given `field`'s value from the model, escaping and formatting if necessary.
				getVal = function(field) {
					var val = config.escape ? model.escape(field) : model.get(field);
					if (_.isUndefined(val)) val = '';
					return format ? applyViewFn(self, format, val, modelAttr) : val;
				};

				// Setup the attributes configuration - a list that maps an attribute or
				// property `name`, to an `observe`d model attribute, using an optional
				// `format`ter.
				//
				//   [{
				//     name: 'attributeOrPropertyName',
				//     observe: 'modelAttrName'
				//     format: function(modelAttrVal, modelAttrName) { ... }
				//   }, ...]
				//
				_.each(attributes, function(attrConfig) {
					var lastClass = '',
						observed = attrConfig.observe || modelAttr,
						updateAttr = function() {
							var val, updateType = _.indexOf(props, attrConfig.name, true) > -1 ? 'prop' : 'attr';
							if (attrConfig.format) val = applyViewFn(self, attrConfig.format, model.get(observed), observed);
							else val = model.get(observed);
							// If it is a class then we need to remove the last value and add the new.
							if (attrConfig.name == 'class') {
								$el.removeClass(lastClass).addClass(val);
								lastClass = val;
							}
							else $el[updateType](attrConfig.name, val);
						};
					observeModelEvent('bind:' + observed, updateAttr);
					updateAttr();
				});

				// If the `visible` config is configured, then the view element will be
				// shown/hidden based on the truthiness of the modelattr's value or the
				// result of the given callback. If a `visibleFn` is also supplied, then
				// that callback will be executed to manually handle showing/hiding the
				// view element.
				if (config.visible) {
					visibleCb = function() {
						updateVisibleBindEl($el, getVal(modelAttr), modelAttr, config, this);
					};
					observeModelEvent('bind:' + modelAttr, visibleCb);
					visibleCb();
					return false;
				}

				if (modelAttr) {
					if (!config.oneWay) {
						// If the bind element is a form element, then configure `this.events` bindings
						// so that the model stays in sync with user input/changes.
						eventCallback = function() {
							var options = _.extend({bindKey:bindKey}, config.setOptions || {});
							model.set(modelAttr, getFormElVal($el), options);
						};
						if ($el.is('input[type=radio]') || $el.is('input[type=checkbox]') || $el.is('select'))
							self.events['change '+selector] =  eventCallback;
						else if ($el.is('input') || $el.is('textarea')) {
							self.events['keyup '+selector] =  eventCallback;
							self.events['change '+selector] =  eventCallback;
						}
					}

					// Setup a `bind:modelAttr` observer for the model to keep the view element in sync.
					observeModelEvent('bind:'+modelAttr, function(val, options) {
						if (options && options.bindKey != bindKey)
							updateViewBindEl(self, $el, config, getVal(modelAttr), model);
					});

					updateViewBindEl(self, $el, config, getVal(modelAttr), model, true);
				}
			});

			// We added to `this.events` so we need to re-delegate.
			this.delegateEvents();

			// Wrap remove so that we can remove model events when this view is removed.
			this.remove = _.wrap(this.remove, function(oldRemove) {
				self.unstickModel();
				oldRemove && oldRemove.call(self);
			});
		}
	});

	// Backbone.Model.set Wrapper
	// --------------------------

	// Wrap set and fire a `bind:[attribute_name]` for each changed attribute,
	// unless there is a {bind:false} option.
	Backbone.Model.prototype.set = _.wrap(Backbone.Model.prototype.set, function(oldSet, key, value, oldOptions) {
		var attrs, attr, val, ret,
			options = oldOptions && _.clone(oldOptions) || {},
			now = this.attributes;

		// Use the parameters to get the `attrs` and `options` objects.
		if (_.isObject(key) || key == null) {
			attrs = key;
			options = value;
		} else {
			attrs = {};
			attrs[key] = value;
		}
		options || (options = {});

		// Delegating to Backbone's model.set().
		ret = oldSet.call(this, attrs, options);

		// Iterate through the attributes that were just set.
		_.each(_.keys(attrs || {}), _.bind(function(attr) {
			// Trigger a custom "bind" event for each attribute that has changed, unless {bind:false} option.
			if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr)))
				this.trigger('bind:' + attr, attrs[attr], options);
		}, this));

		return ret;
	});

	// Utilities
	// ---------

	// Evaluates the given `path` (in object/dot-notation) relative to the given `obj`.
	evaluatePath = function(obj, path) {
		var pathParts = (path || '').split('.');
		return _.reduce(pathParts, function(memo, i) { return memo[i]; }, obj) || obj;
	};

	// If the given `fn` is a string, then view[fn] is called, otherwise it is a function
	// that should be executed.
	applyViewFn = function(view, fn) {
		if (fn) return (_.isString(fn) ? view[fn] : fn).apply(view, _.toArray(arguments).slice(2));
	};

	// Gets the value from the given form element.
	getFormElVal = function($el) {
		var val;
		if ($el.is('input[type=checkbox]')) val = $el.prop('checked');
		else if ($el.is('select')) val = $el.find('option:selected').data('stickit_bind_val');
		else if ($el.is('input[type=number]')) val = Number($el.val());
		else if ($el.is('input[type="radio"]')) val = $el.filter(':checked').val();
		else val = $el.val();
		return val;
	};

	updateVisibleBindEl = function($el, val, attrName, config, context) {
		var visible = config.visible, visibleFn = config.visibleFn, isVisible = !!val;
		if (_.isFunction(visible))
			isVisible = applyViewFn(context, visible, val, attrName);
		if (visibleFn)
			applyViewFn(context, visibleFn, $el, isVisible, attrName);
		else {
			if (isVisible) $el.show();
			else $el.hide();
		}
	};

	// Update the value of `$el` in `view` using the given configuration.
	updateViewBindEl = function(view, $el, config, val, model, isInitializing) {
		var originalVal, radioEl,
			modelAttr = config.modelAttr,
			afterUpdate = config.afterUpdate,
			selectConfig = config.selectOptions,
			updateMethod = config.updateMethod || 'text';

		if ($el.is('input[type=radio]')) {
			radioEl = $el.filter('[value='+val+']');
			originalVal = radioEl.prop('checked');
			radioEl.prop('checked', true);
		} else if ($el.is('input[type=checkbox]')) {
			originalVal = $el.prop('checked');
			$el.prop('checked', val === true);
		} else if ($el.is('input') || $el.is('textarea')) {
			originalVal = $el.val();
			if (originalVal !== val) $el.val(val);
		} else if ($el.is('select')) {
			var optList, list = selectConfig.collection, fieldVal = model.get(modelAttr);

			// Get the current selected option value if the select options exist.
			if ($el[0].options.length && $el[0].selectedIndex >= 0)
				originalVal = $($el[0].options[$el[0].selectedIndex]).data('stickit_bind_val');

			$el.html('');

			// The `list` configuration is a function that returns the options list or a string
			// which represents the path to the list relative to `window`.
			optList = _.isFunction(list) ? list.call(view) : evaluatePath(window, list);

			// Add an empty default option if the current model attribute isn't defined.
			if (fieldVal == null)
				$el.append('<option/>').find('option').prop('selected', true).data('stickit_bind_val', fieldVal);

			_.each(optList, function(obj) {
				var option = $('<option/>'), optionVal = obj;

				// If the list contains a null/undefined value, then an empty option should
				// be appended in the list; otherwise, fill the option with text and value.
				if (obj != null) {
					option.text(obj[selectConfig.labelPath]);
					optionVal = evaluatePath(obj, selectConfig.valuePath);
				}

				// Save the option value so that we can reference it later.
				option.data('stickit_bind_val', optionVal);

				// Determine if this option is selected.
				if (optionVal != null && fieldVal != null && optionVal == fieldVal || (_.isObject(fieldVal) && _.isEqual(optionVal, fieldVal)))
					option.prop('selected', true);

				$el.append(option);
			});
		} else {
			originalVal = $el[updateMethod]();
			$el[updateMethod](val);
		}

		// Execute the `afterUpdate` callback from the `bindings` config.
		if (!isInitializing) applyViewFn(view, afterUpdate, $el, val, originalVal);
	};

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


<commit_before>(function() {

    var ViewKit = Backbone.ViewKit = {};

    // Views
    // ---------------

    ViewKit.ViewPort = Backbone.View.extend({

        getView: function() {
            return null;
        },

        render: function(transition) {
            var view = this.getView();
            var current = this._current;

            if (view) {
                this.$el.append(view.$el);

                if (current && transition) {
                    transition.run(current.$el, view.$el, function() {
                        current.remove();
                    });
                } else {
                    if (current) current.remove();
                }

                view.delegateEvents();

                this._current = view;
            } else {
                if (current) current.remove();
                this._current = null;
                this.$el.empty();
            }

            return this;
        },

        delegateEvents: function() {
            Backbone.View.prototype.delegateEvents.apply(this, arguments);

            var view = this.getView();
            if (view) {
                view.delegateEvents();
            }
        }

    });

    ViewKit.ViewStack = ViewKit.ViewPort.extend({

        constructor: function(options) {
            options || (options = {});

            this._stack = stack();
            this.transitions = options.transitions || {};

            ViewKit.ViewPort.prototype.constructor.apply(this, arguments);
        },

        getView: function() {
            return this._stack.top();
        },

        pushView: function(view, transition) {
            view.viewStack = this;

            this._stack.push(view);
            this.render(transition || this.transitions.push);
        },

        popView: function(transition) {
            var popped = this._stack.pop();

            if (popped) {
                this._cleanup(popped);
            }

            this.render(transition || this.transitions.pop);

            return popped;
        },

        replaceView: function(view, transition) {
            if (this._stack.empty()) {
                throw new Error('View stack is empty');
            }

            var replaced = this._stack.pop();

            if (replaced) {
                this._cleanup(replaced);
            }

            this.pushView(view, transition || this.transitions.replace);

            return replaced;
        },

        _cleanup: function(view) {
            delete view.viewStack;
        }

    });

    ViewKit.ViewSelector = ViewKit.ViewPort.extend({

        constructor: function(options) {
            this._views = [];
            this._index = null;

            options || (options = {});
            if (options.views) this.setViews(options.views);

            ViewKit.ViewPort.prototype.constructor.apply(this, arguments);
        },

        getView: function() {
            return this._views[this._index];
        },

        getViews: function() {
            return this._views;
        },

        setViews: function(views) {
            var self = this;

            _.each(this._views, function(view) {
                self._cleanup(view);
            });

            _.each(views, function(view) {
                view.viewSelector = self;
            });

            this._views = views;
            this._index = null;
        },

        selectView: function(index) {
            if (index >= this._views.length || index < 0) {
                throw new Error('Index out of bounds');
            }

            this._index = index;
            this.render();
        },

        _cleanup: function(view) {
            delete view.viewSelector;
        }

    });

    // Transitions
    // ---------------

    ViewKit.Transition = function(options) {
        options || (options = {});

        this.initialize(options);

        if (this.transition) {
            _.extend(this.transition, _.pick(options, 'duration', 'easing', 'delay'));
        }
    };

    var Config = ViewKit.Transition.Config = {
        transform: '-webkit-transform',
        transition: '-webkit-transition',
        transitionEnd: 'webkitTransitionEnd'
    };

    _.extend(ViewKit.Transition.prototype, Backbone.Events, {

        // Override these
        initialize: function(options) {},
        before: function(from, to) {},
        after: function(from, to) {},
        cleanup: function(from, to) {},

        run: function(from, to, callback) {
            this.trigger('start');

            this.before(from, to);

            if (!this.transition) {
                this.after(from, to);
                this.cleanup(from, to);
                this.trigger('end');
                return callback();
            }

            var els = from.add(to);
            var transition = [
                this.transition.property,
                this.transition.duration + 's',
                this.transition.easing,
                this.transition.delay + 's'
            ].join(' ');

            // Transition
            els.css(Config.transition, transition);
            els.on(Config.transitionEnd, transitionEnd);
            this.after(from, to);

            var count = 0;
            var self = this;

            function transitionEnd() {
                if (++count !== 2) return;

                els.css(Config.transition, '');
                els.off(Config.transitionEnd, transitionEnd);
                self.cleanup(from, to);

                self.trigger('end');
                callback();
            }
        }

    });

    ViewKit.Transition.extend = Backbone.View.extend;

    ViewKit.Transitions = {};

    // Slide

    ViewKit.Transitions.Slide = ViewKit.Transition.extend({

        transition: {
            property: Config.transform,
            duration: 0.4,
            easing: 'ease-out',
            delay: 0
        },

        initialize: function(options) {
            this.reverse = !!options.reverse;
        },

        before: function(from, to) {
            var width = from.parent().width();
            from.css('left', 0);
            to.css('left', this.reverse ? -width : width);
        },

        after: function(from, to) {
            var width = from.parent().width();
            var delta = this.reverse ? width : -width;
            var els = from.add(to);

            els.css(Config.transform, 'translateX(' + delta + 'px)');
        },

        cleanup: function(from, to) {
            var els = from.add(to);

            els.css(Config.transform, '');
            els.css('left', '');
        }

    });

    // Fade

    ViewKit.Transitions.Fade = ViewKit.Transition.extend({

        transition: {
            property: 'opacity',
            duration: 0.4,
            easing: 'ease-out',
            delay: 0
        },

        before: function(from, to) {
            to.css('opacity', 0);
            from.css('opacity', 1);
        },

        after: function(from, to) {
            to.show().css('opacity', 1);
            from.css('opacity', 0);
        },

        cleanup: function(from, to) {
            var views = from.add(to);
            views.css('opacity', '');
            views.css('display', '');
        }

    });

    // Helpers
    // ---------------

    function stack() {
        return {
            items: [],

            push: function(item) {
                this.items.push(item);
            },

            pop: function() {
                return this.items.pop();
            },

            top: function() {
                return this.items[this.items.length - 1];
            },

            empty: function() {
                return this.items.length === 0;
            }
        };
    }

})();<commit_msg>Add transitioning to view selector<commit_after>(function() {

    var ViewKit = Backbone.ViewKit = {};

    // Views
    // ---------------

    ViewKit.ViewPort = Backbone.View.extend({

        getView: function() {
            return null;
        },

        render: function(transition) {
            var view = this.getView();
            var current = this._current;

            if (view) {
                this.$el.append(view.$el);

                if (current && transition) {
                    transition.run(current.$el, view.$el, function() {
                        current.remove();
                    });
                } else {
                    if (current) current.remove();
                }

                view.delegateEvents();

                this._current = view;
            } else {
                if (current) current.remove();
                this._current = null;
                this.$el.empty();
            }

            return this;
        },

        delegateEvents: function() {
            Backbone.View.prototype.delegateEvents.apply(this, arguments);

            var view = this.getView();
            if (view) {
                view.delegateEvents();
            }
        }

    });

    ViewKit.ViewStack = ViewKit.ViewPort.extend({

        constructor: function(options) {
            options || (options = {});

            this._stack = stack();
            this.transitions = options.transitions || {};

            ViewKit.ViewPort.prototype.constructor.apply(this, arguments);
        },

        getView: function() {
            return this._stack.top();
        },

        pushView: function(view, transition) {
            view.viewStack = this;

            this._stack.push(view);
            this.render(transition || this.transitions.push);
        },

        popView: function(transition) {
            var popped = this._stack.pop();

            if (popped) {
                this._cleanup(popped);
            }

            this.render(transition || this.transitions.pop);

            return popped;
        },

        replaceView: function(view, transition) {
            if (this._stack.empty()) {
                throw new Error('View stack is empty');
            }

            var replaced = this._stack.pop();

            if (replaced) {
                this._cleanup(replaced);
            }

            this.pushView(view, transition || this.transitions.replace);

            return replaced;
        },

        _cleanup: function(view) {
            delete view.viewStack;
        }

    });

    ViewKit.ViewSelector = ViewKit.ViewPort.extend({

        constructor: function(options) {
            this._views = [];
            this._index = null;

            options || (options = {});
            this.transition = options.transition;
            if (options.views) this.setViews(options.views);

            ViewKit.ViewPort.prototype.constructor.apply(this, arguments);
        },

        getView: function() {
            return this._views[this._index];
        },

        getViews: function() {
            return this._views;
        },

        setViews: function(views) {
            var self = this;

            _.each(this._views, function(view) {
                self._cleanup(view);
            });

            _.each(views, function(view) {
                view.viewSelector = self;
            });

            this._views = views;
            this._index = null;
        },

        selectView: function(index, transition) {
            if (index >= this._views.length || index < 0) {
                throw new Error('Index out of bounds');
            }

            this._index = index;
            this.render(transition || this.transition);
        },

        _cleanup: function(view) {
            delete view.viewSelector;
        }

    });

    // Transitions
    // ---------------

    ViewKit.Transition = function(options) {
        options || (options = {});

        this.initialize(options);

        if (this.transition) {
            _.extend(this.transition, _.pick(options, 'duration', 'easing', 'delay'));
        }
    };

    var Config = ViewKit.Transition.Config = {
        transform: '-webkit-transform',
        transition: '-webkit-transition',
        transitionEnd: 'webkitTransitionEnd'
    };

    _.extend(ViewKit.Transition.prototype, Backbone.Events, {

        // Override these
        initialize: function(options) {},
        before: function(from, to) {},
        after: function(from, to) {},
        cleanup: function(from, to) {},

        run: function(from, to, callback) {
            this.trigger('start');

            this.before(from, to);

            if (!this.transition) {
                this.after(from, to);
                this.cleanup(from, to);
                this.trigger('end');
                return callback();
            }

            var els = from.add(to);
            var transition = [
                this.transition.property,
                this.transition.duration + 's',
                this.transition.easing,
                this.transition.delay + 's'
            ].join(' ');

            // Transition
            els.css(Config.transition, transition);
            els.on(Config.transitionEnd, transitionEnd);
            this.after(from, to);

            var count = 0;
            var self = this;

            function transitionEnd() {
                if (++count !== 2) return;

                els.css(Config.transition, '');
                els.off(Config.transitionEnd, transitionEnd);
                self.cleanup(from, to);

                self.trigger('end');
                callback();
            }
        }

    });

    ViewKit.Transition.extend = Backbone.View.extend;

    ViewKit.Transitions = {};

    // Slide

    ViewKit.Transitions.Slide = ViewKit.Transition.extend({

        transition: {
            property: Config.transform,
            duration: 0.4,
            easing: 'ease-out',
            delay: 0
        },

        initialize: function(options) {
            this.reverse = !!options.reverse;
        },

        before: function(from, to) {
            var width = from.parent().width();
            from.css('left', 0);
            to.css('left', this.reverse ? -width : width);
        },

        after: function(from, to) {
            var width = from.parent().width();
            var delta = this.reverse ? width : -width;
            var els = from.add(to);

            els.css(Config.transform, 'translateX(' + delta + 'px)');
        },

        cleanup: function(from, to) {
            var els = from.add(to);

            els.css(Config.transform, '');
            els.css('left', '');
        }

    });

    // Fade

    ViewKit.Transitions.Fade = ViewKit.Transition.extend({

        transition: {
            property: 'opacity',
            duration: 0.4,
            easing: 'ease-out',
            delay: 0
        },

        before: function(from, to) {
            to.css('opacity', 0);
            from.css('opacity', 1);
        },

        after: function(from, to) {
            to.show().css('opacity', 1);
            from.css('opacity', 0);
        },

        cleanup: function(from, to) {
            var views = from.add(to);
            views.css('opacity', '');
            views.css('display', '');
        }

    });

    // Helpers
    // ---------------

    function stack() {
        return {
            items: [],

            push: function(item) {
                this.items.push(item);
            },

            pop: function() {
                return this.items.pop();
            },

            top: function() {
                return this.items[this.items.length - 1];
            },

            empty: function() {
                return this.items.length === 0;
            }
        };
    }

})();<|endoftext|>


<commit_before>"use strict";
var Clipboard = {
  _getTextArea: function() {
    var el = document.createElement("textarea");
    el.style.position = "absolute";
    el.style.left = "-100%";
    this._getTextArea = function() { return el; }
    return el;
  },
  copy: function(data) {
    var textArea = this._getTextArea();
    textArea.value = data;
    document.body.appendChild(textArea);
    textArea.select();
    document.execCommand("Copy");
    textArea.remove();
    textArea.value = "";
  },
  paste: function() {
    var textArea = this._getTextArea(), value;
    document.body.appendChild(textArea);
    textArea.focus();
    document.execCommand("Paste");
    value = textArea.value;
    textArea.remove();
    textArea.value = "";
    return value;
  }
},
TabRecency = {
  tabs: null,
  last: null,
  stamp: function() {
    var cache = Object.create(null), last = 0, stamp = 1, time = 0,
    _this = TabRecency, clean = function() {
      var ref = cache, i;
      for (i in ref) {
        if (ref[i] <= 896) { delete ref[i]; }
        else {ref[i] -= 895; }
      }
      stamp = 128;
    };
    chrome.tabs.onActivated.addListener(function(activeInfo) {
      var now = Date.now(), tabId = activeInfo.tabId;
      if (now - time > 500) {
        cache[last] = ++stamp;
        if (stamp === 1023) { clean(); }
      }
      last = tabId; time = now;
    });
    chrome.tabs.query({currentWindow: true, active: true}, function(tab) {
      time = Date.now();
      if (chrome.runtime.lastError) { return chrome.runtime.lastError; }
      last = tab.id;
    });
    _this.tabs = cache;
    _this.last = function() { return last; };
    _this.stamp = function() { return stamp; };
  }
};

setTimeout(TabRecency.stamp, 120);
<commit_msg>TabRecency: fix a typo and add a default value to last<commit_after>"use strict";
var Clipboard = {
  _getTextArea: function() {
    var el = document.createElement("textarea");
    el.style.position = "absolute";
    el.style.left = "-100%";
    this._getTextArea = function() { return el; }
    return el;
  },
  copy: function(data) {
    var textArea = this._getTextArea();
    textArea.value = data;
    document.body.appendChild(textArea);
    textArea.select();
    document.execCommand("Copy");
    textArea.remove();
    textArea.value = "";
  },
  paste: function() {
    var textArea = this._getTextArea(), value;
    document.body.appendChild(textArea);
    textArea.focus();
    document.execCommand("Paste");
    value = textArea.value;
    textArea.remove();
    textArea.value = "";
    return value;
  }
},
TabRecency = {
  tabs: null,
  last: null,
  stamp: function() {
    var cache = Object.create(null), last = 0, stamp = 1, time = 0,
    _this = TabRecency, clean = function() {
      var ref = cache, i;
      for (i in ref) {
        if (ref[i] <= 896) { delete ref[i]; }
        else {ref[i] -= 895; }
      }
      stamp = 128;
    };
    chrome.tabs.onActivated.addListener(function(activeInfo) {
      var now = Date.now(), tabId = activeInfo.tabId;
      if (now - time > 500) {
        cache[last] = ++stamp;
        if (stamp === 1023) { clean(); }
      }
      last = tabId; time = now;
    });
    chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
      time = Date.now();
      if (chrome.runtime.lastError) { return chrome.runtime.lastError; }
      last = tabs[0] ? tabs[0].id : chrome.tabs.TAB_ID_NONE;
    });
    _this.tabs = cache;
    _this.last = function() { return last; };
    _this.stamp = function() { return stamp; };
  }
};

setTimeout(TabRecency.stamp, 120);
<|endoftext|>


<commit_before>class Application extends Model {

    constructor(data) {
        super(data);
        this.databases   = [];
        this.dataSources = [];
        this.pageLinks   = [];
    }

    init() {
        // databases
        for (const name in this.data.databases) {
            this.createDatabase(this.data.databases[name]);
        }

        // dataSources
        for (const name in this.data.dataSources) {
            this.createDataSource(this.data.dataSources[name]);
        }

        // pageLinks
        for (const name in this.data.pageLinks) {
            this.createPageLink(this.data.pageLinks[name]);
        }
    }

    createDatabase(data) {
        const database = new Database(data);
        database.init();
        this.databases.push(database);
    }
    createDataSource(data) {
        const dataSource = new DataSource(data);
        dataSource.init();
        this.dataSources.push(dataSource);
    }
    createPageLink(data) {
        const pageLink = new PageLink(data, this);
        pageLink.init();
        this.pageLinks.push(pageLink);
    }
    async setValue(name, value) {
        //console.log(name + ' = ' + value);
        const data = await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'save',
            params    : Helper.encodeObject({
                attr : name,
                value: value
            })
        });
        this.data['@attributes'][name] = value;
        return data;
    }

    async newPage(params) {
        params['menu'] = (params['startup'] === 'true') ? 'Pages' : '';
        const data = await QForms.doHttpRequest({
            controller: 'Page',
            action    : '_new',
            params    : Helper.encodeObject(params)
        });
        return [data.page, data.pageLink];
    }

    async newDatabase(params) {
        const data = await QForms.doHttpRequest({
            controller: 'Database',
            action    : '_new',
            params    : Helper.encodeObject(params)
        });
        return data;
    }

    async getView(view) {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'getView',
            params    : Helper.encodeObject({
                app : this.data['@attributes'].name,
                view: view
            })
        });
    }

    async saveView(text, view) {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'saveView',
            params    : Helper.encodeObject({
                app : this.data['@attributes'].name,
                view: view,
                text: text
            })
        });
    }

    async saveController(text) {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'saveController',
            params    : Helper.encodeObject({
                app : this.data['@attributes'].name,
                text: text
            })
        });
    }

    async createView() {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'createView',
            params    : Helper.encodeObject({
                app: this.data['@attributes'].name
            })
        });
    }

    async createController() {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'createController',
            params    : Helper.encodeObject({
                app: this.data['@attributes'].name
            })
        });
    }

    async newDataSource(params) {
        return await QForms.doHttpRequest({
            controller: 'DataSource',
            action    : '_new',
            params    : Helper.encodeObject(params)
        });
    }

}
<commit_msg>create page link for new page<commit_after>class Application extends Model {

    constructor(data) {
        super(data);
        this.databases   = [];
        this.dataSources = [];
        this.pageLinks   = [];
    }

    init() {
        // databases
        for (const name in this.data.databases) {
            this.createDatabase(this.data.databases[name]);
        }

        // dataSources
        for (const name in this.data.dataSources) {
            this.createDataSource(this.data.dataSources[name]);
        }

        // pageLinks
        for (const name in this.data.pageLinks) {
            this.createPageLink(this.data.pageLinks[name]);
        }
    }

    createDatabase(data) {
        const database = new Database(data);
        database.init();
        this.databases.push(database);
    }
    createDataSource(data) {
        const dataSource = new DataSource(data);
        dataSource.init();
        this.dataSources.push(dataSource);
    }
    createPageLink(data) {
        const pageLink = new PageLink(data, this);
        pageLink.init();
        this.pageLinks.push(pageLink);
    }
    async setValue(name, value) {
        //console.log(name + ' = ' + value);
        const data = await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'save',
            params    : Helper.encodeObject({
                attr : name,
                value: value
            })
        });
        this.data['@attributes'][name] = value;
        return data;
    }

    async newPage(params) {
        params['menu'] = (params['startup'] === 'true') ? 'Pages' : '';
        const data = await QForms.doHttpRequest({
            controller: 'Page',
            action    : '_new',
            params    : Helper.encodeObject(params)
        });
        this.createPageLink(data.pageLink);
        return [data.page, data.pageLink];
    }

    async newDatabase(params) {
        const data = await QForms.doHttpRequest({
            controller: 'Database',
            action    : '_new',
            params    : Helper.encodeObject(params)
        });
        return data;
    }

    async getView(view) {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'getView',
            params    : Helper.encodeObject({
                app : this.data['@attributes'].name,
                view: view
            })
        });
    }

    async saveView(text, view) {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'saveView',
            params    : Helper.encodeObject({
                app : this.data['@attributes'].name,
                view: view,
                text: text
            })
        });
    }

    async saveController(text) {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'saveController',
            params    : Helper.encodeObject({
                app : this.data['@attributes'].name,
                text: text
            })
        });
    }

    async createView() {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'createView',
            params    : Helper.encodeObject({
                app: this.data['@attributes'].name
            })
        });
    }

    async createController() {
        return await QForms.doHttpRequest({
            controller: 'Application',
            action    : 'createController',
            params    : Helper.encodeObject({
                app: this.data['@attributes'].name
            })
        });
    }

    async newDataSource(params) {
        return await QForms.doHttpRequest({
            controller: 'DataSource',
            action    : '_new',
            params    : Helper.encodeObject(params)
        });
    }

}
<|endoftext|>


<commit_before>jQuery(document).ready(function ()
{
    var $world = jQuery('#apps-physics').jQb2({
        gravity: [0, 16],
        sleep: false,
        debug: false,
        mouse: true,
        autobody: false,
        bounds: true,
        controls: false,
        paused: false,
        fps: 60,
    });

    var worldScale = 30;

    function fromPixel(pos) {return parseInt(pos, 10) / worldScale}
    function fromPixel(pos) {return parseInt(pos, 10) / worldScale}

    var availableApps = [];
    var appSize = 96;
    var appRealSize = 76;

    var world   = $world.data('b2World');
    var objects = $world.data('b2Objects');

    var $apps = $world.find('div.apps-icon').each(function(index, value) {
        var $this = $(this);
        var el = this;

        var bodyDef = new Box2D.Dynamics.b2BodyDef();
        bodyDef.angle = (-Math.PI / 8) + (Math.random() * (Math.PI / 4));
        if (bodyDef.angle > 0) {
            bodyDef.angularVelocity = Math.random() * -0.25;
        } else {
            bodyDef.angularVelocity = Math.random() * 0.25;
        }
        bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;

        var fixDef = new Box2D.Dynamics.b2FixtureDef();

        fixDef.density = 10.0;
        fixDef.friction = 0.5;
        fixDef.restitution = 0.2;

        var outerW = appRealSize;
        var outerH = appRealSize;
        var posX = 80 + Math.floor(Math.random() * (140 - appRealSize) + 1);
        var posY = 0;

        bodyDef.position.Set(
            fromPixel(posX + (outerW / 2)),
            fromPixel(posY + (outerH / 2))
        );

        availableApps.push([el, bodyDef, fixDef]);

    });

    var spawnInterval = setInterval(function() {
        if (availableApps.length > 0) {
            spawnApp();
        } else {
            clearInterval(spawnInterval);
        }
    }, 500);

    function spawnApp()
    {
        if (availableApps.length == 0) {
            return;

        }

        var data = availableApps.pop();

        $(data[0]).css(
            {
                'position' : 'absolute',
                'display' : 'block'
            }
        );

        var body = world.CreateBody(data[1]);
        $.data(data[0], 'b2Body', body);
        $.data(data[0], 'b2Type', 'DOM-Circle');

        data[2].shape = new Box2D.Collision.Shapes.b2CircleShape(
            fromPixel(appRealSize / 2)
        );

        body.CreateFixture(data[2]);
        objects.worldBodies.push(data[0]);
    };

    var $button = $('#marketplace-button');

    var isFirefox18Android = (function() {
        var ua = navigator.userAgent;
        var matches = ua.match(/Android;.*(?:Firefox|Aurora)\/(\d+)\./);
        return (matches && matches[1] >= 18);
    })();

    if (isFirefox18Android) {

        // Hide default text in "Showcased" section, show Fx-specific version
        $('#showcased-nonfx').hide();
        $('#showcased-fx').show();

        // Remove the qr-codes from app previews
        $('.qr-code').remove();

        // swap marketplace button title
        $button.text($button.attr('data-mobile-title'));

    } else {

        var documentClickHandler = function(e)
        {
            var $target = $(e.target);

            // skip if we clicked on the panel
            if (   $target.is($panel)
                || $target.parents('#marketplace-panel').length > 0
                || $target.is($button)
                || $target.parents('#marketplace-button').length > 0
            ) {
                return;
            }

            // close the panel
            if ($panel.css('display') == 'block') {
                $panel.fadeOut();
            }
        }

        // prevent linking to apps, just let QR codes appear
        $('#apps-preview .pager-page a').click(function(e) {
            e.preventDefault();
        });

        // make clicking the button open the panel
        $panel = $('#marketplace-panel');
        $button.click(function(e) {
            e.preventDefault(e);

            // add document click-to-close handler
            if ($panel.css('display') == 'block') {
                $(document).unbind('click', documentClickHandler);
            } else {
                $(document).click(documentClickHandler);
            }

            $panel.fadeToggle();
        });

    }

});
<commit_msg>Don't click-document-to-close for mobile layout. Refs Bug #745033.<commit_after>jQuery(document).ready(function ()
{
    var $world = jQuery('#apps-physics').jQb2({
        gravity: [0, 16],
        sleep: false,
        debug: false,
        mouse: true,
        autobody: false,
        bounds: true,
        controls: false,
        paused: false,
        fps: 60,
    });

    var worldScale = 30;

    function fromPixel(pos) {return parseInt(pos, 10) / worldScale}
    function fromPixel(pos) {return parseInt(pos, 10) / worldScale}

    var availableApps = [];
    var appSize = 96;
    var appRealSize = 76;

    var world   = $world.data('b2World');
    var objects = $world.data('b2Objects');

    var $apps = $world.find('div.apps-icon').each(function(index, value) {
        var $this = $(this);
        var el = this;

        var bodyDef = new Box2D.Dynamics.b2BodyDef();
        bodyDef.angle = (-Math.PI / 8) + (Math.random() * (Math.PI / 4));
        if (bodyDef.angle > 0) {
            bodyDef.angularVelocity = Math.random() * -0.25;
        } else {
            bodyDef.angularVelocity = Math.random() * 0.25;
        }
        bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody;

        var fixDef = new Box2D.Dynamics.b2FixtureDef();

        fixDef.density = 10.0;
        fixDef.friction = 0.5;
        fixDef.restitution = 0.2;

        var outerW = appRealSize;
        var outerH = appRealSize;
        var posX = 80 + Math.floor(Math.random() * (140 - appRealSize) + 1);
        var posY = 0;

        bodyDef.position.Set(
            fromPixel(posX + (outerW / 2)),
            fromPixel(posY + (outerH / 2))
        );

        availableApps.push([el, bodyDef, fixDef]);

    });

    var spawnInterval = setInterval(function() {
        if (availableApps.length > 0) {
            spawnApp();
        } else {
            clearInterval(spawnInterval);
        }
    }, 500);

    function spawnApp()
    {
        if (availableApps.length == 0) {
            return;

        }

        var data = availableApps.pop();

        $(data[0]).css(
            {
                'position' : 'absolute',
                'display' : 'block'
            }
        );

        var body = world.CreateBody(data[1]);
        $.data(data[0], 'b2Body', body);
        $.data(data[0], 'b2Type', 'DOM-Circle');

        data[2].shape = new Box2D.Collision.Shapes.b2CircleShape(
            fromPixel(appRealSize / 2)
        );

        body.CreateFixture(data[2]);
        objects.worldBodies.push(data[0]);
    };

    var $button = $('#marketplace-button');

    var isFirefox18Android = (function() {
        var ua = navigator.userAgent;
        var matches = ua.match(/Android;.*(?:Firefox|Aurora)\/(\d+)\./);
        return (matches && matches[1] >= 18);
    })();

    if (isFirefox18Android) {

        // Hide default text in "Showcased" section, show Fx-specific version
        $('#showcased-nonfx').hide();
        $('#showcased-fx').show();

        // Remove the qr-codes from app previews
        $('.qr-code').remove();

        // swap marketplace button title
        $button.text($button.attr('data-mobile-title'));

    } else {

        var documentClickHandler = function(e)
        {
            var $target = $(e.target);

            // skip if we clicked on the panel
            if (   $target.is($panel)
                || $target.parents('#marketplace-panel').length > 0
                || $target.is($button)
                || $target.parents('#marketplace-button').length > 0
            ) {
                return;
            }

            // skip if relatively positioned (mobile layout)
            if ($panel.css('position') == 'relative') {
                return;
            }

            // close the panel
            if ($panel.css('display') == 'block') {
                $panel.fadeOut();
            }
        }

        // prevent linking to apps, just let QR codes appear
        $('#apps-preview .pager-page a').click(function(e) {
            e.preventDefault();
        });

        // make clicking the button open the panel
        $panel = $('#marketplace-panel');
        $button.click(function(e) {
            e.preventDefault(e);

            // add document click-to-close handler
            if ($panel.css('display') == 'block') {
                $(document).unbind('click', documentClickHandler);
            } else {
                $(document).click(documentClickHandler);
            }

            $panel.fadeToggle();
        });

    }

});
<|endoftext|>


<commit_before>// Amara, universalsubtitles.org
//
// Copyright (C) 2012 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('unisubs.translate.Dialog');

/**
 * @constructor
 *
 */
unisubs.translate.Dialog = function(opener, serverModel, videoSource, subtitleState, standardSubState, reviewOrApprovalType) {
    unisubs.Dialog.call(this, videoSource);
    unisubs.SubTracker.getInstance().start(true);
    this.opener_ = opener;
    this.subtitleState_ = subtitleState;
    this.standardSubState_ = standardSubState;
    this.serverModel_ = serverModel;
    this.serverModel_.init();

    this.captionManager_ =
        new unisubs.CaptionManager(
            this.getVideoPlayerInternal(),
            new unisubs.subtitle.EditableCaptionSet(this.standardSubState_.SUBTITLES));

    this.saved_ = false;
    this.rightPanelListener_ = new goog.events.EventHandler(this);
    this.reviewOrApprovalType_ = reviewOrApprovalType;
    // if this is a review approve dialog, we must fetch saved notes for this task if available
    // but we should only do it when the dialog laods the first time
    // (else as users move back and forward between panels we might
    // overwrite their notes). This is what this flag is for
    this.notesFectched_ = false;
    if ( !Boolean(this.reviewOrApprovalType_)){
       this.notesFectched_ = true;
    }

    this.alreadySaving_ = false;
};

goog.inherits(unisubs.translate.Dialog, unisubs.subtitle.Dialog);

unisubs.translate.Dialog.State_ = {
    TRANSLATE: 0,
    EDIT_METADATA: 1
};
unisubs.translate.Dialog.prototype.createDom = function() {
    unisubs.translate.Dialog.superClass_.createDom.call(this);
    this.setDraggable(false);
    this.showGuidelines_();
    this.enterState_(unisubs.translate.Dialog.State_.TRANSLATE);
};
unisubs.translate.Dialog.prototype.showGuidelines_ = function() {
    var skipGuidelines = unisubs.UserSettings.getBooleanValue(
                            unisubs.UserSettings.Settings.ALWAYS_SKIP_GUIDELINES);

    if(skipGuidelines){
        return;
    }

    if (!unisubs.guidelines['translate']) var s = unisubs.subtitle.Dialog.State_;
    // the same dialog can be used in transcribing or review approval, which guidelines should we use?
    var guideline = this.reviewOrApprovalType_ ? this.getTeamGuidelineForReview() : unisubs.guidelines['translate'];

    if (!guideline) {
        return;
    }
    var guidelinesPanel = new unisubs.GuidelinesPanel(guideline);
    this.showTemporaryPanel(guidelinesPanel);
    this.displayingGuidelines_ = true;

    var that = this;
    this.getHandler().listenOnce(guidelinesPanel, unisubs.GuidelinesPanel.CONTINUE, function(e) {
        goog.Timer.callOnce(function() {
            that.displayingGuidelines_ = false;
            that.hideTemporaryPanel();
        });
    });
};
unisubs.translate.Dialog.prototype.handleSaveAndExitKeyPress_ = function(e) {
    e.preventDefault();
    var that = this;
    setTimeout(function(){
        that.saveWork(false, true);
    }, 1500);
};
unisubs.translate.Dialog.prototype.handleDoneKeyPress_ = function(event) {
    event.preventDefault();
    if (this.state_ == unisubs.translate.Dialog.State_.EDIT_METADATA) {

        // Make sure each new caption has text.
        var halt = false;

        goog.array.forEach(this.translationPanel_.captionSet_.captions_, function(c) {
            if (c.getText() === '') {
                halt = true;
            }
        });

        if (halt === true) {
            alert('You have untranslated captions. You must translate all captions in order to submit.');
            return false;
        }

        this.saveWork(true, false);

    }
    else {
        this.enterState_(unisubs.translate.Dialog.State_.EDIT_METADATA);
    }
};
unisubs.translate.Dialog.prototype.isWorkSaved = function() {
    if (this.reviewOrApprovalType_) {
        if (this.getNotesContent_(this.currentSubtitlePanel_) !== '') {
            return this.saved_;
        }
    }
    return this.saved_ || !this.serverModel_.anySubtitlingWorkDone();
};
unisubs.translate.Dialog.prototype.enterDocument = function() {
    // this is where we listen to the dialog close button
    unisubs.subtitle.Dialog.superClass_.enterDocument.call(this);
    unisubs.Dialog.translationDialogOpen = true;

    this.getHandler().
        listen(
            this.captionManager_,
            unisubs.CaptionManager.CAPTION,
            this.captionReached_);

    if (this.reviewOrApprovalType_ && !this.notesFectched_){
        var func  = this.serverModel_.fetchReviewData ;
        var that = this;
        if (this.reviewOrApprovalType_ == unisubs.Dialog.REVIEW_OR_APPROVAL.APPROVAL){
            func = this.serverModel_.fetchApproveData;
        }

        // make sure we retain the correct scope
        func.call(this.serverModel_, unisubs.task_id, function(body) {
            that.onNotesFetched_(body);
        });
    }
};
unisubs.translate.Dialog.prototype.onNotesFetched_ = function(body) {
    if( this.currentSubtitlePanel_ && this.currentSubtitlePanel_.setNotesContent_){
        this.currentSubtitlePanel_.setNotesContent_(body);
    }
};
unisubs.translate.Dialog.prototype.saveWorkInternal = function(closeAfterSave, saveForLater) {
    if(this.alreadySaving_){
        return;
    }

    this.alreadySaving_ = true;

    var notes = this.getNotesContent_(this.currentSubtitlePanel_);
    if (notes !== '') {
        this.serverModel_.setTaskNotes(notes);
    }

    if (goog.array.isEmpty(
        this.serverModel_.captionSet_.nonblankSubtitles()) && !this.forceSave_){
        this.alreadySaving_ = false;
        if((this.captionSet_.hasTitleChanged() && this.captionSet_.originalTitle == "") ||
             (this.captionSet_.hasDescriptionChanged() && this.captionSet_.originalDescription == "")){
            this.showTitleDescriptionChangedDialog(closeAfterSave, saveForLater);
        } else {
            // there are no subs here, close dialog or back to subtitling
            this.showEmptySubsDialog();
        }
        return;
    }

    var that = this;
    this.getRightPanelInternal().showLoading(true);
    this.serverModel_.finish(
        function(serverMsg){
            unisubs.subtitle.OnSavedDialog.show(serverMsg, function(){
                that.onWorkSaved(closeAfterSave);
            });
        },
        function(opt_status) {
            if (that.finishFailDialog_)
                that.finishFailDialog_.failedAgain(opt_status);
            else
                that.finishFailDialog_ = unisubs.finishfaildialog.Dialog.show(
                    that.serverModel_.getCaptionSet(), opt_status,
                    goog.bind(that.saveWorkInternal, that, closeAfterSave));
        },
        null,
        saveForLater);
};
unisubs.translate.Dialog.prototype.showGuidelinesForState_ = function(state) {
    this.setState_(state);
};
unisubs.Dialog.prototype.setVisible = function(visible) {
    if (visible) {
        unisubs.Dialog.superClass_.setVisible.call(this, true);
        goog.dom.getDocumentScrollElement().scrollTop = 0;
    }
    else {
        if (this.isWorkSaved()) {
            this.hideDialogImpl_();
        }
        else {
            this.showSaveWorkDialog_();
        }
    }
};
unisubs.translate.Dialog.prototype.onWorkSaved = function() {
    if (this.finishFailDialog_) {
        this.finishFailDialog_.setVisible(false);
        this.finishFailDialog_ = null;
    }
    unisubs.widget.ResumeEditingRecord.clear();
    this.getRightPanelInternal().showLoading(false);
    this.saved_ = true;
    this.setVisible(false);
};
unisubs.translate.Dialog.prototype.disposeInternal = function() {
    unisubs.translate.Dialog.superClass_.disposeInternal.call(this);
    this.serverModel_.dispose();
};
unisubs.translate.Dialog.prototype.enterState_ = function(state) {
    this.showGuidelinesForState_(state);
};
unisubs.translate.Dialog.prototype.suspendKeyEvents_ = function(suspended) {
    this.keyEventsSuspended_ = suspended;
    if (this.currentSubtitlePanel_ && this.currentSubtitlePanel_.suspendKeyEvents)
        this.currentSubtitlePanel_.suspendKeyEvents(suspended);
};
unisubs.translate.Dialog.prototype.setState_ = function(state) {
    this.state_ = state;

    this.suspendKeyEvents_(false);

    var s = unisubs.translate.Dialog.State_;

    this.setExtraClass_(' the-translate-panel');

    var nextSubPanel = this.makeCurrentStateSubtitlePanel_();
    var captionPanel = this.getCaptioningAreaInternal();
    captionPanel.removeChildren(true);
    captionPanel.addChild(nextSubPanel, true);

    var rightPanel = nextSubPanel.getRightPanel();
    this.setRightPanelInternal(rightPanel);

    this.getTimelinePanelInternal().removeChildren(true);

    if (this.currentSubtitlePanel_ && this.currentSubtitlePanel_.getNotesContent_){
       var currentNoteContent =  this.currentSubtitlePanel_.getNotesContent_();
       if (nextSubPanel  && nextSubPanel.setNotesContent_){
           nextSubPanel.setNotesContent_(currentNoteContent);
       }
    }

    this.disposeCurrentPanels_();
    this.currentSubtitlePanel_ = nextSubPanel;

    var et = unisubs.RightPanel.EventType;
    this.rightPanelListener_.
        listen(
            rightPanel, et.LEGENDKEY, this.handleLegendKeyPress_).
        listen(
            rightPanel, et.DONE, this.handleDoneKeyPress_).
        listen(
            rightPanel, et.SAVEANDEXIT, this.handleSaveAndExitKeyPress_).
        listen(
            rightPanel, et.GOTOSTEP, this.handleGoToStep_);
    var backButtonText = null;
    if (state == s.EDIT_METADATA ){
        backButtonText = "Back to Translation";
    }
    if (backButtonText){
        rightPanel.showBackLink(backButtonText);
        this.rightPanelListener_.listen(
            rightPanel, et.BACK, this.handleBackKeyPress_);

    }

    var videoPlayer = this.getVideoPlayerInternal();
    if (this.isInDocument()) {
        if (!videoPlayer.isPaused()){
            videoPlayer.pause();
        }
        if(videoPlayer.getPlayheadTime()){
            videoPlayer.setPlayheadTime(0);
            videoPlayer.showCaptionText('');
            videoPlayer.pause();
        }
    }

    var that = this;
    this.getRightPanelInternal().showDownloadLink(
        function() {
            return that.makeJsonSubs();
        });
};
unisubs.translate.Dialog.prototype.makeCurrentStateSubtitlePanel_ = function() {
    var s = unisubs.translate.Dialog.State_;

    if (this.state_ == s.TRANSLATE){
        this.translationPanel_ =  new unisubs.translate.TranslationPanel(
            this.serverModel_.getCaptionSet(),
            this.standardSubState_,
            this,
            this.reviewOrApprovalType_,
            this.serverModel_);
        return this.translationPanel_;
    }
    else if (this.state_ == s.EDIT_METADATA)
        return new unisubs.editmetadata.Panel(
            this.serverModel_.getCaptionSet(),
            this.getVideoPlayerInternal(),
            this.serverModel_,
            this.captionManager_,
            this.standardSubState_ ,
            false,
            this.reviewOrApprovalType_,
            this);
};

/**
 * Tries translate subtitles with BingTranslator
 */
unisubs.translate.Dialog.prototype.translateViaBing = function(){
    //I don't know how better call this. I think it should be incapsulated in translationList_,
    //but have chain of function calls can confuse.
    this.translationPanel_.getTranslationList().translateViaBing(
        this.standardSubState_.LANGUAGE, this.subtitleState_.LANGUAGE);
};
unisubs.translate.Dialog.prototype.getStandardLanguage = function(){
    return this.standardSubState_.LANGUAGE;
};
unisubs.translate.Dialog.prototype.getSubtitleLanguage = function(){
    return this.subtitleState_.LANGUAGE;
};
unisubs.translate.Dialog.prototype.getServerModel = function(){
    return this.serverModel_;
};
unisubs.translate.Dialog.prototype.makeJsonSubs =  function (){
    return this.serverModel_.getCaptionSet().makeJsonSubs();
};
unisubs.translate.Dialog.prototype.forkAndClose = function() {
    var dialog = new unisubs.translate.ForkDialog(
        goog.bind(this.forkImpl_, this));
    dialog.setVisible(true);
};
unisubs.translate.Dialog.prototype.forkImpl_ = function() {
    this.subtitleState_.fork();
    this.serverModel_.fork(this.standardSubState_);
    this.hideToFork();
    this.opener_.openSubtitlingDialog(
        this.serverModel_,
        this.subtitleState_);
};
unisubs.translate.Dialog.prototype.disposeCurrentPanels_ = function() {
    if (this.currentSubtitlePanel_) {
        this.currentSubtitlePanel_.dispose();
        this.currentSubtitlePanel_ = null;
    }
    this.rightPanelListener_.removeAll();
    if (this.timelineSubtitleSet_) {
        this.timelineSubtitleSet_.dispose();
        this.timelineSubtitleSet_ = null;
    }
};
unisubs.translate.Dialog.prototype.captionReached_ = function(event) {
    var c = event.caption;
    var caption;

    if (c) {

        var captionID = c.getCaptionID();

        var translatedCaptionSet = this.translationPanel_.getTranslationList().captionSet_;
        var translatedCaption = translatedCaptionSet.captionByID(captionID);

        if (translatedCaption.getText() !== '') {
            caption = translatedCaption.getText();
        } else {
            caption = c.getText();
        }

    } else {
        caption = '';
    }

    this.getVideoPlayerInternal().showCaptionText(caption);
};
<commit_msg>Re-init the dialog after we convert a translation to timed subtitles.<commit_after>// Amara, universalsubtitles.org
//
// Copyright (C) 2012 Participatory Culture Foundation
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see
// http://www.gnu.org/licenses/agpl-3.0.html.

goog.provide('unisubs.translate.Dialog');

/**
 * @constructor
 *
 */
unisubs.translate.Dialog = function(opener, serverModel, videoSource, subtitleState, standardSubState, reviewOrApprovalType) {
    unisubs.Dialog.call(this, videoSource);
    unisubs.SubTracker.getInstance().start(true);
    this.opener_ = opener;
    this.subtitleState_ = subtitleState;
    this.standardSubState_ = standardSubState;
    this.serverModel_ = serverModel;
    this.serverModel_.init();

    this.captionManager_ =
        new unisubs.CaptionManager(
            this.getVideoPlayerInternal(),
            new unisubs.subtitle.EditableCaptionSet(this.standardSubState_.SUBTITLES));

    this.saved_ = false;
    this.rightPanelListener_ = new goog.events.EventHandler(this);
    this.reviewOrApprovalType_ = reviewOrApprovalType;
    // if this is a review approve dialog, we must fetch saved notes for this task if available
    // but we should only do it when the dialog laods the first time
    // (else as users move back and forward between panels we might
    // overwrite their notes). This is what this flag is for
    this.notesFectched_ = false;
    if ( !Boolean(this.reviewOrApprovalType_)){
       this.notesFectched_ = true;
    }

    this.alreadySaving_ = false;
};

goog.inherits(unisubs.translate.Dialog, unisubs.subtitle.Dialog);

unisubs.translate.Dialog.State_ = {
    TRANSLATE: 0,
    EDIT_METADATA: 1
};
unisubs.translate.Dialog.prototype.createDom = function() {
    unisubs.translate.Dialog.superClass_.createDom.call(this);
    this.setDraggable(false);
    this.showGuidelines_();
    this.enterState_(unisubs.translate.Dialog.State_.TRANSLATE);
};
unisubs.translate.Dialog.prototype.showGuidelines_ = function() {
    var skipGuidelines = unisubs.UserSettings.getBooleanValue(
                            unisubs.UserSettings.Settings.ALWAYS_SKIP_GUIDELINES);

    if(skipGuidelines){
        return;
    }

    if (!unisubs.guidelines['translate']) var s = unisubs.subtitle.Dialog.State_;
    // the same dialog can be used in transcribing or review approval, which guidelines should we use?
    var guideline = this.reviewOrApprovalType_ ? this.getTeamGuidelineForReview() : unisubs.guidelines['translate'];

    if (!guideline) {
        return;
    }
    var guidelinesPanel = new unisubs.GuidelinesPanel(guideline);
    this.showTemporaryPanel(guidelinesPanel);
    this.displayingGuidelines_ = true;

    var that = this;
    this.getHandler().listenOnce(guidelinesPanel, unisubs.GuidelinesPanel.CONTINUE, function(e) {
        goog.Timer.callOnce(function() {
            that.displayingGuidelines_ = false;
            that.hideTemporaryPanel();
        });
    });
};
unisubs.translate.Dialog.prototype.handleSaveAndExitKeyPress_ = function(e) {
    e.preventDefault();
    var that = this;
    setTimeout(function(){
        that.saveWork(false, true);
    }, 1500);
};
unisubs.translate.Dialog.prototype.handleDoneKeyPress_ = function(event) {
    event.preventDefault();
    if (this.state_ == unisubs.translate.Dialog.State_.EDIT_METADATA) {

        // Make sure each new caption has text.
        var halt = false;

        goog.array.forEach(this.translationPanel_.captionSet_.captions_, function(c) {
            if (c.getText() === '') {
                halt = true;
            }
        });

        if (halt === true) {
            alert('You have untranslated captions. You must translate all captions in order to submit.');
            return false;
        }

        this.saveWork(true, false);

    }
    else {
        this.enterState_(unisubs.translate.Dialog.State_.EDIT_METADATA);
    }
};
unisubs.translate.Dialog.prototype.isWorkSaved = function() {
    if (this.reviewOrApprovalType_) {
        if (this.getNotesContent_(this.currentSubtitlePanel_) !== '') {
            return this.saved_;
        }
    }
    return this.saved_ || !this.serverModel_.anySubtitlingWorkDone();
};
unisubs.translate.Dialog.prototype.enterDocument = function() {
    // this is where we listen to the dialog close button
    unisubs.subtitle.Dialog.superClass_.enterDocument.call(this);
    unisubs.Dialog.translationDialogOpen = true;

    this.getHandler().
        listen(
            this.captionManager_,
            unisubs.CaptionManager.CAPTION,
            this.captionReached_);

    if (this.reviewOrApprovalType_ && !this.notesFectched_){
        var func  = this.serverModel_.fetchReviewData ;
        var that = this;
        if (this.reviewOrApprovalType_ == unisubs.Dialog.REVIEW_OR_APPROVAL.APPROVAL){
            func = this.serverModel_.fetchApproveData;
        }

        // make sure we retain the correct scope
        func.call(this.serverModel_, unisubs.task_id, function(body) {
            that.onNotesFetched_(body);
        });
    }
};
unisubs.translate.Dialog.prototype.onNotesFetched_ = function(body) {
    if( this.currentSubtitlePanel_ && this.currentSubtitlePanel_.setNotesContent_){
        this.currentSubtitlePanel_.setNotesContent_(body);
    }
};
unisubs.translate.Dialog.prototype.saveWorkInternal = function(closeAfterSave, saveForLater) {
    if(this.alreadySaving_){
        return;
    }

    this.alreadySaving_ = true;

    var notes = this.getNotesContent_(this.currentSubtitlePanel_);
    if (notes !== '') {
        this.serverModel_.setTaskNotes(notes);
    }

    if (goog.array.isEmpty(
        this.serverModel_.captionSet_.nonblankSubtitles()) && !this.forceSave_){
        this.alreadySaving_ = false;
        if((this.captionSet_.hasTitleChanged() && this.captionSet_.originalTitle == "") ||
             (this.captionSet_.hasDescriptionChanged() && this.captionSet_.originalDescription == "")){
            this.showTitleDescriptionChangedDialog(closeAfterSave, saveForLater);
        } else {
            // there are no subs here, close dialog or back to subtitling
            this.showEmptySubsDialog();
        }
        return;
    }

    var that = this;
    this.getRightPanelInternal().showLoading(true);
    this.serverModel_.finish(
        function(serverMsg){
            unisubs.subtitle.OnSavedDialog.show(serverMsg, function(){
                that.onWorkSaved(closeAfterSave);
            });
        },
        function(opt_status) {
            if (that.finishFailDialog_)
                that.finishFailDialog_.failedAgain(opt_status);
            else
                that.finishFailDialog_ = unisubs.finishfaildialog.Dialog.show(
                    that.serverModel_.getCaptionSet(), opt_status,
                    goog.bind(that.saveWorkInternal, that, closeAfterSave));
        },
        null,
        saveForLater);
};
unisubs.translate.Dialog.prototype.showGuidelinesForState_ = function(state) {
    this.setState_(state);
};
unisubs.Dialog.prototype.setVisible = function(visible) {
    if (visible) {
        unisubs.Dialog.superClass_.setVisible.call(this, true);
        goog.dom.getDocumentScrollElement().scrollTop = 0;
    }
    else {
        if (this.isWorkSaved()) {
            this.hideDialogImpl_();
        }
        else {
            this.showSaveWorkDialog_();
        }
    }
};
unisubs.translate.Dialog.prototype.onWorkSaved = function() {
    if (this.finishFailDialog_) {
        this.finishFailDialog_.setVisible(false);
        this.finishFailDialog_ = null;
    }
    unisubs.widget.ResumeEditingRecord.clear();
    this.getRightPanelInternal().showLoading(false);
    this.saved_ = true;
    this.setVisible(false);
};
unisubs.translate.Dialog.prototype.disposeInternal = function() {
    unisubs.translate.Dialog.superClass_.disposeInternal.call(this);
    this.serverModel_.dispose();
};
unisubs.translate.Dialog.prototype.enterState_ = function(state) {
    this.showGuidelinesForState_(state);
};
unisubs.translate.Dialog.prototype.suspendKeyEvents_ = function(suspended) {
    this.keyEventsSuspended_ = suspended;
    if (this.currentSubtitlePanel_ && this.currentSubtitlePanel_.suspendKeyEvents)
        this.currentSubtitlePanel_.suspendKeyEvents(suspended);
};
unisubs.translate.Dialog.prototype.setState_ = function(state) {
    this.state_ = state;

    this.suspendKeyEvents_(false);

    var s = unisubs.translate.Dialog.State_;

    this.setExtraClass_(' the-translate-panel');

    var nextSubPanel = this.makeCurrentStateSubtitlePanel_();
    var captionPanel = this.getCaptioningAreaInternal();
    captionPanel.removeChildren(true);
    captionPanel.addChild(nextSubPanel, true);

    var rightPanel = nextSubPanel.getRightPanel();
    this.setRightPanelInternal(rightPanel);

    this.getTimelinePanelInternal().removeChildren(true);

    if (this.currentSubtitlePanel_ && this.currentSubtitlePanel_.getNotesContent_){
       var currentNoteContent =  this.currentSubtitlePanel_.getNotesContent_();
       if (nextSubPanel  && nextSubPanel.setNotesContent_){
           nextSubPanel.setNotesContent_(currentNoteContent);
       }
    }

    this.disposeCurrentPanels_();
    this.currentSubtitlePanel_ = nextSubPanel;

    var et = unisubs.RightPanel.EventType;
    this.rightPanelListener_.
        listen(
            rightPanel, et.LEGENDKEY, this.handleLegendKeyPress_).
        listen(
            rightPanel, et.DONE, this.handleDoneKeyPress_).
        listen(
            rightPanel, et.SAVEANDEXIT, this.handleSaveAndExitKeyPress_).
        listen(
            rightPanel, et.GOTOSTEP, this.handleGoToStep_);
    var backButtonText = null;
    if (state == s.EDIT_METADATA ){
        backButtonText = "Back to Translation";
    }
    if (backButtonText){
        rightPanel.showBackLink(backButtonText);
        this.rightPanelListener_.listen(
            rightPanel, et.BACK, this.handleBackKeyPress_);

    }

    var videoPlayer = this.getVideoPlayerInternal();
    if (this.isInDocument()) {
        if (!videoPlayer.isPaused()){
            videoPlayer.pause();
        }
        if(videoPlayer.getPlayheadTime()){
            videoPlayer.setPlayheadTime(0);
            videoPlayer.showCaptionText('');
            videoPlayer.pause();
        }
    }

    var that = this;
    this.getRightPanelInternal().showDownloadLink(
        function() {
            return that.makeJsonSubs();
        });
};
unisubs.translate.Dialog.prototype.makeCurrentStateSubtitlePanel_ = function() {
    var s = unisubs.translate.Dialog.State_;

    if (this.state_ == s.TRANSLATE){
        this.translationPanel_ =  new unisubs.translate.TranslationPanel(
            this.serverModel_.getCaptionSet(),
            this.standardSubState_,
            this,
            this.reviewOrApprovalType_,
            this.serverModel_);
        return this.translationPanel_;
    }
    else if (this.state_ == s.EDIT_METADATA)
        return new unisubs.editmetadata.Panel(
            this.serverModel_.getCaptionSet(),
            this.getVideoPlayerInternal(),
            this.serverModel_,
            this.captionManager_,
            this.standardSubState_ ,
            false,
            this.reviewOrApprovalType_,
            this);
};

/**
 * Tries translate subtitles with BingTranslator
 */
unisubs.translate.Dialog.prototype.translateViaBing = function(){
    //I don't know how better call this. I think it should be incapsulated in translationList_,
    //but have chain of function calls can confuse.
    this.translationPanel_.getTranslationList().translateViaBing(
        this.standardSubState_.LANGUAGE, this.subtitleState_.LANGUAGE);
};
unisubs.translate.Dialog.prototype.getStandardLanguage = function(){
    return this.standardSubState_.LANGUAGE;
};
unisubs.translate.Dialog.prototype.getSubtitleLanguage = function(){
    return this.subtitleState_.LANGUAGE;
};
unisubs.translate.Dialog.prototype.getServerModel = function(){
    return this.serverModel_;
};
unisubs.translate.Dialog.prototype.makeJsonSubs =  function (){
    return this.serverModel_.getCaptionSet().makeJsonSubs();
};
unisubs.translate.Dialog.prototype.forkAndClose = function() {
    var dialog = new unisubs.translate.ForkDialog(
        goog.bind(this.forkImpl_, this));
    dialog.setVisible(true);
};
unisubs.translate.Dialog.prototype.forkImpl_ = function() {
    this.subtitleState_.fork();
    this.serverModel_.fork(this.standardSubState_);
    this.hideToFork();
    var dialog = this.opener_.openSubtitlingDialog(
        this.serverModel_,
        this.subtitleState_);
    this.opener_.onDialogOpened_(dialog);
};
unisubs.translate.Dialog.prototype.disposeCurrentPanels_ = function() {
    if (this.currentSubtitlePanel_) {
        this.currentSubtitlePanel_.dispose();
        this.currentSubtitlePanel_ = null;
    }
    this.rightPanelListener_.removeAll();
    if (this.timelineSubtitleSet_) {
        this.timelineSubtitleSet_.dispose();
        this.timelineSubtitleSet_ = null;
    }
};
unisubs.translate.Dialog.prototype.captionReached_ = function(event) {
    var c = event.caption;
    var caption;

    if (c) {

        var captionID = c.getCaptionID();

        var translatedCaptionSet = this.translationPanel_.getTranslationList().captionSet_;
        var translatedCaption = translatedCaptionSet.captionByID(captionID);

        if (translatedCaption.getText() !== '') {
            caption = translatedCaption.getText();
        } else {
            caption = c.getText();
        }

    } else {
        caption = '';
    }

    this.getVideoPlayerInternal().showCaptionText(caption);
};
<|endoftext|>


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

module.exports = function(config) {

  // Browsers to run on Sauce Labs
  var customLaunchers = {
    'SL_Chrome': {
      base: 'SauceLabs',
      browserName: 'chrome'
    },
    'SL_Firefox' : {
      base: 'SauceLabs',
      browserName: 'firefox'
    }
  };

  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine', 'sinon'],

    // list of files / patterns to load in the browser
    files: [
      'src/le.js',
      'test/leSpec.js'
    ],

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['dots', 'saucelabs'],

    // web server port
    port: 9876,

    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    sauceLabs: {
        username: process.env.SAUCELABS_USERNAME,
        accessKey: process.env.SAUCELABS_ACCESSKEY,
        testName: process.env.TRAVIS_JOB_NUMBER
    },
    captureTimeout: 120000,
    customLaunchers: customLaunchers,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: Object.keys(customLaunchers),
    singleRun: true
  });
};<commit_msg>remove ff test<commit_after>var fs = require('fs');

module.exports = function(config) {

  // Browsers to run on Sauce Labs
  var customLaunchers = {
    'SL_Chrome': {
      base: 'SauceLabs',
      browserName: 'chrome'
    }
  };

  config.set({

    // base path that will be used to resolve all patterns (eg. files, exclude)
    basePath: '',

    // frameworks to use
    // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
    frameworks: ['jasmine', 'sinon'],

    // list of files / patterns to load in the browser
    files: [
      'src/le.js',
      'test/leSpec.js'
    ],

    // test results reporter to use
    // possible values: 'dots', 'progress'
    // available reporters: https://npmjs.org/browse/keyword/karma-reporter
    reporters: ['dots', 'saucelabs'],

    // web server port
    port: 9876,

    colors: true,

    // level of logging
    // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
    logLevel: config.LOG_INFO,

    sauceLabs: {
        username: process.env.SAUCELABS_USERNAME,
        accessKey: process.env.SAUCELABS_ACCESSKEY,
        testName: process.env.TRAVIS_JOB_NUMBER
    },
    captureTimeout: 120000,
    customLaunchers: customLaunchers,

    // start these browsers
    // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
    browsers: Object.keys(customLaunchers),
    singleRun: true
  });
};<|endoftext|>