<commit_before>// Copyright (c) 2012,2013 Peter Coles - http://mrcoles.com/ - All rights reserved.
// Copyright (c) 2015 Jean-Martin Archer
// Use of this source code is governed by the MIT License found in LICENSE

function $(id) { return document.getElementById(id); }
function show(id) { $(id).style.display = 'block'; }

function capturePage(data) {
	function createHiDPICanvas(w, h, ratio) {
		var canvas = document.createElement("canvas");
		canvas.width = w * ratio;
		canvas.height = h * ratio;
		canvas.style.width = w + "px";
		canvas.style.height = h + "px";
		canvas.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
		return canvas;
	}

	var margins = data.margins;
	var canvas = createHiDPICanvas(data.totalWidth + margins.left + margins.right, data.totalHeight + margins.top + margins.bottom + data.titleBar.height, data.pixelRatio);
	var ctx = canvas.getContext('2d');

	chrome.tabs.captureVisibleTab(
		null, {format: 'png', quality: 100}, function(dataURI) {
			if (dataURI) {
				var image = new Image();
				var titleBarImage = new Image();
				titleBarImage.onload = function () {
					addTitleBar(ctx, titleBarImage, data);
				};
				titleBarImage.src = data.titleBar.data;
				image.onload = function() {
					var coords = {
						x: margins.left,
						y: margins.top + data.titleBar.height,
						w: data.totalWidth,
						h: data.totalHeight
					};
					ctx.drawImage(image, coords.x, coords.y, coords.w, coords.h);
					openPage(canvas, data);
				};
				image.src = dataURI;
			}
		});

	function addTitleBar(ctx, titleBarImage, data) {
		var rightDx = data.totalWidth + 7;
		var leftWidth = data.titleBar.leftWidth;
		var offset = data.titleBar.offset;

		var middleBar = {
			sx: offset, sy: 0,
			sw: 5, sh: leftWidth * 2,
			dx: data.margins.left + 5, dy: data.margins.top,
			dw: rightDx - 20, dh: leftWidth
		};
		var leftBar = {
			sx: 0, sy: 0,
			sw: offset * 2, sh: leftWidth * 2,
			dx: data.margins.left, dy: data.margins.top,
			dw: offset, dh: leftWidth
		};
		var rigthBar = {
			sx: offset, sy: 0,
			sw: offset * 2, sh: leftWidth * 2,
			dx: rightDx, dy: data.margins.top,
			dw: offset, dh: leftWidth
		};

		addShadow(ctx, data);
		drawBar(ctx, titleBarImage, middleBar);
		drawBar(ctx, titleBarImage, leftBar);
		drawBar(ctx, titleBarImage, rigthBar);
	}

	function drawBar(ctx, image, coords) {
		ctx.drawImage(image, coords.sx, coords.sy, coords.sw, coords.sh, coords.dx, coords.dy, coords.dw, coords.dh);
	}

	function addShadow(ctx, data) {
		ctx.save();
		var rect = {
			x: data.margins.left + data.shadow.edgeOffset,
			y: data.margins.top + data.shadow.edgeOffset,
			w: data.totalWidth - (data.shadow.edgeOffset * 2),
			h: data.totalHeight + data.titleBar.height - data.shadow.edgeOffset
		};
		ctx.rect(rect.x, rect.y, rect.w, rect.h);
		ctx.shadowColor = data.shadow.color;
		ctx.shadowBlur = data.shadow.blur;
		ctx.shadowOffsetX = data.shadow.offsetX;
		ctx.shadowOffsetY = data.shadow.offsetY;
		ctx.fill();
		ctx.restore();
	}
}

function openPage(canvas, data) {
	// standard dataURI can be too big, let's blob instead
	// http://code.google.com/p/chromium/issues/detail?id=69227#c27

	var dataURI = canvas.toDataURL();
	var byteString = atob(dataURI.split(',')[1]);
	var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

	var ab = new ArrayBuffer(byteString.length);
	var ia = new Uint8Array(ab);
	for (var i = 0; i < byteString.length; i++) {
		ia[i] = byteString.charCodeAt(i);
	}

	var blob = new Blob([ab], {type: mimeString});
	var size = blob.size + (1024/2);
	var name = data.url.split('?')[0].split('#')[0];
	if (name) {
		name = name
			.replace(/^https?:\/\//, '')
			.replace(/[^A-z0-9]+/g, '-')
			.replace(/-+/g, '-')
			.replace(/^[_\-]+/, '')
			.replace(/[_\-]+$/, '');
		name = '-' + name;
	} else {
		name = '';
	}
	name = 'screenshot' + name + '-' + Date.now() + '.png';

	function onwriteend() {
		// open the file that now contains the blob
		window.open('filesystem:chrome-extension://' + chrome.i18n.getMessage('@@extension_id') + '/temporary/' + name);
		chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, {width: data.originalWidth});
	}

	function errorHandler() {
		show('uh-oh');
	}

	// create a blob for writing to a file
	window.webkitRequestFileSystem(window.TEMPORARY, size, function(fs){
		fs.root.getFile(name, {create: true}, function(fileEntry) {
			fileEntry.createWriter(function(fileWriter) {
				fileWriter.onwriteend = onwriteend;
				fileWriter.write(blob);
			}, errorHandler);
		}, errorHandler);
	}, errorHandler);
}

(function () {
	chrome.tabs.getSelected(null, function(tab) {
		// TODO-JMA enabled setZoom when Google Chrome 42 is released.
		// chrome.tabs.setZoom(tab.id, 1.0);
		var loaded = false;
		var PIXEL_RATIO = (function () {
			var ctx = document.createElement("canvas").getContext("2d"),
				dpr = window.devicePixelRatio || 1,
				bsr = ctx.webkitBackingStorePixelRatio ||
					ctx.backingStorePixelRatio || 1;

			return dpr / bsr;
		})();

		var data = {
			url: tab.url,
			targetWidth: 1280,
			totalWidth: null,
			totalHeight: null,
			pixelRatio: PIXEL_RATIO,
			originalWidth: tab.width,
			margins: {
				top: 15,
				bottom: 25,
				left: 25,
				right: 25
			},
			titleBar: {
				height: 36,
				leftWidth: 120,
				offset: 130,
				data: 'data:image/png;base64,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'},
			shadow: {
				color: 'rgba(0, 0, 0, 0.5)',
				blur: 20 * PIXEL_RATIO,
				offsetX: 0,
				offsetY: 5 * PIXEL_RATIO,
				edgeOffset: 3 // shrinks the box generating the shadow so it doesn't show in the rounded the titleBar corners
			}
		};

		chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, {width: data.targetWidth}, function() {
			chrome.tabs.get(tab.id, function(tab) {
				data.totalWidth = tab.width;
				data.totalHeight = tab.height;
				capturePage(data);
			});
		});
		// getting the tab again to get the new tab size.

		window.setTimeout(function() {
			if (!loaded) {
				show('uh-oh');
			}
		}, 1000);
	});
})();
<commit_msg>[popup.js] minor refactoring<commit_after>// Copyright (c) 2012,2013 Peter Coles - http://mrcoles.com/ - All rights reserved.
// Copyright (c) 2015 Jean-Martin Archer
// Use of this source code is governed by the MIT License found in LICENSE

function $(id) { return document.getElementById(id); }
function show(id) { $(id).style.display = 'block'; }

function capturePage(cfg) {
	function createHiDPICanvas(cfg) {
		var canvas = document.createElement("canvas");
		var w = cfg.totalWidth + cfg.margins.left + cfg.margins.right;
		var h = cfg.totalHeight + cfg.margins.top + cfg.margins.bottom + cfg.titleBar.height;
		canvas.width = w * cfg.pixelRatio;
		canvas.height = h * cfg.pixelRatio;
		canvas.style.width = w + "px";
		canvas.style.height = h + "px";
		canvas.getContext("2d").setTransform(cfg.pixelRatio, 0, 0, cfg.pixelRatio, 0, 0);
		return canvas;
	}

	var canvas = createHiDPICanvas(cfg);
	var ctx = canvas.getContext('2d');

	chrome.tabs.captureVisibleTab(
		null, {format: 'png', quality: 100}, function(dataURI) {
			if (dataURI) {
				var image = new Image();
				var titleBarImage = new Image();
				titleBarImage.onload = function () {
					addTitleBar(ctx, titleBarImage, cfg);
				};
				titleBarImage.src = cfg.titleBar.data;
				image.onload = function() {
					var coords = {
						x: cfg.margins.left,
						y: cfg.margins.top + cfg.titleBar.height,
						w: cfg.totalWidth,
						h: cfg.totalHeight
					};
					ctx.drawImage(image, coords.x, coords.y, coords.w, coords.h);
					openPage(canvas, cfg);
				};
				image.src = dataURI;
			}
		});

	function addTitleBar(ctx, titleBarImage, cfg) {
		var rightDx = cfg.totalWidth + 7;
		var leftWidth = cfg.titleBar.leftWidth;
		var offset = cfg.titleBar.offset;

		var middleBar = {
			sx: offset, sy: 0,
			sw: 5, sh: leftWidth * 2,
			dx: cfg.margins.left + 5, dy: cfg.margins.top,
			dw: rightDx - 20, dh: leftWidth
		};
		var leftBar = {
			sx: 0, sy: 0,
			sw: offset * 2, sh: leftWidth * 2,
			dx: cfg.margins.left, dy: cfg.margins.top,
			dw: offset, dh: leftWidth
		};
		var rigthBar = {
			sx: offset, sy: 0,
			sw: offset * 2, sh: leftWidth * 2,
			dx: rightDx, dy: cfg.margins.top,
			dw: offset, dh: leftWidth
		};

		addShadow(ctx, cfg);
		drawBar(ctx, titleBarImage, middleBar);
		drawBar(ctx, titleBarImage, leftBar);
		drawBar(ctx, titleBarImage, rigthBar);
	}

	function drawBar(ctx, image, coords) {
		ctx.drawImage(image, coords.sx, coords.sy, coords.sw, coords.sh, coords.dx, coords.dy, coords.dw, coords.dh);
	}

	function addShadow(ctx, cfg) {
		ctx.save();
		var rect = {
			x: cfg.margins.left + cfg.shadow.edgeOffset,
			y: cfg.margins.top + cfg.shadow.edgeOffset,
			w: cfg.totalWidth - (cfg.shadow.edgeOffset * 2),
			h: cfg.totalHeight + cfg.titleBar.height - cfg.shadow.edgeOffset
		};
		ctx.rect(rect.x, rect.y, rect.w, rect.h);
		ctx.shadowColor = cfg.shadow.color;
		ctx.shadowBlur = cfg.shadow.blur;
		ctx.shadowOffsetX = cfg.shadow.offsetX;
		ctx.shadowOffsetY = cfg.shadow.offsetY;
		ctx.fill();
		ctx.restore();
	}
}

function openPage(canvas, cfg) {
	// standard dataURI can be too big, let's blob instead
	// http://code.google.com/p/chromium/issues/detail?id=69227#c27

	var dataURI = canvas.toDataURL();
	var byteString = atob(dataURI.split(',')[1]);
	var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

	var ab = new ArrayBuffer(byteString.length);
	var ia = new Uint8Array(ab);
	for (var i = 0; i < byteString.length; i++) {
		ia[i] = byteString.charCodeAt(i);
	}

	var blob = new Blob([ab], {type: mimeString});
	var size = blob.size + (1024/2);
	var name = cfg.url.split('?')[0].split('#')[0];
	if (name) {
		name = name
			.replace(/^https?:\/\//, '')
			.replace(/[^A-z0-9]+/g, '-')
			.replace(/-+/g, '-')
			.replace(/^[_\-]+/, '')
			.replace(/[_\-]+$/, '');
		name = '-' + name;
	} else {
		name = '';
	}
	name = 'screenshot' + name + '-' + Date.now() + '.png';

	function onwriteend() {
		// open the file that now contains the blob
		window.open('filesystem:chrome-extension://' + chrome.i18n.getMessage('@@extension_id') + '/temporary/' + name);
		chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, {width: cfg.originalWidth});
	}

	function errorHandler() {
		show('uh-oh');
	}

	// create a blob for writing to a file
	window.webkitRequestFileSystem(window.TEMPORARY, size, function(fs){
		fs.root.getFile(name, {create: true}, function(fileEntry) {
			fileEntry.createWriter(function(fileWriter) {
				fileWriter.onwriteend = onwriteend;
				fileWriter.write(blob);
			}, errorHandler);
		}, errorHandler);
	}, errorHandler);
}

(function () {
	chrome.tabs.getSelected(null, function(tab) {
		// TODO-JMA enabled setZoom when Google Chrome 42 is released.
		// chrome.tabs.setZoom(tab.id, 1.0);
		var loaded = false;
		var PIXEL_RATIO = (function () {
			var ctx = document.createElement("canvas").getContext("2d"),
				dpr = window.devicePixelRatio || 1,
				bsr = ctx.webkitBackingStorePixelRatio ||
					ctx.backingStorePixelRatio || 1;

			return dpr / bsr;
		})();

		var cfg = {
			url: tab.url,
			targetWidth: 1280,
			totalWidth: null,
			totalHeight: null,
			pixelRatio: PIXEL_RATIO,
			originalWidth: tab.width,
			margins: {
				top: 15,
				bottom: 25,
				left: 25,
				right: 25
			},
			titleBar: {
				height: 36,
				leftWidth: 120,
				offset: 130,
				data: 'data:image/png;base64,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'
			},
			shadow: {
				color: 'rgba(0, 0, 0, 0.5)',
				blur: 20 * PIXEL_RATIO,
				offsetX: 0,
				offsetY: 5 * PIXEL_RATIO,
				edgeOffset: 3 // shrinks the box generating the shadow so it doesn't show in the rounded the titleBar corners
			}
		};

		chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, {width: cfg.targetWidth}, function() {
			chrome.tabs.get(tab.id, function(tab) {
				cfg.totalWidth = tab.width;
				cfg.totalHeight = tab.height;
				capturePage(cfg);
			});
		});
		// getting the tab again to get the new tab size.

		window.setTimeout(function() {
			if (!loaded) {
				show('uh-oh');
			}
		}, 1000);
	});
})();
<|endoftext|>


<commit_before>'use strict'
const gulp = requireModule('gulp-with-help'),
    path = require('path'),
    del = require('del'),
    findDirs = requireModule('find-dirs'),
    debug = require('debug')('purge'),
    streamToPromise = require('gulp-stream-to-promise');

function rm(folder) {
  return new Promise((resolve, reject) => {
    try {
      debug(`purging: ${folder}`);
      rimraf(folder, rimrafOpts, err => {
        if (err) {
          reject(err);
        } else {
          debug(`${folder} purged!`);
          resolve();
        }
      });
    } catch (e) {
      reject(`whoops! ${e}`);
    }
  });
}

function isNotInRootDir(dir) {
  // where 'root dir' refers to the gulp context current dir
  var inRootDir = path.resolve(path.basename(dir));
  return inRootDir !== dir;
}

function doRegularRm(dir, inRootToo) {
  return new Promise((resolve, reject) => {
    try {
      debug(`searching for folders matching: ${dir}`);
      var matches = findDirs('.', dir);
      debug(`got: ${matches}`);
      if (!inRootToo) {
        matches = matches.filter(isNotInRootDir);
      }
      if (matches.length === 0) {
        debug(`-> nothing to do for ${dir}`);
        return resolve();
      }
      matches.forEach(f => {
        debug(`should purge: ${f}`);
        rimraf.sync(f);
        debug(`purge complete: ${f}`);
      });
      resolve();
    } catch (e) {
      debug(`whoops! ${e}`);
      reject(e);
    }
  });
}

function doPurge(includeRootFolders) {
  return Promise.all([
    del([
      './source/**/bin/**',
      './src/**/bin/**',
      './source/**/obj/**',
      './src/**/obj/**'
    ]),
    doRegularRm('node_modules', includeRootFolders),
    doRegularRm('bower_components', includeRootFolders),
    doRegularRm('packages', includeRootFolders)
  ]).then(() => {
    debug('-- PURGE COMPLETE! ---');
  });
}

gulp.task('purge',
    'Purges all bins, objs, node_modules, bower_components and packages not in the root',
    function () {
    return doPurge(false);
});

gulp.task('mega-purge',
  'Performs regular purge and in the root (you\'ll have to `npm install` afterwards!',
  function() {
  return doPurge(true);
});

<commit_msg>fix: import rimraf<commit_after>'use strict'
const gulp = requireModule('gulp-with-help'),
    path = require('path'),
    del = require('del'),
    rimraf = require('rimraf'),
    findDirs = requireModule('find-dirs'),
    debug = require('debug')('purge'),
    streamToPromise = require('gulp-stream-to-promise');

function rm(folder) {
  return new Promise((resolve, reject) => {
    try {
      debug(`purging: ${folder}`);
      rimraf(folder, rimrafOpts, err => {
        if (err) {
          reject(err);
        } else {
          debug(`${folder} purged!`);
          resolve();
        }
      });
    } catch (e) {
      reject(`whoops! ${e}`);
    }
  });
}

function isNotInRootDir(dir) {
  // where 'root dir' refers to the gulp context current dir
  var inRootDir = path.resolve(path.basename(dir));
  return inRootDir !== dir;
}

function doRegularRm(dir, inRootToo) {
  return new Promise((resolve, reject) => {
    try {
      debug(`searching for folders matching: ${dir}`);
      var matches = findDirs('.', dir);
      debug(`got: ${matches}`);
      if (!inRootToo) {
        matches = matches.filter(isNotInRootDir);
      }
      if (matches.length === 0) {
        debug(`-> nothing to do for ${dir}`);
        return resolve();
      }
      matches.forEach(f => {
        debug(`should purge: ${f}`);
        rimraf.sync(f);
        debug(`purge complete: ${f}`);
      });
      resolve();
    } catch (e) {
      debug(`whoops! ${e}`);
      reject(e);
    }
  });
}

function doPurge(includeRootFolders) {
  return Promise.all([
    del([
      './source/**/bin/**',
      './src/**/bin/**',
      './source/**/obj/**',
      './src/**/obj/**'
    ]),
    doRegularRm('node_modules', includeRootFolders),
    doRegularRm('bower_components', includeRootFolders),
    doRegularRm('packages', includeRootFolders)
  ]).then(() => {
    debug('-- PURGE COMPLETE! ---');
  });
}

gulp.task('purge',
    'Purges all bins, objs, node_modules, bower_components and packages not in the root',
    function () {
    return doPurge(false);
});

gulp.task('mega-purge',
  'Performs regular purge and in the root (you\'ll have to `npm install` afterwards!',
  function() {
  return doPurge(true);
});

<|endoftext|>


<commit_before>(function($window, $ember) {

    $window.ES = {};

    /**
     * @module ES
     * @subModule Module
     */
    $window.ES.Module = $ember.ObjectController.extend({

        /**
         * @property host
         * @type {String}
         * @default 'localhost'
         */
        host: 'localhost',

        /**
         * @property controllers
         * @type {Array}
         * List of controllers for which the events can be emitted to.
         * @default []
         */
        controllers: [],

        /**
         * @property port
         * @type {Number}
         * @default 080
         */
        port: 80,

        /**
         * @property socket {Object}
         * @type {Object}
         */
        socket: null,

        didBecomeReady: function() {
            alert('Okay');
        },

        /**
         * @method init
         * Responsible for establishing a connect to the Socket.io server.
         * @constructor
         */
        init: function init() {

            // Create the host:port string for connecting, and then attempt to establish
            // a connection.
            var server      = 'http://%@:%@'.fmt($ember.get(this, 'host'), $ember.get(this, 'port')),
                socket      = io.connect(server);

            // Store a reference to the socket.
            this.set('socket', socket);

            /**
             * @on connect
             */
            socket.on('connect', this._listen.bind(this));

        },

        /**
         * @method emit
         * @param eventName {String}
         * @param options {Object}
         * Responsible for emitting an event to the waiting Socket.io server.
         * @return {void}
         */
        emit: function emit(eventName, options) {
            $ember.get(this, 'socket').emit(eventName, options);
        },

        /**
         * @method _listen
         * Responsible for listening to events from Socket.io, and updating controller properties that
         * subscribe to those events.
         * @return {void}
         * @private
         */
        _listen: function _listen() {

            var controllers     = $ember.get(this, 'controllers'),
                getController   = this._getController.bind(this),
                events          = [],
                forEach         = $ember.EnumerableUtils,
                map             = $ember.ArrayPolyfills.map,
                module          = this;

            forEach.forEach(controllers, function (controllerName) {

                // Fetch the controller if it's valid.
                var controller  = getController(controllerName);

                if (controller) {

                    // Iterate over each event defined in the controller's `sockets` hash, so that we can
                    // keep an eye open for them.
                    for (var eventName in controller.sockets) {

                        if (!controller.sockets.hasOwnProperty(eventName)) {
                            // The usual suspect!
                            continue;
                        }

                        if (events.indexOf(eventName) !== -1) {
                            // Don't observe this event if we're already observing it.
                            continue;
                        }

                        // Push the event so we don't listen for it twice.
                        events.push(eventName);

                        // ...And finally we can register the event to listen for it.
                        $ember.get(module, 'socket').on(eventName, module._update.bind(module));

                    }

                }

            });

        },

        /**
         * @method _update
         * @param eventData {String|Number|Object}
         * @return {Number} Number of controllers which responded to the event.
         * @private
         */
        _update: function _update(eventData) {

            var controllers             = $ember.get(this, 'controllers'),
                respondingControllers   = 0,
                getController           = this._getController.bind(this);

            // Iterate over each listener controller and emit the event we caught.
            $ember.EnumerableUtils.forEach(controllers, function(controllerName) {

                // Fetch the controller if it's valid.
                var controller = getController(controllerName);

                if (controller) {

                    // Attempt to find a match for the current event name.
                    var correspondingProperty = controller.sockets['cherryPickedName'];

                    if (!correspondingProperty) {
                        // If we can't find it, then we can't go any further for this controller.
                        return;
                    }

                    // Otherwise we can go ahead and update the property for this event. Voila!
                    $ember.set(controller, correspondingProperty, eventData);
                    respondingControllers++;

                }

            });

            return respondingControllers;

        },

        /**
         * @method _getController
         * @param name {String}
         * Responsible for retrieving a controller if it exists, and if it has defined a `sockets` hash.
         * @return {Object|Boolean}
         * @private
         */
        _getController: function _getController(name) {

            // Format the `name` to match what the lookup container is expecting, and then
            // we'll locate the controller from the `container`.
            name = 'controller:%@'.fmt(name);
            var controller = this.container.lookup(name);

            if (!controller || !'sockets' in controller) {
                // Don't do anything with this controller if it hasn't defined a `sockets` hash.
                return false;
            }

            return controller;

        }

    });

    /**
     * @onLoad Ember.Application
     * @param $app {Object}
     */
    Ember.onLoad('Ember.Application', function($app) {

        $app.initializer({

            /**
             * @property name
             * @type {String}
             * @default 'sockets'
             */
            name: 'sockets',

            /**
             * @method initialize
             * @param container {Object}
             * @param application {Object}
             * @return {void}
             */
            initialize: function(container, application) {

                application.register('socket:main', application.Socket, {
                    singleton: true
                });

                application.inject('controller', 'socket', 'socket:main');

            }

        });
    });

})(window, window.Ember);<commit_msg>Minor cleanup<commit_after>(function($window, $ember) {

    $window.ES = {};

    /**
     * @module ES
     * @subModule Module
     */
    $window.ES.Module = $ember.ObjectController.extend({

        /**
         * @property host
         * @type {String}
         * @default 'localhost'
         */
        host: 'localhost',

        /**
         * @property port
         * @type {Number}
         * @default 080
         */
        port: 80,

        /**
         * @property controllers
         * @type {Array}
         * List of controllers for which the events can be emitted to.
         * @default []
         */
        controllers: [],

        /**
         * @property socket {Object}
         * @type {Object}
         */
        socket: null,

        /**
         * @constructor
         * Responsible for establishing a connect to the Socket.io server.
         */
        init: function init() {

            // Create the host:port string for connecting, and then attempt to establish
            // a connection.
            var server      = 'http://%@:%@'.fmt($ember.get(this, 'host'), $ember.get(this, 'port')),
                socket      = io.connect(server);

            // Store a reference to the socket.
            this.set('socket', socket);

            /**
             * @on connect
             */
            socket.on('connect', this._listen.bind(this));

        },

        /**
         * @method emit
         * @param eventName {String}
         * @param options {Object}
         * Responsible for emitting an event to the waiting Socket.io server.
         * @return {void}
         */
        emit: function emit(eventName, options) {
            $ember.get(this, 'socket').emit(eventName, options);
        },

        /**
         * @method _listen
         * Responsible for listening to events from Socket.io, and updating controller properties that
         * subscribe to those events.
         * @return {void}
         * @private
         */
        _listen: function _listen() {

            var controllers     = $ember.get(this, 'controllers'),
                getController   = this._getController.bind(this),
                events          = [],
                forEach         = $ember.EnumerableUtils,
                map             = $ember.ArrayPolyfills.map,
                module          = this;

            forEach.forEach(controllers, function (controllerName) {

                // Fetch the controller if it's valid.
                var controller  = getController(controllerName);

                if (controller) {

                    // Iterate over each event defined in the controller's `sockets` hash, so that we can
                    // keep an eye open for them.
                    for (var eventName in controller.sockets) {

                        if (!controller.sockets.hasOwnProperty(eventName)) {
                            // The usual suspect!
                            continue;
                        }

                        if (events.indexOf(eventName) !== -1) {
                            // Don't observe this event if we're already observing it.
                            continue;
                        }

                        // Push the event so we don't listen for it twice.
                        events.push(eventName);

                        // ...And finally we can register the event to listen for it.
                        $ember.get(module, 'socket').on(eventName, module._update.bind(module));

                    }

                }

            });

        },

        /**
         * @method _update
         * @param eventData {String|Number|Object}
         * @return {Number} Number of controllers which responded to the event.
         * @private
         */
        _update: function _update(eventData) {

            var controllers             = $ember.get(this, 'controllers'),
                respondingControllers   = 0,
                getController           = this._getController.bind(this);

            // Iterate over each listener controller and emit the event we caught.
            $ember.EnumerableUtils.forEach(controllers, function(controllerName) {

                // Fetch the controller if it's valid.
                var controller = getController(controllerName);

                if (controller) {

                    // Attempt to find a match for the current event name.
                    var correspondingProperty = controller.sockets['cherryPickedName'];

                    if (!correspondingProperty) {
                        // If we can't find it, then we can't go any further for this controller.
                        return;
                    }

                    // Otherwise we can go ahead and update the property for this event. Voila!
                    $ember.set(controller, correspondingProperty, eventData);
                    respondingControllers++;

                }

            });

            return respondingControllers;

        },

        /**
         * @method _getController
         * @param name {String}
         * Responsible for retrieving a controller if it exists, and if it has defined a `sockets` hash.
         * @return {Object|Boolean}
         * @private
         */
        _getController: function _getController(name) {

            // Format the `name` to match what the lookup container is expecting, and then
            // we'll locate the controller from the `container`.
            name = 'controller:%@'.fmt(name);
            var controller = this.container.lookup(name);

            if (!controller || !'sockets' in controller) {
                // Don't do anything with this controller if it hasn't defined a `sockets` hash.
                return false;
            }

            return controller;

        }

    });

    /**
     * @onLoad Ember.Application
     * @param $app {Object}
     */
    Ember.onLoad('Ember.Application', function($app) {

        $app.initializer({

            /**
             * @property name
             * @type {String}
             * @default 'sockets'
             */
            name: 'sockets',

            /**
             * @method initialize
             * @param container {Object}
             * @param application {Object}
             * @return {void}
             */
            initialize: function(container, application) {

                application.register('socket:main', application.Socket, {
                    singleton: true
                });

                application.inject('controller', 'socket', 'socket:main');

            }

        });
    });

})(window, window.Ember);<|endoftext|>


<commit_before>// FIXME: make these const variables
var async = require('async');

var Transaction = require('../models/Transaction');
var Message = require('../models/Message');
var Review = require('../models/Review');
var activities = require('../config/activities');
var Enums = require('../models/Enums');
var messageController = require('../controllers/message');
var helpers = require('./helpers');
var moment = require('moment');
var app = require('../app');
var nodemailer = require('nodemailer');

function dateString (date) {
    return [date.getDate(), date.getMonth() + 1, date.getFullYear()].join('/');
}

function getMessagesForTransaction (t_id, user_id, callback) {
    Message.find({'_transaction': t_id})
    .sort('timeSent')
    .populate('_sender')
    .exec(function (err, messages) {
        if (err) {
          callback(err);
        } else {
            callback(null, messages.map(function (message) {
                return {
                    message: message.message,
                    timeSent: message.timeSent,
                    author: {
                        name: message._sender.profile.name,
                        pic: message._sender.profile.picture,
                        id: message._sender._id,
                        isMe: (message._sender._id == user_id),
                    },
                };
            }));
        }
    });
}

exports.getMessages = function (req, res) {
    getMessagesForTransaction(req.params.id, req.user.id, helpers.respondToAjax(res));
}

exports.getReviews = function (req, res) {
    Review.find({'_transaction': req.params.id})
    .populate('_creator')
    .exec(function (err, reviews) {
        if (err) {
            res.json({error: err});
            return;
        }
        var curr_user_review = reviews.find((review) => review._creator._id == req.user.id);
        // If there is a review that is not ours, it must be another review about us
        var other_review = reviews.find((review) => review._creator._id != req.user.id);
        if (!curr_user_review) {
            res.json({curr_user_has_review: false});
            return;
        } else if (!other_review) {
            res.json({partner_has_review: false});
            return;
        } else {
            res.json({
                review: {
                    author: {
                        name: other_review._creator.profile.name,
                        picture: other_review._creator.profile.picture,
                        id: other_review._creator._id,
                    },
                    text: other_review.text,
                    rating: other_review.rating,
                    timestamp: moment(other_review.timeSent).startOf('day').fromNow(),
                }
            });
        }
    });
}

/**
 * GET /transactions
 * Show transactions for current user
 */
exports.showTransactions = function(req, res) {

    Transaction.aggregate([
        {
            '$match': {
                '_participants': helpers.toObjectId(req.user._id),
            }
        },
        {
            '$lookup': {
                'from': 'messages',
                'localField': '_id',
                'foreignField': '_transaction',
                'as': '_messages',
            }
        },
        {
            '$sort': {
                'updatedAt': 1,
            }
        },
        {
            '$lookup': {
                'from': 'users',
                'localField': '_creator',
                'foreignField': '_id',
                'as': '_creator',
            }
        },
        {
            "$unwind": "$_creator",
        },
        {
            '$lookup': {
                'from': 'skills',
                'localField': 'service',
                'foreignField': '_id',
                'as': 'service',
            }
        },
        {
            "$unwind": "$service",
        },
        {
            '$unwind': '$_participants',
        },
        {
            '$lookup': {
                'from': 'users',
                'localField': '_participants',
                'foreignField': '_id',
                'as': '_participants',
            }
        },
        {
            '$unwind': '$_participants',
        },
        {
            '$group': {
                '_id': '$_id',
                'service': {'$first': '$service'},
                '_creator': {'$first': '$_creator'},
                '_messages': {'$first': '$_messages'},
                'loc': {'$first': { '$ifNull': [ "$loc", {'$literal': {type: 'Point', coordinates: [null, null]}}] }},
                'status': {'$first': '$status'},
                'happenedAt': {'$first': '$happenedAt'},
                'placeName': {'$first': '$placeName'},
                'request_type': {'$first': '$request_type'},
                '_participants': {'$push': '$_participants'},
                'createdAt': {'$first': '$createdAt'}
            }
        }
    ])
    .exec((err, transactions) => {
        var data = transactions.reduce(function (map, t) {
            t.other_person = t._participants.filter((user) => {
                return user._id !== req.user._id
            })[0];
            if (t.status === Enums.StatusType.PROPOSED) {
                map.proposed.push(t);
            } else if (t.status === Enums.StatusType.ACCEPTED) {
                map.upcoming.push(t);
            } else if (t.status === Enums.StatusType.REJECTED) {
              // Don't show rejected exchanges
            } else {
                map.complete.push(t);
            }
            return map;
        }, {
          'proposed': [],
          'upcoming': [],
          'complete': [],
        });

        res.render('users/transactions', {
            title: 'My Transactions',
            transactions: data,
            moment: moment,
            StatusType: Enums.StatusType,
            locale: 'en',
        });
    });
};

/**
 * POST /submitReview
 * Add a review for a transaction
 */
exports.submitReview = function (req, res) {
    // FIXME: Add subject?
    var review = new Review({
        timeSent: new Date(),
        text: req.body.message,
        rating: req.body.rating,
        _transaction: req.body.t_id,
        _creator: req.user.id,
    });

    review.save(helpers.respondToAjax(res));
};

/**
 * GET /confirmExchange
 * Confirm that an exchange happened.
 */
exports.confirmExchange = function (req, res) {
    // FIXME: Add condition that current user is creator or participant
    Transaction.findById(req.params.id, function (err, transaction) {
        var partner_acknowledged_status, me_acknowledged_status;
        if (req.user.id.toString() == transaction._creator.toString()) {
            partner_acknowledged_status = Enums.StatusType.RECIPIENT_ACK;
            me_acknowledged_status = Enums.StatusType.SENDER_ACK;
        } else {
            partner_acknowledged_status = Enums.StatusType.SENDER_ACK;
            me_acknowledged_status = Enums.StatusType.RECIPIENT_ACK;
        }
        // The bulkWrite approach here ensures that avoid race conditions vis-a-vis
        // the updating of the 'status' field. See http://bit.ly/1pFiOVS
        Transaction.collection.bulkWrite(
            [
               { "updateOne": {
                   "filter": {
                       "_id": helpers.toObjectId(transaction.id),
                       "status": partner_acknowledged_status,
                   },
                   "update": {
                       "$set": { "status": Enums.StatusType.COMPLETE }
                   }
               }},
               { "updateOne": {
                   "filter": {
                       "_id": helpers.toObjectId(transaction.id),
                       "status": Enums.StatusType.ACCEPTED,
                   },
                   "update": {
                       "$set": { "status": me_acknowledged_status }
                   }
               }}
            ],
            { "ordered": false },
            function (err, data) {
                Transaction.findById(req.params.id, function (err, transaction) {
                    res.json({
                        status: transaction.status,
                    });
                });
            }
        );
    });
};

/**
 * POST /acceptRequest
 * Accept a request for an exchange.
 */
exports.postAccept = function (req, res) {
    // FIXME: Add condition that current user is not creator
    Transaction.findOneAndUpdate(
        {_id: req.body.id},
        {status: Enums.StatusType.ACCEPTED},
        {new: true},
        function (err, trans) {
            if (req.body.message) {
                messageController.addMessageToTransaction(req.user.id, req.body.message, trans._id, req, function () {});
            }
            res.json({
                id: trans._id,
                status: trans.status,
            });
        }
    );
};

/**
 * POST /rejectRequest
 * Accept a request for an exchange.
 */
exports.rejectRequest = function (req, res) {
    // FIXME: Add condition that current user is not creator
    Transaction.findOneAndUpdate(
      {_id: req.params.id,
        _participants: req.user.id},
      {
        status: Enums.StatusType.REJECTED,
      }, helpers.respondToAjax(res));
};

/**
 * POST /cancelRequest
 * Accept a request for an exchange.
 */
exports.cancelRequest = function (req, res) {
    Transaction.findOneAndUpdate(
      {_id: req.params.id,
        _creator: req.user.id},
      {
        status: Enums.StatusType.CANCELLED,
      }, helpers.respondToAjax(res));
};

/**
 * POST /transactions
 * Add a transaction for current user. This is the initial
 * request so status is PROPOSED.
 */
exports.postTransaction = function (req, res) {
    async.waterfall([
        function (callback) {
            var trans = new Transaction({
                service: req.body.service,
                request_type: req.body.request_type,
                _participants: [req.body.recipient, req.user.id],
                amount: 1,
                _creator: req.user.id,
                status: Enums.StatusType.PROPOSED,
            });
            trans.save(callback);
        },

        function (t, num, callback) {
            var message = req.body.message;
            if (message) {
              messageController.addMessageToTransaction(req.user.id, message, t._id, req, callback);
            } else {
              callback(null);
            }
        },
    ], helpers.respondToAjax(res));
};

function sendUpdateScheduleEmail (sender, recipient, transaction, req, callback) {
    var transporter = nodemailer.createTransport({
      service: 'Mailgun',
      auth: {
        user: process.env.MAILGUN_USER,
        pass: process.env.MAILGUN_PASSWORD,
      },
    });

    app.render('emailTemplates/updateSchedule', {
        layout: false,
        moment: moment,
        sender: sender,
        recipient: recipient,
        transaction: transaction,
        base_url: (req.secure ? 'https://' : 'http://') + req.headers.host,
    }, (err, html_content) => {
        var mailOptions = {
            to: recipient.email,
            from: 'Bazaar Team <team@shareonbazaar.eu>',
            subject: 'Update on your exchange with ' + sender.profile.name,
            html: html_content,
            text: 'Hi ' + recipient.profile.name + ',\n\n' +
              'Your exchange with ' + sender.profile.name + ' has been updated! Please log on to Bazaar to review it.\n',
        };
        transporter.sendMail(mailOptions, (err) => {
            callback(err);
        });
    });
}

/**
 * POST /schedule
 * Update the schedule (time and location) for a given transaction
 */
exports.postSchedule = function (req, res) {
    var update = {};
    if (req.body.date) {
        update.happenedAt = new Date(Number(req.body.date));
    }

    if (req.body.location) {
        update.loc = {
            type: 'Point',
            coordinates: [Number(req.body.location.longitude), Number(req.body.location.latitude)],
        }
        update.placeName = req.body.location.name;
    }

    Transaction.findOneAndUpdate(
        {_id: req.body.id,
        _participants: req.user.id},
        update,
        {new: true})
    .populate('_participants')
    .exec((err, t) => {
            t._participants
            .filter((u) => u.id !== req.user.id)
            .forEach((u) => {
                sendUpdateScheduleEmail(req.user, u, t, req, ()=>{});
            });
            helpers.respondToAjax(res);
        }
    );
};
<commit_msg>Only fetch non-rejected, non-cancelled exchanges<commit_after>// FIXME: make these const variables
var async = require('async');

var Transaction = require('../models/Transaction');
var Message = require('../models/Message');
var Review = require('../models/Review');
var activities = require('../config/activities');
var Enums = require('../models/Enums');
var messageController = require('../controllers/message');
var helpers = require('./helpers');
var moment = require('moment');
var app = require('../app');
var nodemailer = require('nodemailer');

function dateString (date) {
    return [date.getDate(), date.getMonth() + 1, date.getFullYear()].join('/');
}

function getMessagesForTransaction (t_id, user_id, callback) {
    Message.find({'_transaction': t_id})
    .sort('timeSent')
    .populate('_sender')
    .exec(function (err, messages) {
        if (err) {
          callback(err);
        } else {
            callback(null, messages.map(function (message) {
                return {
                    message: message.message,
                    timeSent: message.timeSent,
                    author: {
                        name: message._sender.profile.name,
                        pic: message._sender.profile.picture,
                        id: message._sender._id,
                        isMe: (message._sender._id == user_id),
                    },
                };
            }));
        }
    });
}

exports.getMessages = function (req, res) {
    getMessagesForTransaction(req.params.id, req.user.id, helpers.respondToAjax(res));
}

exports.getReviews = function (req, res) {
    Review.find({'_transaction': req.params.id})
    .populate('_creator')
    .exec(function (err, reviews) {
        if (err) {
            res.json({error: err});
            return;
        }
        var curr_user_review = reviews.find((review) => review._creator._id == req.user.id);
        // If there is a review that is not ours, it must be another review about us
        var other_review = reviews.find((review) => review._creator._id != req.user.id);
        if (!curr_user_review) {
            res.json({curr_user_has_review: false});
            return;
        } else if (!other_review) {
            res.json({partner_has_review: false});
            return;
        } else {
            res.json({
                review: {
                    author: {
                        name: other_review._creator.profile.name,
                        picture: other_review._creator.profile.picture,
                        id: other_review._creator._id,
                    },
                    text: other_review.text,
                    rating: other_review.rating,
                    timestamp: moment(other_review.timeSent).startOf('day').fromNow(),
                }
            });
        }
    });
}

/**
 * GET /transactions
 * Show transactions for current user
 */
exports.showTransactions = function(req, res) {

    Transaction.aggregate([
        {
            '$match': {
                '_participants': helpers.toObjectId(req.user._id),
                'status': {
                    '$in': [Enums.StatusType.PROPOSED,
                            Enums.StatusType.ACCEPTED,
                            Enums.StatusType.RECIPIENT_ACK,
                            Enums.StatusType.SENDER_ACK,
                            Enums.StatusType.COMPLETE]
                    }
            }
        },
        {
            '$lookup': {
                'from': 'messages',
                'localField': '_id',
                'foreignField': '_transaction',
                'as': '_messages',
            }
        },
        {
            '$sort': {
                'updatedAt': 1,
            }
        },
        {
            '$lookup': {
                'from': 'users',
                'localField': '_creator',
                'foreignField': '_id',
                'as': '_creator',
            }
        },
        {
            "$unwind": "$_creator",
        },
        {
            '$lookup': {
                'from': 'skills',
                'localField': 'service',
                'foreignField': '_id',
                'as': 'service',
            }
        },
        {
            "$unwind": "$service",
        },
        {
            '$unwind': '$_participants',
        },
        {
            '$lookup': {
                'from': 'users',
                'localField': '_participants',
                'foreignField': '_id',
                'as': '_participants',
            }
        },
        {
            '$unwind': '$_participants',
        },
        {
            '$group': {
                '_id': '$_id',
                'service': {'$first': '$service'},
                '_creator': {'$first': '$_creator'},
                '_messages': {'$first': '$_messages'},
                'loc': {'$first': { '$ifNull': [ "$loc", {'$literal': {type: 'Point', coordinates: [null, null]}}] }},
                'status': {'$first': '$status'},
                'happenedAt': {'$first': '$happenedAt'},
                'placeName': {'$first': '$placeName'},
                'request_type': {'$first': '$request_type'},
                '_participants': {'$push': '$_participants'},
                'createdAt': {'$first': '$createdAt'}
            }
        }
    ])
    .exec((err, transactions) => {
        var data = transactions.reduce(function (map, t) {
            t.other_person = t._participants.filter((user) => {
                return user._id !== req.user._id
            })[0];
            if (t.status === Enums.StatusType.PROPOSED) {
                map.proposed.push(t);
            } else if (t.status === Enums.StatusType.ACCEPTED) {
                map.upcoming.push(t);
            } else {
                map.complete.push(t);
            }
            return map;
        }, {
          'proposed': [],
          'upcoming': [],
          'complete': [],
        });

        res.render('users/transactions', {
            title: 'My Transactions',
            transactions: data,
            moment: moment,
            StatusType: Enums.StatusType,
            locale: 'en',
        });
    });
};

/**
 * POST /submitReview
 * Add a review for a transaction
 */
exports.submitReview = function (req, res) {
    // FIXME: Add subject?
    var review = new Review({
        timeSent: new Date(),
        text: req.body.message,
        rating: req.body.rating,
        _transaction: req.body.t_id,
        _creator: req.user.id,
    });

    review.save(helpers.respondToAjax(res));
};

/**
 * GET /confirmExchange
 * Confirm that an exchange happened.
 */
exports.confirmExchange = function (req, res) {
    // FIXME: Add condition that current user is creator or participant
    Transaction.findById(req.params.id, function (err, transaction) {
        var partner_acknowledged_status, me_acknowledged_status;
        if (req.user.id.toString() == transaction._creator.toString()) {
            partner_acknowledged_status = Enums.StatusType.RECIPIENT_ACK;
            me_acknowledged_status = Enums.StatusType.SENDER_ACK;
        } else {
            partner_acknowledged_status = Enums.StatusType.SENDER_ACK;
            me_acknowledged_status = Enums.StatusType.RECIPIENT_ACK;
        }
        // The bulkWrite approach here ensures that avoid race conditions vis-a-vis
        // the updating of the 'status' field. See http://bit.ly/1pFiOVS
        Transaction.collection.bulkWrite(
            [
               { "updateOne": {
                   "filter": {
                       "_id": helpers.toObjectId(transaction.id),
                       "status": partner_acknowledged_status,
                   },
                   "update": {
                       "$set": { "status": Enums.StatusType.COMPLETE }
                   }
               }},
               { "updateOne": {
                   "filter": {
                       "_id": helpers.toObjectId(transaction.id),
                       "status": Enums.StatusType.ACCEPTED,
                   },
                   "update": {
                       "$set": { "status": me_acknowledged_status }
                   }
               }}
            ],
            { "ordered": false },
            function (err, data) {
                Transaction.findById(req.params.id, function (err, transaction) {
                    res.json({
                        status: transaction.status,
                    });
                });
            }
        );
    });
};

/**
 * POST /acceptRequest
 * Accept a request for an exchange.
 */
exports.postAccept = function (req, res) {
    // FIXME: Add condition that current user is not creator
    Transaction.findOneAndUpdate(
        {_id: req.body.id},
        {status: Enums.StatusType.ACCEPTED},
        {new: true},
        function (err, trans) {
            if (req.body.message) {
                messageController.addMessageToTransaction(req.user.id, req.body.message, trans._id, req, function () {});
            }
            res.json({
                id: trans._id,
                status: trans.status,
            });
        }
    );
};

/**
 * POST /rejectRequest
 * Accept a request for an exchange.
 */
exports.rejectRequest = function (req, res) {
    // FIXME: Add condition that current user is not creator
    Transaction.findOneAndUpdate(
      {_id: req.params.id,
        _participants: req.user.id},
      {
        status: Enums.StatusType.REJECTED,
      }, helpers.respondToAjax(res));
};

/**
 * POST /cancelRequest
 * Accept a request for an exchange.
 */
exports.cancelRequest = function (req, res) {
    Transaction.findOneAndUpdate(
      {_id: req.params.id,
        _creator: req.user.id},
      {
        status: Enums.StatusType.CANCELLED,
      }, helpers.respondToAjax(res));
};

/**
 * POST /transactions
 * Add a transaction for current user. This is the initial
 * request so status is PROPOSED.
 */
exports.postTransaction = function (req, res) {
    async.waterfall([
        function (callback) {
            var trans = new Transaction({
                service: req.body.service,
                request_type: req.body.request_type,
                _participants: [req.body.recipient, req.user.id],
                amount: 1,
                _creator: req.user.id,
                status: Enums.StatusType.PROPOSED,
            });
            trans.save(callback);
        },

        function (t, num, callback) {
            var message = req.body.message;
            if (message) {
              messageController.addMessageToTransaction(req.user.id, message, t._id, req, callback);
            } else {
              callback(null);
            }
        },
    ], helpers.respondToAjax(res));
};

function sendUpdateScheduleEmail (sender, recipient, transaction, req, callback) {
    var transporter = nodemailer.createTransport({
      service: 'Mailgun',
      auth: {
        user: process.env.MAILGUN_USER,
        pass: process.env.MAILGUN_PASSWORD,
      },
    });

    app.render('emailTemplates/updateSchedule', {
        layout: false,
        moment: moment,
        sender: sender,
        recipient: recipient,
        transaction: transaction,
        base_url: (req.secure ? 'https://' : 'http://') + req.headers.host,
    }, (err, html_content) => {
        var mailOptions = {
            to: recipient.email,
            from: 'Bazaar Team <team@shareonbazaar.eu>',
            subject: 'Update on your exchange with ' + sender.profile.name,
            html: html_content,
            text: 'Hi ' + recipient.profile.name + ',\n\n' +
              'Your exchange with ' + sender.profile.name + ' has been updated! Please log on to Bazaar to review it.\n',
        };
        transporter.sendMail(mailOptions, (err) => {
            callback(err);
        });
    });
}

/**
 * POST /schedule
 * Update the schedule (time and location) for a given transaction
 */
exports.postSchedule = function (req, res) {
    var update = {};
    if (req.body.date) {
        update.happenedAt = new Date(Number(req.body.date));
    }

    if (req.body.location) {
        update.loc = {
            type: 'Point',
            coordinates: [Number(req.body.location.longitude), Number(req.body.location.latitude)],
        }
        update.placeName = req.body.location.name;
    }

    Transaction.findOneAndUpdate(
        {_id: req.body.id,
        _participants: req.user.id},
        update,
        {new: true})
    .populate('_participants')
    .exec((err, t) => {
            t._participants
            .filter((u) => u.id !== req.user.id)
            .forEach((u) => {
                sendUpdateScheduleEmail(req.user, u, t, req, ()=>{});
            });
            helpers.respondToAjax(res);
        }
    );
};
<|endoftext|>


<commit_before>import { Asset } from '../../asset/asset.js';
import { Entity } from '../../framework/entity.js';
import { MeshInstance } from '../../scene/mesh-instance.js';
import { MorphInstance } from '../../scene/morph-instance.js';
import { SkinInstance } from '../../scene/skin-instance.js';
import { SkinInstanceCache } from '../../scene/skin-instance-cache.js';
import { Model } from '../../scene/model.js';

// Container resource returned by the GlbParser. Implements the ContainerResource interface.
class GlbContainerResource {
    constructor(data, asset, assets, defaultMaterial) {
        const createAsset = function (type, resource, index) {
            const subAsset = GlbContainerResource.createAsset(asset.name, type, resource, index);
            assets.add(subAsset);
            return subAsset;
        };

        // render assets
        const renders = [];
        for (let i = 0; i < data.renders.length; ++i) {
            renders.push(createAsset('render', data.renders[i], i));
        }

        // create material assets
        const materials = [];
        for (let i = 0; i < data.materials.length; ++i) {
            materials.push(createAsset('material', data.materials[i], i));
        }

        // create animation assets
        const animations = [];
        for (let i = 0; i < data.animations.length; ++i) {
            animations.push(createAsset('animation', data.animations[i], i));
        }

        this.data = data;
        this._model = null;
        this._assetName = asset.name;
        this._assets = assets;
        this._defaultMaterial = defaultMaterial;
        this.renders = renders;
        this.materials = materials;
        this.textures = data.textures; // texture assets are created directly
        this.animations = animations;
    }

    get model() {
        if (!this._model) {
            // create model only when needed
            const model = GlbContainerResource.createModel(this.data, this._defaultMaterial);
            const modelAsset = GlbContainerResource.createAsset(this._assetName, 'model', model, 0);
            this._assets.add(modelAsset);
            this._model = modelAsset;
        }
        return this._model;
    }

    static createAsset(assetName, type, resource, index) {
        const subAsset = new Asset(assetName + '/' + type + '/' + index, type, {
            url: ''
        });
        subAsset.resource = resource;
        subAsset.loaded = true;
        return subAsset;
    }

    instantiateModelEntity(options) {
        const entity = new Entity();
        entity.addComponent("model", Object.assign({ type: "asset", asset: this.model }, options));
        return entity;
    }

    instantiateRenderEntity(options) {

        const defaultMaterial = this._defaultMaterial;
        const skinedMeshInstances = [];

        const createMeshInstance = function (root, entity, mesh, materials, skins, gltfNode) {

            // clone mesh instance
            const material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex];
            const meshInstance = new MeshInstance(mesh, material);

            // create morph instance
            if (mesh.morph) {
                meshInstance.morphInstance = new MorphInstance(mesh.morph);
            }

            // store data to create skin instance after the hierarchy is created
            if (gltfNode.hasOwnProperty('skin')) {
                skinedMeshInstances.push({
                    meshInstance: meshInstance,
                    rootBone: root,
                    entity: entity
                });
            }

            return meshInstance;
        };

        // helper function to recursively clone a hierarchy of GraphNodes to Entities
        const cloneHierarchy = function (root, node, glb) {

            const entity = new Entity();
            node._cloneInternal(entity);

            // first entity becomes the root
            if (!root) root = entity;

            // find all components needed for this node
            let attachedMi = null;
            for (let i = 0; i < glb.nodes.length; i++) {
                const glbNode = glb.nodes[i];
                if (glbNode === node) {
                    const gltfNode = glb.gltf.nodes[i];

                    // mesh
                    if (gltfNode.hasOwnProperty('mesh')) {
                        const meshGroup = glb.renders[gltfNode.mesh].meshes;
                        for (var mi = 0; mi < meshGroup.length; mi++) {
                            const mesh = meshGroup[mi];
                            if (mesh) {
                                const cloneMi = createMeshInstance(root, entity, mesh, glb.materials, glb.skins, gltfNode);

                                // add it to list
                                if (!attachedMi) {
                                    attachedMi = [];
                                }
                                attachedMi.push(cloneMi);
                            }
                        }
                    }

                    // light - clone (additional child) entity with the light component
                    // cannot clone the component as additional entity has a rotation to handle different light direction
                    if (glb.lights) {
                        const lightEntity = glb.lights.get(gltfNode);
                        if (lightEntity) {
                            entity.addChild(lightEntity.clone());
                        }
                    }

                    // camera
                    if (glb.cameras) {
                        const cameraEntity = glb.cameras.get(gltfNode);
                        if (cameraEntity) {
                            // clone camera component into the entity
                            cameraEntity.camera.system.cloneComponent(cameraEntity, entity);
                        }
                    }
                }
            }

            // create render components for mesh instances
            if (attachedMi) {
                entity.addComponent("render", Object.assign({
                    type: "asset",
                    meshInstances: attachedMi,
                    rootBone: root
                }, options));
            }

            // recursivelly clone children
            const children = node.children;
            for (let i = 0; i < children.length; i++) {
                const childClone = cloneHierarchy(root, children[i], glb);
                entity.addChild(childClone);
            }

            return entity;
        };

        // clone scenes hierarchies
        const sceneClones = [];
        for (const scene of this.data.scenes) {
            sceneClones.push(cloneHierarchy(null, scene, this.data));
        }

        // now that the hierarchy is created, create skin instances and resolve bones using the hierarchy
        skinedMeshInstances.forEach((data) => {
            data.meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(data.meshInstance.mesh.skin, data.rootBone, data.entity);
        });

        // return the scene hierarachy created from scene clones
        return GlbContainerResource.createSceneHierarchy(sceneClones, "Entity");
    }

    // helper function to create a single hierarchy from an array of nodes
    static createSceneHierarchy(sceneNodes, nodeType) {

        // create a single root of the hierarchy - either the single scene, or a new Entity parent if multiple scenes
        let root = null;
        if (sceneNodes.length === 1) {
            // use scene if only one
            root = sceneNodes[0];
        } else {
            // create group node for all scenes
            root = new nodeType('SceneGroup');
            for (const scene of sceneNodes) {
                root.addChild(scene);
            }
        }

        return root;
    }

    // create a pc.Model from the parsed GLB data structures
    static createModel(glb, defaultMaterial) {

        const createMeshInstance = function (model, mesh, skins, skinInstances, materials, node, gltfNode) {
            const material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex];
            const meshInstance = new MeshInstance(mesh, material, node);

            if (mesh.morph) {
                const morphInstance = new MorphInstance(mesh.morph);
                meshInstance.morphInstance = morphInstance;
                model.morphInstances.push(morphInstance);
            }

            if (gltfNode.hasOwnProperty('skin')) {
                const skinIndex = gltfNode.skin;
                const skin = skins[skinIndex];
                mesh.skin = skin;

                const skinInstance = skinInstances[skinIndex];
                meshInstance.skinInstance = skinInstance;
                model.skinInstances.push(skinInstance);
            }

            model.meshInstances.push(meshInstance);
        };

        const model = new Model();

        // create skinInstance for each skin
        const skinInstances = [];
        for (const skin of glb.skins) {
            const skinInstance = new SkinInstance(skin);
            skinInstance.bones = skin.bones;
            skinInstances.push(skinInstance);
        }

        // node hierarchy for the model
        model.graph = GlbContainerResource.createSceneHierarchy(glb.scenes, "GraphNode");

        // create mesh instance for meshes on nodes that are part of hierarchy
        for (let i = 0; i < glb.nodes.length; i++) {
            const node = glb.nodes[i];
            if (node.root === model.graph) {
                const gltfNode = glb.gltf.nodes[i];
                if (gltfNode.hasOwnProperty('mesh')) {
                    const meshGroup = glb.renders[gltfNode.mesh].meshes;
                    for (var mi = 0; mi < meshGroup.length; mi++) {
                        const mesh = meshGroup[mi];
                        if (mesh) {
                            createMeshInstance(model, mesh, glb.skins, skinInstances, glb.materials, node, gltfNode);
                        }
                    }
                }
            }
        }

        return model;
    }

    destroy() {
        const registry = this._assets;

        const destroyAsset = function (asset) {
            registry.remove(asset);
            asset.unload();
        };

        const destroyAssets = function (assets) {
            assets.forEach(function (asset) {
                destroyAsset(asset);
            });
        };

        // unload and destroy assets
        if (this.animations) {
            destroyAssets(this.animations);
            this.animations = null;
        }

        if (this.textures) {
            destroyAssets(this.textures);
            this.textures = null;
        }

        if (this.materials) {
            destroyAssets(this.materials);
            this.materials = null;
        }

        if (this.renders) {
            destroyAssets(this.renders);
            this.renders = null;
        }

        if (this._model) {
            destroyAsset(this._model);
            this._model = null;
        }

        this.data = null;
        this.assets = null;
    }
}

export { GlbContainerResource };
<commit_msg>Fix spelling mistakes in GlbContainerResource (#3871)<commit_after>import { Asset } from '../../asset/asset.js';
import { Entity } from '../../framework/entity.js';
import { MeshInstance } from '../../scene/mesh-instance.js';
import { MorphInstance } from '../../scene/morph-instance.js';
import { SkinInstance } from '../../scene/skin-instance.js';
import { SkinInstanceCache } from '../../scene/skin-instance-cache.js';
import { Model } from '../../scene/model.js';

// Container resource returned by the GlbParser. Implements the ContainerResource interface.
class GlbContainerResource {
    constructor(data, asset, assets, defaultMaterial) {
        const createAsset = function (type, resource, index) {
            const subAsset = GlbContainerResource.createAsset(asset.name, type, resource, index);
            assets.add(subAsset);
            return subAsset;
        };

        // render assets
        const renders = [];
        for (let i = 0; i < data.renders.length; ++i) {
            renders.push(createAsset('render', data.renders[i], i));
        }

        // create material assets
        const materials = [];
        for (let i = 0; i < data.materials.length; ++i) {
            materials.push(createAsset('material', data.materials[i], i));
        }

        // create animation assets
        const animations = [];
        for (let i = 0; i < data.animations.length; ++i) {
            animations.push(createAsset('animation', data.animations[i], i));
        }

        this.data = data;
        this._model = null;
        this._assetName = asset.name;
        this._assets = assets;
        this._defaultMaterial = defaultMaterial;
        this.renders = renders;
        this.materials = materials;
        this.textures = data.textures; // texture assets are created directly
        this.animations = animations;
    }

    get model() {
        if (!this._model) {
            // create model only when needed
            const model = GlbContainerResource.createModel(this.data, this._defaultMaterial);
            const modelAsset = GlbContainerResource.createAsset(this._assetName, 'model', model, 0);
            this._assets.add(modelAsset);
            this._model = modelAsset;
        }
        return this._model;
    }

    static createAsset(assetName, type, resource, index) {
        const subAsset = new Asset(assetName + '/' + type + '/' + index, type, {
            url: ''
        });
        subAsset.resource = resource;
        subAsset.loaded = true;
        return subAsset;
    }

    instantiateModelEntity(options) {
        const entity = new Entity();
        entity.addComponent("model", Object.assign({ type: "asset", asset: this.model }, options));
        return entity;
    }

    instantiateRenderEntity(options) {

        const defaultMaterial = this._defaultMaterial;
        const skinnedMeshInstances = [];

        const createMeshInstance = function (root, entity, mesh, materials, skins, gltfNode) {

            // clone mesh instance
            const material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex];
            const meshInstance = new MeshInstance(mesh, material);

            // create morph instance
            if (mesh.morph) {
                meshInstance.morphInstance = new MorphInstance(mesh.morph);
            }

            // store data to create skin instance after the hierarchy is created
            if (gltfNode.hasOwnProperty('skin')) {
                skinnedMeshInstances.push({
                    meshInstance: meshInstance,
                    rootBone: root,
                    entity: entity
                });
            }

            return meshInstance;
        };

        // helper function to recursively clone a hierarchy of GraphNodes to Entities
        const cloneHierarchy = function (root, node, glb) {

            const entity = new Entity();
            node._cloneInternal(entity);

            // first entity becomes the root
            if (!root) root = entity;

            // find all components needed for this node
            let attachedMi = null;
            for (let i = 0; i < glb.nodes.length; i++) {
                const glbNode = glb.nodes[i];
                if (glbNode === node) {
                    const gltfNode = glb.gltf.nodes[i];

                    // mesh
                    if (gltfNode.hasOwnProperty('mesh')) {
                        const meshGroup = glb.renders[gltfNode.mesh].meshes;
                        for (var mi = 0; mi < meshGroup.length; mi++) {
                            const mesh = meshGroup[mi];
                            if (mesh) {
                                const cloneMi = createMeshInstance(root, entity, mesh, glb.materials, glb.skins, gltfNode);

                                // add it to list
                                if (!attachedMi) {
                                    attachedMi = [];
                                }
                                attachedMi.push(cloneMi);
                            }
                        }
                    }

                    // light - clone (additional child) entity with the light component
                    // cannot clone the component as additional entity has a rotation to handle different light direction
                    if (glb.lights) {
                        const lightEntity = glb.lights.get(gltfNode);
                        if (lightEntity) {
                            entity.addChild(lightEntity.clone());
                        }
                    }

                    // camera
                    if (glb.cameras) {
                        const cameraEntity = glb.cameras.get(gltfNode);
                        if (cameraEntity) {
                            // clone camera component into the entity
                            cameraEntity.camera.system.cloneComponent(cameraEntity, entity);
                        }
                    }
                }
            }

            // create render components for mesh instances
            if (attachedMi) {
                entity.addComponent("render", Object.assign({
                    type: "asset",
                    meshInstances: attachedMi,
                    rootBone: root
                }, options));
            }

            // recursively clone children
            const children = node.children;
            for (let i = 0; i < children.length; i++) {
                const childClone = cloneHierarchy(root, children[i], glb);
                entity.addChild(childClone);
            }

            return entity;
        };

        // clone scenes hierarchies
        const sceneClones = [];
        for (const scene of this.data.scenes) {
            sceneClones.push(cloneHierarchy(null, scene, this.data));
        }

        // now that the hierarchy is created, create skin instances and resolve bones using the hierarchy
        skinnedMeshInstances.forEach((data) => {
            data.meshInstance.skinInstance = SkinInstanceCache.createCachedSkinInstance(data.meshInstance.mesh.skin, data.rootBone, data.entity);
        });

        // return the scene hierarchy created from scene clones
        return GlbContainerResource.createSceneHierarchy(sceneClones, "Entity");
    }

    // helper function to create a single hierarchy from an array of nodes
    static createSceneHierarchy(sceneNodes, nodeType) {

        // create a single root of the hierarchy - either the single scene, or a new Entity parent if multiple scenes
        let root = null;
        if (sceneNodes.length === 1) {
            // use scene if only one
            root = sceneNodes[0];
        } else {
            // create group node for all scenes
            root = new nodeType('SceneGroup');
            for (const scene of sceneNodes) {
                root.addChild(scene);
            }
        }

        return root;
    }

    // create a pc.Model from the parsed GLB data structures
    static createModel(glb, defaultMaterial) {

        const createMeshInstance = function (model, mesh, skins, skinInstances, materials, node, gltfNode) {
            const material = (mesh.materialIndex === undefined) ? defaultMaterial : materials[mesh.materialIndex];
            const meshInstance = new MeshInstance(mesh, material, node);

            if (mesh.morph) {
                const morphInstance = new MorphInstance(mesh.morph);
                meshInstance.morphInstance = morphInstance;
                model.morphInstances.push(morphInstance);
            }

            if (gltfNode.hasOwnProperty('skin')) {
                const skinIndex = gltfNode.skin;
                const skin = skins[skinIndex];
                mesh.skin = skin;

                const skinInstance = skinInstances[skinIndex];
                meshInstance.skinInstance = skinInstance;
                model.skinInstances.push(skinInstance);
            }

            model.meshInstances.push(meshInstance);
        };

        const model = new Model();

        // create skinInstance for each skin
        const skinInstances = [];
        for (const skin of glb.skins) {
            const skinInstance = new SkinInstance(skin);
            skinInstance.bones = skin.bones;
            skinInstances.push(skinInstance);
        }

        // node hierarchy for the model
        model.graph = GlbContainerResource.createSceneHierarchy(glb.scenes, "GraphNode");

        // create mesh instance for meshes on nodes that are part of hierarchy
        for (let i = 0; i < glb.nodes.length; i++) {
            const node = glb.nodes[i];
            if (node.root === model.graph) {
                const gltfNode = glb.gltf.nodes[i];
                if (gltfNode.hasOwnProperty('mesh')) {
                    const meshGroup = glb.renders[gltfNode.mesh].meshes;
                    for (var mi = 0; mi < meshGroup.length; mi++) {
                        const mesh = meshGroup[mi];
                        if (mesh) {
                            createMeshInstance(model, mesh, glb.skins, skinInstances, glb.materials, node, gltfNode);
                        }
                    }
                }
            }
        }

        return model;
    }

    destroy() {
        const registry = this._assets;

        const destroyAsset = function (asset) {
            registry.remove(asset);
            asset.unload();
        };

        const destroyAssets = function (assets) {
            assets.forEach(function (asset) {
                destroyAsset(asset);
            });
        };

        // unload and destroy assets
        if (this.animations) {
            destroyAssets(this.animations);
            this.animations = null;
        }

        if (this.textures) {
            destroyAssets(this.textures);
            this.textures = null;
        }

        if (this.materials) {
            destroyAssets(this.materials);
            this.materials = null;
        }

        if (this.renders) {
            destroyAssets(this.renders);
            this.renders = null;
        }

        if (this._model) {
            destroyAsset(this._model);
            this._model = null;
        }

        this.data = null;
        this.assets = null;
    }
}

export { GlbContainerResource };
<|endoftext|>