<commit_before>var _ = require('underscore');
var jsyaml = require('js-yaml');
var queue = require('queue-async');

var Backbone = require('backbone');
var File = require('../models/file');
var Folder = require('../models/folder');

var cookie = require('../cookie');
var util = require('../util');
var ignore = require('ignore');

module.exports = Backbone.Collection.extend({
  model: function(attributes, options) {
    // TODO: handle 'symlink' and 'submodule' type
    // TODO: coerce tree/folder to a single type
    switch(attributes.type) {
      case 'tree':
        return new Folder(attributes, options);
        break;
      case 'blob':
        return new File(attributes, options);
        break;
      default:
        return new File(attributes, options);
        break;
    }
  },

  initialize: function(models, options) {
    _.bindAll(this);

    this.repo = options.repo;
    this.branch = options.branch;
    this.sha = options.sha;

    // Sort files reverse alphabetically if path begins with '_posts/'
    this.comparator = function(a, b) {
      var typeA = a.get('type');
      var typeB = b.get('type');

      var pathA = a.get('path');
      var pathB = b.get('path');

      var regex = /^_posts\/.*$/

      if (typeA === typeB && typeA === 'file' && regex.test(pathA) && regex.test(pathB)) {
        // Reverse alphabetical
        return pathA < pathB ? 1 : -1;
      } else if (typeA === typeB) {
        // Alphabetical
        return pathA < pathB ? -1 : 1;
      } else {
        switch(typeA) {
          case 'tree':
          case 'folder':
            return -1;
            break;
          case 'file':
            return typeB === 'folder' || typeB === 'tree' ? 1 : -1;
            break;
        }
      }
    };
  },

  parse: function(resp, options) {
    return _.map(resp.tree, (function(file) {
      return  _.extend(file, {
        branch: this.branch,
        collection: this,
        repo: this.repo
      })
    }).bind(this));
  },

  parseConfig: function(config, options) {
    var content = config.get('content');

    // Attempt to parse YAML
    try {
      config = jsyaml.safeLoad(content);
    } catch(err) {
      console.log("Error parsing YAML");
      console.log(err);
    }

    if (config && config.prose) {
      // Load _config.yml, set parsed value on collection
      // Extend to capture settings from outside config.prose
      // while allowing override
      this.config = _.extend({
        baseurl: config.baseurl,
        languages: config.languages
      }, config.prose);

      if (config.prose.ignore) {
        this.parseIgnore(config.prose.ignore);
      }

      if (config.prose.metadata) {
        var metadata = config.prose.metadata;

        // Serial queue to not break global scope JSONP callbacks
        var q = queue(1);

        _.each(metadata, function(raw, key) {
          q.defer(function(cb) {
            var subq = queue();
            var defaults;

            if (_.isObject(raw)) {
              defaults = raw;

              _.each(defaults, function(value, key) {
                var regex = /^https?:\/\//;

                // Parse JSON URL values
                if (value && value.field && value.field.options &&
                    _.isString(value.field.options) &&
                    regex.test(value.field.options)) {

                  subq.defer(function(cb) {
                    $.ajax({
                      cache: true,
                      dataType: 'jsonp',
                      jsonp: false,
                      jsonpCallback: value.field.options.split('?callback=')[1] || 'callback',
                      timeout: 5000,
                      url: value.field.options,
                      success: (function(d) {
                        value.field.options = _.compact(d);
                        cb();
                      }).bind(this)
                    });
                  });
                }

                if (value && value.field && value.field.value === "CURRENT_DATETIME") {
                  value.field.value = (new Date()).format('Y-m-d H:i O');
                }
              });
            } else if (_.isString(raw)) {
              try {
                defaults = jsyaml.safeLoad(raw);

                if (defaults.date === "CURRENT_DATETIME") {
                  var current = (new Date()).format('Y-m-d H:i O');
                  defaults.date = current;
                  raw = raw.replace("CURRENT_DATETIME", current);
                }
              } catch(err) {
                console.log("Error parsing default values.");
                console.log(err);
              }
            }

            subq.awaitAll(function() {
              metadata[key] = defaults;
              cb();
            });
          });
        });

        q.awaitAll((function() {
          // Save parsed config to the collection as it's used accross
          // files of the same collection and shouldn't be re-parsed each time
          this.defaults = metadata;

          if (_.isFunction(options.success)) options.success.apply(this, options.args);
        }).bind(this));
      } else {
        if (_.isFunction(options.success)) options.success.apply(this, options.args);
      }
    } else {
      if (_.isFunction(options.success)) options.success.apply(this, options.args);
    }
  },

  parseIgnore: function(ignorePatterns) {
    var ignoreFilter = ignore().addPattern(ignorePatterns).createFilter();
    this.filteredModel = new Backbone.Collection(this.filter(function(file) {
      return ignoreFilter(file.id);
    }));
  },

  fetch: function(options) {
    options = _.clone(options) || {};

    var success = options.success;
    var args = options.args;

    Backbone.Collection.prototype.fetch.call(this, _.extend(options, {
      success: (function(model, res, options) {
        var config = this.findWhere({ path: '_prose.yml' }) ||
          this.findWhere({ path: '_config.yml' });

        if (config) {
          config.fetch({
            success: (function() {
              this.parseConfig(config, { success: success, args: args });
            }).bind(this)
          });
        } else {
          if (_.isFunction(success)) success.apply(this, args);
        }

      }).bind(this)
    }));
  },

  restore: function(file, options) {
    options = options ? _.clone(options) : {};

    var path = file.filename;
    var success = options.success;

    $.ajax({
      type: 'GET',
      url: file.contents_url,
      headers: {
        Accept: 'application/vnd.github.v3.raw'
      },
      success: (function(res) {
        // initialize new File model with content
        var model = new File({
          branch: this.branch,
          collection: this,
          content: res,
          path: path,
          repo: this.repo
        });

        var name = util.extractFilename(path)[1];
        model.set('placeholder', t('actions.commits.created', { filename: name }));

        // add to collection on save
        model.save({
          success: (function(model, res, options) {
            // Update model attributes and add to collection
            model.set(res.content);
            this.add(model);

            if (_.isFunction(success)) success(model, res, options);
          }).bind(this),
          error: options.error
        });
      }).bind(this),
      error: options.error
    });
  },

  upload: function(file, content, path, options) {
    var success = options.success;

    var extension = file.type.split('/').pop();
    var uid;

    if (!path) {
      uid = file.name;

      if (this.assetsDirectory) {
        path = this.assetsDirectory + '/' + uid;
      } else {
        path = this.model.path ? this.model.path + '/' + uid : uid;
      }
    }

    // If path matches an existing file, confirm the overwrite is intentional
    // then set new content and update the existing file
    var model = this.findWhere({ path: path });

    if (model) {
      // TODO: confirm overwrite with UI prompt
      model.set('content', content);
      model.set('placeholder', t('actions.commits.updated', { filename: file.name }));
    } else {
      // initialize new File model with content
      model = new File({
        branch: this.branch,
        collection: this,
        content: content,
        path: path,
        repo: this.repo
      });

      model.set('placeholder', t('actions.commits.created', { filename: file.name }));
    }

    // add to collection on save
    model.save({
      success: (function(model, res, options) {
        // Update model attributes and add to collection
        model.set(res.content);
        this.add(model);

        if (_.isFunction(success)) success(model, res, options);
      }).bind(this),
      error: options.error
    });
  },

  url: function() {
    return this.repo.url() + '/git/trees/' + this.sha + '?recursive=1';
  }
});
<commit_msg>Extend config object with _config.yml<commit_after>var _ = require('underscore');
var jsyaml = require('js-yaml');
var queue = require('queue-async');

var Backbone = require('backbone');
var File = require('../models/file');
var Folder = require('../models/folder');

var cookie = require('../cookie');
var util = require('../util');
var ignore = require('ignore');

module.exports = Backbone.Collection.extend({
  model: function(attributes, options) {
    // TODO: handle 'symlink' and 'submodule' type
    // TODO: coerce tree/folder to a single type
    switch(attributes.type) {
      case 'tree':
        return new Folder(attributes, options);
        break;
      case 'blob':
        return new File(attributes, options);
        break;
      default:
        return new File(attributes, options);
        break;
    }
  },

  initialize: function(models, options) {
    _.bindAll(this);

    this.repo = options.repo;
    this.branch = options.branch;
    this.sha = options.sha;

    // Sort files reverse alphabetically if path begins with '_posts/'
    this.comparator = function(a, b) {
      var typeA = a.get('type');
      var typeB = b.get('type');

      var pathA = a.get('path');
      var pathB = b.get('path');

      var regex = /^_posts\/.*$/

      if (typeA === typeB && typeA === 'file' && regex.test(pathA) && regex.test(pathB)) {
        // Reverse alphabetical
        return pathA < pathB ? 1 : -1;
      } else if (typeA === typeB) {
        // Alphabetical
        return pathA < pathB ? -1 : 1;
      } else {
        switch(typeA) {
          case 'tree':
          case 'folder':
            return -1;
            break;
          case 'file':
            return typeB === 'folder' || typeB === 'tree' ? 1 : -1;
            break;
        }
      }
    };
  },

  parse: function(resp, options) {
    return _.map(resp.tree, (function(file) {
      return  _.extend(file, {
        branch: this.branch,
        collection: this,
        repo: this.repo
      })
    }).bind(this));
  },

  parseConfig: function(config, options) {
    var content = config.get('content');

    // Attempt to parse YAML
    try {
      config = jsyaml.safeLoad(content);
    } catch(err) {
      console.log("Error parsing YAML");
      console.log(err);
    }

    if (config && config.prose) {
      // Load _config.yml, set parsed value on collection
      // Extend to capture settings from outside config.prose
      // while allowing override
      this.config = _.extend(config, {
        baseurl: config.baseurl,
        languages: config.languages
      }, config.prose);

      if (config.prose.ignore) {
        this.parseIgnore(config.prose.ignore);
      }

      if (config.prose.metadata) {
        var metadata = config.prose.metadata;

        // Serial queue to not break global scope JSONP callbacks
        var q = queue(1);

        _.each(metadata, function(raw, key) {
          q.defer(function(cb) {
            var subq = queue();
            var defaults;

            if (_.isObject(raw)) {
              defaults = raw;

              _.each(defaults, function(value, key) {
                var regex = /^https?:\/\//;

                // Parse JSON URL values
                if (value && value.field && value.field.options &&
                    _.isString(value.field.options) &&
                    regex.test(value.field.options)) {

                  subq.defer(function(cb) {
                    $.ajax({
                      cache: true,
                      dataType: 'jsonp',
                      jsonp: false,
                      jsonpCallback: value.field.options.split('?callback=')[1] || 'callback',
                      timeout: 5000,
                      url: value.field.options,
                      success: (function(d) {
                        value.field.options = _.compact(d);
                        cb();
                      }).bind(this)
                    });
                  });
                }

                if (value && value.field && value.field.value === "CURRENT_DATETIME") {
                  value.field.value = (new Date()).format('Y-m-d H:i O');
                }
              });
            } else if (_.isString(raw)) {
              try {
                defaults = jsyaml.safeLoad(raw);

                if (defaults.date === "CURRENT_DATETIME") {
                  var current = (new Date()).format('Y-m-d H:i O');
                  defaults.date = current;
                  raw = raw.replace("CURRENT_DATETIME", current);
                }
              } catch(err) {
                console.log("Error parsing default values.");
                console.log(err);
              }
            }

            subq.awaitAll(function() {
              metadata[key] = defaults;
              cb();
            });
          });
        });

        q.awaitAll((function() {
          // Save parsed config to the collection as it's used accross
          // files of the same collection and shouldn't be re-parsed each time
          this.defaults = metadata;

          if (_.isFunction(options.success)) options.success.apply(this, options.args);
        }).bind(this));
      } else {
        if (_.isFunction(options.success)) options.success.apply(this, options.args);
      }
    } else {
      if (_.isFunction(options.success)) options.success.apply(this, options.args);
    }
  },

  parseIgnore: function(ignorePatterns) {
    var ignoreFilter = ignore().addPattern(ignorePatterns).createFilter();
    this.filteredModel = new Backbone.Collection(this.filter(function(file) {
      return ignoreFilter(file.id);
    }));
  },

  fetch: function(options) {
    options = _.clone(options) || {};

    var success = options.success;
    var args = options.args;

    Backbone.Collection.prototype.fetch.call(this, _.extend(options, {
      success: (function(model, res, options) {
        var config = this.findWhere({ path: '_prose.yml' }) ||
          this.findWhere({ path: '_config.yml' });

        if (config) {
          config.fetch({
            success: (function() {
              this.parseConfig(config, { success: success, args: args });
            }).bind(this)
          });
        } else {
          if (_.isFunction(success)) success.apply(this, args);
        }

      }).bind(this)
    }));
  },

  restore: function(file, options) {
    options = options ? _.clone(options) : {};

    var path = file.filename;
    var success = options.success;

    $.ajax({
      type: 'GET',
      url: file.contents_url,
      headers: {
        Accept: 'application/vnd.github.v3.raw'
      },
      success: (function(res) {
        // initialize new File model with content
        var model = new File({
          branch: this.branch,
          collection: this,
          content: res,
          path: path,
          repo: this.repo
        });

        var name = util.extractFilename(path)[1];
        model.set('placeholder', t('actions.commits.created', { filename: name }));

        // add to collection on save
        model.save({
          success: (function(model, res, options) {
            // Update model attributes and add to collection
            model.set(res.content);
            this.add(model);

            if (_.isFunction(success)) success(model, res, options);
          }).bind(this),
          error: options.error
        });
      }).bind(this),
      error: options.error
    });
  },

  upload: function(file, content, path, options) {
    var success = options.success;

    var extension = file.type.split('/').pop();
    var uid;

    if (!path) {
      uid = file.name;

      if (this.assetsDirectory) {
        path = this.assetsDirectory + '/' + uid;
      } else {
        path = this.model.path ? this.model.path + '/' + uid : uid;
      }
    }

    // If path matches an existing file, confirm the overwrite is intentional
    // then set new content and update the existing file
    var model = this.findWhere({ path: path });

    if (model) {
      // TODO: confirm overwrite with UI prompt
      model.set('content', content);
      model.set('placeholder', t('actions.commits.updated', { filename: file.name }));
    } else {
      // initialize new File model with content
      model = new File({
        branch: this.branch,
        collection: this,
        content: content,
        path: path,
        repo: this.repo
      });

      model.set('placeholder', t('actions.commits.created', { filename: file.name }));
    }

    // add to collection on save
    model.save({
      success: (function(model, res, options) {
        // Update model attributes and add to collection
        model.set(res.content);
        this.add(model);

        if (_.isFunction(success)) success(model, res, options);
      }).bind(this),
      error: options.error
    });
  },

  url: function() {
    return this.repo.url() + '/git/trees/' + this.sha + '?recursive=1';
  }
});
<|endoftext|>


<commit_before>/*global define*/
define([
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Color'
    ],function(
        defaultValue,
        defined,
        Cartesian2,
        Cartesian3,
        Color) {
    "use strict";

    var Particle = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this.mass = defaultValue(options.mass, 1.0);
        this.position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
        this.velocity = Cartesian3.clone(defaultValue(options.velocity, Cartesian3.ZERO));
        this.life = defaultValue(options.life, Number.MAX_VALUE);
        this.image = options.image;
        this.age = 0.0;
        this.normalizedAge = 0.0;

        this.startColor = defaultValue(options.startColor, Color.WHITE);
        this.endColor = defaultValue(options.endColor, Color.WHITE);

        this.startScale = defaultValue(options.startScale);
        this.endScale = defaultValue(options.endScale);

        var size = Cartesian2.clone(options.size);
        if (!defined(size)) {
            size = new Cartesian2(1.0, 1.0);
        }

        this.size = size;
    };

    var deltaScratch = new Cartesian3();

    Particle.prototype.update = function(forces, dt) {

        // Apply the velocity
        Cartesian3.multiplyByScalar(this.velocity, dt, deltaScratch);
        Cartesian3.add(this.position, deltaScratch, this.position);

        // Update any forces.
        var length = forces.length;
        for (var i = 0; i < length; ++i) {
            var force = forces[i];

            if (typeof force === 'function') {
                // Force is just a simle callback function.
                force(this, dt);
            } else {
                // Call the apply function of the force.
                force.apply(this, dt);
            }
        }

        // Age the particle
        this.age += dt;

        // Compute the normalized age.
        if (this.life === Number.MAX_VALUE) {
            this.normalizedAge = 0.0;
        } else {
            this.normalizedAge = this.age / this.life;
        }

        // If this particle is older than it's lifespan then die.
        return this.age <= this.life;
    };

    return Particle;
});
<commit_msg>Checking for undefined forces<commit_after>/*global define*/
define([
        '../Core/defaultValue',
        '../Core/defined',
        '../Core/Cartesian2',
        '../Core/Cartesian3',
        '../Core/Color'
    ],function(
        defaultValue,
        defined,
        Cartesian2,
        Cartesian3,
        Color) {
    "use strict";

    var Particle = function(options) {
        options = defaultValue(options, defaultValue.EMPTY_OBJECT);

        this.mass = defaultValue(options.mass, 1.0);
        this.position = Cartesian3.clone(defaultValue(options.position, Cartesian3.ZERO));
        this.velocity = Cartesian3.clone(defaultValue(options.velocity, Cartesian3.ZERO));
        this.life = defaultValue(options.life, Number.MAX_VALUE);
        this.image = options.image;
        this.age = 0.0;
        this.normalizedAge = 0.0;

        this.startColor = defaultValue(options.startColor, Color.WHITE);
        this.endColor = defaultValue(options.endColor, Color.WHITE);

        this.startScale = defaultValue(options.startScale);
        this.endScale = defaultValue(options.endScale);

        var size = Cartesian2.clone(options.size);
        if (!defined(size)) {
            size = new Cartesian2(1.0, 1.0);
        }

        this.size = size;
    };

    var deltaScratch = new Cartesian3();

    Particle.prototype.update = function(forces, dt) {

        // Apply the velocity
        Cartesian3.multiplyByScalar(this.velocity, dt, deltaScratch);
        Cartesian3.add(this.position, deltaScratch, this.position);

        // Update any forces.
        if (forces) {
            var length = forces.length;
            for (var i = 0; i < length; ++i) {
                var force = forces[i];

                if (typeof force === 'function') {
                    // Force is just a simle callback function.
                    force(this, dt);
                } else {
                    // Call the apply function of the force.
                    force.apply(this, dt);
                }
            }
        }

        // Age the particle
        this.age += dt;

        // Compute the normalized age.
        if (this.life === Number.MAX_VALUE) {
            this.normalizedAge = 0.0;
        } else {
            this.normalizedAge = this.age / this.life;
        }

        // If this particle is older than it's lifespan then die.
        return this.age <= this.life;
    };

    return Particle;
});
<|endoftext|>


<commit_before>import { Class } from '../../core/Core.js';
import { View } from '../View.js';

import /* { property, nextFrame } from */ '../../foundation/Decorators.js';

const ListItemView = Class({
    Name: 'ListItemView',

    Extends: View,

    content: null,

    index: 0,
    itemLayout: 0,

    selection: null,
    isSelected: false,

    animateIn: false,

    init: function (mixin) {
        const selection = mixin.selection;
        const content = mixin.content;
        if (selection && content) {
            this.isSelected = selection.isStoreKeySelected(
                content.get('storeKey'),
            );
        }
        ListItemView.parent.constructor.call(this, mixin);
    },

    positioning: 'absolute',

    layout: function () {
        const listView = this.get('parentView');
        let top = listView.indexToOffset(this.get('index'));
        const animateIn = this.get('animateIn');
        const isNew = animateIn && !this.get('isInDocument');
        if (isNew) {
            top -= listView.get('itemHeight');
        }

        return {
            top,
            opacity: animateIn ? (isNew ? 0 : 1) : undefined,
        };
    }.property(),

    layoutWillChange: function () {
        this.computedPropertyDidChange('layout');
    }
        .nextLoop()
        .observes('index', 'itemLayout'),

    resetLayout: function () {
        if (this.get('animateIn')) {
            this.computedPropertyDidChange('layout');
        }
    }
        .nextLoop()
        .observes('isInDocument'),
});

export { ListItemView };
<commit_msg>Only delay invalidating list layout if animating<commit_after>import { Class } from '../../core/Core.js';
import { invokeInNextEventLoop } from '../../foundation/RunLoop.js';
import { View } from '../View.js';

import /* { property, nextFrame } from */ '../../foundation/Decorators.js';

const ListItemView = Class({
    Name: 'ListItemView',

    Extends: View,

    content: null,

    index: 0,
    itemLayout: 0,

    selection: null,
    isSelected: false,

    animateIn: false,

    init: function (mixin) {
        const selection = mixin.selection;
        const content = mixin.content;
        if (selection && content) {
            this.isSelected = selection.isStoreKeySelected(
                content.get('storeKey'),
            );
        }
        ListItemView.parent.constructor.call(this, mixin);
    },

    positioning: 'absolute',

    layout: function () {
        const listView = this.get('parentView');
        let top = listView.indexToOffset(this.get('index'));
        const animateIn = this.get('animateIn');
        const isNew = animateIn && !this.get('isInDocument');
        if (isNew) {
            top -= listView.get('itemHeight');
        }

        return {
            top,
            opacity: animateIn ? (isNew ? 0 : 1) : undefined,
        };
    }.property(),

    invalidateLayout() {
        this.computedPropertyDidChange('layout');
    },

    layoutWillChange: function () {
        if (this.get('animateLayer')) {
            invokeInNextEventLoop(this.invalidateLayout, this);
        } else {
            this.invalidateLayout();
        }
    }.observes('index', 'itemLayout'),

    resetLayout: function () {
        if (this.get('animateIn')) {
            this.invalidateLayout();
        }
    }
        .nextLoop()
        .observes('isInDocument'),
});

export { ListItemView };
<|endoftext|>


<commit_before>"use strict";

describe('treebank persister', function() {
  var documentStore;
  var TreebankPersister;
  var state;
  var idHandler;
  var docId = 'some-treebank';
  var noop = function() {};

  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getServices: function(name) {
      return [];
    },
    provideResource: function(name) {
      return {
        'save' : function() {
          return { then: function() {} };
        }
      };
    }
  };

  var s2 = {
    "tokens": {
      "0001": {
        id: '0001',
        head: {
          id: ''
        },
      },
      "0002": {
        id: '0002',
        head: {
          id: '0000'
        },
      }
    },
    id: '2'
  };

  beforeEach(module('arethusa', function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  beforeEach(inject(function(
    _documentStore_, _TreebankPersister_, _state_, _idHandler_, _navigator_) {
    TreebankPersister = _TreebankPersister_;
    documentStore = _documentStore_;
    state = _state_;
    idHandler = _idHandler_;
    _navigator_.init();
    _navigator_.addSentences([s2]);
    documentStore.reset();
    documentStore.addDocument(docId, {
      json: {
        "treebank": {
          "sentence": {
            "_id": 2,
            "word": [
              {
              "_id": "1",
              "_form": "Coniurandi",
              "_lemma": "conjuro1",
              "_postag": "t-spdang-",
              "_head": "",
              "_relation": "ATR"
            },
            {
              "_id": "2",
              "_form": "has",
              "_lemma": "hic1",
              "_postag": "p-p---fa-",
              "_head": "0",
              "_relation": "PNOM"
            }]
          }
        }
      }
    });

    var t1 = s2.tokens['0001'];
    var t2 = s2.tokens['0002'];

    var m1 = new idHandler.Map();
    var m2 = new idHandler.Map();

    m1.add(docId, '0001', '1');
    m2.add(docId, '0002', '2');

    t1.idMap = m1;
    t2.idMap = m2;

    state.replaceState(s2.tokens);
  }));

  describe('this.saveData()', function() {
    var conf, persister;

    beforeEach(function() {
      conf = {
        'resource' : 'test-resource',
        'docIdentifier' : docId
      };
      persister = new TreebankPersister(conf);
    });

    it('saves data when when changes are present', function() {
      expect(persister).toBeDefined();

      state.change('0001', 'head.id', '0002');

      var updatedDoc = documentStore.store[docId];
      persister.saveData(function() {});

      expect(updatedDoc).toBeDefined();
      expect(updatedDoc.json.treebank.sentence.word[0]._head).toBe('2');
    });

    it('does not update when chunk is not marked as changed', function() {
      expect(persister).toBeDefined();

      var updatedDoc = documentStore.store[docId];
      var firstHead = function() {
        return updatedDoc.json.treebank.sentence.word[0]._head;
      };
      var valBeforeSave = firstHead();

      persister.saveData(noop);

      expect(updatedDoc).toBeDefined();
      expect(firstHead()).toBe(valBeforeSave);
    });

    describe('with newly added tokens', function() {
      var aT1, aT2;

      beforeEach(function() {
        function ArtificialToken(id, str) {
          this.id = id;
          this.string = str;
          this.type = 'artificial';
          this.artificial = true;
          this.sentenceId = '2';
          this.idMap = new idHandler.Map();
        }

        aT1 = new ArtificialToken('0002e', [1]);
        aT2 = new ArtificialToken('0002f', [2]);
      });

      function parse(xml) {
        return arethusaUtil.xml2json(xml);
      }

      function areIdsSequential(words) {
        var last = words[words.length - 1];
        var sequential = true;
        for (var i=0; i < words.length; i++) {
          var word = words[i];
          if (word !== last) {
            var next = words[i + 1];
            if ((parseInt(word._id) + 1) !== parseInt(next._id)) {
              sequential = false;
              break;
            }
          }
        }
        return sequential;
      }

      it('handles insertion of one artificial tokens properly', function() {
        state.addToken(aT1, aT1.token);

        var doc = documentStore.store[docId];
        var words = doc.json.treebank.sentence.word;

        expect(words.length).toEqual(2);
        persister.saveData(noop);

        expect(words.length).toEqual(3);

        // check if token made it to the xml by reparsing and checking it
        var fromXml = parse(doc.xml);
        var newWords = fromXml.treebank.sentence.word;

        expect(newWords.length).toEqual(3);

        // new id of the inserted token should be sequential
        expect(areIdsSequential(newWords)).toBeTruthy();
      });

      it('handles insertion of two artificial tokens properly', function() {
        state.addToken(aT1, aT1.id);
        state.addToken(aT2, aT2.id);

        var doc = documentStore.store[docId];
        var words = doc.json.treebank.sentence.word;

        expect(words.length).toEqual(2);
        persister.saveData(noop);

        expect(words.length).toEqual(4);

        // check if token made it to the xml by reparsing and checking it
        var newWords = parse(doc.xml).treebank.sentence.word;

        expect(newWords.length).toEqual(4);

        // new id of the inserted token should be sequential
        expect(areIdsSequential(newWords)).toBeTruthy();

        // resaving is not destroying the document
        persister.saveData(noop);

        var updatedWords = parse(doc.xml).treebank.sentence.word;
        expect(updatedWords.length).toEqual(4);
        expect(areIdsSequential(updatedWords)).toBeTruthy();
      });
    });
  });
});
<commit_msg>Minor refactor in persister spec<commit_after>"use strict";

describe('treebank persister', function() {
  var documentStore;
  var TreebankPersister;
  var state;
  var idHandler;
  var docId = 'some-treebank';
  var noop = function() {};

  var mockConfigurator = {
    configurationFor: function(name) {
      return {};
    },
    getServices: function(name) {
      return [];
    },
    provideResource: function(name) {
      return {
        'save' : function() {
          return { then: function() {} };
        }
      };
    }
  };

  var t1 = {
    id: '0001',
    head: {
      id: ''
    },
  };

  var t2 = {
    id: '0002',
    head: {
      id: '0000'
    },
  };
  var s2 = {
    "tokens": {
      "0001": t1,
      "0002": t2
    },
    id: '2'
  };

  beforeEach(module('arethusa', function($provide) {
    $provide.value('configurator', mockConfigurator);
  }));

  beforeEach(inject(function(
    _documentStore_, _TreebankPersister_, _state_, _idHandler_, _navigator_) {
    TreebankPersister = _TreebankPersister_;
    documentStore = _documentStore_;
    state = _state_;
    idHandler = _idHandler_;
    _navigator_.init();
    _navigator_.addSentences([s2]);
    documentStore.reset();
    documentStore.addDocument(docId, {
      json: {
        "treebank": {
          "sentence": {
            "_id": 2,
            "word": [
              {
              "_id": "1",
              "_form": "Coniurandi",
              "_lemma": "conjuro1",
              "_postag": "t-spdang-",
              "_head": "",
              "_relation": "ATR"
            },
            {
              "_id": "2",
              "_form": "has",
              "_lemma": "hic1",
              "_postag": "p-p---fa-",
              "_head": "0",
              "_relation": "PNOM"
            }]
          }
        }
      }
    });

    var m1 = new idHandler.Map();
    var m2 = new idHandler.Map();

    m1.add(docId, '0001', '1');
    m2.add(docId, '0002', '2');

    t1.idMap = m1;
    t2.idMap = m2;

    state.replaceState(s2.tokens);
  }));

  describe('this.saveData()', function() {
    var conf, persister;

    beforeEach(function() {
      conf = {
        'resource' : 'test-resource',
        'docIdentifier' : docId
      };
      persister = new TreebankPersister(conf);
    });

    it('saves data when when changes are present', function() {
      expect(persister).toBeDefined();

      state.change('0001', 'head.id', '0002');

      var updatedDoc = documentStore.store[docId];
      persister.saveData(function() {});

      expect(updatedDoc).toBeDefined();
      expect(updatedDoc.json.treebank.sentence.word[0]._head).toBe('2');
    });

    it('does not update when chunk is not marked as changed', function() {
      expect(persister).toBeDefined();

      var updatedDoc = documentStore.store[docId];
      var firstHead = function() {
        return updatedDoc.json.treebank.sentence.word[0]._head;
      };
      var valBeforeSave = firstHead();

      persister.saveData(noop);

      expect(updatedDoc).toBeDefined();
      expect(firstHead()).toBe(valBeforeSave);
    });

    describe('with newly added tokens', function() {
      var aT1, aT2;

      beforeEach(function() {
        function ArtificialToken(id, str) {
          this.id = id;
          this.string = str;
          this.type = 'artificial';
          this.artificial = true;
          this.sentenceId = '2';
          this.idMap = new idHandler.Map();
        }

        aT1 = new ArtificialToken('0002e', '[1]');
        aT2 = new ArtificialToken('0002f', '[2]');
      });

      function parse(xml) {
        return arethusaUtil.xml2json(xml);
      }

      function areIdsSequential(words) {
        var last = words[words.length - 1];
        var sequential = true;
        for (var i=0; i < words.length; i++) {
          var word = words[i];
          if (word !== last) {
            var next = words[i + 1];
            if ((parseInt(word._id) + 1) !== parseInt(next._id)) {
              sequential = false;
              break;
            }
          }
        }
        return sequential;
      }

      it('handles insertion of one artificial tokens properly', function() {
        state.addToken(aT1, aT1.token);

        var doc = documentStore.store[docId];
        var words = doc.json.treebank.sentence.word;

        expect(words.length).toEqual(2);
        persister.saveData(noop);

        expect(words.length).toEqual(3);

        // check if token made it to the xml by reparsing and checking it
        var fromXml = parse(doc.xml);
        var newWords = fromXml.treebank.sentence.word;

        expect(newWords.length).toEqual(3);

        // new id of the inserted token should be sequential
        expect(areIdsSequential(newWords)).toBeTruthy();
      });

      it('handles insertion of two artificial tokens properly', function() {
        state.addToken(aT1, aT1.id);
        state.addToken(aT2, aT2.id);

        var doc = documentStore.store[docId];
        var words = doc.json.treebank.sentence.word;

        expect(words.length).toEqual(2);
        persister.saveData(noop);

        expect(words.length).toEqual(4);

        var testArtificialTokens = function(words) {
          expect(words.length).toEqual(4);
          // new id of the inserted token should be sequential
          expect(areIdsSequential(words)).toBeTruthy();
        };

        // check if token made it to the xml by reparsing and checking it
        var newWords = parse(doc.xml).treebank.sentence.word;

        testArtificialTokens(newWords);

        // resaving is not destroying the document
        persister.saveData(noop);

        var updatedWords = parse(doc.xml).treebank.sentence.word;
        testArtificialTokens(updatedWords);

        // and resaving is not destroying the document after a change has been made
        state.change('0001', 'head.id', '0000');
        persister.saveData(noop);

        updatedWords = parse(doc.xml).treebank.sentence.word;
        testArtificialTokens(updatedWords);
      });
    });
  });
});
<|endoftext|>


<commit_before><commit_msg>Fixed travis task<commit_after><|endoftext|>


<commit_before>/*!
 * freezeframe.js v3.0.8
 * MIT License
 */

// make generic private trigger and release functions
//
// add class to image css to support image ready / active
// hide gif when canvas is active
//
// write function to test for features needed, write failure to console
// if unsupported, attach simple image replacement
// fallback image needed
//
// test compatibility with browserstack using feature test function
//
// make warn method public
//
// pass references around in a cleaner way

var freezeframe = (function($) {

  var images, options, is_touch_device;

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Private Methods                                                         //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////

  // decorated console.warn message
  var warn = function(_message) {
    console.warn('✨ freezeframe.js ✨ : ' + _message);
  }

  // does freezeframe instance have any captured images?
  var has_images = function() {
    return this.images.length == 0 ? false : true;
  }

  // filter captured images by selector and warn if none found
  var filter = function(_selector, _images) {
    var filtered_images;

    if(_selector != undefined && _images.length > 1) {
      filtered_images = _images.filter( $(_selector) );
      if (filtered_images.length == 0) {
        warn('no images found for selector "' + _selector + '"')
        return false;
      }
    } else {
      filtered_images = _images;
    }

    return filtered_images;
  }

  // reset .gif to first frame and write to canvas
  var process = function ($_image) {
    var ff = this,
      $canvas = $_image.siblings('canvas'),
      transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend',
      image_width = $_image[0].clientWidth,
      image_height = $_image[0].clientHeight;

    $canvas.attr({
      'width': image_width,
      'height': image_height
    });

    context = $canvas[0].getContext('2d');
    context.drawImage($_image[0], 0, 0, image_width, image_height);

    $canvas.addClass('ff-canvas-ready').on(transitionEnd, function() {
      $(this).off(transitionEnd);
      $_image.addClass('ff-image-ready');

      // remove the loading icon style from the container
      $_image.parent().removeClass('ff-loading-icon');
    });
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Constructor                                                             //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  function freezeframe(_options) {
    var options;

    // default options
    this.options = {
      selector : '.freezeframe',
      animation_play_duration: 5000,
      non_touch_device_trigger_event: 'hover'
    }

    // new selector as string
    options = typeof _options == 'string' ? { 'selector': _options } : _options;

    // new options
    if(options) {
      for (attribute in options) {
        if (attribute in this.options) {
          this.options[attribute] = options[attribute]
        } else {
          warn(attribute + 'not a valid option')
        }
      }
    }

    // is this a touch device?
    this.is_touch_device = ('ontouchstart' in window || 'onmsgesturechange' in window);
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Capture Images                                                          //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.capture = function(_selector) {
    var selector;

    // Passed in string or default string
    if(_selector !== undefined) {
      selector = _selector;
    } else if (this.options.selector !== undefined) {
      selector = this.options.selector;
    } else {
      warn('no selector passed to capture function or set in options')
      return false;
    }

    // Empty jQuery/Zepto object to add into
    if(this.images == undefined) {
      this.images = $();
    }

    // Add new selection, jQuery/Zepto keeps it non redundant
    this.images = this.images.add( $('img' + selector) );

    // Get non gifs outta there
    for (i = 0; i < this.images.length; i++) {
      if (this.images[i].src.split('.').pop().toLowerCase().substring(0, 3) !== 'gif') {
        this.images.splice(i, 1);
      }
    }

    // If nothing was found, throw a fit
    if(this.images.length == 0) {
      warn("no gifs found for selector '" + selector + "'");
      return false;
    }

    return this;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Setup Elements                                                          //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.setup = function(_selector) {
    var ff = this,
      setup_required = this.images.not('.ff-setup'),
      container_classnames = ['ff-container', 'ff-loading-icon'];

    if(!has_images.call(ff)) {
      warn('unable to run setup(), no images captured')
      return false;
    } else if(setup_required.length == 0) {
      warn('unable to run setup(), no images require setup')
      return false;
    }

    filter.call(ff, _selector, setup_required).each(function(e) {
      var $image = $(this);

      $image.addClass('ff-setup ff-image');

      if($image.hasClass('freezeframe-responsive')) {
        container_classnames.push('ff-responsive');
      }

      $canvas = $('<canvas />', {
        class: 'ff-canvas'
      }).attr({
        width: 0,
        height: 0
      });

      var $group = $image.add($canvas);

      $group.wrapAll(
        $('<div />', {
          class: container_classnames.join(' ')
        })
      );

      $canvas.insertBefore($image);

    });

    imagesLoaded(setup_required).on('progress', function(instance, image) {
      process.call(ff, $(image.img));
    });

    return this;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Attach Hover / Click Events                                             //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.attach = function(_selector) {
    var ff = this,
      click_timeout,
      images;

    if(!has_images.call(ff)) {
      warn('unable to run attach(), no images captured')
      return false;
    }

    filter.call(ff, _selector, ff.images).each(function(e) {

      var $image = $(this);
      var $canvas = $(this).siblings('canvas');

      // hover
      if((!ff.is_touch_device && ff.options.non_touch_device_trigger_event == 'hover') || (ff.is_touch_device)) {

        $image.on('mouseenter', function() {
          (function() {

            if($image.hasClass('ff-image-ready')) {
              $image.attr('src', $image[0].src);
              $canvas.removeClass('ff-canvas-ready').addClass('ff-canvas-active');
            }

          })();
        })

        $image.on('mouseleave', function() {
          (function() {

            if($image.hasClass('ff-image-ready')) {
              $canvas.removeClass('ff-canvas-active').addClass('ff-canvas-ready');
            }

          })();
        })
      }

      // click
      if((!ff.is_touch_device && ff.options.non_touch_device_trigger_event == 'click') || (ff.is_touch_device)) {

        var click_timeout;

        $image.on('click', function() {

          (function() {
            var clicked = $canvas.hasClass('ff-canvas-active');

            if($image.hasClass('ff-image-ready')) {

              if(clicked) {

                if(ff.options.animation_play_duration != Infinity) {
                  clearTimeout(click_timeout);
                }

                $canvas.removeClass('ff-canvas-active').addClass('ff-canvas-ready');

              } else {

                $image.attr('src', $image[0].src);
                $canvas.removeClass('ff-canvas-ready').addClass('ff-canvas-active');

                if(ff.options.animation_play_duration != Infinity) {
                  click_timeout = setTimeout(function() {
                    $canvas.removeClass('ff-canvas-active').addClass('ff-canvas-ready');
                  }, ff.options.animation_play_duration);
                }
              }
            }
          })();
        })
      }
    })

    return this;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Trigger Animation                                                       //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.trigger = function(_selector) {
    var ff = this,
      errors = 0;

    filter.call(ff, _selector, ff.images).each(function(e) {

      if($(this).hasClass('ff-image-ready')) {
        $(this).attr('src', $(this)[0].src);
        $(this).siblings('canvas').removeClass('ff-canvas-ready').addClass('ff-canvas-active');
      } else {
        warn('image not done processing ! ' + $(this).attr('src'));
        errors ++;
      }

    });

    return errors == 0 ? true : false;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Release Animation                                                       //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.release = function(_selector) {
    var ff = this,
      errors = 0;

    filter.call(ff, _selector, ff.images).each(function(e) {
      if($(this).hasClass('ff-image-ready')) {
        $(this).siblings('canvas').removeClass('ff-canvas-active').addClass('ff-canvas-ready');
      } else {
        warn('image not done processing ! ' + $(this).attr('src'));
        errors ++;
      }
    });

    return errors == 0 ? true : false;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Freeze Images                                                           //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.freeze = function() {
    this.capture().setup().attach(); // ✨ tada ✨
    return this;
  }

  return freezeframe;
})($);

// jQuery/Zepto plugin
$.fn.freezeframe = function(_options) {

  if (this.length == 0) {
    console.warn('✨ freezeframe.js ✨ : no images found for selector ' + this.selector);
    return false;
  }

  var ff = new freezeframe(_options);

  ff.images = this;

  ff.setup().attach();

  var self = this;
  var methods = ['trigger', 'release'];
  methods.forEach(function(method) {
    self[method] = function() {
      ff[method](self.selector);
      return self;
    };
  });

  return this;
};
<commit_msg>comment cleanup<commit_after>/*!
 * freezeframe.js v3.0.8
 * MIT License
 */

var freezeframe = (function($) {

  var images, options, is_touch_device;

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Private Methods                                                         //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////

  // decorated console.warn message
  var warn = function(_message) {
    console.warn('✨ freezeframe.js ✨ : ' + _message);
  }

  // does freezeframe instance have any captured images?
  var has_images = function() {
    return this.images.length == 0 ? false : true;
  }

  // filter captured images by selector and warn if none found
  var filter = function(_selector, _images) {
    var filtered_images;

    if(_selector != undefined && _images.length > 1) {
      filtered_images = _images.filter( $(_selector) );
      if (filtered_images.length == 0) {
        warn('no images found for selector "' + _selector + '"')
        return false;
      }
    } else {
      filtered_images = _images;
    }

    return filtered_images;
  }

  // reset .gif to first frame and write to canvas
  var process = function ($_image) {
    var ff = this,
      $canvas = $_image.siblings('canvas'),
      transitionEnd = 'transitionend webkitTransitionEnd oTransitionEnd otransitionend',
      image_width = $_image[0].clientWidth,
      image_height = $_image[0].clientHeight;

    $canvas.attr({
      'width': image_width,
      'height': image_height
    });

    context = $canvas[0].getContext('2d');
    context.drawImage($_image[0], 0, 0, image_width, image_height);

    $canvas.addClass('ff-canvas-ready').on(transitionEnd, function() {
      $(this).off(transitionEnd);
      $_image.addClass('ff-image-ready');

      // remove the loading icon style from the container
      $_image.parent().removeClass('ff-loading-icon');
    });
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Constructor                                                             //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  function freezeframe(_options) {
    var options;

    // default options
    this.options = {
      selector : '.freezeframe',
      animation_play_duration: 5000,
      non_touch_device_trigger_event: 'hover'
    }

    // new selector as string
    options = typeof _options == 'string' ? { 'selector': _options } : _options;

    // new options
    if(options) {
      for (attribute in options) {
        if (attribute in this.options) {
          this.options[attribute] = options[attribute]
        } else {
          warn(attribute + 'not a valid option')
        }
      }
    }

    // is this a touch device?
    this.is_touch_device = ('ontouchstart' in window || 'onmsgesturechange' in window);
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Capture Images                                                          //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.capture = function(_selector) {
    var selector;

    // Passed in string or default string
    if(_selector !== undefined) {
      selector = _selector;
    } else if (this.options.selector !== undefined) {
      selector = this.options.selector;
    } else {
      warn('no selector passed to capture function or set in options')
      return false;
    }

    // Empty jQuery/Zepto object to add into
    if(this.images == undefined) {
      this.images = $();
    }

    // Add new selection, jQuery/Zepto keeps it non redundant
    this.images = this.images.add( $('img' + selector) );

    // Get non gifs outta there
    for (i = 0; i < this.images.length; i++) {
      if (this.images[i].src.split('.').pop().toLowerCase().substring(0, 3) !== 'gif') {
        this.images.splice(i, 1);
      }
    }

    // If nothing was found, throw a fit
    if(this.images.length == 0) {
      warn("no gifs found for selector '" + selector + "'");
      return false;
    }

    return this;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Setup Elements                                                          //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.setup = function(_selector) {
    var ff = this,
      setup_required = this.images.not('.ff-setup'),
      container_classnames = ['ff-container', 'ff-loading-icon'];

    if(!has_images.call(ff)) {
      warn('unable to run setup(), no images captured')
      return false;
    } else if(setup_required.length == 0) {
      warn('unable to run setup(), no images require setup')
      return false;
    }

    filter.call(ff, _selector, setup_required).each(function(e) {
      var $image = $(this);

      $image.addClass('ff-setup ff-image');

      if($image.hasClass('freezeframe-responsive')) {
        container_classnames.push('ff-responsive');
      }

      $canvas = $('<canvas />', {
        class: 'ff-canvas'
      }).attr({
        width: 0,
        height: 0
      });

      var $group = $image.add($canvas);

      $group.wrapAll(
        $('<div />', {
          class: container_classnames.join(' ')
        })
      );

      $canvas.insertBefore($image);

    });

    imagesLoaded(setup_required).on('progress', function(instance, image) {
      process.call(ff, $(image.img));
    });

    return this;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Attach Hover / Click Events                                             //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.attach = function(_selector) {
    var ff = this,
      click_timeout,
      images;

    if(!has_images.call(ff)) {
      warn('unable to run attach(), no images captured')
      return false;
    }

    filter.call(ff, _selector, ff.images).each(function(e) {

      var $image = $(this);
      var $canvas = $(this).siblings('canvas');

      // hover
      if((!ff.is_touch_device && ff.options.non_touch_device_trigger_event == 'hover') || (ff.is_touch_device)) {

        $image.on('mouseenter', function() {
          (function() {

            if($image.hasClass('ff-image-ready')) {
              $image.attr('src', $image[0].src);
              $canvas.removeClass('ff-canvas-ready').addClass('ff-canvas-active');
            }

          })();
        })

        $image.on('mouseleave', function() {
          (function() {

            if($image.hasClass('ff-image-ready')) {
              $canvas.removeClass('ff-canvas-active').addClass('ff-canvas-ready');
            }

          })();
        })
      }

      // click
      if((!ff.is_touch_device && ff.options.non_touch_device_trigger_event == 'click') || (ff.is_touch_device)) {

        var click_timeout;

        $image.on('click', function() {

          (function() {
            var clicked = $canvas.hasClass('ff-canvas-active');

            if($image.hasClass('ff-image-ready')) {

              if(clicked) {

                if(ff.options.animation_play_duration != Infinity) {
                  clearTimeout(click_timeout);
                }

                $canvas.removeClass('ff-canvas-active').addClass('ff-canvas-ready');

              } else {

                $image.attr('src', $image[0].src);
                $canvas.removeClass('ff-canvas-ready').addClass('ff-canvas-active');

                if(ff.options.animation_play_duration != Infinity) {
                  click_timeout = setTimeout(function() {
                    $canvas.removeClass('ff-canvas-active').addClass('ff-canvas-ready');
                  }, ff.options.animation_play_duration);
                }
              }
            }
          })();
        })
      }
    })

    return this;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Trigger Animation                                                       //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.trigger = function(_selector) {
    var ff = this,
      errors = 0;

    filter.call(ff, _selector, ff.images).each(function(e) {

      if($(this).hasClass('ff-image-ready')) {
        $(this).attr('src', $(this)[0].src);
        $(this).siblings('canvas').removeClass('ff-canvas-ready').addClass('ff-canvas-active');
      } else {
        warn('image not done processing ! ' + $(this).attr('src'));
        errors ++;
      }

    });

    return errors == 0 ? true : false;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Release Animation                                                       //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.release = function(_selector) {
    var ff = this,
      errors = 0;

    filter.call(ff, _selector, ff.images).each(function(e) {
      if($(this).hasClass('ff-image-ready')) {
        $(this).siblings('canvas').removeClass('ff-canvas-active').addClass('ff-canvas-ready');
      } else {
        warn('image not done processing ! ' + $(this).attr('src'));
        errors ++;
      }
    });

    return errors == 0 ? true : false;
  }

  //////////////////////////////////////////////////////////////////////////////
  //                                                                          //
  //  Freeze Images                                                           //
  //                                                                          //
  //////////////////////////////////////////////////////////////////////////////
  freezeframe.prototype.freeze = function() {
    this.capture().setup().attach(); // ✨ tada ✨
    return this;
  }

  return freezeframe;
})($);

// jQuery/Zepto plugin
$.fn.freezeframe = function(_options) {

  if (this.length == 0) {
    console.warn('✨ freezeframe.js ✨ : no images found for selector ' + this.selector);
    return false;
  }

  var ff = new freezeframe(_options);

  ff.images = this;

  ff.setup().attach();

  var self = this;
  var methods = ['trigger', 'release'];
  methods.forEach(function(method) {
    self[method] = function() {
      ff[method](self.selector);
      return self;
    };
  });

  return this;
};
<|endoftext|>


<commit_before>/**
 * @author jbouny / https://github.com/jbouny
 *
 * Work based on :
 * @author Slayvin / http://slayvin.net : Flat mirror for three.js
 * @author Stemkoski / http://www.adelphi.edu/~stemkoski : An implementation of water shader based on the flat mirror
 * @author Jonas Wagner / http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL
 */

THREE.ShaderLib['water'] = {

	uniforms: { "normalSampler":	{ type: "t", value: null },
				"mirrorSampler":	{ type: "t", value: null },
				"alpha":			{ type: "f", value: 1.0 },
				"time":				{ type: "f", value: 0.0 },
				"distortionScale":	{ type: "f", value: 20.0 },
				"textureMatrix" :	{ type: "m4", value: new THREE.Matrix4() },
				"sunColor":			{ type: "c", value: new THREE.Color( 0x7F7F7F ) },
				"sunDirection":		{ type: "v3", value: new THREE.Vector3( 0.70707, 0.70707, 0 ) },
				"eye":				{ type: "v3", value: new THREE.Vector3( 0, 0, 0 ) },
				"waterColor":		{ type: "c", value: new THREE.Color( 0x555555 ) }
	},

	vertexShader: [
		'uniform mat4 textureMatrix;',
		'uniform float time;',

		'varying vec4 mirrorCoord;',
		'varying vec3 worldPosition;',

		'void main()',
		'{',
		'	mirrorCoord = modelMatrix * vec4( position, 1.0 );',
		'	worldPosition = mirrorCoord.xyz;',
		'	mirrorCoord = textureMatrix * mirrorCoord;',
		'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
		'}'
	].join('\n'),

	fragmentShader: [
		'precision highp float;',

		'uniform sampler2D mirrorSampler;',
		'uniform float alpha;',
		'uniform float time;',
		'uniform float distortionScale;',
		'uniform sampler2D normalSampler;',
		'uniform vec3 sunColor;',
		'uniform vec3 sunDirection;',
		'uniform vec3 eye;',
		'uniform vec3 waterColor;',

		'varying vec4 mirrorCoord;',
		'varying vec3 worldPosition;',

		'vec4 getNoise( vec2 uv )',
		'{',
		'	vec2 uv0 = ( uv / 103.0 ) + vec2(time / 17.0, time / 29.0);',
		'	vec2 uv1 = uv / 107.0-vec2( time / -19.0, time / 31.0 );',
		'	vec2 uv2 = uv / vec2( 8907.0, 9803.0 ) + vec2( time / 101.0, time / 97.0 );',
		'	vec2 uv3 = uv / vec2( 1091.0, 1027.0 ) - vec2( time / 109.0, time / -113.0 );',
		'	vec4 noise = ( texture2D( normalSampler, uv0 ) ) +',
        '		( texture2D( normalSampler, uv1 ) ) +',
        '		( texture2D( normalSampler, uv2 ) ) +',
		'		( texture2D( normalSampler, uv3 ) );',
		'	return noise * 0.5 - 1.0;',
		'}',

		'void sunLight( const vec3 surfaceNormal, const vec3 eyeDirection, float shiny, float spec, float diffuse, inout vec3 diffuseColor, inout vec3 specularColor )',
		'{',
		'	vec3 reflection = normalize( reflect( -sunDirection, surfaceNormal ) );',
		'	float direction = max( 0.0, dot( eyeDirection, reflection ) );',
		'	specularColor += pow( direction, shiny ) * sunColor * spec;',
		'	diffuseColor += max( dot( sunDirection, surfaceNormal ), 0.0 ) * sunColor * diffuse;',
		'}',

		'void main()',
		'{',
		'	vec4 noise = getNoise( worldPosition.xz );',
		'	vec3 surfaceNormal = normalize( noise.xzy * vec3( 1.5, 1.0, 1.5 ) );',

		'	vec3 diffuseLight = vec3(0.0);',
		'	vec3 specularLight = vec3(0.0);',

		'	vec3 worldToEye = eye-worldPosition;',
		'	vec3 eyeDirection = normalize( worldToEye );',
		'	sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );',

		'	float distance = length(worldToEye);',

		'	vec2 distortion = surfaceNormal.xz * ( 0.001 + 1.0 / distance ) * distortionScale;',
		'	vec3 reflectionSample = vec3( texture2D( mirrorSampler, mirrorCoord.xy / mirrorCoord.z + distortion ) );',

		'	float theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );',
		'	float rf0 = 0.3;',
		'	float reflectance = rf0 + ( 1.0 - rf0 ) * pow( ( 1.0 - theta ), 5.0 );',
		'	vec3 scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ) * waterColor;',
		'	vec3 albedo = mix( sunColor * diffuseLight * 0.3 + scatter, ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance );',
		'	gl_FragColor = vec4( albedo, alpha );',
		'}'
	].join('\n')

};

THREE.Water = function ( renderer, camera, scene, options ) {

	THREE.Object3D.call( this );
	this.name = 'water_' + this.id;

	function isPowerOfTwo ( value ) {
		return ( value & ( value - 1 ) ) === 0;
	};
	function optionalParameter ( value, defaultValue ) {
		return value !== undefined ? value : defaultValue;
	};

	options = options || {};

	this.matrixNeedsUpdate = true;

	var width = optionalParameter( options.textureWidth, 512 );
	var height = optionalParameter( options.textureHeight, 512 );
	this.clipBias = optionalParameter( options.clipBias, 0.0 );
	this.alpha = optionalParameter( options.alpha, 1.0 );
	this.time = optionalParameter( options.time, 0.0 );
	this.normalSampler = optionalParameter( options.waterNormals, null );
	this.sunDirection = optionalParameter( options.sunDirection, new THREE.Vector3( 0.70707, 0.70707, 0.0 ) );
	this.sunColor = new THREE.Color( optionalParameter( options.sunColor, 0xffffff ) );
	this.waterColor = new THREE.Color( optionalParameter( options.waterColor, 0x7F7F7F ) );
	this.eye = optionalParameter( options.eye, new THREE.Vector3( 0, 0, 0 ) );
	this.distortionScale = optionalParameter( options.distortionScale, 20.0 );

	this.renderer = renderer;
	this.scene = scene;
	this.mirrorPlane = new THREE.Plane();
	this.normal = new THREE.Vector3( 0, 0, 1 );
	this.mirrorWorldPosition = new THREE.Vector3();
	this.cameraWorldPosition = new THREE.Vector3();
	this.rotationMatrix = new THREE.Matrix4();
	this.lookAtPosition = new THREE.Vector3( 0, 0, -1 );
	this.clipPlane = new THREE.Vector4();

	if ( camera instanceof THREE.PerspectiveCamera )
		this.camera = camera;
	else
	{
		this.camera = new THREE.PerspectiveCamera();
		console.log(this.name + ': camera is not a Perspective Camera!')
	}

	this.textureMatrix = new THREE.Matrix4();

	this.mirrorCamera = this.camera.clone();

	this.texture = new THREE.WebGLRenderTarget( width, height );
	this.tempTexture = new THREE.WebGLRenderTarget( width, height );

	var mirrorShader = THREE.ShaderLib[ "water" ];
	var mirrorUniforms = THREE.UniformsUtils.clone( mirrorShader.uniforms );

	this.material = new THREE.ShaderMaterial( {
		fragmentShader: mirrorShader.fragmentShader,
		vertexShader: mirrorShader.vertexShader,
		uniforms: mirrorUniforms,
		transparent: true
	} );

	this.material.uniforms.mirrorSampler.value = this.texture;
	this.material.uniforms.textureMatrix.value = this.textureMatrix;
	this.material.uniforms.alpha.value = this.alpha;
	this.material.uniforms.time.value = this.time;
	this.material.uniforms.normalSampler.value = this.normalSampler;
	this.material.uniforms.sunColor.value = this.sunColor;
	this.material.uniforms.waterColor.value = this.waterColor;
	this.material.uniforms.sunDirection.value = this.sunDirection;
	this.material.uniforms.distortionScale.value = this.distortionScale;

	this.material.uniforms.eye.value = this.eye;

	if ( !isPowerOfTwo(width) || !isPowerOfTwo(height) )
	{
		this.texture.generateMipmaps = false;
		this.tempTexture.generateMipmaps = false;
	}

	this.updateTextureMatrix();
	this.render();
};

THREE.Water.prototype = Object.create( THREE.Object3D.prototype );

THREE.Water.prototype.renderWithMirror = function (otherMirror) {

	// update the mirror matrix to mirror the current view
	this.updateTextureMatrix();
	this.matrixNeedsUpdate = false;

	// set the camera of the other mirror so the mirrored view is the reference view
	var tempCamera = otherMirror.camera;
	otherMirror.camera = this.mirrorCamera;

	// render the other mirror in temp texture
	otherMirror.renderTemp();
	otherMirror.material.uniforms.mirrorSampler.value = otherMirror.tempTexture;

	// render the current mirror
	this.render();
	this.matrixNeedsUpdate = true;

	// restore material and camera of other mirror
	otherMirror.material.uniforms.mirrorSampler.value = otherMirror.texture;
	otherMirror.camera = tempCamera;

	// restore texture matrix of other mirror
	otherMirror.updateTextureMatrix();
};

THREE.Water.prototype.updateTextureMatrix = function () {

	function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }

	this.updateMatrixWorld();
	this.camera.updateMatrixWorld();

	this.mirrorWorldPosition.getPositionFromMatrix( this.matrixWorld );
	this.cameraWorldPosition.getPositionFromMatrix( this.camera.matrixWorld );

	this.rotationMatrix.extractRotation( this.matrixWorld );

	this.normal.set( 0, 0, 1 );
	this.normal.applyMatrix4( this.rotationMatrix );

	var view = this.mirrorWorldPosition.clone().sub( this.cameraWorldPosition );
	var reflectView = view.reflect( this.normal );
	reflectView.add( this.mirrorWorldPosition );

	this.rotationMatrix.extractRotation( this.camera.matrixWorld );

	this.lookAtPosition.set(0, 0, -1);
	this.lookAtPosition.applyMatrix4( this.rotationMatrix );
	this.lookAtPosition.add( this.cameraWorldPosition );

	var target = this.mirrorWorldPosition.clone().sub( this.lookAtPosition );
	var reflectTarget = target.reflect( this.normal );
	reflectTarget.add( this.mirrorWorldPosition );

	this.up.set(0, -1, 0);
	this.up.applyMatrix4( this.rotationMatrix );
	var reflectUp = this.up.reflect( this.normal );

	this.mirrorCamera.position.copy(reflectView);
	this.mirrorCamera.up = reflectUp;
	this.mirrorCamera.lookAt(reflectTarget);
	this.mirrorCamera.aspect = this.camera.aspect;

	this.mirrorCamera.updateProjectionMatrix();
	this.mirrorCamera.updateMatrixWorld();
	this.mirrorCamera.matrixWorldInverse.getInverse(this.mirrorCamera.matrixWorld);

	// Update the texture matrix
	this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5,
							0.0, 0.5, 0.0, 0.5,
							0.0, 0.0, 0.5, 0.5,
							0.0, 0.0, 0.0, 1.0 );
	this.textureMatrix.multiply(this.mirrorCamera.projectionMatrix);
	this.textureMatrix.multiply(this.mirrorCamera.matrixWorldInverse);

	// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
	// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
	this.mirrorPlane.setFromNormalAndCoplanarPoint( this.normal, this.mirrorWorldPosition );
	this.mirrorPlane.applyMatrix4(this.mirrorCamera.matrixWorldInverse);

	this.clipPlane.set(this.mirrorPlane.normal.x, this.mirrorPlane.normal.y, this.mirrorPlane.normal.z, this.mirrorPlane.constant );

	var q = new THREE.Vector4();
	var projectionMatrix = this.mirrorCamera.projectionMatrix;

	q.x = (sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
	q.y = (sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
	q.z = -1.0;
	q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];

	// Calculate the scaled plane vector
	var c = new THREE.Vector4();
	c = this.clipPlane.multiplyScalar( 2.0 / this.clipPlane.dot(q) );

	// Replacing the third row of the projection matrix
	projectionMatrix.elements[2] = c.x;
	projectionMatrix.elements[6] = c.y;
	projectionMatrix.elements[10] = c.z + 1.0 - this.clipBias;
	projectionMatrix.elements[14] = c.w;

	var worldCoordinates = new THREE.Vector3();
	worldCoordinates.getPositionFromMatrix( this.camera.matrixWorld );
	this.eye = worldCoordinates;
	this.material.uniforms.eye.value = this.eye;
};

THREE.Water.prototype.render = function () {

	if( this.matrixNeedsUpdate )
		this.updateTextureMatrix();

	this.matrixNeedsUpdate = true;

	// Render the mirrored view of the current scene into the target texture
	if( this.scene !== undefined && this.scene instanceof THREE.Scene )
	{
        this.renderer.render( this.scene, this.mirrorCamera, this.texture, true );
	}

};

THREE.Water.prototype.renderTemp = function () {

	if( this.matrixNeedsUpdate )
		this.updateTextureMatrix();

	this.matrixNeedsUpdate = true;

	// Render the mirrored view of the current scene into the target texture
	if( this.scene !== undefined && this.scene instanceof THREE.Scene)
	{
		this.renderer.render( this.scene, this.mirrorCamera, this.tempTexture, true );
	}

};<commit_msg>Add possibility to control the scale of the noise (to adapt the water to the scale of the world)<commit_after>/**
 * @author jbouny / https://github.com/jbouny
 *
 * Work based on :
 * @author Slayvin / http://slayvin.net : Flat mirror for three.js
 * @author Stemkoski / http://www.adelphi.edu/~stemkoski : An implementation of water shader based on the flat mirror
 * @author Jonas Wagner / http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL
 */

THREE.ShaderLib['water'] = {

	uniforms: { "normalSampler":	{ type: "t", value: null },
				"mirrorSampler":	{ type: "t", value: null },
				"alpha":			{ type: "f", value: 1.0 },
				"time":				{ type: "f", value: 0.0 },
				"distortionScale":	{ type: "f", value: 20.0 },
				"noiseScale":		{ type: "f", value: 1.0 },
				"textureMatrix" :	{ type: "m4", value: new THREE.Matrix4() },
				"sunColor":			{ type: "c", value: new THREE.Color( 0x7F7F7F ) },
				"sunDirection":		{ type: "v3", value: new THREE.Vector3( 0.70707, 0.70707, 0 ) },
				"eye":				{ type: "v3", value: new THREE.Vector3( 0, 0, 0 ) },
				"waterColor":		{ type: "c", value: new THREE.Color( 0x555555 ) }
	},

	vertexShader: [
		'uniform mat4 textureMatrix;',
		'uniform float time;',

		'varying vec4 mirrorCoord;',
		'varying vec3 worldPosition;',

		'void main()',
		'{',
		'	mirrorCoord = modelMatrix * vec4( position, 1.0 );',
		'	worldPosition = mirrorCoord.xyz;',
		'	mirrorCoord = textureMatrix * mirrorCoord;',
		'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
		'}'
	].join('\n'),

	fragmentShader: [
		'precision highp float;',

		'uniform sampler2D mirrorSampler;',
		'uniform float alpha;',
		'uniform float time;',
		'uniform float distortionScale;',
		'uniform float noiseScale;',
		'uniform sampler2D normalSampler;',
		'uniform vec3 sunColor;',
		'uniform vec3 sunDirection;',
		'uniform vec3 eye;',
		'uniform vec3 waterColor;',

		'varying vec4 mirrorCoord;',
		'varying vec3 worldPosition;',

		'vec4 getNoise( vec2 uv )',
		'{',
		'	vec2 uv0 = uv / ( 103.0 * noiseScale ) + vec2(time / 17.0, time / 29.0);',
		'	vec2 uv1 = uv / ( 107.0 * noiseScale ) - vec2( time / -19.0, time / 31.0 );',
		'	vec2 uv2 = uv / ( vec2( 8907.0, 9803.0 ) * noiseScale ) + vec2( time / 101.0, time /  097.0 );',
		'	vec2 uv3 = uv / ( vec2( 1091.0, 1027.0 ) * noiseScale ) - vec2( time / 109.0, time / -113.0 );',
		'	vec4 noise = ( texture2D( normalSampler, uv0 ) ) +',
        '		( texture2D( normalSampler, uv1 ) ) +',
        '		( texture2D( normalSampler, uv2 ) ) +',
		'		( texture2D( normalSampler, uv3 ) );',
		'	return noise * 0.5 - 1.0;',
		'}',

		'void sunLight( const vec3 surfaceNormal, const vec3 eyeDirection, float shiny, float spec, float diffuse, inout vec3 diffuseColor, inout vec3 specularColor )',
		'{',
		'	vec3 reflection = normalize( reflect( -sunDirection, surfaceNormal ) );',
		'	float direction = max( 0.0, dot( eyeDirection, reflection ) );',
		'	specularColor += pow( direction, shiny ) * sunColor * spec;',
		'	diffuseColor += max( dot( sunDirection, surfaceNormal ), 0.0 ) * sunColor * diffuse;',
		'}',

		'void main()',
		'{',
		'	vec4 noise = getNoise( worldPosition.xz );',
		'	vec3 surfaceNormal = normalize( noise.xzy * vec3( 1.5, 1.0, 1.5 ) );',

		'	vec3 diffuseLight = vec3(0.0);',
		'	vec3 specularLight = vec3(0.0);',

		'	vec3 worldToEye = eye-worldPosition;',
		'	vec3 eyeDirection = normalize( worldToEye );',
		'	sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );',

		'	float distance = length(worldToEye);',

		'	vec2 distortion = surfaceNormal.xz * ( 0.001 + 1.0 / distance ) * distortionScale;',
		'	vec3 reflectionSample = vec3( texture2D( mirrorSampler, mirrorCoord.xy / mirrorCoord.z + distortion ) );',

		'	float theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );',
		'	float rf0 = 0.3;',
		'	float reflectance = rf0 + ( 1.0 - rf0 ) * pow( ( 1.0 - theta ), 5.0 );',
		'	vec3 scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ) * waterColor;',
		'	vec3 albedo = mix( sunColor * diffuseLight * 0.3 + scatter, ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance );',
		'	gl_FragColor = vec4( albedo, alpha );',
		'}'
	].join('\n')

};

THREE.Water = function ( renderer, camera, scene, options ) {

	THREE.Object3D.call( this );
	this.name = 'water_' + this.id;

	function isPowerOfTwo ( value ) {
		return ( value & ( value - 1 ) ) === 0;
	};
	function optionalParameter ( value, defaultValue ) {
		return value !== undefined ? value : defaultValue;
	};

	options = options || {};

	this.matrixNeedsUpdate = true;

	var width = optionalParameter( options.textureWidth, 512 );
	var height = optionalParameter( options.textureHeight, 512 );
	this.clipBias = optionalParameter( options.clipBias, 0.0 );
	this.alpha = optionalParameter( options.alpha, 1.0 );
	this.time = optionalParameter( options.time, 0.0 );
	this.normalSampler = optionalParameter( options.waterNormals, null );
	this.sunDirection = optionalParameter( options.sunDirection, new THREE.Vector3( 0.70707, 0.70707, 0.0 ) );
	this.sunColor = new THREE.Color( optionalParameter( options.sunColor, 0xffffff ) );
	this.waterColor = new THREE.Color( optionalParameter( options.waterColor, 0x7F7F7F ) );
	this.eye = optionalParameter( options.eye, new THREE.Vector3( 0, 0, 0 ) );
	this.distortionScale = optionalParameter( options.distortionScale, 20.0 );
	this.noiseScale = optionalParameter( options.noiseScale, 1.0 );

	this.renderer = renderer;
	this.scene = scene;
	this.mirrorPlane = new THREE.Plane();
	this.normal = new THREE.Vector3( 0, 0, 1 );
	this.mirrorWorldPosition = new THREE.Vector3();
	this.cameraWorldPosition = new THREE.Vector3();
	this.rotationMatrix = new THREE.Matrix4();
	this.lookAtPosition = new THREE.Vector3( 0, 0, -1 );
	this.clipPlane = new THREE.Vector4();

	if ( camera instanceof THREE.PerspectiveCamera )
		this.camera = camera;
	else
	{
		this.camera = new THREE.PerspectiveCamera();
		console.log(this.name + ': camera is not a Perspective Camera!')
	}

	this.textureMatrix = new THREE.Matrix4();

	this.mirrorCamera = this.camera.clone();

	this.texture = new THREE.WebGLRenderTarget( width, height );
	this.tempTexture = new THREE.WebGLRenderTarget( width, height );

	var mirrorShader = THREE.ShaderLib[ "water" ];
	var mirrorUniforms = THREE.UniformsUtils.clone( mirrorShader.uniforms );

	this.material = new THREE.ShaderMaterial( {
		fragmentShader: mirrorShader.fragmentShader,
		vertexShader: mirrorShader.vertexShader,
		uniforms: mirrorUniforms,
		transparent: true
	} );

	this.material.uniforms.mirrorSampler.value = this.texture;
	this.material.uniforms.textureMatrix.value = this.textureMatrix;
	this.material.uniforms.alpha.value = this.alpha;
	this.material.uniforms.time.value = this.time;
	this.material.uniforms.normalSampler.value = this.normalSampler;
	this.material.uniforms.sunColor.value = this.sunColor;
	this.material.uniforms.waterColor.value = this.waterColor;
	this.material.uniforms.sunDirection.value = this.sunDirection;
	this.material.uniforms.distortionScale.value = this.distortionScale;
	this.material.uniforms.noiseScale.value = this.noiseScale;

	this.material.uniforms.eye.value = this.eye;

	if ( !isPowerOfTwo(width) || !isPowerOfTwo(height) )
	{
		this.texture.generateMipmaps = false;
		this.tempTexture.generateMipmaps = false;
	}

	this.updateTextureMatrix();
	this.render();
};

THREE.Water.prototype = Object.create( THREE.Object3D.prototype );

THREE.Water.prototype.renderWithMirror = function (otherMirror) {

	// update the mirror matrix to mirror the current view
	this.updateTextureMatrix();
	this.matrixNeedsUpdate = false;

	// set the camera of the other mirror so the mirrored view is the reference view
	var tempCamera = otherMirror.camera;
	otherMirror.camera = this.mirrorCamera;

	// render the other mirror in temp texture
	otherMirror.renderTemp();
	otherMirror.material.uniforms.mirrorSampler.value = otherMirror.tempTexture;

	// render the current mirror
	this.render();
	this.matrixNeedsUpdate = true;

	// restore material and camera of other mirror
	otherMirror.material.uniforms.mirrorSampler.value = otherMirror.texture;
	otherMirror.camera = tempCamera;

	// restore texture matrix of other mirror
	otherMirror.updateTextureMatrix();
};

THREE.Water.prototype.updateTextureMatrix = function () {

	function sign(x) { return x ? x < 0 ? -1 : 1 : 0; }

	this.updateMatrixWorld();
	this.camera.updateMatrixWorld();

	this.mirrorWorldPosition.getPositionFromMatrix( this.matrixWorld );
	this.cameraWorldPosition.getPositionFromMatrix( this.camera.matrixWorld );

	this.rotationMatrix.extractRotation( this.matrixWorld );

	this.normal.set( 0, 0, 1 );
	this.normal.applyMatrix4( this.rotationMatrix );

	var view = this.mirrorWorldPosition.clone().sub( this.cameraWorldPosition );
	var reflectView = view.reflect( this.normal );
	reflectView.add( this.mirrorWorldPosition );

	this.rotationMatrix.extractRotation( this.camera.matrixWorld );

	this.lookAtPosition.set(0, 0, -1);
	this.lookAtPosition.applyMatrix4( this.rotationMatrix );
	this.lookAtPosition.add( this.cameraWorldPosition );

	var target = this.mirrorWorldPosition.clone().sub( this.lookAtPosition );
	var reflectTarget = target.reflect( this.normal );
	reflectTarget.add( this.mirrorWorldPosition );

	this.up.set(0, -1, 0);
	this.up.applyMatrix4( this.rotationMatrix );
	var reflectUp = this.up.reflect( this.normal );

	this.mirrorCamera.position.copy(reflectView);
	this.mirrorCamera.up = reflectUp;
	this.mirrorCamera.lookAt(reflectTarget);
	this.mirrorCamera.aspect = this.camera.aspect;

	this.mirrorCamera.updateProjectionMatrix();
	this.mirrorCamera.updateMatrixWorld();
	this.mirrorCamera.matrixWorldInverse.getInverse(this.mirrorCamera.matrixWorld);

	// Update the texture matrix
	this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5,
							0.0, 0.5, 0.0, 0.5,
							0.0, 0.0, 0.5, 0.5,
							0.0, 0.0, 0.0, 1.0 );
	this.textureMatrix.multiply(this.mirrorCamera.projectionMatrix);
	this.textureMatrix.multiply(this.mirrorCamera.matrixWorldInverse);

	// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html
	// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf
	this.mirrorPlane.setFromNormalAndCoplanarPoint( this.normal, this.mirrorWorldPosition );
	this.mirrorPlane.applyMatrix4(this.mirrorCamera.matrixWorldInverse);

	this.clipPlane.set(this.mirrorPlane.normal.x, this.mirrorPlane.normal.y, this.mirrorPlane.normal.z, this.mirrorPlane.constant );

	var q = new THREE.Vector4();
	var projectionMatrix = this.mirrorCamera.projectionMatrix;

	q.x = (sign(this.clipPlane.x) + projectionMatrix.elements[8]) / projectionMatrix.elements[0];
	q.y = (sign(this.clipPlane.y) + projectionMatrix.elements[9]) / projectionMatrix.elements[5];
	q.z = -1.0;
	q.w = (1.0 + projectionMatrix.elements[10]) / projectionMatrix.elements[14];

	// Calculate the scaled plane vector
	var c = new THREE.Vector4();
	c = this.clipPlane.multiplyScalar( 2.0 / this.clipPlane.dot(q) );

	// Replacing the third row of the projection matrix
	projectionMatrix.elements[2] = c.x;
	projectionMatrix.elements[6] = c.y;
	projectionMatrix.elements[10] = c.z + 1.0 - this.clipBias;
	projectionMatrix.elements[14] = c.w;

	var worldCoordinates = new THREE.Vector3();
	worldCoordinates.getPositionFromMatrix( this.camera.matrixWorld );
	this.eye = worldCoordinates;
	this.material.uniforms.eye.value = this.eye;
};

THREE.Water.prototype.render = function () {

	if( this.matrixNeedsUpdate )
		this.updateTextureMatrix();

	this.matrixNeedsUpdate = true;

	// Render the mirrored view of the current scene into the target texture
	if( this.scene !== undefined && this.scene instanceof THREE.Scene )
	{
        this.renderer.render( this.scene, this.mirrorCamera, this.texture, true );
	}

};

THREE.Water.prototype.renderTemp = function () {

	if( this.matrixNeedsUpdate )
		this.updateTextureMatrix();

	this.matrixNeedsUpdate = true;

	// Render the mirrored view of the current scene into the target texture
	if( this.scene !== undefined && this.scene instanceof THREE.Scene)
	{
		this.renderer.render( this.scene, this.mirrorCamera, this.tempTexture, true );
	}

};<|endoftext|>


<commit_before>const promise = module.exports = {}
promise.default = promise

/**
 * promise.app ==> getApp()
 */

if (!wx.showLoading) {
  wx.showLoading = (obj) => {
    obj.icon = 'loading'
    wx.showToast(obj)
  }

  wx.hideBusy = wx.hideToast
}

/**
 * 没有 success fail 回调
 */
var noPromiseMethods = {
  stopRecord: false,
  pauseVoice: true,
  stopVoice: true,
  pauseBackgroundAudio: true,
  stopBackgroundAudio: true,
  showNavigationBarLoading: true,
  hideNavigationBarLoading: true,
  createAnimation: true,
  createContext: true,
  hideKeyboard: true,
  stopPullDownRefresh: true,
  createAudioContext: true,
  showMessage: true
}

function forEach (key) {
  if (noPromiseMethods[key] || key.substr(0, 2) === 'on' || /\w+Sync$/.test(key)) { // 没有 success fail 回调，以 on 开头，或以 Sync 结尾的用原始的方法
    promise[key] = function () {
      return wx[key].apply(wx, arguments)
    }
    return
  }

  // 转成 promise
  promise[key] = function (obj) {
    obj = obj || {}
    return new Promise(function (resolve, reject) {
      obj.success = resolve
      obj.fail = function (res) {
        if (res && res.errMsg) {
          reject(new Error(res.errMsg))
        } else {
          reject(res)
        }
      }
      wx[key](obj)
    })
  }
}

Object.keys(wx).forEach(forEach)

Object.defineProperty(promise, 'app', {
  get: function () {
    return getApp()
  }
})

promise.showBusy = function (title = '正在载入...', mask = true) {
  return promise.showLoading({ title, mask })
}

promise.hideBusy = promise.hideLoading

promise.showMessage = (title, content) => {
  return promise.showModal({ showCancel: false, title, content })
}

promise.page = function () {
  let args = Array.from(arguments).reverse()
  let data = {}

  args.forEach(item => {
    if (item.data) {
      data = Object.assign({}, data, item.data)
    }
  })

  args.unshift({})

  let configs = Object.assign.apply(Object, args)
  configs.data = data

  Page(configs)
}
<commit_msg>showMessage 支持多种参数<commit_after>const promise = module.exports = {}
promise.default = promise

/**
 * promise.app ==> getApp()
 */

if (!wx.showLoading) {
  wx.showLoading = (obj) => {
    obj.icon = 'loading'
    wx.showToast(obj)
  }

  wx.hideBusy = wx.hideToast
}

/**
 * 没有 success fail 回调
 */
var noPromiseMethods = {
  stopRecord: false,
  pauseVoice: true,
  stopVoice: true,
  pauseBackgroundAudio: true,
  stopBackgroundAudio: true,
  showNavigationBarLoading: true,
  hideNavigationBarLoading: true,
  createAnimation: true,
  createContext: true,
  hideKeyboard: true,
  stopPullDownRefresh: true,
  createAudioContext: true,
  showMessage: true
}

function forEach (key) {
  if (noPromiseMethods[key] || key.substr(0, 2) === 'on' || /\w+Sync$/.test(key)) { // 没有 success fail 回调，以 on 开头，或以 Sync 结尾的用原始的方法
    promise[key] = function () {
      return wx[key].apply(wx, arguments)
    }
    return
  }

  // 转成 promise
  promise[key] = function (obj) {
    obj = obj || {}
    return new Promise(function (resolve, reject) {
      obj.success = resolve
      obj.fail = function (res) {
        if (res && res.errMsg) {
          reject(new Error(res.errMsg))
        } else {
          reject(res)
        }
      }
      wx[key](obj)
    })
  }
}

Object.keys(wx).forEach(forEach)

Object.defineProperty(promise, 'app', {
  get: function () {
    return getApp()
  }
})

promise.showBusy = function (title = '正在载入...', mask = true) {
  return promise.showLoading({ title, mask })
}

promise.hideBusy = promise.hideLoading

promise.showMessage = () => {
  var opt = { showCancel: false, title: '', content: '' }

  if (arguments.length === 1) {
    opt.content = arguments[0]
  } else {
    opt.title = arguments[0]
    opt.content = arguments[1]
  }
  return promise.showModal(opt)
}

promise.page = function () {
  let args = Array.from(arguments).reverse()
  let data = {}

  args.forEach(item => {
    if (item.data) {
      data = Object.assign({}, data, item.data)
    }
  })

  args.unshift({})

  let configs = Object.assign.apply(Object, args)
  configs.data = data

  Page(configs)
}
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { connect } from 'react-redux';
import { BODY_600, BODY_960 } from '../constants/defaults';
import { GoogleFontBadge } from './';

const Fragment = React.Fragment;

class Footer extends Component {
  render() {
    let { backgroundMode, screenWidth, locale } = this.props;

    return (

          {
            screenWidth > BODY_960 ?

              <GoogleFontBadge />

 :
            <GoogleFontBadge />
          }

            {
              locale == "ko" ?

Google Fonts에 참여하세요.

                  Google Fonts는 전 세계의 디자이너와 함께 웹에서 손쉽게 사용할 수 있는 폰트를 개발합니다. Google Fonts와 이곳에 여러분이 제작한 한글 폰트를 제공하고 싶다면 언제든지 @googlefonts 앞으로 알려주세요.

                  이 사이트의 소스는 github.com/googlefonts/korean에 공개되어 있습니다. 관련 문의는 이곳에 리포트를 남겨주세요.

 :

Join Our Community

                  We are working with designers around the world to produce best-in-class typeface designs that are made for the web. If you want to offer your own Korean font through Google Fonts, please contact @googlefonts.

                  This website is also open source. To report any issues, please visit github.com/googlefonts/korean

            }

Made by Friends of Google Fonts

              Suyoung Jang

              E Roon Kang

              Wonyoung So

              Guhong Min

              Hannah Son

              twitter.com/googlefonts

              github.com/google/fonts

        {
          screenWidth <= BODY_960 ?
          <Fragment>

          </Fragment> : null
        }

    )
  }
}

let mapStateToProps = state => {
  return {
    backgroundMode: state.backgroundMode == "black" ? "white" : "black",
    screenWidth: state.screenWidth,
    locale: state.locale
  }
};

export default connect(mapStateToProps)(Footer);
<commit_msg>Update Footer.js<commit_after>import React, { Component } from 'react'
import { connect } from 'react-redux';
import { BODY_600, BODY_960 } from '../constants/defaults';
import { GoogleFontBadge } from './';

const Fragment = React.Fragment;

class Footer extends Component {
  render() {
    let { backgroundMode, screenWidth, locale } = this.props;

    return (

          {
            screenWidth > BODY_960 ?

              <GoogleFontBadge />

 :
            <GoogleFontBadge />
          }

            {
              locale == "ko" ?

Google Fonts에 참여하세요.

                  Google Fonts는 전 세계의 디자이너와 함께 웹에서 손쉽게 사용할 수 있는 폰트를 개발합니다. Google Fonts와 이곳에 여러분이 제작한 한글 폰트를 제공하고 싶다면 언제든지 @googlefonts 앞으로 알려주세요.

                  이 사이트의 소스는 github.com/googlefonts/korean에 공개되어 있습니다. 관련 문의는 이곳에 리포트를 남겨주세요.

 :

Join Our Community

                  We are working with designers around the world to produce best-in-class typeface designs that are made for the web. If you want to offer your own Korean font through Google Fonts, please contact @googlefonts.

                  This website is also open source. To report any issues, please visit github.com/googlefonts/korean

            }

Made by Friends of Google Fonts

              Yang Jang

              E Roon Kang

              Wonyoung So

              Guhong Min

              Hannah Son

              twitter.com/googlefonts

              github.com/google/fonts

        {
          screenWidth <= BODY_960 ?
          <Fragment>

          </Fragment> : null
        }

    )
  }
}

let mapStateToProps = state => {
  return {
    backgroundMode: state.backgroundMode == "black" ? "white" : "black",
    screenWidth: state.screenWidth,
    locale: state.locale
  }
};

export default connect(mapStateToProps)(Footer);
<|endoftext|>


<commit_before>import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { logout } from 'containers/Authentication/actions/authActions';

const StyledNav = styled.nav`
  position: absolute;
  top: 0;
  left: 0;
  height: 60px;
  width: 100%;
  z-index: 100;
  box-shadow: none;
`;

class NavBar extends React.PureComponent {
  constructor() {
    super();
    this.logout = this.logout.bind(this);
  }
  logout(e) {
    e.preventDefault();
    this.props.logout();
  }

  render() {
    const { isAuthenticated } = this.props.auth;

    const userLinks = (

<Link className="nav-link" to="/restaurant">{ this.props.auth.user.email }</Link>

<Link className="nav-link" to="/restaurant">My Favorites</Link>

Log out

    );

    const guestLinks = (

<Link className="nav-link" to="/signup">Sign up</Link>

<Link className="nav-link" to="/login">Log in</Link>

    );

    return (

    );
  }
}

NavBar.propTypes = {
  auth: React.PropTypes.object.isRequired,
  logout: React.PropTypes.func.isRequired,
};

function mapStateToProps(state) {
  return {
    auth: state.get('auth'),
  };
}

export default connect(mapStateToProps, { logout })(NavBar);
<commit_msg>change nav style according to user role<commit_after>import React from 'react';
import styled from 'styled-components';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { logout } from 'containers/Authentication/actions/authActions';

const StyledNav = styled.nav`
  position: absolute;
  top: 0;
  left: 0;
  height: 60px;
  width: 100%;
  z-index: 100;
  box-shadow: none;
`;

class NavBar extends React.PureComponent {
  constructor() {
    super();
    this.logout = this.logout.bind(this);
  }
  logout(e) {
    e.preventDefault();
    this.props.logout();
  }

  render() {
    const { isAuthenticated, user} = this.props.auth;
    let link
    const userLinks = (

<Link className="nav-link" to="/profile">{ this.props.auth.user.email }</Link>

<Link className="nav-link" to="/restaurant">My Favorites</Link>

Log out

    );

    const guestLinks = (

<Link className="nav-link" to="/signup">Sign up</Link>

<Link className="nav-link" to="/login">Log in</Link>

    );

    const businessLinks = (

<Link className="nav-link" to="/profile">{ this.props.auth.user.email }</Link>

<Link className="nav-link" to="/restaurant">My restaurant</Link>

Log out

    );

    if(!isAuthenticated){
      link = guestLinks;
    } else if(user.role === "customer"){
      link = userLinks;
    } else if(user.role === "business"){
      link = businessLinks;
    }

    return (

    );
  }
}

NavBar.propTypes = {
  auth: React.PropTypes.object.isRequired,
  logout: React.PropTypes.func.isRequired,
};

function mapStateToProps(state) {
  return {
    auth: state.get('auth'),
  };
}

export default connect(mapStateToProps, { logout })(NavBar);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import Controls from './Controls';
import styles from './Player.css';
import SPlayer from '../utils/SPlayer';

const player = new SPlayer({ debug: true });

class Player extends Component {
  constructor() {
    super();
    this.state = { progress: 0 };
  }

  componentDidMount() {
    document.addEventListener('dragover', e => e.preventDefault());
    document.addEventListener('drop', this.onDrop);
    player.renderVideo(this.playerCanvas);
    player.addEventListener('onPositionChanged', (position) => {
      this.setState({
        progress: (position * 100)
      });
    });
  }

  componentWillUnmount() {
    document.removeEventListener('dragover', e => e.preventDefault());
    document.removeEventListener('drop', this.onDrop);
  }

  onDrop = (e) => {
    e.preventDefault();
    player.play(`file://${e.dataTransfer.files[0].path}`);
  };

  render() {
    return (

        <Controls progress={this.state.progress} togglePause={player.togglePause}/>
        <canvas
          className={styles.player}
          ref={(playerCanvas) => { this.playerCanvas = playerCanvas; }}
          id="splayer"
        />

    );
  }
}

export default Player;

<commit_msg>making thing reduxed<commit_after>import React, { Component } from 'react';
import Controls from './Controls';
import styles from './Player.css';

class Player extends Component {
  constructor() {
    super();
    this.state = { progress: 0 };
  }

  componentDidMount() {
    document.addEventListener('dragover', e => e.preventDefault());
    document.addEventListener('drop', this.onDrop);
    this.props.renderVideo(this.playerCanvas);
  }

  componentWillUnmount() {
    document.removeEventListener('dragover', e => e.preventDefault());
    document.removeEventListener('drop', this.onDrop);
  }

  onDrop = (e) => {
    e.preventDefault();
    this.props.play(`file://${e.dataTransfer.files[0].path}`);
  };

  render() {
    return (

        <Controls progress={this.state.progress}/>
        <canvas
          className={styles.player}
          ref={(playerCanvas) => { this.playerCanvas = playerCanvas; }}
          id="splayer"
        />

    );
  }
}

export default Player;

<|endoftext|>