<commit_before>const path = require('path')
const chalk = require('chalk')
const child_process = require('child_process')
const fs = require('fs')
const inquirer = require('inquirer')

const utils = require('../utils')
const startJSServer = require('./server')

/**
 * Build and run Android app on a connected emulator or device
 * @param {Object} options
 */
function runAndroid(options) {
  startJSServer()

  prepareAndroid({options})
    .then(findAndroidDevice)
    .then(chooseDevice)
    .then(reverseDevice)
    .then(buildApp)
    .then(installApp)
    .then(runApp)
    .catch((err) => {
      if (err) {
        console.log(err)
      }
    })
}

/**
 * Prepare
 * @param {Object} options
 */
function prepareAndroid({options}) {
  return new Promise((resolve, reject) => {
    const rootPath = process.cwd()

    if (!utils.checkAndroid(rootPath)) {
      console.log()
      console.log(chalk.red('  Android project not found !'))
      console.log()
      console.log(`  You should run ${chalk.blue('weexpack init')} first`)
      reject()
    }

    console.log()
    console.log(` => ${chalk.blue.bold('Will start Android app')}`)

    // change working directory to android
    process.chdir(path.join(rootPath, 'android'))

    if (!process.env.ANDROID_HOME) {
      console.log()
      console.log(chalk.red('  Environment variable $ANDROID_HOME not found !'))
      console.log()
      console.log(`  You should set $ANDROID_HOME first.`)
      console.log(`  See ${chalk.cyan('http://stackoverflow.com/questions/19986214/setting-android-home-enviromental-variable-on-mac-os-x')}`)
      reject()
    }

    try {
      child_process.execSync(`adb kill-server`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }
    try {
      child_process.execSync(`adb start-server`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve({options})
  })
}

/**
 * find android devices
 * @param {Object} options
 */
function findAndroidDevice({options}) {
  return new Promise((resolve, reject) => {
    let devicesInfo = ''
    try {
      devicesInfo = child_process.execSync(`adb devices`, {encoding: 'utf8'})
    } catch(e) {
      console.log(chalk.red(`adb devices failed, please make sure you have adb in your PATH.`))
      console.log(`See ${chalk.cyan('http://stackoverflow.com/questions/27301960/errorunable-to-locate-adb-within-sdk-in-android-studio')}`)
      reject()
    }

    let devicesList = utils.parseDevicesResult(devicesInfo)

    resolve({devicesList, options})
  })
}

/**
 * Choose one device to run
 * @param {Array} devicesList: name, version, id, isSimulator
 * @param {Object} options
 */
function chooseDevice({devicesList, options}) {
  return new Promise((resolve, reject) => {
    if (devicesList) {
      const listNames = [new inquirer.Separator(' = devices = ')]
      for (const device of devicesList) {
        listNames.push(
          {
            name: `${device}`,
            value: device
          }
        )
      }

      inquirer.prompt([
        {
          type: 'list',
          message: 'Choose one of the following devices',
          name: 'chooseDevice',
          choices: listNames
        }
      ])
      .then((answers) => {
        const device = answers.chooseDevice
        resolve({device, options})
      })
    } else {
      reject('No android devices found.')
    }
  })
}

/**
 * Adb reverse device, allow device connect host network
 * @param {String} device
 * @param {Object} options
 */
function reverseDevice({device, options}) {
  return new Promise((resolve, reject) => {
    try {
      child_process.execSync(`adb -s ${device} reverse tcp:8080 tcp:8080`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve({device, options})
  })
}

/**
 * Build the Android app
 * @param {String} device
 * @param {Object} options
 */
function buildApp({device, options}) {
  return new Promise((resolve, reject) => {
    console.log(` => ${chalk.blue.bold('Building app ...')}`)
    try {
      child_process.execSync(`./gradlew clean assemble`, {encoding: 'utf8', stdio: [0,1,2]})
    } catch(e) {
      reject()
    }

    resolve({device, options})
  })
}

/**
 * Install the Android app
 * @param {String} device
 * @param {Object} options
 */
function installApp({device, options}) {
  return new Promise((resolve, reject) => {
    console.log(` => ${chalk.blue.bold('Install app ...')}`)

    const apkName = 'app/build/outputs/apk/playground.apk'
    try {
      child_process.execSync(`adb -s ${device} install -r  ${apkName}`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve({device, options})
  })
}

/**
 * Run the Android app on emulator or device
 * @param {String} device
 * @param {Object} options
 */
function runApp({device, options}) {
  return new Promise((resolve, reject) => {
    console.log(` => ${chalk.blue.bold('Running app ...')}`)

    const packageName = fs.readFileSync(
      'app/src/main/AndroidManifest.xml',
      'utf8'
    ).match(/package="(.+?)"/)[1]

    try {
      child_process.execSync(`adb -s ${device} shell am start -n ${packageName}/.SplashActivity`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve()
  })
}

module.exports = runAndroid
<commit_msg>add android run reject error<commit_after>const path = require('path')
const chalk = require('chalk')
const child_process = require('child_process')
const fs = require('fs')
const inquirer = require('inquirer')

const utils = require('../utils')
const startJSServer = require('./server')

/**
 * Build and run Android app on a connected emulator or device
 * @param {Object} options
 */
function runAndroid(options) {
  startJSServer()

  prepareAndroid({options})
    .then(findAndroidDevice)
    .then(chooseDevice)
    .then(reverseDevice)
    .then(buildApp)
    .then(installApp)
    .then(runApp)
    .catch((err) => {
      if (err) {
        console.log(err)
      }
    })
}

/**
 * Prepare
 * @param {Object} options
 */
function prepareAndroid({options}) {
  return new Promise((resolve, reject) => {
    const rootPath = process.cwd()

    if (!utils.checkAndroid(rootPath)) {
      console.log()
      console.log(chalk.red('  Android project not found !'))
      console.log()
      console.log(`  You should run ${chalk.blue('weexpack init')} first`)
      reject()
    }

    console.log()
    console.log(` => ${chalk.blue.bold('Will start Android app')}`)

    // change working directory to android
    process.chdir(path.join(rootPath, 'android'))

    if (!process.env.ANDROID_HOME) {
      console.log()
      console.log(chalk.red('  Environment variable $ANDROID_HOME not found !'))
      console.log()
      console.log(`  You should set $ANDROID_HOME first.`)
      console.log(`  See ${chalk.cyan('http://stackoverflow.com/questions/19986214/setting-android-home-enviromental-variable-on-mac-os-x')}`)
      reject()
    }

    try {
      child_process.execSync(`adb kill-server`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }
    try {
      child_process.execSync(`adb start-server`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve({options})
  })
}

/**
 * find android devices
 * @param {Object} options
 */
function findAndroidDevice({options}) {
  return new Promise((resolve, reject) => {
    let devicesInfo = ''
    try {
      devicesInfo = child_process.execSync(`adb devices`, {encoding: 'utf8'})
    } catch(e) {
      console.log(chalk.red(`adb devices failed, please make sure you have adb in your PATH.`))
      console.log(`See ${chalk.cyan('http://stackoverflow.com/questions/27301960/errorunable-to-locate-adb-within-sdk-in-android-studio')}`)
      reject()
    }

    let devicesList = utils.parseDevicesResult(devicesInfo)

    resolve({devicesList, options})
  })
}

/**
 * Choose one device to run
 * @param {Array} devicesList: name, version, id, isSimulator
 * @param {Object} options
 */
function chooseDevice({devicesList, options}) {
  return new Promise((resolve, reject) => {
    if (devicesList) {
      const listNames = [new inquirer.Separator(' = devices = ')]
      for (const device of devicesList) {
        listNames.push(
          {
            name: `${device}`,
            value: device
          }
        )
      }

      inquirer.prompt([
        {
          type: 'list',
          message: 'Choose one of the following devices',
          name: 'chooseDevice',
          choices: listNames
        }
      ])
      .then((answers) => {
        const device = answers.chooseDevice
        resolve({device, options})
      })
    } else {
      reject('No android devices found.')
    }
  })
}

/**
 * Adb reverse device, allow device connect host network
 * @param {String} device
 * @param {Object} options
 */
function reverseDevice({device, options}) {
  return new Promise((resolve, reject) => {
    try {
      child_process.execSync(`adb -s ${device} reverse tcp:8080 tcp:8080`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve({device, options})
  })
}

/**
 * Build the Android app
 * @param {String} device
 * @param {Object} options
 */
function buildApp({device, options}) {
  return new Promise((resolve, reject) => {
    console.log(` => ${chalk.blue.bold('Building app ...')}`)
    try {
      child_process.execSync(`./gradlew clean assemble`, {encoding: 'utf8', stdio: [0,1,2]})
    } catch(e) {
      reject()
    }

    resolve({device, options})
  })
}

/**
 * Install the Android app
 * @param {String} device
 * @param {Object} options
 */
function installApp({device, options}) {
  return new Promise((resolve, reject) => {
    console.log(` => ${chalk.blue.bold('Install app ...')}`)

    const apkName = 'app/build/outputs/apk/playground.apk'
    try {
      child_process.execSync(`adb -s ${device} install -r  ${apkName}`, {encoding: 'utf8'})
    } catch(e) {
      reject()
    }

    resolve({device, options})
  })
}

/**
 * Run the Android app on emulator or device
 * @param {String} device
 * @param {Object} options
 */
function runApp({device, options}) {
  return new Promise((resolve, reject) => {
    console.log(` => ${chalk.blue.bold('Running app ...')}`)

    const packageName = fs.readFileSync(
      'app/src/main/AndroidManifest.xml',
      'utf8'
    ).match(/package="(.+?)"/)[1]

    try {
      child_process.execSync(`adb -s ${device} shell am start -n ${packageName}/.SplashActivity`, {encoding: 'utf8'})
    } catch(e) {
      reject(e)
    }

    resolve()
  })
}

module.exports = runAndroid
<|endoftext|>


<commit_before>import React, { Component } from 'react';
const Mustache = require('mustache');

class ReactMustache extends React.Component {

  compileTemplate(template, data) {
    // lazy template compiling
    return Mustache.render(template,data);
  }

  render() {
    const { template, data } = this.props;

    if (!template) return false

    const __html = this.compileTemplate(template, data);

    return (
      <Component dangerouslySetInnerHTML={{__html}}/>
    )
  }
}

ReactMustache.defaultProps = {
  data: {},
  Component: 'div',
}

export default ReactMustache;
<commit_msg> Fix Component issue<commit_after>import React, { Component } from 'react';
const Mustache = require('mustache');

export default class ReactMustache extends React.Component {

  compileTemplate(template, data) {
    // lazy template compiling
    return Mustache.render(template,data);
  }

  render() {
    const { template, data, Component } = this.props;

    if (!template) return false

    const __html = this.compileTemplate(template, data);

    return (
      <Component dangerouslySetInnerHTML={{__html}}/>
    )
  }
}

ReactMustache.defaultProps = {
  data: {},
  Component: 'div'
}
<|endoftext|>


<commit_before>define([
    'underscore',
    'jquery',
    'template',
    'drp-app-api',
    'jcrop'
], function(_, $, template, appApi) {

    var ImageEditor = function() {
        this.initialize();
    };

    _.extend(ImageEditor.prototype, {
        MAX_IMAGE_WIDTH:  924,
        MAX_IMAGE_HEIGHT: 693,
        CROP_FORMATS: {
            '4:3': 4 / 3,
            '3:2': 3 / 2,
            '16:9': 16 / 9,
            '1.85:1': 1.85,
            '2.39:1': 2.39,
            '3:4': 3 / 4,
            '2:3': 2 / 3
        },

        initialize: function() {
            _.bindAll(this);

            this.editorPane   = $('.image-editor');
            this.controls     = this.editorPane.find('.controls');
            this.cropRatios   = this.editorPane.find('.crop-presets');
            this.imageView    = this.editorPane.find('.image-container');
            this.imagePreview = $('#image-preview');
            this.imageSize    = { width: 0, height: 0 };

            this.events = $({});
            this.initTransformations();
            this.initRatioPickers();
            this.bindEvents();
        },

        initTransformations: function() {
            this.transformationDefaults = {
                modulate: {
                    brightness: 100,
                    saturation: 100,
                    hue: 100
                },
                contrast: {
                    sharpen: 0
                },
                rotate: {
                    angle: 0
                }
            };

            this.transformations = _.cloneDeep(this.transformationDefaults);
        },

        bindEvents: function() {
            this.editorPane
                .find('.cancel')
                .on('click', this.hide);

            this.editorPane
                .find('.reset')
                .on('click', this.reset);

            this.editorPane
                .find('.insert')
                .on('click', this.insertToArticle);

            this.controls
                .find('input[type=range]')
                .on('change', _.debounce(this.onAdjustSlider, 300));

            this.controls
                .find('.rotate')
                .on('click', this.rotateImage);

            this.cropRatios
                .on('click', '.ratio', this.onLockRatio);

            this.imagePreview
                .on('load', this.onImageLoaded);

            appApi.addListeners({
                pluginElementClicked: this.onEditorSelectImage,
                pluginElementDeselected: this.onEditorDeselectImage
            });
        },

        initRatioPickers: function() {
            var size = 40, format, value;
            for (format in this.CROP_FORMATS) {
                value = this.CROP_FORMATS[format];
                $('<button class="ratio">').attr({
                    'data-ratio': value,
                }).text(format).css({
                    width: size * value,
                    height: size
                }).prependTo(this.cropRatios);
            }

            this.cropRatios.find('.unlock').css('height', size);
        },

        setTranslator: function(translator) {
            this.translator = translator;
        },

        setImboClient: function(imboClient) {
            this.imbo = imboClient;
        },

        buildCropper: function() {
            if (this.cropper) { return; }

            this.cropper = $.Jcrop(this.imagePreview, _.extend({
                onChange: this.onCropChange
            }, this.originalImageSize ? {
                trueSize: [
                    this.originalImageSize.width,
                    this.originalImageSize.height
                ]
            } : {}));
        },

        show: function() {
            // Maximize app window (if in app context)
            appApi.Article.maximizeAppWindow(
                this.translator.translate('IMAGE_EDITOR_TITLE'),
                this.hide
            );

            // Show the editor pane and trigger a show-event
            this.editorPane.removeClass('hidden');
            this.trigger('show');

            return this;
        },

        hide: function() {
            this.imageIdentifier = null;
            this.reset();

            this.editorPane.addClass('hidden');
            this.trigger('hide');

            appApi.Article.restoreAppWindow();

            appApi.hideLoader();
        },

        resetState: function() {
            this.imagePreview.attr('src', 'img/blank.gif');
        },

        loadImage: function(imageId, options) {
            this.resetState();

            // Ensure app knows which image to change metadata on
            this.imageIdentifier = imageId;

            // Set original image size
            this.originalImageSize = {
                width: options.width,
                height: options.height
            };

            // Set original size to cropper
            if (this.cropper) {
                this.cropper.setOptions({
                    trueSize: [options.width, options.height]
                });
            }

            // Start loading image
            this.url = this.imbo.getImageUrl(imageId).maxSize({
                width: this.MAX_IMAGE_WIDTH,
                height: this.MAX_IMAGE_HEIGHT
            }).jpg();

            // Set up crop params, if we have any
            this.cropParams = options.crop && options.crop.x2 ? options.crop : null;
            this.cropAspectRatio = options.cropAspectRatio || null;
            if (this.cropParams) {
                this.cropParams.forceApply = true;
            }

            // Set a fixed crop aspect ratio, if any was selected
            if (options.cropAspectRatio) {
                $('[data-ratio="' + options.cropAspectRatio + '"]').trigger('click');
            }

            // Apply transformations to image and GUI
            if (options.transformations) {
                this.applyTransformations(options.transformations);
            }

            // Load metadata for image
            this.imageMetadata = {};
            this.imbo.getMetadata(imageId, function(err, data) {
                this.imageMetadata = data;
            }.bind(this));

            this.updateImageView();
        },

        applyTransformations: function(transformations) {
            var transformation, i;
            for (i = 0; i < transformations.length; i++) {
                transformation = this.parseTransformation(transformations[i]);

                // For now, we're applying crop at a different stage
                // Change this if multiple crops is supposed to work
                if (transformation.name === 'crop') {
                    continue;
                }

                this.applyTransformation(transformation);
            }
        },

        applyTransformation: function(t) {
            // We need to translate some param names for modulate
            if (t.name === 'modulate') {
                this.transformations.modulate = {
                    brightness: t.params.b,
                    saturation: t.params.s,
                    hue: t.params.h
                };

                for (var key in this.transformations.modulate) {
                    $('#slider-' + key).val(this.transformations.modulate[key]);
                }

                return;
            }

            this.transformations[t.name] = _.merge(
                this.transformations[t.name],
                t.params
            );
        },

        parseTransformation: function(t) {
            var parts  = t.split(':'),
                name   = parts.shift(),
                params = parts.join(':').split(','),
                args   = {};

            for (var i = 0; i < params.length; i++) {
                parts = params[i].split('=');
                args[parts.shift()] = parts.join('=');
            }

            return { name: name, params: args };
        },

        buildImageUrl: function(preview) {
            // Reset URL
            this.url.reset().jpg();

            if (preview) {
                this.url.maxSize({
                    width: this.MAX_IMAGE_WIDTH,
                    height: this.MAX_IMAGE_HEIGHT
                });
            }

            // Find transformations with values that differ from the defaults
            var transformation, option, currentValue, defaultValue, diff = {};
            for (transformation in this.transformations) {
                for (option in this.transformations[transformation]) {
                    currentValue = this.transformations[transformation][option];
                    defaultValue = this.transformationDefaults[transformation][option];

                    if (currentValue !== defaultValue) {
                        diff[transformation] = diff[transformation] || {};
                        diff[transformation][option] = currentValue;
                    }
                }
            }

            // Apply transformations
            for (transformation in diff) {
                this.url[transformation](diff[transformation]);
            }

            return this.url;
        },

        updateImageView: function() {
            // Build new image URL based on transformation states
            this.buildImageUrl(true);

            var imageUrl = this.url.toString();
            this.imagePreview.attr('src', imageUrl);

            // Show a loading indicator while loading image
            if (!this.imagePreview.get(0).complete) {
                appApi.showLoader(
                    this.translator.translate('IMAGE_EDITOR_LOADING_IMAGE')
                );
            }

            if (this.cropper) {
                this.cropper.setImage(imageUrl);
            }
        },

        onAdjustSlider: function(e) {
            var el    = $(e.target),
                name  = el.attr('name'),
                value = e.target.valueAsNumber || e.target.value;

            if (_.contains(['brightness', 'saturation', 'hue'], name)) {
                this.transformations.modulate[name] = value;
            }

            this.updateImageView();
        },

        onImageLoaded: function() {
            // Initialize cropper
            this.buildCropper();

            // Hide loading indication
            appApi.hideLoader();

            // Get new image dimensions
            var img = this.imagePreview.get(0),
                w   = img.naturalWidth,
                h   = img.naturalHeight,
                c   = this.cropParams;

            var rotated = (this.imageSize.width !== w);
            if (this.cropParams && (rotated === false || this.cropParams.forceApply)) {
                this.cropper.setSelect([
                    this.cropParams.x,
                    this.cropParams.y,
                    this.cropParams.x2,
                    this.cropParams.y2
                ]);
            }

            this.imageSize.width  = w;
            this.imageSize.height = h;
        },

        onLockRatio: function(e) {
            var el = $(e.currentTarget);

            el.addClass('active').siblings().removeClass('active');

            // If there is no active crop, add a preview crop
            if (!this.cropParams) {
                this.cropper.setSelect([0, 0, 300, 300]);
            }

            // Now set the ratio to the given aspect ratio
            if (this.cropper) {
                this.cropper.setOptions({ aspectRatio: el.data('ratio') });
            }

            // Make sure the app knows about the selected ratio
            this.cropAspectRatio = el.data('ratio');
        },

        onCropChange: function(coords) {
            this.cropParams = coords;
        },

        rotateImage: function(e) {
            var amount    = parseInt($(e.currentTarget).data('amount'), 10),
                current   = this.transformations.rotate.angle,
                newAmount = (current + amount) % 360,
                trueSize  = [
                    this.originalImageSize.width,
                    this.originalImageSize.height
                ];

            if (newAmount < 0) {
                newAmount = 360 + newAmount;
            }

            if (newAmount === 90 || newAmount === 270) {
                trueSize = trueSize.reverse();
            }

            this.cropper.setOptions({
                'trueSize': trueSize
            });

            this.transformations.rotate.angle = newAmount;

            this.updateImageView();
        },

        reset: function() {
            // Remove transformations
            this.transformations = _.cloneDeep(this.transformationDefaults);

            // Reset sliders
            this.editorPane.find('.sliders').get(0).reset();

            // We're not selecting anything anymore
            this.selectedElementId = null;
            this.selectedElementMarkup = null;

            // Reset cropper
            if (this.cropper) {
                this.cropper.release();
                this.cropParams = null;
            }

            // Update image view
            this.updateImageView();
        },

        insertToArticle: function() {
            var url  = this.buildImageUrl(false),
                crop = this.cropParams,
                img  = $('<img />');

            // @todo Find a better way to handle unintentional crops
            if (crop && crop.w > 25 && crop.h > 25) {
                url.crop({ x: crop.x, y: crop.y, width: crop.w, height: crop.h });
            }

            // Use template to build markup
            var markup = template({
                'url': url.maxSize({ width: 552 }).jpg().toString(),
                'width': 552,
                'title': this.imageMetadata['drp:title']             || '',
                'author': this.imageMetadata['drp:photographer']     || '',
                'source': this.imageMetadata['drp:agency']           || '',
                'description': this.imageMetadata['drp:description'] || '',
                'imageIdentifier': this.imageIdentifier,
                'cropParams': JSON.stringify(_.mapValues(crop, Math.floor)),
                'cropRatio': JSON.stringify(this.cropAspectRatio),
                'transformations': JSON.stringify(url.getTransformations())
            });

            var elId = this.selectedElementId;

            if (elId) {
                appApi.Editor.replaceElementById(
                    elId,
                    $(this.selectedElementMarkup).html(markup).get(0).outerHTML,
                    function() { appApi.Editor.markAsActive(elId); }
                );
            } else {
                appApi.Editor.insertElement(
                    markup,
                    { select: true }
                );
            }

            this.hide();
        },

        onEditorSelectImage: function(e) {
            this.selectedElementId = e.id;
            appApi.Editor.getHTMLById(e.id, function(html) {
                this.selectedElementMarkup = html;

                var el  = $(html),
                    img = el.find('img');

                var transformations = img.data('transformations'),
                    imageIdentifier = img.data('image-identifier'),
                    cropParameters  = img.data('crop-parameters'),
                    cropAspectRatio = img.data('crop-aspect-ratio') || null;

                this.trigger('editor-image-selected', [{
                    imageIdentifier: imageIdentifier,
                    transformations: transformations,
                    cropAspectRatio: cropAspectRatio,
                    cropParams:      cropParameters
                }]);

            }.bind(this));
        },

        onEditorDeselectImage: function() {
            this.trigger('editor-image-deselected');

            // We're not selecting anything anymore
            this.selectedElementId = null;
            this.selectedElementMarkup = null;
        },

        on: function(e, handler) {
            this.events.on(e, handler);
            return this;
        },

        off: function(e, handler) {
            this.events.off(e, handler);
            return this;
        },

        trigger: function(e, data) {
            this.events.trigger(e, data);
            return this;
        }
    });

    return ImageEditor;

});
<commit_msg>Add dp-imbo-image class to dp-plugin-element in editor<commit_after>define([
    'underscore',
    'jquery',
    'template',
    'drp-app-api',
    'jcrop'
], function(_, $, template, appApi) {

    var ImageEditor = function() {
        this.initialize();
    };

    _.extend(ImageEditor.prototype, {
        MAX_IMAGE_WIDTH:  924,
        MAX_IMAGE_HEIGHT: 693,
        CROP_FORMATS: {
            '4:3': 4 / 3,
            '3:2': 3 / 2,
            '16:9': 16 / 9,
            '1.85:1': 1.85,
            '2.39:1': 2.39,
            '3:4': 3 / 4,
            '2:3': 2 / 3
        },

        initialize: function() {
            _.bindAll(this);

            this.editorPane   = $('.image-editor');
            this.controls     = this.editorPane.find('.controls');
            this.cropRatios   = this.editorPane.find('.crop-presets');
            this.imageView    = this.editorPane.find('.image-container');
            this.imagePreview = $('#image-preview');
            this.imageSize    = { width: 0, height: 0 };

            this.events = $({});
            this.initTransformations();
            this.initRatioPickers();
            this.bindEvents();
        },

        initTransformations: function() {
            this.transformationDefaults = {
                modulate: {
                    brightness: 100,
                    saturation: 100,
                    hue: 100
                },
                contrast: {
                    sharpen: 0
                },
                rotate: {
                    angle: 0
                }
            };

            this.transformations = _.cloneDeep(this.transformationDefaults);
        },

        bindEvents: function() {
            this.editorPane
                .find('.cancel')
                .on('click', this.hide);

            this.editorPane
                .find('.reset')
                .on('click', this.reset);

            this.editorPane
                .find('.insert')
                .on('click', this.insertToArticle);

            this.controls
                .find('input[type=range]')
                .on('change', _.debounce(this.onAdjustSlider, 300));

            this.controls
                .find('.rotate')
                .on('click', this.rotateImage);

            this.cropRatios
                .on('click', '.ratio', this.onLockRatio);

            this.imagePreview
                .on('load', this.onImageLoaded);

            appApi.addListeners({
                pluginElementClicked: this.onEditorSelectImage,
                pluginElementDeselected: this.onEditorDeselectImage
            });
        },

        initRatioPickers: function() {
            var size = 40, format, value;
            for (format in this.CROP_FORMATS) {
                value = this.CROP_FORMATS[format];
                $('<button class="ratio">').attr({
                    'data-ratio': value,
                }).text(format).css({
                    width: size * value,
                    height: size
                }).prependTo(this.cropRatios);
            }

            this.cropRatios.find('.unlock').css('height', size);
        },

        setTranslator: function(translator) {
            this.translator = translator;
        },

        setImboClient: function(imboClient) {
            this.imbo = imboClient;
        },

        buildCropper: function() {
            if (this.cropper) { return; }

            this.cropper = $.Jcrop(this.imagePreview, _.extend({
                onChange: this.onCropChange
            }, this.originalImageSize ? {
                trueSize: [
                    this.originalImageSize.width,
                    this.originalImageSize.height
                ]
            } : {}));
        },

        show: function() {
            // Maximize app window (if in app context)
            appApi.Article.maximizeAppWindow(
                this.translator.translate('IMAGE_EDITOR_TITLE'),
                this.hide
            );

            // Show the editor pane and trigger a show-event
            this.editorPane.removeClass('hidden');
            this.trigger('show');

            return this;
        },

        hide: function() {
            this.imageIdentifier = null;
            this.reset();

            this.editorPane.addClass('hidden');
            this.trigger('hide');

            appApi.Article.restoreAppWindow();

            appApi.hideLoader();
        },

        resetState: function() {
            this.imagePreview.attr('src', 'img/blank.gif');
        },

        loadImage: function(imageId, options) {
            this.resetState();

            // Ensure app knows which image to change metadata on
            this.imageIdentifier = imageId;

            // Set original image size
            this.originalImageSize = {
                width: options.width,
                height: options.height
            };

            // Set original size to cropper
            if (this.cropper) {
                this.cropper.setOptions({
                    trueSize: [options.width, options.height]
                });
            }

            // Start loading image
            this.url = this.imbo.getImageUrl(imageId).maxSize({
                width: this.MAX_IMAGE_WIDTH,
                height: this.MAX_IMAGE_HEIGHT
            }).jpg();

            // Set up crop params, if we have any
            this.cropParams = options.crop && options.crop.x2 ? options.crop : null;
            this.cropAspectRatio = options.cropAspectRatio || null;
            if (this.cropParams) {
                this.cropParams.forceApply = true;
            }

            // Set a fixed crop aspect ratio, if any was selected
            if (options.cropAspectRatio) {
                $('[data-ratio="' + options.cropAspectRatio + '"]').trigger('click');
            }

            // Apply transformations to image and GUI
            if (options.transformations) {
                this.applyTransformations(options.transformations);
            }

            // Load metadata for image
            this.imageMetadata = {};
            this.imbo.getMetadata(imageId, function(err, data) {
                this.imageMetadata = data;
            }.bind(this));

            this.updateImageView();
        },

        applyTransformations: function(transformations) {
            var transformation, i;
            for (i = 0; i < transformations.length; i++) {
                transformation = this.parseTransformation(transformations[i]);

                // For now, we're applying crop at a different stage
                // Change this if multiple crops is supposed to work
                if (transformation.name === 'crop') {
                    continue;
                }

                this.applyTransformation(transformation);
            }
        },

        applyTransformation: function(t) {
            // We need to translate some param names for modulate
            if (t.name === 'modulate') {
                this.transformations.modulate = {
                    brightness: t.params.b,
                    saturation: t.params.s,
                    hue: t.params.h
                };

                for (var key in this.transformations.modulate) {
                    $('#slider-' + key).val(this.transformations.modulate[key]);
                }

                return;
            }

            this.transformations[t.name] = _.merge(
                this.transformations[t.name],
                t.params
            );
        },

        parseTransformation: function(t) {
            var parts  = t.split(':'),
                name   = parts.shift(),
                params = parts.join(':').split(','),
                args   = {};

            for (var i = 0; i < params.length; i++) {
                parts = params[i].split('=');
                args[parts.shift()] = parts.join('=');
            }

            return { name: name, params: args };
        },

        buildImageUrl: function(preview) {
            // Reset URL
            this.url.reset().jpg();

            if (preview) {
                this.url.maxSize({
                    width: this.MAX_IMAGE_WIDTH,
                    height: this.MAX_IMAGE_HEIGHT
                });
            }

            // Find transformations with values that differ from the defaults
            var transformation, option, currentValue, defaultValue, diff = {};
            for (transformation in this.transformations) {
                for (option in this.transformations[transformation]) {
                    currentValue = this.transformations[transformation][option];
                    defaultValue = this.transformationDefaults[transformation][option];

                    if (currentValue !== defaultValue) {
                        diff[transformation] = diff[transformation] || {};
                        diff[transformation][option] = currentValue;
                    }
                }
            }

            // Apply transformations
            for (transformation in diff) {
                this.url[transformation](diff[transformation]);
            }

            return this.url;
        },

        updateImageView: function() {
            // Build new image URL based on transformation states
            this.buildImageUrl(true);

            var imageUrl = this.url.toString();
            this.imagePreview.attr('src', imageUrl);

            // Show a loading indicator while loading image
            if (!this.imagePreview.get(0).complete) {
                appApi.showLoader(
                    this.translator.translate('IMAGE_EDITOR_LOADING_IMAGE')
                );
            }

            if (this.cropper) {
                this.cropper.setImage(imageUrl);
            }
        },

        onAdjustSlider: function(e) {
            var el    = $(e.target),
                name  = el.attr('name'),
                value = e.target.valueAsNumber || e.target.value;

            if (_.contains(['brightness', 'saturation', 'hue'], name)) {
                this.transformations.modulate[name] = value;
            }

            this.updateImageView();
        },

        onImageLoaded: function() {
            // Initialize cropper
            this.buildCropper();

            // Hide loading indication
            appApi.hideLoader();

            // Get new image dimensions
            var img = this.imagePreview.get(0),
                w   = img.naturalWidth,
                h   = img.naturalHeight,
                c   = this.cropParams;

            var rotated = (this.imageSize.width !== w);
            if (this.cropParams && (rotated === false || this.cropParams.forceApply)) {
                this.cropper.setSelect([
                    this.cropParams.x,
                    this.cropParams.y,
                    this.cropParams.x2,
                    this.cropParams.y2
                ]);
            }

            this.imageSize.width  = w;
            this.imageSize.height = h;
        },

        onLockRatio: function(e) {
            var el = $(e.currentTarget);

            el.addClass('active').siblings().removeClass('active');

            // If there is no active crop, add a preview crop
            if (!this.cropParams) {
                this.cropper.setSelect([0, 0, 300, 300]);
            }

            // Now set the ratio to the given aspect ratio
            if (this.cropper) {
                this.cropper.setOptions({ aspectRatio: el.data('ratio') });
            }

            // Make sure the app knows about the selected ratio
            this.cropAspectRatio = el.data('ratio');
        },

        onCropChange: function(coords) {
            this.cropParams = coords;
        },

        rotateImage: function(e) {
            var amount    = parseInt($(e.currentTarget).data('amount'), 10),
                current   = this.transformations.rotate.angle,
                newAmount = (current + amount) % 360,
                trueSize  = [
                    this.originalImageSize.width,
                    this.originalImageSize.height
                ];

            if (newAmount < 0) {
                newAmount = 360 + newAmount;
            }

            if (newAmount === 90 || newAmount === 270) {
                trueSize = trueSize.reverse();
            }

            this.cropper.setOptions({
                'trueSize': trueSize
            });

            this.transformations.rotate.angle = newAmount;

            this.updateImageView();
        },

        reset: function() {
            // Remove transformations
            this.transformations = _.cloneDeep(this.transformationDefaults);

            // Reset sliders
            this.editorPane.find('.sliders').get(0).reset();

            // We're not selecting anything anymore
            this.selectedElementId = null;
            this.selectedElementMarkup = null;

            // Reset cropper
            if (this.cropper) {
                this.cropper.release();
                this.cropParams = null;
            }

            // Update image view
            this.updateImageView();
        },

        insertToArticle: function() {
            var url  = this.buildImageUrl(false),
                crop = this.cropParams,
                img  = $('<img />');

            // @todo Find a better way to handle unintentional crops
            if (crop && crop.w > 25 && crop.h > 25) {
                url.crop({ x: crop.x, y: crop.y, width: crop.w, height: crop.h });
            }

            // Use template to build markup
            var markup = template({
                'url': url.maxSize({ width: 552 }).jpg().toString(),
                'width': 552,
                'title': this.imageMetadata['drp:title']             || '',
                'author': this.imageMetadata['drp:photographer']     || '',
                'source': this.imageMetadata['drp:agency']           || '',
                'description': this.imageMetadata['drp:description'] || '',
                'imageIdentifier': this.imageIdentifier,
                'cropParams': JSON.stringify(_.mapValues(crop, Math.floor)),
                'cropRatio': JSON.stringify(this.cropAspectRatio),
                'transformations': JSON.stringify(url.getTransformations())
            });

            var elId = this.selectedElementId;

            if (elId) {
                appApi.Editor.replaceElementById(
                    elId,
                    $(this.selectedElementMarkup).html(markup).get(0).outerHTML,
                    function() {
                        appApi.Editor.addClasses(elId, ['dp-imbo-image']);
                        appApi.Editor.markAsActive(elId);
                    }
                );
            } else {
                appApi.Editor.insertElement(
                    markup,
                    { select: true },
                    function(elId) {
                        appApi.Editor.addClasses(elId, ['dp-imbo-image']);
                    }
                );
            }

            this.hide();
        },

        onEditorSelectImage: function(e) {
            this.selectedElementId = e.id;
            appApi.Editor.getHTMLById(e.id, function(html) {
                this.selectedElementMarkup = html;

                var el  = $(html),
                    img = el.find('img');

                var transformations = img.data('transformations'),
                    imageIdentifier = img.data('image-identifier'),
                    cropParameters  = img.data('crop-parameters'),
                    cropAspectRatio = img.data('crop-aspect-ratio') || null;

                this.trigger('editor-image-selected', [{
                    imageIdentifier: imageIdentifier,
                    transformations: transformations,
                    cropAspectRatio: cropAspectRatio,
                    cropParams:      cropParameters
                }]);

            }.bind(this));
        },

        onEditorDeselectImage: function() {
            this.trigger('editor-image-deselected');

            // We're not selecting anything anymore
            this.selectedElementId = null;
            this.selectedElementMarkup = null;
        },

        on: function(e, handler) {
            this.events.on(e, handler);
            return this;
        },

        off: function(e, handler) {
            this.events.off(e, handler);
            return this;
        },

        trigger: function(e, data) {
            this.events.trigger(e, data);
            return this;
        }
    });

    return ImageEditor;

});
<|endoftext|>


<commit_before>// Copyright 2013 Google Inc. All Rights Reserved.

/**
 * @fileoverview JSCIN for Chrome Extension, content injection.
 * @author hungte@google.com (Hung-Te Lin)
 */

// TODO(hungte) Support dynamic DOM nodes better.
// TODO(hungte) Load jquery on demand.

function CreateImeFrame () {
  var frame = document.createElement("iframe");
  var frameURL = chrome.extension.getURL('input_api/ime.html');
  frame.setAttribute("src", frameURL);
  frame.setAttribute("scrolling", "no");
  frame.setAttribute("frameBorder", 0);
  frame.setAttribute("allowTransparency", true);
  frame.style.zIndex = 999999;
  frame.style.border = 0;
  frame.style.padding = 0;
  frame.style.width = "30em";
  frame.style.height = "10em";
  frame.style.position = "absolute";
  frame.style.backgroundColor = "transparent";
  frame.style.display = "none";
  document.getElementsByTagName('body')[0].appendChild(frame);
  return frame;
}

function init () {
  // Check chrome.input.ime availability.  The correct way is to check
  // chrome.input.ime binding but that would spew error message in console for
  // every page; so let's check userAgent instead because for right now only
  // ChromeOS supports that.
  if (window.navigator.userAgent.indexOf(' CrOS ') >= 0)
    return;

  var impl = new ChromeInputImeImplChromeExtension('content');
  impl.debug("Extension IME installed.");
  impl.init();
  if (window.self === window.top)
    impl.setFrame($(CreateImeFrame()));
}

// For content.js we can't wait for readystatechange - that is controlled by
// manifest:run_at.
init();
<commit_msg>Bind on pages without <body>.<commit_after>// Copyright 2013 Google Inc. All Rights Reserved.

/**
 * @fileoverview JSCIN for Chrome Extension, content injection.
 * @author hungte@google.com (Hung-Te Lin)
 */

// TODO(hungte) Support dynamic DOM nodes better.
// TODO(hungte) Load jquery on demand.

function CreateImeFrame () {
  var frame = document.createElement("iframe");
  var frameURL = chrome.extension.getURL('input_api/ime.html');
  frame.setAttribute("src", frameURL);
  frame.setAttribute("scrolling", "no");
  frame.setAttribute("frameBorder", 0);
  frame.setAttribute("allowTransparency", true);
  frame.style.zIndex = 999999;
  frame.style.border = 0;
  frame.style.padding = 0;
  frame.style.width = "30em";
  frame.style.height = "10em";
  frame.style.position = "absolute";
  frame.style.backgroundColor = "transparent";
  frame.style.display = "none";
  var ref = document.getElementsByTagName('body')[0] || document.children[0];
  ref.appendChild(frame);
  return frame;
}

function init () {
  // Check chrome.input.ime availability.  The correct way is to check
  // chrome.input.ime binding but that would spew error message in console for
  // every page; so let's check userAgent instead because for right now only
  // ChromeOS supports that.
  if (window.navigator.userAgent.indexOf(' CrOS ') >= 0)
    return;

  var impl = new ChromeInputImeImplChromeExtension('content');
  impl.debug("Extension IME installed.");
  impl.init();
  if (window.self === window.top)
    impl.setFrame($(CreateImeFrame()));
}

// For content.js we can't wait for readystatechange - that is controlled by
// manifest:run_at.
init();
<|endoftext|>


<commit_before>class TabPaper {
  constructor(title = "Untitled", w = 900, h = 900 * 1.41, bps = 4, bl = 4, lw = 840, lh = 140) {
    this.width = w;
    this.height = h;
    this.lineHeight = lh;
    this.lineWidth = lw;
    this.beatLength = bl;
    this.beatPerSection = bps;
    this.vHTML = "";
    this.st = 0;
    this.scale = 1;
    this.content = document.createElement("div");
    this.content.style.outline = "none";
    this.content.onselectstart = function() {
      return false;
    };
    this.content.setAttribute("tabindex", "1");
    this.content.addEventListener("click", this.ckEvent.bind(this));
    this.content.addEventListener("keydown", this.kdEvent.bind(this));
    this.data = null;
    this.title = title;
    this.sel = [];
    this.check();
  }
  check() {
    if (this.width < 500) this.width = 500;
    if (this.height < 500) this.height = 500;
    if (this.lineHeight > 300) this.lineHeight = 120;
    if (this.lineWidth > this.width) this.lineWidth = this.width - 60;
  }
  render() {
    var checkY = function() {
      if (iy + 130 > this.height) {
        iy = 60;
        this.vHTML += `</svg>

				<svg width="${this.width}" height="${this.height}"
				style="background:#FFFFFF";>`;
      }
    }.bind(this);
    let nx = (this.width - this.lineWidth) / 2,
      ix = nx,
      iy = 80;
    if (!this.data) {
      this.drawLine(ix, iy);
      this.vHTML =
        `

			<svg width="${this.width}" height="${this.height}"
			style="background:#FFFFFF";>` +
        this.vHTML +
        "</svg>
";
      this.content.innerHTML = this.vHTML;
      this.zoom();
      return;
    }

    this.vHTML = `

		<svg width="${this.width}" height="${this.height}"
		style="background:#FFFFFF";>`;
    this.drawTitle(ix, iy);
    iy += 40;
    this.drawLine(ix, iy);
    ix += 80;
    this.counter = 0;

    var note_distance = 80;
    var distance_ratio = 1.0;
    for (let i = 0; i < this.data.length; i++) {
      let totaltime = 0;
      // estimate note length

      if (i % 4 == 0) {
        distance_ratio = 1.0;
        var cursor = ix;
        var orig_cursor = ix;
        for (let j = i; j < i + 4 && j < this.data.length; j++) {
          for (let k = 0; k < this.data[j].length; k++) cursor += note_distance * this.beatLength / this.data[j][k][0];
        }
        if (cursor > nx + this.lineWidth) {
          distance_ratio = (nx + this.lineWidth - orig_cursor) / (cursor + 60 - orig_cursor);
        }
      }
      for (let j = 0; j < this.data[i].length; j++) {
        this.drawNote(ix, iy, i, j, this.data[i][j][0], this.data[i][j].slice(1));
        ix += distance_ratio * note_distance * this.beatLength / this.data[i][j][0];
        totaltime += 1 / this.data[i][j][0] * this.beatLength;
      }
      if (i != this.data.length - 1) {
        if (totaltime != this.beatPerSection) this.drawAlert(ix, iy);
        if (i % 4 < 3) this.drawBar(ix, iy);
        ix += distance_ratio * 20;
        if (i % 4 == 3) {
          console.log(ix);
          console.log(nx + this.lineWidth);
          this.drawBar(nx + this.lineWidth, iy);
          (ix = nx), (iy += this.lineHeight);
          checkY();
          this.drawLine(ix, iy);
          ix += 80;
        }
      }
    }
    this.vHTML += "</svg>
";
    this.content.innerHTML = this.vHTML;
    this.zoom();
    this.noteTextList = this.content.getElementsByClassName("notetext");
    this.noteCircleList = this.content.getElementsByClassName("notecircle");
    this.vHTML = "";
  }

  setDisplayer(e) {
    this.displayer = e;
  }

  zoom() {
    for (let i = 0; i < this.content.children.length; i++) {
      this.content.children[i].style.width = this.width * this.scale + 6 + "px";
      this.content.children[i].style.height = this.height * this.scale + 6 + "px";
      this.content.children[i].style.margin = "auto";
      this.content.children[i].children[0].style.transformOrigin = "0% 0%";
      this.content.children[i].children[0].style.transform = `scale(${this.scale},${this.scale})`;
    }
  }

  selNote(section, pos, id, ln) {
    this.sel[0] = [section, pos, id, ln];
    for (let i = 0; i < this.noteCircleList.length; i++) this.noteCircleList[i].setAttribute("fill", "rgb(255,255,255)");
    this.noteCircleList[ln].setAttribute("fill", "rgb(80,255,80)");
  }

  ckEvent(e) {
    if (e.target.getAttribute("data-type") == "nt") {
      var section = e.target.getAttribute("section");
      var pos = e.target.getAttribute("pos");
      var id = e.target.getAttribute("i");
      this.selNote(section, pos, id, e.target.getAttribute("ln"));
    }
  }

  setScale(s) {
    if (s <= 2.0 && s >= 0.3) this.scale = s;
  }

  kdEvent(e) {
    if (e.keyCode == 107) {
      if (this.sel.length > 0) {
        this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1]++;
        this.noteTextList[this.sel[0][3]].innerHTML = this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1];
      }
    }
    if (e.keyCode == 109) {
      if (this.sel.length > 0) {
        if (this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1] > 0) {
          this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1]--;
          this.noteTextList[this.sel[0][3]].innerHTML = this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1];
        }
      }
    }
    if (e.keyCode == 87) {
      if (this.sel.length > 0) {
        let temp = this.sel[0];
        if (this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1] > 1) {
          this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1]--;
          let ory = this.noteTextList[temp[3]].getAttribute("y");
          let orcy = this.noteCircleList[temp[3]].getAttribute("cy");
          this.noteTextList[temp[3]].setAttribute("y", `${Number(ory) - 14}`);
          this.noteCircleList[temp[3]].setAttribute("cy", `${Number(orcy) - 14}`);
        }
      }
    }
    if (e.keyCode == 83) {
      if (this.sel.length > 0) {
        let temp = this.sel[0];
        if (this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1] < 6) {
          this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1]++;
          let ory = this.noteTextList[temp[3]].getAttribute("y");
          let orcy = this.noteCircleList[temp[3]].getAttribute("cy");
          this.noteTextList[temp[3]].setAttribute("y", `${Number(ory) + 14}`);
          this.noteCircleList[temp[3]].setAttribute("cy", `${Number(orcy) + 14}`);
        }
      }
    }
  }

  load(data) {
    this.data = data;
  }

  drawTitle(x, y) {
    this.vHTML += `<text x="${this.width / 2}" y="${y}" fill="black" text-anchor="middle" font-size="28">${this.title}</text>`;
  }

  drawBar(x, y) {
    this.vHTML += `<path d='M${x} ${y} l0 70'
		style='stroke:black;stroke-width:1'></path>`;
  }

  drawAlert(x, y) {
    this.vHTML += `<circle cx='${x}' cy='${y - 14}' r='5'
		fill='red' stroke-width='0' stroke='red'></circle>`;
  }

  drawLine(x, y) {
    this.vHTML += '<svg  stroke-linecap="square" >';
    for (let i = 0; i < 6; i++) {
      this.vHTML += `<line x1='${x}' y1='${y + i * 14}' x2='${x + this.lineWidth}' y2='${y + i * 14}'
				style="stroke:black;stroke-width:1"
			></line>`;
    }
    this.vHTML += `<text style='fill:black;font:bold 22px Sans-serif'>
		<tspan x='${x + 10}' y='${y + 21}'>T</tspan>
		<tspan x='${x + 10}' y='${y + 21 + 21}'>A</tspan>
		<tspan x='${x + 10}' y='${y + 42 + 21}'>B</tspan>
		</text>`;
    this.vHTML += `<text style='fill:black;font:bold 24px serif'>
		<tspan x='${x + 33}' y='${y + 28}'>${this.beatPerSection}</tspan>
		<tspan x='${x + 33}' y='${y + 23 + 28}'>${this.beatLength}</tspan>
		</text>`;
    this.vHTML += `<path d='M${x} ${y} l0 70 M${x + this.lineWidth} ${y} l0 70'
		style='stroke:black;stroke-width:1'></path>`;
    this.vHTML = this.vHTML + "</svg>";
  }

  drawNote(x, y, section, pos, length, data) {
    this.vHTML += "<svg>";
    for (let i = 0; i < data.length / 2; i++) {
      this.vHTML += `<circle class="notecircle" cx='${x}' cy='${y + 14 * (data[i * 2] - 1)}' r='5'
			fill='white' stroke-width='0' stroke='black' style='cursor:pointer;'></circle>`;
      this.vHTML += `<text class="notetext" ln=${this.counter} data-type="nt" section="${section}" pos="${pos}" i="${i}" x='${x - 4}' y='${y +
        14 * (data[i * 2] - 1) +
        5}'
			fill='black' style='font-size:14px;cursor:pointer;'>${data[i * 2 + 1]}</text>`;
      this.counter++;
    }

    this.vHTML += `<path d='M${x} ${y + 78} l0 25' stroke-width='1' stroke='black'></path>`;
    if (length > this.beatLength) {
      this.vHTML += `<path d='M${x} ${y + 102} l6 1' stroke-width='2' stroke='black'></path>`;
    }
    if (length > this.beatLength * 2) {
      var j = 1;
      for (let i = this.beatLength * 2; i < length; i *= 2) {
        this.vHTML += `<path d='M${x} ${y + 102 - 4 * j} l6 1' stroke-width='1' stroke='black'></path>`;
        j++;
      }
    }
    this.vHTML += "</svg>";
  }
}
<commit_msg>Removed debuging message<commit_after>class TabPaper {
  constructor(title = "Untitled", w = 900, h = 900 * 1.41, bps = 4, bl = 4, lw = 840, lh = 140) {
    this.width = w;
    this.height = h;
    this.lineHeight = lh;
    this.lineWidth = lw;
    this.beatLength = bl;
    this.beatPerSection = bps;
    this.vHTML = "";
    this.st = 0;
    this.scale = 1;
    this.content = document.createElement("div");
    this.content.style.outline = "none";
    this.content.onselectstart = function() {
      return false;
    };
    this.content.setAttribute("tabindex", "1");
    this.content.addEventListener("click", this.ckEvent.bind(this));
    this.content.addEventListener("keydown", this.kdEvent.bind(this));
    this.data = null;
    this.title = title;
    this.sel = [];
    this.check();
  }
  check() {
    if (this.width < 500) this.width = 500;
    if (this.height < 500) this.height = 500;
    if (this.lineHeight > 300) this.lineHeight = 120;
    if (this.lineWidth > this.width) this.lineWidth = this.width - 60;
  }
  render() {
    var checkY = function() {
      if (iy + 130 > this.height) {
        iy = 60;
        this.vHTML += `</svg>

				<svg width="${this.width}" height="${this.height}"
				style="background:#FFFFFF";>`;
      }
    }.bind(this);
    let nx = (this.width - this.lineWidth) / 2,
      ix = nx,
      iy = 80;
    if (!this.data) {
      this.drawLine(ix, iy);
      this.vHTML =
        `

			<svg width="${this.width}" height="${this.height}"
			style="background:#FFFFFF";>` +
        this.vHTML +
        "</svg>
";
      this.content.innerHTML = this.vHTML;
      this.zoom();
      return;
    }

    this.vHTML = `

		<svg width="${this.width}" height="${this.height}"
		style="background:#FFFFFF";>`;
    this.drawTitle(ix, iy);
    iy += 40;
    this.drawLine(ix, iy);
    ix += 80;
    this.counter = 0;

    var note_distance = 80;
    var distance_ratio = 1.0;
    for (let i = 0; i < this.data.length; i++) {
      let totaltime = 0;
      // estimate note length

      if (i % 4 == 0) {
        distance_ratio = 1.0;
        var cursor = ix;
        var orig_cursor = ix;
        for (let j = i; j < i + 4 && j < this.data.length; j++) {
          for (let k = 0; k < this.data[j].length; k++) cursor += note_distance * this.beatLength / this.data[j][k][0];
        }
        if (cursor > nx + this.lineWidth) {
          distance_ratio = (nx + this.lineWidth - orig_cursor) / (cursor + 60 - orig_cursor);
        }
      }
      for (let j = 0; j < this.data[i].length; j++) {
        this.drawNote(ix, iy, i, j, this.data[i][j][0], this.data[i][j].slice(1));
        ix += distance_ratio * note_distance * this.beatLength / this.data[i][j][0];
        totaltime += 1 / this.data[i][j][0] * this.beatLength;
      }
      if (i != this.data.length - 1) {
        if (totaltime != this.beatPerSection) this.drawAlert(ix, iy);
        if (i % 4 < 3) this.drawBar(ix, iy);
        ix += distance_ratio * 20;
        if (i % 4 == 3) {
          this.drawBar(nx + this.lineWidth, iy);
          (ix = nx), (iy += this.lineHeight);
          checkY();
          this.drawLine(ix, iy);
          ix += 80;
        }
      }
    }
    this.vHTML += "</svg>
";
    this.content.innerHTML = this.vHTML;
    this.zoom();
    this.noteTextList = this.content.getElementsByClassName("notetext");
    this.noteCircleList = this.content.getElementsByClassName("notecircle");
    this.vHTML = "";
  }

  setDisplayer(e) {
    this.displayer = e;
  }

  zoom() {
    for (let i = 0; i < this.content.children.length; i++) {
      this.content.children[i].style.width = this.width * this.scale + 6 + "px";
      this.content.children[i].style.height = this.height * this.scale + 6 + "px";
      this.content.children[i].style.margin = "auto";
      this.content.children[i].children[0].style.transformOrigin = "0% 0%";
      this.content.children[i].children[0].style.transform = `scale(${this.scale},${this.scale})`;
    }
  }

  selNote(section, pos, id, ln) {
    this.sel[0] = [section, pos, id, ln];
    for (let i = 0; i < this.noteCircleList.length; i++) this.noteCircleList[i].setAttribute("fill", "rgb(255,255,255)");
    this.noteCircleList[ln].setAttribute("fill", "rgb(80,255,80)");
  }

  ckEvent(e) {
    if (e.target.getAttribute("data-type") == "nt") {
      var section = e.target.getAttribute("section");
      var pos = e.target.getAttribute("pos");
      var id = e.target.getAttribute("i");
      this.selNote(section, pos, id, e.target.getAttribute("ln"));
    }
  }

  setScale(s) {
    if (s <= 2.0 && s >= 0.3) this.scale = s;
  }

  kdEvent(e) {
    if (e.keyCode == 107) {
      if (this.sel.length > 0) {
        this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1]++;
        this.noteTextList[this.sel[0][3]].innerHTML = this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1];
      }
    }
    if (e.keyCode == 109) {
      if (this.sel.length > 0) {
        if (this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1] > 0) {
          this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1]--;
          this.noteTextList[this.sel[0][3]].innerHTML = this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1 + 1];
        }
      }
    }
    if (e.keyCode == 87) {
      if (this.sel.length > 0) {
        let temp = this.sel[0];
        if (this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1] > 1) {
          this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1]--;
          let ory = this.noteTextList[temp[3]].getAttribute("y");
          let orcy = this.noteCircleList[temp[3]].getAttribute("cy");
          this.noteTextList[temp[3]].setAttribute("y", `${Number(ory) - 14}`);
          this.noteCircleList[temp[3]].setAttribute("cy", `${Number(orcy) - 14}`);
        }
      }
    }
    if (e.keyCode == 83) {
      if (this.sel.length > 0) {
        let temp = this.sel[0];
        if (this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1] < 6) {
          this.data[this.sel[0][0]][this.sel[0][1]][this.sel[0][2] * 2 + 1]++;
          let ory = this.noteTextList[temp[3]].getAttribute("y");
          let orcy = this.noteCircleList[temp[3]].getAttribute("cy");
          this.noteTextList[temp[3]].setAttribute("y", `${Number(ory) + 14}`);
          this.noteCircleList[temp[3]].setAttribute("cy", `${Number(orcy) + 14}`);
        }
      }
    }
  }

  load(data) {
    this.data = data;
  }

  drawTitle(x, y) {
    this.vHTML += `<text x="${this.width / 2}" y="${y}" fill="black" text-anchor="middle" font-size="28">${this.title}</text>`;
  }

  drawBar(x, y) {
    this.vHTML += `<path d='M${x} ${y} l0 70'
		style='stroke:black;stroke-width:1'></path>`;
  }

  drawAlert(x, y) {
    this.vHTML += `<circle cx='${x}' cy='${y - 14}' r='5'
		fill='red' stroke-width='0' stroke='red'></circle>`;
  }

  drawLine(x, y) {
    this.vHTML += '<svg  stroke-linecap="square" >';
    for (let i = 0; i < 6; i++) {
      this.vHTML += `<line x1='${x}' y1='${y + i * 14}' x2='${x + this.lineWidth}' y2='${y + i * 14}'
				style="stroke:black;stroke-width:1"
			></line>`;
    }
    this.vHTML += `<text style='fill:black;font:bold 22px Sans-serif'>
		<tspan x='${x + 10}' y='${y + 21}'>T</tspan>
		<tspan x='${x + 10}' y='${y + 21 + 21}'>A</tspan>
		<tspan x='${x + 10}' y='${y + 42 + 21}'>B</tspan>
		</text>`;
    this.vHTML += `<text style='fill:black;font:bold 24px serif'>
		<tspan x='${x + 33}' y='${y + 28}'>${this.beatPerSection}</tspan>
		<tspan x='${x + 33}' y='${y + 23 + 28}'>${this.beatLength}</tspan>
		</text>`;
    this.vHTML += `<path d='M${x} ${y} l0 70 M${x + this.lineWidth} ${y} l0 70'
		style='stroke:black;stroke-width:1'></path>`;
    this.vHTML = this.vHTML + "</svg>";
  }

  drawNote(x, y, section, pos, length, data) {
    this.vHTML += "<svg>";
    for (let i = 0; i < data.length / 2; i++) {
      this.vHTML += `<circle class="notecircle" cx='${x}' cy='${y + 14 * (data[i * 2] - 1)}' r='5'
			fill='white' stroke-width='0' stroke='black' style='cursor:pointer;'></circle>`;
      this.vHTML += `<text class="notetext" ln=${this.counter} data-type="nt" section="${section}" pos="${pos}" i="${i}" x='${x - 4}' y='${y +
        14 * (data[i * 2] - 1) +
        5}'
			fill='black' style='font-size:14px;cursor:pointer;'>${data[i * 2 + 1]}</text>`;
      this.counter++;
    }

    this.vHTML += `<path d='M${x} ${y + 78} l0 25' stroke-width='1' stroke='black'></path>`;
    if (length > this.beatLength) {
      this.vHTML += `<path d='M${x} ${y + 102} l6 1' stroke-width='2' stroke='black'></path>`;
    }
    if (length > this.beatLength * 2) {
      var j = 1;
      for (let i = this.beatLength * 2; i < length; i *= 2) {
        this.vHTML += `<path d='M${x} ${y + 102 - 4 * j} l6 1' stroke-width='1' stroke='black'></path>`;
        j++;
      }
    }
    this.vHTML += "</svg>";
  }
}
<|endoftext|>


<commit_before>/* eslint-env browser */
/* global rempl */

rempl.getSubscriber(function(api) {
    var host;

    rempl.createEnv(parent, api.id, function(env) {
        host = env;
        env.connected.link(function(connected) {
            document.getElementById('env').hidden = !connected;
        });
        env.subscribe(function(data) {
            switch (data.type) {
                case 'DidChangeActivePaneItem':
                    if (data.pane.isEditor) {
                        document.getElementById('env').innerHTML += `<li><b>${data.host}:</b> ${data.pane.title} (${data.pane.grammar})</li>`;
                    }
                    break;
            }
        });
    });

    api.subscribe(function(counter) {
        document.getElementById('counter').innerHTML = counter;

        host.send({
            type: 'data',
            data: {
                type: 'setStatusBarContent',
                content: counter
            }
        });
    });

    window.reset = function() {
        api.callRemote('reset');
    };

    document.body
        .appendChild(document.createElement('div'))
        .innerHTML = `

          Counter:

          <button onclick="reset()">reset</button>`;
});
<commit_msg>renamed env-var in example/node-ui<commit_after>/* eslint-env browser */
/* global rempl */

rempl.getSubscriber(function(api) {
    var theEnv;

    rempl.createEnv(parent, api.id, function(env) {
        theEnv = env;
        env.connected.link(function(connected) {
            document.getElementById('env').hidden = !connected;
        });
        env.subscribe(function(data) {
            switch (data.type) {
                case 'DidChangeActivePaneItem':
                    if (data.pane.isEditor) {
                        document.getElementById('env').innerHTML += `<li><b>${data.host}:</b> ${data.pane.title} (${data.pane.grammar})</li>`;
                    }
                    break;
            }
        });
    });

    api.subscribe(function(counter) {
        document.getElementById('counter').innerHTML = counter;

        theEnv.send({
            type: 'data',
            data: {
                type: 'setStatusBarContent',
                content: counter
            }
        });
    });

    window.reset = function() {
        api.callRemote('reset');
    };

    document.body
        .appendChild(document.createElement('div'))
        .innerHTML = `

          Counter:

          <button onclick="reset()">reset</button>`;
});
<|endoftext|>


<commit_before>const path = require('path');

module.exports = ({ file, options, env }) => {
  return {
    plugins: {
      'postcss-import': {
        root: './',
        path: [path.join(__dirname, './src/components')],
      },
      'postcss-pseudoelements': {},
      'postcss-mixins': {},
      'postcss-each': {},
      'postcss-cssnext': {
        features: {
          customProperties: {
            warnings: false,
          },
        },
      },
      'postcss-preset-env': {
        stage: false,
        customProperties: true,
      },
      'postcss-nested': {},
      'postcss-reporter': {
        clearMessages: true,
      },
      cssnano: env === 'production',
    },
  };
};
<commit_msg>🔥 Remove postcss-cssnext config<commit_after>const path = require('path');

module.exports = ({ file, options, env }) => {
  return {
    plugins: {
      'postcss-import': {
        root: './',
        path: [path.join(__dirname, './src/components')],
      },
      'postcss-pseudoelements': {},
      'postcss-mixins': {},
      'postcss-each': {},
      'postcss-preset-env': {
        stage: false,
        customProperties: true,
      },
      'postcss-nested': {},
      'postcss-reporter': {
        clearMessages: true,
      },
      cssnano: env === 'production',
    },
  };
};
<|endoftext|>


<commit_before>/*jslint node : true, nomen: true, plusplus: true, vars: true, eqeq: true,*/
"use strict";
var Stomp = require('stompjs');

function getClient(options) {
    var client = options.tcp ? Stomp.overTCP(options.tcp.host || 'localhost', options.tcp.port || 61613) : Stomp.overWS(options.ws.url);
    var heartbeat = options.heartbeat || {};
    client.heartbeat.outgoing = heartbeat.outgoing || 2000;
    client.heartbeat.incoming = heartbeat.incoming || 2000;
    return client;
}

//### Stompjs Module
module.exports = function setup(options, imports, register) {

    var log = imports.log.getLogger('stompjs');

    var service = {
        client: getClient(options),
        onDestruct: function (callback) {
            log.info('closing Stomp service');
            service.client.disconnect(callback);
        },
        connectCounter : 1,
        respawnDelay : function () {
            return Math.pow(service.connectCounter++, 2) * 100;
        },
        respawnScheduled : false,
        registered : false,
        connected : false
    };

    function connect(client, cb) {
        client.connect(options.headers || {}, function () {
            cb(null, cb);
        }, function errorcb(err) {
            cb(err);
        });
    }

    function buildQueues(config) {
        var res = {};
        Object.keys(config || {}).forEach(function (key) {
            res[key] = {
                send : function (headers, message) {
                    service.client.send(config[key], headers, message);
                },
                subscribe : function (callback, headers) {
                    service.client.subscribe(config[key], callback, headers);
                }
            };
        });
        return res;
    }

    service.queues = buildQueues(options.queues);

    var reconnect = function () {
        log.info('trying to reconnect...');
        service.respawnScheduled = false;
        if (service.connected) {
            return;
        }
        service.client = getClient(options);
        connect(service.client, function (err) {
            if (err) {
                log.error('connection error retrying ' + err);
                if (!service.connected && service.connectCounter < 20 && !service.respawnScheduled) {
                    setTimeout(reconnect, service.respawnDelay());
                    service.respawnScheduled = true;
                }
                return;
            }
            service.connectCounter = 1;
            service.connected = true;
            log.info('connected');
        });
    };

    connect(service.client, function (err) {
        if (err) {
            service.connected = false;
            if (!service.registered) {
                // stop registration process
                return register(err);
            }
            log.error('connection error ' + err);
            // connection lost : try to reconnect
            if (!service.respawnScheduled) {
                setTimeout(reconnect, service.respawnDelay());
                service.respawnScheduled = true;
            }
            return;
        }
        service.registered = true;
        service.connected = true;
        register(null, {stomp: service});
        log.info('Stomp service registered');
    });
};
<commit_msg>reimplement reconnect with setInterval<commit_after>/*jslint node : true, nomen: true, plusplus: true, vars: true, eqeq: true,*/
"use strict";
var Stomp = require('stompjs');

function getClient(options) {
    var client = options.tcp ? Stomp.overTCP(options.tcp.host || 'localhost', options.tcp.port || 61613) : Stomp.overWS(options.ws.url);
    var heartbeat = options.heartbeat || {};
    client.heartbeat.outgoing = heartbeat.outgoing || 2000;
    client.heartbeat.incoming = heartbeat.incoming || 2000;
    return client;
}

//### Stompjs Module
module.exports = function setup(options, imports, register) {

    var log = imports.log.getLogger('stompjs'), iv;

    function cleanUp() {
        if (iv) {
            clearInterval(iv);
        }
        iv = null;
    }

    var service = {
        client: getClient(options),
        onDestruct: function (callback) {
            log.info('closing Stomp service');
            service.client.disconnect(callback);
            cleanUp();
        },
        registered : false,
        connected : false
    };

    function connect(cb) {
        service.client.connect(options.headers || {}, function () {
            service.connected = true;
            cb(null, cb);
        }, function errorcb(err) {
            service.connected = false;
            cb(err);
        });
    }

    function buildQueues(config) {
        var res = {};
        Object.keys(config || {}).forEach(function (key) {
            res[key] = {
                send : function (headers, message) {
                    service.client.send(config[key], headers, message);
                },
                subscribe : function (callback, headers) {
                    service.client.subscribe(config[key], callback, headers);
                }
            };
        });
        return res;
    }

    service.queues = buildQueues(options.queues);

    var errorHandler = function errorHandler(err, handler) {
        log.error('connection error retrying ' + err);
        if (!iv) {
            // connection lost : try to reconnect
            iv = setInterval(handler, 10 * 1000);
        }
    };

    var reconnect = function () {
        log.info('trying to reconnect...');
        service.client = getClient(options);
        connect(function (err) {
            if (err) {
                return errorHandler(err, reconnect);
            }
            cleanUp();
            log.info('connected');
        });
    };

    connect(function (err) {
        if (err) {
            if (!service.registered) {
                // stop registration process
                return register(err);
            }
            return errorHandler(err, reconnect);
        }
        service.registered = true;
        register(null, {stomp: service});
        log.info('Stomp service registered');
    });
};
<|endoftext|>


<commit_before>// ImageLoader class. Scroll to bottom for example usage.
var ImageLoader = new Class({
	Implements: [Events, Options],

	// Default options
	options: {
		container: window,

		// Name of the "img" elements' data attribute that stores the image URL
		dataAttribute: "data-src",

		// Delay between loading two images (in milliseconds)
		delay: 0,

		// Array of "img" elements whose content we want to lazy load
		elements: [],

		// Maximum number of concurrent downloads
		maxConcurrentDownloads: 4,

		// Maximum distance that images can be away from the visible part of the
		// container and still be loaded
		maxDistance: 500,

		// CSS class that is used to hide empty image elements from people with
		// JavaScript disabled
		noJsClass: "no-js",

		onComplete: function() {
			console.log("Loading images completed.");
		},

		onError: function(element) {
			console.log("Image failed to load: " + element.getProperty(this.options.dataAttribute));
			element.getParent().destroy();
		},

		onLoad: function(element) {
			console.log("Image loaded: " + element.getProperty(this.options.dataAttribute));
			element.setProperty("src", element.getProperty(this.options.dataAttribute));
		}
	},

	initialize: function(options) {
		// Merge provided options with default options
		this.setOptions(options);

		// Make hidden image elements visible for people with JavaScript enabled
		$(document.body).removeClass(this.options.noJsClass);

		// Number of loaded images
		this.loadedImagesCount = 0;

		// Number of running downloads
		this.concurrentDownloads = 0;

		// Timeout IDs of delayed functions (queued downloads)
		this.timeoutIds = [];

		this.loadedElements = [];
		this.unloadedElements = this.options.elements.slice();

		this.loadedElementsCoordinates = [];
		this.unloadedElementsCoordinates = [];

		this.elementsToLoad = [];
		this.containerCoordinates = {};
	},

	/**
	 * Starts the image loader.
	 */
	run: function() {
		this.fetchContainerCoordinates();
		this.fetchElementCoordinates();
		this.fetchOriginalSrc();
		this.manageImages();

		window.addEvent("resize", function() {
			if (this.resizeTimeoutId) {
				clearTimeout(this.resizeTimeoutId);
			}

			this.resizeTimeoutId = function() {
				this.fetchContainerCoordinates();
				this.fetchElementCoordinates();
				this.manageImages();
			}.bind(this).delay(50);
		}.bind(this));

		window.addEvent("scroll", function() {
			if (this.scrollTimeoutId) {
				clearTimeout(this.scrollTimeoutId);
			}

			this.scrollTimeoutId = function() {
				this.fetchContainerCoordinates();
				this.manageImages();
			}.bind(this).delay(50);
		}.bind(this));
	},

	/**
	 * Gives image loader control over provided image elements.
	 */
	manageElements: function(elements) {
		this.options.elements = this.options.elements.concat(elements);
		this.unloadedElements = this.unloadedElements.concat(elements);

		this.fetchElementCoordinates();
		this.fetchOriginalSrc();
		this.manageImages();
	},

	fetchContainerCoordinates: function() {
		var containerScroll = this.options.container.getScroll();
		var containerSize = this.options.container.getSize();

		this.containerCoordinates = {
			bottom: containerScroll.y + containerSize.y + this.options.maxDistance,
			left: containerScroll.x - this.options.maxDistance,
			right: containerScroll.x + containerSize.x + this.options.maxDistance,
			top: containerScroll.y - this.options.maxDistance
		}
	},

	fetchElementCoordinates: function() {
		this.loadedElementsCoordinates = [];
		this.unloadedElementsCoordinates = [];

		for (var i = 0; i < this.loadedElements.length; i++) {
			this.loadedElementsCoordinates.push(this.loadedElements[i].getCoordinates());
		}

		for (var i = 0; i < this.unloadedElements.length; i++) {
			this.unloadedElementsCoordinates.push(this.unloadedElements[i].getCoordinates());
		}
	},

	fetchOriginalSrc: function() {
		for (var i = 0; i < this.options.elements.length; i++) {
			this.options.elements[i].store("originalSrc", this.options.elements[i].getProperty("src"));
		}
	},

	manageImages: function() {
		this.cancelQueuedDownloads();
		this.unloadLoadedElements();
		this.loadUnloadedElements();
		this.loadConcurrently();
	},

	cancelQueuedDownloads: function() {
		for (var i = 0; i < this.timeoutIds.length; i++) {
			clearTimeout(this.timeoutIds[i]);
		}

		this.timeoutIds = [];
	},

	unloadLoadedElements: function() {
		for (var i = 0; i < this.loadedElements.length; i++) {
			if (!this.elementIsVisible(this.loadedElementsCoordinates[i], this.containerCoordinates)) {
				this.loadedElements[i].setProperty("src", this.loadedElements[i].retrieve("originalSrc"));
				this.unloadedElements.push(this.loadedElements.splice(i, 1)[0]);
				this.unloadedElementsCoordinates.push(this.loadedElementsCoordinates.splice(i, 1)[0]);
				i--;
			}
		}
	},

	loadUnloadedElements: function() {
		this.elementsToLoad = [];

		for (var i = 0; i < this.unloadedElements.length; i++) {
			if (this.elementIsVisible(this.unloadedElementsCoordinates[i], this.containerCoordinates)) {
				this.elementsToLoad.push(this.unloadedElements[i]);
			}
		}
	},

	loadConcurrently: function() {
		for (var i = this.concurrentDownloads; i < this.options.maxConcurrentDownloads; i++) {
			this.loadNextImage();
		}
	},

	/**
	 * Loads the next image.
	 */
	loadNextImage: function() {
		if (this.elementsToLoad.length === 0) {
			return;
		}

		this.loadImage(this.elementsToLoad.splice(0, 1)[0]);
	},

	/**
	 * Loads a particular image.
	 *
	 * Fires a class-wide "load" event if the image loaded, otherwise an "error"
	 * event. Also fires a class-wide "complete" event if it was the last image
	 * to load.
	 * @param integer index Position in the array "this.options.elements"
	 */
	loadImage: function(imageElement) {
		var image = new Image();

		image.addEvent("error", this.imageEventHandler.bind(this, "error", imageElement));
		image.addEvent("load", this.imageEventHandler.bind(this, "load", imageElement));

		this.concurrentDownloads++;

		var index = this.unloadedElements.indexOf(imageElement);
		this.loadedElements.push(this.unloadedElements.splice(index, 1)[0]);
		this.loadedElementsCoordinates.push(this.unloadedElementsCoordinates.splice(index, 1)[0]);

		// Start loading the image
		image.src = imageElement.getProperty(this.options.dataAttribute);
	},

	imageEventHandler: function(eventName, imageElement) {
		this.concurrentDownloads--;

		// Relay the image event
		this.fireEvent(eventName, imageElement);

		// If this was the last image to load, fire the "complete" event
		if (++this.loadedImagesCount === this.options.elements.length) {
			this.fireEvent("complete");
			return;
		}

		// Queue next image
		var timeoutId = this.loadNextImage.bind(this).delay(this.options.delay);
		this.timeoutIds.push(timeoutId);
	},

	elementIsVisible: function(imageElementCoordinates, containerCoordinates) {
		return imageElementCoordinates.bottom >= containerCoordinates.top
			&& imageElementCoordinates.top <= containerCoordinates.bottom
			&& imageElementCoordinates.right >= containerCoordinates.left
			&& imageElementCoordinates.left <= containerCoordinates.right;
	}
});<commit_msg>Fixed problem where onComplete would fire incorrectly.<commit_after>// ImageLoader class. Scroll to bottom for example usage.
var ImageLoader = new Class({
	Implements: [Events, Options],

	// Default options
	options: {
		container: window,

		// Name of the "img" elements' data attribute that stores the image URL
		dataAttribute: "data-src",

		// Delay between loading two images (in milliseconds)
		delay: 0,

		// Array of "img" elements whose content we want to lazy load
		elements: [],

		// Maximum number of concurrent downloads
		maxConcurrentDownloads: 4,

		// Maximum distance that images can be away from the visible part of the
		// container and still be loaded
		maxDistance: 500,

		// CSS class that is used to hide empty image elements from people with
		// JavaScript disabled
		noJsClass: "no-js",

		onComplete: function() {
			console.log("Loading images completed.");
		},

		onError: function(element) {
			console.log("Image failed to load: " + element.getProperty(this.options.dataAttribute));
			element.getParent().destroy();
		},

		onLoad: function(element) {
			console.log("Image loaded: " + element.getProperty(this.options.dataAttribute));
			element.setProperty("src", element.getProperty(this.options.dataAttribute));
		}
	},

	initialize: function(options) {
		// Merge provided options with default options
		this.setOptions(options);

		// Make hidden image elements visible for people with JavaScript enabled
		$(document.body).removeClass(this.options.noJsClass);

		// Number of loaded images
		this.loadedImagesCount = 0;

		// Flag for whether all images were loaded
		this.completed = false;

		// Number of running downloads
		this.concurrentDownloads = 0;

		// Timeout IDs of delayed functions (queued downloads)
		this.timeoutIds = [];

		this.loadedElements = [];
		this.unloadedElements = this.options.elements.slice();

		this.loadedElementsCoordinates = [];
		this.unloadedElementsCoordinates = [];

		this.elementsToLoad = [];
		this.containerCoordinates = {};
	},

	/**
	 * Starts the image loader.
	 */
	run: function() {
		this.fetchContainerCoordinates();
		this.fetchElementCoordinates();
		this.fetchOriginalSrc();
		this.manageImages();

		window.addEvent("resize", function() {
			if (this.resizeTimeoutId) {
				clearTimeout(this.resizeTimeoutId);
			}

			this.resizeTimeoutId = function() {
				this.fetchContainerCoordinates();
				this.fetchElementCoordinates();
				this.manageImages();
			}.bind(this).delay(50);
		}.bind(this));

		window.addEvent("scroll", function() {
			if (this.scrollTimeoutId) {
				clearTimeout(this.scrollTimeoutId);
			}

			this.scrollTimeoutId = function() {
				this.fetchContainerCoordinates();
				this.manageImages();
			}.bind(this).delay(50);
		}.bind(this));
	},

	/**
	 * Gives image loader control over provided image elements.
	 */
	manageElements: function(elements) {
		this.completed = false;

		this.options.elements = this.options.elements.concat(elements);
		this.unloadedElements = this.unloadedElements.concat(elements);

		this.fetchElementCoordinates();
		this.fetchOriginalSrc();
		this.manageImages();
	},

	fetchContainerCoordinates: function() {
		var containerScroll = this.options.container.getScroll();
		var containerSize = this.options.container.getSize();

		this.containerCoordinates = {
			bottom: containerScroll.y + containerSize.y + this.options.maxDistance,
			left: containerScroll.x - this.options.maxDistance,
			right: containerScroll.x + containerSize.x + this.options.maxDistance,
			top: containerScroll.y - this.options.maxDistance
		}
	},

	fetchElementCoordinates: function() {
		this.loadedElementsCoordinates = [];
		this.unloadedElementsCoordinates = [];

		for (var i = 0; i < this.loadedElements.length; i++) {
			this.loadedElementsCoordinates.push(this.loadedElements[i].getCoordinates());
		}

		for (var i = 0; i < this.unloadedElements.length; i++) {
			this.unloadedElementsCoordinates.push(this.unloadedElements[i].getCoordinates());
		}
	},

	fetchOriginalSrc: function() {
		for (var i = 0; i < this.options.elements.length; i++) {
			this.options.elements[i].store("originalSrc", this.options.elements[i].getProperty("src"));
		}
	},

	manageImages: function() {
		this.cancelQueuedDownloads();
		this.unloadLoadedElements();
		this.loadUnloadedElements();
		this.loadConcurrently();
	},

	cancelQueuedDownloads: function() {
		for (var i = 0; i < this.timeoutIds.length; i++) {
			clearTimeout(this.timeoutIds[i]);
		}

		this.timeoutIds = [];
	},

	unloadLoadedElements: function() {
		for (var i = 0; i < this.loadedElements.length; i++) {
			if (!this.elementIsVisible(this.loadedElementsCoordinates[i], this.containerCoordinates)) {
				this.loadedElements[i].setProperty("src", this.loadedElements[i].retrieve("originalSrc"));
				this.unloadedElements.push(this.loadedElements.splice(i, 1)[0]);
				this.unloadedElementsCoordinates.push(this.loadedElementsCoordinates.splice(i, 1)[0]);
				i--;
			}
		}
	},

	loadUnloadedElements: function() {
		this.elementsToLoad = [];

		for (var i = 0; i < this.unloadedElements.length; i++) {
			if (this.elementIsVisible(this.unloadedElementsCoordinates[i], this.containerCoordinates)) {
				this.elementsToLoad.push(this.unloadedElements[i]);
			}
		}
	},

	loadConcurrently: function() {
		for (var i = this.concurrentDownloads; i < this.options.maxConcurrentDownloads; i++) {
			this.loadNextImage();
		}
	},

	/**
	 * Loads the next image.
	 */
	loadNextImage: function() {
		if (this.elementsToLoad.length === 0) {
			return;
		}

		this.loadImage(this.elementsToLoad.splice(0, 1)[0]);
	},

	/**
	 * Loads a particular image.
	 *
	 * Fires a class-wide "load" event if the image loaded, otherwise an "error"
	 * event. Also fires a class-wide "complete" event if it was the last image
	 * to load.
	 * @param integer index Position in the array "this.options.elements"
	 */
	loadImage: function(imageElement) {
		var image = new Image();

		image.addEvent("error", this.imageEventHandler.bind(this, "error", imageElement));
		image.addEvent("load", this.imageEventHandler.bind(this, "load", imageElement));

		this.concurrentDownloads++;

		var index = this.unloadedElements.indexOf(imageElement);
		this.loadedElements.push(this.unloadedElements.splice(index, 1)[0]);
		this.loadedElementsCoordinates.push(this.unloadedElementsCoordinates.splice(index, 1)[0]);

		// Start loading the image
		image.src = imageElement.getProperty(this.options.dataAttribute);
	},

	imageEventHandler: function(eventName, imageElement) {
		this.concurrentDownloads--;

		// Relay the image event
		this.fireEvent(eventName, imageElement);

		// Mark image as loaded
		if (!imageElement.retrieve("loaded")) {
			imageElement.store("loaded", true);
			this.loadedImagesCount++;
		}

		// If this was the last image to load, fire the "complete" event
		if (this.loadedImagesCount === this.options.elements.length
				&& !this.completed) {
			this.completed = true;
			this.fireEvent("complete");
			return;
		}

		// Queue next image
		var timeoutId = this.loadNextImage.bind(this).delay(this.options.delay);
		this.timeoutIds.push(timeoutId);
	},

	elementIsVisible: function(imageElementCoordinates, containerCoordinates) {
		return imageElementCoordinates.bottom >= containerCoordinates.top
			&& imageElementCoordinates.top <= containerCoordinates.bottom
			&& imageElementCoordinates.right >= containerCoordinates.left
			&& imageElementCoordinates.left <= containerCoordinates.right;
	}
});<|endoftext|>