<commit_before>
/**
 * Groups model methods file
 */

// module dependencies
var Q = require('q');
var constant = require('../constants/groups');
var orm = require('orm');

/**
 * Groups model static methods
 */
exports.static = {
  /**
   * Check group permission with information
   * and then resolve a group instance or reject with reason
   * @param info Requested params, group and secret required
   * @return {Q.promise} Q promise with group instance resolve
   */
  getAuthGroup: function (info) {
    var groupName = info.group;
    var authSecret = info.secret;
    // find group model where name matched
    // name unique promised so limit 1 is ok
    return Q.ninvoke(this, 'find', {name: groupName}, 1)
      .then(function (found) {
        // TODO: different error type: server error or client error
        var group = found[0]; // just one/zero result
        if (!group) {
          throw new Error('Group not found'); // TODO: i18n
        } else if (!group.isSecretOK(authSecret)) {
          // TODO: authSecret is '' and fail auth
          throw new Error('Bad secret'); // TODO: i18n
        } else {
          return group;
        }
      });
  },

  /**
   * Check group permission with Express request object
   * and then resolve a group instance or reject with reason
   * @param req Express request object with params
   * @return {Q.promise} Q promise with group instance resolve
   */
  getReqAuthGroup: function (req) {
    // TODO: Check req.session for user
    return this.getAuthGroup({
      group: req.params.group,
      secret: req.params.secret
    });
  }
};

/**
 * Groups model instance methods
 */
exports.instance = {
  /**
   * Check secret code for group instance
   * @param {string} secret Secret code provided by user
   * @returns {boolean} True if group auth successful
   */
  isSecretOK: function(secret) {
    // group is public or secret code matched
    return (this.secret === constant.PUBLIC_SECRET ||
      this.secret === secret);
  },

  /**
   * Fetch old messages with lastID
   * or last messages when lastID is null
   * @param {int} lastID Last fetched ID
   * @param {int} perQuery Count of return items
   * @returns {Q.promise} Q promise with messages list resolve
   */
  getMessagesWithLastID: function(lastID, perQuery) {
    var query = this.getMessages();
    if (lastID) { // if fetched
      query = query.find({id: orm.lt(lastID)});
    } // else if is first query, select last messages
    query = query.order('-id').limit(perQuery);
    return Q.ninvoke(query, 'run');
  }
};
<commit_msg>fix compatibility with hasOne<commit_after>
/**
 * Groups model methods file
 */

// module dependencies
var Q = require('q');
var constant = require('../constants/groups');
var orm = require('orm');

/**
 * Groups model static methods
 */
exports.static = {
  /**
   * Check group permission with information
   * and then resolve a group instance or reject with reason
   * @param info Requested params, group and secret required
   * @return {Q.promise} Q promise with group instance resolve
   */
  getAuthGroup: function (info) {
    var groupName = info.group;
    var authSecret = info.secret;
    // find group model where name matched
    // name unique promised so limit 1 is ok
    return Q.ninvoke(this, 'find', {name: groupName}, 1)
      .then(function (found) {
        // TODO: different error type: server error or client error
        var group = found[0]; // just one/zero result
        if (!group) {
          throw new Error('Group not found'); // TODO: i18n
        } else if (!group.isSecretOK(authSecret)) {
          // TODO: authSecret is '' and fail auth
          throw new Error('Bad secret'); // TODO: i18n
        } else {
          return group;
        }
      });
  },

  /**
   * Check group permission with Express request object
   * and then resolve a group instance or reject with reason
   * @param req Express request object with params
   * @return {Q.promise} Q promise with group instance resolve
   */
  getReqAuthGroup: function (req) {
    // TODO: Check req.session for user
    return this.getAuthGroup({
      group: req.params.group,
      secret: req.params.secret
    });
  }
};

/**
 * Groups model instance methods
 */
exports.instance = {
  /**
   * Check secret code for group instance
   * @param {string} secret Secret code provided by user
   * @returns {boolean} True if group auth successful
   */
  isSecretOK: function(secret) {
    // group is public or secret code matched
    return (this.secret === constant.PUBLIC_SECRET ||
      this.secret === secret);
  },

  /**
   * Fetch old messages with lastID
   * or last messages when lastID is null
   * @param {int} lastID Last fetched ID
   * @param {int} perQuery Count of return items
   * @returns {Q.promise} Q promise with messages list resolve
   */
  getMessagesWithLastID: function(lastID, perQuery) {
    var database = require('../database');
    var query = database.db.models.messages.findByGroup({id: this.id});
    if (lastID) { // if fetched
      query = query.find({id: orm.lt(lastID)});
    } // else if is first query, select last messages
    query = query.order('-id').limit(perQuery);
    return Q.ninvoke(query, 'run');
  }
};
<|endoftext|>


<commit_before>function getMonzo() {
  return {
    // general
    name: "Monzo Account",
    provider: "monzo",

    // auth
    authType: "oauth2",
    accessToken: null,
    accessExpires: -1,
    // variables below used while auth is being negotiated
    clientId: null,
    clientSecret: null,
    state: null,
    redirectUri: null,

    // other variables
    accountId: null,
    accountName: null,
    cardId: null,
    cardDigits: null,
    cardExpires: null
  };
}

// auth functions
function monzoAuth(monzo) {
  if (monzo.clientId == null) {
    throw new Error("clientId isn't set and yet auth was called!");
  }
  if (monzo.clientSecret == null) {
    throw new Error("clientSecret isn't set and yet auth was called!");
  }
  if (monzo.state == null) {
    throw new Error("state isn't set and yet auth was called!");
  }
  if (monzo.redirectUri == null) {
    throw new Error("redirectUri isn't set and yet auth was called!");
  }
  window.location = "https://auth.getmondo.co.uk/?client_id=" + monzo.clientId + "&redirect_uri=" + monzo.redirectUri + "&response_type=code&state=" + monzo.state;
}

function monzoCallback(monzo, authCode) {
  if (monzo.clientId == null) {
    throw new Error("clientId isn't set and yet callback was called!");
  }
  if (monzo.clientSecret == null) {
    throw new Error("clientSecret isn't set and yet callback was called!");
  }
  if (monzo.state == null) {
    throw new Error("state isn't set and yet callback was called!");
  }
  if (monzo.redirectUri == null) {
    throw new Error("redirectUri isn't set and yet callback was called!");
  }

  var request = new XMLHttpRequest();
  request.open("POST", "https://api.monzo.com/oauth2/token", false);
  request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  request.send("grant_type=authorization_code&client_id=" + monzo.clientId + "&client_secret=" + monzo.clientSecret + "&redirect_uri=" + monzo.redirectUri + "&code=" + authCode);
  if (request.status == 200) {
    monzo.accessToken = JSON.parse(request.responseText).access_token;
    monzo.accessExpires = Date.now() + JSON.parse(request.responseText).expires_in * 1000;
    // clear the variables we were using while getting an access token
    delete monzo["clientId"];
    delete monzo["clientSecret"];
    delete monzo["redirectUri"];
    delete monzo["state"];
    // get the account id
    var request = new XMLHttpRequest();
    request.open("GET", "https://api.monzo.com/accounts", false);
    request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
    request.send();
    monzo.accountId = JSON.parse(request.responseText).accounts[0].id;
  }
}

// HTML functions
function monzoTransactionHTML(monzo, id) {
  var HTML = "";
  HTML += "
" + monzo.name + "
";
  return HTML;
}

function monzoDetailsHTML(monzo, id) {
  var HTML = "";
  var balance = monzoGetBalance(monzo);
  var card = monzoGetCards(monzo);
  HTML += "
Welcome, " + card.name + "!
";
  HTML += "
Balance: £" + (balance.balance*0.01).toFixed(2) + "
";
  HTML += "
You spent £" + (balance.spend_today*0.01).toFixed(2) + " today.
";
  if (card.status = "ACTIVE") {
    HTML += "
Your card (last 4 digits " + card.last_digits + ") is currently active.
";
    HTML += 'Freeze your card
';
  } else if (card.status = "INACTIVE") {
    HTML += "
Your card (last 4 digits " + card.last_digits + ") is currently frozen.
";
    HTML += 'Defrost your card
';
  } else {
    HTML += "
Your card (last 4 digits " + card.last_digits + ") has unknown status!
";
  }
  return HTML;
}

// other functions
function monzoGetBalance(monzo) {
  var request = new XMLHttpRequest();
  request.open("GET", "https://api.monzo.com/balance?account_id=" + monzo.accountId, false);
  request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
  request.send();
  return JSON.parse(request.responseText);
}

function monzoGetCards(monzo) {
  var request = new XMLHttpRequest();
  request.open("GET", "https://api.monzo.com/card/list?account_id=" + monzo.accountId, false);
  request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
  request.send();
  return JSON.parse(request.responseText).cards[0];
}

function monzoFreezeCard(cardId, targetStatus) {
  var request = new XMLHttpRequest();
  request.open("PUT", "https://api.monzo.com/card/toggle?card_id=" + cardId + "&status=" + targetStatus, false);
  request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
  request.send();
  location.reload();
}<commit_msg>Tweaked freezing card so it now takes an account (instead of a card ID) as an argument - the first card will be operated on<commit_after>function getMonzo() {
  return {
    // general
    name: "Monzo Account",
    provider: "monzo",

    // auth
    authType: "oauth2",
    accessToken: null,
    accessExpires: -1,
    // variables below used while auth is being negotiated
    clientId: null,
    clientSecret: null,
    state: null,
    redirectUri: null,

    // other variables
    accountId: null,
    cardId: null
  };
}

// auth functions
function monzoAuth(monzo) {
  if (monzo.clientId == null) {
    throw new Error("clientId isn't set and yet auth was called!");
  }
  if (monzo.clientSecret == null) {
    throw new Error("clientSecret isn't set and yet auth was called!");
  }
  if (monzo.state == null) {
    throw new Error("state isn't set and yet auth was called!");
  }
  if (monzo.redirectUri == null) {
    throw new Error("redirectUri isn't set and yet auth was called!");
  }
  window.location = "https://auth.getmondo.co.uk/?client_id=" + monzo.clientId + "&redirect_uri=" + monzo.redirectUri + "&response_type=code&state=" + monzo.state;
}

function monzoCallback(monzo, authCode) {
  if (monzo.clientId == null) {
    throw new Error("clientId isn't set and yet callback was called!");
  }
  if (monzo.clientSecret == null) {
    throw new Error("clientSecret isn't set and yet callback was called!");
  }
  if (monzo.state == null) {
    throw new Error("state isn't set and yet callback was called!");
  }
  if (monzo.redirectUri == null) {
    throw new Error("redirectUri isn't set and yet callback was called!");
  }

  var request = new XMLHttpRequest();
  request.open("POST", "https://api.monzo.com/oauth2/token", false);
  request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
  request.send("grant_type=authorization_code&client_id=" + monzo.clientId + "&client_secret=" + monzo.clientSecret + "&redirect_uri=" + monzo.redirectUri + "&code=" + authCode);
  if (request.status == 200) {
    monzo.accessToken = JSON.parse(request.responseText).access_token;
    monzo.accessExpires = Date.now() + JSON.parse(request.responseText).expires_in * 1000;
    // clear the variables we were using while getting an access token
    delete monzo["clientId"];
    delete monzo["clientSecret"];
    delete monzo["redirectUri"];
    delete monzo["state"];
    // get the account id
    var request = new XMLHttpRequest();
    request.open("GET", "https://api.monzo.com/accounts", false);
    request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
    request.send();
    monzo.accountId = JSON.parse(request.responseText).accounts[0].id;
    monzo.cardId = monzoGetCards(monzo).id;
  }
}

// HTML functions
function monzoTransactionHTML(monzo, id) {
  var HTML = "";
  HTML += "
" + monzo.name + "
";
  return HTML;
}

function monzoDetailsHTML(monzo, id) {
  var HTML = "";
  var balance = monzoGetBalance(monzo);
  var card = monzoGetCards(monzo);
  HTML += "
Welcome, " + card.name + "!
";
  HTML += "
Balance: £" + (balance.balance*0.01).toFixed(2) + "
";
  HTML += "
You spent £" + (balance.spend_today*0.01).toFixed(2) + " today.
";
  if (card.status = "ACTIVE") {
    HTML += "
Your card (last 4 digits " + card.last_digits + ") is currently active.
";
    HTML += 'Freeze your card
';
  } else if (card.status = "INACTIVE") {
    HTML += "
Your card (last 4 digits " + card.last_digits + ") is currently frozen.
";
    HTML += 'Defrost your card
';
  } else {
    HTML += "
Your card (last 4 digits " + card.last_digits + ") has unknown status!
";
  }
  return HTML;
}

// other functions
function monzoGetBalance(monzo) {
  var request = new XMLHttpRequest();
  request.open("GET", "https://api.monzo.com/balance?account_id=" + monzo.accountId, false);
  request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
  request.send();
  return JSON.parse(request.responseText);
}

function monzoGetCards(monzo) {
  var request = new XMLHttpRequest();
  request.open("GET", "https://api.monzo.com/card/list?account_id=" + monzo.accountId, false);
  request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
  request.send();
  return JSON.parse(request.responseText).cards[0];
}

function monzoFreezeCard(monzo, targetStatus) {
  var request = new XMLHttpRequest();
  request.open("PUT", "https://api.monzo.com/card/toggle?card_id=" + monzo.cardId + "&status=" + targetStatus, false);
  request.setRequestHeader("Authorization", "Bearer " + monzo.accessToken);
  request.send();
  location.reload();
}<|endoftext|>


<commit_before>var ip = "192.168.1.21:4316"
var oldjson;

function genboard() {
    $.ajax({
        type: "POST",
        url: "/api/service/list",
        success: function(response) {
            console.log(response);

            var json_obj = response; //parse JSON
            if (json_obj != oldjson) {
                var songoutput = "

Songs

";
                var bibleoutput = "

Readings

";
                for (var i in json_obj.results.items) {
                    if (json_obj.results.items[i].plugin == "songs") {
                        var classes = json_obj.results.items[i].selected ? "sellected" : "";
                        songoutput += "" + json_obj.results.items[i].title + "
";
                    } else if (json_obj.results.items[i].plugin == "bibles") {
                        var classes = json_obj.results.items[i].selected ? "sellected" : "";
                        bibleoutput += "" + json_obj.results.items[i].title.split(", ")[0] + "
";
                    }

                }
                songoutput += "

";
                bibleoutput += "

";
                output = songoutput + bibleoutput;
                $("#board").html(output);
                oldjson = json_obj;
            }
        }
    })
}

function doPoll() {
    genboard(); // process results here
    setTimeout(doPoll, 5000);
}
doPoll();
<commit_msg>made page update only happen if there has been a change in the json<commit_after>var ip = "192.168.1.21:4316"
var oldjson;

function genboard() {
    $.ajax({
        type: "POST",
        url: "/api/service/list",
        success: function(response) {
            console.log(response);

            var json_obj = response; //parse JSON
            if (JSON.stringify(json_obj) != oldjson) {
                var songoutput = "

Songs

";
                var bibleoutput = "

Readings

";
                for (var i in json_obj.results.items) {
                    if (json_obj.results.items[i].plugin == "songs") {
                        var classes = json_obj.results.items[i].selected ? "sellected" : "";
                        songoutput += "" + json_obj.results.items[i].title + "
";
                    } else if (json_obj.results.items[i].plugin == "bibles") {
                        var classes = json_obj.results.items[i].selected ? "sellected" : "";
                        bibleoutput += "" + json_obj.results.items[i].title.split(", ")[0] + "
";
                    }

                }
                songoutput += "

";
                bibleoutput += "

";
                output = songoutput + bibleoutput;
                $("#board").html(output);
                oldjson = JSON.stringify(json_obj);
            }
        }
    })
}

function doPoll() {
    genboard(); // process results here
    setTimeout(doPoll, 5000);
}
doPoll();
<|endoftext|>


<commit_before>/*
* Unobtrusive autocomplete
*
* To use it, you just have to include the HTML attribute autocomplete
* with the autocomplete URL as the value
*
*   Example:
*       <input type="text" data-autocomplete="/url/to/autocomplete">
*
* Optionally, you can use a jQuery selector to specify a field that can
* be updated with the element id whenever you find a matching value
*
*   Example:
*       <input type="text" data-autocomplete="/url/to/autocomplete" id_element="#id_field">
*/

$(document).ready(function(){
	$('input[data-autocomplete]').railsAutocomplete();
});

(function(jQuery)
{
	var self = null;
	jQuery.fn.railsAutocomplete = function() {
		return this.live('focus',function() {
			if (!this.railsAutoCompleter) {
				this.railsAutoCompleter = new jQuery.railsAutocomplete(this);
			}
		});
	};

	jQuery.railsAutocomplete = function (e) {
		_e = e;
		this.init(_e);
	};

	jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = {
		railsAutocomplete: '0.0.1'
	};

	jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend;
	jQuery.railsAutocomplete.fn.extend({
		init: function(e) {
			e.delimiter = $(e).attr('data-delimiter') || null;
			function split( val ) {
				return val.split( e.delimiter );
			}
			function extractLast( term ) {
				return split( term ).pop().replace(/^\s+/,"");
			}

	    $(e).autocomplete({
				source: function( request, response ) {
					$.getJSON( $(e).attr('data-autocomplete'), {
					term: extractLast( request.term )
					}, function() {
                        $(arguments[0]).each(function(i, el) {
                            var obj = {};
                            obj[el.id] = el;
                            $(e).data(obj);
                        });
                        response.apply(null, arguments);
                    });
				},
				search: function() {
					// custom minLength
					var term = extractLast( this.value );
					if ( term.length < 2 ) {
						return false;
					}
				},
				focus: function() {
								// prevent value inserted on focus
					return false;
				},
				select: function( event, ui ) {
					var terms = split( this.value );
					// remove the current input
					terms.pop();
					// add the selected item
					terms.push( ui.item.value );
					// add placeholder to get the comma-and-space at the end
					if (e.delimiter != null) {
						terms.push( "" );
						this.value = terms.join( e.delimiter );
					} else {
						this.value = terms.join("");
						if ($(this).attr('id_element')) {
							$($(this).attr('id_element')).val(ui.item.id);
						}
                        if ($(this).attr('data-update-elements')) {
                            var data = $(this).data(ui.item.id.toString());
                            var update_elements = $.parseJSON($(this).attr("data-update-elements"));
                            for (var key in update_elements) {
                                $(update_elements[key]).val(data[key]);
                            }
                        }
					}
					$(this).trigger('railsAutocomplete.select');

					return false;
				}
			});
    }
  });
})(jQuery);
<commit_msg>Clear old id from id_element if user makes change to autocomplete field after having made previous selection.<commit_after>/*
* Unobtrusive autocomplete
*
* To use it, you just have to include the HTML attribute autocomplete
* with the autocomplete URL as the value
*
*   Example:
*       <input type="text" data-autocomplete="/url/to/autocomplete">
*
* Optionally, you can use a jQuery selector to specify a field that can
* be updated with the element id whenever you find a matching value
*
*   Example:
*       <input type="text" data-autocomplete="/url/to/autocomplete" id_element="#id_field">
*/

$(document).ready(function(){
	$('input[data-autocomplete]').railsAutocomplete();
});

(function(jQuery)
{
	var self = null;
	jQuery.fn.railsAutocomplete = function() {
		return this.live('focus',function() {
			if (!this.railsAutoCompleter) {
				this.railsAutoCompleter = new jQuery.railsAutocomplete(this);
			}
		});
	};

	jQuery.railsAutocomplete = function (e) {
		_e = e;
		this.init(_e);
	};

	jQuery.railsAutocomplete.fn = jQuery.railsAutocomplete.prototype = {
		railsAutocomplete: '0.0.1'
	};

	jQuery.railsAutocomplete.fn.extend = jQuery.railsAutocomplete.extend = jQuery.extend;
	jQuery.railsAutocomplete.fn.extend({
		init: function(e) {
			e.delimiter = $(e).attr('data-delimiter') || null;
			function split( val ) {
				return val.split( e.delimiter );
			}
			function extractLast( term ) {
				return split( term ).pop().replace(/^\s+/,"");
			}

	    $(e).autocomplete({
				source: function( request, response ) {
					$.getJSON( $(e).attr('data-autocomplete'), {
					term: extractLast( request.term )
					}, function() {
                        $(arguments[0]).each(function(i, el) {
                            var obj = {};
                            obj[el.id] = el;
                            $(e).data(obj);
                        });
                        response.apply(null, arguments);
                    });
				},
				search: function() {
					// custom minLength
					var term = extractLast( this.value );
					if ( term.length < 2 ) {
						return false;
					}
				},
				focus: function() {
								// prevent value inserted on focus
					return false;
				},
				select: function( event, ui ) {
					var terms = split( this.value );
					// remove the current input
					terms.pop();
					// add the selected item
					terms.push( ui.item.value );
					// add placeholder to get the comma-and-space at the end
					if (e.delimiter != null) {
						terms.push( "" );
						this.value = terms.join( e.delimiter );
					} else {
						this.value = terms.join("");
						if ($(this).attr('id_element')) {
							$($(this).attr('id_element')).val(ui.item.id);
						}
                        if ($(this).attr('data-update-elements')) {
                            var data = $(this).data(ui.item.id.toString());
                            var update_elements = $.parseJSON($(this).attr("data-update-elements"));
                            for (var key in update_elements) {
                                $(update_elements[key]).val(data[key]);
                            }
                        }
					}
// If a user changes the field after already making a selection make sure to remove id of a previous selection.
                                        $(this).bind('change.clearId', function(){
                                                $($(this).attr('id_element')).val("");
                                                $(this).unbind('change.clearId');
                                            });
					$(this).trigger('railsAutocomplete.select');

					return false;
				}
			});
    }
  });
})(jQuery);
<|endoftext|>


<commit_before>$(() => {
  // Add 'target="_blank"' to any links in the annotation/guidance sections,
  // question text and example answer text
  $('#plan-guidance-tab .panel-body p > a, #new_answer .form-group p > a, #new_answer .panel-body p > a').each((i, el) => {
    const link = $(el);
    if (!link.attr('target')) {
      link.attr('target', '_blank');
    }
  });
});
<commit_msg>removed trailing spaces from JS<commit_after>$(() => {
  // Add 'target="_blank"' to any links in the annotation/guidance sections,
  // question text and example answer text
  $('#plan-guidance-tab .panel-body p > a, #new_answer .form-group p > a, #new_answer .panel-body p > a').each((i, el) => {
    const link = $(el);
    if (!link.attr('target')) {
      link.attr('target', '_blank');
    }
  });
});
<|endoftext|>


<commit_before>var fs               = require('fs')
,   util             = require('util')
,   events           = require('events')
,   moment           = require('moment')
,   Playlist         = require('./api/Playlist')
,   Melted           = require('./api/Melted')
,   Media            = require('./api/Media')
,   ScheduledMedia   = require('./api/ScheduledMedia')
,   mvcp_server      = require('./drivers/mvcp/mvcp-driver')
,   playlists_driver = require('./drivers/playlists/playlists-driver')
,   status_driver    = require('./drivers/status/pubsub')
,   utils            = require('./utils')
,   config           = require('mbc-common').config.Mosto.General
,   _                = require('underscore')
,   heartbeats        = require('./heartbeats')
,   models           = require('./models/Mosto')
;

function mosto(customConfig) {

    /** CONFIGURATION */
    this.config = customConfig || config;
    this.server = undefined;
    this.server_started = false;
    this.pl_driver = undefined;
    this.status_driver = undefined;

    /* MODULES */
    this.heartbeats = undefined;

    events.EventEmitter.call(this);
}

util.inherits(mosto, events.EventEmitter);

mosto.prototype.initDriver = function() {

    console.log("mbc-mosto: [INFO] Initializing playlists driver");

    self.driver.on ("create", function(playlist) {

        self.fetcher.addPlaylist( playlist, self.fetcher );
    } );
    self.driver.on ("update", function(playlist) {
        self.fetcher.updatePlaylist( playlist, self.fetcher);
    } );
    self.driver.on ("delete", function(playlist) {
        self.fetcher.removePlaylist( playlist, self.fetcher);
    } );

    self.driver.start();
};

mosto.prototype.stopDriver = function() {

    console.log("mbc-mosto: [INFO] Stopping playlists driver");

    self.driver.stop();

    self.driver.removeAllListeners("create");
    self.driver.removeAllListeners("update");
    self.driver.removeAllListeners("delete");
};

mosto.prototype.startMvcpServer = function(callback) {
    var result = self.server.initServer();
    result.then(function() {
        console.log("mbc-mosto: [INFO] MVCP server started");
        self.server_started = true;
        if (callback !== undefined) {
            callback();
        }
    }, function(err) {
        var e = new Error("mbc-mosto: [ERROR] Error starting MVCP server: " + err + ".\nRetrying in 2 seconds...");
        console.error(e);
        setTimeout(function() {
            self.startMvcpServer(callback);
        }, 2000);
    });
};

console.log("mbc-mosto: [INFO] Starting mbc-mosto... ") ;
mosto.prototype.initHeartbeats = function() {
    var self = this;

    console.log("mbc-mosto: [INFO] Initializing heartbeats");

    self.heartbeats.on('frameStatus', function(status) {
        self.status_driver.setStatusClip(StatusClip(
            status.media.id,
            status.media.get('playlist_order'),
            status.media.playlist.id,
            status.media.get('fps'),
            status.frame,
            status.media.get('length')
        ));
    });
    self.heartbeats.on('clipStatus', function(media) {
        var melted_medias = self.playlists.get('melted_medias');
        var playlists = self.playlists.get('playlists');
        var status = {
            clip:{
                previous: undefined,
                current: undefined,
                next: undefined
            },
            show: {
                previous: undefined,
                current: undefined,
                next: undefined
            },
            position: 0,
            clips: melted_medias.toJSON(),
            status: none
        };
        /* clips */
        var index = melted_medias.indexOf(media);
        status.clip.current = media.toJSON();
        if( index > 0 )
            status.clip.previous = melted_medias.at(index-1).toJSON();
        if( index < melted_medias.length )
            status.clip.next = melted_medias.at(index+1).toJSON();
        /* shows */
        var playlist = playlists.find(function(pl) {
            return pl.indexOf(media) >= 0;
        });
        index = playlists.indexOf(playlist);
        status.show.current = playlist.toJSON();
        if( index > 0 )
            status.show.previous = playlists.at(index-1).toJSON();
        if( index < playlists.length )
            status.show.next = playlists.at(index+1).toJSON();
        self.status_driver.setStatus(status);
    });
    self.heartbeats.init();
};
mosto.prototype.fetchPlaylists = function(window) {
    self.driver.getPlaylists(window, function(playlists) {
        playlists.forEach(function(playlist) {
            var mostoPlaylist = self.getModelPlaylistFromApiPlaylist(playlist);
            self.playlists.get("playlists").add(mostoPlaylist, {merge: true});
        });
        self.playlists.get("melted_medias").sync();
    });
};

mosto.prototype.getModelPlaylistFromApiPlaylist = function(playlist) {
    //TODO: Remove this function and change driver to send Caspa Models
    var playlistJson   = {};
    playlistJson.name  = playlist.name;
    playlistJson.start = moment(playlist.startDate);
    playlistJson.end   = moment(playlist.endDate);
    playlistJson.id    = playlist.id;

    var start = playlistJson.start;
    var medias = new Mosto.MediaCollection();
    playlist.medias.forEach(function(media) {
        var mediaJson = {};
        mediaJson.playlist_order = media.orig_order;
        mediaJson.name           = media.name;
        mediaJson.type           = media.type;
        mediaJson.file           = media.file;
        //TODO: ASSUMING LENGTH COMES IN SOME KIND OF DATE FORMAT THAT CAN BE USED BY MOMENT
        mediaJson.length         = (moment(media.length) / 1000) * media.fps;
        mediaJson.fps            = media.fps;
        mediaJson.start          = start;
        mediaJson.end            = start + moment(media.length);
        mediaJson.id             = media.id;

        var mostoMedia = new Mosto.Media(mediaJson);
        medias.add(mostoMedia);

        start = start + moment(media.length);
    });

    playlistJson.medias = medias;

    return new Mosto.Playlist(playlistJson);
};

mosto.prototype.init = function( melted, callback) {
    console.log("mbc-mosto: [INFO] Init mbc-mosto... ") ;
    var self = this;
    /*
     * inicializar los drivers
     * inicializar los modelos backbone
     * inicializar los heartbeats
     * linkear los eventos entre pl driver y backbone driver
     * linkear heartbeat con status driver
     */
    function startall() {
        self.server        = new mvcp_server(self.config.mvcp_server);
        self.pl_driver     = new playlists_driver(self.config.playlist_server);
        self.status_driver = new status_driver();
        self.playlists = models.Playlists;

        self.pl_driver.on('create');
        self.pl_driver.on('update');
        self.pl_driver.on('delete');

        self.initDriver();
        self.heartbeats.init();

        self.startMvcpServer( function() {
            self.player.play( self.player );
            if (callback) callback();
        } );
    }

    function check_and_start() {
        Melted.is_running(function(running) {
            if (!running) {
                Melted.start(function(pid) {
                    Melted.setup( undefined, undefined, function(result) {
                        startall();
                    });
                });
            } else {
                startall();
            }
        });
    };

    if (melted!==undefined) {
        Melted = melted;
        check_and_start();
    }
    else
        Melted.take( check_and_start );

}

mosto.prototype.finish = function(callback) {
    console.log("mbc-mosto: [INFO] Finish mbc-mosto... ") ;
    self.stopDriver();
    self.player.stop();
    Melted.stop(function(pid) {
        setTimeout( function() {
            Melted.leave();
            if (callback) callback();
        }, 1000 );
    });
}

}

exports = module.exports = function(customConfig) {
    var mosto_server = new mosto(customConfig);
    return mosto_server;
};
/*

  mosto tiene que:

  * inicializar melted
  * inicializar los drivers
  * inicializar los modelos backbone
  * inicializar los heartbeats
  * linkear los eventos entre pl driver y backbone driver
  * linkear heartbeat con status driver

  */
<commit_msg>Added listener for forceCheckout signal from heartbeats<commit_after>var fs               = require('fs')
,   util             = require('util')
,   events           = require('events')
,   moment           = require('moment')
,   Playlist         = require('./api/Playlist')
,   Melted           = require('./api/Melted')
,   Media            = require('./api/Media')
,   ScheduledMedia   = require('./api/ScheduledMedia')
,   mvcp_server      = require('./drivers/mvcp/mvcp-driver')
,   playlists_driver = require('./drivers/playlists/playlists-driver')
,   status_driver    = require('./drivers/status/pubsub')
,   utils            = require('./utils')
,   config           = require('mbc-common').config.Mosto.General
,   _                = require('underscore')
,   heartbeats        = require('./heartbeats')
,   models           = require('./models/Mosto')
;

function mosto(customConfig) {

    /** CONFIGURATION */
    this.config = customConfig || config;
    this.server = undefined;
    this.server_started = false;
    this.pl_driver = undefined;
    this.status_driver = undefined;

    /* MODULES */
    this.heartbeats = undefined;

    events.EventEmitter.call(this);
}

util.inherits(mosto, events.EventEmitter);

mosto.prototype.initDriver = function() {

    console.log("mbc-mosto: [INFO] Initializing playlists driver");

    self.driver.on ("create", function(playlist) {

        self.fetcher.addPlaylist( playlist, self.fetcher );
    } );
    self.driver.on ("update", function(playlist) {
        self.fetcher.updatePlaylist( playlist, self.fetcher);
    } );
    self.driver.on ("delete", function(playlist) {
        self.fetcher.removePlaylist( playlist, self.fetcher);
    } );

    self.driver.start();
};

mosto.prototype.stopDriver = function() {

    console.log("mbc-mosto: [INFO] Stopping playlists driver");

    self.driver.stop();

    self.driver.removeAllListeners("create");
    self.driver.removeAllListeners("update");
    self.driver.removeAllListeners("delete");
};

mosto.prototype.startMvcpServer = function(callback) {
    var result = self.server.initServer();
    result.then(function() {
        console.log("mbc-mosto: [INFO] MVCP server started");
        self.server_started = true;
        if (callback !== undefined) {
            callback();
        }
    }, function(err) {
        var e = new Error("mbc-mosto: [ERROR] Error starting MVCP server: " + err + ".\nRetrying in 2 seconds...");
        console.error(e);
        setTimeout(function() {
            self.startMvcpServer(callback);
        }, 2000);
    });
};

console.log("mbc-mosto: [INFO] Starting mbc-mosto... ") ;
mosto.prototype.initHeartbeats = function() {
    var self = this;

    console.log("mbc-mosto: [INFO] Initializing heartbeats");

    self.heartbeats.on('frameStatus', function(status) {
        self.status_driver.setStatusClip(StatusClip(
            status.media.id,
            status.media.get('playlist_order'),
            status.media.playlist.id,
            status.media.get('fps'),
            status.frame,
            status.media.get('length')
        ));
    });
    self.heartbeats.on('clipStatus', function(media) {
        var melted_medias = self.playlists.get('melted_medias');
        var playlists = self.playlists.get('playlists');
        var status = {
            clip:{
                previous: undefined,
                current: undefined,
                next: undefined
            },
            show: {
                previous: undefined,
                current: undefined,
                next: undefined
            },
            position: 0,
            clips: melted_medias.toJSON(),
            status: none
        };
        /* clips */
        var index = melted_medias.indexOf(media);
        status.clip.current = media.toJSON();
        if( index > 0 )
            status.clip.previous = melted_medias.at(index-1).toJSON();
        if( index < melted_medias.length )
            status.clip.next = melted_medias.at(index+1).toJSON();
        /* shows */
        var playlist = playlists.find(function(pl) {
            return pl.indexOf(media) >= 0;
        });
        index = playlists.indexOf(playlist);
        status.show.current = playlist.toJSON();
        if( index > 0 )
            status.show.previous = playlists.at(index-1).toJSON();
        if( index < playlists.length )
            status.show.next = playlists.at(index+1).toJSON();
        self.status_driver.setStatus(status);
    });

    self.heartbeats.on("forceCheckout", function(window) {
        self.fetchPlaylists(window);
    });
    self.heartbeats.init();
};
mosto.prototype.fetchPlaylists = function(window) {
    self.driver.getPlaylists(window, function(playlists) {
        playlists.forEach(function(playlist) {
            var mostoPlaylist = self.getModelPlaylistFromApiPlaylist(playlist);
            self.playlists.get("playlists").add(mostoPlaylist, {merge: true});
        });
        self.playlists.get("melted_medias").sync();
    });
};

mosto.prototype.getModelPlaylistFromApiPlaylist = function(playlist) {
    //TODO: Remove this function and change driver to send Caspa Models
    var playlistJson   = {};
    playlistJson.name  = playlist.name;
    playlistJson.start = moment(playlist.startDate);
    playlistJson.end   = moment(playlist.endDate);
    playlistJson.id    = playlist.id;

    var start = playlistJson.start;
    var medias = new Mosto.MediaCollection();
    playlist.medias.forEach(function(media) {
        var mediaJson = {};
        mediaJson.playlist_order = media.orig_order;
        mediaJson.name           = media.name;
        mediaJson.type           = media.type;
        mediaJson.file           = media.file;
        //TODO: ASSUMING LENGTH COMES IN SOME KIND OF DATE FORMAT THAT CAN BE USED BY MOMENT
        mediaJson.length         = (moment(media.length) / 1000) * media.fps;
        mediaJson.fps            = media.fps;
        mediaJson.start          = start;
        mediaJson.end            = start + moment(media.length);
        mediaJson.id             = media.id;

        var mostoMedia = new Mosto.Media(mediaJson);
        medias.add(mostoMedia);

        start = start + moment(media.length);
    });

    playlistJson.medias = medias;

    return new Mosto.Playlist(playlistJson);
};

mosto.prototype.init = function( melted, callback) {
    console.log("mbc-mosto: [INFO] Init mbc-mosto... ") ;
    var self = this;
    /*
     * inicializar los drivers
     * inicializar los modelos backbone
     * inicializar los heartbeats
     * linkear los eventos entre pl driver y backbone driver
     * linkear heartbeat con status driver
     */
    function startall() {
        self.server        = new mvcp_server(self.config.mvcp_server);
        self.pl_driver     = new playlists_driver(self.config.playlist_server);
        self.status_driver = new status_driver();
        self.playlists = models.Playlists;

        self.pl_driver.on('create');
        self.pl_driver.on('update');
        self.pl_driver.on('delete');

        self.initDriver();
        self.heartbeats.init();

        self.startMvcpServer( function() {
            self.player.play( self.player );
            if (callback) callback();
        } );
    }

    function check_and_start() {
        Melted.is_running(function(running) {
            if (!running) {
                Melted.start(function(pid) {
                    Melted.setup( undefined, undefined, function(result) {
                        startall();
                    });
                });
            } else {
                startall();
            }
        });
    };

    if (melted!==undefined) {
        Melted = melted;
        check_and_start();
    }
    else
        Melted.take( check_and_start );

}

mosto.prototype.finish = function(callback) {
    console.log("mbc-mosto: [INFO] Finish mbc-mosto... ") ;
    self.stopDriver();
    self.player.stop();
    Melted.stop(function(pid) {
        setTimeout( function() {
            Melted.leave();
            if (callback) callback();
        }, 1000 );
    });
}

}

exports = module.exports = function(customConfig) {
    var mosto_server = new mosto(customConfig);
    return mosto_server;
};
/*

  mosto tiene que:

  * inicializar melted
  * inicializar los drivers
  * inicializar los modelos backbone
  * inicializar los heartbeats
  * linkear los eventos entre pl driver y backbone driver
  * linkear heartbeat con status driver

  */
<|endoftext|>


<commit_before>var
    esPhinx,
    Iterable;

(function($) {
    "use strict";

    $.prototype.extend({

        autocomplete: function(elementsList, options, callback) {
            var
                fillOften,
                searchTextBox = this,
                nonCharacterKeysPattern = new RegExp($
                    .nonCharacterKeysPattern()),
                inline = false,
                elements2Recovery = [],
                afterAmountCharacters = options.afterAmountCharacters,

                fillInToRecovery = function(object, times) {
                    if (typeof times != "number") {times  = 1;}

                    var
                        i = 0;

                    while (i++ < times) {
                        elements2Recovery.push(object);
                    }
                },

                resolveArguments = function(options) {
                    if (Object.getPrototypeOf(options) !=
                        Object.getPrototypeOf({})) {
                        options = {};
                    }

                    if (typeof options.caseSensitive != "boolean") {
                        options.caseSensitive = false;
                    }

                    if (typeof options.orderlySearch != "boolean") {
                        options.orderlySearch = false;
                    }

                    if (typeof afterAmountCharacters != "number") {
                        afterAmountCharacters = 1;
                    }

                },

                inlineSearch = function(element, options) {
                    var
                        eventListenerMethod,
                        previousSearchText = searchTextBox.value();

                    if (typeof eventListenerMethod == "function") {
                        searchTextBox.off("input", eventListenerMethod);
                        searchTextBox.off("focus", eventListenerMethod);
                    }

                    eventListenerMethod = function(e) {
                        var
                            resolvedList,
                            foundElements,
                            startingIndex,
                            finalIndex,
                            foundElement,
                            previousElement = element,
                            parent = element.clone(),
                            searchTextLength = searchTextBox.value().length,
                            charactersDifference = previousSearchText.length -
                                    searchTextLength,

                            replaceLast = function(object) {
                                elements2Recovery.pop();
                                fillInToRecovery(object);
                            };

                        if (searchTextBox.value()) {

                            startingIndex = searchTextBox.cursorPosition() - 2;
                            finalIndex = startingIndex + charactersDifference -
                                1;

                            // when delete
                            if (searchTextLength < previousSearchText.length &&
                                searchTextLength >= afterAmountCharacters) {

                                if (startingIndex === 0) {
                                    elements2Recovery.delete(startingIndex,
                                        finalIndex);
                                } else {
                                    elements2Recovery.delete(startingIndex,
                                        elements2Recovery.lastIndex());
                                }
                                // debugger

                                previousElement = elements2Recovery[
                                        elements2Recovery.lastIndex()];
                            }

                            resolvedList = resolveList(previousElement,
                                options.order);
                            foundElements = resolvedList.elements;

                            debugger
                            if (searchTextLength > afterAmountCharacters ||
                                afterAmountCharacters == 1) {

                                if (foundElements.some()) {
                                    foundElement = parent
                                                         .append(foundElements);
                                    // there was a difference for more
                                    if (charactersDifference < 0) {
                                        fillOften = Math
                                            .abs(charactersDifference);
                                        fillInToRecovery(foundElement,
                                                       fillOften);
                                    } else {
                                        replaceLast(foundElement);
                                    }

                                    previousElement = foundElement;
                                } else {
                                    // there was a difference for less
                                    if (charactersDifference > 0) {
                                        // errado se deletar dois ou mais
                                        fillOften = searchTextLength -
                                            afterAmountCharacters +
                                            charactersDifference -
                                            elements2Recovery.length;
                                        debugger
                                    } else {
                                        fillOften = Math
                                            .abs(charactersDifference);
                                    }

                                    fillInToRecovery(previousElement,
                                                     fillOften);
                                }
                            }

                            // if (foundElements.some()) {

                            //     fillOften = searchTextLength -
                            //         elements2Recovery.length -
                            //         afterAmountCharacters + 1;

                            //     debugger
                            //     if ((charactersDifference < 0 &&
                            //         searchTextLength >
                            //         afterAmountCharacters) ||
                            //         afterAmountCharacters == 1) {

                            //         fillInToRecovery(parent
                            //                          .append(foundElements),
                            //                        fillOften);
                            //     }

                            //     previousElement = parent.append(foundElements);
                            // } else {
                            //     if (searchTextLength >
                            //         afterAmountCharacters ||
                            //         afterAmountCharacters == 1) {

                            //         fillInToRecovery(previousElements,
                            //                        fillOften);
                            //     }
                            // }

                            previousSearchText = searchTextBox.value();
                            callback.call($(this), foundElements, resolvedList
                                          .maps, e);
                        } else {
                            // reset
                            elements2Recovery = [];
                            previousSearchText = "";
                            previousElement = element;
                            callback.call($(this), $(), e);
                        }

                    };

                    searchTextBox.on("input", eventListenerMethod);
                    searchTextBox.on("focus", eventListenerMethod);
                },

                remoteSearch = function(options) {
                    var
                        word,
                        prevText,
                        nextText,
                        element,
                        URLParameters,
                        resolvedList,
                        clipboardData,

                        request = function(e) {
                            if (options.remote.URLParameters) {
                                URLParameters = options.remote.URLParameters
                                    .call(searchTextBox);
                            }

                            $.Ajax.new(options.remote.url.call(searchTextBox))
                                .get({
                                    URLParameters: URLParameters,
                                    progress: function(xhr) {
                                        if (options.remote.loading) {
                                            options.remote
                                                .loading(xhr, e);
                                        }
                                    },
                                    complete: function() {
                                        $(e.target).off("keypress");
                                    },
                                    success: function(r) {
                                        element = $(r);

                                        if (element.some()) {
                                            fillOften = (searchTextBox
                                                .value().length -
                                                afterAmountCharacters) + 1;

                                            fillInToRecovery(element,
                                                           fillOften);
                                        }

                                        resolvedList = resolveList(element,
                                                                options.order);
                                        callback.call(searchTextBox,
                                            resolvedList.elements,
                                            resolvedList.maps, e);
                                        if (element.length) {
                                            inlineSearch(element, options);
                                        }
                                    }
                                });
                        };

                    searchTextBox.on("keyup", function(e) {
                        word = searchTextBox.value();

                        if (word.length > afterAmountCharacters) {
                            inline = true;
                        } else if (word.length < afterAmountCharacters) {
                            inline = false;
                        }

                        if (word.length == afterAmountCharacters &&
                            !nonCharacterKeysPattern.test(e.key) &&
                            !inline &&
                            options.remote) {
                            $(e.target).on("keypress", function(e) {
                                e.preventDefault();
                            });

                            request(e);
                        }

                    });

                    searchTextBox.on("paste", function(e) {
                        word = searchTextBox.value();
                        prevText = word
                            .slice(0, searchTextBox.cursorPosition());
                        nextText = word
                            .slice(searchTextBox.selectionEnd(), word.length);

                        clipboardData = e.clipboardData.getData("text");

                        if (word.length >= afterAmountCharacters) {
                            inline = true;
                        } else {
                            inline = false;
                        }

                        word = prevText + clipboardData + nextText;

                        if (word.length >= afterAmountCharacters &&
                            !inline &&
                            options.remote) {
                            searchTextBox.value(word);

                            request(e);
                            e.preventDefault();
                        }
                    });

                },

                mapIf = function(searchTextAsArray, textNodeAsArray) {
                    var
                        iterator,
                        sliceIndex,
                        regexp,
                        textNodeHashSet = Object
                            .asCountableLiteralObject(textNodeAsArray),
                        mapped = {},
                        count = 0,
                        index = 0,
                        previousIndex = -1;

                    if (textNodeHashSet.length >= searchTextAsArray.length) {
                        iterator = Iterable.Proxy.new(searchTextAsArray);
                        iterator.each(function(slice) {
                            slice = slice.trim();
                            index = Object.firstFromASlice(textNodeHashSet,
                                                             slice, index,
                                                             options
                                                             .caseSensitive);

                            if (index && ((options.orderlySearch &&
                                index > previousIndex) ||
                                !options.orderlySearch) &&
                                textNodeAsArray.countSlice(slice,
                                    options.caseSensitive) >=
                                    searchTextAsArray.count(slice)) {

                                previousIndex = index;

                                if (options.caseSensitive) {
                                    regexp = new RegExp(slice);
                                } else {
                                    regexp = new RegExp(slice, "i");
                                }

                                sliceIndex = textNodeAsArray[index]
                                    .search(regexp);
                                mapped[index] = textNodeAsArray[index]
                                    .slice(sliceIndex, sliceIndex + slice
                                           .length);
                                Object.delete(textNodeHashSet, index);
                                count += 1;
                            } else {
                                this.finalize();
                            }
                        });

                        if (count == searchTextAsArray.length) {
                            return mapped;
                        }
                    }

                    return false;
                },

                // resolveList = function(list, order = "desc") {
                resolveList = function(list, order) {
                    if (order != "asc") {order = "desc";}
                    var
                        textNode,
                        mapped,
                        textNodeAsArray,
                        sorted,
                        found = {elements: $(), maps: []},
                        textBoxContentArr = searchTextBox.value().split(" ")
                            .compact();

                    if (list.elements().count() == 1 &&
                        list.isA(window.Element)) {
                        list = list.childElements().clone(true);
                    }

                    if (order == "asc") {
                        sorted = list.ascendingSortTextNode();
                    } else {
                        sorted = list.descendingSortTextNode();
                    }

                    sorted.each(function(element) {
                        textNode = $(element).textContent().trim();
                        textNodeAsArray = textNode.split(" ");
                        mapped = mapIf(textBoxContentArr, textNodeAsArray);
                        if (mapped) {
                            found.elements.push(element);
                            found.maps.push(mapped);
                        }
                    });

                    return found;
                };

            resolveArguments(options);

            // init
            if (Object.getPrototypeOf(elementsList) != Object.prototype) {
                inlineSearch(elementsList, options);
            } else {
                options = elementsList;
                remoteSearch(options);
            }
        }

    });

})(esPhinx);
<commit_msg>autocomplete<commit_after>var
    esPhinx,
    Iterable;

(function($) {
    "use strict";

    $.prototype.extend({

        autocomplete: function(elementsList, options, callback) {
            var
                fillOften,
                searchTextBox = this,
                nonCharacterKeysPattern = new RegExp($
                    .nonCharacterKeysPattern()),
                inline = false,
                elements2Recovery = [],
                afterAmountCharacters = options.afterAmountCharacters,

                fillInToRecovery = function(object, times) {
                    if (typeof times != "number") {times  = 1;}

                    var
                        i = 0;

                    while (i++ < times) {
                        elements2Recovery.push(object);
                    }
                },

                resolveArguments = function(options) {
                    if (Object.getPrototypeOf(options) !=
                        Object.getPrototypeOf({})) {
                        options = {};
                    }

                    if (typeof options.caseSensitive != "boolean") {
                        options.caseSensitive = false;
                    }

                    if (typeof options.orderlySearch != "boolean") {
                        options.orderlySearch = false;
                    }

                    if (typeof afterAmountCharacters != "number") {
                        afterAmountCharacters = 1;
                    }

                },

                inlineSearch = function(element, options) {
                    var
                        eventListenerMethod,
                        previousSearchText = searchTextBox.value();

                    if (typeof eventListenerMethod == "function") {
                        searchTextBox.off("input", eventListenerMethod);
                        searchTextBox.off("focus", eventListenerMethod);
                    }

                    eventListenerMethod = function(e) {
                        var
                            resolvedList,
                            foundElements,
                            startingIndex,
                            finalIndex,
                            foundElement,
                            previousElement = element,
                            parent = element.clone(),
                            searchTextLength = searchTextBox.value().length,
                            charactersDifference = previousSearchText.length -
                                    searchTextLength,

                            replaceLast = function(object) {
                                elements2Recovery.pop();
                                fillInToRecovery(object);
                            };

                        if (searchTextBox.value()) {

                            startingIndex = searchTextBox.cursorPosition() - 2;
                            finalIndex = startingIndex + charactersDifference -
                                1;

                            // when delete
                            if (searchTextLength < previousSearchText.length &&
                                searchTextLength >= afterAmountCharacters) {

                                if (startingIndex === 0) {
                                    elements2Recovery.delete(startingIndex,
                                        finalIndex);
                                } else {
                                    elements2Recovery.delete(startingIndex,
                                        elements2Recovery.lastIndex());
                                }
                                // debugger

                                previousElement = elements2Recovery[
                                        elements2Recovery.lastIndex()];
                            }

                            resolvedList = resolveList(previousElement,
                                options.order);
                            foundElements = resolvedList.elements;

                            debugger
                            if (searchTextLength > afterAmountCharacters ||
                                afterAmountCharacters == 1) {

                                if (foundElements.some()) {
                                    foundElement = parent
                                                         .append(foundElements);
                                    // there was a difference for more
                                    if (charactersDifference < 0) {
                                        fillOften = Math
                                            .abs(charactersDifference);
                                        fillInToRecovery(foundElement,
                                                       fillOften);
                                    } else {
                                        replaceLast(foundElement);
                                    }

                                    previousElement = foundElement;
                                } else {
                                    // there was a difference for less
                                    if (charactersDifference > 0) {
                                        // errado se deletar dois ou mais
                                        fillOften = searchTextLength -
                                            afterAmountCharacters +
                                            charactersDifference -
                                            elements2Recovery.length;
                                        debugger
                                    } else {
                                        fillOften = Math
                                            .abs(charactersDifference);
                                    }

                                    fillInToRecovery(previousElement,
                                                     fillOften);
                                }
                            }

                            previousSearchText = searchTextBox.value();
                            callback.call($(this), foundElements, resolvedList
                                          .maps, e);
                        } else {
                            // reset
                            elements2Recovery = [];
                            previousSearchText = "";
                            previousElement = element;
                            callback.call($(this), $(), e);
                        }

                    };

                    searchTextBox.on("input", eventListenerMethod);
                    searchTextBox.on("focus", eventListenerMethod);
                },

                remoteSearch = function(options) {
                    var
                        word,
                        prevText,
                        nextText,
                        element,
                        URLParameters,
                        resolvedList,
                        clipboardData,

                        request = function(e) {
                            if (options.remote.URLParameters) {
                                URLParameters = options.remote.URLParameters
                                    .call(searchTextBox);
                            }

                            $.Ajax.new(options.remote.url.call(searchTextBox))
                                .get({
                                    URLParameters: URLParameters,
                                    progress: function(xhr) {
                                        if (options.remote.loading) {
                                            options.remote
                                                .loading(xhr, e);
                                        }
                                    },
                                    complete: function() {
                                        $(e.target).off("keypress");
                                    },
                                    success: function(r) {
                                        element = $(r);

                                        if (element.some()) {
                                            fillOften = (searchTextBox
                                                .value().length -
                                                afterAmountCharacters) + 1;

                                            fillInToRecovery(element,
                                                           fillOften);
                                        }

                                        resolvedList = resolveList(element,
                                                                options.order);
                                        callback.call(searchTextBox,
                                            resolvedList.elements,
                                            resolvedList.maps, e);
                                        if (element.length) {
                                            inlineSearch(element, options);
                                        }
                                    }
                                });
                        };

                    searchTextBox.on("keyup", function(e) {
                        word = searchTextBox.value();

                        if (word.length > afterAmountCharacters) {
                            inline = true;
                        } else if (word.length < afterAmountCharacters) {
                            inline = false;
                        }

                        if (word.length == afterAmountCharacters &&
                            !nonCharacterKeysPattern.test(e.key) &&
                            !inline &&
                            options.remote) {
                            $(e.target).on("keypress", function(e) {
                                e.preventDefault();
                            });

                            request(e);
                        }

                    });

                    searchTextBox.on("paste", function(e) {
                        word = searchTextBox.value();
                        prevText = word
                            .slice(0, searchTextBox.cursorPosition());
                        nextText = word
                            .slice(searchTextBox.selectionEnd(), word.length);

                        clipboardData = e.clipboardData.getData("text");

                        if (word.length >= afterAmountCharacters) {
                            inline = true;
                        } else {
                            inline = false;
                        }

                        word = prevText + clipboardData + nextText;

                        if (word.length >= afterAmountCharacters &&
                            !inline &&
                            options.remote) {
                            searchTextBox.value(word);

                            request(e);
                            e.preventDefault();
                        }
                    });

                },

                mapIf = function(searchTextAsArray, textNodeAsArray) {
                    var
                        iterator,
                        sliceIndex,
                        regexp,
                        textNodeHashSet = Object
                            .asCountableLiteralObject(textNodeAsArray),
                        mapped = {},
                        count = 0,
                        index = 0,
                        previousIndex = -1;

                    if (textNodeHashSet.length >= searchTextAsArray.length) {
                        iterator = Iterable.Proxy.new(searchTextAsArray);
                        iterator.each(function(slice) {
                            slice = slice.trim();
                            index = Object.firstFromASlice(textNodeHashSet,
                                                             slice, index,
                                                             options
                                                             .caseSensitive);

                            if (index && ((options.orderlySearch &&
                                index > previousIndex) ||
                                !options.orderlySearch) &&
                                textNodeAsArray.countSlice(slice,
                                    options.caseSensitive) >=
                                    searchTextAsArray.count(slice)) {

                                previousIndex = index;

                                if (options.caseSensitive) {
                                    regexp = new RegExp(slice);
                                } else {
                                    regexp = new RegExp(slice, "i");
                                }

                                sliceIndex = textNodeAsArray[index]
                                    .search(regexp);
                                mapped[index] = textNodeAsArray[index]
                                    .slice(sliceIndex, sliceIndex + slice
                                           .length);
                                Object.delete(textNodeHashSet, index);
                                count += 1;
                            } else {
                                this.finalize();
                            }
                        });

                        if (count == searchTextAsArray.length) {
                            return mapped;
                        }
                    }

                    return false;
                },

                // resolveList = function(list, order = "desc") {
                resolveList = function(list, order) {
                    if (order != "asc") {order = "desc";}
                    var
                        textNode,
                        mapped,
                        textNodeAsArray,
                        sorted,
                        found = {elements: $(), maps: []},
                        textBoxContentArr = searchTextBox.value().split(" ")
                            .compact();

                    if (list.elements().count() == 1 &&
                        list.isA(window.Element)) {
                        list = list.childElements().clone(true);
                    }

                    if (order == "asc") {
                        sorted = list.ascendingSortTextNode();
                    } else {
                        sorted = list.descendingSortTextNode();
                    }

                    sorted.each(function(element) {
                        textNode = $(element).textContent().trim();
                        textNodeAsArray = textNode.split(" ");
                        mapped = mapIf(textBoxContentArr, textNodeAsArray);
                        if (mapped) {
                            found.elements.push(element);
                            found.maps.push(mapped);
                        }
                    });

                    return found;
                };

            resolveArguments(options);

            // init
            if (Object.getPrototypeOf(elementsList) != Object.prototype) {
                inlineSearch(elementsList, options);
            } else {
                options = elementsList;
                remoteSearch(options);
            }
        }

    });

})(esPhinx);
<|endoftext|>


<commit_before>define(
  'ephox.boss.api.DomUniverse',

  [
    'ephox.compass.Arr',
    'ephox.peanut.Fun',
    'ephox.sugar.api.Compare',
    'ephox.sugar.api.Css',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'ephox.sugar.api.InsertAll',
    'ephox.sugar.api.Node',
    'ephox.sugar.api.PredicateFilter',
    'ephox.sugar.api.PredicateFind',
    'ephox.sugar.api.Remove',
    'ephox.sugar.api.SelectorFilter',
    'ephox.sugar.api.SelectorFind',
    'ephox.sugar.api.Text',
    'ephox.sugar.api.Traverse'
  ],

  function (Arr, Fun, Compare, Css, Element, Insert, InsertAll, Node, PredicateFilter, PredicateFind, Remove, SelectorFilter, SelectorFind, Text, Traverse) {
    return function () {
      var clone = function (element) {
        return Element.fromDom(element.dom().cloneNode(false));
      };

      var isBoundary = function (element) {
        if (!Node.isElement(element)) return false;
        if (Node.name(element) === 'body') return true;
        var display = Css.get(element, 'display');
        return Arr.contains(['block', 'table-cell', 'table-row', 'table', 'list-item'], display);
      };

      var isEmptyTag = function (element) {
        if (!Node.isElement(element)) return false;
        return Arr.contains(['br', 'img'], Node.name(element));
      };

      var comparePosition = function (element, other) {
        return element.dom().compareDocumentPosition(other.dom());
      };

      return {
        up: Fun.constant({
          selector: SelectorFind.ancestor,
          predicate: PredicateFind.ancestor,
          all: Traverse.parents
        }),
        down: Fun.constant({
          selector: SelectorFilter.descendants,
          predicate: PredicateFilter.descendants
        }),
        styles: Fun.constant({
          get: Css.get,
          set: Css.set,
          remove: Css.remove
        }),
        insert: Fun.constant({
          before: Insert.before,
          after: Insert.after,
          afterAll: InsertAll.after,
          append: Insert.append,
          appendAll: InsertAll.append,
          prepend: Insert.prepend,
          wrap: Insert.wrap
        }),
        remove: Fun.constant({
          unwrap: Remove.unwrap,
          detach: Remove.detach,
          remove: Remove.remove
        }),
        create: Fun.constant({
          nu: Element.fromTag,
          clone: clone,
          text: Element.fromText
        }),
        query: Fun.constant({
          comparePosition: comparePosition,
          prevSibling: Traverse.prevSibling,
          nextSibling: Traverse.nextSibling,
        }),
        property: Fun.constant({
          children: Traverse.children,
          name: Node.name,
          parent: Traverse.parent,
          isText: Node.isText,
          isElement: Node.isElement,
          getText: Text.get,
          setText: Text.set,
          isBoundary: isBoundary,
          isEmptyTag: isEmptyTag
        }),
        eq: Compare.eq,
        is: Compare.is
      };
    };
  }
);
<commit_msg>TBIO-119: incorrect trailing comma<commit_after>define(
  'ephox.boss.api.DomUniverse',

  [
    'ephox.compass.Arr',
    'ephox.peanut.Fun',
    'ephox.sugar.api.Compare',
    'ephox.sugar.api.Css',
    'ephox.sugar.api.Element',
    'ephox.sugar.api.Insert',
    'ephox.sugar.api.InsertAll',
    'ephox.sugar.api.Node',
    'ephox.sugar.api.PredicateFilter',
    'ephox.sugar.api.PredicateFind',
    'ephox.sugar.api.Remove',
    'ephox.sugar.api.SelectorFilter',
    'ephox.sugar.api.SelectorFind',
    'ephox.sugar.api.Text',
    'ephox.sugar.api.Traverse'
  ],

  function (Arr, Fun, Compare, Css, Element, Insert, InsertAll, Node, PredicateFilter, PredicateFind, Remove, SelectorFilter, SelectorFind, Text, Traverse) {
    return function () {
      var clone = function (element) {
        return Element.fromDom(element.dom().cloneNode(false));
      };

      var isBoundary = function (element) {
        if (!Node.isElement(element)) return false;
        if (Node.name(element) === 'body') return true;
        var display = Css.get(element, 'display');
        return Arr.contains(['block', 'table-cell', 'table-row', 'table', 'list-item'], display);
      };

      var isEmptyTag = function (element) {
        if (!Node.isElement(element)) return false;
        return Arr.contains(['br', 'img'], Node.name(element));
      };

      var comparePosition = function (element, other) {
        return element.dom().compareDocumentPosition(other.dom());
      };

      return {
        up: Fun.constant({
          selector: SelectorFind.ancestor,
          predicate: PredicateFind.ancestor,
          all: Traverse.parents
        }),
        down: Fun.constant({
          selector: SelectorFilter.descendants,
          predicate: PredicateFilter.descendants
        }),
        styles: Fun.constant({
          get: Css.get,
          set: Css.set,
          remove: Css.remove
        }),
        insert: Fun.constant({
          before: Insert.before,
          after: Insert.after,
          afterAll: InsertAll.after,
          append: Insert.append,
          appendAll: InsertAll.append,
          prepend: Insert.prepend,
          wrap: Insert.wrap
        }),
        remove: Fun.constant({
          unwrap: Remove.unwrap,
          detach: Remove.detach,
          remove: Remove.remove
        }),
        create: Fun.constant({
          nu: Element.fromTag,
          clone: clone,
          text: Element.fromText
        }),
        query: Fun.constant({
          comparePosition: comparePosition,
          prevSibling: Traverse.prevSibling,
          nextSibling: Traverse.nextSibling
        }),
        property: Fun.constant({
          children: Traverse.children,
          name: Node.name,
          parent: Traverse.parent,
          isText: Node.isText,
          isElement: Node.isElement,
          getText: Text.get,
          setText: Text.set,
          isBoundary: isBoundary,
          isEmptyTag: isEmptyTag
        }),
        eq: Compare.eq,
        is: Compare.is
      };
    };
  }
);
<|endoftext|>


<commit_before>import {
  getCaseInsensitveFile,
  readUint8array,
  asyncTreeFlatMap,
} from "./utils";
import MakiObject from "./runtime/MakiObject";
import WinampAbstractionLayer from "./runtime/WinampAbstractionLayer";
import Layout from "./runtime/Layout";
import Layer from "./runtime/Layer";
import Container from "./runtime/Container";
import JsElements from "./runtime/JsElements";
import JsGammaSet from "./runtime/JsGammaSet";
import JsGroupDef from "./runtime/JsGroupDef";
import Group from "./runtime/Group";
import Button from "./runtime/Button";
import ToggleButton from "./runtime/ToggleButton";
import Text from "./runtime/Text";
import Status from "./runtime/Status";

async function loadImage(imgUrl) {
  return await new Promise(resolve => {
    const img = new Image();
    img.addEventListener("load", () => {
      resolve(img);
    });
    img.src = imgUrl;
  });
}

const schema = {
  groupdef: [
    "scripts",
    "layer",
    "layoutstatus",
    "hideobject",
    "button",
    "wasabi:titlebar",
    "group",
    "script",
    "sendparams",
    "eqvis",
    "slider",
    "component",
    "colorthemes:list",
    "wasabi:button",
    "text",
    "vis",
    "grid",
    "rect",
    "animatedlayer",
    "nstatesbutton",
    "togglebutton",
    "songticker",
    "menu",
    "status",
    "albumart",
    "playlistplus",
  ],
  group: [
    "button",
    "layer",
    "text",
    "vis",
    "group",
    "scripts",
    "layoutstatus",
    "hideobject",
    "wasabi:titlebar",
    "menu",
    "nstatesbutton",
    "status",
    "script",
    "songticker",
    "grid",
    "animatedlayer",
    "togglebutton",
    "slider",
    "rect",
    "eqvis",
    "playlistplus",
  ],
  layout: [
    "wasabi:standardframe:status",
    "text",
    "wasabi:standardframe:nostatus",
    "layer",
    "button",
    "togglebutton",
    "status",
    "slider",
    "group",
    "sendparams",
    "script",
    "grid",
    "vis",
    "rect",
    "component",
  ],
  container: ["groupdef", "layout", "scripts"],
  scripts: ["script"],
  elements: [
    "color",
    "bitmap",
    "bitmapfont",
    "truetypefont",
    "cursor",
    "elementalias",
  ],
  skininfo: [
    "version",
    "name",
    "author",
    "comment",
    "email",
    "homepage",
    "screenshot",
  ],
  wasabixml: [
    "skininfo",
    "scripts",
    "elements",
    "groupdef",
    "container",
    "gammaset",
  ],
  // same as above, wa3 vs wa5
  winampabstractionlayer: [
    "skininfo",
    "scripts",
    "elements",
    "groupdef",
    "container",
    "gammaset",
    "accelerators",
  ],
  gammaset: ["gammagroup"],
  accelerators: ["accelerator"],
};

const noop = (node, parent) => new MakiObject(node, parent);

const parsers = {
  groupdef: (node, parent) => new JsGroupDef(node, parent),
  skininfo: noop,
  version: noop,
  name: noop,
  comment: noop,
  author: noop,
  email: noop,
  homepage: noop,
  screenshot: noop,
  container: (node, parent) => new Container(node, parent),
  scripts: noop,
  gammaset: (node, parent) => new JsGammaSet(node, parent),
  color: noop,
  layer: (node, parent) => new Layer(node, parent),
  layoutstatus: noop,
  hideobject: noop,
  button: (node, parent) => new Button(node, parent),
  group: (node, parent) => new Group(node, parent),
  layout: (node, parent) => new Layout(node, parent),
  sendparams: noop,
  elements: (node, parent) => new JsElements(node, parent),
  bitmap: async (node, parent, zip) => {
    let { file, gammagroup, h, id, w, x, y } = node.attributes;
    // TODO: Escape file for regex
    const img = getCaseInsensitveFile(zip, file);
    if (img === undefined) {
      return new MakiObject(node, parent);
    }
    const imgBlob = await img.async("blob");
    const imgUrl = URL.createObjectURL(imgBlob);
    if (w === undefined || h === undefined) {
      const image = await loadImage(imgUrl);
      w = image.width;
      h = image.height;
      x = x !== undefined ? x : 0;
      y = y !== undefined ? y : 0;
    }

    return new MakiObject(node, parent, {
      id,
      file,
      gammagroup,
      h,
      w,
      x,
      y,
      imgUrl,
    });
  },
  eqvis: noop,
  slider: noop,
  gammagroup: noop,
  truetypefont: noop,
  component: noop,
  text: (node, parent) => new Text(node, parent),
  togglebutton: (node, parent) => new ToggleButton(node, parent),
  status: (node, parent) => new Status(node, parent),
  slider: noop,
  bitmapfont: noop,
  vis: noop,
  "wasabi:titlebar": noop,
  "colorthemes:list": noop,
  "wasabi:standardframe:status": noop,
  "wasabi:standardframe:nostatus": noop,
  "wasabi:button": noop,
  accelerators: noop,
  accelerator: noop,
  cursor: noop,
  elementalias: noop,
  grid: noop,
  rect: noop,
  animatedlayer: noop,
  nstatesbutton: noop,
  songticker: noop,
  menu: noop,
  albumart: noop,
  playlistplus: noop,
  async script(node, parent, zip) {
    const script = await readUint8array(zip, node.attributes.file);
    return new MakiObject(node, parent, { script });
  },
};

async function parseChildren(node, children, zip) {
  if (node.type === "comment") {
    return;
  }
  if (node.name == null) {
    console.error(node);
    throw new Error("Unknown node");
  }

  const validChildren = new Set(schema[node.name.toLowerCase()]);
  const resolvedChildren = await Promise.all(
    children.map(async child => {
      if (child.type === "comment") {
        return;
      }
      if (child.type === "text") {
        // TODO: Handle text
        return new MakiObject({ ...child }, node);
      }
      if (child.name == null) {
        console.error(child);
        throw new Error("Unknown node");
      }
      const childName = child.name.toLowerCase();
      if (childName == null) {
        console.error(node);
        throw new Error("Unknown node");
      }

      if (!validChildren.has(childName)) {
        throw new Error(`Invalid child of a ${node.name}: ${childName}`);
      }

      const childParser = parsers[childName];
      if (childParser == null) {
        throw new Error(`Missing parser for ${childName}`);
        return;
      }
      const parsedChild = await childParser(child, node, zip);
      if (child.children != null && child.children.length > 0) {
        await parseChildren(parsedChild, child.children, zip);
      }
      return parsedChild;
    })
  );
  // remove comments other trimmed nodes
  const filteredChildren = resolvedChildren.filter(item => item !== undefined);

  node.js_addChildren(filteredChildren);
}

async function applyGroupDefs(root) {
  await asyncTreeFlatMap(root, async node => {
    switch (node.name) {
      case "group": {
        if (!node.children || node.children.length === 0) {
          const groupdef = node.js_groupdefLookup(node.attributes.id);
          if (!groupdef) {
            console.warn(
              "Unable to find groupdef. Rendering null",
              node.attributes.id
            );
            return {};
          }
          node.children = groupdef.children;
          // Do we need to copy the items instead of just changing the parent?
          node.children.forEach(item => {
            item.parent = node;
          });
          node.attributes = {
            ...node.attributes,
            ...groupdef.attributes,
          };
        }
        return {};
      }
      default: {
        return node;
      }
    }
  });
}

async function initialize(zip, skinXml) {
  const xmlRoot = skinXml.children[0];
  const root = new WinampAbstractionLayer(skinXml.children[0], null);
  await parseChildren(root, xmlRoot.children, zip);
  await applyGroupDefs(root);
  return root;
}

export default initialize;
<commit_msg>Handle img load errors by rejecting the promise<commit_after>import {
  getCaseInsensitveFile,
  readUint8array,
  asyncTreeFlatMap,
} from "./utils";
import MakiObject from "./runtime/MakiObject";
import WinampAbstractionLayer from "./runtime/WinampAbstractionLayer";
import Layout from "./runtime/Layout";
import Layer from "./runtime/Layer";
import Container from "./runtime/Container";
import JsElements from "./runtime/JsElements";
import JsGammaSet from "./runtime/JsGammaSet";
import JsGroupDef from "./runtime/JsGroupDef";
import Group from "./runtime/Group";
import Button from "./runtime/Button";
import ToggleButton from "./runtime/ToggleButton";
import Text from "./runtime/Text";
import Status from "./runtime/Status";

async function loadImage(imgUrl) {
  return await new Promise(resolve => {
    const img = new Image();
    img.addEventListener("load", () => {
      resolve(img);
    });
    img.addEventListener("error", e => {
      reject(e);
    });
    img.src = imgUrl;
  });
}

const schema = {
  groupdef: [
    "scripts",
    "layer",
    "layoutstatus",
    "hideobject",
    "button",
    "wasabi:titlebar",
    "group",
    "script",
    "sendparams",
    "eqvis",
    "slider",
    "component",
    "colorthemes:list",
    "wasabi:button",
    "text",
    "vis",
    "grid",
    "rect",
    "animatedlayer",
    "nstatesbutton",
    "togglebutton",
    "songticker",
    "menu",
    "status",
    "albumart",
    "playlistplus",
  ],
  group: [
    "button",
    "layer",
    "text",
    "vis",
    "group",
    "scripts",
    "layoutstatus",
    "hideobject",
    "wasabi:titlebar",
    "menu",
    "nstatesbutton",
    "status",
    "script",
    "songticker",
    "grid",
    "animatedlayer",
    "togglebutton",
    "slider",
    "rect",
    "eqvis",
    "playlistplus",
  ],
  layout: [
    "wasabi:standardframe:status",
    "text",
    "wasabi:standardframe:nostatus",
    "layer",
    "button",
    "togglebutton",
    "status",
    "slider",
    "group",
    "sendparams",
    "script",
    "grid",
    "vis",
    "rect",
    "component",
  ],
  container: ["groupdef", "layout", "scripts"],
  scripts: ["script"],
  elements: [
    "color",
    "bitmap",
    "bitmapfont",
    "truetypefont",
    "cursor",
    "elementalias",
  ],
  skininfo: [
    "version",
    "name",
    "author",
    "comment",
    "email",
    "homepage",
    "screenshot",
  ],
  wasabixml: [
    "skininfo",
    "scripts",
    "elements",
    "groupdef",
    "container",
    "gammaset",
  ],
  // same as above, wa3 vs wa5
  winampabstractionlayer: [
    "skininfo",
    "scripts",
    "elements",
    "groupdef",
    "container",
    "gammaset",
    "accelerators",
  ],
  gammaset: ["gammagroup"],
  accelerators: ["accelerator"],
};

const noop = (node, parent) => new MakiObject(node, parent);

const parsers = {
  groupdef: (node, parent) => new JsGroupDef(node, parent),
  skininfo: noop,
  version: noop,
  name: noop,
  comment: noop,
  author: noop,
  email: noop,
  homepage: noop,
  screenshot: noop,
  container: (node, parent) => new Container(node, parent),
  scripts: noop,
  gammaset: (node, parent) => new JsGammaSet(node, parent),
  color: noop,
  layer: (node, parent) => new Layer(node, parent),
  layoutstatus: noop,
  hideobject: noop,
  button: (node, parent) => new Button(node, parent),
  group: (node, parent) => new Group(node, parent),
  layout: (node, parent) => new Layout(node, parent),
  sendparams: noop,
  elements: (node, parent) => new JsElements(node, parent),
  bitmap: async (node, parent, zip) => {
    let { file, gammagroup, h, id, w, x, y } = node.attributes;
    // TODO: Escape file for regex
    const img = getCaseInsensitveFile(zip, file);
    if (img === undefined) {
      return new MakiObject(node, parent);
    }
    const imgBlob = await img.async("blob");
    const imgUrl = URL.createObjectURL(imgBlob);
    if (w === undefined || h === undefined) {
      const image = await loadImage(imgUrl);
      w = image.width;
      h = image.height;
      x = x !== undefined ? x : 0;
      y = y !== undefined ? y : 0;
    }

    return new MakiObject(node, parent, {
      id,
      file,
      gammagroup,
      h,
      w,
      x,
      y,
      imgUrl,
    });
  },
  eqvis: noop,
  slider: noop,
  gammagroup: noop,
  truetypefont: noop,
  component: noop,
  text: (node, parent) => new Text(node, parent),
  togglebutton: (node, parent) => new ToggleButton(node, parent),
  status: (node, parent) => new Status(node, parent),
  slider: noop,
  bitmapfont: noop,
  vis: noop,
  "wasabi:titlebar": noop,
  "colorthemes:list": noop,
  "wasabi:standardframe:status": noop,
  "wasabi:standardframe:nostatus": noop,
  "wasabi:button": noop,
  accelerators: noop,
  accelerator: noop,
  cursor: noop,
  elementalias: noop,
  grid: noop,
  rect: noop,
  animatedlayer: noop,
  nstatesbutton: noop,
  songticker: noop,
  menu: noop,
  albumart: noop,
  playlistplus: noop,
  async script(node, parent, zip) {
    const script = await readUint8array(zip, node.attributes.file);
    return new MakiObject(node, parent, { script });
  },
};

async function parseChildren(node, children, zip) {
  if (node.type === "comment") {
    return;
  }
  if (node.name == null) {
    console.error(node);
    throw new Error("Unknown node");
  }

  const validChildren = new Set(schema[node.name.toLowerCase()]);
  const resolvedChildren = await Promise.all(
    children.map(async child => {
      if (child.type === "comment") {
        return;
      }
      if (child.type === "text") {
        // TODO: Handle text
        return new MakiObject({ ...child }, node);
      }
      if (child.name == null) {
        console.error(child);
        throw new Error("Unknown node");
      }
      const childName = child.name.toLowerCase();
      if (childName == null) {
        console.error(node);
        throw new Error("Unknown node");
      }

      if (!validChildren.has(childName)) {
        throw new Error(`Invalid child of a ${node.name}: ${childName}`);
      }

      const childParser = parsers[childName];
      if (childParser == null) {
        throw new Error(`Missing parser for ${childName}`);
        return;
      }
      const parsedChild = await childParser(child, node, zip);
      if (child.children != null && child.children.length > 0) {
        await parseChildren(parsedChild, child.children, zip);
      }
      return parsedChild;
    })
  );
  // remove comments other trimmed nodes
  const filteredChildren = resolvedChildren.filter(item => item !== undefined);

  node.js_addChildren(filteredChildren);
}

async function applyGroupDefs(root) {
  await asyncTreeFlatMap(root, async node => {
    switch (node.name) {
      case "group": {
        if (!node.children || node.children.length === 0) {
          const groupdef = node.js_groupdefLookup(node.attributes.id);
          if (!groupdef) {
            console.warn(
              "Unable to find groupdef. Rendering null",
              node.attributes.id
            );
            return {};
          }
          node.children = groupdef.children;
          // Do we need to copy the items instead of just changing the parent?
          node.children.forEach(item => {
            item.parent = node;
          });
          node.attributes = {
            ...node.attributes,
            ...groupdef.attributes,
          };
        }
        return {};
      }
      default: {
        return node;
      }
    }
  });
}

async function initialize(zip, skinXml) {
  const xmlRoot = skinXml.children[0];
  const root = new WinampAbstractionLayer(skinXml.children[0], null);
  await parseChildren(root, xmlRoot.children, zip);
  await applyGroupDefs(root);
  return root;
}

export default initialize;
<|endoftext|>