<commit_before>/**
 * Copyright (c) 2014-2017 Eric Gach <eric.gach@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

const Clutter = imports.gi.Clutter;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const St = imports.gi.St;

const debug = Me.imports.utils.debug;

const DeskChangerButton = new Lang.Class({
    Name: 'DeskChangerButton',
    Extends: St.Button,

    _init: function (icon, callback) {
        this.icon = new St.Icon({icon_name: icon + '-symbolic', icon_size: 20});
        this.parent({
            child: this.icon,
            style_class: 'system-menu-action'// : 'notification-icon-button control-button'
        });
        this._handler = this.connect('clicked', callback);
    },

    destroy: function () {
        debug('removing button clicked handler ' + this._handler);
        this.disconnect(this._handler);
        this.icon.destroy();
        this.parent();
    },

    set_icon: function (icon) {
        this.icon.icon_name = icon + '-symbolic';
    }
});

const DeskChangerIcon = new Lang.Class({
    Name: 'DeskChangerIcon',
    Extends: St.Bin,

    _init: function (daemon, settings) {
        this._gicon = Gio.icon_new_for_string(Me.path + '/icons/wallpaper-icon.png');
        this.daemon = daemon;
        this._settings = settings;
        this.parent({style_class: 'panel-status-menu-box'});
        // fallback when the daemon is not running
        this._icon = null;
        // the preview can be shown as the icon instead
        this._preview = null;
        // this will switch between the icon and preview when the setting is changed
        this._settings.connect('changed::icon-preview', Lang.bind(this, this.update_child));
        this.daemon.connectSignal('preview', Lang.bind(this, function (proxy, e, properties) {
            let file = properties[0];
            if (this._icon) {
                this.update_child(file);
            }
        }));
        this.update_child();
    },

    destroy: function () {
        if (this._icon) {
            this._icon.destroy();
        }

        if (this._preview) {
            this._preview.destroy();
        }

        this.parent();
    },

    update_child: function (file) {
        if (this._settings.icon_preview && this._createPreview(file)) {
            debug('updating icon to preview');
            this.set_child(this._preview);

            if (this._icon) {
                this._icon.destroy();
                this._icon = null;
            }
        } else if (!(this._icon)) {
            this._icon = new St.Icon({gicon: this._gicon, style_class: 'system-status-icon'});
            this.set_child(this._icon);

            if (this._preview) {
                this._preview.destroy();
                this._preview = null;
            }
        }
    },

    _createPreview: function (file) {
        if (this._preview) {
            this._preview.destroy();
            this._preview = null;
        }

        this._preview = new DeskChangerPreview(34, this.daemon, Lang.bind(this, this.update_child));

        if (!(this._preview.file)) {
            if (typeof file === "string") {
                this._preview.set_wallpaper(file);
            } else {
                this._preview.destroy();
                this._preview = null;
                return false;
            }
        }

        return true;
    }
});

const DeskChangerPreview = new Lang.Class({
    Name: 'DeskChangerPreview',
    Extends: St.Bin,

    _init: function (width, daemon, callback) {
        this.parent({});
        this._file = null;
        this._callback = callback;
        this.daemon = daemon;
        this._texture = null;
        this._width = width;
        this._next_file_id = this.daemon.connectSignal('preview', Lang.bind(this, function (proxy, signalName, parameters) {
            let file = parameters[0];
            this.set_wallpaper(file);
        }));

        if (this.daemon.bus.queue && this.daemon.bus.queue.length > 0) {
            this.set_wallpaper(this.daemon.bus.queue[0], false);
        }
    },

    destroy: function () {
        this.daemon.disconnectSignal(this._next_file_id);
        if (this._texture) {
            this._texture.destroy();
        }
    },

    set_wallpaper: function (file, c) {
        if (this._texture) {
            this._texture.destroy();
        }

        this._texture = new Clutter.Texture({
            filter_quality: Clutter.TextureQuality.HIGH,
            keep_aspect_ratio: true,
            width: this._width
        });
        this._file = file = GLib.uri_unescape_string(file, null);
        file = file.replace('file://', '');
        debug('setting preview to ' + file);
        try{
            this._texture.set_from_file(file);
            this.set_child(this._texture);
        } catch (Exception) {
            debug('ERROR: Failed to set preview of ' + file);
            this._texture.destroy();
            this._texture = null;
            if (file.substr(-4) !== '.xml') {
                return;
            }
        }

        if (c === true && typeof this._callback === 'function') {
            this._callback(file);
        }
    },

    get file() {
        return this._file;
    }
});

const DeskChangerStateButton = new Lang.Class({
    Name: 'DeskChangerStateButton',
    Extends: DeskChangerButton,

    _init: function (states, callback) {
        if (states.length < 2) {
            RangeError('You must provide at least two states for the button');
        }

        this._callback = callback;
        this._states = states;
        this._state = 0;
        this.parent(this._states[0].icon, Lang.bind(this, this._clicked));
    },

    set_state: function (state) {
        if (state == this._states[this._state].name) {
            // We are already on that state... dafuq?!
            return;
        }

        for (let i = 0; i < this._states.length; i++) {
            if (this._states[i].name == state) {
                this.set_icon(this._states[i].icon);
                this._state = i;
                break;
            }
        }
    },

    _clicked: function () {
        let state = this._state;
        if (++state >= this._states.length)
            state = 0;
        state = this._states[state].name;
        this.set_state(state);
        this._callback(state);
    }
});
<commit_msg>Updating to fix memory leak outlined in #19<commit_after>/**
 * Copyright (c) 2014-2017 Eric Gach <eric.gach@gmail.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

const Clutter = imports.gi.Clutter;
const Cogl = imports.gi.Cogl;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GdkPixbuf = imports.gi.GdkPixbuf;
const Lang = imports.lang;
const Me = imports.misc.extensionUtils.getCurrentExtension();
const St = imports.gi.St;

const debug = Me.imports.utils.debug;

const DeskChangerButton = new Lang.Class({
    Name: 'DeskChangerButton',
    Extends: St.Button,

    _init: function (icon, callback) {
        this.icon = new St.Icon({icon_name: icon + '-symbolic', icon_size: 20});
        this.parent({
            child: this.icon,
            style_class: 'system-menu-action'// : 'notification-icon-button control-button'
        });
        this._handler = this.connect('clicked', callback);
    },

    destroy: function () {
        debug('removing button clicked handler ' + this._handler);
        this.disconnect(this._handler);
        this.icon.destroy();
        this.parent();
    },

    set_icon: function (icon) {
        this.icon.icon_name = icon + '-symbolic';
    }
});

const DeskChangerIcon = new Lang.Class({
    Name: 'DeskChangerIcon',
    Extends: St.Bin,

    _init: function (daemon, settings) {
        this._gicon = Gio.icon_new_for_string(Me.path + '/icons/wallpaper-icon.png');
        this.daemon = daemon;
        this._settings = settings;
        this.parent({style_class: 'panel-status-menu-box'});
        // fallback when the daemon is not running
        this._icon = null;
        // the preview can be shown as the icon instead
        this._preview = null;
        // this will switch between the icon and preview when the setting is changed
        this._settings.connect('changed::icon-preview', Lang.bind(this, this.update_child));
        this.daemon.connectSignal('preview', Lang.bind(this, function (proxy, e, properties) {
            let file = properties[0];
            if (this._icon) {
                this.update_child(file);
            }
        }));
        this.update_child();
    },

    destroy: function () {
        if (this._icon) {
            this._icon.destroy();
        }

        if (this._preview) {
            this._preview.destroy();
        }

        this.parent();
    },

    update_child: function (file) {
        if (this._settings.icon_preview && this._createPreview(file)) {
            debug('updating icon to preview');
            this.set_child(this._preview);

            if (this._icon) {
                this._icon.destroy();
                this._icon = null;
            }
        } else if (!(this._icon)) {
            this._icon = new St.Icon({gicon: this._gicon, style_class: 'system-status-icon'});
            this.set_child(this._icon);

            if (this._preview) {
                this._preview.destroy();
                this._preview = null;
            }
        }
    },

    _createPreview: function (file) {
        if (this._preview) {
            this._preview.destroy();
            this._preview = null;
        }

        this._preview = new DeskChangerPreview(34, this.daemon, Lang.bind(this, this.update_child));

        if (!(this._preview.file)) {
            if (typeof file === "string") {
                this._preview.set_wallpaper(file);
            } else {
                this._preview.destroy();
                this._preview = null;
                return false;
            }
        }

        return true;
    }
});

const DeskChangerPreview = new Lang.Class({
    Name: 'DeskChangerPreview',
    Extends: St.Bin,

    _init: function (width, daemon, callback) {
        this.parent({
            x_align: St.Align.MIDDLE
        });
        this._file = null;
        this._callback = callback;
        this.daemon = daemon;
        this._texture = null;
        this._width = width;
        this._next_file_id = this.daemon.connectSignal('preview', Lang.bind(this, function (proxy, signalName, parameters) {
            let file = parameters[0];
            this.set_wallpaper(file);
        }));

        if (this.daemon.bus.queue && this.daemon.bus.queue.length > 0) {
            this.set_wallpaper(this.daemon.bus.queue[0], false);
        }

        this.set_child(this._texture);
    },

    destroy: function () {
        this.daemon.disconnectSignal(this._next_file_id);
        if (this._texture) {
            this._texture.destroy();
            this._texture = null;
        }
    },

    set_wallpaper: function (file, c) {
        if (this._texture) {
            this._texture.destroy();
            this._texture = null;
        }

        this._file = file = GLib.uri_unescape_string(file, null);
        file = file.replace('file://', '');
        debug('setting preview to ' + file);
        try{
            let pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(file, this._width, -1, true);
            let height = Math.round(pixbuf.get_height() / (pixbuf.get_width() / this._width));
            let image = new Clutter.Image();
            image.set_data(
                pixbuf.get_pixels(),
                (pixbuf.get_has_alpha()? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888),
                this._width,
                height,
                pixbuf.get_rowstride()
            );
            //pixbuf.unref();
            this._texture = new Clutter.Actor({height: height, width: this._width});
            this._texture.set_content(image);
            this.add_actor(this._texture);
            //image.unref();
        } catch (e) {
            debug('ERROR: Failed to set preview of ' + file + ': ' + e);
            if (this._texture) {
                this._texture.destroy();
                this._texture = null;
            }
            if (file.substr(-4) !== '.xml') {
                return;
            }
        }

        if (c === true && typeof this._callback === 'function') {
            this._callback(file);
        }
    },

    get file() {
        return this._file;
    }
});

const DeskChangerStateButton = new Lang.Class({
    Name: 'DeskChangerStateButton',
    Extends: DeskChangerButton,

    _init: function (states, callback) {
        if (states.length < 2) {
            RangeError('You must provide at least two states for the button');
        }

        this._callback = callback;
        this._states = states;
        this._state = 0;
        this.parent(this._states[0].icon, Lang.bind(this, this._clicked));
    },

    set_state: function (state) {
        if (state == this._states[this._state].name) {
            // We are already on that state... dafuq?!
            return;
        }

        for (let i = 0; i < this._states.length; i++) {
            if (this._states[i].name == state) {
                this.set_icon(this._states[i].icon);
                this._state = i;
                break;
            }
        }
    },

    _clicked: function () {
        let state = this._state;
        if (++state >= this._states.length)
            state = 0;
        state = this._states[state].name;
        this.set_state(state);
        this._callback(state);
    }
});
<|endoftext|>


<commit_before>define(['scribe-common/src/element'], function (element) {

  /**
   * This plugin modifies the `unlink` command so that, when the user's
   * selection is collapsed, remove the containing A.
   */

  'use strict';
  return function () {
    return function (scribe) {
      console.log('var1');
      var insertHTMLCommand = new scribe.api.Command('insertHTML');

      insertHTMLCommand.execute = function () {
        console.log('execute');
        scribe.insertHTML('some html');
        // command.execute();
      };

      insertHTMLCommand.queryEnabled = function () {
        // var command = scribe.getCommand('insertHTML').execute('some html');
        return command.queryEnabled();
      };

      insertHTMLCommand.queryState = function () {
        console.log('state');
        var selection = new scribe.api.Selection();
        var blockquoteElement = selection.getContaining(function (element) {
          return element.nodeName === 'BLOCKQUOTE';
        });

        return scribe.allowsBlockElements() && !! blockquoteElement;
      };

      scribe.commands.insertHTML = insertHTMLCommand;

      // /**
       // * If the paragraphs option is set to true, we unapply the blockquote on
      //  * <enter> keypresses if the caret is on a new line.
      //  */
      // if (scribe.allowsBlockElements()) {
      //   scribe.el.addEventListener('keydown', function (event) {
      //     if (event.keyCode === 13) { // enter

      //       var command = scribe.getCommand('blockquote');
      //       if (command.queryState()) {
      //         var selection = new scribe.api.Selection();
      //         if (selection.isCaretOnNewLine()) {
      //           event.preventDefault();
      //           command.execute();
      //         }
      //       }
      //     }
      //   });
      // }
    };
  };

});

//# sourceMappingURL=scribe-plugin-blockquote-command.js.map
<commit_msg>Add insert HTML command module<commit_after>define('scribe-plugin-inserthtml-command',[],function () {

  /**
   * This plugin adds a command for inserting Markdown snippets
   */

  return function () {
    return function (scribe) {
      var insertSnippetCommand = new scribe.api.Command('insertHTML');
      insertSnippetCommand.nodeName = 'PRE';

      insertSnippetCommand.execute = function () {
        var htmlSnippet = '<pre>'
          + '$callout\n'
          + '^ Title\n'
          + '^^ Content can be added here\n'
          + '$'
          '</pre>';

        scribe.api.SimpleCommand.prototype.execute.call(this, htmlSnippet);
      };

     insertSnippetCommand.queryState = function () {
        /**
         * We override the native `document.queryCommandState` for links because
         * the `createLink` and `unlink` commands are not supported.
         * As per: http://jsbin.com/OCiJUZO/1/edit?js,console,output
         */
        var selection = new scribe.api.Selection();
        return !! selection.getContaining(function (node) {
          return node.nodeName === this.nodeName;
        }.bind(this));
      };

      /**
       * All: Executing a heading command inside a list element corrupts the markup.
       * Disabling for now.
       */
      insertSnippetCommand.queryEnabled = function () {
        var selection = new scribe.api.Selection();
        var preNode = selection.getContaining(function (node) {
          return node.nodeName === 'PRE';
        });

        return scribe.api.Command.prototype.queryEnabled.apply(this, arguments)
          && scribe.allowsBlockElements() && ! preNode;
      };

      scribe.commands.insertSomething = insertSnippetCommand;
    };
  };

});

///# sourceMappingURL=scribe-plugin-inserthtml-command.js.map
<|endoftext|>


<commit_before>'use strict';

/**
 * @file Defines the provider settings.
 *
 * Will set the path to Mongo, and applications id
 * Most of the configuration can be done using system environment variables.
 */

// Load environment variables from .env file
var dotenv = require('dotenv');
dotenv.load();

// node_env can either be "development" or "production"
var node_env = process.env.NODE_ENV || "development";
var default_port = 8000;
if(node_env === "production") {
  default_port = 80;
}

var mandatories = ['GMAIL_API_ID', 'GMAIL_API_SECRET', 'ANYFETCH_API_ID', 'ANYFETCH_API_SECRET'];
mandatories.forEach(function(mandatory) {
  if(!process.env[mandatory]) {
    console.log(mandatory + " missing, the provider may fail.");
  }
});

// Exports configuration
module.exports = {
  env: node_env,
  port: process.env.PORT || default_port,
  maxSize: process.env.MAX_SIZE || 50,

  mongoUrl: process.env.MONGO_URL || process.env.MONGOLAB_URI,
  redisUrl: process.env.REDIS_URL || process.env.REDISCLOUD_URL,

  usersConcurrency: process.env.GMAIL_USERS_CONCURRENCY || 1,
  concurrency: process.env.GMAIL_CONCURRENCY || 10,

  googleId: process.env.GMAIL_API_ID,
  googleSecret: process.env.GMAIL_API_SECRET,

  appId: process.env.ANYFETCH_API_ID,
  appSecret: process.env.ANYFETCH_API_SECRET,

  providerUrl: process.env.PROVIDER_URL,

  testRefreshToken: process.env.GMAIL_TEST_REFRESH_TOKEN,
  testAccount: process.env.GMAIL_TEST_ACCOUNT_NAME,
  imapTimeout: process.env.IMAP_TIMEOUT || 20000,

  retry: 2,
  retryDelay: 20 * 1000,

  opbeat: {
    organizationId: process.env.OPBEAT_ORGANIZATION_ID,
    appId: process.env.OPBEAT_APP_ID,
    secretToken: process.env.OPBEAT_SECRET_TOKEN
  }
};
<commit_msg>Add config.appName<commit_after>'use strict';

/**
 * @file Defines the provider settings.
 *
 * Will set the path to Mongo, and applications id
 * Most of the configuration can be done using system environment variables.
 */

// Load environment variables from .env file
var dotenv = require('dotenv');
dotenv.load();

// node_env can either be "development" or "production"
var node_env = process.env.NODE_ENV || "development";
var default_port = 8000;
if(node_env === "production") {
  default_port = 80;
}

var mandatories = ['GMAIL_API_ID', 'GMAIL_API_SECRET', 'ANYFETCH_API_ID', 'ANYFETCH_API_SECRET'];
mandatories.forEach(function(mandatory) {
  if(!process.env[mandatory]) {
    console.log(mandatory + " missing, the provider may fail.");
  }
});

// Exports configuration
module.exports = {
  env: node_env,
  port: process.env.PORT || default_port,
  maxSize: process.env.MAX_SIZE || 50,

  mongoUrl: process.env.MONGO_URL || process.env.MONGOLAB_URI,
  redisUrl: process.env.REDIS_URL || process.env.REDISCLOUD_URL,

  usersConcurrency: process.env.GMAIL_USERS_CONCURRENCY || 1,
  concurrency: process.env.GMAIL_CONCURRENCY || 10,

  googleId: process.env.GMAIL_API_ID,
  googleSecret: process.env.GMAIL_API_SECRET,

  appId: process.env.ANYFETCH_API_ID,
  appSecret: process.env.ANYFETCH_API_SECRET,

  appName: process.env.APP_NAME,
  providerUrl: process.env.PROVIDER_URL,

  testRefreshToken: process.env.GMAIL_TEST_REFRESH_TOKEN,
  testAccount: process.env.GMAIL_TEST_ACCOUNT_NAME,
  imapTimeout: process.env.IMAP_TIMEOUT || 20000,

  retry: 2,
  retryDelay: 20 * 1000,

  opbeat: {
    organizationId: process.env.OPBEAT_ORGANIZATION_ID,
    appId: process.env.OPBEAT_APP_ID,
    secretToken: process.env.OPBEAT_SECRET_TOKEN
  }
};
<|endoftext|>


<commit_before>const _             = require('lodash');
const Promise       = require('bluebird');
const elasticsearch = require('elasticsearch');
const sr            = require('sync-request');
const bunyan        = require('bunyan');
const PrettyStream  = require('bunyan-prettystream');
const semver        = require('semver');
const config        = require('../config');

const prettyStdOut = new PrettyStream({mode: 'dev'});
prettyStdOut.pipe(process.stdout);

const LogToBunyan = function () {
  const self = this;
  const bun  = bunyan.createLogger({
    name:   `${config.FRAMEWORK_NAME}-es`,
    type:   'raw',
    level:  config.elasticsearch.logLevel,
    stream: prettyStdOut
  });

  self.error = bun.error.bind(bun);
  self.warning = bun.warn.bind(bun);
  self.info = bun.info.bind(bun);
  self.debug = bun.debug.bind(bun);
  self.trace = (method, requestUrl, body, responseBody, responseStatus) => {
    bun.trace({
      method:         method,
      requestUrl:     requestUrl,
      body:           body,
      responseBody:   responseBody,
      responseStatus: responseStatus
    });
  };
  self.close = () => {
  };
};

const DEFAULT_ELASTICSEARCH_PORT = 9200;
const SUPPORTED_API_VERSION      = _.keys(elasticsearch.Client.apis);

const createEsClient = (hostConfig) => {
  let host   = hostConfig.host || 'localhost';
  const port = hostConfig.port || DEFAULT_ELASTICSEARCH_PORT;

  const protocol = (host.startsWith('https') || port === 443) ? 'https' : 'http';
  host = host.replace('https://', '').replace('http://', '');

  let path = hostConfig.path || '/';
  if (!path.startsWith('/')) {
    path = `/${path}`;
  }

  /**
   *  NOTE: The below section should be removed and replaced with an async implementation.
   *        This is blocking the main thread while it waits for the result of the API check, meaning nothing else
   *        can run.
   */
  const uri      = `${protocol}://${host}:${port}${path}`;
  let apiVersion = null;
  try {
    const results      = sr('GET', uri, {
      maxRetries: 5,
      retry:      true,
      timeout:    5000
    });
    const version      = JSON.parse(results.getBody('utf8')).version.number;
    const majorVersion = semver.major(version);
    let minorVersion   = semver.minor(version);
    while (true) { // eslint-disable-line no-constant-condition
      apiVersion = `${majorVersion}.${minorVersion}`;
      if (SUPPORTED_API_VERSION.includes(apiVersion)) {
        config.log.info(`${apiVersion} supported`);
        break;
      } else {
        config.log.info(`${apiVersion} is not supported version for this ES client, incrementing minor version`);
        minorVersion++;
      }
    }
  } catch (e) {
    config.log.error(e);
  }

  if (!apiVersion) {
    throw new Error(`unable to connect to '${uri}' to get es version`);
  }

  return new elasticsearch.Client({
    host:               {host, port, protocol, path},
    apiVersion:         apiVersion,
    suggestCompression: true,
    log:                LogToBunyan,
    defer:              function () {
      let resolve = null;
      let reject  = null;

      const promise = new Promise((res, rej) => {
        resolve = res;
        reject = rej;
      });
      return {
        resolve: resolve,
        reject:  reject,
        promise: promise
      };
    },
    maxRetries:     3,
    requestTimeout: 240000,
    pingTimeout:    240000,
    deadTimeout:    240000,
    keepAlive:      false
  });
};

module.exports = createEsClient;<commit_msg>add auth token support<commit_after>const _             = require('lodash');
const Promise       = require('bluebird');
const elasticsearch = require('elasticsearch');
const sr            = require('sync-request');
const bunyan        = require('bunyan');
const PrettyStream  = require('bunyan-prettystream');
const semver        = require('semver');
const config        = require('../config');

const prettyStdOut = new PrettyStream({mode: 'dev'});
prettyStdOut.pipe(process.stdout);

const LogToBunyan = function () {
  const self = this;
  const bun  = bunyan.createLogger({
    name:   `${config.FRAMEWORK_NAME}-es`,
    type:   'raw',
    level:  config.elasticsearch.logLevel,
    stream: prettyStdOut
  });

  self.error   = bun.error.bind(bun);
  self.warning = bun.warn.bind(bun);
  self.info    = bun.info.bind(bun);
  self.debug   = bun.debug.bind(bun);
  self.trace   = (method, requestUrl, body, responseBody, responseStatus) => {
    bun.trace({
      method:         method,
      requestUrl:     requestUrl,
      body:           body,
      responseBody:   responseBody,
      responseStatus: responseStatus
    });
  };
  self.close   = () => {
  };
};

const DEFAULT_ELASTICSEARCH_PORT = 9200;
const SUPPORTED_API_VERSION      = _.keys(elasticsearch.Client.apis);

const createEsClient = (hostConfig) => {
  let host   = hostConfig.host || 'localhost';
  const port = hostConfig.port || DEFAULT_ELASTICSEARCH_PORT;

  const protocol = (host.startsWith('https') || port === 443) ? 'https' : 'http';
  host           = host.replace('https://', '').replace('http://', '');

  let path = hostConfig.path || '/';
  if (!path.startsWith('/')) {
    path = `/${path}`;
  }

  /**
   *  NOTE: The below section should be removed and replaced with an async implementation.
   *        This is blocking the main thread while it waits for the result of the API check, meaning nothing else
   *        can run.
   */
  const uri      = `${protocol}://${host}:${port}${path}`;
  let apiVersion = null;
  try {
    const results      = sr('GET', uri, {
      maxRetries: 5,
      retry:      true,
      timeout:    5000
    });
    const version      = JSON.parse(results.getBody('utf8')).version.number;
    const majorVersion = semver.major(version);
    let minorVersion   = semver.minor(version);
    while (true) { // eslint-disable-line no-constant-condition
      apiVersion = `${majorVersion}.${minorVersion}`;
      if (SUPPORTED_API_VERSION.includes(apiVersion)) {
        config.log.info(`${apiVersion} supported`);
        break;
      } else {
        config.log.info(`${apiVersion} is not supported version for this ES client, incrementing minor version`);
        minorVersion++;
      }
    }
  } catch (e) {
    config.log.error(e);
  }

  if (!apiVersion) {
    throw new Error(`unable to connect to '${uri}' to get es version`);
  }

  let headers = {};
  if (process.env.AUTH_TOKEN) {
    headers['Authorization'] = process.env.AUTH_TOKEN;
  }

  return new elasticsearch.Client({
    host:               {host, port, protocol, path, headers},
    apiVersion:         apiVersion,
    suggestCompression: true,
    log:                LogToBunyan,
    defer:              function () {
      let resolve = null;
      let reject  = null;

      const promise = new Promise((res, rej) => {
        resolve = res;
        reject  = rej;
      });
      return {
        resolve: resolve,
        reject:  reject,
        promise: promise
      };
    },
    maxRetries:         3,
    requestTimeout:     240000,
    pingTimeout:        240000,
    deadTimeout:        240000,
    keepAlive:          false
  });
};

module.exports = createEsClient;<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var marked = require('marked');
var renderer = new marked.Renderer();
var Handlebars = require('handlebars');

marked.setOptions({
	renderer: renderer,
	highlight: function (code) {
		return require('highlight.js').highlightAuto(code).value;
	}
});

module.exports = function(grunt) {
	grunt.registerTask('convert_documentation_data', function() {
		var json = JSON.parse(fs.readFileSync('doc/data.json', { encoding: 'utf8' }));

		var data = {
			methods: extractTopLevelMethods(json),
			properties: extractTopLevelProperties(json),
			classes: extractClasses(json).map(function(klass) {
				klass.methods = extractClassMethods(json, getInheritanceChain(json, klass.name));
				klass.properties = extractClassProperties(json, getInheritanceChain(json, klass.name));
				return klass;
			}).filter(function(klass) {
				return klass.name !== 'EG';
			})
		};

		fs.writeFileSync('doc/ember-graph.json', JSON.stringify(data));
	});
};

function extractTopLevelMethods(json) {
	return json.classitems.filter(function(item) {
		return (item.itemtype === 'method' && (item.category || []).indexOf('top-level') >= 0);
	}).map(function(item) {
		// Make sure this method isn't detected later in the conversion
		item.class = null;

		return convertMethodItem(item);
	}).sort(function(a, b) {
		return (a.name < b.name ? -1 : 1);
	});
}

function extractTopLevelProperties(json) {
	return json.classitems.filter(function(item) {
		return (item.itemtype === 'property' && (item.category || []).indexOf('top-level') >= 0);
	}).map(function(item) {
		// Make sure this property isn't detected later in the conversion
		item.class = null;

		return convertPropertyItem(item);
	}).sort(function(a, b) {
		return (a.name < b.name ? -1 : 1);
	});
}

function extractClasses(data) {
	return Object.keys(data.classes).sort().map(function(className) {
		return convertClassItem(data.classes[className]);
	});
}

function getInheritanceChain(data, className) {
	var superClass = data.classes[className].extends;

	if (data.classes[superClass]) {
		var chain = getInheritanceChain(data, superClass);
		chain.push(className);
		return chain;
	} else {
		return [className];
	}
}

function extractClassMethods(data, classChain, methods) {
	methods = methods || {};

	if (classChain.length <= 0) {
		return Object.keys(methods).sort().map(function(key) {
			return methods[key];
		});
	}

	var classItems = data.classitems.filter(function(item) {
		return (item.itemtype === 'method' && item.class === classChain[0]);
	});

	classItems.forEach(function(item) {
		methods[item.name] = convertMethodItem(item);
	});

	var chainMethods = extractClassMethods(data, classChain.slice(1), methods);
	return chainMethods.filter(function(method) {
		return (method.private === false || method.defined_in === classChain[classChain.length -1]);
	});
}

function extractClassProperties(data, classChain, properties) {
	properties = properties || {};

	if (classChain.length <= 0) {
		return Object.keys(properties).sort().map(function(key) {
			return properties[key];
		});
	}

	var classItems = data.classitems.filter(function(item) {
		return (item.itemtype === 'property' && item.class === classChain[0]);
	});

	classItems.forEach(function(item) {
		properties[item.name] = convertPropertyItem(item);
	});

	var chainProperties = extractClassProperties(data, classChain.slice(1), properties);
	return chainProperties.filter(function(property) {
		return (property.private === false || property.defined_in === classChain[classChain.length - 1]);
	});
}

function convertClassItem(item) {
	return {
		name: item.name,
		'extends': item.extends || null,
		uses: item.uses || [],
		description: templateAndParseText(item.description || ''),
		deprecated: item.deprecated === true,
		file: {
			path: item.file,
			line: item.line
		}
	};
}

function convertMethodItem(item) {
	return {
		name: item.name,
		description: templateAndParseText(item.description || ''),
		'static': item.static === 1,
		deprecated: item.deprecated === true,
		parameters: (item.params || []).map(function(param) {
			param.description = templateAndParseText(param.description || '');
			return param;
		}),
		'return': item.return,
		defined_in: item.class,
		'public': (item.access !== 'protected' && item.access !== 'private'),
		'protected': item.access === 'protected',
		'private': item.access === 'private',
		abstract: (item.category || []).indexOf('abstract') >= 0,
		file: {
			path: item.file,
			line: item.line
		}
	};
}

function convertPropertyItem(item) {
	return {
		name: item.name,
		description: templateAndParseText(item.description || ''),
		type: item.type,
		'static': item.static === 1,
		deprecated: item.deprecated === true,
		readOnly: item.final === 1,
		'default': item.default,
		defined_in: item.class,
		'public': (item.access !== 'protected' && item.access !== 'private'),
		'protected': item.access === 'protected',
		'private': item.access === 'private',
		file: {
			path: item.file,
			line: item.line
		}
	};
}

function templateAndParseText(text) {
	text = text || '';
	return marked(Handlebars.compile(text)());
}<commit_msg>Eliminate duplicate code.<commit_after>'use strict';

var fs = require('fs');
var marked = require('marked');
var renderer = new marked.Renderer();
var Handlebars = require('handlebars');

marked.setOptions({
	renderer: renderer,
	highlight: function (code) {
		return require('highlight.js').highlightAuto(code).value;
	}
});

module.exports = function(grunt) {
	grunt.registerTask('convert_documentation_data', function() {
		var json = JSON.parse(fs.readFileSync('doc/data.json', { encoding: 'utf8' }));

		var data = {
			methods: extractTopLevelMethods(json),
			properties: extractTopLevelProperties(json),
			classes: extractClasses(json).map(function(klass) {
				klass.methods = extractClassMethods(json, getInheritanceChain(json, klass.name));
				klass.properties = extractClassProperties(json, getInheritanceChain(json, klass.name));
				return klass;
			}).filter(function(klass) {
				return klass.name !== 'EG';
			})
		};

		fs.writeFileSync('doc/ember-graph.json', JSON.stringify(data));
	});
};

function extractTopLevelMethods(json) {
	return extractTopLevelItems(json, 'method');
}

function extractTopLevelProperties(json) {
	return extractTopLevelItems(json, 'property');
}

function extractTopLevelItems(json, type) {
	return json.classitems.filter(function(item) {
		return (item.itemtype === type && (item.category || []).indexOf('top-level') >= 0);
	}).map(function(item) {
		item.class = null;
		return (type === 'method' ? convertMethodItem : convertPropertyItem)(item);
	}).sort(function(a, b) {
		return (a.name < b.name ? -1 : 1);
	});
}

function extractClasses(data) {
	return Object.keys(data.classes).sort().map(function(className) {
		return convertClassItem(data.classes[className]);
	});
}

function getInheritanceChain(data, className) {
	var superClass = data.classes[className].extends;

	if (data.classes[superClass]) {
		var chain = getInheritanceChain(data, superClass);
		chain.push(className);
		return chain;
	} else {
		return [className];
	}
}

function extractClassMethods(data, classChain, methods) {
	return extractClassItems(data, classChain, 'method', {});
}

function extractClassProperties(data, classChain) {
	return extractClassItems(data, classChain, 'property', {});
}

function extractClassItems(data, classChain, type, items) {
	if (classChain.length <= 0) {
		return Object.keys(items).sort().map(function(key) {
			return items[key];
		});
	}

	var classItems = data.classitems.filter(function(item) {
		return (item.itemtype === type && item.class === classChain[0]);
	});

	classItems.forEach(function(item) {
		items[item.name] = (type === 'method' ? convertMethodItem : convertPropertyItem)(item);
	});

	var chainItems = extractClassItems(data, classChain.slice(1), type, items);
	return chainItems.filter(function(item) {
		return (item.private === false || item.defined_in === classChain[classChain.length - 1]);
	});
}

function convertClassItem(item) {
	return {
		name: item.name,
		'extends': item.extends || null,
		uses: item.uses || [],
		description: templateAndParseText(item.description || ''),
		deprecated: item.deprecated === true,
		file: {
			path: item.file,
			line: item.line
		}
	};
}

function convertMethodItem(item) {
	return {
		name: item.name,
		description: templateAndParseText(item.description || ''),
		'static': item.static === 1,
		deprecated: item.deprecated === true,
		parameters: (item.params || []).map(function(param) {
			param.description = templateAndParseText(param.description || '');
			return param;
		}),
		'return': item.return,
		defined_in: item.class,
		'public': (item.access !== 'protected' && item.access !== 'private'),
		'protected': item.access === 'protected',
		'private': item.access === 'private',
		abstract: (item.category || []).indexOf('abstract') >= 0,
		file: {
			path: item.file,
			line: item.line
		}
	};
}

function convertPropertyItem(item) {
	return {
		name: item.name,
		description: templateAndParseText(item.description || ''),
		type: item.type,
		'static': item.static === 1,
		deprecated: item.deprecated === true,
		readOnly: item.final === 1,
		'default': item.default,
		defined_in: item.class,
		'public': (item.access !== 'protected' && item.access !== 'private'),
		'protected': item.access === 'protected',
		'private': item.access === 'private',
		file: {
			path: item.file,
			line: item.line
		}
	};
}

function templateAndParseText(text) {
	text = text || '';
	return marked(Handlebars.compile(text)());
}<|endoftext|>


<commit_before>(function () {
    "use strict";

    Mapbender.Digitizer = Mapbender.Digitizer || {};
    Mapbender.Digitizer.TableRenderer = function() {
        Mapbender.DataManager.TableRenderer.apply(this, arguments);
    };
    Mapbender.Digitizer.TableRenderer.prototype = Object.create(Mapbender.DataManager.TableRenderer.prototype);
    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        constructor: Mapbender.Digitizer.TableRenderer
    });

    Mapbender.Digitizer.TableRenderer.prototype.getButtonsOption = function(schema) {
        var buttons = [];

        // This save button is the only direct way to save modified geometries
        if (schema.allowDigitize) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.save.title'),
                cssClass: '-fn-save fa fas fa-save'
            });
        }

        if (schema.copy && schema.copy.enable) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.clone.title'),
                cssClass: 'fa fas fa-copy -fn-copy'
            });
        }
        if (schema.allowCustomStyle) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.style.change'),
                cssClass: '-fn-edit-style fa fas fa-eyedropper fa-eye-dropper'  // NOTE: fas and fa-eye-dropper for FA5+; fa-eyedropper for FA4
            });
        }

        if (schema.allowChangeVisibility) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.visibility.toggleoff'),
                cssClass: 'fa far fa-eye -fn-toggle-visibility'
            });
        }
        var upstreamButtons = Mapbender.DataManager.TableRenderer.prototype.getButtonsOption.call(this, schema);
        return _.union(buttons, upstreamButtons);
    };

    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        render: function(schema) {
            var table = Mapbender.DataManager.TableRenderer.prototype.render.call(this, schema);
            this.registerEvents(schema, $(table));
            return table;
        },
        registerEvents: function(schema, $table) {
            var widget = this.owner;
            $table.on('mouseenter mouseleave', 'tbody > tr', function(event) {
                var hover = event.handleObj.origType === 'mouseenter';
                var feature = $(this).data().item;
                if (feature) {
                    feature.dispatchEvent({type: 'Digitizer.HoverFeature', hover: hover});
                }
            });
            $table.on('click', 'tbody > tr', function (e) {
                // Do nothing if click hit an interaction button; return true to allow other handlers
                if ($(e.target).hasClass('button')) {
                    return true;
                }
                var feature = $(this).data().item;
                if (feature) {
                    widget.zoomToFeature(schema, feature);
                }
            });
            this.registerButtonEvents(schema, $table);
        },
        registerButtonEvents: function(schema, $table) {
            var self = this;
            $table.on('click', 'tbody > tr .-fn-save', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var data = $(this).closest('tr').data();
                if (data.schema && data.item) {
                    self.owner._saveItem(data.schema, undefined, data.item);
                }
            });
            $table.on('click', 'tbody > tr .-fn-toggle-visibility', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var $tr = $(this).closest('tr');
                var feature = $tr.data().item;
                feature.set('hidden', !feature.get('hidden'));
                self.updateButtonStates_($tr.get(0), feature);
            });
            $table.on('click', 'tbody > tr .-fn-edit-style', function(event) {
                var data = $(this).closest('tr').data();
                if (data.schema && data.item) {
                    self.owner.openStyleEditor(data.schema, data.item);
                }
            });
            $table.on('click', 'tbody > tr .-fn-copy', function(event) {
                // Avoid calling row click handlers (may already try to zoom to feature, or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var data = $(this).closest('tr').data();
                if (data.schema && data.item) {
                    self.owner.cloneFeature(data.schema, data.item);
                }
            });
        },
        getOptions: function(schema) {
            return Object.assign(Mapbender.DataManager.TableRenderer.prototype.getOptions.call(this, schema), {
                // "pageLength" is a top-level schema attribute, unlike DM (nested in "table")
                // "searching" is aliased to top-level schema.inlineSearch, unlike DM (schema.table.searching)
                pageLength: schema.pageLength,
                searching: schema.inlineSearch
            });
        },
        getDefaultColumnConfigs: function(schema) {
            // @todo DataManager: data manager does not use default columns, but it should; without
            //                    columns, dataTables crashes immediately.
            var tableFields = [];

            tableFields.push({
                data: schema.featureType.uniqueId,
                label: 'Nr.',
                width: '20%'
            });
            return tableFields;
        },
        getColumnsConfigs: function(schema) {
            var fieldConfigs = schema.tableFields || [];
            if (!Array.isArray(fieldConfigs)) {
                // Digitizer vs DM quirk: digitizer uses a PHP-style mapping of attribute name to other config values
                // Adapt by unravelling object-to-object mapping to list of object; add the top-level key as the "data" property
                fieldConfigs = _.map(fieldConfigs, function(value, key) {
                    return Object.assign({}, value, {
                        data: key
                    });
                });
            }
            if (!fieldConfigs.length) {
                fieldConfigs = this.getDefaultColumnConfigs(schema);
            }
            return fieldConfigs;
        },
        onRowCreation: function(schema, tr, feature) {
            Mapbender.DataManager.TableRenderer.prototype.onRowCreation.apply(this, arguments);
            // Place table row into feature data for quick access (synchronized highlighting etc)
            feature.set('table-row', tr);
            // Inline save buttons start out disabled
            $('.-fn-save', tr).prop('disabled', !feature.get('dirty'));
            this.registerFeatureEvents(schema, feature);
        },
        registerFeatureEvents: function(schema, feature) {
            var self = this;
            // Update interaction buttons when "hidden" and "dirty" values change
            feature.on(ol.ObjectEventType.PROPERTYCHANGE, function(event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    self.updateButtonStates_(tr, feature);
                }
            });
            feature.on('Digitizer.HoverFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                var hover = !!event.hover || (typeof (event.hover) === 'undefined');
                if (tr) {
                    $(tr).toggleClass('hover', hover);
                }
            });
        },
        updateButtonStates_: function(tr, feature) {
            var hidden = !!feature.get('hidden');
            var tooltip;
            if (hidden) {
                tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleon')
            } else {
                tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleoff')
            }
            $('.-fn-toggle-visibility', tr)
                .toggleClass('fa-eye-slash', hidden)
                .toggleClass('fa-eye', !hidden)
                .attr('title', tooltip)
            ;
            $('.-fn-save', tr).prop('disabled', !feature.get('dirty'));
            // @todo: integrate with "save all" button (outside table)?
            // activate (trivial): frame.find(".resultTableControlButtons .save").removeAttr("disabled");
            // @todo: deactivation requires counting of modified features...
        }
    });
})();
<commit_msg>Support proper upstream DM icon markup nesting in table<commit_after>(function () {
    "use strict";

    Mapbender.Digitizer = Mapbender.Digitizer || {};
    Mapbender.Digitizer.TableRenderer = function() {
        Mapbender.DataManager.TableRenderer.apply(this, arguments);
    };
    Mapbender.Digitizer.TableRenderer.prototype = Object.create(Mapbender.DataManager.TableRenderer.prototype);
    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        constructor: Mapbender.Digitizer.TableRenderer
    });

    Mapbender.Digitizer.TableRenderer.prototype.getButtonsOption = function(schema) {
        var buttons = [];

        // This save button is the only direct way to save modified geometries
        if (schema.allowDigitize) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.save.title'),
                cssClass: '-fn-save fa fas fa-save'
            });
        }

        if (schema.copy && schema.copy.enable) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.clone.title'),
                cssClass: 'fa fas fa-copy -fn-copy'
            });
        }
        if (schema.allowCustomStyle) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.style.change'),
                cssClass: '-fn-edit-style fa fas fa-eyedropper fa-eye-dropper'  // NOTE: fas and fa-eye-dropper for FA5+; fa-eyedropper for FA4
            });
        }

        if (schema.allowChangeVisibility) {
            buttons.push({
                title: Mapbender.trans('mb.digitizer.feature.visibility.toggleoff'),
                cssClass: 'fa far fa-eye -fn-toggle-visibility'
            });
        }
        var upstreamButtons = Mapbender.DataManager.TableRenderer.prototype.getButtonsOption.call(this, schema);
        return _.union(buttons, upstreamButtons);
    };

    Object.assign(Mapbender.Digitizer.TableRenderer.prototype, {
        render: function(schema) {
            var table = Mapbender.DataManager.TableRenderer.prototype.render.call(this, schema);
            this.registerEvents(schema, $(table));
            return table;
        },
        registerEvents: function(schema, $table) {
            var widget = this.owner;
            $table.on('mouseenter mouseleave', 'tbody > tr', function(event) {
                var hover = event.handleObj.origType === 'mouseenter';
                var feature = $(this).data().item;
                if (feature) {
                    feature.dispatchEvent({type: 'Digitizer.HoverFeature', hover: hover});
                }
            });
            $table.on('click', 'tbody > tr', function (e) {
                // Do nothing if click hit an interaction button; return true to allow other handlers
                var $target = $(e.target);
                var $parentsAndSelf = $target.parentsUntil(this).add($target);
                if ($parentsAndSelf.filter('.button,.btn').length) {
                    return true;
                }
                var feature = $(this).data().item;
                if (feature) {
                    widget.zoomToFeature(schema, feature);
                }
            });
            this.registerButtonEvents(schema, $table);
        },
        registerButtonEvents: function(schema, $table) {
            var self = this;
            $table.on('click', 'tbody > tr .-fn-save', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var data = $(this).closest('tr').data();
                if (data.schema && data.item) {
                    self.owner._saveItem(data.schema, undefined, data.item);
                }
            });
            $table.on('click', 'tbody > tr .-fn-toggle-visibility', function(event) {
                // Avoid calling row click handlers (may zoom to feature or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var $tr = $(this).closest('tr');
                var feature = $tr.data().item;
                feature.set('hidden', !feature.get('hidden'));
                self.updateButtonStates_($tr.get(0), feature);
            });
            $table.on('click', 'tbody > tr .-fn-edit-style', function(event) {
                var data = $(this).closest('tr').data();
                if (data.schema && data.item) {
                    self.owner.openStyleEditor(data.schema, data.item);
                }
            });
            $table.on('click', 'tbody > tr .-fn-copy', function(event) {
                // Avoid calling row click handlers (may already try to zoom to feature, or open the edit dialog, depending on schema config)
                event.stopPropagation();
                var data = $(this).closest('tr').data();
                if (data.schema && data.item) {
                    self.owner.cloneFeature(data.schema, data.item);
                }
            });
        },
        getOptions: function(schema) {
            return Object.assign(Mapbender.DataManager.TableRenderer.prototype.getOptions.call(this, schema), {
                // "pageLength" is a top-level schema attribute, unlike DM (nested in "table")
                // "searching" is aliased to top-level schema.inlineSearch, unlike DM (schema.table.searching)
                pageLength: schema.pageLength,
                searching: schema.inlineSearch
            });
        },
        getDefaultColumnConfigs: function(schema) {
            // @todo DataManager: data manager does not use default columns, but it should; without
            //                    columns, dataTables crashes immediately.
            var tableFields = [];

            tableFields.push({
                data: schema.featureType.uniqueId,
                label: 'Nr.',
                width: '20%'
            });
            return tableFields;
        },
        getColumnsConfigs: function(schema) {
            var fieldConfigs = schema.tableFields || [];
            if (!Array.isArray(fieldConfigs)) {
                // Digitizer vs DM quirk: digitizer uses a PHP-style mapping of attribute name to other config values
                // Adapt by unravelling object-to-object mapping to list of object; add the top-level key as the "data" property
                fieldConfigs = _.map(fieldConfigs, function(value, key) {
                    return Object.assign({}, value, {
                        data: key
                    });
                });
            }
            if (!fieldConfigs.length) {
                fieldConfigs = this.getDefaultColumnConfigs(schema);
            }
            return fieldConfigs;
        },
        onRowCreation: function(schema, tr, feature) {
            Mapbender.DataManager.TableRenderer.prototype.onRowCreation.apply(this, arguments);
            // Place table row into feature data for quick access (synchronized highlighting etc)
            feature.set('table-row', tr);
            // Inline save buttons start out disabled
            $('.-fn-save', tr).prop('disabled', !feature.get('dirty'));
            this.registerFeatureEvents(schema, feature);
        },
        registerFeatureEvents: function(schema, feature) {
            var self = this;
            // Update interaction buttons when "hidden" and "dirty" values change
            feature.on(ol.ObjectEventType.PROPERTYCHANGE, function(event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                if (tr) {
                    self.updateButtonStates_(tr, feature);
                }
            });
            feature.on('Digitizer.HoverFeature', function (event) {
                var feature = event.target;
                var tr = feature && feature.get('table-row');
                var hover = !!event.hover || (typeof (event.hover) === 'undefined');
                if (tr) {
                    $(tr).toggleClass('hover', hover);
                }
            });
        },
        updateButtonStates_: function(tr, feature) {
            var hidden = !!feature.get('hidden');
            var tooltip;
            if (hidden) {
                tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleon')
            } else {
                tooltip = Mapbender.trans('mb.digitizer.feature.visibility.toggleoff')
            }
            var $visibilityButton = $('.-fn-toggle-visibility', tr);
            // Support both icon class ON button (legacy misuse) and icon markup INSIDE button transparently
            var $visibilityIcon = $visibilityButton.children().add($visibilityButton).filter('.fa');
            $visibilityIcon
                .toggleClass('fa-eye-slash', hidden)
                .toggleClass('fa-eye', !hidden)
                .attr('title', tooltip)
            ;
            $('.-fn-save', tr).prop('disabled', !feature.get('dirty'));
            // @todo: integrate with "save all" button (outside table)?
            // activate (trivial): frame.find(".resultTableControlButtons .save").removeAttr("disabled");
            // @todo: deactivation requires counting of modified features...
        }
    });
})();
<|endoftext|>


<commit_before>"use strict";

var api = {
	appendHeader(conf, key, val, force) {
		conf = conf || {};
		conf.headers = conf.headers || {};
		if (val != undefined) {
			if (force || conf.headers[key] === undefined) {
				conf.headers[key] = val;
			}
		}
		return conf;
	},

	get: function(path, conf, control) {
		var url = (api.base_url || '') + path;
		if (arguments.length === 2 && typeof conf === 'string') {
			control = conf;
			conf = {};
		} else {
			control = control || {};
		}
		if (typeof control === 'string') {
			control = {formatter: control};
		}
		switch (control.formatter) {
			case undefined:
				control.formatter = resp => {
					return resp.text().then(raw => {
						try {
							return JSON.parse(raw);
						} catch (e) {
							return raw;
						}
					});
				}
			break;
			case 'text':
				control.formatter = resp => resp.text();
			break;
		}

		conf = Object.assign({
			cache: 'no-cache',
			mode: 'cors',
			credentials: 'include'
		}, conf);

		api.appendHeader(conf, 'Accept', "application/json");
		api.appendHeader(conf, 'Authorization', api.authorization);

		return fetch(url, conf).then(response => {
			if (control.orig_body) conf.body = control.orig_body;
			return control.formatter(response).then(body => {
				var headers = {};
				for (var h of response.headers) {
					headers[h[0]] = h[1];
				}
				return { body, headers, code: response.status, response, request: {url, conf} };
			});
		}, error => {
			return { code: 0, body: {errors:[error]}, request: {url, conf}, headers: {} };
		}).then(normalized => {
			if (normalized.body && normalized.body.errors && normalized.body.errors.length) {
				throw normalized;
			}
			return normalized;
		});
	},

	post: function(path, body, conf, control) {
		if (arguments.length === 3 && typeof conf === 'string') {
			control = conf;
			conf = {};
		}
		conf = api.appendHeader(conf, "Content-Type", "application/json; charset=utf-8");
		conf.method = 'POST';
		if (body !== undefined) conf.body = body;
		if (typeof body === 'object') {
			conf.body = JSON.stringify(body);
			control = control || {};
			control.orig_body = body;
		}
		return api.get(path, conf, control);
	},

	postForm: function(path, body, conf, control) {
		if (arguments.length === 3 && typeof conf === 'string') {
			control = conf;
			conf = {};
		}
		conf = api.appendHeader(conf, "Content-Type", "application/x-www-form-urlencoded");
		return api.post(path, body, conf, control);
	}
};
<commit_msg>auto-backup orcrist 2018-08-20<commit_after>"use strict";

window.api = {
	appendHeader(conf, key, val, force) {
		conf = conf || {};
		conf.headers = conf.headers || {};
		if (val != undefined) {
			if (force || conf.headers[key] === undefined) {
				conf.headers[key] = val;
			}
		}
		return conf;
	},

	get: function(path, conf, control) {
		if (arguments.length === 2 && typeof conf === 'string') {
			control = conf;
			conf = {};
		} else {
			control = control || {};
		}
		if (typeof control === 'string') {
			control = {formatter: control};
		}

		var needs_prefix = true;
		var prefix = api.base_url || '';
		if (path[0] === '/') prefix = '';
		if (path.match(/^https?:\/\//)) prefix = '';
		var url = (control.base_url || prefix) + path;

		switch (control.formatter) {
			case undefined:
				control.formatter = resp => {
					return resp.text().then(raw => {
						try {
							return JSON.parse(raw);
						} catch (e) {
							return raw;
						}
					});
				}
			break;
			case 'text':
				control.formatter = resp => resp.text();
			break;
		}

		conf = Object.assign({
			cache: 'no-cache',
			mode: 'cors',
			credentials: 'include'
		}, conf);

		api.appendHeader(conf, 'Accept', "application/json");
		api.appendHeader(conf, 'Authorization', api.authorization);

		return Q(fetch(url, conf)).then(response => {
			if (control.orig_body) conf.body = control.orig_body;
			return control.formatter(response).then(body => {
				var headers = {};
				for (var h of response.headers) {
					headers[h[0]] = h[1];
				}
				return { body, headers, code: response.status, response, request: {url, conf} };
			});
		}, error => {
			return { code: 0, body: {errors:[error]}, request: {url, conf}, headers: {} };
		}).then(normalized => {
			if (normalized.body && normalized.body.errors && normalized.body.errors.length) {
				throw normalized;
			}
			return normalized;
		});
	},

	post: function(path, body, conf, control) {
		if (arguments.length === 3 && typeof conf === 'string') {
			control = conf;
			conf = {};
		}
		conf = api.appendHeader(conf, "Content-Type", "application/json; charset=utf-8");
		conf.method = 'POST';
		if (body !== undefined) conf.body = body;
		if (typeof body === 'object') {
			conf.body = JSON.stringify(body);
			control = control || {};
			control.orig_body = body;
		}
		return api.get(path, conf, control);
	},

	postForm: function(path, body, conf, control) {
		if (arguments.length === 3 && typeof conf === 'string') {
			control = conf;
			conf = {};
		}
		conf = api.appendHeader(conf, "Content-Type", "application/x-www-form-urlencoded");
		return api.post(path, body, conf, control);
	}
};
<|endoftext|>


<commit_before>'use strict';

class TimelineEngine {
    /**
     * @param  {Theme} theme
     * @param  {Renderer} renderer
     * @param  {Legend} legend
     * @param  {Element} threshold
     * @param  {Object} request
     * @param  {Number} eventHeight
     * @param  {Number} horizontalMargin
     */
    constructor(theme, renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
        this.theme = theme;
        this.renderer = renderer;
        this.legend = legend;
        this.threshold = threshold;
        this.request = request;
        this.scale = renderer.width / request.end;
        this.eventHeight = eventHeight;
        this.horizontalMargin = horizontalMargin;
        this.labelY = Math.round(this.eventHeight * 0.48);
        this.periodY = Math.round(this.eventHeight * 0.66);
        this.FqcnMatcher = /\\([^\\]+)$/i;
        this.origin = null;

        this.createEventElements = this.createEventElements.bind(this);
        this.createBackground = this.createBackground.bind(this);
        this.createPeriod = this.createPeriod.bind(this);
        this.render = this.render.bind(this);
        this.renderEvent = this.renderEvent.bind(this);
        this.renderPeriod = this.renderPeriod.bind(this);
        this.onResize = this.onResize.bind(this);
        this.isActive = this.isActive.bind(this);

        this.threshold.addEventListener('change', this.render);
        this.legend.addEventListener('change', this.render);

        window.addEventListener('resize', this.onResize);

        this.createElements();
        this.render();
    }

    onResize() {
        this.renderer.measure();
        this.setScale(this.renderer.width / this.request.end);
    }

    setScale(scale) {
        if (scale !== this.scale) {
            this.scale = scale;
            this.render();
        }
    }

    createElements() {
        this.origin = this.renderer.setFullVerticalLine(this.createBorder(), 0);
        this.renderer.add(this.origin);

        this.request.events
            .filter(event => event.category === 'section')
            .map(this.createBackground)
            .forEach(this.renderer.add);

        this.request.events
            .map(this.createEventElements)
            .forEach(this.renderer.add);
    }

    createBackground(event) {
        const subrequest = event.name === '__section__.child';
        const background = this.renderer.create('rect', subrequest ? 'timeline-subrequest' : 'timeline-border');

        event.elements = Object.assign(event.elements || {}, { background });

        return background;
    }

    createEventElements(event) {
        const { name, category, duration, memory, periods } = event;
        const border = this.renderer.setFullHorizontalLine(this.createBorder(), 0);
        const lines = periods.map(period => this.createPeriod(period, category));
        const label = this.createLabel(this.getShortName(name), duration, memory, periods[0]);
        const title = this.renderer.createTitle(name);
        const group = this.renderer.group([title, border, label].concat(lines), this.theme.getCategoryColor(event.category));

        event.elements = Object.assign(event.elements || {}, { group, label, border });

        this.legend.add(event.category)

        return group;
    }

    createLabel(name, duration, memory, period) {
        const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label');
        const sublabel = this.renderer.createTspan(`  ${duration} ms / ${memory} Mb`, 'timeline-sublabel');

        label.appendChild(sublabel);

        return label;
    }

    createPeriod(period, category) {
        const timeline = this.renderer.createPath(null, 'timeline-period', this.theme.getCategoryColor(category));

        period.draw = category === 'section' ? this.renderer.setSectionLine : this.renderer.setPeriodLine;
        period.elements = Object.assign(period.elements || {}, { timeline });

        return timeline;
    }

    createBorder() {
        return this.renderer.createPath(null, 'timeline-border');
    }

    isActive(event) {
        const { duration, category } = event;

        return duration >= this.threshold.value && this.legend.isActive(category);
    }

    render() {
        const events = this.request.events.filter(this.isActive);
        const width = this.renderer.width + this.horizontalMargin * 2;
        const height = this.eventHeight * events.length;

        // Set view box
        this.renderer.setViewBox(-this.horizontalMargin, 0, width, height);

        // Show 0ms origin
        this.renderer.setFullVerticalLine(this.origin, 0);

        // Render all events
        this.request.events.forEach(event => this.renderEvent(event, events.indexOf(event)));
    }

    renderEvent(event, index) {
        const { name, category, duration, memory, periods, elements } = event;
        const { group, label, border, background } = elements;
        const visible = index >= 0;

        group.setAttribute('visibility', visible ? 'visible' : 'hidden');

        if (background) {
            background.setAttribute('visibility', visible ? 'visible' : 'hidden');

            if (visible) {
                const [min, max] = this.getEventLimits(event);

                this.renderer.setFullRectangle(background, min * this.scale, max * this.scale);
            }
        }

        if (visible) {
            // Position the group
            group.setAttribute('transform', `translate(0, ${index * this.eventHeight})`);

            // Update top border
            this.renderer.setFullHorizontalLine(border, 0);

            // render label and ensure it doesn't escape the viewport
            this.renderLabel(label, event);

            // Update periods
            periods.forEach(this.renderPeriod);
        }
    }

    renderLabel(label, event) {
        const width = this.getLabelWidth(label);
        const [min, max] = this.getEventLimits(event);
        const alignLeft = (min * this.scale) + width <= this.renderer.width;

        label.setAttribute('x', (alignLeft ? min : max) * this.scale);
        label.setAttribute('text-anchor', alignLeft ? 'start' : 'end');
    }

    renderPeriod(period) {
        const { elements, start, duration } = period;

        period.draw(elements.timeline, start * this.scale, this.periodY, Math.max(duration * this.scale, 1));
    }

    getLabelWidth(label) {
        if (typeof label.width === 'undefined') {
            label.width = label.getBBox().width;
        }

        return label.width;
    }

    getEventLimits(event) {
        if (typeof event.limits === 'undefined') {
            const { periods } = event;

            event.limits = [
                periods[0].start,
                periods[periods.length - 1].end
            ];
        }

        return event.limits;
    }

    getShortName(name) {
        const matches = this.FqcnMatcher.exec(name);

        if (matches) {
            return matches[1];
        }

        return name;
    }
}

class Legend {
    constructor(element, theme) {
        this.element = element;
        this.theme = theme;

        this.toggle = this.toggle.bind(this);
        this.createCategory = this.createCategory.bind(this);

        this.categories = [];
        this.theme.getDefaultCategories().forEach(this.createCategory)
    }

    add(category) {
        this.get(category).classList.add('present');
    }

    createCategory(category) {
        const element = document.createElement('button');
        element.className = `timeline-category active`;
        element.style.borderColor = this.theme.getCategoryColor(category);
        element.innerText = category;
        element.value = category;
        element.type = 'button';
        element.addEventListener('click', this.toggle);

        this.element.appendChild(element);

        this.categories.push(element);

        return element;
    }

    toggle(event) {
        event.target.classList.toggle('active');

        this.emit('change');
    }

    isActive(category) {
        return this.get(category).classList.contains('active');
    }

    get(category) {
        return this.categories.find(element => element.value === category) || this.createCategory(category);
    }

    getClassname(category) {
        return this.classnames[category] || '';
    }

    getSectionClassname() {
        return this.classnames.section;
    }

    getDefaultClassname() {
        return this.classnames.default;
    }

    getStandardClassenames() {
        return Array.from(Object.values(this.classnames))
            .filter(className => className !== this.getSectionClassname());
    }

    emit(name) {
        this.element.dispatchEvent(new Event(name));
    }

    addEventListener(name, callback) {
        this.element.addEventListener(name, callback);
    }

    removeEventListener(name, callback) {
        this.element.removeEventListener(name, callback);
    }
}

class SvgRenderer {
    /**
     * @param  {SVGElement} element
     */
    constructor(element) {
        this.ns = 'http://www.w3.org/2000/svg';
        this.width = null;
        this.viewBox = {};
        this.element = element;

        this.add = this.add.bind(this);

        this.setViewBox(0, 0, 0, 0);
        this.measure();
    }

    setViewBox(x, y, width, height) {
        this.viewBox = { x, y, width, height };
        this.element.setAttribute('viewBox', `${x} ${y} ${width} ${height}`);
    }

    measure() {
        this.width = this.element.getBoundingClientRect().width;
    }

    add(element) {
        this.element.appendChild(element);
    }

    group(elements, className) {
        const group = this.create('g', className);

        elements.forEach(element => group.appendChild(element));

        return group;
    }

    setHorizontalLine(element, x, y, width) {
        element.setAttribute('d', `M${x},${y} h${width}`);

        return element;
    }

    setVerticalLine(element, x, y, height) {
        element.setAttribute('d', `M${x},${y} v${height}`);

        return element;
    }

    setFullHorizontalLine(element, y) {
        return this.setHorizontalLine(element, this.viewBox.x, y, this.viewBox.width);
    }

    setFullVerticalLine(element, x) {
        return this.setVerticalLine(element, x, this.viewBox.y, this.viewBox.height);
    }

    setFullRectangle(element, min, max) {
        element.setAttribute('x', min);
        element.setAttribute('y', this.viewBox.y);
        element.setAttribute('width', max - min);
        element.setAttribute('height', this.viewBox.height);
    }

    setSectionLine(element, x, y, width, height = 4, markerSize = 6) {
        const totalHeight = height + markerSize;
        const maxMarkerWidth = Math.min(markerSize, width / 2);
        const widthWithoutMarker = Math.max(0, width - (maxMarkerWidth * 2));

        element.setAttribute('d', `M${x},${y + totalHeight} v${-totalHeight} h${width} v${totalHeight} l${-maxMarkerWidth} ${-markerSize} h${-widthWithoutMarker} Z`);
    }

    setPeriodLine(element, x, y, width, height = 4, markerWidth = 2, markerHeight = 4) {
        const totalHeight = height + markerHeight;
        const maxMarkerWidth = Math.min(markerWidth, width);

        element.setAttribute('d', `M${x + maxMarkerWidth},${y + totalHeight} h${-maxMarkerWidth} v${-totalHeight} h${width} v${height} h${maxMarkerWidth-width}Z`);
    }

    createText(content, x, y, className) {
        const element = this.create('text', className);

        element.setAttribute('x', x);
        element.setAttribute('y', y);
        element.textContent = content;

        return element;
    }

    createTspan(content, className) {
        const element = this.create('tspan', className);

        element.textContent = content;

        return element;
    }

    createTitle(content) {
        const element = this.create('title');

        element.textContent = content;

        return element;
    }

    createPath(path = null, className = null, color = null) {
        const element = this.create('path', className);

        if (path) {
            element.setAttribute('d', path);
        }

        if (color) {
            element.setAttribute('fill', color);
        }

        return element;
    }

    create(name, className = null) {
        const element = document.createElementNS(this.ns, name);

        if (className) {
            element.setAttribute('class', className);
        }

        return element;
    }
}

class Theme {
    constructor(element) {
        this.reservedCategoryColors = {
            'default': '#777',
            'section': '#999',
            'event_listener': '#00b8f5',
            'template': '#66cc00',
            'doctrine': '#ff6633',
            'messenger_middleware': '#bdb81e',
            'controller.argument_value_resolver': '#8c5de6',
        };

        this.customCategoryColors = [
            '#dbab09', // dark yellow
            '#ea4aaa', // pink
            '#964b00', // brown
            '#22863a', // dark green
            '#0366d6', // dark blue
            '#17a2b8', // teal
        ];

        this.getCategoryColor = this.getCategoryColor.bind(this);
        this.getDefaultCategories = this.getDefaultCategories.bind(this);
    }

    getDefaultCategories() {
        return Object.keys(this.reservedCategoryColors);
    }

    getCategoryColor(category) {
        return this.reservedCategoryColors[category] || this.getRandomColor(category);
    }

    getRandomColor(category) {
        // instead of pure randomness, colors are assigned deterministically based on the
        // category name, to ensure that each custom category always displays the same color
        return this.customCategoryColors[this.hash(category) % this.customCategoryColors.length];
    }

    // copied from https://github.com/darkskyapp/string-hash
    hash(string) {
        var hash = 5381;
        var i = string.length;

        while(i) {
            hash = (hash * 33) ^ string.charCodeAt(--i);
        }

        return hash >>> 0;
    }
}
<commit_msg>minor #33587 [WebProfilerBundle] Clean time.js (fancyweb)<commit_after>'use strict';

class TimelineEngine {
    /**
     * @param  {Theme} theme
     * @param  {Renderer} renderer
     * @param  {Legend} legend
     * @param  {Element} threshold
     * @param  {Object} request
     * @param  {Number} eventHeight
     * @param  {Number} horizontalMargin
     */
    constructor(theme, renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
        this.theme = theme;
        this.renderer = renderer;
        this.legend = legend;
        this.threshold = threshold;
        this.request = request;
        this.scale = renderer.width / request.end;
        this.eventHeight = eventHeight;
        this.horizontalMargin = horizontalMargin;
        this.labelY = Math.round(this.eventHeight * 0.48);
        this.periodY = Math.round(this.eventHeight * 0.66);
        this.FqcnMatcher = /\\([^\\]+)$/i;
        this.origin = null;

        this.createEventElements = this.createEventElements.bind(this);
        this.createBackground = this.createBackground.bind(this);
        this.createPeriod = this.createPeriod.bind(this);
        this.render = this.render.bind(this);
        this.renderEvent = this.renderEvent.bind(this);
        this.renderPeriod = this.renderPeriod.bind(this);
        this.onResize = this.onResize.bind(this);
        this.isActive = this.isActive.bind(this);

        this.threshold.addEventListener('change', this.render);
        this.legend.addEventListener('change', this.render);

        window.addEventListener('resize', this.onResize);

        this.createElements();
        this.render();
    }

    onResize() {
        this.renderer.measure();
        this.setScale(this.renderer.width / this.request.end);
    }

    setScale(scale) {
        if (scale !== this.scale) {
            this.scale = scale;
            this.render();
        }
    }

    createElements() {
        this.origin = this.renderer.setFullVerticalLine(this.createBorder(), 0);
        this.renderer.add(this.origin);

        this.request.events
            .filter(event => event.category === 'section')
            .map(this.createBackground)
            .forEach(this.renderer.add);

        this.request.events
            .map(this.createEventElements)
            .forEach(this.renderer.add);
    }

    createBackground(event) {
        const subrequest = event.name === '__section__.child';
        const background = this.renderer.create('rect', subrequest ? 'timeline-subrequest' : 'timeline-border');

        event.elements = Object.assign(event.elements || {}, { background });

        return background;
    }

    createEventElements(event) {
        const { name, category, duration, memory, periods } = event;
        const border = this.renderer.setFullHorizontalLine(this.createBorder(), 0);
        const lines = periods.map(period => this.createPeriod(period, category));
        const label = this.createLabel(this.getShortName(name), duration, memory, periods[0]);
        const title = this.renderer.createTitle(name);
        const group = this.renderer.group([title, border, label].concat(lines), this.theme.getCategoryColor(event.category));

        event.elements = Object.assign(event.elements || {}, { group, label, border });

        this.legend.add(event.category)

        return group;
    }

    createLabel(name, duration, memory, period) {
        const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label');
        const sublabel = this.renderer.createTspan(`  ${duration} ms / ${memory} Mb`, 'timeline-sublabel');

        label.appendChild(sublabel);

        return label;
    }

    createPeriod(period, category) {
        const timeline = this.renderer.createPath(null, 'timeline-period', this.theme.getCategoryColor(category));

        period.draw = category === 'section' ? this.renderer.setSectionLine : this.renderer.setPeriodLine;
        period.elements = Object.assign(period.elements || {}, { timeline });

        return timeline;
    }

    createBorder() {
        return this.renderer.createPath(null, 'timeline-border');
    }

    isActive(event) {
        const { duration, category } = event;

        return duration >= this.threshold.value && this.legend.isActive(category);
    }

    render() {
        const events = this.request.events.filter(this.isActive);
        const width = this.renderer.width + this.horizontalMargin * 2;
        const height = this.eventHeight * events.length;

        // Set view box
        this.renderer.setViewBox(-this.horizontalMargin, 0, width, height);

        // Show 0ms origin
        this.renderer.setFullVerticalLine(this.origin, 0);

        // Render all events
        this.request.events.forEach(event => this.renderEvent(event, events.indexOf(event)));
    }

    renderEvent(event, index) {
        const { name, category, duration, memory, periods, elements } = event;
        const { group, label, border, background } = elements;
        const visible = index >= 0;

        group.setAttribute('visibility', visible ? 'visible' : 'hidden');

        if (background) {
            background.setAttribute('visibility', visible ? 'visible' : 'hidden');

            if (visible) {
                const [min, max] = this.getEventLimits(event);

                this.renderer.setFullRectangle(background, min * this.scale, max * this.scale);
            }
        }

        if (visible) {
            // Position the group
            group.setAttribute('transform', `translate(0, ${index * this.eventHeight})`);

            // Update top border
            this.renderer.setFullHorizontalLine(border, 0);

            // render label and ensure it doesn't escape the viewport
            this.renderLabel(label, event);

            // Update periods
            periods.forEach(this.renderPeriod);
        }
    }

    renderLabel(label, event) {
        const width = this.getLabelWidth(label);
        const [min, max] = this.getEventLimits(event);
        const alignLeft = (min * this.scale) + width <= this.renderer.width;

        label.setAttribute('x', (alignLeft ? min : max) * this.scale);
        label.setAttribute('text-anchor', alignLeft ? 'start' : 'end');
    }

    renderPeriod(period) {
        const { elements, start, duration } = period;

        period.draw(elements.timeline, start * this.scale, this.periodY, Math.max(duration * this.scale, 1));
    }

    getLabelWidth(label) {
        if (typeof label.width === 'undefined') {
            label.width = label.getBBox().width;
        }

        return label.width;
    }

    getEventLimits(event) {
        if (typeof event.limits === 'undefined') {
            const { periods } = event;

            event.limits = [
                periods[0].start,
                periods[periods.length - 1].end
            ];
        }

        return event.limits;
    }

    getShortName(name) {
        const matches = this.FqcnMatcher.exec(name);

        if (matches) {
            return matches[1];
        }

        return name;
    }
}

class Legend {
    constructor(element, theme) {
        this.element = element;
        this.theme = theme;

        this.toggle = this.toggle.bind(this);
        this.createCategory = this.createCategory.bind(this);

        this.categories = [];
        this.theme.getDefaultCategories().forEach(this.createCategory);
    }

    add(category) {
        this.get(category).classList.add('present');
    }

    createCategory(category) {
        const element = document.createElement('button');
        element.className = `timeline-category active`;
        element.style.borderColor = this.theme.getCategoryColor(category);
        element.innerText = category;
        element.value = category;
        element.type = 'button';
        element.addEventListener('click', this.toggle);

        this.element.appendChild(element);

        this.categories.push(element);

        return element;
    }

    toggle(event) {
        event.target.classList.toggle('active');

        this.emit('change');
    }

    isActive(category) {
        return this.get(category).classList.contains('active');
    }

    get(category) {
        return this.categories.find(element => element.value === category) || this.createCategory(category);
    }

    emit(name) {
        this.element.dispatchEvent(new Event(name));
    }

    addEventListener(name, callback) {
        this.element.addEventListener(name, callback);
    }

    removeEventListener(name, callback) {
        this.element.removeEventListener(name, callback);
    }
}

class SvgRenderer {
    /**
     * @param  {SVGElement} element
     */
    constructor(element) {
        this.ns = 'http://www.w3.org/2000/svg';
        this.width = null;
        this.viewBox = {};
        this.element = element;

        this.add = this.add.bind(this);

        this.setViewBox(0, 0, 0, 0);
        this.measure();
    }

    setViewBox(x, y, width, height) {
        this.viewBox = { x, y, width, height };
        this.element.setAttribute('viewBox', `${x} ${y} ${width} ${height}`);
    }

    measure() {
        this.width = this.element.getBoundingClientRect().width;
    }

    add(element) {
        this.element.appendChild(element);
    }

    group(elements, className) {
        const group = this.create('g', className);

        elements.forEach(element => group.appendChild(element));

        return group;
    }

    setHorizontalLine(element, x, y, width) {
        element.setAttribute('d', `M${x},${y} h${width}`);

        return element;
    }

    setVerticalLine(element, x, y, height) {
        element.setAttribute('d', `M${x},${y} v${height}`);

        return element;
    }

    setFullHorizontalLine(element, y) {
        return this.setHorizontalLine(element, this.viewBox.x, y, this.viewBox.width);
    }

    setFullVerticalLine(element, x) {
        return this.setVerticalLine(element, x, this.viewBox.y, this.viewBox.height);
    }

    setFullRectangle(element, min, max) {
        element.setAttribute('x', min);
        element.setAttribute('y', this.viewBox.y);
        element.setAttribute('width', max - min);
        element.setAttribute('height', this.viewBox.height);
    }

    setSectionLine(element, x, y, width, height = 4, markerSize = 6) {
        const totalHeight = height + markerSize;
        const maxMarkerWidth = Math.min(markerSize, width / 2);
        const widthWithoutMarker = Math.max(0, width - (maxMarkerWidth * 2));

        element.setAttribute('d', `M${x},${y + totalHeight} v${-totalHeight} h${width} v${totalHeight} l${-maxMarkerWidth} ${-markerSize} h${-widthWithoutMarker} Z`);
    }

    setPeriodLine(element, x, y, width, height = 4, markerWidth = 2, markerHeight = 4) {
        const totalHeight = height + markerHeight;
        const maxMarkerWidth = Math.min(markerWidth, width);

        element.setAttribute('d', `M${x + maxMarkerWidth},${y + totalHeight} h${-maxMarkerWidth} v${-totalHeight} h${width} v${height} h${maxMarkerWidth-width}Z`);
    }

    createText(content, x, y, className) {
        const element = this.create('text', className);

        element.setAttribute('x', x);
        element.setAttribute('y', y);
        element.textContent = content;

        return element;
    }

    createTspan(content, className) {
        const element = this.create('tspan', className);

        element.textContent = content;

        return element;
    }

    createTitle(content) {
        const element = this.create('title');

        element.textContent = content;

        return element;
    }

    createPath(path = null, className = null, color = null) {
        const element = this.create('path', className);

        if (path) {
            element.setAttribute('d', path);
        }

        if (color) {
            element.setAttribute('fill', color);
        }

        return element;
    }

    create(name, className = null) {
        const element = document.createElementNS(this.ns, name);

        if (className) {
            element.setAttribute('class', className);
        }

        return element;
    }
}

class Theme {
    constructor(element) {
        this.reservedCategoryColors = {
            'default': '#777',
            'section': '#999',
            'event_listener': '#00b8f5',
            'template': '#66cc00',
            'doctrine': '#ff6633',
            'messenger_middleware': '#bdb81e',
            'controller.argument_value_resolver': '#8c5de6',
        };

        this.customCategoryColors = [
            '#dbab09', // dark yellow
            '#ea4aaa', // pink
            '#964b00', // brown
            '#22863a', // dark green
            '#0366d6', // dark blue
            '#17a2b8', // teal
        ];

        this.getCategoryColor = this.getCategoryColor.bind(this);
        this.getDefaultCategories = this.getDefaultCategories.bind(this);
    }

    getDefaultCategories() {
        return Object.keys(this.reservedCategoryColors);
    }

    getCategoryColor(category) {
        return this.reservedCategoryColors[category] || this.getRandomColor(category);
    }

    getRandomColor(category) {
        // instead of pure randomness, colors are assigned deterministically based on the
        // category name, to ensure that each custom category always displays the same color
        return this.customCategoryColors[this.hash(category) % this.customCategoryColors.length];
    }

    // copied from https://github.com/darkskyapp/string-hash
    hash(string) {
        var hash = 5381;
        var i = string.length;

        while(i) {
            hash = (hash * 33) ^ string.charCodeAt(--i);
        }

        return hash >>> 0;
    }
}
<|endoftext|>