<commit_before>'use strict';

const path = require('path');

const bunyan = require('bunyan');
const ClusterMaster = require('flora-cluster').Master;
const chokidar = require('chokidar');
const AsyncEventEmitter = require('async-eventemitter');

/**
 * A wrapper around the flora-cluster master.
 */
class Master extends AsyncEventEmitter {
    /**
     * @param {string} configPath
     */
    constructor(configPath) {
        super();

        if (!configPath) throw new Error('Master must be called with a configPath parameter');

        this._configPath = configPath;
        this._clusterMaster = null;
        this._watcher = null;
        this._reloadTimeout = null;
        this._plugins = [];

        this.config = null;
        this.log = null;
    }

    /**
     * Run the cluster master process.
     *
     * Loads the config, runs flora-cluster master and (optionally) watches the
     * filesystem for changes.
     *
     * @param {Function} callback
     */
    run(callback) {
        this._reloadConfig();

        this.log = this.log || this.config.log || bunyan.createLogger({ name: 'flora', component: 'master' });
        const log = this.log;
        callback = callback || function nop() {};

        this._clusterMaster = new ClusterMaster({
            exec: this.config.exec,
            workers: this.config.workers,
            log: this.log,
            startupTimeout: this.config.startupTimeout,
            shutdownTimeout: this.config.shutdownTimeout,
            beforeReload: this._beforeReload.bind(this),
            beforeShutdown: this._beforeShutdown.bind(this)
        });

        this._plugins.forEach((plugin) => {
            const [obj, options] = plugin;
            this.log.debug(`Registering master plugin "${obj.name || '(unnamed)'}"`);
            obj.register(this, options);
        });

        this._clusterMaster.on('init', () => this.emit('init'));

        this._clusterMaster.on('shutdown', () => this.emit('shutdown'));

        this._clusterMaster.run();

        if (this.config.reloadOnChange) {
            const watchPaths = [
                this._configPath,
                path.join(__dirname, '..')
            ];

            if (this.config.resourcesPath) watchPaths.push(this.config.resourcesPath);

            log.info({ watchPaths }, 'Watching filesystem for changes');

            this._watcher = chokidar.watch(watchPaths, {
                ignoreInitial: true,
                ignored: ['.*', '**/node_modules', '**/.*', '**/build/*', '**/test/*'],
                interval: 2000,
                usePolling: false,
                followSymlinks: false
            }).on('all', (event, p) => {
                log.info(`watcher: ${event}: ${p}`);
                if (this._reloadTimeout) clearTimeout(this._reloadTimeout);
                this._reloadTimeout = setTimeout(() => {
                    this._clusterMaster.reload();
                }, this.config.reloadTimeout || 1000);
            });
        }

        if (callback) callback(null);
    }

    /**
     * Retrieve the cluster status
     *
     * @param {Function} callback
     */
    serverStatus(callback) {
        return this._clusterMaster.serverStatus(callback);
    }

    /**
     * Register a plugin.
     *
     * The plugin has to be an object with a "register" function property, which is called with
     * parameters (master, options).
     *
     * @param {Object} plugin
     * @param {Object} options
     */
    register(plugin, options) {
        if (typeof plugin !== 'object' || typeof plugin.register !== 'function') {
            throw new Error('The plugin must provide a "register" function');
        }

        this._plugins.push([plugin, options]);
        if (this._clusterMaster) plugin.register(this, options);
    }

    /**
     * Called before the master process is reloaded.
     *
     * @param {Function} callback
     * @private
     */
    _beforeReload(callback) {
        this._reloadConfig();

        this._clusterMaster.setConfig({
            workers: this.config.workers,
            log: this.log,
            startupTimeout: this.config.startupTimeout,
            shutdownTimeout: this.config.shutdownTimeout
        });

        callback();
    }

    /**
     * Called before the master process is shut down.
     *
     * @param {Function} callback
     * @private
     */
    _beforeShutdown(callback) {
        if (this._reloadTimeout) clearTimeout(this._reloadTimeout);
        if (this._watcher) this._watcher.close();
        callback();
    }

    /**
     * Reloads the config. Usually called before the master process is reloaded.
     *
     * @private
     */
    _reloadConfig() {
        const resolvedConfigPath = require.resolve(this._configPath);
        delete require.cache[resolvedConfigPath];
        // eslint-disable-next-line global-require, import/no-dynamic-require
        this.config = require(resolvedConfigPath);
    }
}

module.exports = Master;
<commit_msg>master: enable followSymlinks again, to prevent unnecessary restart no startup when watching symlinks<commit_after>'use strict';

const path = require('path');

const bunyan = require('bunyan');
const ClusterMaster = require('flora-cluster').Master;
const chokidar = require('chokidar');
const AsyncEventEmitter = require('async-eventemitter');

/**
 * A wrapper around the flora-cluster master.
 */
class Master extends AsyncEventEmitter {
    /**
     * @param {string} configPath
     */
    constructor(configPath) {
        super();

        if (!configPath) throw new Error('Master must be called with a configPath parameter');

        this._configPath = configPath;
        this._clusterMaster = null;
        this._watcher = null;
        this._reloadTimeout = null;
        this._plugins = [];

        this.config = null;
        this.log = null;
    }

    /**
     * Run the cluster master process.
     *
     * Loads the config, runs flora-cluster master and (optionally) watches the
     * filesystem for changes.
     *
     * @param {Function} callback
     */
    run(callback) {
        this._reloadConfig();

        this.log = this.log || this.config.log || bunyan.createLogger({ name: 'flora', component: 'master' });
        const log = this.log;
        callback = callback || function nop() {};

        this._clusterMaster = new ClusterMaster({
            exec: this.config.exec,
            workers: this.config.workers,
            log: this.log,
            startupTimeout: this.config.startupTimeout,
            shutdownTimeout: this.config.shutdownTimeout,
            beforeReload: this._beforeReload.bind(this),
            beforeShutdown: this._beforeShutdown.bind(this)
        });

        this._plugins.forEach((plugin) => {
            const [obj, options] = plugin;
            this.log.debug(`Registering master plugin "${obj.name || '(unnamed)'}"`);
            obj.register(this, options);
        });

        this._clusterMaster.on('init', () => this.emit('init'));

        this._clusterMaster.on('shutdown', () => this.emit('shutdown'));

        this._clusterMaster.run();

        if (this.config.reloadOnChange) {
            const watchPaths = [
                this._configPath,
                path.join(__dirname, '..')
            ];

            if (this.config.resourcesPath) watchPaths.push(this.config.resourcesPath);

            log.info({ watchPaths }, 'Watching filesystem for changes');

            this._watcher = chokidar.watch(watchPaths, {
                ignoreInitial: true,
                ignored: ['.*', '**/node_modules', '**/.*', '**/build/*', '**/test/*'],
                interval: 2000,
                usePolling: false
            }).on('all', (event, p) => {
                log.info(`watcher: ${event}: ${p}`);
                if (this._reloadTimeout) clearTimeout(this._reloadTimeout);
                this._reloadTimeout = setTimeout(() => {
                    this._clusterMaster.reload();
                }, this.config.reloadTimeout || 1000);
            });
        }

        if (callback) callback(null);
    }

    /**
     * Retrieve the cluster status
     *
     * @param {Function} callback
     */
    serverStatus(callback) {
        return this._clusterMaster.serverStatus(callback);
    }

    /**
     * Register a plugin.
     *
     * The plugin has to be an object with a "register" function property, which is called with
     * parameters (master, options).
     *
     * @param {Object} plugin
     * @param {Object} options
     */
    register(plugin, options) {
        if (typeof plugin !== 'object' || typeof plugin.register !== 'function') {
            throw new Error('The plugin must provide a "register" function');
        }

        this._plugins.push([plugin, options]);
        if (this._clusterMaster) plugin.register(this, options);
    }

    /**
     * Called before the master process is reloaded.
     *
     * @param {Function} callback
     * @private
     */
    _beforeReload(callback) {
        this._reloadConfig();

        this._clusterMaster.setConfig({
            workers: this.config.workers,
            log: this.log,
            startupTimeout: this.config.startupTimeout,
            shutdownTimeout: this.config.shutdownTimeout
        });

        callback();
    }

    /**
     * Called before the master process is shut down.
     *
     * @param {Function} callback
     * @private
     */
    _beforeShutdown(callback) {
        if (this._reloadTimeout) clearTimeout(this._reloadTimeout);
        if (this._watcher) this._watcher.close();
        callback();
    }

    /**
     * Reloads the config. Usually called before the master process is reloaded.
     *
     * @private
     */
    _reloadConfig() {
        const resolvedConfigPath = require.resolve(this._configPath);
        delete require.cache[resolvedConfigPath];
        // eslint-disable-next-line global-require, import/no-dynamic-require
        this.config = require(resolvedConfigPath);
    }
}

module.exports = Master;
<|endoftext|>


<commit_before>var createCache = require('uber-cache');

var defaultSize              = 100;
var defaultTTLInMilliSeconds = 60 * 1000;

function normalizeOptions(options) {
  options = options || {};

  options.size = options.size || defaultSie;

  var ttlInSeconds = options.ttlInSeconds || 0;
  options.ttlInMilliSeconds = options.ttl ||
                                options.ttlInMilliSeconds ||
                                (ttlInSeconds * 1000) ||
                                defaultTTLInMilliSeconds;

  return options;
}

function generateKey(request) {
  return request.method + '\n' + request.url;
}

function sendCachedResponse(response, cached) {
  response.statusCode = cached.status;
  Object.keys(cached.headers).forEach(function(key) {
    response.setHeader(key, cached.headers[key]);
  });
  cached.body.forEach(function(chunk) {
    response.write(chunk.data, chunk.encoding);
  });
  response.end();
}

function Cache(options) {
  options = normalizeOptions(options);
  var cache = this.cache = createCache({
    size: options.size
  });

  this.isCachableRequest = function(request) {
    return request.method == 'GET';
  };

  this.Entry = function ResponseCacheEntry(cache) {
    this.data = {
    status:  0,
      headers: {},
      body:    []
    };
  };
  this.Entry.prototype = {
    get cachable() {
      return this.data.status == 200;
    },
    write: function(chunk, encoding) {
      this.data.body.push({
        data:     chunk,
        encoding: encoding
      });
    },
    finalize: function(response) {
      this.data.status = response.statusCode;
      this.data.headers = response.headers;
    }
  };
}
Cache.prototype = {
  'get': function(key, callback) {
    return this.cache.get(key, callback);
  },
  'set': function(key, value, ttl, callback) {
    return this.cache.set(key, value, ttl, callback);
  }
};

exports = module.exports = function(options) {
  var cache = new Cache(options);

  return function(request, response, next) {
    if (!cache.isCachableRequest(request)) {
      next();
      return;
    }

    var originalWrite = response.write;
    var originalEnd = response.end;

    var cacheKey = generateKey(request);
    cache.get(cacheKey, function(error, cachedResponse) {
      if (error) {
        console.error(error);
        return;
      }

      if (cachedResponse) {
        sendCachedResponse(response, cachedResponse);
        return;
      }

      var entry = new cache.Entry();

      response.write = function(chunk, encoding) {
        entry.write(chunk, encoding);
        return originalWrite.call(response, chunk, encoding);
      };

      response.end = function(chunk, encoding) {
        entry.write(chunk, encoding);
        var returnValue = originalEnd.call(response, chunk, encoding);

        entry.finalize(response);
        if (entry.cachable) {
          cache.set(cacheKey, entry.data, options.ttlInMilliSeconds);
        }

        return returnValue;
      };
    });
  };
};
<commit_msg>Bind operations to hook response to the Entry class<commit_after>var createCache = require('uber-cache');

var defaultSize              = 100;
var defaultTTLInMilliSeconds = 60 * 1000;

function normalizeOptions(options) {
  options = options || {};

  options.size = options.size || defaultSie;

  var ttlInSeconds = options.ttlInSeconds || 0;
  options.ttlInMilliSeconds = options.ttl ||
                                options.ttlInMilliSeconds ||
                                (ttlInSeconds * 1000) ||
                                defaultTTLInMilliSeconds;

  return options;
}

function generateKey(request) {
  return request.method + '\n' + request.url;
}

function sendCachedResponse(response, cached) {
  response.statusCode = cached.status;
  Object.keys(cached.headers).forEach(function(key) {
    response.setHeader(key, cached.headers[key]);
  });
  cached.body.forEach(function(chunk) {
    response.write(chunk.data, chunk.encoding);
  });
  response.end();
}

function Cache(options) {
  options = normalizeOptions(options);
  var cache = this.cache = createCache({
    size: options.size
  });

  this.isCachableRequest = function(request) {
    return request.method == 'GET';
  };

  this.Entry = function ResponseCacheEntry(key) {
    this.key = key;
    this.data = {
      status:  0,
      headers: {},
      body:    []
    };
  };
  this.Entry.prototype = {
    get cachable() {
      return this.data.status == 200;
    },
    write: function(chunk, encoding) {
      this.data.body.push({
        data:     chunk,
        encoding: encoding
      });
    },
    finalize: function(response) {
      this.data.status = response.statusCode;
      this.data.headers = response.headers;
    },
    hook: function(response) {
      var self = this;

      var originalWrite = response.write;
      response.write = function(chunk, encoding) {
        self.write(chunk, encoding);
        return originalWrite.call(response, chunk, encoding);
      };

      var originalEnd = response.end;
      response.end = function(chunk, encoding) {
        self.write(chunk, encoding);
        var returnValue = originalEnd.call(response, chunk, encoding);

        self.finalize(response);
        if (self.cachable) {
          cache.set(self.key, self.data, options.ttlInMilliSeconds);
        }

        return returnValue;
      };
    }
  };
}
Cache.prototype = {
  'get': function(key, callback) {
    return this.cache.get(key, callback);
  },
  'set': function(key, value, ttl, callback) {
    return this.cache.set(key, value, ttl, callback);
  }
};

exports = module.exports = function(options) {
  var cache = new Cache(options);

  return function(request, response, next) {
    if (!cache.isCachableRequest(request)) {
      next();
      return;
    }

    var cacheKey = generateKey(request);
    cache.get(cacheKey, function(error, cachedResponse) {
      if (error) {
        console.error(error);
        return;
      }

      if (cachedResponse) {
        sendCachedResponse(response, cachedResponse);
      } else {
        var entry = new cache.Entry(cacheKey);
        entry.hook(response);
        next();
      }
    });
  };
};
<|endoftext|>


<commit_before>$(function(){
    var last_input = "";
    var input = "";
    var output = "";
    var menu = ".home";

    setInterval(function(){
        if ($(menu + " .cursor").text() == ""){
            $(menu + " .cursor").text(" ")
        }
        if ($(menu + " .cursor").css("background-color") == "transparent"){
            $(menu + " .cursor").css("background-color", "rgba(0, 0, 0, 0.6)");
        } else {
            $(menu + " .cursor").css("background-color", "rgba(0, 0, 0, 0)");
        };

    }, 500);

    var update_scroller = function() {
        $("#screen").scrollTop($("#screen")[0].scrollHeight);
    };
    var write_it = function(token){
        var cur = $(menu + " .cursor");
        cur.text(token);
        cur.removeClass('cursor');
        if (cur.next().length == 0){
            cur.after(" ")
        }
        cur.next().addClass('cursor');
    };
    var send_it = function(string){
        $.ajax({
            type: "POST",
            url: "/",
            data: {input: input}
        }).done(function(response){
            output = response.output;
            $(".home .output:last").text(output);
        }).fail(function(){
            $(".home .output:last").text("Something Went Wrong");
        }).always(function(){
            $(".cursor").removeClass("cursor");
            $(".home").append("

");
            $(".home").append("

");
        });
    };
    var get_input = function(){
        var input = "";
        $(".home .input:last ins").each(function(){
            input += this.innerHTML;
        });
        return input.trim();
    };

    $("#buttons button").click(function(event) {
        $(".default").show();
        $(".alpha").hide();
        $(".second").hide();
        switch ($(this).attr('id')) {
            case '+':
            case '-':
            case '/':
            case '*':
            case '^':
            case '\u00B2':
                {
                    if (menu == ".home" && get_input() == ""){
                        write_it('Ans');
                    };
                }
            case 'A':
            case '.':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'H':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
            case '0':
            case '?':
            case ':':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '.':
            case "\u03C0":
            case "\u02C9":
            case "\u2148":
            case "\u0022":
            case "\u2423":
            case "\u221A(":
            case 'ln(':
            case 'e^(':
            case 'e':
            case '10^(':
            case 'log(':
            case 'sin(':
            case 'tan(':
            case 'cos(':
            case 'sin^-1(':
            case 'tan^-1(':
            case 'cos^-1(':
            case '(':
            case '(':
            case ')':
            case '{':
            case '}':
            case '[':
            case ']':
            case ',':
            case 'Ans':
                {
                    write_it(this.id);
                }
                break;
            case 'right':
                {
                    if ($(".cursor").next().length == 0){
                        break;
                    } else if ($("p").hasClass("cursor")) {
                        break;
                    };
                    var cur = $(".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)")
                    cur.removeClass("cursor");
                    cur.next().addClass("cursor");
                }
                break;
            case 'left':
                {
                    if ($(".cursor").prev().length == 0){
                        break;
                    } else if ($("p").hasClass("cursor")) {
                        break;
                    };
                    var cur = $(".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)")
                    cur.removeClass("cursor");
                    cur.prev().addClass("cursor");
                }
                break;
            case 'up':
                {
                    var cur = $(".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)");
                    if ($("p").hasClass("cursor")) {
                        var previous = cur.prev().filter(":visible");
                        if ((previous.text() != "") && (previous.is("ul") == false)) {
                            cur.prev().addClass("cursor");
                            cur.removeClass("cursor");
                        } else {
                            $(".submenu:first").addClass("cursor");
                        }
                    } else if ($("li").hasClass("cursor")) {
                        break;
                    }
                    if (menu == ".home") {
                        cur.removeClass("cursor");
                        if($(cur.prevAll()[35]).length == 0){
                            $(".home .input:last ins:first").addClass("cursor");
                            break;
                        };
                        $(cur.prevAll()[35]).addClass("cursor");
                    } else if (menu == ".yequals") {
                        if (cur.parent().prev().length != 0){
                            cur.removeClass("cursor");
                            cur.parent().prev().find("ins:not(ins:first)").first().addClass("cursor");
                        };
                    };
                }
                break;
            case 'down':
                {
                    var cur = $(menu + ".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)")
                    if ($("p").hasClass("cursor")) {
                        if (cur.attr('class').indexOf(cur.next().attr('class')) > -1) {
                            cur.next().addClass("cursor");
                            cur.removeClass("cursor");
                        } else {
                            break;
                        }
                    } else if ($("li").hasClass("cursor")){
                        $(".submenu_options:visible:first").addClass("cursor");
                        cur.removeClass("cursor");
                    }
                    if (menu == ".home") {
                        cur.removeClass("cursor");
                        if($(cur.nextAll()[35]).length == 0){
                            $(".home .input:last ins:last").addClass("cursor");
                            break;
                        };
                        $(cur.nextAll()[35]).addClass("cursor");
                    } else if (menu == ".yequals") {
                        if (cur.parent().next().length != 0){
                            cur.removeClass("cursor");
                            cur.parent().next().find("ins:not(ins:first)").first().addClass("cursor");
                        };
                    };
                }
                break;
            case 'delete':
                {
                    var cur = $(".cursor");
                    if (cur.next().length != 0){
                        cur.next().addClass('cursor');
                        cur.remove()
                    };
                }
                break;
            case 'clear':
                {
                    $(".cursor").removeClass('cursor');
                    menu = ".home";
                    $(".yequals").hide();
                    $(".graph").hide();
                    $(".table").hide();
                    $(".home").show();
                    $(".home").empty();
                    $(".home").append("

");
                    $(".home").append("

");
                    input = "";
                }
                break;
            case 'second_mode':
                {
                    $(".default").hide();
                    $(".second").show();
                }
                break;
            case 'alpha_mode':
                {
                    $(".default").hide();
                    $(".alpha").show();
                }
                break;
            case 'y_equals':
                {
                    $(".cursor").removeClass("cursor");
                    $(".yequals .y_func:first ins:not(.yequals ins:first):first").addClass('cursor');
                    menu = ".yequals";
                    $(".home").hide();
                    $("#all_menus").show();
                    $(".view").hide();
                    $(".yequals").show();
                }
                break;
            case 'ENTER':
                {

                    if (menu == ".table"){
                        update_table();
                    };
                    if ($(menu + " ins").hasClass("cursor")){
                            if (menu == ".yequals"){
                                $("#down").click();
                                break;
                            };
                            input = get_input()
                            if (input == ""){
                                input = last_input;
                            };
                            last_input = input;
                            input = input.split("Ans").join(output);
                            send_it(input);
                            input = "";
                    } else if ($(menu + ".submenu").hasClass("cursor")){
                        var cur_id = "." + $(menu + ".cursor").attr('id');
                        $(".submenu_options").hide();
                        $(cur_id).show();
                    } else if ($("p" + menu).hasClass("cursor")){
                        var cur = $(menu + ".cursor");
                        cur.css("background-color", "rgba(0, 0, 0, 0)")
                        var cur_id = $(menu + ".cursor").attr('id');
                        $("#all_menus").hide();
                        $(".home").show();
                        menu = ".home"
                        console.log(cur_id)
                        write_it(cur_id);
                    };
                }
                break;
            case 'math':
                {
                    menu = ".math_menu";
                    var $math_submenu = $(".submenu:first");
                    $math_submenu.addClass("cursor");
                    $(".math_menu").show();
                    $("#all_menus").show();
                    $(".math_submenu").show();
                    $(".num_submenu").hide();
                    $(".cpx_submenu").hide();
                    $(".prb_submenu").hide();
                    $(".home").hide();
                    $(".yequals").hide();
                }
                break;
            case 'quit':
                {
                    menu = ".home";
                    $("#all_menus").hide();
                    $(".yequals").hide();
                    $(".home").show()
                }
                break;
            case 'graph':
                {
                    $.ajax({
                        type: "POST",
                        url: "/graph/",
                        data: get_equations(),
                    }).done(function(response){
                        output = response.output;
                        $(".graph").html('');
                        $(".home").hide();
                        $(".view").hide();
                        $(".graph").show();
                        input = "";
                        menu = ".home";
                    }).fail(function(){
                        $(".home .output:last").text("ERR: GRAPH SYNTAX");
                        $(".yequals").hide();
                        $(".home").show();
                        menu = ".home";
                    });
                }
                break;
            case 'TABLE':
                {
                    $(".view").hide();
                    $(".table").show();
                    menu = ".table";
                }
                break;
            default: break;
        };
        update_scroller();
        $(".home .input ins:not(.input:last .cursor)").css("background-color", "rgba(0, 0, 0, 0)");
        $("ins:not(.cursor)").css("background-color", "rgba(0, 0, 0, 0)");
    });

    function get_equations(){
        var equations = {};
        for (i=0; i<10; i++){
            var yfn = "";
            var $y = $($(".y_func")[i]);
            $y.find("ins:not(ins:first)").each(function(){
                yfn += this.innerHTML;
            });
            equations[$y.find("ins:first").text()] = yfn;
        };
        return equations;
    }

    $("body").keyup(function(event){
        switch (event.key) {
            case "Enter":
                {
                    $("#ENTER").click();
                }
                break;
            case "Up":
            case "ArrowUp":
                {
                    $("#up").click();
                }
                break;
            case "Down":
            case "ArrowDown":
                {
                    $("#down").click();
                }
                break;
            case "Left":
            case "ArrowLeft":
                {
                    $("#left").click();
                }
                break;
            case "Right":
            case "ArrowRight":
                {
                    $("#right").click();
                }
                break;
            case "Del":
                {
                    $("#delete").click();
                }
                break;
            case "s":
                {
                    document.getElementById("sin(").click();
                }
                break;
            case "c":
                {
                    document.getElementById("cos(").click();
                }
                break;
            case "t":
                {
                    document.getElementById("tan(").click();
                }
                break;
            case "i":
                {
                    document.getElementById("\u2148").click();
                }
                break;
            case " ":
                {
                    document.getElementById("\u2423").click();
                }
                break;
            case "x":
                {
                    document.getElementById("X").click();
                }
                break;
            case "Backspace":
                {
                    $("#left").click();
                    $("#delete").click();
                }
                break;
            default:
                {
                    if (document.getElementById(event.key) != null) {
                        document.getElementById(event.key).click();
                    };
                }
                break;
        };
    });

    $("#all_menus").hide();
});<commit_msg>fixed bug with enter<commit_after>$(function(){
    var last_input = "";
    var input = "";
    var output = "";
    var menu = ".home";

    setInterval(function(){
        if ($(menu + " .cursor").text() == ""){
            $(menu + " .cursor").text(" ")
        }
        if ($(menu + " .cursor").css("background-color") == "transparent"){
            $(menu + " .cursor").css("background-color", "rgba(0, 0, 0, 0.6)");
        } else {
            $(menu + " .cursor").css("background-color", "rgba(0, 0, 0, 0)");
        };

    }, 500);

    var update_scroller = function() {
        $("#screen").scrollTop($("#screen")[0].scrollHeight);
    };
    var write_it = function(token){
        var cur = $(menu + " .cursor");
        cur.text(token);
        cur.removeClass('cursor');
        if (cur.next().length == 0){
            cur.after(" ")
        }
        cur.next().addClass('cursor');
    };
    var send_it = function(string){
        $.ajax({
            type: "POST",
            url: "/",
            data: {input: input}
        }).done(function(response){
            output = response.output;
            $(".home .output:last").text(output);
        }).fail(function(){
            $(".home .output:last").text("Something Went Wrong");
        }).always(function(){
            $(".cursor").removeClass("cursor");
            $(".home").append("

");
            $(".home").append("

");
        });
    };
    var get_input = function(){
        var input = "";
        $(".home .input:last ins").each(function(){
            input += this.innerHTML;
        });
        return input.trim();
    };

    $("#buttons button").click(function(event) {
        $(".default").show();
        $(".alpha").hide();
        $(".second").hide();
        switch ($(this).attr('id')) {
            case '+':
            case '-':
            case '/':
            case '*':
            case '^':
            case '\u00B2':
                {
                    if (menu == ".home" && get_input() == ""){
                        write_it('Ans');
                    };
                }
            case 'A':
            case '.':
            case 'B':
            case 'C':
            case 'D':
            case 'E':
            case 'F':
            case 'G':
            case 'H':
            case 'I':
            case 'J':
            case 'K':
            case 'L':
            case 'M':
            case 'N':
            case 'O':
            case 'P':
            case 'Q':
            case 'R':
            case 'S':
            case 'T':
            case 'U':
            case 'V':
            case 'W':
            case 'X':
            case 'Y':
            case 'Z':
            case '0':
            case '?':
            case ':':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '.':
            case "\u03C0":
            case "\u02C9":
            case "\u2148":
            case "\u0022":
            case "\u2423":
            case "\u221A(":
            case 'ln(':
            case 'e^(':
            case 'e':
            case '10^(':
            case 'log(':
            case 'sin(':
            case 'tan(':
            case 'cos(':
            case 'sin^-1(':
            case 'tan^-1(':
            case 'cos^-1(':
            case '(':
            case '(':
            case ')':
            case '{':
            case '}':
            case '[':
            case ']':
            case ',':
            case 'Ans':
                {
                    write_it(this.id);
                }
                break;
            case 'right':
                {
                    if ($(".cursor").next().length == 0){
                        break;
                    } else if ($("p").hasClass("cursor")) {
                        break;
                    };
                    var cur = $(".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)")
                    cur.removeClass("cursor");
                    cur.next().addClass("cursor");
                }
                break;
            case 'left':
                {
                    if ($(".cursor").prev().length == 0){
                        break;
                    } else if ($("p").hasClass("cursor")) {
                        break;
                    };
                    var cur = $(".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)")
                    cur.removeClass("cursor");
                    cur.prev().addClass("cursor");
                }
                break;
            case 'up':
                {
                    var cur = $(".cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)");
                    if ($("p").hasClass("cursor")) {
                        var previous = cur.prev().filter(":visible");
                        if ((previous.text() != "") && (previous.is("ul") == false)) {
                            cur.prev().addClass("cursor");
                            cur.removeClass("cursor");
                        } else {
                            $(".submenu:first").addClass("cursor");
                        }
                    } else if ($("li").hasClass("cursor")) {
                        break;
                    }
                    if (menu == ".home") {
                        cur.removeClass("cursor");
                        if($(cur.prevAll()[35]).length == 0){
                            $(".home .input:last ins:first").addClass("cursor");
                            break;
                        };
                        $(cur.prevAll()[35]).addClass("cursor");
                    } else if (menu == ".yequals") {
                        if (cur.parent().prev().length != 0){
                            cur.removeClass("cursor");
                            cur.parent().prev().find("ins:not(ins:first)").first().addClass("cursor");
                        };
                    };
                }
                break;
            case 'down':
                {
                    var cur = $(menu + " .cursor");
                    cur.css("background-color", "rgba(0, 0, 0, 0)")
                    if ($("p").hasClass("cursor")) {
                        if (cur.attr('class').indexOf(cur.next().attr('class')) > -1) {
                            cur.next().addClass("cursor");
                            cur.removeClass("cursor");
                        } else {
                            break;
                        }
                    } else if ($("li").hasClass("cursor")){
                        $(".submenu_options:visible:first").addClass("cursor");
                        cur.removeClass("cursor");
                    }
                    if (menu == ".home") {
                        cur.removeClass("cursor");
                        if($(cur.nextAll()[35]).length == 0){
                            $(".home .input:last ins:last").addClass("cursor");
                            break;
                        };
                        $(cur.nextAll()[35]).addClass("cursor");
                    } else if (menu == ".yequals") {
                        if (cur.parent().next().length != 0){
                            cur.removeClass("cursor");
                            cur.parent().next().find("ins:not(ins:first)").first().addClass("cursor");
                        };
                    };
                }
                break;
            case 'delete':
                {
                    var cur = $(".cursor");
                    if (cur.next().length != 0){
                        cur.next().addClass('cursor');
                        cur.remove()
                    };
                }
                break;
            case 'clear':
                {
                    $(".cursor").removeClass('cursor');
                    menu = ".home";
                    $(".yequals").hide();
                    $(".graph").hide();
                    $(".table").hide();
                    $(".home").show();
                    $(".home").empty();
                    $(".home").append("

");
                    $(".home").append("

");
                    input = "";
                }
                break;
            case 'second_mode':
                {
                    $(".default").hide();
                    $(".second").show();
                }
                break;
            case 'alpha_mode':
                {
                    $(".default").hide();
                    $(".alpha").show();
                }
                break;
            case 'y_equals':
                {
                    $(".cursor").removeClass("cursor");
                    $(".yequals .y_func:first ins:not(.yequals ins:first):first").addClass('cursor');
                    menu = ".yequals";
                    $(".home").hide();
                    $("#all_menus").show();
                    $(".view").hide();
                    $(".yequals").show();
                }
                break;
            case 'ENTER':
                {

                    if (menu == ".table"){
                        update_table();
                    };
                    if ($(menu + " ins").hasClass("cursor")){
                            if (menu == ".yequals"){
                                $("#down").click();
                                break;
                            };
                            input = get_input()
                            if (input == ""){
                                input = last_input;
                            };
                            last_input = input;
                            input = input.split("Ans").join(output);
                            send_it(input);
                            input = "";
                    } else if ($(menu + ".submenu").hasClass("cursor")){
                        var cur_id = "." + $(menu + ".cursor").attr('id');
                        $(".submenu_options").hide();
                        $(cur_id).show();
                    } else if ($("p" + menu).hasClass("cursor")){
                        var cur = $(menu + ".cursor");
                        cur.css("background-color", "rgba(0, 0, 0, 0)")
                        var cur_id = $(menu + ".cursor").attr('id');
                        $("#all_menus").hide();
                        $(".home").show();
                        menu = ".home"
                        console.log(cur_id)
                        write_it(cur_id);
                    };
                }
                break;
            case 'math':
                {
                    menu = ".math_menu";
                    var $math_submenu = $(".submenu:first");
                    $math_submenu.addClass("cursor");
                    $(".math_menu").show();
                    $("#all_menus").show();
                    $(".math_submenu").show();
                    $(".num_submenu").hide();
                    $(".cpx_submenu").hide();
                    $(".prb_submenu").hide();
                    $(".home").hide();
                    $(".yequals").hide();
                }
                break;
            case 'quit':
                {
                    menu = ".home";
                    $("#all_menus").hide();
                    $(".yequals").hide();
                    $(".home").show()
                }
                break;
            case 'graph':
                {
                    $.ajax({
                        type: "POST",
                        url: "/graph/",
                        data: get_equations(),
                    }).done(function(response){
                        output = response.output;
                        $(".graph").html('');
                        $(".home").hide();
                        $(".view").hide();
                        $(".graph").show();
                        input = "";
                        menu = ".home";
                    }).fail(function(){
                        $(".home .output:last").text("ERR: GRAPH SYNTAX");
                        $(".yequals").hide();
                        $(".home").show();
                        menu = ".home";
                    });
                }
                break;
            case 'TABLE':
                {
                    $(".view").hide();
                    $(".table").show();
                    menu = ".table";
                }
                break;
            default: break;
        };
        update_scroller();
        $(".home .input ins:not(.input:last .cursor)").css("background-color", "rgba(0, 0, 0, 0)");
        $("ins:not(.cursor)").css("background-color", "rgba(0, 0, 0, 0)");
        $("button").blur();
    });

    function get_equations(){
        var equations = {};
        for (i=0; i<10; i++){
            var yfn = "";
            var $y = $($(".y_func")[i]);
            $y.find("ins:not(ins:first)").each(function(){
                yfn += this.innerHTML;
            });
            equations[$y.find("ins:first").text()] = yfn;
        };
        return equations;
    }

    $("body").keyup(function(event){
        switch (event.key) {
            case "Enter":
                {
                    $("#ENTER").click();
                }
                break;
            case "Up":
            case "ArrowUp":
                {
                    $("#up").click();
                }
                break;
            case "Down":
            case "ArrowDown":
                {
                    $("#down").click();
                }
                break;
            case "Left":
            case "ArrowLeft":
                {
                    $("#left").click();
                }
                break;
            case "Right":
            case "ArrowRight":
                {
                    $("#right").click();
                }
                break;
            case "Del":
                {
                    $("#delete").click();
                }
                break;
            case "s":
                {
                    document.getElementById("sin(").click();
                }
                break;
            case "c":
                {
                    document.getElementById("cos(").click();
                }
                break;
            case "t":
                {
                    document.getElementById("tan(").click();
                }
                break;
            case "i":
                {
                    document.getElementById("\u2148").click();
                }
                break;
            case " ":
                {
                    document.getElementById("\u2423").click();
                }
                break;
            case "x":
                {
                    document.getElementById("X").click();
                }
                break;
            case "Backspace":
                {
                    $("#left").click();
                    $("#delete").click();
                }
                break;
            default:
                {
                    if (document.getElementById(event.key) != null) {
                        document.getElementById(event.key).click();
                    };
                }
                break;
        };
    });

    $("#all_menus").hide();
});<|endoftext|>


<commit_before>var camera, scene, renderer, width, height, clock, orbitControl, fpsStats, intersectedObject;
var objects = [];
var fans = [];
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

var hoverFanColor = 0xD88080;
var editFanColor = 0x00FF00;
var normalFanColor = 0xB20000;

init();
animate();

function init() {
	//Loads physijs files, creates scene etc.

	Physijs.scripts.worker = '/js/external/physijs_worker.js';
	Physijs.scripts.ammo = '/js/external/ammo.js';

	window.addEventListener( 'resize', onWindowResize, false);

	width = document.getElementById('simulationContainer').offsetWidth;
	height = document.getElementById('simulationContainer').offsetHeight;

	fpsStats = new Stats();
	fpsStats.setMode(0);

	fpsStats.domElement.style.position = 'absolute';
	fpsStats.domElement.style.right = '0px';
	fpsStats.domElement.style.top = '0px';

	document.getElementById("simulationContainer").appendChild(fpsStats.domElement);

	renderer = new THREE.WebGLRenderer ( { antialias: true});
	renderer.setSize(width, height);
	document.getElementById("simulationContainer").appendChild(renderer.domElement);

	scene = new Physijs.Scene;

	scene.setGravity(new THREE.Vector3( 0, 12, 0));

	createObjects(50);

	createCase();

	createFan("intake", new THREE.Vector3(0, 100, -450));
	createFan("exhaust", new THREE.Vector3(0, 700, 450));

	camera = new THREE.PerspectiveCamera(45, width/height, 0.1, 10000);

	camera.position.x = 800;
	camera.position.y = 800;
	camera.position.z = -800;

	scene.add(camera);

	orbitControl = new THREE.OrbitControls(camera, document.getElementById('simulationContainer'));
	//orbitControl.enablePan = false;
	orbitControl.constraint.maxDistance = 1000;
	orbitControl.constraint.maxDistance = 2200;
	clock = new THREE.Clock();

	var skyboxGeometry = new THREE.CubeGeometry(9000, 9000, 9000);
	var skyboxMaterial = new THREE.MeshBasicMaterial({ color: 0x262B30, side: THREE.BackSide });
	var skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial);

	scene.add(skybox);

	var topLight = new THREE.DirectionalLight(0xffffff, 1);
	topLight.position.set(0, 1, 0);

	var bottomLight = new THREE.DirectionalLight(0xffffff, 0.5);
	bottomLight.position.set(0, -1, 0);

	scene.add(topLight);
	scene.add(bottomLight);

	var pointLightA = new THREE.PointLight(0xffffff, 0.75);
	pointLightA.position.set(0, -100, -400);

	var pointLightB = new THREE.PointLight(0xffffff, 0.75);
	pointLightB.position.set(-600, 0, -800);

	scene.add(pointLightA);
	scene.add(pointLightB);

	scene.add(new THREE.AxisHelper(200));

	renderer.domElement.addEventListener( 'mousemove', handleMouseMove, false );
	renderer.domElement.addEventListener( 'mousedown', handleMouseClick, false );
}

function animate() {
	//Animates/simulates scene

	fpsStats.begin();

	scene.simulate(); //Run physics simulation

	requestAnimationFrame(animate);
	var delta = clock.getDelta();
	orbitControl.update(delta);

	renderer.render(scene, camera);

	fpsStats.end();
}

function createObjects(numToCreate) {
	//Creates numToCreate random physics objects to be used in the simulation
	for (var i=0; i < numToCreate; i++) {

		var object;

		var randNum = Math.random();
		var matColor;

		if (randNum < 0.34) {
			matColor = 0xD9216A;
		} else if (randNum > 0.34 && randNum < 0.67) {
			matColor = 0x18ABDB;
		} else {
			matColor = 0x18DB3F;
		}

		var sphereGeometry = new THREE.SphereGeometry(10, 16, 16);

		var sphereMaterial = Physijs.createMaterial(
          new THREE.MeshLambertMaterial({
            color: matColor
          }),
	      0.3, // friction
	      0.9 // restitution
        );

		object = new Physijs.SphereMesh(sphereGeometry, sphereMaterial);

		object.position.set((( Math.random() - 0.5 ) * 280), (( Math.random() - 0.25) * 200), (( Math.random() - 3.5 ) * 200));

		object.addEventListener( 'collision', handleCollision );

		scene.add(object);

		objects.push(object);
	}
}

function createCase() {
	//Creates a 3D model of a computer case

	var caseGroup = new THREE.Object3D(); //Empty container to store each case plane in

	var caseMaterial = new THREE.MeshLambertMaterial({
		color: 0x5F6E7D
	})

	var transparentMaterial = new THREE.MeshBasicMaterial({
	    opacity: 0.2,
	    color: 0x5F6E7D,
	    transparent: true,
	    side: THREE.DoubleSide
	});

	var componentMaterial = new THREE.MeshLambertMaterial({
		color: 0xD5D3D0
	})

	var caseWidth = 350;
	var caseHeight = 800;
	var caseLength = 900;
	var caseThickness = 4;
	var fanHoleSize = 200;

	var caseBottomGeometry = new THREE.CubeGeometry(caseWidth, caseThickness, caseLength);
	var caseTopGeometry = new THREE.CubeGeometry(caseWidth, caseThickness, caseLength);
	var caseVisibleSideGeometry = new THREE.CubeGeometry(caseThickness, caseHeight, caseLength);
	var caseInvisibleSideGeometry = new THREE.CubeGeometry(caseThickness, caseHeight, caseLength);
	var caseBackGeometry = new THREE.CubeGeometry(caseWidth, caseHeight - fanHoleSize, caseThickness);
	var caseFrontGeometry = new THREE.CubeGeometry(caseWidth, caseHeight - fanHoleSize, caseThickness);

	var gpuGeometry = new THREE.CubeGeometry(caseWidth - 100, 25, 375);

	var caseBottomPlane = new Physijs.BoxMesh(caseBottomGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseTopPlane = new Physijs.BoxMesh(caseTopGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseVisibleSidePlane = new Physijs.BoxMesh(caseVisibleSideGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseInvisibleSidePlane = new Physijs.BoxMesh(caseInvisibleSideGeometry, transparentMaterial, 0); //Gravity, 0 = weightless
	var caseBackPlane = new Physijs.BoxMesh(caseBackGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseFrontPlane = new Physijs.BoxMesh(caseFrontGeometry, caseMaterial, 0); //Gravity, 0 = weightless

	var gpuPlane = new Physijs.BoxMesh(gpuGeometry, componentMaterial, 0); //Gravity, 0 = weightless

	caseBottomPlane.position.set(0, 0, 0);

	caseTopPlane.position.set(0, caseHeight, 0);

	caseVisibleSidePlane.position.set(-caseWidth/2, caseHeight/2, 0);

	caseInvisibleSidePlane.position.set(caseWidth/2, caseHeight/2, 0);

	caseBackPlane.position.set(0, caseHeight/2 - (fanHoleSize/2), caseLength/2);

	caseFrontPlane.position.set(0, caseHeight/2 + (fanHoleSize/2), -caseLength/2);

	gpuPlane.position.set(-45, caseHeight/2 - 100, 200);

	scene.add(caseBottomPlane);
	scene.add(caseTopPlane);
	scene.add(caseVisibleSidePlane);
	scene.add(caseInvisibleSidePlane);
	scene.add(caseBackPlane);
	scene.add(caseFrontPlane);
	scene.add(gpuPlane);
}

function createFan(paramMode, position) {
	//------------------Testing adding fans--------------------//

	//Test out apply force
	var fanMaterial = new THREE.MeshLambertMaterial({
		opacity: 0.4,
	    color: normalFanColor,
	    transparent: true,
	    side: THREE.DoubleSide
	})

	var fanGeometry = new THREE.CubeGeometry(300, 200, 400);
	var fanMesh = new Physijs.BoxMesh(fanGeometry, fanMaterial, 0); //Gravity, 0 = weightless

	fanMesh.position.set(position.x, position.y, position.z);
	fanMesh._physijs.collision_flags = 4;	//Allows collision detection, but doesn't affect velocity etc. of object colliding with it
	fanMesh.mode = paramMode;
	fanMesh.editing = false;
	fans.push(fanMesh);

	scene.add(fanMesh);

	//Add component controller for this fan
	document.getElementById('tabbedPaneContainer').insertAdjacentHTML('beforeend', '<component-Settings id="' + fanMesh.id +'"></component-Settings>');

	//-------------------------------------------------------//
}

function handleCollision(collided_with, linearVelocity, angularVelocity) {
	//Event gets called when physics objects (spheres) collide with another object
	for (var i = 0; i < fans.length; i++) {
		if (collided_with.id === fans[i].id) {
			//Collided with fan
			if ( fans[i].mode === "intake" ) {
				var forceVector = new THREE.Vector3(0, 500000, 1000000); 	//Force/Impulse is quantified by units pushing in a 3 axis directions. NOTE: A really big number is needed to produce any noticeable affect
			} else if (fans[i].mode === "exhaust" ) {
				var forceVector = new THREE.Vector3(0, 0, 1000000); 	//Force/Impulse is quantified by units pushing in a 3 axis directions. NOTE: A really big number is needed to produce any noticeable affect
			}
			this.applyCentralImpulse(forceVector);
		}
	}
}

function handleMouseMove(event) {
	//When a user hovers on a fan change fan color to hover state only if we are NOT editing that fan

	var touchFan = detectTouchingFan(event);

	if (touchFan) {
		//Only change to hover color when we are NOT editing the current fan
		if (touchFan.object.editing == false) {
			touchFan.object.material.color.setHex(hoverFanColor);
		}
	} else {
		for (var i = 0; i < fans.length; i++) {
			//Only set fans that we are NOT editing to normal fan color
			if (fans[i].editing == false) {
				fans[i].material.color.setHex(normalFanColor);
			}
		}
	}
}

function handleMouseClick(event) {
	//When a user clicks on a fan, open the component control panel section and change fan color

	var touchFan = detectTouchingFan(event);

	//For peace of mind, reset all fans to not editing when we click
	for (var i = 0; i < fans.length; i++) {
		fans[i].editing = false;
		fans[i].material.color.setHex(normalFanColor);
		document.getElementById(fans[i].id).style.color = "black";
	}

	if (touchFan) {
		touchFan.object.material.color.setHex(editFanColor);
		touchFan.object.editing = true;
		document.getElementById(touchFan.object.id).style.color = "red";	//Placeholder. When user clicks on the fan it's component section will display
	}
}

function detectTouchingFan(event) {
	//Returns a Fan object if the mouse is touching one, else nothing is returned

	//Have to normalise these coords so that they are between -1 and 1
	mouse.x = ( ( (event.clientX - document.getElementById('tabbedPaneContainer').offsetWidth) / width ) * 2 - 1); //Have to minus the tabbedPaneContainer width because oftherwise it would be included in the normalising to get X in terms of the canvas
	mouse.y = - ( event.clientY / height ) * 2 + 1;

	raycaster.setFromCamera( mouse, camera );

	var intersects = raycaster.intersectObjects( fans, true );

	if ( intersects.length > 0 ) {
		return intersectedObject = intersects[0];
	} else {
		return null;
	}
}

function restartSim() {
	//Removes all existing physics objects from the scene, then generates new physics objects
	for (var i=0; i < objects.length; i++) {
		 if(objects[i] != null) {
			scene.remove(objects[i]);
		}
	}
	//animate();
	createObjects(50);
}

function onWindowResize(){
	//Dynamically resizes renderer and camera when window is resized

	width = document.getElementById('simulationContainer').offsetWidth;
	height = document.getElementById('simulationContainer').offsetHeight;

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize( width, height );

}

//TODO:
// - Maybe use an eventListener for when $scope changes instead of checking every update frame -> better for perfomance<commit_msg>Fixed maximum zoom in issue of OrbitControl<commit_after>var camera, scene, renderer, width, height, clock, orbitControl, fpsStats, intersectedObject;
var objects = [];
var fans = [];
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

var hoverFanColor = 0xD88080;
var editFanColor = 0x00FF00;
var normalFanColor = 0xB20000;

init();
animate();

function init() {
	//Loads physijs files, creates scene etc.

	Physijs.scripts.worker = '/js/external/physijs_worker.js';
	Physijs.scripts.ammo = '/js/external/ammo.js';

	window.addEventListener( 'resize', onWindowResize, false);

	width = document.getElementById('simulationContainer').offsetWidth;
	height = document.getElementById('simulationContainer').offsetHeight;

	fpsStats = new Stats();
	fpsStats.setMode(0);

	fpsStats.domElement.style.position = 'absolute';
	fpsStats.domElement.style.right = '0px';
	fpsStats.domElement.style.top = '0px';

	document.getElementById("simulationContainer").appendChild(fpsStats.domElement);

	renderer = new THREE.WebGLRenderer ( { antialias: true});
	renderer.setSize(width, height);
	document.getElementById("simulationContainer").appendChild(renderer.domElement);

	scene = new Physijs.Scene;

	scene.setGravity(new THREE.Vector3( 0, 12, 0));

	createObjects(50);

	createCase();

	createFan("intake", new THREE.Vector3(0, 100, -450));
	createFan("exhaust", new THREE.Vector3(0, 700, 450));

	camera = new THREE.PerspectiveCamera(45, width/height, 0.1, 10000);

	camera.position.x = 800;
	camera.position.y = 800;
	camera.position.z = -800;

	scene.add(camera);

	orbitControl = new THREE.OrbitControls(camera, document.getElementById('simulationContainer'));
	//orbitControl.enablePan = false;
	orbitControl.constraint.minDistance = 600;
	orbitControl.constraint.maxDistance = 2200;
	clock = new THREE.Clock();

	var skyboxGeometry = new THREE.CubeGeometry(9000, 9000, 9000);
	var skyboxMaterial = new THREE.MeshBasicMaterial({ color: 0x262B30, side: THREE.BackSide });
	var skybox = new THREE.Mesh(skyboxGeometry, skyboxMaterial);

	scene.add(skybox);

	var topLight = new THREE.DirectionalLight(0xffffff, 1);
	topLight.position.set(0, 1, 0);

	var bottomLight = new THREE.DirectionalLight(0xffffff, 0.5);
	bottomLight.position.set(0, -1, 0);

	scene.add(topLight);
	scene.add(bottomLight);

	var pointLightA = new THREE.PointLight(0xffffff, 0.75);
	pointLightA.position.set(0, -100, -400);

	var pointLightB = new THREE.PointLight(0xffffff, 0.75);
	pointLightB.position.set(-600, 0, -800);

	scene.add(pointLightA);
	scene.add(pointLightB);

	scene.add(new THREE.AxisHelper(200));

	renderer.domElement.addEventListener( 'mousemove', handleMouseMove, false );
	renderer.domElement.addEventListener( 'mousedown', handleMouseClick, false );
}

function animate() {
	//Animates/simulates scene

	fpsStats.begin();

	scene.simulate(); //Run physics simulation

	requestAnimationFrame(animate);
	var delta = clock.getDelta();
	orbitControl.update(delta);

	renderer.render(scene, camera);

	fpsStats.end();
}

function createObjects(numToCreate) {
	//Creates numToCreate random physics objects to be used in the simulation
	for (var i=0; i < numToCreate; i++) {

		var object;

		var randNum = Math.random();
		var matColor;

		if (randNum < 0.34) {
			matColor = 0xD9216A;
		} else if (randNum > 0.34 && randNum < 0.67) {
			matColor = 0x18ABDB;
		} else {
			matColor = 0x18DB3F;
		}

		var sphereGeometry = new THREE.SphereGeometry(10, 16, 16);

		var sphereMaterial = Physijs.createMaterial(
          new THREE.MeshLambertMaterial({
            color: matColor
          }),
	      0.3, // friction
	      0.9 // restitution
        );

		object = new Physijs.SphereMesh(sphereGeometry, sphereMaterial);

		object.position.set((( Math.random() - 0.5 ) * 280), (( Math.random() - 0.25) * 200), (( Math.random() - 3.5 ) * 200));

		object.addEventListener( 'collision', handleCollision );

		scene.add(object);

		objects.push(object);
	}
}

function createCase() {
	//Creates a 3D model of a computer case

	var caseGroup = new THREE.Object3D(); //Empty container to store each case plane in

	var caseMaterial = new THREE.MeshLambertMaterial({
		color: 0x5F6E7D
	})

	var transparentMaterial = new THREE.MeshBasicMaterial({
	    opacity: 0.2,
	    color: 0x5F6E7D,
	    transparent: true,
	    side: THREE.DoubleSide
	});

	var componentMaterial = new THREE.MeshLambertMaterial({
		color: 0xD5D3D0
	})

	var caseWidth = 350;
	var caseHeight = 800;
	var caseLength = 900;
	var caseThickness = 4;
	var fanHoleSize = 200;

	var caseBottomGeometry = new THREE.CubeGeometry(caseWidth, caseThickness, caseLength);
	var caseTopGeometry = new THREE.CubeGeometry(caseWidth, caseThickness, caseLength);
	var caseVisibleSideGeometry = new THREE.CubeGeometry(caseThickness, caseHeight, caseLength);
	var caseInvisibleSideGeometry = new THREE.CubeGeometry(caseThickness, caseHeight, caseLength);
	var caseBackGeometry = new THREE.CubeGeometry(caseWidth, caseHeight - fanHoleSize, caseThickness);
	var caseFrontGeometry = new THREE.CubeGeometry(caseWidth, caseHeight - fanHoleSize, caseThickness);

	var gpuGeometry = new THREE.CubeGeometry(caseWidth - 100, 25, 375);

	var caseBottomPlane = new Physijs.BoxMesh(caseBottomGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseTopPlane = new Physijs.BoxMesh(caseTopGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseVisibleSidePlane = new Physijs.BoxMesh(caseVisibleSideGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseInvisibleSidePlane = new Physijs.BoxMesh(caseInvisibleSideGeometry, transparentMaterial, 0); //Gravity, 0 = weightless
	var caseBackPlane = new Physijs.BoxMesh(caseBackGeometry, caseMaterial, 0); //Gravity, 0 = weightless
	var caseFrontPlane = new Physijs.BoxMesh(caseFrontGeometry, caseMaterial, 0); //Gravity, 0 = weightless

	var gpuPlane = new Physijs.BoxMesh(gpuGeometry, componentMaterial, 0); //Gravity, 0 = weightless

	caseBottomPlane.position.set(0, 0, 0);

	caseTopPlane.position.set(0, caseHeight, 0);

	caseVisibleSidePlane.position.set(-caseWidth/2, caseHeight/2, 0);

	caseInvisibleSidePlane.position.set(caseWidth/2, caseHeight/2, 0);

	caseBackPlane.position.set(0, caseHeight/2 - (fanHoleSize/2), caseLength/2);

	caseFrontPlane.position.set(0, caseHeight/2 + (fanHoleSize/2), -caseLength/2);

	gpuPlane.position.set(-45, caseHeight/2 - 100, 200);

	scene.add(caseBottomPlane);
	scene.add(caseTopPlane);
	scene.add(caseVisibleSidePlane);
	scene.add(caseInvisibleSidePlane);
	scene.add(caseBackPlane);
	scene.add(caseFrontPlane);
	scene.add(gpuPlane);
}

function createFan(paramMode, position) {
	//------------------Testing adding fans--------------------//

	//Test out apply force
	var fanMaterial = new THREE.MeshLambertMaterial({
		opacity: 0.4,
	    color: normalFanColor,
	    transparent: true,
	    side: THREE.DoubleSide
	})

	var fanGeometry = new THREE.CubeGeometry(300, 200, 400);
	var fanMesh = new Physijs.BoxMesh(fanGeometry, fanMaterial, 0); //Gravity, 0 = weightless

	fanMesh.position.set(position.x, position.y, position.z);
	fanMesh._physijs.collision_flags = 4;	//Allows collision detection, but doesn't affect velocity etc. of object colliding with it
	fanMesh.mode = paramMode;
	fanMesh.editing = false;
	fans.push(fanMesh);

	scene.add(fanMesh);

	//Add component controller for this fan
	document.getElementById('tabbedPaneContainer').insertAdjacentHTML('beforeend', '<component-Settings id="' + fanMesh.id +'"></component-Settings>');

	//-------------------------------------------------------//
}

function handleCollision(collided_with, linearVelocity, angularVelocity) {
	//Event gets called when physics objects (spheres) collide with another object
	for (var i = 0; i < fans.length; i++) {
		if (collided_with.id === fans[i].id) {
			//Collided with fan
			if ( fans[i].mode === "intake" ) {
				var forceVector = new THREE.Vector3(0, 500000, 1000000); 	//Force/Impulse is quantified by units pushing in a 3 axis directions. NOTE: A really big number is needed to produce any noticeable affect
			} else if (fans[i].mode === "exhaust" ) {
				var forceVector = new THREE.Vector3(0, 0, 1000000); 	//Force/Impulse is quantified by units pushing in a 3 axis directions. NOTE: A really big number is needed to produce any noticeable affect
			}
			this.applyCentralImpulse(forceVector);
		}
	}
}

function handleMouseMove(event) {
	//When a user hovers on a fan change fan color to hover state only if we are NOT editing that fan

	var touchFan = detectTouchingFan(event);

	if (touchFan) {
		//Only change to hover color when we are NOT editing the current fan
		if (touchFan.object.editing == false) {
			touchFan.object.material.color.setHex(hoverFanColor);
		}
	} else {
		for (var i = 0; i < fans.length; i++) {
			//Only set fans that we are NOT editing to normal fan color
			if (fans[i].editing == false) {
				fans[i].material.color.setHex(normalFanColor);
			}
		}
	}
}

function handleMouseClick(event) {
	//When a user clicks on a fan, open the component control panel section and change fan color

	var touchFan = detectTouchingFan(event);

	//For peace of mind, reset all fans to not editing when we click
	for (var i = 0; i < fans.length; i++) {
		fans[i].editing = false;
		fans[i].material.color.setHex(normalFanColor);
		document.getElementById(fans[i].id).style.color = "black";
	}

	if (touchFan) {
		touchFan.object.material.color.setHex(editFanColor);
		touchFan.object.editing = true;
		document.getElementById(touchFan.object.id).style.color = "red";	//Placeholder. When user clicks on the fan it's component section will display
	}
}

function detectTouchingFan(event) {
	//Returns a Fan object if the mouse is touching one, else nothing is returned

	//Have to normalise these coords so that they are between -1 and 1
	mouse.x = ( ( (event.clientX - document.getElementById('tabbedPaneContainer').offsetWidth) / width ) * 2 - 1); //Have to minus the tabbedPaneContainer width because oftherwise it would be included in the normalising to get X in terms of the canvas
	mouse.y = - ( event.clientY / height ) * 2 + 1;

	raycaster.setFromCamera( mouse, camera );

	var intersects = raycaster.intersectObjects( fans, true );

	if ( intersects.length > 0 ) {
		return intersectedObject = intersects[0];
	} else {
		return null;
	}
}

function restartSim() {
	//Removes all existing physics objects from the scene, then generates new physics objects
	for (var i=0; i < objects.length; i++) {
		 if(objects[i] != null) {
			scene.remove(objects[i]);
		}
	}
	//animate();
	createObjects(50);
}

function onWindowResize(){
	//Dynamically resizes renderer and camera when window is resized

	width = document.getElementById('simulationContainer').offsetWidth;
	height = document.getElementById('simulationContainer').offsetHeight;

    camera.aspect = width / height;
    camera.updateProjectionMatrix();

    renderer.setSize( width, height );

}

//TODO:
// - Maybe use an eventListener for when $scope changes instead of checking every update frame -> better for perfomance<|endoftext|>


<commit_before>'use strict'
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode')
const spawn = require('child_process').spawn
const fs = require('fs')
const path = require('path')

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed

function activate (context) {
  const outputChannel = vscode.window.createOutputChannel(vscode.workspace.getConfiguration('miramac.node').outputWindowName)
  let terminals = []
  let process
  let runningStatus = null

  // executes JS code with node
  let run = vscode.commands.registerCommand('extension.miramac.node.exec', function () {
    const config = vscode.workspace.getConfiguration('miramac.node')
    // test for running process
    if (runningStatus) {
      vscode.window.showErrorMessage('Process is already running!')
      return
    }

    let editor = vscode.window.activeTextEditor
    if (!editor) {
      return // No open text editor
    }
    if (editor._documentData._languageId === 'Log') {
      editor = vscode.window.visibleTextEditors[0]
    }

    let dirName = path.dirname(editor.document.fileName)
    if (!dirName || dirName === '' || dirName === '.') {
      vscode.window.showWarningMessage('Unknown working directory! Try to save the file before running.')
      return
    }

    let selection = editor.selection
    let text = editor.document.getText(selection)
    if ((text === '' && config.executeFileOrSelection === 'both') || config.executeFileOrSelection === 'file') {
      text = editor.document.getText()
    }
    if (config.includeCode) {
      text = config.includeCode.replace('${execPath}', dirName) + ';\n' + text // eslint-disable-line no-template-curly-in-string
    }

    // get config params object
    let options = {}
    if (config.env) {
      options.env = config.env
    }
    // get cwd from config, replace placeholder and add it to the params object
    options.cwd = (typeof config.cwd === 'string') ? config.cwd.replace('${execPath}', dirName) : dirName  // eslint-disable-line no-template-curly-in-string

    // set tempfile
    let tmpFile = path.join(dirName, `node_${require('crypto').createHash('sha1').update(Math.random().toString()).digest('hex').substr(0, 13)}.tmp`)
    // wirte code in temp file and exec the file with node
    fs.writeFileSync(tmpFile, text)

    let args = [tmpFile]
    if (Array.isArray(config.args) && config.args.length > 0) {
      args = args.concat(config.args)
    }

    let startTime = new Date()
    let nodeBin = (typeof config.nodeBin === 'string') ? config.nodeBin : 'node'

    if (!config.terminalMode) {
      runningStatus = vscode.window.setStatusBarMessage('Running...')
      outputChannel.show(vscode.window.visibleTextEditors.length + 1)
      if (config.clearOutput) {
        outputChannel.clear()
      }
      if (config.showInfo) {
        outputChannel.appendLine('Info: Start process (' + startTime.toLocaleTimeString() + ')')
      }
      // spawn new node.js process
      process = spawn(nodeBin, args, options)

      // process event handlers
      process.stdout.on('data', function (data) {
        if (!config.showStdout) return
        outputChannel.append(data.toString())
      })
      process.stderr.on('data', function (data) {
        if (!config.showStderr) return
        outputChannel.appendLine('Error: ')
        outputChannel.appendLine(data.toString())
      })
      process.on('close', function () {
        fs.unlink(tmpFile)
        if (runningStatus) {
          if (config.showInfo) {
            outputChannel.appendLine('Info: End process (' + new Date().toLocaleTimeString() + ')')
          }
          runningStatus.dispose()
          runningStatus = null
        }
      })
      process.on('error', function (err) {
        fs.unlink(tmpFile)
        outputChannel.appendLine('Process error: ')
        outputChannel.appendLine(err.toString())
        if (config.showInfo) {
          outputChannel.appendLine('Info: End process with errors (' + new Date().toLocaleTimeString() + ')')
        }
        if (runningStatus) {
          runningStatus.dispose()
          runningStatus = null
        }
      })
    } else { // Terminal Mode
      let terminalIndex = terminals.length
      let terminal = vscode.window.createTerminal(`Node.js (${startTime.toLocaleTimeString()})`)
      terminal.filePath = tmpFile
      terminal.index = terminalIndex
      terminal.show()
      terminal.sendText(`cd "${dirName}"`)
      terminal.sendText(`${config.nodeBin} "${path.basename(terminal.filePath)}" ${config.args.join(' ')}`)
      terminal.sendText(`rm "${terminal.filePath}"`) // try to delete the file after execution
      terminals.push(terminal)
    }
  })

  // event for closing an terminal window
  vscode.window.onDidCloseTerminal(function (terminal) {
    if (terminal.filePath) {
      if (fs.existsSync(terminal.filePath)) {
        fs.unlink(terminal.filePath) // if tmp file still exist, try to delete it
      }
      terminals.splice(terminal.index, 1)
    }
  })

  // cancel the exec process
  var cancel = vscode.commands.registerCommand('extension.miramac.node.cancel', function () {
    const config = vscode.workspace.getConfiguration('miramac.node')
    if (config.terminalMode) {
      return
    }
    if (runningStatus) {
      if (config.showInfo) {
        outputChannel.appendLine('Process canceled')
      }
      if (process) {
        process.kill()
      } else {
        runningStatus = null
      }
    }
  })

  context.subscriptions.push(run, cancel)
}

module.exports = activate
<commit_msg>Show code total execution time on process end<commit_after>'use strict'
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
const vscode = require('vscode')
const spawn = require('child_process').spawn
const fs = require('fs')
const path = require('path')

// this method is called when your extension is activated
// your extension is activated the very first time the command is executed

function activate (context) {
  const outputChannel = vscode.window.createOutputChannel(vscode.workspace.getConfiguration('miramac.node').outputWindowName)
  let terminals = []
  let process
  let runningStatus = null

  // executes JS code with node
  let run = vscode.commands.registerCommand('extension.miramac.node.exec', function () {
    const config = vscode.workspace.getConfiguration('miramac.node')
    // test for running process
    if (runningStatus) {
      vscode.window.showErrorMessage('Process is already running!')
      return
    }

    let editor = vscode.window.activeTextEditor
    if (!editor) {
      return // No open text editor
    }
    if (editor._documentData._languageId === 'Log') {
      editor = vscode.window.visibleTextEditors[0]
    }

    let dirName = path.dirname(editor.document.fileName)
    if (!dirName || dirName === '' || dirName === '.') {
      vscode.window.showWarningMessage('Unknown working directory! Try to save the file before running.')
      return
    }

    let selection = editor.selection
    let text = editor.document.getText(selection)
    if ((text === '' && config.executeFileOrSelection === 'both') || config.executeFileOrSelection === 'file') {
      text = editor.document.getText()
    }
    if (config.includeCode) {
      text = config.includeCode.replace('${execPath}', dirName) + ';\n' + text // eslint-disable-line no-template-curly-in-string
    }

    // get config params object
    let options = {}
    if (config.env) {
      options.env = config.env
    }
    // get cwd from config, replace placeholder and add it to the params object
    options.cwd = (typeof config.cwd === 'string') ? config.cwd.replace('${execPath}', dirName) : dirName  // eslint-disable-line no-template-curly-in-string

    // set tempfile
    let tmpFile = path.join(dirName, `node_${require('crypto').createHash('sha1').update(Math.random().toString()).digest('hex').substr(0, 13)}.tmp`)
    // wirte code in temp file and exec the file with node
    fs.writeFileSync(tmpFile, text)

    let args = [tmpFile]
    if (Array.isArray(config.args) && config.args.length > 0) {
      args = args.concat(config.args)
    }

    let startTime = new Date()
    let nodeBin = (typeof config.nodeBin === 'string') ? config.nodeBin : 'node'

    if (!config.terminalMode) {
      runningStatus = vscode.window.setStatusBarMessage('Running...')
      outputChannel.show(vscode.window.visibleTextEditors.length + 1)
      if (config.clearOutput) {
        outputChannel.clear()
      }
      if (config.showInfo) {
        outputChannel.appendLine('Info: Start process (' + startTime.toLocaleTimeString() + ')')
      }
      // spawn new node.js process
      process = spawn(nodeBin, args, options)

      // process event handlers
      process.stdout.on('data', function (data) {
        if (!config.showStdout) return
        outputChannel.append(data.toString())
      })
      process.stderr.on('data', function (data) {
        if (!config.showStderr) return
        outputChannel.appendLine('Error: ')
        outputChannel.appendLine(data.toString())
      })
      process.on('close', function () {
        fs.unlink(tmpFile)
        if (runningStatus) {
          if (config.showInfo) {
            outputChannel.appendLine('Info: End process in ' + getDuration(startTime, new Date()) + ' (mm:ss:fff)')
          }
          runningStatus.dispose()
          runningStatus = null
        }
      })
      process.on('error', function (err) {
        fs.unlink(tmpFile)
        outputChannel.appendLine('Process error: ')
        outputChannel.appendLine(err.toString())
        if (config.showInfo) {
          outputChannel.appendLine('Info: End process with errors in ' + getDuration(startTime, new Date()) + ' (mm:ss:fff)')
        }
        if (runningStatus) {
          runningStatus.dispose()
          runningStatus = null
        }
      })
    } else { // Terminal Mode
      let terminalIndex = terminals.length
      let terminal = vscode.window.createTerminal(`Node.js (${startTime.toLocaleTimeString()})`)
      terminal.filePath = tmpFile
      terminal.index = terminalIndex
      terminal.show()
      terminal.sendText(`cd "${dirName}"`)
      terminal.sendText(`${config.nodeBin} "${path.basename(terminal.filePath)}" ${config.args.join(' ')}`)
      terminal.sendText(`rm "${terminal.filePath}"`) // try to delete the file after execution
      terminals.push(terminal)
    }
  })

  // event for closing an terminal window
  vscode.window.onDidCloseTerminal(function (terminal) {
    if (terminal.filePath) {
      if (fs.existsSync(terminal.filePath)) {
        fs.unlink(terminal.filePath) // if tmp file still exist, try to delete it
      }
      terminals.splice(terminal.index, 1)
    }
  })

  // cancel the exec process
  var cancel = vscode.commands.registerCommand('extension.miramac.node.cancel', function () {
    const config = vscode.workspace.getConfiguration('miramac.node')
    if (config.terminalMode) {
      return
    }
    if (runningStatus) {
      if (config.showInfo) {
        outputChannel.appendLine('Process canceled')
      }
      if (process) {
        process.kill()
      } else {
        runningStatus = null
      }
    }
  })

  context.subscriptions.push(run, cancel)
}

module.exports = activate

// get a duration as Timestring (mm:ss.fff)
function getDuration (start, end) {
  let duration = new Date(end - start)
  return numPad(duration.getMinutes(), 1) + ':' + numPad(duration.getSeconds(), 2) + ':' + numPad(duration.getMilliseconds(), 3)
}

// Numberpadding
function numPad (number, size) {
  let result = number + ''
  while (result.length < size) {
    result = '0' + result
  }
  return result
}
<|endoftext|>


<commit_before>const GameEngine = require('../../../../lib/engine');
const Swapper = require('./swapper');

const keyBindingMap = require('./keybindings');
const actionMap = require('./actions');

class UserInterface {
  constructor() {
    this.game = new GameEngine();
    this.gameLoop = null;
    this.blocks = [];
    this.previewBlocks = [];
    this.swapper = new Swapper(this, 0, 0);
    this.frameRate = 30;
  }

  get isGameRunning() {
    return this.game != null && this.gameLoop != null;
  }

  install() {
    this.installDOMElements();
    this.installEventListeners();
    this.installGameLoop();
  }

  installDOMElements() {
    const gridElement = document.createElement('div');

    gridElement.classList.add('grid');
    document.body.appendChild(gridElement);
    for (let y = 0; y < this.game.height; ++y) {
      const rowElement = document.createElement('div');

      rowElement.classList.add('row');
      gridElement.appendChild(rowElement);
      for (let x = 0; x < this.game.width; ++x) {
        const blockElement = document.createElement('div');

        blockElement.classList.add('block');
        blockElement.addEventListener('click', (ev) => {
          ev.preventDefault();
          this.action('swap', { x, y });
        });
        rowElement.appendChild(blockElement);
        this.blocks.push(blockElement);
      }
    }

    this.blocks[0].classList.add('swapper');
    this.blocks[1].classList.add('swapper');

    const previewRowElement = document.createElement('div');

    previewRowElement.classList.add('row', 'preview');
    gridElement.appendChild(previewRowElement);
    for (let x = 0; x < this.game.width; ++x) {
      const blockElement = document.createElement('div');

      blockElement.classList.add('block');
      blockElement.addEventListener('click', (ev) => {
        ev.preventDefault();
        this.action('addRow');
      });
      previewRowElement.appendChild(blockElement);
      this.previewBlocks.push(blockElement);
    }
  }

  installEventListeners() {
    window.addEventListener('keydown', (ev) => {
      const actionName = keyBindingMap[ev.key];

      if (typeof actionName === 'string') {
        ev.preventDefault();
        this.action(actionName);
      }
    });
  }

  installGameLoop() {
    this.update(this.game.step());
    this.gameLoop = window.setInterval(() => {
      if (this.isGameRunning) {
        this.update(this.game.step());
      } else {
        window.clearInterval(this.gameLoop);
        this.gameLoop = null;
      }
    }, 1000 / this.frameRate);
  }

  update(state) {
    state.blocks.forEach((block, index) => {
      const element = this.blocks[index];

      element.classList.remove('red', 'green', 'blue');
      if (block.color) {
        element.classList.add(block.color);
      }
    });

    state.nextRow.forEach((block, index) => {
      const element = this.previewBlocks[index];

      element.classList.remove('red', 'green', 'blue');
      element.classList.add(block.color);
    });
  }

  action(actionName, actionArguments = {}) {
    const actionCallback = actionMap[actionName];

    if (typeof actionCallback === 'function') {
      actionCallback(this, actionArguments);
    }
  }
}

module.exports = UserInterface;
<commit_msg>Add gamepad support<commit_after>const GameEngine = require('../../../../lib/engine');
const Swapper = require('./swapper');

const keyBindingMap = require('./keybindings');
const actionMap = require('./actions');

class UserInterface {
  constructor() {
    this.game = new GameEngine();
    this.gameLoop = null;
    this.gamepadSupport = {
      devices: {},
      previousTimestamps: {},
    };
    this.blocks = [];
    this.previewBlocks = [];
    this.swapper = new Swapper(this, 0, 0);
    this.frameRate = 30;
  }

  get isGameRunning() {
    return this.game != null && this.gameLoop != null;
  }

  install() {
    this.installDOMElements();
    this.installEventListeners();
    this.installGameLoop();
  }

  installDOMElements() {
    const gridElement = document.createElement('div');

    gridElement.classList.add('grid');
    document.body.appendChild(gridElement);
    for (let y = 0; y < this.game.height; ++y) {
      const rowElement = document.createElement('div');

      rowElement.classList.add('row');
      gridElement.appendChild(rowElement);
      for (let x = 0; x < this.game.width; ++x) {
        const blockElement = document.createElement('div');

        blockElement.classList.add('block');
        blockElement.addEventListener('click', (ev) => {
          ev.preventDefault();
          this.action('swap', { x, y });
        });
        rowElement.appendChild(blockElement);
        this.blocks.push(blockElement);
      }
    }

    this.blocks[0].classList.add('swapper');
    this.blocks[1].classList.add('swapper');

    const previewRowElement = document.createElement('div');

    previewRowElement.classList.add('row', 'preview');
    gridElement.appendChild(previewRowElement);
    for (let x = 0; x < this.game.width; ++x) {
      const blockElement = document.createElement('div');

      blockElement.classList.add('block');
      blockElement.addEventListener('click', (ev) => {
        ev.preventDefault();
        this.action('addRow');
      });
      previewRowElement.appendChild(blockElement);
      this.previewBlocks.push(blockElement);
    }
  }

  installEventListeners() {
    window.addEventListener('keydown', (ev) => {
      const actionName = keyBindingMap[ev.key];

      if (typeof actionName === 'string') {
        ev.preventDefault();
        this.action(actionName);
      }
    });
    window.addEventListener('gamepadconnected', (ev) => {
      this.gamepadSupport.devices[ev.gamepad.index] = ev.gamepad;
      if (Object.keys(this.gamepadSupport.devices).length === 1) {
        this.installGamepadListener();
      }
    });
    window.addEventListener('gamepaddisconnected', (ev) => {
      delete this.gamepadSupport.devices[ev.gamepad.index];
    });
  }

  installGameLoop() {
    this.update(this.game.step());
    this.gameLoop = window.setInterval(() => {
      if (this.isGameRunning) {
        this.update(this.game.step());
      } else {
        window.clearInterval(this.gameLoop);
        this.gameLoop = null;
      }
    }, 1000 / this.frameRate);
  }

  installGamepadListener() {
    const gamepadListener = () => {
      for (let index in this.gamepadSupport.devices) {
        const device = this.gamepadSupport.devices[index];
        const timestamp = device.timestamp;
        const previousTimestamp = this.gamepadSupport.previousTimestamps[index];

        if (previousTimestamp && previousTimestamp === timestamp) {
          continue;
        }
        this.gamepadSupport.previousTimestamps[index] = timestamp;

        for (let i = 0; i < device.buttons.length; ++i) {
          if (!device.buttons[i].pressed) {
            continue;
          } else if (i === 0) {
            this.action('swap');
          } else if (i === 1) {
            this.action('addRow');
          }
        }

        for (let i = 0; i < device.axes.length && i < 2; ++i) {
          const axis = device.axes[i];

          if (axis === 1 || axis === -1) {
            if (i === 0) {
              this.action(axis > 0 ? 'moveSwapperRight' : 'moveSwapperLeft');
            } else if (i === 1) {
              this.action(axis > 0 ? 'moveSwapperDown' : 'moveSwapperUp');
            }
          }
        }
      }
      if (Object.keys(this.gamepadSupport.devices).length > 0) {
        window.requestAnimationFrame(gamepadListener);
      }
    };

    gamepadListener();
  }

  update(state) {
    state.blocks.forEach((block, index) => {
      const element = this.blocks[index];

      element.classList.remove('red', 'green', 'blue');
      if (block.color) {
        element.classList.add(block.color);
      }
    });

    state.nextRow.forEach((block, index) => {
      const element = this.previewBlocks[index];

      element.classList.remove('red', 'green', 'blue');
      element.classList.add(block.color);
    });
  }

  action(actionName, actionArguments = {}) {
    const actionCallback = actionMap[actionName];

    if (typeof actionCallback === 'function') {
      actionCallback(this, actionArguments);
    }
  }
}

module.exports = UserInterface;
<|endoftext|>


<commit_before>/*
 * Prana Memory Storage.
 */

/*
 * Module dependencies.
 */
var utils = require('./utils');

/**
 * Memory Storage constructor.
 *
 * @constructor
 * @param {Object} settings Storage settings.
 */
var MemoryStorage = module.exports = function(settings) {
  this.settings = settings;

  // Data storage container.
  this.data = settings.data || {};
};

/**
 * List stored items.
 *
 * @param {Object} query Optional query object.
 * @param {Function} callback Function to run when data is returned.
 */
MemoryStorage.prototype.list = function(query, callback) {
  // Allow filtering by key.
  if (typeof query === 'object' && query[this.settings.keyProperty] !== undefined) {
    var key = query[this.settings.keyProperty];
    var result = {};
    result[key] = this.data[key];
    return callback(null, result);
  }

  // Otherwise list all items.
  callback(null, this.data);
};

/**
 * Load a stored item.
 *
 * @todo Move this to a abstract class Storage. Using list and returning first
 *   item found.
 * @param {String} key Item key to search for.
 * @param {Function} callback Function to run when data is returned.
 */
MemoryStorage.prototype.load = function(key, callback) {
  var keyProperty = this.settings.keyProperty;
  var query = {};
  query[keyProperty] = key;

  // We use the list method here so other storage engines can extend the Memory
  // Storage implement the list and benefit from this.
  var result = this.list(query, function(err, items) {
    callback(null, items[key]);
  });
};

/**
 * Save a item.
 *
 * @param {Object} item Object representing the item.
 * @param {Function} callback Function to run when data is saved.
 */
MemoryStorage.prototype.save = function(item, callback) {
  this.data[item.key] = item;
  callback(null, item);
};

/**
 * Delete a item.
 *
 * @param {String} key Item key to search for.
 * @param {Function} callback Function to run when data is deleted.
 */
MemoryStorage.prototype.delete = function(key, callback) {
  var item = this.data[key];
  delete this.data[key];
  callback(null, item);
};
<commit_msg>Honoring keyProperty type/storage setting.<commit_after>/*
 * Prana Memory Storage.
 */

/*
 * Module dependencies.
 */
var utils = require('./utils');

/**
 * Memory Storage constructor.
 *
 * @constructor
 * @param {Object} settings Storage settings.
 */
var MemoryStorage = module.exports = function(settings) {
  this.settings = settings;

  // Data storage container.
  this.data = settings.data || {};
};

/**
 * List stored items.
 *
 * @param {Object} query Optional query object.
 * @param {Function} callback Function to run when data is returned.
 */
MemoryStorage.prototype.list = function(query, callback) {
  // Allow filtering by key.
  if (typeof query === 'object' && query[this.settings.keyProperty] !== undefined) {
    var key = query[this.settings.keyProperty];
    var result = {};
    result[key] = this.data[key];
    return callback(null, result);
  }

  // Otherwise list all items.
  callback(null, this.data);
};

/**
 * Load a stored item.
 *
 * @todo Move this to a abstract class Storage. Using list and returning first
 *   item found.
 * @param {String} key Item key to search for.
 * @param {Function} callback Function to run when data is returned.
 */
MemoryStorage.prototype.load = function(key, callback) {
  var keyProperty = this.settings.keyProperty;
  var query = {};
  query[keyProperty] = key;

  // We use the list method here so other storage engines can extend the Memory
  // Storage implement the list and benefit from this.
  var result = this.list(query, function(err, items) {
    callback(null, items[key]);
  });
};

/**
 * Save a item.
 *
 * @param {Object} item Object representing the item.
 * @param {Function} callback Function to run when data is saved.
 */
MemoryStorage.prototype.save = function(item, callback) {
  var keyProperty = this.settings.keyProperty;
  this.data[item[keyProperty]] = item;
  callback(null, item);
};

/**
 * Delete a item.
 *
 * @param {String} key Item key to search for.
 * @param {Function} callback Function to run when data is deleted.
 */
MemoryStorage.prototype.delete = function(key, callback) {
  var item = this.data[key];
  delete this.data[key];
  callback(null, item);
};
<|endoftext|>