<commit_before>(function(Browser) {

	Browser.Items = Browser.Items || {};
	Browser.Items.Views = Browser.Items || {};

	Browser.Items.Views.List = Backbone.View.extend({

		tagName : 'tr',
		className : 'list-media list-fancymedia',

		initialize: function () {

			this.el.id = this.model.id;

			//list item has a thumbnail item with same model as a subview
			this.thumbnailView = new Browser.Items.Views.Thumb({
				model:this.model,
				thumbnail_width:100,
				thumbnail_height:80,
				show_caption:false,
				fancybox:false,
				draggable:false
			});

		},

		render: function ( done )
		{
			var _this = this;

			var template;
			switch( this.model.get('media_type') )
			{
				case 'Image':
					template = this.getImageTemplate();
					break;
				case 'Document':
					template = this.getDefaultTemplate();
					break;
				case 'Website':
					template = this.getWebsiteTemplate();
					break;
				case 'Tweet':
					template = this.getTweetTemplate();
					break;
				case 'Text':
					template = this.getTestimonialTemplate();
					break;
				case 'Video':
					template = this.getDefaultTemplate();
					break;
				case 'Audio':
					template = this.getDefaultTemplate();
					break;
				case 'PDF':
					template = this.getDefaultTemplate();
					break;
				case 'Collection':
					$(this.el).removeClass('list-fancymedia');
					template = this.getCollectionTemplate();
					break;

				default:
					template = this.getDefaultTemplate();
			}

			var blanks = this.model.attributes;

			if (!_.isUndefined(this.model.get("media_date_created"))&&!_.isNull(this.model.get("media_date_created"))){
				blanks["media_date"] = new Date(this.model.get("media_date_created").replace(" ", "T"));
				blanks["media_date"]=blanks["media_date"].format("mmmm dS, yyyy
h:MM TT");
			} else {
				blanks["media_date"] = "n/a";
			}
			if (this.model.get("text") !== null){
				var excerpt = this.model.get("text").replace(/\r\n/gi, '
');
				blanks["text"] = this.linkifyTweet(excerpt);

			}
			if (this.model.get("description") === null){
				blanks["description"] = " ";
			}
			if (this.model.get("description") !== null && this.model.get("description").length > 255){
				blanks["description"] = this.model.get("description").substring(0,255) + "...";
			}
			if (this.model.get("title") === null || this.model.get("title") == "none" || this.model.get("title") === ""){
				blanks["title"] = "";
			}
			// Checks if the media username is rijs and switches it to media creator username
			if (this.model.get("media_creator_username") === "rijs"){
				blanks["media_creator_username"] = this.model.get("media_creator_realname");
			}

			if (this.model.get("media_creator_realname") === null || this.model.get("media_creator_realname") === "" || this.model.get("media_creator_realname") == "Unknown" || this.model.get("media_creator_realname") == "unknown"){
				blanks["author"] = this.model.get("media_creator_username");
			} else {
				blanks["author"] = this.model.get("media_creator_realname");
			}
			if (this.model.get('attributes')) {
			    if (this.model.get('attributes').privacy) {
			        if (this.model.get('attributes').privacy == 'Hide') {
			            blanks["author"] = "Anonymous";
			        }
			    }
			}

			if (this.model.get("media_type") == "Text" && this.model.get('description').length < this.model.get('text').length){
				blanks["description"] = this.model.get('description') + '...';
			}

			if (this.model.get("media_type") == "Website"){
				var parts = this.model.get('attribution_uri').split('http');
				blanks["original_url"] = parts[2] !== undefined ? "http" + parts[2] : this.model.get('attribution_uri');
			}

			//console.log(this.model.get("description"));
			var text = document.getElementById("search").textContent;
			var old_arr = text.split(" ");

			var new_arr = [];

			for (var i = 0; i < old_arr.length; i++)
			{
				var str = old_arr[i].match(/[A-Za-z]+/);
				new_arr.push(str);
			}

			var final_arr = [];

			for (var j = 0; j < new_arr.length; j++)
			{
				if (new_arr[j] != null)
				{
					final_arr.push(new_arr[j]);
				}
			}

			final_arr.splice(0,1);
			console.log(final_arr);

/*
			var real_text = [];
			var str = "";
			console.log(text);
			for (var i = 0; i < text.length; i++)
			{
				if (text[i] != "" || text[i] != " ")
				{
					if (text[i] == "" || text[i] == " ")
					{
						console.log(str);
						real_text.push(str);
						str = "";
					}
					else
					{
						str.concat(text[i]);
						console.log(str);
					}
				}
			}

			console.log(real_text);

			var filter = text.split(" ");
			var arr = [];

			for(var i = 0; i < filter.length; i++)
			{
				if (filter[i] != "" || filter[i] != " ")
				{
					arr.push(filter[i]);
				}
			}

			console.log(arr);*/

			$(this.el).html( _.template( template, blanks ) );

			$(this.el).find('.zeega-item-thumbnail').append(this.thumbnailView.render().el);

			if (blanks["author"] === ""){
				$(this.el).find('.jda-item-author').hide();
			}

			if (this.model.get("media_type") == "Collection"){
				this.draggable=false;
			}
			else {
				this.draggable=true;
			}

			if(this.draggable){
				$(this.el).draggable({
					cursor : 'crosshair',
					cursorAt : {
						top : 50,
						left : 50
					},
					appendTo : 'body',
					opacity : 0.8,

					helper : function(){
						var drag = $(this).find('.thumbnail')
						.clone()
						.css({
							'z-index':'101'
						});
						return drag;
					},

					//init the dragged item variable
					start : function(){
						$(this).draggable('option','revert',true);
						jda.app.draggedItem = _this.model;
					}
			});
			}
			$(this.el).find(".jdicon-small-drag").tooltip({'title':'Drag to add to your collection','placement':'bottom', delay: { show: 600, hide: 100 }});

			if( blanks["display_name"] === ""){
				$(this.el).find(".jda-user-link").hide();
			} else {
				$(this.el).find(".jda-user-link").click(function(){jda.app.goToUser(_this.model.get('user_id')); return false;});
			}

			return this;
		},

		/* formats tweet text, doesn't linkify bc tweet is already linked to fancybox */
		linkifyTweet : function(tweet){

			// urls
			var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
			tweet = tweet.replace(exp,"$1");

			// users
			tweet = tweet.replace(/(^|)@(\w+)/gi, function (s) {
				return '' + s + '';
			});

			// tags
			tweet = tweet.replace(/(^|)#(\w+)/gi, function (s) {
				return '' + s + '';
			});

			return tweet;
		},

		getImageTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= title %>

by: <%= author %>'+
				'
<%= description %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getDefaultTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= title %>

by: <%= author %>
'+
				'
<%= description %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
			'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getDocumentTemplate : function()
		{
			html =

			''+
				''+
				'
<%= author %>
'+
			'	'+
			''+
				'
<%= title %>
'+
				'
<%= description %>
'+
			'	'+
			''+
				'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getWebsiteTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= title %>
'+
				'
<%= original_url %>
'+
				'
<%= description %>
'+
			'	'+
			''+
				'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getTweetTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= text %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getTestimonialTemplate : function()
		{
			html =
			''+
				'

'+
			'	'+
			''+
				'
<%= title %>

Testimonial by: <%= author %>
'+
				'
<%= description %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';
			return html;
		},

		getCollectionTemplate : function()
		{
			html =

				''+
				'

'+
				'	'+
				''+
					'
<%= title %>

by <%= display_name %>
'+
					'
<%= description %>
'+
				'	'+
				'<%= media_date %>'+
					'<input class="jda-item-checkbox" type="checkbox">'+
				'	';

			return html;
		}

	});

})(jda.module("browser"));
<commit_msg>Testing what final_arr has<commit_after>(function(Browser) {

	Browser.Items = Browser.Items || {};
	Browser.Items.Views = Browser.Items || {};

	Browser.Items.Views.List = Backbone.View.extend({

		tagName : 'tr',
		className : 'list-media list-fancymedia',

		initialize: function () {

			this.el.id = this.model.id;

			//list item has a thumbnail item with same model as a subview
			this.thumbnailView = new Browser.Items.Views.Thumb({
				model:this.model,
				thumbnail_width:100,
				thumbnail_height:80,
				show_caption:false,
				fancybox:false,
				draggable:false
			});

		},

		render: function ( done )
		{
			var _this = this;

			var template;
			switch( this.model.get('media_type') )
			{
				case 'Image':
					template = this.getImageTemplate();
					break;
				case 'Document':
					template = this.getDefaultTemplate();
					break;
				case 'Website':
					template = this.getWebsiteTemplate();
					break;
				case 'Tweet':
					template = this.getTweetTemplate();
					break;
				case 'Text':
					template = this.getTestimonialTemplate();
					break;
				case 'Video':
					template = this.getDefaultTemplate();
					break;
				case 'Audio':
					template = this.getDefaultTemplate();
					break;
				case 'PDF':
					template = this.getDefaultTemplate();
					break;
				case 'Collection':
					$(this.el).removeClass('list-fancymedia');
					template = this.getCollectionTemplate();
					break;

				default:
					template = this.getDefaultTemplate();
			}

			var blanks = this.model.attributes;

			if (!_.isUndefined(this.model.get("media_date_created"))&&!_.isNull(this.model.get("media_date_created"))){
				blanks["media_date"] = new Date(this.model.get("media_date_created").replace(" ", "T"));
				blanks["media_date"]=blanks["media_date"].format("mmmm dS, yyyy
h:MM TT");
			} else {
				blanks["media_date"] = "n/a";
			}
			if (this.model.get("text") !== null){
				var excerpt = this.model.get("text").replace(/\r\n/gi, '
');
				blanks["text"] = this.linkifyTweet(excerpt);

			}
			if (this.model.get("description") === null){
				blanks["description"] = " ";
			}
			if (this.model.get("description") !== null && this.model.get("description").length > 255){
				blanks["description"] = this.model.get("description").substring(0,255) + "...";
			}
			if (this.model.get("title") === null || this.model.get("title") == "none" || this.model.get("title") === ""){
				blanks["title"] = "";
			}
			// Checks if the media username is rijs and switches it to media creator username
			if (this.model.get("media_creator_username") === "rijs"){
				blanks["media_creator_username"] = this.model.get("media_creator_realname");
			}

			if (this.model.get("media_creator_realname") === null || this.model.get("media_creator_realname") === "" || this.model.get("media_creator_realname") == "Unknown" || this.model.get("media_creator_realname") == "unknown"){
				blanks["author"] = this.model.get("media_creator_username");
			} else {
				blanks["author"] = this.model.get("media_creator_realname");
			}
			if (this.model.get('attributes')) {
			    if (this.model.get('attributes').privacy) {
			        if (this.model.get('attributes').privacy == 'Hide') {
			            blanks["author"] = "Anonymous";
			        }
			    }
			}

			if (this.model.get("media_type") == "Text" && this.model.get('description').length < this.model.get('text').length){
				blanks["description"] = this.model.get('description') + '...';
			}

			if (this.model.get("media_type") == "Website"){
				var parts = this.model.get('attribution_uri').split('http');
				blanks["original_url"] = parts[2] !== undefined ? "http" + parts[2] : this.model.get('attribution_uri');
			}

			//console.log(this.model.get("description"));
			var text = document.getElementById("search").textContent;
			var old_arr = text.split(" ");

			var new_arr = [];

			for (var i = 0; i < old_arr.length; i++)
			{
				var str = old_arr[i].match(/[A-Za-z]+/);
				new_arr.push(str);
			}

			var final_arr = [];

			for (var j = 0; j < new_arr.length; j++)
			{
				if (new_arr[j] != null)
				{
					final_arr.push(new_arr[j]);
				}
			}

			// Gets rid of the "text:" string that we don't want
			final_arr.splice(0,1);

			console.log(final_arr[0]);

/*
			var real_text = [];
			var str = "";
			console.log(text);
			for (var i = 0; i < text.length; i++)
			{
				if (text[i] != "" || text[i] != " ")
				{
					if (text[i] == "" || text[i] == " ")
					{
						console.log(str);
						real_text.push(str);
						str = "";
					}
					else
					{
						str.concat(text[i]);
						console.log(str);
					}
				}
			}

			console.log(real_text);

			var filter = text.split(" ");
			var arr = [];

			for(var i = 0; i < filter.length; i++)
			{
				if (filter[i] != "" || filter[i] != " ")
				{
					arr.push(filter[i]);
				}
			}

			console.log(arr);*/

			$(this.el).html( _.template( template, blanks ) );

			$(this.el).find('.zeega-item-thumbnail').append(this.thumbnailView.render().el);

			if (blanks["author"] === ""){
				$(this.el).find('.jda-item-author').hide();
			}

			if (this.model.get("media_type") == "Collection"){
				this.draggable=false;
			}
			else {
				this.draggable=true;
			}

			if(this.draggable){
				$(this.el).draggable({
					cursor : 'crosshair',
					cursorAt : {
						top : 50,
						left : 50
					},
					appendTo : 'body',
					opacity : 0.8,

					helper : function(){
						var drag = $(this).find('.thumbnail')
						.clone()
						.css({
							'z-index':'101'
						});
						return drag;
					},

					//init the dragged item variable
					start : function(){
						$(this).draggable('option','revert',true);
						jda.app.draggedItem = _this.model;
					}
			});
			}
			$(this.el).find(".jdicon-small-drag").tooltip({'title':'Drag to add to your collection','placement':'bottom', delay: { show: 600, hide: 100 }});

			if( blanks["display_name"] === ""){
				$(this.el).find(".jda-user-link").hide();
			} else {
				$(this.el).find(".jda-user-link").click(function(){jda.app.goToUser(_this.model.get('user_id')); return false;});
			}

			return this;
		},

		/* formats tweet text, doesn't linkify bc tweet is already linked to fancybox */
		linkifyTweet : function(tweet){

			// urls
			var exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
			tweet = tweet.replace(exp,"$1");

			// users
			tweet = tweet.replace(/(^|)@(\w+)/gi, function (s) {
				return '' + s + '';
			});

			// tags
			tweet = tweet.replace(/(^|)#(\w+)/gi, function (s) {
				return '' + s + '';
			});

			return tweet;
		},

		getImageTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= title %>

by: <%= author %>'+
				'
<%= description %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getDefaultTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= title %>

by: <%= author %>
'+
				'
<%= description %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
			'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getDocumentTemplate : function()
		{
			html =

			''+
				''+
				'
<%= author %>
'+
			'	'+
			''+
				'
<%= title %>
'+
				'
<%= description %>
'+
			'	'+
			''+
				'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getWebsiteTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= title %>
'+
				'
<%= original_url %>
'+
				'
<%= description %>
'+
			'	'+
			''+
				'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getTweetTemplate : function()
		{
			html =

			''+
				'

'+
			'	'+
			''+
				'
<%= text %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';

			return html;
		},

		getTestimonialTemplate : function()
		{
			html =
			''+
				'

'+
			'	'+
			''+
				'
<%= title %>

Testimonial by: <%= author %>
'+
				'
<%= description %>
'+
			'	'+
			'<%= media_date %><input class="jda-item-checkbox" type="checkbox">'+
				'

via <%= display_name %>

'+
			'	';
			return html;
		},

		getCollectionTemplate : function()
		{
			html =

				''+
				'

'+
				'	'+
				''+
					'
<%= title %>

by <%= display_name %>
'+
					'
<%= description %>
'+
				'	'+
				'<%= media_date %>'+
					'<input class="jda-item-checkbox" type="checkbox">'+
				'	';

			return html;
		}

	});

})(jda.module("browser"));
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');

/**
 * This is a semverist plugin for set/geting the semverist element.
 * It is self-contained.
 *
 * Mixins follow the formula for mixins described at:
 * http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/
 * More info in the README.
 *
 */
module.exports = superclass => class extends superclass {
  init() {
    this.setCompositionType();
    this.setCompositionDestination();
    super.init();
  }

  /**
   * Assembles the composition based on the manifest.
   *
   */
  assembleManifest() {
    // calls to write composition - handled in plugins
    // chained from object writer to directory writer (if directory)
    this.writeComposition();
  }

  /**
   * Sets the composition for this manifest.
   *
   * @param {Object} draft - A semver shaped hierarchy with elements at the
   *   patch or lower levels based on the semverish object that the manifest
   *   was built upon.
   */
  setComposition(draft) {
    this.composition = draft;
  }

  /**
   * Returns the composition
   *
   * @returns {Object} - A semver shaped hierarchy keyed by semver strings
   *   with element attributes of fully formed elements created from semverist
   *   elements contained in the semverish object the manifest was built upon.
   */
  getComposition() {
    return this.composition;
  }

  /**
   * Sets the composition type from config.
   *   This can be default (object) or directory and influences the out
   *   put of the composer to either an object in this.composition or
   *   an object in this.composition and a series of files and directories
   *   written to composer.destination.
   */
  setCompositionType() {
    // Sets the composition type according to configuration passed to manifest.
    this.compositionType = _.get(this.getConfig(), 'composer.composerType', 'default');
  }

  /**
   * Returns the composition type for this manifest.
   *
   * @returns {string} - The composition type.
   */
  getCompositionType() {
    return this.compositionType;
  }

  /**
   * Sets the composition destination, a path. The path will be an object path
   *   in the case of a default composition type or a directory path in the
   *   case of a directory composition type.
   *
   */
  setCompositionDestination() {
    // Sets the composition destination according to configuration
    // passed to manifest.
    this.compositionDestination = _.get(this.getConfig(), 'composer.destination', null);
    if (this.getCompositionType() === 'directory' && this.compositionDestination === null) {
      throw new Error('If the composition type is directory there must be a destination path');
    }
  }

  /**
   * Returns the composition destination.
   *
   * @returns {string} - the composition destination.
   */
  getCompositionDestination() {
    return this.compositionDestination;
  }

  /**
   * Revises the path to handle any special cases.
   *   Can be a place for super overrides to modify
   *   the path for special prereleases and the like.
   *
   * @static
   * @param {Object} parsedPath - A semver utils parsed
   *   path.
   * @returns {string} - A valid object path.
   */
  static pathBuildImprovement(parsedPath) {
    return parsedPath;
  }
};
<commit_msg>Changed to falsey from code review from elliott.<commit_after>'use strict';

const _ = require('lodash');

/**
 * This is a semverist plugin for set/geting the semverist element.
 * It is self-contained.
 *
 * Mixins follow the formula for mixins described at:
 * http://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/
 * More info in the README.
 *
 */
module.exports = superclass => class extends superclass {
  init() {
    this.setCompositionType();
    this.setCompositionDestination();
    super.init();
  }

  /**
   * Assembles the composition based on the manifest.
   *
   */
  assembleManifest() {
    // calls to write composition - handled in plugins
    // chained from object writer to directory writer (if directory)
    this.writeComposition();
  }

  /**
   * Sets the composition for this manifest.
   *
   * @param {Object} draft - A semver shaped hierarchy with elements at the
   *   patch or lower levels based on the semverish object that the manifest
   *   was built upon.
   */
  setComposition(draft) {
    this.composition = draft;
  }

  /**
   * Returns the composition
   *
   * @returns {Object} - A semver shaped hierarchy keyed by semver strings
   *   with element attributes of fully formed elements created from semverist
   *   elements contained in the semverish object the manifest was built upon.
   */
  getComposition() {
    return this.composition;
  }

  /**
   * Sets the composition type from config.
   *   This can be default (object) or directory and influences the out
   *   put of the composer to either an object in this.composition or
   *   an object in this.composition and a series of files and directories
   *   written to composer.destination.
   */
  setCompositionType() {
    // Sets the composition type according to configuration passed to manifest.
    this.compositionType = _.get(this.getConfig(), 'composer.composerType', 'default');
  }

  /**
   * Returns the composition type for this manifest.
   *
   * @returns {string} - The composition type.
   */
  getCompositionType() {
    return this.compositionType;
  }

  /**
   * Sets the composition destination, a path. The path will be an object path
   *   in the case of a default composition type or a directory path in the
   *   case of a directory composition type.
   *
   */
  setCompositionDestination() {
    // Sets the composition destination according to configuration
    // passed to manifest.
    this.compositionDestination = _.get(this.getConfig(), 'composer.destination', null);
    if (this.getCompositionType() === 'directory' && !this.compositionDestination) {
      throw new Error('If the composition type is directory there must be a destination path');
    }
  }

  /**
   * Returns the composition destination.
   *
   * @returns {string} - the composition destination.
   */
  getCompositionDestination() {
    return this.compositionDestination;
  }

  /**
   * Revises the path to handle any special cases.
   *   Can be a place for super overrides to modify
   *   the path for special prereleases and the like.
   *
   * @static
   * @param {Object} parsedPath - A semver utils parsed
   *   path.
   * @returns {string} - A valid object path.
   */
  static pathBuildImprovement(parsedPath) {
    return parsedPath;
  }
};
<|endoftext|>


<commit_before>var _ = require("lodash"); // Because of _.merge
var semver = require("semver");

/**
 * PreProcess.
 *
 * @param {Object[]} parsedFiles
 * @param {String[]} filenames
 * @param {String} useName
 * @param {String} defineName
 * @returns {Object}
 */
function preProcess(parsedFiles, filenames, useName, defineName)
{
	useName = useName || "permission";
	defineName = defineName || "definePermission";
	var result = {};
	for(var fileIndex = 0; fileIndex < parsedFiles.length; fileIndex += 1)
	{
		var parsedFile = parsedFiles[fileIndex];
		for(var blockIndex = 0; blockIndex < parsedFile.length; blockIndex += 1)
		{
			var block = parsedFile[blockIndex];
			if(block["global"][defineName])
			{
				var name = block["global"][defineName]["name"];
				var version = "0.0.0";

				if(block.version)
				{
					version = block.version;
				}

				if( ! result[defineName]) result[defineName] = {};
				if( ! result[defineName][name]) result[defineName][name] = {};
				result[defineName][name][version] = block["global"][defineName];
			}
		} // for blockIndex
	} // for fileIndex
	return result;
} // preProcess

/**
 * PostProcess.
 *
 * @param {Object[]} parsedFiles
 * @param {String[]} filenames
 * @param {Object[]} preProcessResults
 * @param {String} findName
 * @param {String} defineName
 */
function postProcess(parsedFiles, filenames, preProcessResults, findName, defineName)
{
	findName = findName || "permission";
	defineName = defineName || "definePermission";
	for(var fileIndex = 0; fileIndex < parsedFiles.length; fileIndex += 1)
	{
		var parsedFile = parsedFiles[fileIndex];
		for(var blockIndex = 0; blockIndex < parsedFile.length; blockIndex += 1)
		{
			var block = parsedFile[blockIndex];
			if(block["local"][findName])
			{
				var name = block["local"][findName];
				var version = "0.0.0";

				if(block.version)
				{
					version = block.version;
				}

				if(preProcessResults[defineName][name])
				{
					var matchedData = {};
					var matchedVersion = version;
					if(preProcessResults[defineName][name][version])
					{
						// Exact Version
						matchedData = preProcessResults[defineName][name][version];
					}
					else
					{
						// Find matching version
						var foundIndex = -1;
						var lastVersion = "0.0.0";

						var versionKeys = Object.keys(preProcessResults[defineName][name]);
						for(var versionIndex = 0; versionIndex < versionKeys.length; versionIndex += 1)
						{
							var currentVersion = versionKeys[versionIndex];
							if(semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion))
							{
								lastVersion = currentVersion;
								foundIndex = versionIndex;
							}
						} // for preIndex

						if(foundIndex === -1)
						{
							throw new Error("Name \"" + name + "\" in file \"" + filenames[fileIndex] +
								"\" block number " + block.index + " has no matching version. " +
								"Check if referenced definition block has a higher version number."
							);
						}
						var versionName = versionKeys[foundIndex];
						matchedData = preProcessResults[defineName][name][versionName];
					}

					// Copy matched elements into parsed block
					block.local[findName] = matchedData;
				}
//				else
//				{
//					// TODO: Show warning (no error).
//					throw new Error("Name \"" + name + "\" in file \"" + filenames[fileIndex] +
//						"\" block number " + block.index + " is not defined."
//					);
//				}
			}
		} // for blockIndex
	} // for fileIndex
} // postProcess

/**
 * Exports.
 */
module.exports = {
	preProcess: preProcess,
	postProcess: postProcess
};<commit_msg>Fix for issue #41<commit_after>var _ = require("lodash"); // Because of _.merge
var semver = require("semver");

/**
 * PreProcess.
 *
 * @param {Object[]} parsedFiles
 * @param {String[]} filenames
 * @param {String} useName
 * @param {String} defineName
 * @returns {Object}
 */
function preProcess(parsedFiles, filenames, useName, defineName)
{
	useName = useName || "permission";
	defineName = defineName || "definePermission";
	var result = {};
	for(var fileIndex = 0; fileIndex < parsedFiles.length; fileIndex += 1)
	{
		var parsedFile = parsedFiles[fileIndex];
		for(var blockIndex = 0; blockIndex < parsedFile.length; blockIndex += 1)
		{
			var block = parsedFile[blockIndex];
			if(block["global"][defineName])
			{
				var name = block["global"][defineName]["name"];
				var version = "0.0.0";

				if(block.version)
				{
					version = block.version;
				}

				if( ! result[defineName]) result[defineName] = {};
				if( ! result[defineName][name]) result[defineName][name] = {};
				result[defineName][name][version] = block["global"][defineName];
			}
		} // for blockIndex
	} // for fileIndex
	return result;
} // preProcess

/**
 * PostProcess.
 *
 * @param {Object[]} parsedFiles
 * @param {String[]} filenames
 * @param {Object[]} preProcessResults
 * @param {String} findName
 * @param {String} defineName
 */
function postProcess(parsedFiles, filenames, preProcessResults, findName, defineName)
{
	findName = findName || "permission";
	defineName = defineName || "definePermission";
	for(var fileIndex = 0; fileIndex < parsedFiles.length; fileIndex += 1)
	{
		var parsedFile = parsedFiles[fileIndex];
		for(var blockIndex = 0; blockIndex < parsedFile.length; blockIndex += 1)
		{
			var block = parsedFile[blockIndex];
			if(block["local"][findName])
			{
				var name = block["local"][findName];
				var version = "0.0.0";

				if(block.version)
				{
					version = block.version;
				}

				if(preProcessResults[defineName] && preProcessResults[defineName][name])
				{
					var matchedData = {};
					var matchedVersion = version;
					if(preProcessResults[defineName][name][version])
					{
						// Exact Version
						matchedData = preProcessResults[defineName][name][version];
					}
					else
					{
						// Find matching version
						var foundIndex = -1;
						var lastVersion = "0.0.0";

						var versionKeys = Object.keys(preProcessResults[defineName][name]);
						for(var versionIndex = 0; versionIndex < versionKeys.length; versionIndex += 1)
						{
							var currentVersion = versionKeys[versionIndex];
							if(semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion))
							{
								lastVersion = currentVersion;
								foundIndex = versionIndex;
							}
						} // for preIndex

						if(foundIndex === -1)
						{
							throw new Error("Name \"" + name + "\" in file \"" + filenames[fileIndex] +
								"\" block number " + block.index + " has no matching version. " +
								"Check if referenced definition block has a higher version number."
							);
						}
						var versionName = versionKeys[foundIndex];
						matchedData = preProcessResults[defineName][name][versionName];
					}

					// Copy matched elements into parsed block
					block.local[findName] = matchedData;
				}
//				else
//				{
//					// TODO: Show warning (no error).
//					throw new Error("Name \"" + name + "\" in file \"" + filenames[fileIndex] +
//						"\" block number " + block.index + " is not defined."
//					);
//				}
			}
		} // for blockIndex
	} // for fileIndex
} // postProcess

/**
 * Exports.
 */
module.exports = {
	preProcess: preProcess,
	postProcess: postProcess
};
<|endoftext|>


<commit_before>{
  "id":13,
  "name":"Universal Credit Digital Service",
  "description": "Lets users apply for benefit payments, manage appointments and report on their search for employment, so that they can take control of their monthly income and prepare to return to work.",
  "theme": "Universal Credit",
  "location": "London",
  "phase":"beta",
  "phase_modifier":"Public",
  "sro":"Neil Couling",
  "service_man":"Anthony Brigginshaw",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "April 2013"}
    ],
    "alpha": [
      {"label":"Completed", "date": "June 2013"}
    ],
    "beta": [
      {"label":"Completed: Delivered digital strategic solution", "date": "October 2013"}
    ]
  },
  "priority":"Top"
}
<commit_msg>Update universal-credit.js<commit_after>{
  "id":13,
  "name":"Universal Credit Digital Service",
  "description": "Universal Credit (UC) Digital provides a simple, personalised digital and non-digital service for claimants and staff. The service encourages individual responsibility, supporting people finding work and finding better paid work. The service fully supports claimants through applying and evidencing the range of benefits supported. UC Digital allows DWP agents to manage the benefit and accurately and timely maintain the claimant information while supporting the operations and delivery of the service.",
  "theme": "Universal Credit",
  "location": "London",
  "phase":"beta",
  "phase_modifier":"Public",
  "sro":"Neil Couling",
  "service_man":"Anthony Brigginshaw",
  "phase-history":{
    "discovery": [
      {"label":"Completed", "date": "April 2013"}
    ],
    "alpha": [
      {"label":"Completed", "date": "June 2013"}
    ],
    "beta": [
      {"label":"Completed: Delivered digital strategic solution", "date": "October 2013"}
    ]
  },
  "priority":"Top"
}
<|endoftext|>


<commit_before>console.log("Running service worker");
self.addEventListener('paymentrequest', function(event) {
    console.log("PaymentRequest: " + JSON.stringify(event.data));
    windowUrl = "https://tommythorsen.github.io/webpayments-demo/payment-apps/tommypay/app.html";
    ServiceWorkerClients.openWindow(windowUrl).then(function(WindowClient) {
        console.log("window opened!");
    });
});
<commit_msg>try again<commit_after>console.log("Running service worker");
self.addEventListener('paymentrequest', function(event) {
    console.log("PaymentRequest: " + JSON.stringify(event.data));
    windowUrl = "https://tommythorsen.github.io/webpayments-demo/payment-apps/tommypay/app.html";
    Clients.openWindow(windowUrl).then(function(WindowClient) {
        console.log("window opened!");
    });
});
<|endoftext|>


<commit_before>/**
 * @overview ccm component  pdf htm-viewer
 * @see https://github.com/mozilla/pdf.js/
 * @author Tea Kless <tea.kless@web.de>, 2018
 * @license The MIT License (MIT)
 */

{
  var component = {

    /**
     * unique component name
     * @type {string}
     */
    name: 'pdf_viewer',

    /**
     * recommended used framework version
     * @type {string}
     */
    ccm: 'https://akless.github.io/ccm/ccm.js',

    /**
     * default instance configuration
     * @type {object}
     */
    config: {

      "html": {
        "id": "pdf-viewer",
        "class": "container-fluid",
        "inner": [
          {
            "inner": {
              "tag": "a",
              "href": "%href%",
              "title": "Folien runterladen",
              "download": true,
              "target": "_blank",
              "inner": [
                {
                  "tag": "span",
                  "class": "glyphicon glyphicon-download"
                },
                " Download "
              ]
            }
          },
          {
            "id": "pdf-elem",
            "inner": {
              "id": "pdf-view",
              "inner": {
                "id": "canvas",
                "tag": "canvas"
              }
            }
          },
          {
            "class": "navigation text-center",
            "inner": {
                "class": "btn-group",
                "inner":[
                  {
                    "tag": "a",
                    "typ": "button",
                    "class": "active btn btn-prev btn-info info",
                    "onclick": "%prev%",
                    "inner": "Previous"
                  },
                  {
                    "tag": "a",
                    "typ": "button",
                    "id": "page-num",
                    "class": "active btn btn-success info",
                  },
                  {
                    "tag": "a",
                    "typ": "button",
                    "class": "btn btn-next btn-warning info",
                    "onclick": "%next%",
                    "inner": "Next"
                  }
                ]
              }
          }
        ]
      },
      path_to_pdf: "//cdn.mozilla.net/pdfjs/tracemonkey.pdf",
      scale: "1.5",
      //responsive: "..//pdf-viewer/resources/responsive.css",
      "pdfJS": [ "ccm.load", "//mozilla.github.io/pdf.js/build/pdf.js" ],
      "css": [ "ccm.load", "https://tkless.github.io/ccm-components/lib/bootstrap/css/bootstrap.css",
        { "context": "head", "url": "https://tkless.github.io/ccm-components/lib/bootstrap/css/font-face.css" },
        "..//pdf-viewer/resources/default.css"
      ]

    },

    Instance: function () {
      /**
       * own reference for inner functions
       * @type {Instance}
       */
      const self = this;

      /**
       * privatized instance members
       * @type {object}
       */
      let my;

      /**
       * shortcut to help functions
       * @type {Object.<string,function>}
       */
      let $;

      /**
       * is called once after the initialization and is then deleted
       * @param {function} callback - called after all synchronous and asynchronous operations are complete
       */

      let pdfDoc,
          pageNum ,
          pageRendering,
          pageNumPending,
          scale = 0.8,
          ctx;

      this.init = callback => {
        if ( self.responsive ) ccm.load( { context: this.element.parentNode, url:  "..//pdf-viewer/resources/responsive.css" } );
        // pdf.js
        pdfDoc = null;
        pageNum = 1;
        pageRendering = false;
        pageNumPending = null;
        scale = 0.8;

        callback();
      };

      this.ready = callback => {
        // set shortcut to help functions
        $ = self.ccm.helper;

        // privatize all possible instance members
        my = $.privatize( self );

        if ( self.logger ) self.logger.log( 'ready', my );

        // specify PDF.js workerSrc property
        PDFJS.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.js';

        /**
         * Asynchronously downloads PDF.
         */
        PDFJS.getDocument( my.path_to_pdf ).then( function( pdf ) {
          pdfDoc = pdf;
          callback();
        });

      };

      /**
       * starts the instance
       * @param {function} [callback] - called after all synchronous and asynchronous operations are complete
       */
      this.start = callback => {

        // render input elements
        $.setContent( self.element, $.html( my.html, {
          href: my.path_to_pdf,
          prev: function () {
            if ( self.logger ) self.logger.log( 'prev', pageNum-1 );

            // set active button
            self.element.querySelector( '.btn-next' ).classList.remove( 'active' );
            self.element.querySelector( '.btn-prev' ).classList.add( 'active' );
            onPrevPage();
            },
          next: function () {
            if ( self.logger ) self.logger.log( 'prev', pageNum+1 );

            // set active button
            self.element.querySelector( '.btn-prev' ).classList.remove( 'active' );
            self.element.querySelector( '.btn-next' ).classList.add( 'active' );
            onNextPage(); }
        } ) );

        // set canvas
        canvas = self.element.querySelector( 'canvas' );
        // disable to downloading the files
        canvas.addEventListener('contextmenu', function(e) {
          e.preventDefault();
        });
        ctx = canvas.getContext('2d');

        // Initial/first page rendering
        renderPage( pageNum );

        /**
         * Get page info from document, resize canvas accordingly, and render page.
         * @param num Page number.
         */
        function renderPage( num ) {
          pageRendering = true;
          // Using promise to fetch the page
          pdfDoc.getPage( num ).then( function( page ) {
            let viewport = page.getViewport( my.scale );
            canvas.height = viewport.height;
            canvas.width = viewport.width;

            // Render PDF page into canvas context
            let renderContext = {
              canvasContext: ctx,
              viewport: viewport
            };
            let renderTask = page.render( renderContext );

            // Wait for rendering to finish
            renderTask.promise.then(function() {
              pageRendering = false;
              if (pageNumPending !== null) {
                // New page rendering is pending
                renderPage(pageNumPending);
                pageNumPending = null;
              }
            });
          });

          // Update page counters
          self.element.querySelector('#page-num').innerHTML = num + " / "+ pdfDoc.numPages;

          //set page width to display page number in the middle of pdf-file
          //self.element.querySelector( '#page' ).style.width = self.element.querySelector( '#pdf-view' ).offsetWidth + "px";

        }

        /**
         * If another page rendering in progress, waits until the rendering is
         * finised. Otherwise, executes rendering immediately.
         */
        function queueRenderPage(num) {
          if (pageRendering) {
            pageNumPending = num;
          } else {
            renderPage(num);
          }
        }

        /**
         * Displays previous page.
         */
        function onPrevPage() {
          if (pageNum <= 1) {
            return;
          }
          pageNum--;
          queueRenderPage(pageNum);
        }

        /**
         * Displays next page.
         */
        function onNextPage() {
          if (pageNum >= pdfDoc.numPages) {
            return;
          }
          pageNum++;
          queueRenderPage(pageNum);
        }

        if( callback ) callback();

      };

    }

  };

  function p(){window.ccm[v].component(component)}const f="ccm."+component.name+(component.version?"-"+component.version.join("."):"")+".js";if(window.ccm&&null===window.ccm.files[f])window.ccm.files[f]=component;else{const n=window.ccm&&window.ccm.components[component.name];n&&n.ccm&&(component.ccm=n.ccm),"string"==typeof component.ccm&&(component.ccm={url:component.ccm});var v=component.ccm.url.split("/").pop().split("-");if(v.length>1?(v=v[1].split("."),v.pop(),"min"===v[v.length-1]&&v.pop(),v=v.join(".")):v="latest",window.ccm&&window.ccm[v])p();else{const e=document.createElement("script");document.head.appendChild(e),component.ccm.integrity&&e.setAttribute("integrity",component.ccm.integrity),component.ccm.crossorigin&&e.setAttribute("crossorigin",component.ccm.crossorigin),e.onload=function(){p(),document.head.removeChild(e)},e.src=component.ccm.url}}
}<commit_msg>center navigation buttons<commit_after>/**
 * @overview ccm component  pdf htm-viewer
 * @see https://github.com/mozilla/pdf.js/
 * @author Tea Kless <tea.kless@web.de>, 2018
 * @license The MIT License (MIT)
 */

{
  var component = {

    /**
     * unique component name
     * @type {string}
     */
    name: 'pdf_viewer',

    /**
     * recommended used framework version
     * @type {string}
     */
    ccm: 'https://akless.github.io/ccm/ccm.js',

    /**
     * default instance configuration
     * @type {object}
     */
    config: {

      "html": {
        "id": "pdf-viewer",
        "class": "container-fluid",
        "inner": [
          {
            "inner": {
              "tag": "a",
              "href": "%href%",
              "title": "Folien runterladen",
              "download": true,
              "target": "_blank",
              "inner": [
                {
                  "tag": "span",
                  "class": "glyphicon glyphicon-download"
                },
                " Download "
              ]
            }
          },
          {
            "id": "pdf-elem",
            "inner": {
              "id": "pdf-view",
              "inner": {
                "id": "canvas",
                "tag": "canvas"
              }
            }
          },
          {
            "id": "nav",
            "class": "navigation text-center",
            "inner": {
                "class": "btn-group",
                "inner":[
                  {
                    "tag": "a",
                    "typ": "button",
                    "class": "active btn btn-prev btn-info info",
                    "onclick": "%prev%",
                    "inner": "Previous"
                  },
                  {
                    "tag": "a",
                    "typ": "button",
                    "id": "page-num",
                    "class": "active btn btn-success info",
                  },
                  {
                    "tag": "a",
                    "typ": "button",
                    "class": "btn btn-next btn-warning info",
                    "onclick": "%next%",
                    "inner": "Next"
                  }
                ]
              }
          }
        ]
      },
      path_to_pdf: "//cdn.mozilla.net/pdfjs/tracemonkey.pdf",
      //pdf_as_data: [ "ccm.load", "path/to/ccm-dtastore"],
      scale: "1.5",
      //responsive: "..//pdf-viewer/resources/responsive.css",
      "pdfJS": [ "ccm.load", "//mozilla.github.io/pdf.js/build/pdf.js" ],
      "css": [ "ccm.load", "https://tkless.github.io/ccm-components/lib/bootstrap/css/bootstrap.css",
        { "context": "head", "url": "https://tkless.github.io/ccm-components/lib/bootstrap/css/font-face.css" },
        "..//pdf-viewer/resources/default.css"
      ]

    },

    Instance: function () {
      /**
       * own reference for inner functions
       * @type {Instance}
       */
      const self = this;

      /**
       * privatized instance members
       * @type {object}
       */
      let my;

      /**
       * shortcut to help functions
       * @type {Object.<string,function>}
       */
      let $;

      /**
       * is called once after the initialization and is then deleted
       * @param {function} callback - called after all synchronous and asynchronous operations are complete
       */

      let pdfDoc,
          pageNum ,
          pageRendering,
          pageNumPending,
          scale = 0.8,
          ctx;

      this.init = callback => {
        if ( self.responsive ) ccm.load( { context: this.element.parentNode, url:  "..//pdf-viewer/resources/responsive.css" } );
        // pdf.js
        pdfDoc = null;
        pageNum = 1;
        pageRendering = false;
        pageNumPending = null;
        scale = 0.8;

        callback();
      };

      this.ready = callback => {
        // set shortcut to help functions
        $ = self.ccm.helper;

        // privatize all possible instance members
        my = $.privatize( self );

        if ( self.logger ) self.logger.log( 'ready', my );

        // specify PDF.js workerSrc property
        PDFJS.workerSrc = '//mozilla.github.io/pdf.js/build/pdf.worker.js';

        /**
         * Asynchronously downloads PDF.
         */
        PDFJS.getDocument( my.path_to_pdf ).then( function( pdf ) {
          pdfDoc = pdf;
          callback();
        });

      };

      /**
       * starts the instance
       * @param {function} [callback] - called after all synchronous and asynchronous operations are complete
       */
      this.start = callback => {

        // render input elements
        $.setContent( self.element, $.html( my.html, {
          href: my.path_to_pdf,
          prev: function () {
            if ( self.logger ) self.logger.log( 'prev', pageNum-1 );

            // set active button
            self.element.querySelector( '.btn-next' ).classList.remove( 'active' );
            self.element.querySelector( '.btn-prev' ).classList.add( 'active' );
            onPrevPage();
            },
          next: function () {
            if ( self.logger ) self.logger.log( 'prev', pageNum+1 );

            // set active button
            self.element.querySelector( '.btn-prev' ).classList.remove( 'active' );
            self.element.querySelector( '.btn-next' ).classList.add( 'active' );
            onNextPage(); }
        } ) );

        // set canvas
        canvas = self.element.querySelector( 'canvas' );
        // disable to downloading the files
        canvas.addEventListener('contextmenu', function(e) {
          e.preventDefault();
        });
        ctx = canvas.getContext('2d');

        // Initial/first page rendering
        renderPage( pageNum );

        /**
         * Get page info from document, resize canvas accordingly, and render page.
         * @param num Page number.
         */
        function renderPage( num ) {
          pageRendering = true;
          // Using promise to fetch the page
          pdfDoc.getPage( num ).then( function( page ) {
            let viewport = page.getViewport( my.scale );
            canvas.height = viewport.height;
            canvas.width = viewport.width;

            // Render PDF page into canvas context
            let renderContext = {
              canvasContext: ctx,
              viewport: viewport
            };
            let renderTask = page.render( renderContext );

            // Wait for rendering to finish
            renderTask.promise.then(function() {
              pageRendering = false;
              if (pageNumPending !== null) {
                // New page rendering is pending
                renderPage(pageNumPending);
                pageNumPending = null;
              }

              //set width to display page number in the middle of pdf-file
              self.element.querySelector( '#nav' ).style.width = self.element.querySelector( '#canvas' ).offsetWidth + "px";
            });
          });

          // Update page counters
          self.element.querySelector('#page-num').innerHTML = num + " / "+ pdfDoc.numPages;

        }

        /**
         * If another page rendering in progress, waits until the rendering is
         * finised. Otherwise, executes rendering immediately.
         */
        function queueRenderPage(num) {
          if (pageRendering) {
            pageNumPending = num;
          } else {
            renderPage(num);

            //set width to display page number in the middle of pdf-file
            self.element.querySelector( '#nav' ).style.width = self.element.querySelector( '#canvas' ).offsetWidth + "px";
          }
        }

        /**
         * Displays previous page.
         */
        function onPrevPage() {
          if (pageNum <= 1) {
            return;
          }
          pageNum--;
          queueRenderPage(pageNum);
        }

        /**
         * Displays next page.
         */
        function onNextPage() {
          if (pageNum >= pdfDoc.numPages) {
            return;
          }
          pageNum++;
          queueRenderPage(pageNum);
        }

        if( callback ) callback();

      };

    }

  };

  function p(){window.ccm[v].component(component)}const f="ccm."+component.name+(component.version?"-"+component.version.join("."):"")+".js";if(window.ccm&&null===window.ccm.files[f])window.ccm.files[f]=component;else{const n=window.ccm&&window.ccm.components[component.name];n&&n.ccm&&(component.ccm=n.ccm),"string"==typeof component.ccm&&(component.ccm={url:component.ccm});var v=component.ccm.url.split("/").pop().split("-");if(v.length>1?(v=v[1].split("."),v.pop(),"min"===v[v.length-1]&&v.pop(),v=v.join(".")):v="latest",window.ccm&&window.ccm[v])p();else{const e=document.createElement("script");document.head.appendChild(e),component.ccm.integrity&&e.setAttribute("integrity",component.ccm.integrity),component.ccm.crossorigin&&e.setAttribute("crossorigin",component.ccm.crossorigin),e.onload=function(){p(),document.head.removeChild(e)},e.src=component.ccm.url}}
}<|endoftext|>


<commit_before>const StringUtil = require('../util/string-util');
const log = require('../util/log');

const loadVector_ = function (costume, runtime, rotationCenter, optVersion) {
    return new Promise(resolve => {
        let svgString = costume.asset.decodeText();
        // SVG Renderer load fixes "quirks" associated with Scratch 2 projects
        if (optVersion && optVersion === 2 && !runtime.v2SvgAdapter) {
            log.error('No V2 SVG adapter present; SVGs may not render correctly.');
        } else if (optVersion && optVersion === 2 && runtime.v2SvgAdapter) {
            runtime.v2SvgAdapter.loadString(svgString, true /* fromVersion2 */);
            svgString = runtime.v2SvgAdapter.toString();
            // Put back into storage
            const storage = runtime.storage;
            costume.asset.encodeTextData(svgString, storage.DataFormat.SVG, true);
            costume.assetId = costume.asset.assetId;
            costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
        }
        // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's
        // undefined here
        costume.skinId = runtime.renderer.createSVGSkin(svgString, rotationCenter);
        costume.size = runtime.renderer.getSkinSize(costume.skinId);
        // Now we should have a rotationCenter even if we didn't before
        if (!rotationCenter) {
            rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
            costume.rotationCenterX = rotationCenter[0];
            costume.rotationCenterY = rotationCenter[1];
            costume.bitmapResolution = 1;
        }

        resolve(costume);
    });
};

const canvasPool = (function () {
    /**
     * A pool of canvas objects that can be reused to reduce memory
     * allocations. And time spent in those allocations and the later garbage
     * collection.
     */
    class CanvasPool {
        constructor () {
            this.pool = [];
            this.clearSoon = null;
        }

        /**
         * After a short wait period clear the pool to let the VM collect
         * garbage.
         */
        clear () {
            if (!this.clearSoon) {
                this.clearSoon = new Promise(resolve => setTimeout(resolve, 1000))
                    .then(() => {
                        this.pool.length = 0;
                        this.clearSoon = null;
                    });
            }
        }

        /**
         * Return a canvas. Create the canvas if the pool is empty.
         * @returns {HTMLCanvasElement} A canvas element.
         */
        create () {
            return this.pool.pop() || document.createElement('canvas');
        }

        /**
         * Release the canvas to be reused.
         * @param {HTMLCanvasElement} canvas A canvas element.
         */
        release (canvas) {
            this.clear();
            this.pool.push(canvas);
        }
    }

    return new CanvasPool();
}());

/**
 * Return a promise to fetch a bitmap from storage and return it as a canvas
 * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3)
 * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function
 * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information.
 * @param {!object} costume - the Scratch costume object.
 * @param {!Runtime} runtime - Scratch runtime, used to access the v2BitmapAdapter
 * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If
 *     none is given, the rotation center of the costume will be set to the middle of the costume later on.
 * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume.
 * @returns {?Promise} - a promise which will resolve to an object {canvas, rotationCenter, assetMatchesBase},
 *     or reject on error.
 *     assetMatchesBase is true if the asset matches the base layer; false if it required adjustment
 */
const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {
    if (!costume || !costume.asset) {
        return Promise.reject('Costume load failed. Assets were missing.');
    }
    if (!runtime.v2BitmapAdapter) {
        return Promise.reject('No V2 Bitmap adapter present.');
    }

    return Promise.all([costume.asset, costume.textLayerAsset].map(asset => {
        if (!asset) {
            return null;
        }

        if (typeof createImageBitmap !== 'undefined') {
            return createImageBitmap(
                new Blob([asset.data], {type: asset.assetType.contentType})
            );
        }

        return new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = function () {
                resolve(image);
                image.onload = null;
                image.onerror = null;
            };
            image.onerror = function () {
                reject('Costume load failed. Asset could not be read.');
                image.onload = null;
                image.onerror = null;
            };
            image.src = asset.encodeDataURI();
        });
    }))
        .then(([baseImageElement, textImageElement]) => {
            const mergeCanvas = canvasPool.create();

            const scale = costume.bitmapResolution === 1 ? 2 : 1;
            mergeCanvas.width = baseImageElement.width;
            mergeCanvas.height = baseImageElement.height;

            const ctx = mergeCanvas.getContext('2d');
            ctx.drawImage(baseImageElement, 0, 0);
            if (textImageElement) {
                ctx.drawImage(textImageElement, 0, 0);
            }
            // Track the canvas we merged the bitmaps onto separately from the
            // canvas that we receive from resize if scale is not 1. We know
            // resize treats mergeCanvas as read only data. We don't know when
            // resize may use or modify the canvas. So we'll only release the
            // mergeCanvas back into the canvas pool. Reusing the canvas from
            // resize may cause errors.
            let canvas = mergeCanvas;
            if (scale !== 1) {
                canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale);
            }

            // By scaling, we've converted it to bitmap resolution 2
            if (rotationCenter) {
                rotationCenter[0] = rotationCenter[0] * scale;
                rotationCenter[1] = rotationCenter[1] * scale;
                costume.rotationCenterX = rotationCenter[0];
                costume.rotationCenterY = rotationCenter[1];
            }
            costume.bitmapResolution = 2;

            // Clean up the costume object
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;

            return {
                canvas,
                mergeCanvas,
                rotationCenter,
                // True if the asset matches the base layer; false if it required adjustment
                assetMatchesBase: scale === 1 && !textImageElement
            };
        })
        .catch(error => {
            log.warn(`${error.name}: ${error.message}`);
            // Clean up the text layer properties if it fails to load
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;
        });
};

const loadBitmap_ = function (costume, runtime, _rotationCenter) {
    return fetchBitmapCanvas_(costume, runtime, _rotationCenter)
        .then(fetched => {
            const updateCostumeAsset = function (dataURI) {
                if (!runtime.v2BitmapAdapter) {
                    // TODO: This might be a bad practice since the returned
                    // promise isn't acted on. If this is something we should be
                    // creating a rejected promise for we should also catch it
                    // somewhere and act on that error (like logging).
                    //
                    // Return a rejection to stop executing updateCostumeAsset.
                    return Promise.reject('No V2 Bitmap adapter present.');
                }

                const storage = runtime.storage;
                costume.asset = storage.createAsset(
                    storage.AssetType.ImageBitmap,
                    storage.DataFormat.PNG,
                    runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI),
                    null,
                    true // generate md5
                );
                costume.dataFormat = storage.DataFormat.PNG;
                costume.assetId = costume.asset.assetId;
                costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
            };

            if (!fetched.assetMatchesBase) {
                updateCostumeAsset(fetched.canvas.toDataURL());
            }

            return fetched;
        })
        .then(({canvas, mergeCanvas, rotationCenter}) => {
            // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined...
            costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter);
            canvasPool.release(mergeCanvas);
            const renderSize = runtime.renderer.getSkinSize(costume.skinId);
            costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2

            if (!rotationCenter) {
                rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
                // Actual rotation center, since all bitmaps are resolution 2
                costume.rotationCenterX = rotationCenter[0] * 2;
                costume.rotationCenterY = rotationCenter[1] * 2;
                costume.bitmapResolution = 2;
            }
            return costume;
        });
};

/**
 * Initialize a costume from an asset asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @property {!Asset} costume.asset - the asset of the costume loaded from storage.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostumeFromAsset = function (costume, runtime, optVersion) {
    costume.assetId = costume.asset.assetId;
    const renderer = runtime.renderer;
    if (!renderer) {
        log.error('No rendering module present; cannot load costume: ', costume.name);
        return Promise.resolve(costume);
    }
    const AssetType = runtime.storage.AssetType;
    let rotationCenter;
    // Use provided rotation center and resolution if they are defined. Bitmap resolution
    // should only ever be 1 or 2.
    if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&
            typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) {
        rotationCenter = [costume.rotationCenterX, costume.rotationCenterY];
    }
    if (costume.asset.assetType.runtimeFormat === AssetType.ImageVector.runtimeFormat) {
        return loadVector_(costume, runtime, rotationCenter, optVersion)
            .catch(() => {
                // Use default asset if original fails to load
                costume.assetId = runtime.storage.defaultAssetId.ImageVector;
                costume.asset = runtime.storage.get(costume.assetId);
                costume.md5 = `${costume.assetId}.${AssetType.ImageVector.runtimeFormat}`;
                return loadVector_(costume, runtime);
            });
    }
    return loadBitmap_(costume, runtime, rotationCenter, optVersion);
};

/**
 * Load a costume's asset into memory asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!string} md5ext - the MD5 and extension of the costume to be loaded.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostume = function (md5ext, costume, runtime, optVersion) {
    const idParts = StringUtil.splitFirst(md5ext, '.');
    const md5 = idParts[0];
    const ext = idParts[1].toLowerCase();
    costume.dataFormat = ext;

    if (costume.asset) {
        // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file
        return loadCostumeFromAsset(costume, runtime, optVersion);
    }

    // Need to load the costume from storage. The server should have a reference to this md5.
    if (!runtime.storage) {
        log.error('No storage module present; cannot load costume asset: ', md5ext);
        return Promise.resolve(costume);
    }

    if (!runtime.storage.defaultAssetId) {
        log.error(`No default assets found`);
        return Promise.resolve(costume);
    }

    const AssetType = runtime.storage.AssetType;
    const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap;

    const costumePromise = runtime.storage.load(assetType, md5, ext);
    if (!costumePromise) {
        log.error(`Couldn't fetch costume asset: ${md5ext}`);
        return;
    }

    let textLayerPromise;
    if (costume.textLayerMD5) {
        textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png');
    } else {
        textLayerPromise = Promise.resolve(null);
    }

    return Promise.all([costumePromise, textLayerPromise]).then(assetArray => {
        costume.asset = assetArray[0];
        if (assetArray[1]) {
            costume.textLayerAsset = assetArray[1];
        }
        return loadCostumeFromAsset(costume, runtime, optVersion);
    });
};

module.exports = {
    loadCostume,
    loadCostumeFromAsset
};
<commit_msg>Move error log to right place<commit_after>const StringUtil = require('../util/string-util');
const log = require('../util/log');

const loadVector_ = function (costume, runtime, rotationCenter, optVersion) {
    return new Promise(resolve => {
        let svgString = costume.asset.decodeText();
        // SVG Renderer load fixes "quirks" associated with Scratch 2 projects
        if (optVersion && optVersion === 2 && !runtime.v2SvgAdapter) {
            log.error('No V2 SVG adapter present; SVGs may not render correctly.');
        } else if (optVersion && optVersion === 2 && runtime.v2SvgAdapter) {
            runtime.v2SvgAdapter.loadString(svgString, true /* fromVersion2 */);
            svgString = runtime.v2SvgAdapter.toString();
            // Put back into storage
            const storage = runtime.storage;
            costume.asset.encodeTextData(svgString, storage.DataFormat.SVG, true);
            costume.assetId = costume.asset.assetId;
            costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
        }
        // createSVGSkin does the right thing if rotationCenter isn't provided, so it's okay if it's
        // undefined here
        costume.skinId = runtime.renderer.createSVGSkin(svgString, rotationCenter);
        costume.size = runtime.renderer.getSkinSize(costume.skinId);
        // Now we should have a rotationCenter even if we didn't before
        if (!rotationCenter) {
            rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
            costume.rotationCenterX = rotationCenter[0];
            costume.rotationCenterY = rotationCenter[1];
            costume.bitmapResolution = 1;
        }

        resolve(costume);
    });
};

const canvasPool = (function () {
    /**
     * A pool of canvas objects that can be reused to reduce memory
     * allocations. And time spent in those allocations and the later garbage
     * collection.
     */
    class CanvasPool {
        constructor () {
            this.pool = [];
            this.clearSoon = null;
        }

        /**
         * After a short wait period clear the pool to let the VM collect
         * garbage.
         */
        clear () {
            if (!this.clearSoon) {
                this.clearSoon = new Promise(resolve => setTimeout(resolve, 1000))
                    .then(() => {
                        this.pool.length = 0;
                        this.clearSoon = null;
                    });
            }
        }

        /**
         * Return a canvas. Create the canvas if the pool is empty.
         * @returns {HTMLCanvasElement} A canvas element.
         */
        create () {
            return this.pool.pop() || document.createElement('canvas');
        }

        /**
         * Release the canvas to be reused.
         * @param {HTMLCanvasElement} canvas A canvas element.
         */
        release (canvas) {
            this.clear();
            this.pool.push(canvas);
        }
    }

    return new CanvasPool();
}());

/**
 * Return a promise to fetch a bitmap from storage and return it as a canvas
 * If the costume has bitmapResolution 1, it will be converted to bitmapResolution 2 here (the standard for Scratch 3)
 * If the costume has a text layer asset, which is a text part from Scratch 1.4, then this function
 * will merge the two image assets. See the issue LLK/scratch-vm#672 for more information.
 * @param {!object} costume - the Scratch costume object.
 * @param {!Runtime} runtime - Scratch runtime, used to access the v2BitmapAdapter
 * @param {?object} rotationCenter - optionally passed in coordinates for the center of rotation for the image. If
 *     none is given, the rotation center of the costume will be set to the middle of the costume later on.
 * @property {number} costume.bitmapResolution - the resolution scale for a bitmap costume.
 * @returns {?Promise} - a promise which will resolve to an object {canvas, rotationCenter, assetMatchesBase},
 *     or reject on error.
 *     assetMatchesBase is true if the asset matches the base layer; false if it required adjustment
 */
const fetchBitmapCanvas_ = function (costume, runtime, rotationCenter) {
    if (!costume || !costume.asset) {
        return Promise.reject('Costume load failed. Assets were missing.');
    }
    if (!runtime.v2BitmapAdapter) {
        return Promise.reject('No V2 Bitmap adapter present.');
    }

    return Promise.all([costume.asset, costume.textLayerAsset].map(asset => {
        if (!asset) {
            return null;
        }

        if (typeof createImageBitmap !== 'undefined') {
            return createImageBitmap(
                new Blob([asset.data], {type: asset.assetType.contentType})
            );
        }

        return new Promise((resolve, reject) => {
            const image = new Image();
            image.onload = function () {
                resolve(image);
                image.onload = null;
                image.onerror = null;
            };
            image.onerror = function () {
                reject('Costume load failed. Asset could not be read.');
                image.onload = null;
                image.onerror = null;
            };
            image.src = asset.encodeDataURI();
        });
    }))
        .then(([baseImageElement, textImageElement]) => {
            const mergeCanvas = canvasPool.create();

            const scale = costume.bitmapResolution === 1 ? 2 : 1;
            mergeCanvas.width = baseImageElement.width;
            mergeCanvas.height = baseImageElement.height;

            const ctx = mergeCanvas.getContext('2d');
            ctx.drawImage(baseImageElement, 0, 0);
            if (textImageElement) {
                ctx.drawImage(textImageElement, 0, 0);
            }
            // Track the canvas we merged the bitmaps onto separately from the
            // canvas that we receive from resize if scale is not 1. We know
            // resize treats mergeCanvas as read only data. We don't know when
            // resize may use or modify the canvas. So we'll only release the
            // mergeCanvas back into the canvas pool. Reusing the canvas from
            // resize may cause errors.
            let canvas = mergeCanvas;
            if (scale !== 1) {
                canvas = runtime.v2BitmapAdapter.resize(mergeCanvas, canvas.width * scale, canvas.height * scale);
            }

            // By scaling, we've converted it to bitmap resolution 2
            if (rotationCenter) {
                rotationCenter[0] = rotationCenter[0] * scale;
                rotationCenter[1] = rotationCenter[1] * scale;
                costume.rotationCenterX = rotationCenter[0];
                costume.rotationCenterY = rotationCenter[1];
            }
            costume.bitmapResolution = 2;

            // Clean up the costume object
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;

            return {
                canvas,
                mergeCanvas,
                rotationCenter,
                // True if the asset matches the base layer; false if it required adjustment
                assetMatchesBase: scale === 1 && !textImageElement
            };
        })
        .catch(() => {
            // Clean up the text layer properties if it fails to load
            delete costume.textLayerMD5;
            delete costume.textLayerAsset;
        });
};

const loadBitmap_ = function (costume, runtime, _rotationCenter) {
    return fetchBitmapCanvas_(costume, runtime, _rotationCenter)
        .then(fetched => {
            const updateCostumeAsset = function (dataURI) {
                if (!runtime.v2BitmapAdapter) {
                    // TODO: This might be a bad practice since the returned
                    // promise isn't acted on. If this is something we should be
                    // creating a rejected promise for we should also catch it
                    // somewhere and act on that error (like logging).
                    //
                    // Return a rejection to stop executing updateCostumeAsset.
                    return Promise.reject('No V2 Bitmap adapter present.');
                }

                const storage = runtime.storage;
                costume.asset = storage.createAsset(
                    storage.AssetType.ImageBitmap,
                    storage.DataFormat.PNG,
                    runtime.v2BitmapAdapter.convertDataURIToBinary(dataURI),
                    null,
                    true // generate md5
                );
                costume.dataFormat = storage.DataFormat.PNG;
                costume.assetId = costume.asset.assetId;
                costume.md5 = `${costume.assetId}.${costume.dataFormat}`;
            };

            if (!fetched.assetMatchesBase) {
                updateCostumeAsset(fetched.canvas.toDataURL());
            }

            return fetched;
        })
        .then(({canvas, mergeCanvas, rotationCenter}) => {
            // createBitmapSkin does the right thing if costume.bitmapResolution or rotationCenter are undefined...
            costume.skinId = runtime.renderer.createBitmapSkin(canvas, costume.bitmapResolution, rotationCenter);
            canvasPool.release(mergeCanvas);
            const renderSize = runtime.renderer.getSkinSize(costume.skinId);
            costume.size = [renderSize[0] * 2, renderSize[1] * 2]; // Actual size, since all bitmaps are resolution 2

            if (!rotationCenter) {
                rotationCenter = runtime.renderer.getSkinRotationCenter(costume.skinId);
                // Actual rotation center, since all bitmaps are resolution 2
                costume.rotationCenterX = rotationCenter[0] * 2;
                costume.rotationCenterY = rotationCenter[1] * 2;
                costume.bitmapResolution = 2;
            }
            return costume;
        });
};

/**
 * Initialize a costume from an asset asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @property {!Asset} costume.asset - the asset of the costume loaded from storage.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostumeFromAsset = function (costume, runtime, optVersion) {
    costume.assetId = costume.asset.assetId;
    const renderer = runtime.renderer;
    if (!renderer) {
        log.error('No rendering module present; cannot load costume: ', costume.name);
        return Promise.resolve(costume);
    }
    const AssetType = runtime.storage.AssetType;
    let rotationCenter;
    // Use provided rotation center and resolution if they are defined. Bitmap resolution
    // should only ever be 1 or 2.
    if (typeof costume.rotationCenterX === 'number' && !isNaN(costume.rotationCenterX) &&
            typeof costume.rotationCenterY === 'number' && !isNaN(costume.rotationCenterY)) {
        rotationCenter = [costume.rotationCenterX, costume.rotationCenterY];
    }
    if (costume.asset.assetType.runtimeFormat === AssetType.ImageVector.runtimeFormat) {
        return loadVector_(costume, runtime, rotationCenter, optVersion)
            .catch(error => {
                log.warn(`${error.name}: ${error.message}`);
                // Use default asset if original fails to load
                costume.assetId = runtime.storage.defaultAssetId.ImageVector;
                costume.asset = runtime.storage.get(costume.assetId);
                costume.md5 = `${costume.assetId}.${AssetType.ImageVector.runtimeFormat}`;
                return loadVector_(costume, runtime);
            });
    }
    return loadBitmap_(costume, runtime, rotationCenter, optVersion);
};

/**
 * Load a costume's asset into memory asynchronously.
 * Do not call this unless there is a renderer attached.
 * @param {!string} md5ext - the MD5 and extension of the costume to be loaded.
 * @param {!object} costume - the Scratch costume object.
 * @property {int} skinId - the ID of the costume's render skin, once installed.
 * @property {number} rotationCenterX - the X component of the costume's origin.
 * @property {number} rotationCenterY - the Y component of the costume's origin.
 * @property {number} [bitmapResolution] - the resolution scale for a bitmap costume.
 * @param {!Runtime} runtime - Scratch runtime, used to access the storage module.
 * @param {?int} optVersion - Version of Scratch that the costume comes from. If this is set
 *     to 2, scratch 3 will perform an upgrade step to handle quirks in SVGs from Scratch 2.0.
 * @returns {?Promise} - a promise which will resolve after skinId is set, or null on error.
 */
const loadCostume = function (md5ext, costume, runtime, optVersion) {
    const idParts = StringUtil.splitFirst(md5ext, '.');
    const md5 = idParts[0];
    const ext = idParts[1].toLowerCase();
    costume.dataFormat = ext;

    if (costume.asset) {
        // Costume comes with asset. It could be coming from camera, image upload, drag and drop, or file
        return loadCostumeFromAsset(costume, runtime, optVersion);
    }

    // Need to load the costume from storage. The server should have a reference to this md5.
    if (!runtime.storage) {
        log.error('No storage module present; cannot load costume asset: ', md5ext);
        return Promise.resolve(costume);
    }

    if (!runtime.storage.defaultAssetId) {
        log.error(`No default assets found`);
        return Promise.resolve(costume);
    }

    const AssetType = runtime.storage.AssetType;
    const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap;

    const costumePromise = runtime.storage.load(assetType, md5, ext);
    if (!costumePromise) {
        log.error(`Couldn't fetch costume asset: ${md5ext}`);
        return;
    }

    let textLayerPromise;
    if (costume.textLayerMD5) {
        textLayerPromise = runtime.storage.load(AssetType.ImageBitmap, costume.textLayerMD5, 'png');
    } else {
        textLayerPromise = Promise.resolve(null);
    }

    return Promise.all([costumePromise, textLayerPromise]).then(assetArray => {
        costume.asset = assetArray[0];
        if (assetArray[1]) {
            costume.textLayerAsset = assetArray[1];
        }
        return loadCostumeFromAsset(costume, runtime, optVersion);
    });
};

module.exports = {
    loadCostume,
    loadCostumeFromAsset
};
<|endoftext|>


<commit_before>'use strict';

jQuery(function ($) {
    var destinationOptions = $('#destination').find('option');
    var destinationOptionLength = destinationOptions.length;
    var submit = $('.js-submit-copy');

    if (destinationOptionLength === 1 && destinationOptions.prop('disabled')) {
        submit.prop('disabled', true);
    }
});
<commit_msg>adds commit to copy file js<commit_after>'use strict';
/*
    This functionality is used in folder/choose_copy_destination.html template
    to disable submit if there is only one folder to copy
*/

jQuery(function ($) {
    var destinationOptions = $('#destination').find('option');
    var destinationOptionLength = destinationOptions.length;
    var submit = $('.js-submit-copy');

    if (destinationOptionLength === 1 && destinationOptions.prop('disabled')) {
        submit.prop('disabled', true);
    }
});
<|endoftext|>


<commit_before><commit_msg>Update project cropit to v0.5.1<commit_after><|endoftext|>


<commit_before><commit_msg>Update project js-data to 3.0.0-alpha.3<commit_after><|endoftext|>