<commit_before>/**
 * base dialog for all dialogs in the application
 * it does a custom show/hide
 */

cdb.admin.BaseDialog = cdb.ui.common.Dialog.extend({

  hide: function() {
    var self = this;

    this.$el.find(".modal").animate({
      marginTop: "50px",
      opacity: 0
    },300, function() {
      if(self.options.clean_on_hide) {
        self.clean();
      }
    });
    this.$el.find(".mamufas").fadeOut(300);
  },

  open: function() {
    var self = this;

    this.$el.find(".modal").css({
      "opacity": "0",
      "marginTop": "150px"
    });

    this.$el.find(".mamufas").fadeIn();
    this.$el.find(".modal").animate({
      marginTop: "100px",
      opacity: 1
    },300);
  },

  /**
   * call this method is the dialog has some
   * selector
   */
  setWizard: function() {
    // Option selected by default
    this.option = 0;
    _.bindAll(this, '_changeOption');
    this.$el.delegate('ul > li > a.radiobutton', 'click', this._changeOption);
  },

  /** when wizard is enabled this function is used to switch
   * between options */
  _changeOption: function(ev) {
    ev.preventDefault();

    var $el   = $(ev.target)
      , $list = $el.closest("ul")
      , $li   = $el.closest("li");

    // Stop if option is disabled
    if ($el.hasClass("disabled") || $el.hasClass("selected")) {
      return false;
    }

    // If not activate it and desactivate previous one
    $list
      .find("li.active")
      .removeClass("active")
      .find(" > a.radiobutton")
      .removeClass("selected");

    this.option = $li.attr("data-option");

    $el.addClass("selected");
    $li.addClass("active");

  }

});
<commit_msg>Decreased 20px the y position of the modals window in the dashboard<commit_after>/**
 * base dialog for all dialogs in the application
 * it does a custom show/hide
 */

cdb.admin.BaseDialog = cdb.ui.common.Dialog.extend({

  hide: function() {
    var self = this;

    this.$el.find(".modal").animate({
      marginTop: "50px",
      opacity: 0
    },300, function() {
      if(self.options.clean_on_hide) {
        self.clean();
      }
    });
    this.$el.find(".mamufas").fadeOut(300);
  },

  open: function() {
    var self = this;

    this.$el.find(".modal").css({
      "opacity": "0",
      "marginTop": "170px"
    });

    this.$el.find(".mamufas").fadeIn();
    this.$el.find(".modal").animate({
      marginTop: "120px",
      opacity: 1
    },300);
  },

  /**
   * call this method is the dialog has some
   * selector
   */
  setWizard: function() {
    // Option selected by default
    this.option = 0;
    _.bindAll(this, '_changeOption');
    this.$el.delegate('ul > li > a.radiobutton', 'click', this._changeOption);
  },

  /** when wizard is enabled this function is used to switch
   * between options */
  _changeOption: function(ev) {
    ev.preventDefault();

    var $el   = $(ev.target)
      , $list = $el.closest("ul")
      , $li   = $el.closest("li");

    // Stop if option is disabled
    if ($el.hasClass("disabled") || $el.hasClass("selected")) {
      return false;
    }

    // If not activate it and desactivate previous one
    $list
      .find("li.active")
      .removeClass("active")
      .find(" > a.radiobutton")
      .removeClass("selected");

    this.option = $li.attr("data-option");

    $el.addClass("selected");
    $li.addClass("active");

  }

});
<|endoftext|>


<commit_before>const uuid = require('uuid');
const merge = require('lodash/merge');
const isString = require('lodash/isString');
const cloneDeep = require('lodash/cloneDeep');
const utils = require('./utils');

class GhostError extends Error {
    constructor(options) {
        options = options || {};

        super();

        /**
         * defaults
         */
        this.statusCode = 500;
        this.errorType = 'InternalServerError';
        this.level = 'normal';
        this.message = 'The server has encountered an error.';
        this.id = uuid.v1();

        /**
         * custom overrides
         */
        this.id = options.id || this.id;
        this.statusCode = options.statusCode || this.statusCode;
        this.level = options.level || this.level;
        this.context = options.context || this.context;
        this.help = options.help || this.help;
        this.errorType = this.name = options.errorType || this.errorType;
        this.errorDetails = options.errorDetails;
        // @ts-ignore
        this.code = options.code || null;
        this.property = options.property || null;
        this.redirect = options.redirect || null;

        this.message = options.message || this.message;
        this.hideStack = options.hideStack;

        // NOTE: Error to inherit from, override!
        //       Nested objects are getting copied over in one piece (can be changed, but not needed right now)
        if (options.err) {
            // CASE: Support err as string (it happens that third party libs return a string instead of an error instance)
            if (isString(options.err)) {
                /* eslint-disable no-restricted-syntax */
                options.err = new Error(options.err);
                /* eslint-enable no-restricted-syntax */
            }

            Object.getOwnPropertyNames(options.err).forEach((property) => {
                if (['errorType', 'name', 'statusCode', 'message', 'level'].indexOf(property) !== -1) {
                    return;
                }

                // CASE: `code` should put options as priority over err
                if (property === 'code') {
                    // @ts-ignore
                    this[property] = this[property] || options.err[property];
                    return;
                }

                if (property === 'stack') {
                    if (this.hideStack) {
                        return;
                    }
                    this[property] = utils.wrapStack(this, options.err);
                    return;
                }

                this[property] = options.err[property] || this[property];
            });
        }
    }

    prepareErrorForUser() {
        const error = cloneDeep(this);

        let stackbits = error.stack.split(/\n/);

        // We build this up backwards, so we always insert at position 1

        if (process.env.NODE_ENV === 'production') {
            stackbits.splice(1, stackbits.length - 1);
        } else {
            // Clearly mark the strack trace
            stackbits.splice(1, 0, `Stack Trace:`);
        }

        // Add in our custom cotext and help methods

        if (this.help) {
            stackbits.splice(1, 0, `${this.help}`);
        }

        if (this.context) {
            stackbits.splice(1, 0, `${this.context}`);
        }

        error.stack = stackbits.join('\n');
        return error;
    }
}

const ghostErrors = {
    InternalServerError: class InternalServerError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                level: 'critical',
                errorType: 'InternalServerError',
                message: 'The server has encountered an error.'
            }, options));
        }
    },
    IncorrectUsageError: class IncorrectUsageError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 400,
                level: 'critical',
                errorType: 'IncorrectUsageError',
                message: 'We detected a misuse. Please read the stack trace.'
            }, options));
        }
    },
    NotFoundError: class NotFoundError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 404,
                errorType: 'NotFoundError',
                message: 'Resource could not be found.',
                hideStack: true
            }, options));
        }
    },
    BadRequestError: class BadRequestError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 400,
                errorType: 'BadRequestError',
                message: 'The request could not be understood.'
            }, options));
        }
    },
    UnauthorizedError: class UnauthorizedError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 401,
                errorType: 'UnauthorizedError',
                message: 'You are not authorised to make this request.'
            }, options));
        }
    },
    NoPermissionError: class NoPermissionError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 403,
                errorType: 'NoPermissionError',
                message: 'You do not have permission to perform this request.'
            }, options));
        }
    },
    ValidationError: class ValidationError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 422,
                errorType: 'ValidationError',
                message: 'The request failed validation.'
            }, options));
        }
    },
    UnsupportedMediaTypeError: class UnsupportedMediaTypeError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 415,
                errorType: 'UnsupportedMediaTypeError',
                message: 'The media in the request is not supported by the server.'
            }, options));
        }
    },
    TooManyRequestsError: class TooManyRequestsError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 429,
                errorType: 'TooManyRequestsError',
                message: 'Server has received too many similar requests in a short space of time.'
            }, options));
        }
    },
    MaintenanceError: class MaintenanceError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 503,
                errorType: 'MaintenanceError',
                message: 'The server is temporarily down for maintenance.'
            }, options));
        }
    },
    MethodNotAllowedError: class MethodNotAllowedError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 405,
                errorType: 'MethodNotAllowedError',
                message: 'Method not allowed for resource.'
            }, options));
        }
    },
    RequestEntityTooLargeError: class RequestEntityTooLargeError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 413,
                errorType: 'RequestEntityTooLargeError',
                message: 'Request was too big for the server to handle.'
            }, options));
        }
    },
    TokenRevocationError: class TokenRevocationError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 503,
                errorType: 'TokenRevocationError',
                message: 'Token is no longer available.'
            }, options));
        }
    },
    VersionMismatchError: class VersionMismatchError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 400,
                errorType: 'VersionMismatchError',
                message: 'Requested version does not match server version.'
            }, options));
        }
    },
    DataExportError: class DataExportError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                errorType: 'DataExportError'
            }, options));
        }
    },
    DataImportError: class DataImportError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                errorType: 'DataImportError'
            }, options));
        }
    },
    DatabaseVersionError: class DatabaseVersionError extends GhostError {
        constructor(options) {
            super(merge({
                hideStack: true,
                statusCode: 500,
                errorType: 'DatabaseVersionError'
            }, options));
        }
    },
    EmailError: class EmailError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                errorType: 'EmailError'
            }, options));
        }
    },
    ThemeValidationError: class ThemeValidationError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 422,
                errorType: 'ThemeValidationError',
                errorDetails: {}
            }, options));
        }
    },
    DisabledFeatureError: class DisabledFeatureError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 409,
                errorType: 'DisabledFeatureError'
            }, options));
        }
    },
    UpdateCollisionError: class UpdateCollisionError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 409,
                errorType: 'UpdateCollisionError'
            }, options));
        }
    },
    HostLimitError: class HostLimitError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'HostLimitError',
                hideStack: true,
                statusCode: 403
            }, options));
        }
    },
    HelperWarning: class HelperWarning extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'HelperWarning',
                hideStack: true
            }, options));
        }
    },
    PasswordResetRequiredError: class PasswordResetRequiredError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'PasswordResetRequiredError',
                statusCode: 401,
                message: 'For security, you need to create a new password. An email has been sent to you with instructions!'
            }, options));
        }
    },
    UnhandledJobError: class UnhandledJobError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'UnhandledJobError',
                message: 'Processed job threw an unhandled error',
                level: 'critical'
            }, options));
        }
    },
    NoContentError: class NoContentError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'NoContentError',
                statusCode: 204,
                hideStack: true
            }, options));
        }
    }
};

module.exports = ghostErrors;

const ghostErrorsWithBase = Object.assign({}, ghostErrors, {GhostError});
module.exports.utils = {
    serialize: utils.serialize.bind(ghostErrorsWithBase),
    deserialize: utils.deserialize.bind(ghostErrorsWithBase),
    isGhostError: utils.isGhostError.bind(ghostErrorsWithBase)
};
<commit_msg>Add ConflictError (HTTP 409) to @tryghost/errors<commit_after>const uuid = require('uuid');
const merge = require('lodash/merge');
const isString = require('lodash/isString');
const cloneDeep = require('lodash/cloneDeep');
const utils = require('./utils');

class GhostError extends Error {
    constructor(options) {
        options = options || {};

        super();

        /**
         * defaults
         */
        this.statusCode = 500;
        this.errorType = 'InternalServerError';
        this.level = 'normal';
        this.message = 'The server has encountered an error.';
        this.id = uuid.v1();

        /**
         * custom overrides
         */
        this.id = options.id || this.id;
        this.statusCode = options.statusCode || this.statusCode;
        this.level = options.level || this.level;
        this.context = options.context || this.context;
        this.help = options.help || this.help;
        this.errorType = this.name = options.errorType || this.errorType;
        this.errorDetails = options.errorDetails;
        // @ts-ignore
        this.code = options.code || null;
        this.property = options.property || null;
        this.redirect = options.redirect || null;

        this.message = options.message || this.message;
        this.hideStack = options.hideStack;

        // NOTE: Error to inherit from, override!
        //       Nested objects are getting copied over in one piece (can be changed, but not needed right now)
        if (options.err) {
            // CASE: Support err as string (it happens that third party libs return a string instead of an error instance)
            if (isString(options.err)) {
                /* eslint-disable no-restricted-syntax */
                options.err = new Error(options.err);
                /* eslint-enable no-restricted-syntax */
            }

            Object.getOwnPropertyNames(options.err).forEach((property) => {
                if (['errorType', 'name', 'statusCode', 'message', 'level'].indexOf(property) !== -1) {
                    return;
                }

                // CASE: `code` should put options as priority over err
                if (property === 'code') {
                    // @ts-ignore
                    this[property] = this[property] || options.err[property];
                    return;
                }

                if (property === 'stack') {
                    if (this.hideStack) {
                        return;
                    }
                    this[property] = utils.wrapStack(this, options.err);
                    return;
                }

                this[property] = options.err[property] || this[property];
            });
        }
    }

    prepareErrorForUser() {
        const error = cloneDeep(this);

        let stackbits = error.stack.split(/\n/);

        // We build this up backwards, so we always insert at position 1

        if (process.env.NODE_ENV === 'production') {
            stackbits.splice(1, stackbits.length - 1);
        } else {
            // Clearly mark the strack trace
            stackbits.splice(1, 0, `Stack Trace:`);
        }

        // Add in our custom cotext and help methods

        if (this.help) {
            stackbits.splice(1, 0, `${this.help}`);
        }

        if (this.context) {
            stackbits.splice(1, 0, `${this.context}`);
        }

        error.stack = stackbits.join('\n');
        return error;
    }
}

const ghostErrors = {
    InternalServerError: class InternalServerError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                level: 'critical',
                errorType: 'InternalServerError',
                message: 'The server has encountered an error.'
            }, options));
        }
    },
    IncorrectUsageError: class IncorrectUsageError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 400,
                level: 'critical',
                errorType: 'IncorrectUsageError',
                message: 'We detected a misuse. Please read the stack trace.'
            }, options));
        }
    },
    NotFoundError: class NotFoundError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 404,
                errorType: 'NotFoundError',
                message: 'Resource could not be found.',
                hideStack: true
            }, options));
        }
    },
    BadRequestError: class BadRequestError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 400,
                errorType: 'BadRequestError',
                message: 'The request could not be understood.'
            }, options));
        }
    },
    UnauthorizedError: class UnauthorizedError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 401,
                errorType: 'UnauthorizedError',
                message: 'You are not authorised to make this request.'
            }, options));
        }
    },
    NoPermissionError: class NoPermissionError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 403,
                errorType: 'NoPermissionError',
                message: 'You do not have permission to perform this request.'
            }, options));
        }
    },
    ValidationError: class ValidationError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 422,
                errorType: 'ValidationError',
                message: 'The request failed validation.'
            }, options));
        }
    },
    UnsupportedMediaTypeError: class UnsupportedMediaTypeError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 415,
                errorType: 'UnsupportedMediaTypeError',
                message: 'The media in the request is not supported by the server.'
            }, options));
        }
    },
    TooManyRequestsError: class TooManyRequestsError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 429,
                errorType: 'TooManyRequestsError',
                message: 'Server has received too many similar requests in a short space of time.'
            }, options));
        }
    },
    MaintenanceError: class MaintenanceError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 503,
                errorType: 'MaintenanceError',
                message: 'The server is temporarily down for maintenance.'
            }, options));
        }
    },
    MethodNotAllowedError: class MethodNotAllowedError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 405,
                errorType: 'MethodNotAllowedError',
                message: 'Method not allowed for resource.'
            }, options));
        }
    },
    RequestEntityTooLargeError: class RequestEntityTooLargeError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 413,
                errorType: 'RequestEntityTooLargeError',
                message: 'Request was too big for the server to handle.'
            }, options));
        }
    },
    TokenRevocationError: class TokenRevocationError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 503,
                errorType: 'TokenRevocationError',
                message: 'Token is no longer available.'
            }, options));
        }
    },
    VersionMismatchError: class VersionMismatchError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 400,
                errorType: 'VersionMismatchError',
                message: 'Requested version does not match server version.'
            }, options));
        }
    },
    DataExportError: class DataExportError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                errorType: 'DataExportError'
            }, options));
        }
    },
    DataImportError: class DataImportError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                errorType: 'DataImportError'
            }, options));
        }
    },
    DatabaseVersionError: class DatabaseVersionError extends GhostError {
        constructor(options) {
            super(merge({
                hideStack: true,
                statusCode: 500,
                errorType: 'DatabaseVersionError'
            }, options));
        }
    },
    EmailError: class EmailError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 500,
                errorType: 'EmailError'
            }, options));
        }
    },
    ThemeValidationError: class ThemeValidationError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 422,
                errorType: 'ThemeValidationError',
                errorDetails: {}
            }, options));
        }
    },
    DisabledFeatureError: class DisabledFeatureError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 409,
                errorType: 'DisabledFeatureError'
            }, options));
        }
    },
    UpdateCollisionError: class UpdateCollisionError extends GhostError {
        constructor(options) {
            super(merge({
                statusCode: 409,
                errorType: 'UpdateCollisionError'
            }, options));
        }
    },
    HostLimitError: class HostLimitError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'HostLimitError',
                hideStack: true,
                statusCode: 403
            }, options));
        }
    },
    HelperWarning: class HelperWarning extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'HelperWarning',
                hideStack: true
            }, options));
        }
    },
    PasswordResetRequiredError: class PasswordResetRequiredError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'PasswordResetRequiredError',
                statusCode: 401,
                message: 'For security, you need to create a new password. An email has been sent to you with instructions!'
            }, options));
        }
    },
    UnhandledJobError: class UnhandledJobError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'UnhandledJobError',
                message: 'Processed job threw an unhandled error',
                level: 'critical'
            }, options));
        }
    },
    NoContentError: class NoContentError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'NoContentError',
                statusCode: 204,
                hideStack: true
            }, options));
        }
    },
    ConflictError: class ConflictError extends GhostError {
        constructor(options) {
            super(merge({
                errorType: 'ConflictError',
                statusCode: 409
            }, options));
        }
    }
};

module.exports = ghostErrors;

const ghostErrorsWithBase = Object.assign({}, ghostErrors, {GhostError});
module.exports.utils = {
    serialize: utils.serialize.bind(ghostErrorsWithBase),
    deserialize: utils.deserialize.bind(ghostErrorsWithBase),
    isGhostError: utils.isGhostError.bind(ghostErrorsWithBase)
};
<|endoftext|>


<commit_before>import m from 'mithril';

function controller() {
  return {
    message: m.prop(''),
  };
}

function view(ctrl) {
  return (

      <input
        className="entry__input"
        autocomplete="off"
        onchange={m.withAttr('value', ctrl.message)}
        value={ctrl.message()}
      />
      <button type="button" className="entry__send">
        Send
      </button>

  );
}

const Entry = { controller, view };

export default Entry;
<commit_msg>submit message with button<commit_after>import m from 'mithril';

function controller() {
  return {
    message: m.prop(''),

    send() {
      const { message } = this;

      if(message()) {
        // TODO: replace with emitter
        console.log(message());
        message('');
      }
    },
  };
}

function view(ctrl) {
  return (

      <input
        className="entry__input"
        autocomplete="off"
        onchange={m.withAttr('value', ctrl.message)}
        value={ctrl.message()}
      />
      <button type="button" className="entry__send" onclick={() => ctrl.send()}>
        Send
      </button>

  );
}

const Entry = { controller, view };

export default Entry;
<|endoftext|>


<commit_before>// SAMPLE DATA (Can be used for testing!)
// A sample pad
var pad = {
	name: 'Sample Pad',
	color: 'red',
	contents: [
	{
		program: "Firefox",
		files: ["http://www.illinois.edu", "http://www.cnn.com", "http://www.piazza.com"]
	},{
		program: "Evince",
        //Jon's machine
		//files: ["home/jmpierc2/cs233/lab7.pdf", "home/jmpierc2/cs233/lab8.pdf"]
        //Andrew's machine
        files: ["~/research/functional_logic/Curry_report.pdf",
            "~/research/functional_logic/Hanus_Multi-paradigm_Declarative_Languages.pdf"]
	},{
		program: "Calculator",
		files: []
	},{
		program: "gVim",
        //Jon's machine
		//files: ["home/jmpierc2/lilypad/frontend/frontend.js", "home/jmpierc2/lilypad/frontend/lilylibrary.js", "home/jmpierc2/lilypad/backend/backend.py", "home/jmpierc2/lilypad/backend/config.json"]
        //Andrew's machine
		files: ["~/classes/cs465/LilyPad/frontend/frontend.js", "~/classes/cs465/LilyPad/frontend/lilylibrary.js", "~/classes/cs465/LilyPad/backend/backend.py", "~/classes/cs465/LilyPad/backend/config.json"]
	}
	]
};
// A pad delta. Essentially a "contents" array like the one from above.
var paddelta = [
	{
		program: "Firefox",
		files: ["http://www.cnn.com", "http://www.twitter.com", "http://www.facebook.com"]
	},
	{
		program: "Kile",
        //Jon's machine
		//files: ["home/jmpierc2/cs473/hw4.tex", "home/jmpierc2/cs473/hw4.tex"]
        //Andrew's machine
		files: ["~/maude/maude-psl/masters/description.tex",
        "~/maude/maude-psl/masters/thesis.tex"]
	},
	{
		program: "Terminal",
		files: []
	},
	{
		program: "Evince",
		files: ["~/maude/maude-psl/masters/thesis.pdf",
        "~/maude/maude-npa-v2_0/maude-npa-manual-v2_0.pdf"]
	}
];
// END SAMPLE DATA

// generateShellScript
// input (Object): pad - A pad in the format of the sample one above
// output (String): A shell script in the format defined by the google doc
// author: Andrew
var generateShellScript = function(pad) {
    scriptText = ""
    var programName;
    var programFiles;
    var fileString;
    for (var i=0; i < pad.contents.length; i++){
        programName = pad.contents[i].program;
        programFiles = pad.contents[i].files;
        var programInfo = getProgramInfo(programName, config);
        fileString = "";
        for (var j=0; j < programFiles.length; j++){
            fileString += ' "' +  programFiles[j] + '"';
        }
        scriptText += "( { " + programInfo.system_name + fileString + " ; } ) &\n";
    }
    return scriptText
};
//console.log(generateShellScript(pad))

// insertIntoPad
// Inserts every element of the delta into the pad, keeping track of paths
// that appear in both pad and delta, and are associated with the same program.
// input (Object): pad - What we are inserting to. A pad like the one above.
// input (Array): delta - Delta array (stuff we're adding), like the one above
// output (Array): Array of paths found in both pad and delta that are
// associated with the same program.
// author: Lorraine
var insertIntoPad = function(pad, delta) {
	var retArr = [];
	//var retCtr = 0;
	//for each program in delta
	for(var i = 0;i<delta.length;i++){
		//see if it is already in pad contents
		var hasProg = false;
		for(var j=0;j<pad.contents.length;j++){
			//if yes then
			if(pad.contents[j].program == delta[i].program){
				hasProg = true;
				//if there are files under that prog in delta
				if(0 != delta[i].files.length){
				  var arrEntry = new Object();
				  arrEntry.program = delta[i].program;
				  arrEntry.files = [];
				  var hasDuplicateFile = false;
				  //then for each of those files
				   for(var k = 0; k<delta[i].files.length;k++){
					var hasFile = false;
					//if in pad add to duplicates array entry
					for(var l = 0; l<pad.contents[j].files.length;l++){
					   if(pad.contents[j].files[l] == delta[i].files[k]){
						arrEntry.files.push(delta[i].files[k]);
						hasFile = true;
						hasDuplicateFile = true;
					   }
					}
					//else add to pad contents
					if(!hasFile){
						pad.contents[j].files.push(delta[i].files[k]);
					}
				   }
				   if(hasDuplicateFile){
					retArr.push(arrEntry);
				   }
				}
				//else add to duplicates
				else{
					retArr.push(delta[i]);
				}
			}
		}
		//if not in pad then add everything
		if(!hasProg){
			pad.contents.push(delta[i]);
		}

	}
	return retArr;
};

// getDefaultPrograms
// Given an array of paths, create a delta object like the example one above.
// Match each path to the appropriate default program from config.json.
// author: John
var getDefaultPrograms = function(paths, config) {

};

// getAlternativePrograms
// Depends on config.json!
// This function should have a way to detect URLs
// input (Array): A array of file/url paths
// output: A list of programs (array of display-name strings) that supports
// every filetype in the list.
// author: John
var getAlternativePrograms = function(paths, config) {

};

// switchToAlternative
// Switches the program associated with certain paths.
// Given a pad as input and a delta , adds the program/file pairings specified
// in delta to pad. Like insertIntoPad, this should return the list of
// paths that appear in both pad and delta and are associated with the same
// program.
// // author: Lorraine
var switchToAlternative = function(pad, delta) {

};

// getConciseProgramList
// Returns an array mapping program display names to the number of files
// associated with said program.
// input (Object): pad - A pad like the example one above
// output (Object array): Array of {name: "program display name", count: int}
// author: Andrew
var getConciseProgramList = function(pad) {
    var array = [];
    for (var i = 0; i < pad.contents.length; i++){
        array.push({name: pad.contents[i].program,
                    count: pad.contents[i].files.length});
    }
    return array
};

/*
var array = getConciseProgramList(pad);
for(var i = 0; i < array.length; i++)
{
    console.log(array[i]);
}
*/

// getStandalonePrograms
// Searches the config object, returns an array of all entries that are standalone programs
// author: Lorraine
var getStandalonePrograms = function(config) {
	var progs = config.programs;
	var ctr = 0;
	var retArr = [];
	for(var i = 0; i<progs.length;i++){
		if(true == progs[i].standalone){
			//add this program to the return array
			retArr[ctr] = progs[i];
			ctr++;
		}
	}
	return retArr;

};

// Given a program name string, returns the corresponding entry from the config.
// author: John
// Yes, I wrote this for you. Don't feel bad. -Jonathan
var getProgramInfo = function(name, config){
	for(var i = 0; i < config.programs.length; i++) {
		if(config.programs[i].display_name.toLowerCase() === name.toLowerCase()) {
			return config.programs[i];
		}
	}
};

//I stole this regex without shame from one searls at:
//https://gist.github.com/searls/1033143
//which is a Javascript form of the regex discussed by one
//John Gruber at:
//http://daringfireball.net/2010/07/improved_regex_for_matching_urls
var urlRegExp = /^((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))$/i;
//...The Internet is awesome.
//returns true if input (a string) is a valid URL.
// author: Andrew
var validateURL = function(input){
    return urlRegExp.test(input)
};

//console.log(validateURL("http://stackoverflow.com/questions/6298566/regex-match-whole-string"));
<commit_msg>Added file to default and alternatives mapping functions.<commit_after>// SAMPLE DATA (Can be used for testing!)
// A sample pad
var pad = {
	name: 'Sample Pad',
	color: 'red',
	contents: [
	{
		program: "Firefox",
		files: ["http://www.illinois.edu", "http://www.cnn.com", "http://www.piazza.com"]
	},{
		program: "Evince",
        //Jon's machine
		//files: ["home/jmpierc2/cs233/lab7.pdf", "home/jmpierc2/cs233/lab8.pdf"]
        //Andrew's machine
        files: ["~/research/functional_logic/Curry_report.pdf",
            "~/research/functional_logic/Hanus_Multi-paradigm_Declarative_Languages.pdf"]
	},{
		program: "Calculator",
		files: []
	},{
		program: "gVim",
        //Jon's machine
		//files: ["home/jmpierc2/lilypad/frontend/frontend.js", "home/jmpierc2/lilypad/frontend/lilylibrary.js", "home/jmpierc2/lilypad/backend/backend.py", "home/jmpierc2/lilypad/backend/config.json"]
        //Andrew's machine
		files: ["~/classes/cs465/LilyPad/frontend/frontend.js", "~/classes/cs465/LilyPad/frontend/lilylibrary.js", "~/classes/cs465/LilyPad/backend/backend.py", "~/classes/cs465/LilyPad/backend/config.json"]
	}
	]
};
// A pad delta. Essentially a "contents" array like the one from above.
var paddelta = [
	{
		program: "Firefox",
		files: ["http://www.cnn.com", "http://www.twitter.com", "http://www.facebook.com"]
	},
	{
		program: "Kile",
        //Jon's machine
		//files: ["home/jmpierc2/cs473/hw4.tex", "home/jmpierc2/cs473/hw4.tex"]
        //Andrew's machine
		files: ["~/maude/maude-psl/masters/description.tex",
        "~/maude/maude-psl/masters/thesis.tex"]
	},
	{
		program: "Terminal",
		files: []
	},
	{
		program: "Evince",
		files: ["~/maude/maude-psl/masters/thesis.pdf",
        "~/maude/maude-npa-v2_0/maude-npa-manual-v2_0.pdf"]
	}
];
// END SAMPLE DATA

// generateShellScript
// input (Object): pad - A pad in the format of the sample one above
// output (String): A shell script in the format defined by the google doc
// author: Andrew
var generateShellScript = function(pad) {
    scriptText = ""
    var programName;
    var programFiles;
    var fileString;
    for (var i=0; i < pad.contents.length; i++){
        programName = pad.contents[i].program;
        programFiles = pad.contents[i].files;
        var programInfo = getProgramInfo(programName, config);
        fileString = "";
        for (var j=0; j < programFiles.length; j++){
            fileString += ' "' +  programFiles[j] + '"';
        }
        scriptText += "( { " + programInfo.system_name + fileString + " ; } ) &\n";
    }
    return scriptText;
};
//console.log(generateShellScript(pad))

// insertIntoPad
// Inserts every element of the delta into the pad, keeping track of paths
// that appear in both pad and delta, and are associated with the same program.
// input (Object): pad - What we are inserting to. A pad like the one above.
// input (Array): delta - Delta array (stuff we're adding), like the one above
// output (Array): Array of paths found in both pad and delta that are
// associated with the same program.
// author: Lorraine
var insertIntoPad = function(pad, delta) {
	var retArr = [];
	//var retCtr = 0;
	//for each program in delta
	for(var i = 0;i<delta.length;i++){
		//see if it is already in pad contents
		var hasProg = false;
		for(var j=0;j<pad.contents.length;j++){
			//if yes then
			if(pad.contents[j].program == delta[i].program){
				hasProg = true;
				//if there are files under that prog in delta
				if(0 != delta[i].files.length){
				  var arrEntry = new Object();
				  arrEntry.program = delta[i].program;
				  arrEntry.files = [];
				  var hasDuplicateFile = false;
				  //then for each of those files
				   for(var k = 0; k<delta[i].files.length;k++){
					var hasFile = false;
					//if in pad add to duplicates array entry
					for(var l = 0; l<pad.contents[j].files.length;l++){
					   if(pad.contents[j].files[l] == delta[i].files[k]){
						arrEntry.files.push(delta[i].files[k]);
						hasFile = true;
						hasDuplicateFile = true;
					   }
					}
					//else add to pad contents
					if(!hasFile){
						pad.contents[j].files.push(delta[i].files[k]);
					}
				   }
				   if(hasDuplicateFile){
					retArr.push(arrEntry);
				   }
				}
				//else add to duplicates
				else{
					retArr.push(delta[i]);
				}
			}
		}
		//if not in pad then add everything
		if(!hasProg){
			pad.contents.push(delta[i]);
		}

	}
	return retArr;
};

var getFileType = function(path){
	var dotIndex = path.lastIndexOf(".");
	return path.substring(dotIndex + 1);
};

var getDefaultProgram = function(fileType, config) {
	var defaults = config.defaults;
	for(i = 0; i < defaults.length; i++) {
		if(fileType === defaults[i].type) {
			return defaults[i].program;
		}
	}
	return null;
};

var createDeltaArr = function(programToPaths) {
	var deltaArr = [];
	for(programName in programToPaths) {
		paths = programToPaths[programName];
		delta = {program: programName, files: paths};
		deltaArr.push(delta);
	}
	return deltaArr;
};

var isValidURL = function(str) {
	var regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
	return regexp.test(str);
}

// getDefaultPrograms
// Given an array of paths, create a delta object like the example one above.
// Match each path to the appropriate default program from config.json.
// author: John
var getDefaultPrograms = function(paths, config) {

	var programToPaths = {};
	var i = 0;
	for(; i < paths.length; i++) {
		var path = paths[i];
		var defaultProgram = "";
		if(isValidURL(path)) {
			defaultProgram = "Firefox"; //default web browser???
		} else {
			var fileType = getFileType(path);
			defaultProgram = getDefaultProgram(fileType, config);
			if(defaultProgram === null) {
				continue; // log error?
			}
		}
		if(!(defaultProgram in programToPaths)) {
			programToPaths[defaultProgram] = [path];
		} else {
			programToPaths[defaultProgram].push(path)
		}
	}
	return createDeltaArr(programToPaths);
};

// getAlternativePrograms
// Depends on config.json!
// This function should have a way to detect URLs
// input (Array): A array of file/url paths
// output: A list of programs (array of display-name strings) that supports
// every filetype in the list.
// author: John
var getAlternativePrograms = function(paths, config) {
	var i = 0;
	var altProgramsList = [];
	for(; i < paths.length; i++) {
		var altPrograms = [];
		var path = paths[i];
		var type = ""
		if(isValidURL(path)) {
			type = "url";
		} else {
			type = getFileType(path);
		}
		var programs = config.programs;
		var j = 0;
		for(;j < programs.length; j++) {
			var program = programs[j];
			if($.inArray(type, program.types) !== -1){
				altPrograms.push(program.display_name);
			}
		}
		altProgramsList.push(altPrograms);
	}
	return altProgramsList;
};

// switchToAlternative
// Switches the program associated with certain paths.
// Given a pad as input and a delta , adds the program/file pairings specified
// in delta to pad. Like insertIntoPad, this should return the list of
// paths that appear in both pad and delta and are associated with the same
// program.
// // author: Lorraine
var switchToAlternative = function(pad, delta) {

};

// getConciseProgramList
// Returns an array mapping program display names to the number of files
// associated with said program.
// input (Object): pad - A pad like the example one above
// output (Object array): Array of {name: "program display name", count: int}
// author: Andrew
var getConciseProgramList = function(pad) {
    var array = [];
    for (var i = 0; i < pad.contents.length; i++){
        array.push({name: pad.contents[i].program,
                    count: pad.contents[i].files.length});
    }
    return array
};

/*
var array = getConciseProgramList(pad);
for(var i = 0; i < array.length; i++)
{
    console.log(array[i]);
}
*/

// getStandalonePrograms
// Searches the config object, returns an array of all entries that are standalone programs
// author: Lorraine
var getStandalonePrograms = function(config) {
	var progs = config.programs;
	var ctr = 0;
	var retArr = [];
	for(var i = 0; i<progs.length;i++){
		if(true == progs[i].standalone){
			//add this program to the return array
			retArr[ctr] = progs[i];
			ctr++;
		}
	}
	return retArr;

};

// Given a program name string, returns the corresponding entry from the config.
// author: John
// Yes, I wrote this for you. Don't feel bad. -Jonathan
var getProgramInfo = function(name, config){
	for(var i = 0; i < config.programs.length; i++) {
		if(config.programs[i].display_name.toLowerCase() === name.toLowerCase()) {
			return config.programs[i];
		}
	}
};

//I stole this regex without shame from one searls at:
//https://gist.github.com/searls/1033143
//which is a Javascript form of the regex discussed by one
//John Gruber at:
//http://daringfireball.net/2010/07/improved_regex_for_matching_urls
var urlRegExp = /^((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))$/i;
//...The Internet is awesome.
//returns true if input (a string) is a valid URL.
// author: Andrew
var validateURL = function(input){
    return urlRegExp.test(input)
};

//console.log(validateURL("http://stackoverflow.com/questions/6298566/regex-match-whole-string"));
<|endoftext|>


<commit_before>// @remove-on-eject-begin
/**
 * Copyright (c) 2014-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
// @remove-on-eject-end
'use strict';

const path = require('path');

// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html

module.exports = {
  process(src, filename) {
    const assetFilename = JSON.stringify(path.basename(filename));

    if (filename.match(/\.svg$/)) {
      return `module.exports = {
        __esModule: true,
        default: ${assetFilename},
        ReactComponent: () => ${assetFilename},
      };`;
    }

    return `module.exports = ${assetFilename};`;
  },
};
<commit_msg>Format SVG React snapshots as <svg> tag with props (#5196)<commit_after>// @remove-on-eject-begin
/**
 * Copyright (c) 2014-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */
// @remove-on-eject-end
'use strict';

const path = require('path');

// This is a custom Jest transformer turning file imports into filenames.
// http://facebook.github.io/jest/docs/en/webpack.html

module.exports = {
  process(src, filename) {
    const assetFilename = JSON.stringify(path.basename(filename));

    if (filename.match(/\.svg$/)) {
      return `module.exports = {
        __esModule: true,
        default: ${assetFilename},
        ReactComponent: (props) => ({
          $$typeof: Symbol.for('react.element'),
          type: 'svg',
          ref: null,
          key: null,
          props: Object.assign({}, props, {
            children: ${assetFilename}
          })
        }),
      };`;
    }

    return `module.exports = ${assetFilename};`;
  },
};
<|endoftext|>


<commit_before>import { editor } from './editor';
import { jumpToLine } from './codemirror/tools';
import { isEmptyString, getQueryStringObject, serializeToQueryString } from '../tools/helpers';

const HIGHLIGHT_CLASS = 'editor-highlight';

let anchorLine;
let targetLine;
let manuallyHighlighted = false;

editor.on('gutterClick', function (cm, line, gutter, event) {
    // Do work when the click occurs for the left (or main) mouse button only
    if (event.button !== 0) {
        return;
    }

    // Do work only on the line number target element
    if (!event.target.classList.contains('CodeMirror-linenumber')) {
        return;
    }

    // The `line` parameter is a 0-indexed line number.

    // The meta (command or control keys) will allow highlighting of
    // non-sequential lines.
    if (event.metaKey === true) {
        const didHighlight = toggleHighlightLine(cm.getDoc(), line);
        anchorLine = didHighlight ? line : undefined;

        // Reset
        targetLine = undefined;
    }
    // Shift keys will allow highlighting of multiple lines.
    else if (event.shiftKey === true && anchorLine !== undefined) {
        // Clears a previous range if it exists
        if (targetLine) {
            unhighlightLines(anchorLine, targetLine);
        }

        // Then highlight the desired range
        highlightLines(anchorLine, line);

        // Remember state of how this happened
        targetLine = line;
        manuallyHighlighted = true;
    }
    // If shift key is not pressed or there is not a previously selected line
    // (which you need to do the whole range) then select one line.
    else {
        // Clear all existing highlights first
        unhighlightAll();

        // If the clicked line is the same as the one before, reset
        if (line === anchorLine) {
            anchorLine = undefined;
        }
        // Otherwise, highlight that one line
        else {
            highlightLine(cm.getDoc(), line);
            anchorLine = line;
            manuallyHighlighted = true;
        }

        // Reset
        targetLine = undefined;
    }

    // Update the query string
    updateLinesQueryString();
});

// Editor operations, such as cut, paste, delete, or inserts, can mutate
// highlighted lines. This will make sure the query string remains updated.
editor.on('changes', function (cm, changes) {
    updateLinesQueryString();
});

/**
 * Highlights a given line in the document.
 *
 * @param {CodeMirror Doc} doc - The document to add a highlight to.
 * @param {Number} line - The line number to add a highlight to.
 */
function highlightLine (doc, line) {
    doc.addLineClass(line, 'gutter', HIGHLIGHT_CLASS);
    doc.addLineClass(line, 'background', HIGHLIGHT_CLASS);
}

/**
 * Removes highlight a given line in the document.
 *
 * @param {CodeMirror Doc} doc - The document to remove a highlight from.
 * @param {Number} line - The line number to remove a highlight from.
 */
function unhighlightLine (doc, line) {
    doc.removeLineClass(line, 'gutter', HIGHLIGHT_CLASS);
    doc.removeLineClass(line, 'background', HIGHLIGHT_CLASS);
}

/**
 * Toggles the highlight on a given line in the document.
 *
 * @param {CodeMirror Doc} doc - The document the line is in.
 * @param {Number} line - The line number to toggle highlight on.
 * @returns {Boolean} - a boolean value corresponding to the action
 *          that was performed. Returns `true` if a line was highlighted,
 *          and `false` if a line was unhighlighted.
 */
function toggleHighlightLine (doc, line) {
    const lineInfo = editor.lineInfo(line);
    if (lineInfo.bgClass === HIGHLIGHT_CLASS) {
        unhighlightLine(doc, line);
        return false;
    }
    else {
        highlightLine(doc, line);
        return true;
    }
}

/**
 * Highlights a line or a range of lines by applying a highlight class.
 * Accepts arguments in any order and it will still loop in the
 * correct direction.
 *
 * @param {Number} from - Required. The line number to start
 *          highlighting from. Lines are zero-indexed.
 * @param {Number} to - Required. The line number to end
 *          highlighting on. Lines are zero-indexed.
 */
function highlightLines (startLine, endLine) {
    const doc = editor.getDoc();

    // Swap arguments if startLine is higher than endLine
    if (startLine > endLine) {
        [startLine, endLine] = [endLine, startLine];
    }

    for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
        highlightLine(doc, currentLine);
    }
}

/**
 * Unhighlights all lines between a range of lines. Accepts
 * arguments in any order and it will still loop in the correct direction.
 *
 * @param {Number} from - Required. The line number to start
 *          unhighlighting from. Lines are zero-indexed.
 * @param {Number} to - Required. The line number to end
 *          unhighlighting on. Lines are zero-indexed.
 */
function unhighlightLines (startLine, endLine) {
    const doc = editor.getDoc();

    // Swap arguments if startLine is higher than endLine
    if (startLine > endLine) {
        [startLine, endLine] = [endLine, startLine];
    }

    for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
        unhighlightLine(doc, currentLine);
    }
}

/**
 * Highlights lines, given a comma delimited set of line numbers or ranges of
 * line numbers. Expects a string that would have been formed with
 * getLineNumberString(). The following are valid strings:
 *
 *      '6'
 *      '6-10'
 *      '6-10,12-30'
 *      '6-10,12-30,45,60-12'
 *
 * Note that the ranges are assumed to be in sequential order, and line numbers
 * start from 1 instead of 0. The first linenumber or range will get jumped to.
 *
 * @param {String} lines - Required. A human-readable sequence of lines and
 *          line ranges to highlight.
 */
export function highlightRanges (lines) {
    const ranges = lines.split(',');

    for (let i = 0, j = ranges.length; i < j; i++) {
        const lines = ranges[i].split('-');

        // Lines are zero-indexed in CodeMirror, so subtract 1 from it.
        // Just in case, the return value is clamped to a minimum value of 0.
        const startLine = Math.max(Number(lines[0]) - 1, 0);
        const endLine = Math.max(Number(lines[1]) - 1, 0);

        // Only jump to the first range given.
        if (i === 0) {
            jumpToLine(editor, startLine);
        }

        highlightLines(startLine, endLine);
    }
}

/**
 * Given a node, find all the lines that are part of that entire block, and then
 * applies a highlight class to each of those lines.
 * TODO: This can still be pretty buggy because `stateAfter` is still not
 * guaranteed.
 *
 * @param {Object} node - YAML-Tangram node object
 */
export function highlightBlock (node) {
    const doc = editor.getDoc();

    // Scroll the top of the block into view. Do this first so that
    // CodeMirror will parse the lines in this viewport. This is necessary
    // for the `stateAfter.keyLevel` to be available.
    jumpToLine(editor, node.range.from.line);

    // Determine the range to highlight from.
    const blockLine = node.range.from.line;
    // This can still sometimes fail, for unknown reasons.
    const blockLevel = doc.getLineHandle(blockLine).stateAfter.keyLevel;
    let toLine = blockLine;
    let thisLevel = blockLevel;
    do {
        const nextLineHandle = doc.getLineHandle(toLine + 1);
        if (nextLineHandle !== undefined && !isEmptyString(nextLineHandle.text)) {
            // The nextLineHandle might not have a stateAfter, so wrap in try {}
            try {
                thisLevel = nextLineHandle.stateAfter.keyLevel;
            }
            catch (err) {
                break;
            }

            if (thisLevel > blockLevel) {
                toLine++;
            }
        }
        // Break if no next line. Required to prevent infinite loops.
        else {
            break;
        }
    } while (thisLevel > blockLevel);

    // First, remove all existing instances of the highlight class.
    // Then highlight the block's range and update query string.
    unhighlightAll();
    highlightLines(node.range.from.line, toLine);
    updateLinesQueryString();

    // Reset
    anchorLine = undefined;
    targetLine = undefined;
    manuallyHighlighted = false;
}

/**
 * Removes highlights from all lines in the document.
 *
 * @param {Boolean} defer - Optional. Default is false. If `true`, then this
 *          function does not unhighlight any lines if the current highlighting
 *          was created by a user clicking on the gutters.
 */
export function unhighlightAll ({ defer = false } = {}) {
    if (defer === true) {
        return;
    }

    const doc = editor.getDoc();

    for (let i = 0, j = doc.lineCount(); i <= j; i++) {
        unhighlightLine(doc, i);
    }
}

/**
 * Gets a string representation of all highlighted lines in the editor.
 * This looks at the editor itself, so it's guaranteed to be the current state
 * of the editor.
 *
 * @returns {String} linenumbers - in the format of '2-5,10,18-20', as
 *          returned by getLineNumberString() function
 */
export function getAllHighlightedLines () {
    const lineNumbers = [];

    for (let i = 0, j = editor.getDoc().lineCount(); i < j; i++) {
        const lineInfo = editor.lineInfo(i);
        if (lineInfo.bgClass === HIGHLIGHT_CLASS) {
            lineNumbers.push(i);
        }
    }

    const string = getLineNumberString(lineNumbers);

    return string;
}

/**
 * Updates the ?lines= query string to the currently highlighted lines in the
 * document.
 *
 */
export function updateLinesQueryString () {
    const locationPrefix = window.location.pathname;
    const queryObj = getQueryStringObject();
    const allHighlightedLines = getAllHighlightedLines();

    queryObj.lines = allHighlightedLines !== '' ? allHighlightedLines : null;

    const queryString = serializeToQueryString(queryObj);
    window.history.replaceState({}, null, locationPrefix + queryString + window.location.hash);
}

/**
 * Given an array of sorted line numbers, convert it to a sequence of ranges,
 * for example:
 *
 * [2, 3, 4, 5, 10, 18, 19, 20]  =>  ['2-5', '10', '18-20']
 * [1, 2, 3, 5, 7, 9, 10, 11, 12, 14]  =>  ['1-3', '5', '7', '9-12', '14']
 * [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] => ['1-10']
 *
 */
function getLineNumberRanges (array) {
    const ranges = [];
    let start, end;

    for (let i = 0, j = array.length; i < j; i++) {
        start = array[i];
        end = start;
        while (array[i + 1] - array[i] === 1) {
            end = array[i + 1]; // increment the index if the numbers sequential
            i++;
        }
        ranges.push(start === end ? start.toString() : start + '-' + end);
    }

    return ranges;
}

/**
 * Given an array of sorted line numbers, convert it to a range and flatten
 * it to a single string. For example:
 *
 * e.g. [2, 3, 4, 5, 10, 18, 19, 20]  =>  '2-5,10,18-20'
 * This is ideal for storing in URL strings.
 *
 */
function getLineNumberString (array) {
    // Line number arrays are zero-indexed, so before converting to a string,
    // we increment each number by one.
    array = array.map(number => {
        return number + 1;
    });
    const ranges = getLineNumberRanges(array);
    return ranges.join(',');
}
<commit_msg>Fix a bug where active lines may not be included in highlighted lines check<commit_after>import { editor } from './editor';
import { jumpToLine } from './codemirror/tools';
import { isEmptyString, getQueryStringObject, serializeToQueryString } from '../tools/helpers';

const HIGHLIGHT_CLASS = 'editor-highlight';

let anchorLine;
let targetLine;
let manuallyHighlighted = false;

editor.on('gutterClick', function (cm, line, gutter, event) {
    // Do work when the click occurs for the left (or main) mouse button only
    if (event.button !== 0) {
        return;
    }

    // Do work only on the line number target element
    if (!event.target.classList.contains('CodeMirror-linenumber')) {
        return;
    }

    // The `line` parameter is a 0-indexed line number.

    // The meta (command or control keys) will allow highlighting of
    // non-sequential lines.
    if (event.metaKey === true) {
        const didHighlight = toggleHighlightLine(cm.getDoc(), line);
        anchorLine = didHighlight ? line : undefined;

        // Reset
        targetLine = undefined;
    }
    // Shift keys will allow highlighting of multiple lines.
    else if (event.shiftKey === true && anchorLine !== undefined) {
        // Clears a previous range if it exists
        if (targetLine) {
            unhighlightLines(anchorLine, targetLine);
        }

        // Then highlight the desired range
        highlightLines(anchorLine, line);

        // Remember state of how this happened
        targetLine = line;
        manuallyHighlighted = true;
    }
    // If shift key is not pressed or there is not a previously selected line
    // (which you need to do the whole range) then select one line.
    else {
        // Clear all existing highlights first
        unhighlightAll();

        // If the clicked line is the same as the one before, reset
        if (line === anchorLine) {
            anchorLine = undefined;
        }
        // Otherwise, highlight that one line
        else {
            highlightLine(cm.getDoc(), line);
            anchorLine = line;
            manuallyHighlighted = true;
        }

        // Reset
        targetLine = undefined;
    }

    // Update the query string
    updateLinesQueryString();
});

// Editor operations, such as cut, paste, delete, or inserts, can mutate
// highlighted lines. This will make sure the query string remains updated.
editor.on('changes', function (cm, changes) {
    updateLinesQueryString();
});

/**
 * Highlights a given line in the document.
 *
 * @param {CodeMirror Doc} doc - The document to add a highlight to.
 * @param {Number} line - The line number to add a highlight to.
 */
function highlightLine (doc, line) {
    doc.addLineClass(line, 'gutter', HIGHLIGHT_CLASS);
    doc.addLineClass(line, 'background', HIGHLIGHT_CLASS);
}

/**
 * Removes highlight a given line in the document.
 *
 * @param {CodeMirror Doc} doc - The document to remove a highlight from.
 * @param {Number} line - The line number to remove a highlight from.
 */
function unhighlightLine (doc, line) {
    doc.removeLineClass(line, 'gutter', HIGHLIGHT_CLASS);
    doc.removeLineClass(line, 'background', HIGHLIGHT_CLASS);
}

/**
 * Toggles the highlight on a given line in the document.
 *
 * @param {CodeMirror Doc} doc - The document the line is in.
 * @param {Number} line - The line number to toggle highlight on.
 * @returns {Boolean} - a boolean value corresponding to the action
 *          that was performed. Returns `true` if a line was highlighted,
 *          and `false` if a line was unhighlighted.
 */
function toggleHighlightLine (doc, line) {
    if (isLineHighlighted(line)) {
        unhighlightLine(doc, line);
        return false;
    }
    else {
        highlightLine(doc, line);
        return true;
    }
}

/**
 * Highlights a line or a range of lines by applying a highlight class.
 * Accepts arguments in any order and it will still loop in the
 * correct direction.
 *
 * @param {Number} from - Required. The line number to start
 *          highlighting from. Lines are zero-indexed.
 * @param {Number} to - Required. The line number to end
 *          highlighting on. Lines are zero-indexed.
 */
function highlightLines (startLine, endLine) {
    const doc = editor.getDoc();

    // Swap arguments if startLine is higher than endLine
    if (startLine > endLine) {
        [startLine, endLine] = [endLine, startLine];
    }

    for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
        highlightLine(doc, currentLine);
    }
}

/**
 * Unhighlights all lines between a range of lines. Accepts
 * arguments in any order and it will still loop in the correct direction.
 *
 * @param {Number} from - Required. The line number to start
 *          unhighlighting from. Lines are zero-indexed.
 * @param {Number} to - Required. The line number to end
 *          unhighlighting on. Lines are zero-indexed.
 */
function unhighlightLines (startLine, endLine) {
    const doc = editor.getDoc();

    // Swap arguments if startLine is higher than endLine
    if (startLine > endLine) {
        [startLine, endLine] = [endLine, startLine];
    }

    for (let currentLine = startLine; currentLine <= endLine; currentLine++) {
        unhighlightLine(doc, currentLine);
    }
}

/**
 * Highlights lines, given a comma delimited set of line numbers or ranges of
 * line numbers. Expects a string that would have been formed with
 * getLineNumberString(). The following are valid strings:
 *
 *      '6'
 *      '6-10'
 *      '6-10,12-30'
 *      '6-10,12-30,45,60-12'
 *
 * Note that the ranges are assumed to be in sequential order, and line numbers
 * start from 1 instead of 0. The first linenumber or range will get jumped to.
 *
 * @param {String} lines - Required. A human-readable sequence of lines and
 *          line ranges to highlight.
 */
export function highlightRanges (lines) {
    const ranges = lines.split(',');

    for (let i = 0, j = ranges.length; i < j; i++) {
        const lines = ranges[i].split('-');

        // Lines are zero-indexed in CodeMirror, so subtract 1 from it.
        // Just in case, the return value is clamped to a minimum value of 0.
        const startLine = Math.max(Number(lines[0]) - 1, 0);
        const endLine = Math.max(Number(lines[1]) - 1, 0);

        // Only jump to the first range given.
        if (i === 0) {
            jumpToLine(editor, startLine);
        }

        highlightLines(startLine, endLine);
    }
}

/**
 * Given a node, find all the lines that are part of that entire block, and then
 * applies a highlight class to each of those lines.
 * TODO: This can still be pretty buggy because `stateAfter` is still not
 * guaranteed.
 *
 * @param {Object} node - YAML-Tangram node object
 */
export function highlightBlock (node) {
    const doc = editor.getDoc();

    // Scroll the top of the block into view. Do this first so that
    // CodeMirror will parse the lines in this viewport. This is necessary
    // for the `stateAfter.keyLevel` to be available.
    jumpToLine(editor, node.range.from.line);

    // Determine the range to highlight from.
    const blockLine = node.range.from.line;
    // This can still sometimes fail, for unknown reasons.
    const blockLevel = doc.getLineHandle(blockLine).stateAfter.keyLevel;
    let toLine = blockLine;
    let thisLevel = blockLevel;
    do {
        const nextLineHandle = doc.getLineHandle(toLine + 1);
        if (nextLineHandle !== undefined && !isEmptyString(nextLineHandle.text)) {
            // The nextLineHandle might not have a stateAfter, so wrap in try {}
            try {
                thisLevel = nextLineHandle.stateAfter.keyLevel;
            }
            catch (err) {
                break;
            }

            if (thisLevel > blockLevel) {
                toLine++;
            }
        }
        // Break if no next line. Required to prevent infinite loops.
        else {
            break;
        }
    } while (thisLevel > blockLevel);

    // First, remove all existing instances of the highlight class.
    // Then highlight the block's range and update query string.
    unhighlightAll();
    highlightLines(node.range.from.line, toLine);
    updateLinesQueryString();

    // Reset
    anchorLine = undefined;
    targetLine = undefined;
    manuallyHighlighted = false;
}

/**
 * Removes highlights from all lines in the document.
 *
 * @param {Boolean} defer - Optional. Default is false. If `true`, then this
 *          function does not unhighlight any lines if the current highlighting
 *          was created by a user clicking on the gutters.
 */
export function unhighlightAll ({ defer = false } = {}) {
    if (defer === true) {
        return;
    }

    const doc = editor.getDoc();

    for (let i = 0, j = doc.lineCount(); i <= j; i++) {
        unhighlightLine(doc, i);
    }
}

/**
 * Returns true if a line is highlighted. It does this by checking whether the
 * line number has the highlight class attached to it. Note that other background
 * classes may exist (e.g. for active lines).
 *
 * @param {Number} line - the line number to check.
 * @returns {Boolean}
 */
function isLineHighlighted (line) {
    const lineInfo = editor.lineInfo(line);

    // .bgClass may be null
    if (!lineInfo.bgClass) {
        return false;
    }

    return (lineInfo.bgClass.indexOf(HIGHLIGHT_CLASS) >= 0);
}

/**
 * Gets a string representation of all highlighted lines in the editor.
 * This looks at the editor itself, so it's guaranteed to be the current state
 * of the editor.
 *
 * @returns {String} linenumbers - in the format of '2-5,10,18-20', as
 *          returned by getLineNumberString() function
 */
function getAllHighlightedLines () {
    const lineNumbers = [];

    for (let i = 0, j = editor.getDoc().lineCount(); i < j; i++) {
        if (isLineHighlighted(i)) {
            lineNumbers.push(i);
        }
    }

    const string = getLineNumberString(lineNumbers);

    return string;
}

/**
 * Updates the ?lines= query string to the currently highlighted lines in the
 * document.
 *
 */
export function updateLinesQueryString () {
    const locationPrefix = window.location.pathname;
    const queryObj = getQueryStringObject();
    const allHighlightedLines = getAllHighlightedLines();

    queryObj.lines = allHighlightedLines !== '' ? allHighlightedLines : null;

    const queryString = serializeToQueryString(queryObj);
    window.history.replaceState({}, null, locationPrefix + queryString + window.location.hash);
}

/**
 * Given an array of sorted line numbers, convert it to a sequence of ranges,
 * for example:
 *
 * [2, 3, 4, 5, 10, 18, 19, 20]  =>  ['2-5', '10', '18-20']
 * [1, 2, 3, 5, 7, 9, 10, 11, 12, 14]  =>  ['1-3', '5', '7', '9-12', '14']
 * [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] => ['1-10']
 *
 */
function getLineNumberRanges (array) {
    const ranges = [];
    let start, end;

    for (let i = 0, j = array.length; i < j; i++) {
        start = array[i];
        end = start;
        while (array[i + 1] - array[i] === 1) {
            end = array[i + 1]; // increment the index if the numbers sequential
            i++;
        }
        ranges.push(start === end ? start.toString() : start + '-' + end);
    }

    return ranges;
}

/**
 * Given an array of sorted line numbers, convert it to a range and flatten
 * it to a single string. For example:
 *
 * e.g. [2, 3, 4, 5, 10, 18, 19, 20]  =>  '2-5,10,18-20'
 * This is ideal for storing in URL strings.
 *
 */
function getLineNumberString (array) {
    // Line number arrays are zero-indexed, so before converting to a string,
    // we increment each number by one.
    array = array.map(number => {
        return number + 1;
    });
    const ranges = getLineNumberRanges(array);
    return ranges.join(',');
}
<|endoftext|>


<commit_before>
  /**
   *  Actions menu view, aka LayersPanel
   *
   *  - It needs at least visualization and user models.
   *    Globalerror to show connection or fetching errors.
   *
   *    var menu = new cdb.admin.LayersPanel({
   *      vis: vis_model,
   *      user: user_model,
   *      globalError: globalError_obj
   *    });
   */

  cdb.admin.LayersPanel = cdb.ui.common.TabPane.extend({

    className: 'table_panel',
    animation_time: 300,

    _MAX_LAYERS: 3,
    _MIN_LAYERS: 1,

    _TEXTS: {
      visualization: {
        msg:    _t('If you want to add multiple layers you need to create a visualization first.'),
        error:  _t('Error adding that table into the new visualization')
      },
      remove: {
        title:  _t('Delete this layer'),
        desc:   _t('You are about to delete this layer. Doing so won\'t remove your table, \
                only this visualization will be affected.'),
        ok:     _t('Delete layer')
      },
      cant_remove: {
        title:  _t('Delete this layer'),
        desc:   _t('You can\'t remove all layers from the visualization, it needs at least one to make it work.'),
        ok:     _t('Close')
      },
      error: {
        default: _t('Something went wrong, try again later')
      }
    },

    events: {
      'click .add_layer': '_addLayerDialog'
    },

    initialize: function() {
      this.layer_panels = []; // Layer panels added in the current visualization
      this.vis = this.options.vis;
      this.map = this.vis.map;
      this.globalError = this.options.globalError;

      // View bindings
      _.bindAll(this, 'sortLayers', 'manageLayers', '_checkResize');
      this.map.layers.bind('add',    this._newLayer, this);
      this.map.layers.bind('reset',  this._resetLayers, this);
      $(window).bind('resize', this._checkResize);
      this.add_related_model(this.map);
      this.bindSort(); // Bind sort movement

      this._addLayers(this.map.layers);

      // Setting several pane parameters
      this.isOpen = true;
      this.activeWorkView = 'table';

      cdb.ui.common.TabPane.prototype.initialize.call(this);
    },

    /* Layer panel functions */

    _resetLayers: function() {
      //clean all views
      this.removeTabs();
      // Empty layers array
      this.layer_panels = [];
      // Add again the layers
      this._addLayers(this.map.layers);
    },

    _addLayers: function(layers) {
      // Add 'add layer' button first
      this._addLayerAddButton();

      // Add layers
      var self = this;
      layers.each(function(l, pos) {
        self._addLayer(l, pos);
      });

      // Set default layer
      this._setDefaultLayer();
    },

    _newLayer: function(layer) {
      this._addLayer(layer);
    },

    _addLayer: function(layer, pos) {
      var self = this;
      if (layer.get('type') == 'CartoDB') {

        // Check order and set it if it is undefined
        // this should be done in the model and test it
        //if (!layer.get('order')) {
          //layer.save('order', pos);
        //}

        console.log("add layer", layer.get('table_name'), layer.get('order'));
        var v = new cdb.admin.LayerPanelView({
          model: layer,
          vis: self.vis,
          user: self.options.user,
          globalError: self.options.globalError
        });

        v.bind('toggle',    self._toggle, self);
        v.bind('switchTo',  self._switchTo, self);
        v.bind('delete',    self._remove, self);

        self.addTab(layer.cid, v);
        v.setActiveWorkView(self.activeWorkView);

        self.layer_panels.push(v);

        // no pos -> new layer -> set as current
        if (!pos) {
          self._switchTo(v);
        }

        self._checkLayers();
      }
    },

    _removeLayer: function(cid) {
      var layer_view = this.getPane(cid);
      var self = this;

      this.layer_panels = _.filter(this.layer_panels, function(view) {
        if (view != layer_view) return view;
      });

      this.bind('tabEnabled', function(cid) {
        var view = self.getPane(cid);
        self.vis.save('active_layer_id', view.dataLayer.id);
        self.unbind('tabEnabled');
      });

      this.removeTab(cid);

      //this.sortLayers();
      this._checkLayers();
      this.manageLayers();
    },

    _setDefaultLayer: function() {
      var layer;
      var layer_id = this.vis.get('active_layer_id');

      // No layer_id, get last layer
      if (!layer_id) {
        var layer = _.last(this.layer_panels);
      }

      // Get layer from layer_id
      if (!layer) {
        for (var i in this.layer_panels) {
          var lyr = this.layer_panels[i];
          if (lyr.dataLayer.id == layer_id) {
            layer = lyr;
            break;
          }
        }
      }

      // Any layer? Switch to it
      if (layer) {
        this._switchTo(layer);
      }
    },

    // Check how many layers-view there are in the panel
    _checkLayers: function() {
      if (this.map.layers.getLayersByType('CartoDB') >= this._MAX_LAYERS) {
        this._hideLayerButton();
      } else {
        this._showLayerButton();
      }
    },

    /* Add layer functions */

    _addLayerAddButton: function() {
      this.$('.add_layer').remove();
      var template = this.getTemplate('table/views/add_layer');
      this.$el.prepend(template());
    },

    _showLayerButton: function() {
      this.$('section.add_layer').show();
    },

    _hideLayerButton: function() {
      this.$('section.add_layer').hide();
    },

    _addLayerDialog: function(e) {
      this.killEvent(e);
      var self = this;

      this.new_layer_dlg && this.new_layer_dlg.clean();
      this.new_layer_dlg = new cdb.admin.NewLayerDialog({
        ok: function(tableName) {

          if(!self.vis.isVisualization()) {
            self.create_vis_dialog && self.create_vis_dialog.clean();
            self.create_vis_dialog = new cdb.admin.CreateVizDialog({
              model: self.vis,
              msg: self._TEXTS.visualization.msg,
              onResponse: function(vis) {
                self.map.addCartodbLayerFromTable(tableName, {
                  success: function() {
                    window.location.href = "/viz/" + self.vis.get("id") + "/";
                  },
                  error: function() {
                    self.create_vis_dialog.onCreationError(null, self._TEXTS.visualization.error)
                  }
                });
              }
            });
            self.create_vis_dialog.appendToBody().open();
          } else {
            self.map.addCartodbLayerFromTable(tableName);
          }

        }
      })
      this.new_layer_dlg.appendToBody().open();
    },

    bindSort: function() {
      this.unbindSort();

      this.$el.sortable({
        axis:       "y",
        items:      "> .layer_panel",
        handle:     ".layer_info a.info",
        contaiment: this.$el,
        opacity:    0.5,
        update:       this.sortLayers
      });
    },

    unbindSort: function() {
      this.$el.sortable('destroy')
    },

    sortLayers: function() {
      var self = this;
      // set the new order in layer collection from layer view
      // positions
      var order = 1;
      this.$el.children().each(function(index, element) {
        var id = element.getAttribute('model-id');
        if(id) {
          var dataLayer = self.map.layers.get(id);
          if(dataLayer) {
            // use silent, save after all are sorted, see below
            dataLayer.set({ 'order': order }, { silent: true });
            order ++;
          }
        }
      })

      // layers don't need to be reset when they are sorted
      // and save the new layers order to the server
      this.map.layers.unbind('reset',  this._resetLayers, this);
      this.map.layers.saveLayers();
      this.map.layers.bind('reset',  this._resetLayers, this);

    },

    /* Magic layers */
    manageLayers: function() {
      // If there isn't any layer added
      if (this.map.layers.getLayersByType('CartoDB') == 0) return false;

      var $active_layer = this.activePane.$el;

      // Move rest of panels
      this.$el.find('.layer_panel').removeClass('active');

      // Add active class
      $active_layer.addClass('active');

      // Set rest of layer_panels to height 60px
      // Decide later about compress them
      // TODO: not use jquery
      this.$el.find('.layer_panel').not('.layer_panel.active').css({
        height: 66
      });

      // Get previous siblings and calculate space
      // TODO: not use jquery
      var pre_size = $active_layer.prevAll('.layer_panel').size();
      var t_s = (pre_size == 0) ? 0 : (pre_size * 53);

      // Get next siblings and calculate space
      // TODO: not use jquery
      var next_size = $active_layer.nextAll('.layer_panel').size();
      var b_s = (next_size == 0) ? 0 : (next_size * 53);

      // Set available space for the active layer_space
      // TODO: not use jquery
      var offset = (this.map.layers.getLayersByType('CartoDB') >= this._MAX_LAYERS) ? 15 : - 25;
      $active_layer.css({
        height: this.$el.height() - (t_s + b_s) + offset
      })
    },

    /* testing resize event */
    _checkResize: function(e) {
      if (this.resize) clearTimeout(this.resize);
      var self = this;
      this.resize = setTimeout(self.manageLayers, 100);
    },

    show: function(modName) {
      var width = 600,
          action = 'show';

      // Select the tab
      var mod = this.getActivePane();
      mod.tabs.activate(modName);
      mod.panels.active(modName);

      switch (modName) {
        case 'filters_mod':
        case 'wizards_mod':
        case 'infowindow_mod':
          width = 450;
          action = 'narrow';
          break;
        default:
      }

      // Show panel -> trigger
      cdb.god.trigger('panel_action', action);

      this.isOpen = true;
      this.$el.animate({
          width: width,
          right: 0
      }, this.animation_time, function() {
        cdb.god.trigger("end_" + action);
      });
    },

    hide: function(modName) {
      var panel_width = this.$el.width();

      // Hide the tab
      var mod = this.getActivePane();
      //mod.tabs.deactivate(modName);

      // Hide panel -> trigger
      cdb.god.trigger("panel_action", "hide");

      this.isOpen = false;
      this.$el.animate({
          right: 63 - panel_width
      }, this.animation_time);
    },

    _switchTo: function(l) {
      this.active(l.dataLayer.cid);
      if(this.vis.get('active_layer_id') != l.dataLayer.id) {
        this.vis.save('active_layer_id', l.dataLayer.id);
      }
      this.trigger('switch', l);
      // Check if panel width is correct
      // only if panel is opened
      if (this.isOpen) {
        var modName = l.panels.activeTab;
        this.show(modName);
      }
      this.manageLayers();
    },

    _toggle: function(modName) {
      // only hide if we click on active tab
      if (this.isOpen && modName == this.getActivePane().panels.activeTab) {
          this.hide(modName);
      } else {
          this.show(modName);
      }
    },

    // We need to review this method, it is not easy to
    // make operations when the view is cleaning/removing
    // For now the model destroy is happening here
    _remove: function(layer_view) {
      var cid = layer_view.dataLayer.cid;
      var self = this;
      var permitted = true;

      // Check if there is at least 1 layer
      // if not, don't let user do it
      if (this.map.layers.getLayersByType('CartoDB') <= this._MIN_LAYERS) {
        permitted = false;
      }

      this.remove_dlg && this.remove_dlg.clean();
      this.remove_dlg = new cdb.admin.BaseDialog({
        title: self._TEXTS[permitted ? 'remove' : 'cant_remove'].title,
        description: self._TEXTS[permitted ? 'remove' : 'cant_remove'].desc,
        template_name: 'common/views/confirm_dialog',
        clean_on_hide: true,
        enter_to_confirm: true,
        ok_button_classes: "right button grey",
        ok_title: self._TEXTS[permitted ? 'remove' : 'cant_remove'].ok,
        cancel_button_classes: "underline margin15 " + (permitted ? '' : 'hide'),
        modal_type: "confirmation",
        width: 500
      });

      if (permitted) {
        // If user confirms, app removes this panel view,
        // including the map layer
        this.remove_dlg.ok = function() {
          // Destroy layer model
          layer_view.model.destroy({
            success: function() {
              self._removeLayer(cid)
            },
            error: function(){
              self.globalError.showError(that._TEXTS.error.default, 'error', 3000);
            }
          });
        }
      }

      // Show it up
      this.remove_dlg
        .appendToBody()
        .open();
    },

    setActiveWorkView: function(workView) {
      this.hide();
      this.activeWorkView = workView;

      // Set active work view for all panes,
      // not only for the current one
      this.each(function(tab, pane){
        pane.setActiveWorkView(workView);
      })
    },

    clean: function() {
      $(window).unbind('resize', this._checkResize);
      if (this.resize) clearTimeout(this.resize);
      this.unbindSort();
      cdb.ui.common.TabPane.prototype.clean.call(this);
    }
  });
<commit_msg>removed console from actions menu<commit_after>
  /**
   *  Actions menu view, aka LayersPanel
   *
   *  - It needs at least visualization and user models.
   *    Globalerror to show connection or fetching errors.
   *
   *    var menu = new cdb.admin.LayersPanel({
   *      vis: vis_model,
   *      user: user_model,
   *      globalError: globalError_obj
   *    });
   */

  cdb.admin.LayersPanel = cdb.ui.common.TabPane.extend({

    className: 'table_panel',
    animation_time: 300,

    _MAX_LAYERS: 3,
    _MIN_LAYERS: 1,

    _TEXTS: {
      visualization: {
        msg:    _t('If you want to add multiple layers you need to create a visualization first.'),
        error:  _t('Error adding that table into the new visualization')
      },
      remove: {
        title:  _t('Delete this layer'),
        desc:   _t('You are about to delete this layer. Doing so won\'t remove your table, \
                only this visualization will be affected.'),
        ok:     _t('Delete layer')
      },
      cant_remove: {
        title:  _t('Delete this layer'),
        desc:   _t('You can\'t remove all layers from the visualization, it needs at least one to make it work.'),
        ok:     _t('Close')
      },
      error: {
        default: _t('Something went wrong, try again later')
      }
    },

    events: {
      'click .add_layer': '_addLayerDialog'
    },

    initialize: function() {
      this.layer_panels = []; // Layer panels added in the current visualization
      this.vis = this.options.vis;
      this.map = this.vis.map;
      this.globalError = this.options.globalError;

      // View bindings
      _.bindAll(this, 'sortLayers', 'manageLayers', '_checkResize');
      this.map.layers.bind('add',    this._newLayer, this);
      this.map.layers.bind('reset',  this._resetLayers, this);
      $(window).bind('resize', this._checkResize);
      this.add_related_model(this.map);
      this.bindSort(); // Bind sort movement

      this._addLayers(this.map.layers);

      // Setting several pane parameters
      this.isOpen = true;
      this.activeWorkView = 'table';

      cdb.ui.common.TabPane.prototype.initialize.call(this);
    },

    /* Layer panel functions */

    _resetLayers: function() {
      //clean all views
      this.removeTabs();
      // Empty layers array
      this.layer_panels = [];
      // Add again the layers
      this._addLayers(this.map.layers);
    },

    _addLayers: function(layers) {
      // Add 'add layer' button first
      this._addLayerAddButton();

      // Add layers
      var self = this;
      layers.each(function(l, pos) {
        self._addLayer(l, pos);
      });

      // Set default layer
      this._setDefaultLayer();
    },

    _newLayer: function(layer) {
      this._addLayer(layer);
    },

    _addLayer: function(layer, pos) {
      var self = this;
      if (layer.get('type') == 'CartoDB') {

        var v = new cdb.admin.LayerPanelView({
          model: layer,
          vis: self.vis,
          user: self.options.user,
          globalError: self.options.globalError
        });

        v.bind('toggle',    self._toggle, self);
        v.bind('switchTo',  self._switchTo, self);
        v.bind('delete',    self._remove, self);

        self.addTab(layer.cid, v);
        v.setActiveWorkView(self.activeWorkView);

        self.layer_panels.push(v);

        // no pos -> new layer -> set as current
        if (!pos) {
          self._switchTo(v);
        }

        self._checkLayers();
      }
    },

    _removeLayer: function(cid) {
      var layer_view = this.getPane(cid);
      var self = this;

      this.layer_panels = _.filter(this.layer_panels, function(view) {
        if (view != layer_view) return view;
      });

      this.bind('tabEnabled', function(cid) {
        var view = self.getPane(cid);
        self.vis.save('active_layer_id', view.dataLayer.id);
        self.unbind('tabEnabled');
      });

      this.removeTab(cid);

      //this.sortLayers();
      this._checkLayers();
      this.manageLayers();
    },

    _setDefaultLayer: function() {
      var layer;
      var layer_id = this.vis.get('active_layer_id');

      // No layer_id, get last layer
      if (!layer_id) {
        var layer = _.last(this.layer_panels);
      }

      // Get layer from layer_id
      if (!layer) {
        for (var i in this.layer_panels) {
          var lyr = this.layer_panels[i];
          if (lyr.dataLayer.id == layer_id) {
            layer = lyr;
            break;
          }
        }
      }

      // Any layer? Switch to it
      if (layer) {
        this._switchTo(layer);
      }
    },

    // Check how many layers-view there are in the panel
    _checkLayers: function() {
      if (this.map.layers.getLayersByType('CartoDB') >= this._MAX_LAYERS) {
        this._hideLayerButton();
      } else {
        this._showLayerButton();
      }
    },

    /* Add layer functions */

    _addLayerAddButton: function() {
      this.$('.add_layer').remove();
      var template = this.getTemplate('table/views/add_layer');
      this.$el.prepend(template());
    },

    _showLayerButton: function() {
      this.$('section.add_layer').show();
    },

    _hideLayerButton: function() {
      this.$('section.add_layer').hide();
    },

    _addLayerDialog: function(e) {
      this.killEvent(e);
      var self = this;

      this.new_layer_dlg && this.new_layer_dlg.clean();
      this.new_layer_dlg = new cdb.admin.NewLayerDialog({
        ok: function(tableName) {

          if(!self.vis.isVisualization()) {
            self.create_vis_dialog && self.create_vis_dialog.clean();
            self.create_vis_dialog = new cdb.admin.CreateVizDialog({
              model: self.vis,
              msg: self._TEXTS.visualization.msg,
              onResponse: function(vis) {
                self.map.addCartodbLayerFromTable(tableName, {
                  success: function() {
                    window.location.href = "/viz/" + self.vis.get("id") + "/";
                  },
                  error: function() {
                    self.create_vis_dialog.onCreationError(null, self._TEXTS.visualization.error)
                  }
                });
              }
            });
            self.create_vis_dialog.appendToBody().open();
          } else {
            self.map.addCartodbLayerFromTable(tableName);
          }

        }
      })
      this.new_layer_dlg.appendToBody().open();
    },

    bindSort: function() {
      this.unbindSort();

      this.$el.sortable({
        axis:       "y",
        items:      "> .layer_panel",
        handle:     ".layer_info a.info",
        contaiment: this.$el,
        opacity:    0.5,
        update:       this.sortLayers
      });
    },

    unbindSort: function() {
      this.$el.sortable('destroy')
    },

    sortLayers: function() {
      var self = this;
      // set the new order in layer collection from layer view
      // positions
      var order = 1;
      this.$el.children().each(function(index, element) {
        var id = element.getAttribute('model-id');
        if(id) {
          var dataLayer = self.map.layers.get(id);
          if(dataLayer) {
            // use silent, save after all are sorted, see below
            dataLayer.set({ 'order': order }, { silent: true });
            order ++;
          }
        }
      })

      // layers don't need to be reset when they are sorted
      // and save the new layers order to the server
      this.map.layers.unbind('reset',  this._resetLayers, this);
      this.map.layers.saveLayers();
      this.map.layers.bind('reset',  this._resetLayers, this);

    },

    /* Magic layers */
    manageLayers: function() {
      // If there isn't any layer added
      if (this.map.layers.getLayersByType('CartoDB') == 0) return false;

      var $active_layer = this.activePane.$el;

      // Move rest of panels
      this.$el.find('.layer_panel').removeClass('active');

      // Add active class
      $active_layer.addClass('active');

      // Set rest of layer_panels to height 60px
      // Decide later about compress them
      // TODO: not use jquery
      this.$el.find('.layer_panel').not('.layer_panel.active').css({
        height: 66
      });

      // Get previous siblings and calculate space
      // TODO: not use jquery
      var pre_size = $active_layer.prevAll('.layer_panel').size();
      var t_s = (pre_size == 0) ? 0 : (pre_size * 53);

      // Get next siblings and calculate space
      // TODO: not use jquery
      var next_size = $active_layer.nextAll('.layer_panel').size();
      var b_s = (next_size == 0) ? 0 : (next_size * 53);

      // Set available space for the active layer_space
      // TODO: not use jquery
      var offset = (this.map.layers.getLayersByType('CartoDB') >= this._MAX_LAYERS) ? 15 : - 25;
      $active_layer.css({
        height: this.$el.height() - (t_s + b_s) + offset
      })
    },

    /* testing resize event */
    _checkResize: function(e) {
      if (this.resize) clearTimeout(this.resize);
      var self = this;
      this.resize = setTimeout(self.manageLayers, 100);
    },

    show: function(modName) {
      var width = 600,
          action = 'show';

      // Select the tab
      var mod = this.getActivePane();
      mod.tabs.activate(modName);
      mod.panels.active(modName);

      switch (modName) {
        case 'filters_mod':
        case 'wizards_mod':
        case 'infowindow_mod':
          width = 450;
          action = 'narrow';
          break;
        default:
      }

      // Show panel -> trigger
      cdb.god.trigger('panel_action', action);

      this.isOpen = true;
      this.$el.animate({
          width: width,
          right: 0
      }, this.animation_time, function() {
        cdb.god.trigger("end_" + action);
      });
    },

    hide: function(modName) {
      var panel_width = this.$el.width();

      // Hide the tab
      var mod = this.getActivePane();
      //mod.tabs.deactivate(modName);

      // Hide panel -> trigger
      cdb.god.trigger("panel_action", "hide");

      this.isOpen = false;
      this.$el.animate({
          right: 63 - panel_width
      }, this.animation_time);
    },

    _switchTo: function(l) {
      this.active(l.dataLayer.cid);
      if(this.vis.get('active_layer_id') != l.dataLayer.id) {
        this.vis.save('active_layer_id', l.dataLayer.id);
      }
      this.trigger('switch', l);
      // Check if panel width is correct
      // only if panel is opened
      if (this.isOpen) {
        var modName = l.panels.activeTab;
        this.show(modName);
      }
      this.manageLayers();
    },

    _toggle: function(modName) {
      // only hide if we click on active tab
      if (this.isOpen && modName == this.getActivePane().panels.activeTab) {
          this.hide(modName);
      } else {
          this.show(modName);
      }
    },

    // We need to review this method, it is not easy to
    // make operations when the view is cleaning/removing
    // For now the model destroy is happening here
    _remove: function(layer_view) {
      var cid = layer_view.dataLayer.cid;
      var self = this;
      var permitted = true;

      // Check if there is at least 1 layer
      // if not, don't let user do it
      if (this.map.layers.getLayersByType('CartoDB') <= this._MIN_LAYERS) {
        permitted = false;
      }

      this.remove_dlg && this.remove_dlg.clean();
      this.remove_dlg = new cdb.admin.BaseDialog({
        title: self._TEXTS[permitted ? 'remove' : 'cant_remove'].title,
        description: self._TEXTS[permitted ? 'remove' : 'cant_remove'].desc,
        template_name: 'common/views/confirm_dialog',
        clean_on_hide: true,
        enter_to_confirm: true,
        ok_button_classes: "right button grey",
        ok_title: self._TEXTS[permitted ? 'remove' : 'cant_remove'].ok,
        cancel_button_classes: "underline margin15 " + (permitted ? '' : 'hide'),
        modal_type: "confirmation",
        width: 500
      });

      if (permitted) {
        // If user confirms, app removes this panel view,
        // including the map layer
        this.remove_dlg.ok = function() {
          // Destroy layer model
          layer_view.model.destroy({
            success: function() {
              self._removeLayer(cid)
            },
            error: function(){
              self.globalError.showError(that._TEXTS.error.default, 'error', 3000);
            }
          });
        }
      }

      // Show it up
      this.remove_dlg
        .appendToBody()
        .open();
    },

    setActiveWorkView: function(workView) {
      this.hide();
      this.activeWorkView = workView;

      // Set active work view for all panes,
      // not only for the current one
      this.each(function(tab, pane){
        pane.setActiveWorkView(workView);
      })
    },

    clean: function() {
      $(window).unbind('resize', this._checkResize);
      if (this.resize) clearTimeout(this.resize);
      this.unbindSort();
      cdb.ui.common.TabPane.prototype.clean.call(this);
    }
  });
<|endoftext|>