<commit_before>/*
	BigEval.js
	by Avi Aryan (http://aviaryan.in)
	https://github.com/aviaryan/bigEval.js
*/

var BigEval = function(){
	this.err = 0;
	this.errMsg = "";
	this.errBR = "IMPROPER_BRACKETS";
	this.errMS = "MISSING_OPERATOR_AT_";
	this.errMN = "MISSING_OPERAND_AT_";
	this.errIC = "INVALID_CHAR_AT_";
	this.errFN = "INVALID_FUNCTION_";
	this.errVD = "UNDEFINED_VARIABLE_";
	this.errFL = "FUNCTION_LIMIT_EXCEEDED_BY_";

	this.order = ['!' , '@' , '\\/*%' , '+-' , '&' , '^' , '|'];
	// https://en.wikipedia.org/wiki/Order_of_operations#Programming_languages

	// CONSTANTS
	var a = this.CONSTANT = {};
	a.PI = Math.PI;
	a.PI_2 = a.PI / 2; // Math.PI_2;
	a.LOG2E = Math.LOG2E;
	a.DEG = a.PI / 180;
	a.E = Math.E;
	a.INFINITY = "Infinity";
	a.NaN = "NaN";
};

BigEval.prototype.exec = function(s){
	this.err = 0;
	this.errMsg = "";

	// validate brackets
	this.validate(s);
	if (this.err)
		return this.errMsg;

	// replace ** by @
	s = s.replace(/\*\*/g, '@');

	// validate missing operator
	var misOperator = /[a-z0-9][ \t]+[a-z0-9\.]/ig, p;
	if (misOperator.exec(s)){
		this.err = 1;
		return this.errMsg = this.errMS + misOperator.lastIndex;
	}

	// validate missing operand
	var misOperands = /[\+\-\\\/\*\@\%\&\^\|][ \t]*([\\\/\*\@\!\%\&\^\|\)]|$)/g;
	if (misOperands.exec(s)){
		this.err = 1;
		return this.errMsg = this.errMN + misOperands.lastIndex;
	}

	s = this.plusMinus( s.replace(/[ \t]/g, '') );

	s = this.solve(s);
	if (s.charAt(0) == '+')
		s = s.slice(1);
	return s;
};

BigEval.prototype.solve = function(s){

	// validate first char
	var fc = s.charAt(0);
	if (!fc.match(/[a-z0-9\.\+\-\(]/i)){
		this.err = 1;
		return this.errMsg = this.errIC;
	}

	s = this.addPlusSign(s);
	var ob = s.indexOf('('), cb, fname, freturn;

	// if bracket present, work on them
	while (ob != -1){
		var obct = 1;
		for (var i = ob+1; i < s.length; i++){
			if (s[i] == '(')
				obct++;
			else if (s[i] == ')'){
				obct--;
				if (obct == 0){
					cb = i;
					break;
				}
			}
		}
		// Function found
		if (s.charAt(ob-1).match(/[a-z0-9_]/i)){
			fname = s.slice(0, ob).match(/[a-z0-9_]+$/i);
			freturn = this.solve( s.slice(ob+1, cb) );
			freturn = this.solveFunc( freturn , fname[0] ) + '';
			s = s.slice(0, ob-fname[0].length) + freturn + s.slice(cb+1);
		} else {
			s = s.slice(0, ob) + this.solve( s.slice(ob+1, cb) ) + s.slice(cb+1);
		}

		if (this.err)
			return this.errMsg;
		ob = s.indexOf('(');
	}

	// check for comma - then function throw it back
	if (s.indexOf(',') != -1)
		return this.addPlusSign(s);

	// solve expression (no brackets exist)
	var p, bp, ap, seg, c, cs, isAddOn=0, b, a;
	for (var i = 0; i < this.order.length; i++){

		cs = this.order[i];
		if (cs == '+-'){ // resolve +- made due to bracket solving
			s = this.plusMinus(s);
			isAddOn = 1;
		}

		p = this.leastIndexOf(s, cs, 1);

		while (p > 0){ // the first is sign, no need to take that
			bp = s.slice(0,p).match(/[\-\+]*(\de\-|\de\+|[a-z0-9_\.])+$/i); // kepp e-,e+ before other regex to have it matched
			// & ^ | are after + in priority so they dont need be above
			ap = s.slice(p+1).match(/[\-\+]*(\de\-|\de\+|[a-z0-9_\.])+/i);
			if (ap == null)
				ap = [""];

			if (bp == null){ // 12 & -20 - here -20 is sign.. bp of it is null . ignore it
				p = this.leastIndexOf(s, cs, p+1);
				continue;
			}
			if (!isAddOn) // slice the extra +- sign that is not for number
				if (bp[0].match(/[\-\+]/))
					bp[0] = bp[0].slice(1);

			if (isAddOn) // +- only ignore 1e-7
				if ( bp[0].charAt(bp[0].length - 1) == 'e' )
					if ( bp[0].charAt(bp[0].length - 2).match(/\d/) ){ // is number
						p = this.leastIndexOf(s, cs, p+1);
						continue;
					}

			// look for variables
			c = s.charAt(p);
			//alert(bp[0] + s.charAt(p) + ap[0]);
			b = this.parseVar( this.plusMinus(bp[0]) ); a = this.parseVar( this.plusMinus(ap[0]) );
			if (this.err)
				return this.errMsg;

			if (c == '!'){
				if ( bp[0].charAt(0) == '+' || bp[0].charAt(0) == '-' )
					bp[0] = bp[0].slice(1);
				b = this.parseVar(bp[0]);
				seg = this.fac( b ) + "";
				ap = [""]; // to avoid latter segment from being affected (unary operator)
			} else {
				if (c == '/' || c == '\\')
					seg = this.div( b , a );
				else if (c == '*')
					seg = this.mul( b , a );
				else if (c == '+')
					seg = this.add( b , a );
				else if (c == '-')
					seg = this.sub( b , a );
				else if (c == '@')
					seg = this.pow( b , a );
				else if (c == '%')
					seg = this.mod( b , a );
				else if (c == '&')
					seg = this.and( b , a );
				else if (c == '^')
					seg = this.xor( b , a );
				else if (c == '|')
					seg = this.or(  b , a );

				seg = this.addPlusSign(seg + "");
			}
			s = s.slice(0, p-bp[0].length) + seg + s.slice(p+ap[0].length+1);
			p = this.leastIndexOf(s, cs, 1);
			//alert(s);
		}
	}

	s = this.addPlusSign(s);
	return this.parseVar(s);
};

BigEval.prototype.validate = function(s){
	// checks expression for errors
	var stack = [], err = 0;

	for ( var i = 0; i < s.length; i++ ){
		if ( s[i] == '(' )
			stack.push(0);
		else if ( s[i] == ')' ){
			if (stack.length > 0)
				stack.pop();
			else {
				this.makeError(this.errBR);
				break;
			}
		}
	}

	if (this.err == 0)
		if (stack.length != 0)
			this.makeError(this.errBR);
};

BigEval.prototype.solveFunc = function(s, fname){
	var arr = s.split(','), f, isMath = 0, isBigEval = 0;
	if ( typeof(this[fname]) == "function" ){
		isBigEval = 1;
	}
	else if ( typeof(Math[fname]) == "function" ){
		f = Math[fname];
		isMath = 1;
	}
	else if ( typeof(window[fname]) == "function" )
		f = window[fname];
	else {
		this.makeError(this.errFN + fname);
		return 0;
	}

	for (var i = 0; i<arr.length; i++)
		arr[i] = (isMath) ? Number(this.solve(arr[i])) : this.solve(arr[i]);

	if (arr.length == 1)
		return isBigEval ? this[fname].call(this, arr[0]) : f(arr[0]);
	else if (arr.length == 2)
		return isBigEval ? this[fname].call(this, arr[0], arr[1]) : f(arr[0], arr[1]);
	else if (arr.length == 3)
		return isBigEval ? this[fname].call(this, arr[0], arr[1], arr[2]) : f(arr[0], arr[1], arr[2]);
	else if (arr.length == 4)
		return isBigEval ? this[fname].call(this, arr[0], arr[1], arr[2], arr[3]) : f(arr[0], arr[1], arr[2], arr[3]);
	else
		return this.makeError(this.errFL + fname);
};

BigEval.prototype.parseVar = function(s){
	var z;
	//console.log(s);
	if (z = s.match(/^[\+\-]?[a-z][a-z0-9_]*$/i)){
		var zs="";
		if (z[0].charAt(0) == '-' || z[0].charAt(0) == '+'){
			zs = z[0].slice(0,1);
			z[0] = z[0].slice(1);
		}
		if (typeof this.CONSTANT[z[0].toUpperCase()] !== 'undefined')
			return zs + this.CONSTANT[ z[0].toUpperCase() ];
		else if (typeof this.CONSTANT[z[0]] !== 'undefined')
			return zs + this.CONSTANT[ z[0] ];
		else
			return this.makeError(this.errVD + z[0]);
	}
	else
		return s;
};

BigEval.prototype.leastIndexOf = function(s, cs, sp){
	var l = -1, p;
	for (var i=0; i<cs.length; i++){
		p = s.indexOf(cs[i], sp);
		if (p==-1)
			continue;
		if (l==-1)
			l=p;
		else if (p<l)
			l=p;
	}
	return l;
};

BigEval.prototype.plusMinus = function(s){
	return s.replace(/\+\+/g, '+').replace(/\+\-/g, '-').replace(/\-\+/g, '-').replace(/\-\-/g, '+');
};

BigEval.prototype.addPlusSign = function(s){
	if (s.charAt(0) != '-' && s.charAt(0) != '+')
		s = '+' + s;
	return s;
};

BigEval.prototype.makeError = function(msg){
	this.err = 1;
	return this.errMsg = msg;
};

/**
 * Extension functions
 */

BigEval.prototype.add = function(a, b){
	return Number(a)+Number(b);
};

BigEval.prototype.sub = function(a, b){
	return Number(a)-Number(b);
};

BigEval.prototype.mul = function(a, b){
	return Number(a)*Number(b);
};

BigEval.prototype.div = function(a, b){
	return Number(a)/Number(b);
};

BigEval.prototype.pow = function(a, b){
	return Math.pow(Number(a), Number(b));
};

BigEval.prototype.fac = function(n){
	var s = "1";
	n = Number(n);
	for (var i = 2; i <= n; i++)
		s = this.mul(s, i);
	return s;
};

BigEval.prototype.mod = function(a, b){
	return Number(a)%Number(b);
};

BigEval.prototype.and = function(a, b){
	return Number(a) & Number(b);
};

BigEval.prototype.xor = function(a, b){
	return Number(a) ^ Number(b);
};

BigEval.prototype.or = function(a, b){
	return Number(a) | Number(b);
};

/**
 * Node compatibility
 */
if (typeof exports !== 'undefined'){
	module.exports = BigEval;
}<commit_msg>use a safer, reliable way to export<commit_after>/*
	BigEval.js
	by Avi Aryan (http://aviaryan.in)
	https://github.com/aviaryan/bigEval.js
*/

var BigEval = function(){
	this.err = 0;
	this.errMsg = "";
	this.errBR = "IMPROPER_BRACKETS";
	this.errMS = "MISSING_OPERATOR_AT_";
	this.errMN = "MISSING_OPERAND_AT_";
	this.errIC = "INVALID_CHAR_AT_";
	this.errFN = "INVALID_FUNCTION_";
	this.errVD = "UNDEFINED_VARIABLE_";
	this.errFL = "FUNCTION_LIMIT_EXCEEDED_BY_";

	this.order = ['!' , '@' , '\\/*%' , '+-' , '&' , '^' , '|'];
	// https://en.wikipedia.org/wiki/Order_of_operations#Programming_languages

	// CONSTANTS
	var a = this.CONSTANT = {};
	a.PI = Math.PI;
	a.PI_2 = a.PI / 2; // Math.PI_2;
	a.LOG2E = Math.LOG2E;
	a.DEG = a.PI / 180;
	a.E = Math.E;
	a.INFINITY = "Infinity";
	a.NaN = "NaN";
};

BigEval.prototype.exec = function(s){
	this.err = 0;
	this.errMsg = "";

	// validate brackets
	this.validate(s);
	if (this.err)
		return this.errMsg;

	// replace ** by @
	s = s.replace(/\*\*/g, '@');

	// validate missing operator
	var misOperator = /[a-z0-9][ \t]+[a-z0-9\.]/ig, p;
	if (misOperator.exec(s)){
		this.err = 1;
		return this.errMsg = this.errMS + misOperator.lastIndex;
	}

	// validate missing operand
	var misOperands = /[\+\-\\\/\*\@\%\&\^\|][ \t]*([\\\/\*\@\!\%\&\^\|\)]|$)/g;
	if (misOperands.exec(s)){
		this.err = 1;
		return this.errMsg = this.errMN + misOperands.lastIndex;
	}

	s = this.plusMinus( s.replace(/[ \t]/g, '') );

	s = this.solve(s);
	if (s.charAt(0) == '+')
		s = s.slice(1);
	return s;
};

BigEval.prototype.solve = function(s){

	// validate first char
	var fc = s.charAt(0);
	if (!fc.match(/[a-z0-9\.\+\-\(]/i)){
		this.err = 1;
		return this.errMsg = this.errIC;
	}

	s = this.addPlusSign(s);
	var ob = s.indexOf('('), cb, fname, freturn;

	// if bracket present, work on them
	while (ob != -1){
		var obct = 1;
		for (var i = ob+1; i < s.length; i++){
			if (s[i] == '(')
				obct++;
			else if (s[i] == ')'){
				obct--;
				if (obct == 0){
					cb = i;
					break;
				}
			}
		}
		// Function found
		if (s.charAt(ob-1).match(/[a-z0-9_]/i)){
			fname = s.slice(0, ob).match(/[a-z0-9_]+$/i);
			freturn = this.solve( s.slice(ob+1, cb) );
			freturn = this.solveFunc( freturn , fname[0] ) + '';
			s = s.slice(0, ob-fname[0].length) + freturn + s.slice(cb+1);
		} else {
			s = s.slice(0, ob) + this.solve( s.slice(ob+1, cb) ) + s.slice(cb+1);
		}

		if (this.err)
			return this.errMsg;
		ob = s.indexOf('(');
	}

	// check for comma - then function throw it back
	if (s.indexOf(',') != -1)
		return this.addPlusSign(s);

	// solve expression (no brackets exist)
	var p, bp, ap, seg, c, cs, isAddOn=0, b, a;
	for (var i = 0; i < this.order.length; i++){

		cs = this.order[i];
		if (cs == '+-'){ // resolve +- made due to bracket solving
			s = this.plusMinus(s);
			isAddOn = 1;
		}

		p = this.leastIndexOf(s, cs, 1);

		while (p > 0){ // the first is sign, no need to take that
			bp = s.slice(0,p).match(/[\-\+]*(\de\-|\de\+|[a-z0-9_\.])+$/i); // kepp e-,e+ before other regex to have it matched
			// & ^ | are after + in priority so they dont need be above
			ap = s.slice(p+1).match(/[\-\+]*(\de\-|\de\+|[a-z0-9_\.])+/i);
			if (ap == null)
				ap = [""];

			if (bp == null){ // 12 & -20 - here -20 is sign.. bp of it is null . ignore it
				p = this.leastIndexOf(s, cs, p+1);
				continue;
			}
			if (!isAddOn) // slice the extra +- sign that is not for number
				if (bp[0].match(/[\-\+]/))
					bp[0] = bp[0].slice(1);

			if (isAddOn) // +- only ignore 1e-7
				if ( bp[0].charAt(bp[0].length - 1) == 'e' )
					if ( bp[0].charAt(bp[0].length - 2).match(/\d/) ){ // is number
						p = this.leastIndexOf(s, cs, p+1);
						continue;
					}

			// look for variables
			c = s.charAt(p);
			//alert(bp[0] + s.charAt(p) + ap[0]);
			b = this.parseVar( this.plusMinus(bp[0]) ); a = this.parseVar( this.plusMinus(ap[0]) );
			if (this.err)
				return this.errMsg;

			if (c == '!'){
				if ( bp[0].charAt(0) == '+' || bp[0].charAt(0) == '-' )
					bp[0] = bp[0].slice(1);
				b = this.parseVar(bp[0]);
				seg = this.fac( b ) + "";
				ap = [""]; // to avoid latter segment from being affected (unary operator)
			} else {
				if (c == '/' || c == '\\')
					seg = this.div( b , a );
				else if (c == '*')
					seg = this.mul( b , a );
				else if (c == '+')
					seg = this.add( b , a );
				else if (c == '-')
					seg = this.sub( b , a );
				else if (c == '@')
					seg = this.pow( b , a );
				else if (c == '%')
					seg = this.mod( b , a );
				else if (c == '&')
					seg = this.and( b , a );
				else if (c == '^')
					seg = this.xor( b , a );
				else if (c == '|')
					seg = this.or(  b , a );

				seg = this.addPlusSign(seg + "");
			}
			s = s.slice(0, p-bp[0].length) + seg + s.slice(p+ap[0].length+1);
			p = this.leastIndexOf(s, cs, 1);
			//alert(s);
		}
	}

	s = this.addPlusSign(s);
	return this.parseVar(s);
};

BigEval.prototype.validate = function(s){
	// checks expression for errors
	var stack = [], err = 0;

	for ( var i = 0; i < s.length; i++ ){
		if ( s[i] == '(' )
			stack.push(0);
		else if ( s[i] == ')' ){
			if (stack.length > 0)
				stack.pop();
			else {
				this.makeError(this.errBR);
				break;
			}
		}
	}

	if (this.err == 0)
		if (stack.length != 0)
			this.makeError(this.errBR);
};

BigEval.prototype.solveFunc = function(s, fname){
	var arr = s.split(','), f, isMath = 0, isBigEval = 0;
	if ( typeof(this[fname]) == "function" ){
		isBigEval = 1;
	}
	else if ( typeof(Math[fname]) == "function" ){
		f = Math[fname];
		isMath = 1;
	}
	else if ( typeof(window[fname]) == "function" )
		f = window[fname];
	else {
		this.makeError(this.errFN + fname);
		return 0;
	}

	for (var i = 0; i<arr.length; i++)
		arr[i] = (isMath) ? Number(this.solve(arr[i])) : this.solve(arr[i]);

	if (arr.length == 1)
		return isBigEval ? this[fname].call(this, arr[0]) : f(arr[0]);
	else if (arr.length == 2)
		return isBigEval ? this[fname].call(this, arr[0], arr[1]) : f(arr[0], arr[1]);
	else if (arr.length == 3)
		return isBigEval ? this[fname].call(this, arr[0], arr[1], arr[2]) : f(arr[0], arr[1], arr[2]);
	else if (arr.length == 4)
		return isBigEval ? this[fname].call(this, arr[0], arr[1], arr[2], arr[3]) : f(arr[0], arr[1], arr[2], arr[3]);
	else
		return this.makeError(this.errFL + fname);
};

BigEval.prototype.parseVar = function(s){
	var z;
	//console.log(s);
	if (z = s.match(/^[\+\-]?[a-z][a-z0-9_]*$/i)){
		var zs="";
		if (z[0].charAt(0) == '-' || z[0].charAt(0) == '+'){
			zs = z[0].slice(0,1);
			z[0] = z[0].slice(1);
		}
		if (typeof this.CONSTANT[z[0].toUpperCase()] !== 'undefined')
			return zs + this.CONSTANT[ z[0].toUpperCase() ];
		else if (typeof this.CONSTANT[z[0]] !== 'undefined')
			return zs + this.CONSTANT[ z[0] ];
		else
			return this.makeError(this.errVD + z[0]);
	}
	else
		return s;
};

BigEval.prototype.leastIndexOf = function(s, cs, sp){
	var l = -1, p;
	for (var i=0; i<cs.length; i++){
		p = s.indexOf(cs[i], sp);
		if (p==-1)
			continue;
		if (l==-1)
			l=p;
		else if (p<l)
			l=p;
	}
	return l;
};

BigEval.prototype.plusMinus = function(s){
	return s.replace(/\+\+/g, '+').replace(/\+\-/g, '-').replace(/\-\+/g, '-').replace(/\-\-/g, '+');
};

BigEval.prototype.addPlusSign = function(s){
	if (s.charAt(0) != '-' && s.charAt(0) != '+')
		s = '+' + s;
	return s;
};

BigEval.prototype.makeError = function(msg){
	this.err = 1;
	return this.errMsg = msg;
};

/**
 * Extension functions
 */

BigEval.prototype.add = function(a, b){
	return Number(a)+Number(b);
};

BigEval.prototype.sub = function(a, b){
	return Number(a)-Number(b);
};

BigEval.prototype.mul = function(a, b){
	return Number(a)*Number(b);
};

BigEval.prototype.div = function(a, b){
	return Number(a)/Number(b);
};

BigEval.prototype.pow = function(a, b){
	return Math.pow(Number(a), Number(b));
};

BigEval.prototype.fac = function(n){
	var s = "1";
	n = Number(n);
	for (var i = 2; i <= n; i++)
		s = this.mul(s, i);
	return s;
};

BigEval.prototype.mod = function(a, b){
	return Number(a)%Number(b);
};

BigEval.prototype.and = function(a, b){
	return Number(a) & Number(b);
};

BigEval.prototype.xor = function(a, b){
	return Number(a) ^ Number(b);
};

BigEval.prototype.or = function(a, b){
	return Number(a) | Number(b);
};

/**
 * Node compatibility
 */
if (typeof module !== 'undefined' && module.exports && typeof exports !== 'undefined'){
	module.exports = BigEval;
}<|endoftext|>


<commit_before>import { merge } from './updates.js';
import * as symbols from './symbols.js';
import * as template from './template.js';
import SeamlessButton from './SeamlessButton.js';

const wrap = Symbol('wrap');

/**
 * Adds buttons for managing playback of a slideshow, audio, etc.
 *
 * [Play controls let the user go back, pause/resume, or forward](/demos/slideshowWithPlayControls.html)
 *
 * @module PlayControlsMixin
 * @elementrole {SeamlessButton} controlButton
 */
export default function PlayControlsMixin(Base) {

  // The class prototype added by the mixin.
  class PlayControls extends Base {

    constructor() {
      // @ts-ignore
      super();
      this[symbols.renderedRoles] = {};
    }

    [symbols.beforeUpdate]() {
      if (super[symbols.beforeUpdate]) { super[symbols.beforeUpdate](); }
      if (this[symbols.renderedRoles].controlButtonRole !== this.state.controlButtonRole) {
        const controlButtons = this.shadowRoot.querySelectorAll('.controlButton');
        template.transmute(controlButtons, this.state.controlButtonRole);
        this.$.previousButton.addEventListener('click', event => {
          this.selectPrevious();
          event.stopPropagation();
        });
        this.$.playButton.addEventListener('click', event => {
          if (!this.playing) {
            this.play();
          } else {
            this.pause();
          }
          event.stopPropagation();
        });
        this.$.nextButton.addEventListener('click', event => {
          this.selectNext();
          event.stopPropagation();
        });
        assumeButtonFocus(this, this.$.previousButton);
        assumeButtonFocus(this, this.$.playButton);
        assumeButtonFocus(this, this.$.nextButton);
        this[symbols.renderedRoles].controlButtonRole = this.state.controlButtonRole;
      }
    }

    /**
     * The class, tag, or template used for the play control buttons.
     *
     * @type {function|string|HTMLTemplateElement}
     * @default SeamlessButton
     */
    get controlButtonRole() {
      return this.state.controlButtonRole;
    }
    set controlButtonRole(controlButtonRole) {
      this.setState({ controlButtonRole });
    }

    get defaultState() {
      return Object.assign({}, super.defaultState, {
        controlButtonRole: SeamlessButton
      });
    }

    // Pressing Space is the same as clicking the button.
    [symbols.keydown](event) {
      let handled;

      switch (event.key) {
        case ' ':
          this.click();
          handled = true;
          break;
      }

      // Prefer mixin result if it's defined, otherwise use base result.
      return handled || (super[symbols.keydown] && super[symbols.keydown](event));
    }

    get updates() {
      const playing = this.playing;

      const rightToLeft = this[symbols.rightToLeft];
      const transform = rightToLeft ?
        'rotate(180deg)' :
        '';

      return merge(super.updates, {
        $: {
          nextIcon: {
            style: {
              transform
            }
          },
          pausedIcon: {
            style: {
              display: playing ? 'none' : ''
            }
          },
          playingIcon: {
            style: {
              display: playing ? '' : 'none'
            }
          },
          previousIcon: {
            style: {
              transform
            }
          },
        }
      })
    }

    /**
     * Destructively wrap a node with elements for play controls.
     *
     * @param {Node} original - the element that should be wrapped by play controls
     */
    [wrap](original) {
      const playControlsTemplate = template.html`

            <slot name="previousButton">
              <svg id="previousIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="skip-previous">
                  <path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/>
                </g>
              </svg>
            </slot>

            <slot name="playButton">
              <svg id="playingIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="pause-circle-outline">
                  <path d="M9 16h2V8H9v8zm3-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm1-4h2V8h-2v8z"></path>
                </g>
              </svg>
              <svg id="pausedIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="play-circle-outline">
                  <path d="M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path>
                </g>
              </svg>
            </slot>

            <slot name="nextButton">
              <svg id="nextIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="skip-next">
                  <path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/>
                </g>
              </svg>
            </slot>

      `;
      template.wrap(original, playControlsTemplate.content, '#playControlsContainer');
    }
  }

  return PlayControls;
}

PlayControlsMixin.wrap = wrap;

/*
 * By default, a button will always take focus on mousedown. For this component,
 * we want to override that behavior, such that a mousedown on a button keeps
 * the focus on the outer component.
 */
function assumeButtonFocus(element, button) {
  button.addEventListener('mousedown', event => {
    // Given the main element the focus if it doesn't already have it.
    element.focus();
    // Prevent the default focus-on-mousedown behavior.
    event.preventDefault();
  });
}
<commit_msg>Take advantage of SeamlessButton.focusOnAncestor.<commit_after>import { merge } from './updates.js';
import * as symbols from './symbols.js';
import * as template from './template.js';
import SeamlessButton from './SeamlessButton.js';

const wrap = Symbol('wrap');

/**
 * Adds buttons for managing playback of a slideshow, audio, etc.
 *
 * [Play controls let the user go back, pause/resume, or forward](/demos/slideshowWithPlayControls.html)
 *
 * @module PlayControlsMixin
 * @elementrole {SeamlessButton} controlButton
 */
export default function PlayControlsMixin(Base) {

  // The class prototype added by the mixin.
  class PlayControls extends Base {

    constructor() {
      // @ts-ignore
      super();
      this[symbols.renderedRoles] = {};
    }

    [symbols.beforeUpdate]() {
      if (super[symbols.beforeUpdate]) { super[symbols.beforeUpdate](); }
      if (this[symbols.renderedRoles].controlButtonRole !== this.state.controlButtonRole) {
        const controlButtons = this.shadowRoot.querySelectorAll('.controlButton');
        template.transmute(controlButtons, this.state.controlButtonRole);
        this.$.previousButton.addEventListener('click', event => {
          this.selectPrevious();
          event.stopPropagation();
        });
        this.$.playButton.addEventListener('click', event => {
          if (!this.playing) {
            this.play();
          } else {
            this.pause();
          }
          event.stopPropagation();
        });
        this.$.nextButton.addEventListener('click', event => {
          this.selectNext();
          event.stopPropagation();
        });
        this[symbols.renderedRoles].controlButtonRole = this.state.controlButtonRole;
      }
    }

    /**
     * The class, tag, or template used for the play control buttons.
     *
     * @type {function|string|HTMLTemplateElement}
     * @default SeamlessButton
     */
    get controlButtonRole() {
      return this.state.controlButtonRole;
    }
    set controlButtonRole(controlButtonRole) {
      this.setState({ controlButtonRole });
    }

    get defaultState() {
      return Object.assign({}, super.defaultState, {
        controlButtonRole: SeamlessButton
      });
    }

    // Pressing Space is the same as clicking the button.
    [symbols.keydown](event) {
      let handled;

      switch (event.key) {
        case ' ':
          this.click();
          handled = true;
          break;
      }

      // Prefer mixin result if it's defined, otherwise use base result.
      return handled || (super[symbols.keydown] && super[symbols.keydown](event));
    }

    get updates() {
      const playing = this.playing;

      const rightToLeft = this[symbols.rightToLeft];
      const transform = rightToLeft ?
        'rotate(180deg)' :
        '';

      return merge(super.updates, {
        $: Object.assign(
          {
            nextIcon: {
              style: {
                transform
              }
            },
            pausedIcon: {
              style: {
                display: playing ? 'none' : ''
              }
            },
            playingIcon: {
              style: {
                display: playing ? '' : 'none'
              }
            },
            previousIcon: {
              style: {
                transform
              }
            }
          },
          'focusOnAncestor' in this.$.playButton && {
            nextButton: {
              focusOnAncestor: true
            },
            playButton: {
              focusOnAncestor: true
            },
            previousButton: {
              focusOnAncestor: true
            }
          }
        )
      });
    }

    /**
     * Destructively wrap a node with elements for play controls.
     *
     * @param {Node} original - the element that should be wrapped by play controls
     */
    [wrap](original) {
      const playControlsTemplate = template.html`

            <slot name="previousButton">
              <svg id="previousIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="skip-previous">
                  <path d="M6 6h2v12H6zm3.5 6l8.5 6V6z"/>
                </g>
              </svg>
            </slot>

            <slot name="playButton">
              <svg id="playingIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="pause-circle-outline">
                  <path d="M9 16h2V8H9v8zm3-14C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm1-4h2V8h-2v8z"></path>
                </g>
              </svg>
              <svg id="pausedIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="play-circle-outline">
                  <path d="M10 16.5l6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path>
                </g>
              </svg>
            </slot>

            <slot name="nextButton">
              <svg id="nextIcon" class="icon" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet">
                <g id="skip-next">
                  <path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/>
                </g>
              </svg>
            </slot>

      `;
      template.wrap(original, playControlsTemplate.content, '#playControlsContainer');
    }
  }

  return PlayControls;
}

PlayControlsMixin.wrap = wrap;
<|endoftext|>


<commit_before>const mirrorHosts = [
    "api-01.openchargemap.io",
    "api-mirror-01.openchargemap.io"
];

addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event))
});

async function fetchAndApply(event) {

    let enableCache = false;
    // redirect request to backend api
    let url = new URL(event.request.url);

    console.log(url.href);
    console.log(event.request.method);

    if (
      event.request.method != "POST" &&
      !url.href.includes("/geocode") &&
      !url.href.includes("/.well-known")
      ) {

        console.log("Using mirror API for request.");

        // if not doing a POST request reads can be served randomly from mirrors or from cache
        //url.hostname = mirrorHosts[getRandomInt(mirrorHosts.length)];
        url.hostname = mirrorHosts[1];

        if (url.hostname != mirrorHosts[0]) {
            url.protocol = "http";
            url.port = "80";
        }

        // get cached response if available, otherwise fetch new response and cache it
        let cache = caches.default;
        let response = null;

        if (enableCache) response = await cache.match(event.request);

        if (!response) {
            let modifiedRequest = new Request(url, {
                method: event.request.method,
                headers: event.request.headers
            });

            try {
                response = await fetch(modifiedRequest);

                // Make the headers mutable by re-constructing the Response.

                response = new Response(response.body, response);
                response.headers.set('x-forwarded-host', url.hostname);
                if (response.headers.get('Access-Control-Allow-Origin') === null) {
                    response.headers.append("Access-Control-Allow-Origin", "*");
                }

                if (enableCache) event.waitUntil(cache.put(event.request, response.clone()));

                return response;

            } catch {
                // failed to fetch from mirror, try primary
                console.log("Forwarded request failed. Retrying to primary API");

                response = await fetch(event.request);

                if (enableCache)  event.waitUntil(cache.put(event.request, response.clone()));

                return response;
            }

        } else {
            console.log("Using cached response");
            return response;
        }

    } else {

        // POSTs (and certain GETs) only go to primary API

        console.log("Using primary API for request. " + url.origin);

        response = await fetch(event.request);
        return response
    }
}

function getRandomInt(max) {
    return Math.floor(Math.random() * max);
}<commit_msg>Update api router<commit_after>

addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event))
});

async function fetchAndApply(event) {

    let mirrorHosts = [
        "api-01.openchargemap.io"
    ];

    // check if we think this user is currently an editor (has posted)
    let clientIP = event.request.headers.get('CF-Connecting-IP');
    let ip_key = clientIP != null ? "API_EDITOR_" + clientIP.replace(".", "_") : null;
    let isEditor = false;
    if (ip_key != null) {
        if (await OCM_CONFIG_KV.get(ip_key) != null) {
            isEditor = true;
        }
    }

    // get list of mirrors from KV store and append to our working list
    let kv_mirrors = await OCM_CONFIG_KV.get("API_MIRRORS", "json");
    mirrorHosts.push(...kv_mirrors);

    let kv_skipped_mirrors = await OCM_CONFIG_KV.get("API_SKIPPED_MIRRORS", "json");

    // remove any mirrors temporarily not in use.
    if (kv_skipped_mirrors != null) {
        console.log("Skipped Mirrors:");
        console.log(kv_skipped_mirrors);
        mirrorHosts = mirrorHosts.filter(k => !kv_skipped_mirrors.includes(k))
    }

    console.log("Viable mirrors:");
    console.log(mirrorHosts);

    ////////////////

    let enableCache = false;
    // redirect request to backend api
    let url = new URL(event.request.url);

    console.log(url.href);
    console.log(event.request.method);

    if (
        event.request.method != "POST" &&
        !url.href.includes("/geocode") &&
        !url.href.includes("/.well-known")
    ) {

        let mirrorIndex = getRandomInt(0, mirrorHosts.length);

        if (mirrorIndex > 0) {
            console.log("Using mirror API " + mirrorIndex + " for request.");
        }
        // if not doing a POST request reads can be served randomly from mirrors or from cache
        url.hostname = mirrorHosts[mirrorIndex];

        if (url.hostname != mirrorHosts[0]) {
            url.protocol = "http";
            url.port = "80";
        }

        // get cached response if available, otherwise fetch new response and cache it
        let cache = caches.default;
        let response = null;

        if (enableCache) response = await cache.match(event.request);

        if (!response) {
            let modifiedRequest = new Request(url, {
                method: event.request.method,
                headers: event.request.headers
            });

            try {
                response = await fetch(modifiedRequest);

                // Make the headers mutable by re-constructing the Response.

                response = new Response(response.body, response);
                if (!response.ok) {
                    console.log("Forwarded request failed. " + response.status);
                    throw "Mirror response status failed:" + response.status;
                }

                response.headers.set('x-forwarded-host', url.hostname);
                if (response.headers.get('Access-Control-Allow-Origin') === null) {
                    response.headers.append("Access-Control-Allow-Origin", "*");
                }

                if (enableCache) event.waitUntil(cache.put(event.request, response.clone()));

                return response;

            } catch {
                // failed to fetch from mirror, try primary
                console.log("Forwarded request failed. Retrying to primary API");

                // add failed mirror to skipped mirrors
                if (mirrorIndex != 0) {
                    await OCM_CONFIG_KV.put("API_SKIPPED_MIRRORS", JSON.stringify([mirrorHosts[mirrorIndex]]), { expirationTtl: 60 * 60 });
                }

                response = await fetch(event.request);

                if (enableCache) event.waitUntil(cache.put(event.request, response.clone()));

                return response;
            }

        } else {
            console.log("Using cached response");
            return response;
        }

    } else {

        // POSTs (and certain GETs) only go to primary API

        console.log("Using primary API for request. " + url.origin);

        if (event.request.method == "POST") {
            if (ip_key != null) {
                await OCM_CONFIG_KV.put(ip_key, "true", { expirationTtl: 60 * 60 * 12 });
                console.log("Logged user as an editor:" + ip_key);
            }
        }

        response = await fetch(event.request);
        return response
    }
}

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min)) + min; //The maximum is exclusive and the minimum is inclusive
}<|endoftext|>


<commit_before>module.exports = (options) => ({
  params: ['username', 'password'],
  grantTypeFactory: function passwordGrantTypeFactory(providerInstance) {
    return async function passwordGrantType(ctx, next) {
      const { username, password } = ctx.oidc.params;
      const account = await options.authenticateUser(username, password);

      if (account) {
        const { AccessToken, IdToken } = providerInstance;
        const at = new AccessToken({
          accountId: 'foo',
          clientId: ctx.oidc.client.clientId,
          grantId: ctx.oidc.uuid,
        });

        const accessToken = await at.save();
        const expiresIn = AccessToken.expiresIn;

        const token = new IdToken(
          Object.assign({}, await Promise.resolve(account.claims())),
          ctx.oidc.client.sectorIdentifier
        );
        token.set('at_hash', accessToken);

        const idToken = await token.sign(ctx.oidc.client);

        ctx.body = {
          access_token: accessToken,
          expires_in: expiresIn,
          token_type: 'Bearer',
          id_token: idToken,
        };
      } else {
        ctx.body = {
          error: 'invalid_grant',
          error_description: 'invalid credentials provided',
        };
      }

      await next();
    };
  }
});
<commit_msg>set account id on tokens correctly<commit_after>module.exports = (options) => ({
  params: ['username', 'password'],
  grantTypeFactory: function passwordGrantTypeFactory(providerInstance) {
    return async function passwordGrantType(ctx, next) {
      const { username, password } = ctx.oidc.params;
      const account = await options.authenticateUser(username, password);

      if (account) {
        const { AccessToken, IdToken } = providerInstance;
        const at = new AccessToken({
          accountId: account.accountId,
          clientId: ctx.oidc.client.clientId,
          grantId: ctx.oidc.uuid,
        });

        const accessToken = await at.save();
        const expiresIn = AccessToken.expiresIn;

        const token = new IdToken(
          Object.assign({}, await Promise.resolve(account.claims())),
          ctx.oidc.client.sectorIdentifier
        );
        token.set('at_hash', accessToken);
        token.set('sub', account.accountId);

        const idToken = await token.sign(ctx.oidc.client);

        ctx.body = {
          access_token: accessToken,
          expires_in: expiresIn,
          token_type: 'Bearer',
          id_token: idToken,
        };
      } else {
        ctx.body = {
          error: 'invalid_grant',
          error_description: 'invalid credentials provided',
        };
      }

      await next();
    };
  }
});
<|endoftext|>


<commit_before>
/**
 * common header for map/table views
 */

var OptionsMenu = cdb.admin.DropdownMenu.extend({
  events: {
    'click .export':        '_export',
    'click .duplicate':     '_duplicate',
    'click .append':        '_append',
    'click .delete_table':  '_delete',
    'click .merge_tables':  '_mergeTables'
  },

  show: function() {
    this.render();
    this.constructor.__super__.show.apply(this);
  },

  _export: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // export anything...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var export_dialog = new cdb.admin.ExportTableDialog({
      model: this.options.table,
      config: config,
      user_data: user_data
    });

    $("body").append(export_dialog.render().el);
    export_dialog.open();
  },

  _duplicate: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // duplicate a table or a query...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var duplicate_dialog = new cdb.admin.DuplicateTable({
      model: this.options.table
    });

    $("body").append(duplicate_dialog.render().el);
    duplicate_dialog.open();
  },

  _append: function(e){
    e.preventDefault();
  },

  _delete: function(e){
    e.preventDefault();

    this.delete_dialog = new cdb.admin.DeleteDialog({
      model: this.options.table,
      config: config,
      user_data: user_data,
    });
    $("body").append(this.delete_dialog.render().el);
    this.delete_dialog.open();

    this.delete_dialog.wait()
      .done(this.deleteTable.bind(this));
  },

  _mergeTables: function(e) { // TODO: set prior state
    e.preventDefault();

    var mergeDialog = new cdb.admin.MergeTablesDialog({
      table: this.options.table
    });

    mergeDialog.appendToBody().open({ center:true });

    //window.mergeTableDialog = mergeDialog;

  },

  deleteTable: function() {
    var self = this;
    this.options.table.destroy({wait: true})
      .done(function(){
        window.location = "/dashboard";
      }
    );
  },

});

cdb.admin.Header = cdb.core.View.extend({
  _TEXTS: {
    _DEFAULT_DESCRIPTION:         'add a description for this table...',
    _DEFAULT_QUERY_DESCRIPTION:   'There is no description...',
    _DEFAULT_ADD_TAG_BUTTON:      'add tags',
    _DEFAULT_EDIT_TAG_BUTTON:     'edit tags'
  },
  _PRIVACY_VALUES: [
    'public',
    'private'
  ],
  _MAX_DESCRIPTION_LENGTH: 200,
  events: {
    'click .clearview':           'clearView',
    'click .status':              '_addPrivacySelector',
    'click .change_title':        '_changeTitle',
    'click .table_description p': '_changeDescription',
    'click .georeference':        'georeference',
    'click span.tags > *':        '_changeTags'
  },

  initialize: function(options) {
    var self = this;

    this.$body = this.options.body || $('body');
    this.table = this.model;
    this.globalError = this.options.globalError;
    this.createBindings();

    this.add_related_model(this.table);
    this.$('.clearview').hide();

    // Print all the information and don't
    // wait for table changes, it renders faster
    this.setTableInfo();
    this.createUserMenu();
    this.createOptionsMenu();

    // if(this.table.isInSQLView()) {
    //   this.onSQLView();
    // }
  },

  createBindings: function() {
    _.bindAll(this, '_changeTitle');

    this.table.bind('change:dataSource',  this.onSQLView,         this);
    this.table.bind('change:name',        this._setName,          this);
    this.table.bind('change:description', this._setDescription,   this);
    this.table.bind('change:tags',        this._setTags,          this);
    this.table.bind('change:privacy',     this._setPrivacy,       this);
  },

  createUserMenu: function() {
    // User menu
    var user_menu = this.user_menu = new cdb.admin.DropdownMenu({
      target: $('a.account'),
      username: this.options.user.get('username'),
      host: this.options.config.account_host,
      template_base: 'common/views/settings_item'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", user_menu.hide, user_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", user_menu.hide, user_menu);
    });

    cdb.god.bind("closeDialogs", user_menu.hide, user_menu);
    this.$body.append(user_menu.render().el);
    this.addView(user_menu);
  },

  createOptionsMenu: function() {
    // Options menu
    var options_menu = this.options_menu = new OptionsMenu({
      target: $('a.options'),
      model: { username: this.options.user.get('username') },
      username: this.options.user.get('username'),
      table: this.table,
      template_base: 'table/views/header_table_options'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", options_menu.hide, options_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", options_menu.hide, options_menu);
    });
    cdb.god.bind("closeDialogs", options_menu.hide, options_menu);
    this.addView(options_menu);
  },

  setOptionsMenu: function(dl) {
    this.options_menu.options.dataLayer = dl;
    $('body').append(this.options_menu.render().el);
  },

  setDataLayer: function(dl) {
    this.dataLayer = dl;
    // Set the new datalayer to the options menu
    // due to the fact that we need for duplicating
    // dialog, export dialog, and so on...
    this.setOptionsMenu(dl);
  },

  georeference: function(e) {
    e.preventDefault();
    var dlg = new cdb.admin.GeoreferenceDialog({
      model: this.table,
      geocoder: this.options.geocoder
    });
    dlg.appendToBody().open();
  },

  setTableInfo: function() {
    this._setName();
    this._setPrivacy();
    this._setDescription();
    this._setTags();
  },

  /**
   *  Set Name of the table
   */
  _setName: function() {
    this.$('h2.special a').html(this.table.get('name'));
  },

  /**
   *  Set privacy status of the table
   */
  _setPrivacy: function() {
    var $status = this.$('.status');

    for(var n in this._PRIVACY_VALUES) {
      var privacyName = this._PRIVACY_VALUES[n]
      $status.removeClass(privacyName);
    }

    $status
      .addClass(this.table.get('privacy').toLowerCase())
      .html(this.table.get('privacy'));
  },

  /**
   *  Set table description in the header
   */
  _setDescription: function() {
    var default_description = this._TEXTS._DEFAULT_DESCRIPTION;

    if(this.table.sqlView) {
      default_description = "" + this._TEXTS._DEFAULT_QUERY_DESCRIPTION + "";
    }

    this.$('.table_description p').html(this.table.get('description') || default_description);
  },

  /**
   *  Set table tags in the header
   */
  _setTags: function() {
    var tag_button_text = this._TEXTS._DEFAULT_ADD_TAG_BUTTON;
    if (this.table.get('tags')) {
      tag_button_text = this._TEXTS._DEFAULT_EDIT_TAG_BUTTON;
    }
    if (this.table.sqlView) {
      tag_button_text = '';
    }
    if (this.table.get('tags')) {
      var tags = this.table.get('tags').split(",");

      var i = _.size(tags);
      var word = (i == 1) ? "tag" : "tags";
      var count = "" + i + " " + word + "";

      this.$('span.tags').removeClass("empty");
      this.$('span.tags').html(count);

    } else {
      this.$('span.tags').addClass("empty");
      this.$('span.tags').html(""+tag_button_text+"");
    }
  },

  onSQLView: function() {
    this.setTableInfo();
    if(this.table.isInSQLView()) {
      this.$('.clearview').show();
      this.$el.addClass('query');
      this.$('.status').hide();
      this.$('.georeference').parent().hide();
    } else {
      this.$('.clearview').hide();
      this.$el.removeClass('query');
      this.$('.status').show();
      this.$('.georeference').parent().show();
      this.trigger('clearSQLView');
    }

  },

  clearView: function(e) {
    e.preventDefault();
    if(this.dataLayer) {
      this.trigger('clearSQLView');
      /*
      this.dataLayer.save({ query: undefined });
      this.dataLayer.addToHistory("query", "SELECT * FROM " + this.table.get('name'));
      */
      $.faviconNotify('/favicons/cartofante_blue.png');
    }
    return false;
  },

  _addPrivacySelector: function(ev) {
    ev.preventDefault();

    this.privacy && this.privacy.clean();
    cdb.god.trigger("closeDialogs");
        // Add privacy selector
    var privacy = this.privacy = new cdb.admin.PrivacySelector({
      model: this.table,
      limitation: !this.options.user.get("actions").private_tables,
      direction: 'down',
      upgrade_url: 'https://' + config.account_host + "/account/" + user_data.username + "/upgrade"
    });

    cdb.god.bind("closeDialogs", this.privacy.hide, this.privacy);

    this.$el.parent().append(this.privacy.render().el);

    this.privacy.show($(ev.target),"offset");

    return false;
  },

  _changeDescription: function(e) {
    var self = this;
    this.killEvent(e);
    if(!this.table.sqlView) {
      this.description_dialog && this.description_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var description_dialog = this.description_dialog = new cdb.admin.EditTextDialog({
        initial_value: this.table.get('description') || '',
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        maxLength: self._MAX_DESCRIPTION_LENGTH,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          // safety check
          val = val.substr(0, self._MAX_DESCRIPTION_LENGTH);
          if (val !== self.model.get('description')) {
            // Show a loader, and when saves the new description
            // Take into account we don't trigger save event to
            // avoid change the whole application
            self.globalError.showError('Saving ... ', 'load');
            $.when(self.model.save({ description:val })).done(function() {
              self.globalError.showError('Saved', 'info', 3000);
            }).fail(function() {
              self.globalError.showError('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", description_dialog.hide, description_dialog);

      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop();
      var w = Math.max($(e.target).width() + 100, 280);

      description_dialog.showAt(pos.left - 20, pos.top - 13, w);
    }
  },

  _changeTitle: function(e) {
    var self = this;
    this.killEvent(e);
    if(!this.table.sqlView) {
      this.title_dialog && this.title_dialog.clean();
      cdb.god.trigger("closeDialogs");
      var previousName = this.table.get('name');
      var title_dialog = this.title_dialog = new cdb.admin.EditTextDialog({
        initial_value: previousName,
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('name') && val != '') {

            var change_confirmation = new cdb.admin.BaseDialog({
              title: "Rename this table",
              description: "This change will affect you API calls. If you are accesing this table via API don't forget to update the name in the API calls after changing the name.",
              template_name: 'common/views/confirm_dialog',
              clean_on_hide: true,
              enter_to_confirm: true,
              ok_button_classes: "right button grey",
              ok_title: "Yes, do it",
              cancel_button_classes: "underline margin15",
              cancel_title: "Cancel",
              modal_type: "confirmation",
              width: 500
            });

            // If user confirms, app removes the row
            change_confirmation.ok = function() {
              self._renameTable(val)
            }

            change_confirmation
              .appendToBody()
              .open();
          }
        }
      });

      cdb.god.bind("closeDialogs", title_dialog.hide, title_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft()
      pos.top -= $(window).scrollTop()
      var w = Math.max($(e.target).width() + 100, 280);
      title_dialog.showAt(pos.left - 20, pos.top - 6, w);
    }
  },

  _renameTable: function(newName) {
    var self = this;
    self.globalError.showError('Renaming your table ... ', 'load', -1);
    $.when(self.model.save({ name: newName }, {wait: true})).done(function() {
      self.globalError.showError('Saved', 'info', 3000);
    });
  },

  _changeTags: function(e) {
    var self = this;
    e.preventDefault();
    e.stopPropagation();
    if(!this.table.sqlView) {
      this.tags_dialog && this.tags_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var tags_dialog = this.tags_dialog = new cdb.admin.TagsDialog({
        initial_value: this.table.get('tags'),
        template_name: 'table/views/edit_name',
        clean_on_hide: false,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('tags')) {
            // Show a loader, and when saves new tags
            // Take into account we don't trigger save event to
            // avoid change the whole application (same as description)
            self.model.notice('Saving ... ', 'load');
            $.when(self.model.save({ tags:val })).done(function() {
              self.model.notice('Saved', 'info', 3000);
            }).fail(function() {
              self.model.notice('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", tags_dialog.hide, tags_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var $tags = $(e.target).closest("span.tags")
      var pos = $tags.offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop() + 6;

      var w = Math.max($tags.find("p").width() + 100, 280);
      tags_dialog.showAt(pos.left - 10, pos.top - 5);
    }
  }
});
<commit_msg>fixed finally private_tables user paramater<commit_after>
/**
 * common header for map/table views
 */

var OptionsMenu = cdb.admin.DropdownMenu.extend({
  events: {
    'click .export':        '_export',
    'click .duplicate':     '_duplicate',
    'click .append':        '_append',
    'click .delete_table':  '_delete',
    'click .merge_tables':  '_mergeTables'
  },

  show: function() {
    this.render();
    this.constructor.__super__.show.apply(this);
  },

  _export: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // export anything...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var export_dialog = new cdb.admin.ExportTableDialog({
      model: this.options.table,
      config: config,
      user_data: user_data
    });

    $("body").append(export_dialog.render().el);
    export_dialog.open();
  },

  _duplicate: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // duplicate a table or a query...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var duplicate_dialog = new cdb.admin.DuplicateTable({
      model: this.options.table
    });

    $("body").append(duplicate_dialog.render().el);
    duplicate_dialog.open();
  },

  _append: function(e){
    e.preventDefault();
  },

  _delete: function(e){
    e.preventDefault();

    this.delete_dialog = new cdb.admin.DeleteDialog({
      model: this.options.table,
      config: config,
      user_data: user_data,
    });
    $("body").append(this.delete_dialog.render().el);
    this.delete_dialog.open();

    this.delete_dialog.wait()
      .done(this.deleteTable.bind(this));
  },

  _mergeTables: function(e) { // TODO: set prior state
    e.preventDefault();

    var mergeDialog = new cdb.admin.MergeTablesDialog({
      table: this.options.table
    });

    mergeDialog.appendToBody().open({ center:true });

    //window.mergeTableDialog = mergeDialog;

  },

  deleteTable: function() {
    var self = this;
    this.options.table.destroy({wait: true})
      .done(function(){
        window.location = "/dashboard";
      }
    );
  },

});

cdb.admin.Header = cdb.core.View.extend({
  _TEXTS: {
    _DEFAULT_DESCRIPTION:         'add a description for this table...',
    _DEFAULT_QUERY_DESCRIPTION:   'There is no description...',
    _DEFAULT_ADD_TAG_BUTTON:      'add tags',
    _DEFAULT_EDIT_TAG_BUTTON:     'edit tags'
  },
  _PRIVACY_VALUES: [
    'public',
    'private'
  ],
  _MAX_DESCRIPTION_LENGTH: 200,
  events: {
    'click .clearview':           'clearView',
    'click .status':              '_addPrivacySelector',
    'click .change_title':        '_changeTitle',
    'click .table_description p': '_changeDescription',
    'click .georeference':        'georeference',
    'click span.tags > *':        '_changeTags'
  },

  initialize: function(options) {
    var self = this;

    this.$body = this.options.body || $('body');
    this.table = this.model;
    this.globalError = this.options.globalError;
    this.createBindings();

    this.add_related_model(this.table);
    this.$('.clearview').hide();

    // Print all the information and don't
    // wait for table changes, it renders faster
    this.setTableInfo();
    this.createUserMenu();
    this.createOptionsMenu();

    // if(this.table.isInSQLView()) {
    //   this.onSQLView();
    // }
  },

  createBindings: function() {
    _.bindAll(this, '_changeTitle');

    this.table.bind('change:dataSource',  this.onSQLView,         this);
    this.table.bind('change:name',        this._setName,          this);
    this.table.bind('change:description', this._setDescription,   this);
    this.table.bind('change:tags',        this._setTags,          this);
    this.table.bind('change:privacy',     this._setPrivacy,       this);
  },

  createUserMenu: function() {
    // User menu
    var user_menu = this.user_menu = new cdb.admin.DropdownMenu({
      target: $('a.account'),
      username: this.options.user.get('username'),
      host: this.options.config.account_host,
      template_base: 'common/views/settings_item'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", user_menu.hide, user_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", user_menu.hide, user_menu);
    });

    cdb.god.bind("closeDialogs", user_menu.hide, user_menu);
    this.$body.append(user_menu.render().el);
    this.addView(user_menu);
  },

  createOptionsMenu: function() {
    // Options menu
    var options_menu = this.options_menu = new OptionsMenu({
      target: $('a.options'),
      model: { username: this.options.user.get('username') },
      username: this.options.user.get('username'),
      table: this.table,
      template_base: 'table/views/header_table_options'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", options_menu.hide, options_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", options_menu.hide, options_menu);
    });
    cdb.god.bind("closeDialogs", options_menu.hide, options_menu);
    this.addView(options_menu);
  },

  setOptionsMenu: function(dl) {
    this.options_menu.options.dataLayer = dl;
    $('body').append(this.options_menu.render().el);
  },

  setDataLayer: function(dl) {
    this.dataLayer = dl;
    // Set the new datalayer to the options menu
    // due to the fact that we need for duplicating
    // dialog, export dialog, and so on...
    this.setOptionsMenu(dl);
  },

  georeference: function(e) {
    e.preventDefault();
    var dlg = new cdb.admin.GeoreferenceDialog({
      model: this.table,
      geocoder: this.options.geocoder
    });
    dlg.appendToBody().open();
  },

  setTableInfo: function() {
    this._setName();
    this._setPrivacy();
    this._setDescription();
    this._setTags();
  },

  /**
   *  Set Name of the table
   */
  _setName: function() {
    this.$('h2.special a').html(this.table.get('name'));
  },

  /**
   *  Set privacy status of the table
   */
  _setPrivacy: function() {
    var $status = this.$('.status');

    for(var n in this._PRIVACY_VALUES) {
      var privacyName = this._PRIVACY_VALUES[n]
      $status.removeClass(privacyName);
    }

    $status
      .addClass(this.table.get('privacy').toLowerCase())
      .html(this.table.get('privacy'));
  },

  /**
   *  Set table description in the header
   */
  _setDescription: function() {
    var default_description = this._TEXTS._DEFAULT_DESCRIPTION;

    if(this.table.sqlView) {
      default_description = "" + this._TEXTS._DEFAULT_QUERY_DESCRIPTION + "";
    }

    this.$('.table_description p').html(this.table.get('description') || default_description);
  },

  /**
   *  Set table tags in the header
   */
  _setTags: function() {
    var tag_button_text = this._TEXTS._DEFAULT_ADD_TAG_BUTTON;
    if (this.table.get('tags')) {
      tag_button_text = this._TEXTS._DEFAULT_EDIT_TAG_BUTTON;
    }
    if (this.table.sqlView) {
      tag_button_text = '';
    }
    if (this.table.get('tags')) {
      var tags = this.table.get('tags').split(",");

      var i = _.size(tags);
      var word = (i == 1) ? "tag" : "tags";
      var count = "" + i + " " + word + "";

      this.$('span.tags').removeClass("empty");
      this.$('span.tags').html(count);

    } else {
      this.$('span.tags').addClass("empty");
      this.$('span.tags').html(""+tag_button_text+"");
    }
  },

  onSQLView: function() {
    this.setTableInfo();
    if(this.table.isInSQLView()) {
      this.$('.clearview').show();
      this.$el.addClass('query');
      this.$('.status').hide();
      this.$('.georeference').parent().hide();
    } else {
      this.$('.clearview').hide();
      this.$el.removeClass('query');
      this.$('.status').show();
      this.$('.georeference').parent().show();
      this.trigger('clearSQLView');
    }

  },

  clearView: function(e) {
    e.preventDefault();
    if(this.dataLayer) {
      this.trigger('clearSQLView');
      /*
      this.dataLayer.save({ query: undefined });
      this.dataLayer.addToHistory("query", "SELECT * FROM " + this.table.get('name'));
      */
      $.faviconNotify('/favicons/cartofante_blue.png');
    }
    return false;
  },

  _addPrivacySelector: function(ev) {
    ev.preventDefault();

    this.privacy && this.privacy.clean();
    cdb.god.trigger("closeDialogs");

    // Add privacy selector
    var privacy = this.privacy = new cdb.admin.PrivacySelector({
      model: this.table,
      limitation: !this.options.user_data.actions.private_tables,
      direction: 'down',
      upgrade_url: 'https://' + config.account_host + "/account/" + user_data.username + "/upgrade"
    });

    cdb.god.bind("closeDialogs", this.privacy.hide, this.privacy);

    this.$el.parent().append(this.privacy.render().el);

    this.privacy.show($(ev.target),"offset");

    return false;
  },

  _changeDescription: function(e) {
    var self = this;
    this.killEvent(e);
    if(!this.table.sqlView) {
      this.description_dialog && this.description_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var description_dialog = this.description_dialog = new cdb.admin.EditTextDialog({
        initial_value: this.table.get('description') || '',
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        maxLength: self._MAX_DESCRIPTION_LENGTH,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          // safety check
          val = val.substr(0, self._MAX_DESCRIPTION_LENGTH);
          if (val !== self.model.get('description')) {
            // Show a loader, and when saves the new description
            // Take into account we don't trigger save event to
            // avoid change the whole application
            self.globalError.showError('Saving ... ', 'load');
            $.when(self.model.save({ description:val })).done(function() {
              self.globalError.showError('Saved', 'info', 3000);
            }).fail(function() {
              self.globalError.showError('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", description_dialog.hide, description_dialog);

      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop();
      var w = Math.max($(e.target).width() + 100, 280);

      description_dialog.showAt(pos.left - 20, pos.top - 13, w);
    }
  },

  _changeTitle: function(e) {
    var self = this;
    this.killEvent(e);
    if(!this.table.sqlView) {
      this.title_dialog && this.title_dialog.clean();
      cdb.god.trigger("closeDialogs");
      var previousName = this.table.get('name');
      var title_dialog = this.title_dialog = new cdb.admin.EditTextDialog({
        initial_value: previousName,
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('name') && val != '') {

            var change_confirmation = new cdb.admin.BaseDialog({
              title: "Rename this table",
              description: "This change will affect you API calls. If you are accesing this table via API don't forget to update the name in the API calls after changing the name.",
              template_name: 'common/views/confirm_dialog',
              clean_on_hide: true,
              enter_to_confirm: true,
              ok_button_classes: "right button grey",
              ok_title: "Yes, do it",
              cancel_button_classes: "underline margin15",
              cancel_title: "Cancel",
              modal_type: "confirmation",
              width: 500
            });

            // If user confirms, app removes the row
            change_confirmation.ok = function() {
              self._renameTable(val)
            }

            change_confirmation
              .appendToBody()
              .open();
          }
        }
      });

      cdb.god.bind("closeDialogs", title_dialog.hide, title_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft()
      pos.top -= $(window).scrollTop()
      var w = Math.max($(e.target).width() + 100, 280);
      title_dialog.showAt(pos.left - 20, pos.top - 6, w);
    }
  },

  _renameTable: function(newName) {
    var self = this;
    self.globalError.showError('Renaming your table ... ', 'load', -1);
    $.when(self.model.save({ name: newName }, {wait: true})).done(function() {
      self.globalError.showError('Saved', 'info', 3000);
    });
  },

  _changeTags: function(e) {
    var self = this;
    e.preventDefault();
    e.stopPropagation();
    if(!this.table.sqlView) {
      this.tags_dialog && this.tags_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var tags_dialog = this.tags_dialog = new cdb.admin.TagsDialog({
        initial_value: this.table.get('tags'),
        template_name: 'table/views/edit_name',
        clean_on_hide: false,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('tags')) {
            // Show a loader, and when saves new tags
            // Take into account we don't trigger save event to
            // avoid change the whole application (same as description)
            self.model.notice('Saving ... ', 'load');
            $.when(self.model.save({ tags:val })).done(function() {
              self.model.notice('Saved', 'info', 3000);
            }).fail(function() {
              self.model.notice('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", tags_dialog.hide, tags_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var $tags = $(e.target).closest("span.tags")
      var pos = $tags.offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop() + 6;

      var w = Math.max($tags.find("p").width() + 100, 280);
      tags_dialog.showAt(pos.left - 10, pos.top - 5);
    }
  }
});
<|endoftext|>


<commit_before>var React = require('react');

class ReactTypeInAndOut extends React.Component {

    constructor (props) {
        super(props);

        var words = props.words;

        words = words.reduce((acc, word) => {

            // include empty string as start/end
            var charlist = [''].concat(word.split(''));

            var forwards = charlist.slice(0, -1).map((lc, lIdx, lw) => {
                return lw.slice(0, lIdx + 1).join('');
            });

            var backwards = forwards.slice(1).reverse();

            return acc.concat(forwards, [word], backwards);

        }, []);

        this.state = {
            words: words,
            currentWord: words[0],
            start: 0,
            cursorAnimated: !!this.props.delayStart
        };
    }

    startTyping (words) {
        var start = this.state.start;
        var running = setInterval(() => {

            var cursorAnimated = false;
            start = (start + 1) % words.length;

            // avoid getting inside this loop if no repeating delay is provided
            if (this.props.delayRepeat > 0 && start === 0) {
                clearInterval(running);
                setTimeout(this.startTyping.bind(this, words), this.props.delayRepeat);
                cursorAnimated = true;

            // delay when word is fully typed and will regress
            } else if (this.props.delayOnWordFinish && start > 1 && words[start - 1] === words[start + 1]) {
                clearInterval(running);
                setTimeout(this.startTyping.bind(this, words), this.props.delayOnWordFinish);
                cursorAnimated = true;
            }

            this.setState({
                currentWord: words[start],
                start: start,
                cursorAnimated: cursorAnimated
            });

        }, this.props.speed);
    }

    componentDidMount () {
        setTimeout(() => {
            this.startTyping(this.state.words);
        }, this.props.delayStart);
    }

    render () {
        var currentWord = this.state.currentWord;
        var className = this.props.className;
        var cursorClassName = className + '__icon';

        if (this.state.cursorAnimated) {
            cursorClassName = cursorClassName + ' ' + cursorClassName + '--animated';
        }

        return (

              {currentWord}
              {this.props.showCursor && |}

        );
    }
}

ReactTypeInAndOut.propTypes = {
    words: React.PropTypes.arrayOf(React.PropTypes.string),
    speed: React.PropTypes.number,
    delayRepeat: React.PropTypes.number,
    delayStart: React.PropTypes.number,
    delayOnWordFinish: React.PropTypes.number,
    showCursor: React.PropTypes.bool,
    className: React.PropTypes.string
};

ReactTypeInAndOut.defaultProps = {
    speed: 200,
    delayRepeat: 2000,
    delayStart: 0,
    delayOnWordFinish: 2000,
    showCursor: true,
    className: 'react-type-in-and-out'
};

export default ReactTypeInAndOut;
<commit_msg>More appropriate prop name<commit_after>var React = require('react');

class ReactTypeInAndOut extends React.Component {

    constructor (props) {
        super(props);

        var words = props.words;

        words = words.reduce((acc, word) => {

            // include empty string as start/end
            var charlist = [''].concat(word.split(''));

            var forwards = charlist.slice(0, -1).map((lc, lIdx, lw) => {
                return lw.slice(0, lIdx + 1).join('');
            });

            var backwards = forwards.slice(1).reverse();

            return acc.concat(forwards, [word], backwards);

        }, []);

        this.state = {
            words: words,
            currentWord: words[0],
            start: 0,
            cursorAnimated: !!this.props.delayStart
        };
    }

    startTyping (words) {
        var start = this.state.start;
        var running = setInterval(() => {

            var cursorAnimated = false;
            start = (start + 1) % words.length;

            // avoid getting inside this loop if no repeating delay is provided
            if (this.props.delayRestart > 0 && start === 0) {
                clearInterval(running);
                setTimeout(this.startTyping.bind(this, words), this.props.delayRestart);
                cursorAnimated = true;

            // delay when word is fully typed and will regress
            } else if (this.props.delayOnWordFinish && start > 1 && words[start - 1] === words[start + 1]) {
                clearInterval(running);
                setTimeout(this.startTyping.bind(this, words), this.props.delayOnWordFinish);
                cursorAnimated = true;
            }

            this.setState({
                currentWord: words[start],
                start: start,
                cursorAnimated: cursorAnimated
            });

        }, this.props.speed);
    }

    componentDidMount () {
        setTimeout(() => {
            this.startTyping(this.state.words);
        }, this.props.delayStart);
    }

    render () {
        var currentWord = this.state.currentWord;
        var className = this.props.className;
        var cursorClassName = className + '__icon';

        if (this.state.cursorAnimated) {
            cursorClassName = cursorClassName + ' ' + cursorClassName + '--animated';
        }

        return (

              {currentWord}
              {this.props.showCursor && |}

        );
    }
}

ReactTypeInAndOut.propTypes = {
    words: React.PropTypes.arrayOf(React.PropTypes.string),
    speed: React.PropTypes.number,
    delayRestart: React.PropTypes.number,
    delayStart: React.PropTypes.number,
    delayOnWordFinish: React.PropTypes.number,
    showCursor: React.PropTypes.bool,
    className: React.PropTypes.string
};

ReactTypeInAndOut.defaultProps = {
    speed: 200,
    delayRestart: 2000,
    delayStart: 0,
    delayOnWordFinish: 2000,
    showCursor: true,
    className: 'react-type-in-and-out'
};

export default ReactTypeInAndOut;
<|endoftext|>


<commit_before>
/**
 * common header for map/table views
 */

var OptionsMenu = cdb.admin.DropdownMenu.extend({
  events: {
    'click .export':        '_export',
    'click .duplicate':     '_duplicate',
    'click .append':        '_append',
    'click .delete_table':  '_delete'
  },

  show: function() {
    this.render();
    this.constructor.__super__.show.apply(this);
  },

  _export: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // export anything...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var export_dialog = new cdb.admin.ExportTableDialog({
      model: this.options.table,
      config: config,
      user_data: user_data
    });

    $("body").append(export_dialog.render().el);
    export_dialog.open();
  },

  _duplicate: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // duplicate a table or a query...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var duplicate_dialog = new cdb.admin.DuplicateTable({
      model: this.options.table
    });

    $("body").append(duplicate_dialog.render().el);
    duplicate_dialog.open();
  },

  _append: function(e){
    e.preventDefault();
  },

  _delete: function(e){
    e.preventDefault();
    var delete_dialog = new cdb.admin.DeleteDialog({
      model: this.options.table,
      config: config,
      user_data: user_data,
      ok: function() {
        location = "/dashboard";
      }
    });

    $("body").append(delete_dialog.render().el);
    delete_dialog.open();
  }
});

cdb.admin.Header = cdb.core.View.extend({
  _TEXTS: {
    _DEFAULT_DESCRIPTION: 'add a description for this table...',
    _DEFAULT_ADD_TAG_BUTTON: 'add tags',
    _DEFAULT_EDIT_TAG_BUTTON: 'edit tags'
  },
  events: {
    'click .clearview':           'clearView',
    'click .status':              '_addPrivacySelector',
    'click .change_title':        '_changeTitle',
    'click .table_description p': '_changeDescription',
    'click .georeference':        'georeference',
    'click span.tags > *':        '_changeTags'
  },

  initialize: function() {
    var self = this;

    this.$body = this.options.body || $('body');
    this.table = this.model;

    this.createBindings();

    this.add_related_model(this.table);
    this.$('.clearview').hide();

    // Print all the information and don't
    // wait for table changes, it renders faster
    this.setTableInfo();
    this.createUserMenu();
    this.createOptionsMenu();

    if(this.table.isInSQLView()) {
      this.onSQLView();
    }
  },

  createBindings: function() {
    _.bindAll(this, '_changeTitle', '_setDescription', '_setTags', '_setNameAndStatus');
    this.table.bind('change:dataSource', this.onSQLView, this);
    this.table.bind('privacyUpdated', this._setNameAndStatus);
    this.bind('descriptionChanged', this._setDescription);
    this.bind('tagsChanged', this._setTags);
    this.bind('nameChanged', this._setNameAndStatus);
  },

  createUserMenu: function() {
    // User menu
    this.user_menu = new cdb.admin.DropdownMenu({
      target: $('a.account'),
      username: this.options.user.get('username'),
      host: this.options.config.account_host,
      template_base: 'common/views/settings_item'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", self.user_menu.hide, self.user_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", self.user_menu.hide, self.user_menu);
    });

    cdb.god.bind("closeDialogs", this.user_menu.hide, this.user_menu);
    this.$body.append(this.user_menu.render().el);
    this.addView(this.user_menu);
  },

  createOptionsMenu: function() {
    // Options menu
    this.options_menu = new OptionsMenu({
      target: $('a.options'),
      model: { username: this.options.user.get('username') },
      username: this.options.user.get('username'),
      table: this.table,
      template_base: 'table/views/header_table_options'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", self.options_menu.hide, self.options_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", self.options_menu.hide, self.options_menu);
    });
    cdb.god.bind("closeDialogs", this.options_menu.hide, this.options_menu);
    this.addView(this.options_menu);
  },

  setOptionsMenu: function(dl) {
    this.options_menu.options.dataLayer = dl;
    $('body').append(this.options_menu.render().el);
  },

  setDataLayer: function(dl) {
    this.dataLayer = dl;
    // Set the new datalayer to the options menu
    // due to the fact that we need for duplicating
    // dialog, export dialog, and so on...
    this.setOptionsMenu(dl);
  },

  georeference: function(e) {
    e.preventDefault();
    var dlg = new cdb.admin.GeoreferenceDialog({
      model: this.table,
      geocoder: this.options.geocoder
    });
    dlg.appendToBody().open();
  },

  setTableInfo: function() {
    this._setNameAndStatus();
    this._setDescription();
    this._setTags();
  },

  /**
   *  Set Name and status of the table
   */
  _setNameAndStatus: function() {
    this.$('h2.special a').html(this.table.get('name'));

    this.$('.status')
      .addClass(this.table.get('privacy').toLowerCase())
      .html(this.table.get('privacy'));
  },

  /**
   *  Set table description in the header
   */
  _setDescription: function() {
    var default_description = this._TEXTS._DEFAULT_DESCRIPTION;
    if(this.table.sqlView) {
      default_description = '';
    }

    this.$('.table_description p').text(this.table.get('description') || default_description);
  },

  /**
   *  Set table tags in the header
   */
  _setTags: function() {
    var tag_button_text = this._TEXTS._DEFAULT_ADD_TAG_BUTTON;
    if (this.table.get('tags')) {
      tag_button_text = this._TEXTS._DEFAULT_EDIT_TAG_BUTTON;
    }
    if (this.table.sqlView) {
      tag_button_text = '';
    }
    if (this.table.get('tags')) {
      var tags = this.table.get('tags').split(",")
        , html = '
';

      _.each(tags, function(tag,i) {
        html += "" + tag + "";
      });

      this.$('span.tags').html(html + "
");
      this.$('span.tags').append(""+tag_button_text+"");
    } else {
      this.$('span.tags').html(""+tag_button_text+"");
    }
  },

  onSQLView: function() {
    this.setTableInfo();
    if(this.table.isInSQLView()) {
      this.$('.clearview').show();
      this.$el.addClass('query');
      this.$('.status').hide();
      this.$('.georeference').parent().hide();
    } else {
      this.$('.clearview').hide();
      this.$el.removeClass('query');
      this.$('.status').show();
      this.$('.georeference').parent().show();
    }
  },

  clearView: function(e) {
    e.preventDefault();
    this.dataLayer && this.dataLayer.save({ query: undefined });
    $.faviconNotify('/favicons/cartofante_blue.png');
    return false;
  },

  _addPrivacySelector: function(ev) {
    ev.preventDefault();

    this.privacy && this.privacy.clean();
    cdb.god.trigger("closeDialogs");
        // Add privacy selector
    var privacy = this.privacy = new cdb.admin.PrivacySelector({
      model: this.table,
      limitation: !this.options.user.get("private_tables"),
      direction: 'down',
      upgrade_url: 'https://' + config.account_host + "/account/" + user_data.username + "/upgrade"
    });

    cdb.god.bind("closeDialogs", this.privacy.hide, this.privacy);

    this.$el.parent().append(this.privacy.render().el);

    this.privacy.show($(ev.target),"offset");

    return false;
  },

  _changeDescription: function(e) {
    var self = this;
    e.preventDefault();
    e.stopPropagation();
    if(!this.table.sqlView) {
      this.description_dialog && this.description_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var description_dialog = this.description_dialog = new cdb.admin.EditTextDialog({
        initial_value: this.table.get('description') || '',
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('description')) {
            // Show a loader, and when saves the new description
            // Take into account we don't trigger save event to
            // avoid change the whole application
            self.model.notice('Saving ... ', 'load');
            $.when(self.model.save({ description:val }, { silent:true })).done(function() {
              self.trigger('descriptionChanged');
              self.model.notice('Saved', 'info', 3000);
            }).fail(function() {
              self.model.notice('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", description_dialog.hide, description_dialog);

      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop();
      var w = Math.max($(e.target).width() + 100, 280);

      description_dialog.showAt(pos.left - 20, pos.top - 13, w);
    }
  },

  _changeTitle: function(e) {
    var self = this;
    this.killEvent(e);
    if(!this.table.sqlView) {
      this.title_dialog && this.title_dialog.clean();
      cdb.god.trigger("closeDialogs");
      var previousName = this.table.get('name');
      var title_dialog = this.title_dialog = new cdb.admin.EditTextDialog({
        initial_value: previousName,
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if(val !== self.model.get('name')) {
            var confirmation = new cdb.admin.RenameConfirmationDialog({
              model: self.table,
              newName: val
            });
            confirmation.appendToBody().open();
            var promise = confirmation.confirm();
            $.when(promise).done(function(newName) {
              var tileStyle = self.options.map.get('dataLayer').get('tile_style');
              var replaceRegexp = new RegExp('#'+previousName, 'g');
              tileStyle = tileStyle.replace(replaceRegexp, '#'+newName);

              // Change the table name, now we want to trigger the model change event
              // to propagate this change to the whole application
              self.model.notice('Saving ... ', 'load');
              $.when(self.options.map.get('dataLayer').save({'tile_style': tileStyle})).done(function() {
                self.trigger('nameChanged');
                self.model.notice('Saved', 'info', 3000);
              }).fail(function() {
                self.model.notice('Something has failed', 'error', 5000);
              });
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", title_dialog.hide, title_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft()
      pos.top -= $(window).scrollTop()
      var w = Math.max($(e.target).width() + 100, 280);
      title_dialog.showAt(pos.left - 20, pos.top - 6, w);
    }
  },

  _changeTags: function(e) {
    var self = this;
    e.preventDefault();
    e.stopPropagation();
    if(!this.table.sqlView) {
      this.tags_dialog && this.tags_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var tags_dialog = this.tags_dialog = new cdb.admin.TagsDialog({
        initial_value: this.table.get('tags'),
        template_name: 'table/views/edit_name',
        clean_on_hide: false,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('tags')) {
            // Show a loader, and when saves new tags
            // Take into account we don't trigger save event to
            // avoid change the whole application (same as description)
            self.model.notice('Saving ... ', 'load');
            $.when(self.model.save({ tags:val }, { silent:true })).done(function() {
              self.trigger('tagsChanged');
              self.model.notice('Saved', 'info', 3000);
            }).fail(function() {
              self.model.notice('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", tags_dialog.hide, tags_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var $tags = $(e.target).closest("span.tags")
      var pos = $tags.offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop() + 6;

      var w = Math.max($tags.find("p").width() + 100, 280);
      tags_dialog.showAt(pos.left - 20, pos.top - 8);
    }
  }
});
<commit_msg>privacy class bug fixed<commit_after>
/**
 * common header for map/table views
 */

var OptionsMenu = cdb.admin.DropdownMenu.extend({
  events: {
    'click .export':        '_export',
    'click .duplicate':     '_duplicate',
    'click .append':        '_append',
    'click .delete_table':  '_delete'
  },

  show: function() {
    this.render();
    this.constructor.__super__.show.apply(this);
  },

  _export: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // export anything...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var export_dialog = new cdb.admin.ExportTableDialog({
      model: this.options.table,
      config: config,
      user_data: user_data
    });

    $("body").append(export_dialog.render().el);
    export_dialog.open();
  },

  _duplicate: function(e){
    e.preventDefault();

    // Should check if query is applied and it is correct, if not, user can't
    // duplicate a table or a query...
    if (this.options.table.isInSQLView() && !this.options.dataLayer.get("query")) {
      return;
    }

    var duplicate_dialog = new cdb.admin.DuplicateTable({
      model: this.options.table
    });

    $("body").append(duplicate_dialog.render().el);
    duplicate_dialog.open();
  },

  _append: function(e){
    e.preventDefault();
  },

  _delete: function(e){
    e.preventDefault();
    var delete_dialog = new cdb.admin.DeleteDialog({
      model: this.options.table,
      config: config,
      user_data: user_data,
      ok: function() {
        location = "/dashboard";
      }
    });

    $("body").append(delete_dialog.render().el);
    delete_dialog.open();
  }
});

cdb.admin.Header = cdb.core.View.extend({
  _TEXTS: {
    _DEFAULT_DESCRIPTION: 'add a description for this table...',
    _DEFAULT_ADD_TAG_BUTTON: 'add tags',
    _DEFAULT_EDIT_TAG_BUTTON: 'edit tags'
  },
  _PRIVACY_VALUES: [
    'public',
    'private'
  ],
  events: {
    'click .clearview':           'clearView',
    'click .status':              '_addPrivacySelector',
    'click .change_title':        '_changeTitle',
    'click .table_description p': '_changeDescription',
    'click .georeference':        'georeference',
    'click span.tags > *':        '_changeTags'
  },

  initialize: function() {
    var self = this;

    this.$body = this.options.body || $('body');
    this.table = this.model;

    this.createBindings();

    this.add_related_model(this.table);
    this.$('.clearview').hide();

    // Print all the information and don't
    // wait for table changes, it renders faster
    this.setTableInfo();
    this.createUserMenu();
    this.createOptionsMenu();

    if(this.table.isInSQLView()) {
      this.onSQLView();
    }
  },

  createBindings: function() {
    _.bindAll(this, '_changeTitle', '_setDescription', '_setTags', '_setNameAndStatus');
    this.table.bind('change:dataSource', this.onSQLView, this);
    this.table.bind('privacyUpdated', this._setNameAndStatus);
    this.bind('descriptionChanged', this._setDescription);
    this.bind('tagsChanged', this._setTags);
    this.bind('nameChanged', this._setNameAndStatus);
  },

  createUserMenu: function() {
    // User menu
    this.user_menu = new cdb.admin.DropdownMenu({
      target: $('a.account'),
      username: this.options.user.get('username'),
      host: this.options.config.account_host,
      template_base: 'common/views/settings_item'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", self.user_menu.hide, self.user_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", self.user_menu.hide, self.user_menu);
    });

    cdb.god.bind("closeDialogs", this.user_menu.hide, this.user_menu);
    this.$body.append(this.user_menu.render().el);
    this.addView(this.user_menu);
  },

  createOptionsMenu: function() {
    // Options menu
    this.options_menu = new OptionsMenu({
      target: $('a.options'),
      model: { username: this.options.user.get('username') },
      username: this.options.user.get('username'),
      table: this.table,
      template_base: 'table/views/header_table_options'
    }).bind("onDropdownShown",function(ev) {
      cdb.god.unbind("closeDialogs", self.options_menu.hide, self.options_menu);
      cdb.god.trigger("closeDialogs");
      cdb.god.bind("closeDialogs", self.options_menu.hide, self.options_menu);
    });
    cdb.god.bind("closeDialogs", this.options_menu.hide, this.options_menu);
    this.addView(this.options_menu);
  },

  setOptionsMenu: function(dl) {
    this.options_menu.options.dataLayer = dl;
    $('body').append(this.options_menu.render().el);
  },

  setDataLayer: function(dl) {
    this.dataLayer = dl;
    // Set the new datalayer to the options menu
    // due to the fact that we need for duplicating
    // dialog, export dialog, and so on...
    this.setOptionsMenu(dl);
  },

  georeference: function(e) {
    e.preventDefault();
    var dlg = new cdb.admin.GeoreferenceDialog({
      model: this.table,
      geocoder: this.options.geocoder
    });
    dlg.appendToBody().open();
  },

  setTableInfo: function() {
    this._setNameAndStatus();
    this._setDescription();
    this._setTags();
  },

  /**
   *  Set Name and status of the table
   */
  _setNameAndStatus: function() {
    this.$('h2.special a').html(this.table.get('name'));

    var $status = this.$('.status');

    for(var n in this._PRIVACY_VALUES) {
      var privacyName = this._PRIVACY_VALUES[n]
      $status.removeClass(privacyName);
    }

    $status
      .addClass(this.table.get('privacy').toLowerCase())
      .html(this.table.get('privacy'));
  },

  /**
   *  Set table description in the header
   */
  _setDescription: function() {
    var default_description = this._TEXTS._DEFAULT_DESCRIPTION;
    if(this.table.sqlView) {
      default_description = '';
    }

    this.$('.table_description p').text(this.table.get('description') || default_description);
  },

  /**
   *  Set table tags in the header
   */
  _setTags: function() {
    var tag_button_text = this._TEXTS._DEFAULT_ADD_TAG_BUTTON;
    if (this.table.get('tags')) {
      tag_button_text = this._TEXTS._DEFAULT_EDIT_TAG_BUTTON;
    }
    if (this.table.sqlView) {
      tag_button_text = '';
    }
    if (this.table.get('tags')) {
      var tags = this.table.get('tags').split(",")
        , html = '
';

      _.each(tags, function(tag,i) {
        html += "" + tag + "";
      });

      this.$('span.tags').html(html + "
");
      this.$('span.tags').append(""+tag_button_text+"");
    } else {
      this.$('span.tags').html(""+tag_button_text+"");
    }
  },

  onSQLView: function() {
    this.setTableInfo();
    if(this.table.isInSQLView()) {
      this.$('.clearview').show();
      this.$el.addClass('query');
      this.$('.status').hide();
      this.$('.georeference').parent().hide();
    } else {
      this.$('.clearview').hide();
      this.$el.removeClass('query');
      this.$('.status').show();
      this.$('.georeference').parent().show();
    }
  },

  clearView: function(e) {
    e.preventDefault();
    this.dataLayer && this.dataLayer.save({ query: undefined });
    $.faviconNotify('/favicons/cartofante_blue.png');
    return false;
  },

  _addPrivacySelector: function(ev) {
    ev.preventDefault();

    this.privacy && this.privacy.clean();
    cdb.god.trigger("closeDialogs");
        // Add privacy selector
    var privacy = this.privacy = new cdb.admin.PrivacySelector({
      model: this.table,
      limitation: !this.options.user.get("private_tables"),
      direction: 'down',
      upgrade_url: 'https://' + config.account_host + "/account/" + user_data.username + "/upgrade"
    });

    cdb.god.bind("closeDialogs", this.privacy.hide, this.privacy);

    this.$el.parent().append(this.privacy.render().el);

    this.privacy.show($(ev.target),"offset");

    return false;
  },

  _changeDescription: function(e) {
    var self = this;
    e.preventDefault();
    e.stopPropagation();
    if(!this.table.sqlView) {
      this.description_dialog && this.description_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var description_dialog = this.description_dialog = new cdb.admin.EditTextDialog({
        initial_value: this.table.get('description') || '',
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('description')) {
            // Show a loader, and when saves the new description
            // Take into account we don't trigger save event to
            // avoid change the whole application
            self.model.notice('Saving ... ', 'load');
            $.when(self.model.save({ description:val }, { silent:true })).done(function() {
              self.trigger('descriptionChanged');
              self.model.notice('Saved', 'info', 3000);
            }).fail(function() {
              self.model.notice('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", description_dialog.hide, description_dialog);

      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop();
      var w = Math.max($(e.target).width() + 100, 280);

      description_dialog.showAt(pos.left - 20, pos.top - 13, w);
    }
  },

  _changeTitle: function(e) {
    var self = this;
    this.killEvent(e);
    if(!this.table.sqlView) {
      this.title_dialog && this.title_dialog.clean();
      cdb.god.trigger("closeDialogs");
      var previousName = this.table.get('name');
      var title_dialog = this.title_dialog = new cdb.admin.EditTextDialog({
        initial_value: previousName,
        template_name: 'table/views/edit_name',
        clean_on_hide: true,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if(val !== self.model.get('name')) {
            var confirmation = new cdb.admin.RenameConfirmationDialog({
              model: self.table,
              newName: val
            });
            confirmation.appendToBody().open();
            var promise = confirmation.confirm();
            $.when(promise).done(function(newName) {
              var tileStyle = self.options.map.get('dataLayer').get('tile_style');
              var replaceRegexp = new RegExp('#'+previousName, 'g');
              tileStyle = tileStyle.replace(replaceRegexp, '#'+newName);

              // Change the table name, now we want to trigger the model change event
              // to propagate this change to the whole application
              self.model.notice('Saving ... ', 'load');
              $.when(self.options.map.get('dataLayer').save({'tile_style': tileStyle})).done(function() {
                self.trigger('nameChanged');
                self.model.notice('Saved', 'info', 3000);
              }).fail(function() {
                self.model.notice('Something has failed', 'error', 5000);
              });
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", title_dialog.hide, title_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var pos = $(e.target).offset();
      pos.left -= $(window).scrollLeft()
      pos.top -= $(window).scrollTop()
      var w = Math.max($(e.target).width() + 100, 280);
      title_dialog.showAt(pos.left - 20, pos.top - 6, w);
    }
  },

  _changeTags: function(e) {
    var self = this;
    e.preventDefault();
    e.stopPropagation();
    if(!this.table.sqlView) {
      this.tags_dialog && this.tags_dialog.clean();
      cdb.god.trigger("closeDialogs");

      var tags_dialog = this.tags_dialog = new cdb.admin.TagsDialog({
        initial_value: this.table.get('tags'),
        template_name: 'table/views/edit_name',
        clean_on_hide: false,
        modal_class: 'edit_name_dialog',
        res: function(val) {
          if (val !== self.model.get('tags')) {
            // Show a loader, and when saves new tags
            // Take into account we don't trigger save event to
            // avoid change the whole application (same as description)
            self.model.notice('Saving ... ', 'load');
            $.when(self.model.save({ tags:val }, { silent:true })).done(function() {
              self.trigger('tagsChanged');
              self.model.notice('Saved', 'info', 3000);
            }).fail(function() {
              self.model.notice('Something has failed', 'error', 5000);
            });
          }
        }
      });

      cdb.god.bind("closeDialogs", tags_dialog.hide, tags_dialog);

      // when the table has horizontal scroll for some reason
      // object offset returned has the scroll added
      var $tags = $(e.target).closest("span.tags")
      var pos = $tags.offset();
      pos.left -= $(window).scrollLeft();
      pos.top -= $(window).scrollTop() + 6;

      var w = Math.max($tags.find("p").width() + 100, 280);
      tags_dialog.showAt(pos.left - 20, pos.top - 8);
    }
  }
});
<|endoftext|>