<commit_before>var path = require('path'),
    util = require('util'),
    growl = require('growl'),
    async = require('async'),
    fs = require('fs'),
    isBinaryFile = require('isbinaryfile'),
    ShopifyApi = require('shopify-api');

module.exports = function(grunt) {
    var shopify = {};
    shopify._api = false;
    shopify._basePath = false;

    /*
     * Queued task worker.
     *
     * Receive task object and process it.
     *
     * @param {Object} task
     * @param {Function} callback
     * @see {@link https://github.com/caolan/async#queue}
     */
    shopify._queueWorker = function(task, callback) {
        var config = grunt.config('shopify');
        var rate_limit = config.options.rate_limit_delay ?
                config.options.rate_limit_delay :
                500 // default val

        function postUploadCallback() {
            task.done();
            // wait before concluding the task
            setTimeout(callback, rate_limit);
        }

        switch (task.action) {
            case 'upload':
                shopify.upload(task.filepath, postUploadCallback);
                break;
            case 'remove':
                shopify.remove(task.filepath, postUploadCallback);
                break;
            default:
                shopify.notify('unrecognized worker task action: ' + task.action, true);
                break;
        }
    }

    shopify.queue = async.queue(shopify._queueWorker, 1);

    /*
     * Get the Shopify API instance.
     *
     * @return {ShopifyApi}
     */
    shopify._getApi = function() {
        if (!shopify._api) {
            var config = grunt.config('shopify');
            var opts = {
                auth: config.options.api_key + ':' + config.options.password,
                host: config.options.url,
                port: config.options.port,
                timeout: config.options.timeout
            };

            shopify._api = new ShopifyApi(opts);
        }

        return shopify._api;
    };

    /*
     * Get the base path.
     *
     * @return {string}
     */
    shopify._getBasePath = function() {
        if (!shopify._basePath) {
            var config = grunt.config('shopify'),
                base = ('base' in config.options) ? config.options.base : false;

            shopify._basePath = (base.length > 0) ? path.resolve(base) : process.cwd();
        }

        return shopify._basePath;
    };

    /*
     * Get the Theme ID.
     *
     * @return {integer}
     */
    shopify._getThemeId = function() {
        var config = grunt.config('shopify');
        return ('theme' in config.options) ? config.options.theme : false;
    };

    /*
     * Determine if path is within our base path.
     *
     * @return {Boolean}
     */
    shopify._isPathInBase = function(filepath) {
        var basePath = shopify._getBasePath();

        try {
            return grunt.file.doesPathContain(basePath, path.resolve(filepath));
        } catch(e) {
            return false;
        }
    };

    /*
     * Determine if path is valid to use.
     *
     * @return {Boolean}
     */
    shopify._isValidPath = function(filepath) {
        if (!shopify._isPathInBase(filepath)) {
            shopify.notify('File "' + filepath + '" not in base path');
            return false;
        } else if (!shopify._isWhitelistedPath(filepath)) {
            var relative = shopify._makePathRelative(filepath);
            shopify.notify('File "' + relative + '" not allowed by Shopify whitelist');
            return false;
        }

        return true;
    };

    /*
     * Determine if path is allowed by Shopify.
     *
     * @return {Boolean}
     */
    shopify._isWhitelistedPath = function(filepath) {
        filepath = shopify._makePathRelative(filepath);

        return filepath.match(/^(assets|config|layout|snippets|templates|locales)\//i);
    };

    /*
     * Determine if path is being watched.
     *
     * @return {Boolean}
     */
    shopify._isWatchedPath = function(filepath) {
        watchedFolders = grunt.config('watch').shopify.files;

        return grunt.file.isMatch(watchedFolders,filepath);
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     */
    shopify._makeAssetKey = function(filepath) {
        filepath = shopify._makePathRelative(filepath);

        return encodeURI(filepath);
    };

    /**
     * Make a path relative to base path.
     *
     * @param {string} filepath
     * @return {string}
     */
    shopify._makePathRelative = function(filepath) {
        var basePath = shopify._getBasePath();

        filepath = path.relative(basePath, filepath);

        return filepath.replace(/\\/g, '/');
    };

    /*
     * Save a Shopify asset to disk.
     *
     * @param {string} key
     * @param {Object} obj
     * @param {Function} done
     */
    shopify._saveAsset = function(key, obj, done) {
        var contents,
            basePath = shopify._getBasePath(),
            destination = path.join(basePath, key);

        shopify.notify('Downloading "' + key + '".');

        if (typeof obj.asset.value !== 'undefined') {
            contents = obj.asset.value;
        } else if (typeof obj.asset.attachment !== 'undefined') {
            contents = new Buffer(obj.asset.attachment, 'base64');
        } else {
            return done(new Error('Parsed object is not complete'));
        }

        if (grunt.option('no-write')) {
            console.log(util.inspect(obj));
        } else {
            grunt.file.write(destination, contents);
            shopify.notify('File "' + key + '" saved to disk.');
        }

        done();
    };

    /*
     * Helper for reporting messages to the user.
     *
     * @param {string} msg
     */
    shopify.notify = function(msg, err) {
        var config = grunt.config('shopify');

        msg = decodeURI(msg);
        err = err || false;

        if (config.options.disable_growl_notifications !== false) {
            growl(msg, { title: 'Grunt Shopify'});
        }

        if (!config.options.disable_grunt_log) {
            if (err) {
                grunt.log.error('[grunt-shopify] - ' + msg);
            } else {
                grunt.log.ok('[grunt-shopify] - ' + msg);
            }
        }
    };

    /*
     * Remove a given file path from Shopify.
     *
     * File should be the relative path on the local filesystem.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.remove = function(filepath, done) {
        if (!shopify._isValidPath(filepath)) {
            return done();
        }

        var api = shopify._getApi(),
            themeId = shopify._getThemeId(),
            key = shopify._makeAssetKey(filepath);

        shopify.notify('File "' + key + '" being removed.');

        function onDestroy(err) {
            if (!err) {
                shopify.notify('File "' + key + '" removed.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.destroy(themeId, key, onDestroy);
        } else {
            api.assetLegacy.destroy(key, onDestroy);
        }
    };

    /*
     * Upload a given file path to Shopify
     *
     * Assets need to be in a suitable directory.
     *      - Liquid templates => "templates/"
     *      - Liquid layouts => "layout/"
     *      - Liquid snippets => "snippets/"
     *      - Theme settings => "config/"
     *      - General assets => "assets/"
     *      - Language files => "locales/"
     *
     * Some requests may fail if those folders are ignored
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.upload = function(filepath, done) {
        if (!shopify._isValidPath(filepath)) {
            return done();
        }

        var api = shopify._getApi(),
            themeId = shopify._getThemeId(),
            key = shopify._makeAssetKey(filepath),
            isBinary = isBinaryFile(filepath),
            props = {
                asset: {
                    key: key
                }
            },
            contents;

        contents = grunt.file.read(filepath, { encoding: isBinary ? null : 'utf8' });
        shopify.notify('Uploading "'+ key +'"');

        if (isBinary) {
            props.asset.attachment = contents.toString('base64');
        } else {
            props.asset.value = contents.toString();
        }

        function onUpdate(err, resp) {
            if (err && err.type === 'ShopifyInvalidRequestError') {
                shopify.notify('Error uploading file ' + JSON.stringify(err.detail), true);
            } else if (!err) {
                shopify.notify('File "' + key + '" uploaded.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.update(themeId, props, onUpdate);
        } else {
            api.assetLegacy.update(props, onUpdate);
        }
    };

    /*
     * Deploy an entire theme to Shopify.
     *
     * @param {Function} done
     */
    shopify.deploy = function(done, options) {
        var c = grunt.config('shopify');

        var basePath = shopify._getBasePath();
        var filepaths = grunt.file.expand({ cwd: basePath }, [
            'assets/*.*',
            'config/*.*',
            'layout/*.*',
            'locales/*.*',
            'snippets/*.*',
            'templates/*.*',
            'templates/customers/*.*'
        ]);

        if (options.noJson) {
            var index = filepaths.indexOf('config/settings_data.json');
            filepaths.splice(index, 1);
        }

        async.eachSeries(filepaths, function(filepath, next) {
            shopify.upload(path.join(basePath, filepath), next);
        }, function(err, resp) {
            if (err && err.type === 'ShopifyInvalidRequestError') {
                shopify.notify('Error deploying theme ' + JSON.stringify(err.detail), true);
            } else if (!err) {
                shopify.notify('Theme deploy complete.');
            }

            done(err);
        });
    };

    /*
     * Download an asset from Shopify.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.download = function(filepath, done) {
        var api = shopify._getApi(),
            themeId = shopify._getThemeId(),
            key = shopify._makeAssetKey(filepath);

        function onRetrieve(err, obj) {
            if (err) {
                if (err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error downloading asset file ' + JSON.stringify(err.detail), true);
                }

                return done(err);
            }

            if (!obj.asset) {
                return done(new Error('Failed to get asset data'));
            }

            shopify._saveAsset(key, obj, done);
        }

        if (themeId) {
            api.asset.retrieve(themeId, key, onRetrieve);
        } else {
            api.assetLegacy.retrieve(key, onRetrieve);
        }
    };

    /*
     * Download an entire theme from Shopify.
     *
     * @param {Function} done
     */
    shopify.downloadTheme = function(done) {
        var api = shopify._getApi();
        var config = grunt.config('shopify');
        var themeId = shopify._getThemeId(),
            basePath = shopify._getBasePath();
        var rate_limit = config.options.rate_limit_delay ?
            config.options.rate_limit_delay :
            500 // default val

        function onRetrieve(err, obj) {
            if (err) {
                if (err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error downloading theme ' + JSON.stringify(err.detail), true);
                }

                return done(err);
            }

            if (!obj.assets) {
                return done(new Error('Failed to get theme assets list'));
            }

            async.eachSeries(obj.assets, function(asset, next) {
                setTimeout(function() {
                    shopify.download(path.join(basePath, asset.key), next);
                }, rate_limit);
            }, function(err) {
                if (!err) {
                    shopify.notify('Theme download complete.');
                }

                done(err);
            });
        }

        if (themeId) {
            api.asset.list(themeId, onRetrieve);
        } else {
            api.assetLegacy.list(onRetrieve);
        }
    };

    /*
     * Display the list of available themes.
     *
     * @param {Function} done
     */
    shopify.themes = function(done) {
        var api = shopify._getApi();

        api.theme.list(function(err, obj) {
            if (err) {
                return done(err);
            }

            if (!obj.themes) {
                return done(new Error('Failed to get themes list'));
            }

            obj.themes.forEach(function(theme) {
                var str = theme.id + ' - ' + theme.name;

                if (theme.role.length > 0) {
                    str += ' (' + theme.role + ')';
                }

                grunt.log.writeln(str);
            });

            done();
        });
    };

    /*
     * Sync local files to Shopify based on last modified date
     *
     * @param {Function} done
     */
    shopify.sync = function (done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId(),
            basePath = shopify._getBasePath();

        var fileMap = {};

        function onRetrieve(err, obj) {
            if (err) {
                if (err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error downloading theme ' + JSON.stringify(err.detail), true);
                }

                return done(err);
            }

            if (!obj.assets) {
                return done(new Error('Failed to get theme assets list'));
            }

            for (var i = 0; i < obj.assets.length; i++) {
                var file = obj.assets[i];
                fileMap[file.key] = {
                    'updated_at': file.updated_at,
                    'size': file.size
                };
            }

            var filepaths = grunt.file.expand({cwd: basePath}, [
                'assets/*.*',
                'config/*.*',
                'layout/*.*',
                'locales/*.*',
                'snippets/*.*',
                'templates/*.*',
                'templates/customers/*.*'
            ]);

            var filesToUpdate = [];

            for (var i = 0; i < filepaths.length; i++) {
                var absPath = filepaths[i];
                var filePath = path.join(basePath, absPath);
                var date = fs.lstatSync(filePath).mtime;
                if (!fileMap[absPath]) {
                    filesToUpdate.push(filePath);
                    continue;
                }
                if (new Date(fileMap[absPath].updated_at).getTime() < date.getTime()) {
                    filesToUpdate.push(filePath);
                }
            }
            async.eachSeries(filesToUpdate, function (filepath, next) {
                shopify.upload(filepath, next);
            }, function (err, resp) {
                if (err && err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error syncing theme ' + JSON.stringify(err.detail), true);
                } else if (!err) {
                    shopify.notify('Theme sync complete.');
                }
                done(err);
            });
        }

        if (themeId) {
            api.asset.list(themeId, onRetrieve);
        } else {
            api.assetLegacy.list(onRetrieve);
        }
    };

    shopify.watchHandler = function(action, filepath) {
        function errorHandler(err) {
            if (err) {
                shopify.notify(err.message, true);
            }
        }

        if (!shopify._isWatchedPath(filepath)) {
            return;
        }

        if (action === 'deleted') {
            shopify.queue.push({
                action: 'remove',
                filepath: filepath,
                done: errorHandler
            });
        } else if (grunt.file.isFile(filepath)) {
            switch (action) {
                case 'added':
                case 'changed':
                case 'renamed':
                shopify.queue.push({
                    action: 'upload',
                    filepath: filepath,
                    done: errorHandler
                });
                break;
            }
        } else {
            shopify.notify('Skipping non-file ' + filepath);
        }
    };

    return shopify;
};
<commit_msg>Added sections path to whitelist, to allow for section development.<commit_after>var path = require('path'),
    util = require('util'),
    growl = require('growl'),
    async = require('async'),
    fs = require('fs'),
    isBinaryFile = require('isbinaryfile'),
    ShopifyApi = require('shopify-api');

module.exports = function(grunt) {
    var shopify = {};
    shopify._api = false;
    shopify._basePath = false;

    /*
     * Queued task worker.
     *
     * Receive task object and process it.
     *
     * @param {Object} task
     * @param {Function} callback
     * @see {@link https://github.com/caolan/async#queue}
     */
    shopify._queueWorker = function(task, callback) {
        var config = grunt.config('shopify');
        var rate_limit = config.options.rate_limit_delay ?
                config.options.rate_limit_delay :
                500 // default val

        function postUploadCallback() {
            task.done();
            // wait before concluding the task
            setTimeout(callback, rate_limit);
        }

        switch (task.action) {
            case 'upload':
                shopify.upload(task.filepath, postUploadCallback);
                break;
            case 'remove':
                shopify.remove(task.filepath, postUploadCallback);
                break;
            default:
                shopify.notify('unrecognized worker task action: ' + task.action, true);
                break;
        }
    }

    shopify.queue = async.queue(shopify._queueWorker, 1);

    /*
     * Get the Shopify API instance.
     *
     * @return {ShopifyApi}
     */
    shopify._getApi = function() {
        if (!shopify._api) {
            var config = grunt.config('shopify');
            var opts = {
                auth: config.options.api_key + ':' + config.options.password,
                host: config.options.url,
                port: config.options.port,
                timeout: config.options.timeout
            };

            shopify._api = new ShopifyApi(opts);
        }

        return shopify._api;
    };

    /*
     * Get the base path.
     *
     * @return {string}
     */
    shopify._getBasePath = function() {
        if (!shopify._basePath) {
            var config = grunt.config('shopify'),
                base = ('base' in config.options) ? config.options.base : false;

            shopify._basePath = (base.length > 0) ? path.resolve(base) : process.cwd();
        }

        return shopify._basePath;
    };

    /*
     * Get the Theme ID.
     *
     * @return {integer}
     */
    shopify._getThemeId = function() {
        var config = grunt.config('shopify');
        return ('theme' in config.options) ? config.options.theme : false;
    };

    /*
     * Determine if path is within our base path.
     *
     * @return {Boolean}
     */
    shopify._isPathInBase = function(filepath) {
        var basePath = shopify._getBasePath();

        try {
            return grunt.file.doesPathContain(basePath, path.resolve(filepath));
        } catch(e) {
            return false;
        }
    };

    /*
     * Determine if path is valid to use.
     *
     * @return {Boolean}
     */
    shopify._isValidPath = function(filepath) {
        if (!shopify._isPathInBase(filepath)) {
            shopify.notify('File "' + filepath + '" not in base path');
            return false;
        } else if (!shopify._isWhitelistedPath(filepath)) {
            var relative = shopify._makePathRelative(filepath);
            shopify.notify('File "' + relative + '" not allowed by Shopify whitelist');
            return false;
        }

        return true;
    };

    /*
     * Determine if path is allowed by Shopify.
     *
     * @return {Boolean}
     */
    shopify._isWhitelistedPath = function(filepath) {
        filepath = shopify._makePathRelative(filepath);

        return filepath.match(/^(assets|config|layout|snippets|sections|templates|locales)\//i);
    };

    /*
     * Determine if path is being watched.
     *
     * @return {Boolean}
     */
    shopify._isWatchedPath = function(filepath) {
        watchedFolders = grunt.config('watch').shopify.files;

        return grunt.file.isMatch(watchedFolders,filepath);
    };

    /*
     * Convert a file path on the local file system to an asset path in shopify
     * as you may run grunt at a higher directory locally.
     *
     * The original path to a file may be something like shop/assets/site.css
     * whereas we require assets/site.css in the API. To customize the base
     * set shopify.options.base config option.
     *
     * @param {string}
     * @return {string}
     */
    shopify._makeAssetKey = function(filepath) {
        filepath = shopify._makePathRelative(filepath);

        return encodeURI(filepath);
    };

    /**
     * Make a path relative to base path.
     *
     * @param {string} filepath
     * @return {string}
     */
    shopify._makePathRelative = function(filepath) {
        var basePath = shopify._getBasePath();

        filepath = path.relative(basePath, filepath);

        return filepath.replace(/\\/g, '/');
    };

    /*
     * Save a Shopify asset to disk.
     *
     * @param {string} key
     * @param {Object} obj
     * @param {Function} done
     */
    shopify._saveAsset = function(key, obj, done) {
        var contents,
            basePath = shopify._getBasePath(),
            destination = path.join(basePath, key);

        shopify.notify('Downloading "' + key + '".');

        if (typeof obj.asset.value !== 'undefined') {
            contents = obj.asset.value;
        } else if (typeof obj.asset.attachment !== 'undefined') {
            contents = new Buffer(obj.asset.attachment, 'base64');
        } else {
            return done(new Error('Parsed object is not complete'));
        }

        if (grunt.option('no-write')) {
            console.log(util.inspect(obj));
        } else {
            grunt.file.write(destination, contents);
            shopify.notify('File "' + key + '" saved to disk.');
        }

        done();
    };

    /*
     * Helper for reporting messages to the user.
     *
     * @param {string} msg
     */
    shopify.notify = function(msg, err) {
        var config = grunt.config('shopify');

        msg = decodeURI(msg);
        err = err || false;

        if (config.options.disable_growl_notifications !== false) {
            growl(msg, { title: 'Grunt Shopify'});
        }

        if (!config.options.disable_grunt_log) {
            if (err) {
                grunt.log.error('[grunt-shopify] - ' + msg);
            } else {
                grunt.log.ok('[grunt-shopify] - ' + msg);
            }
        }
    };

    /*
     * Remove a given file path from Shopify.
     *
     * File should be the relative path on the local filesystem.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.remove = function(filepath, done) {
        if (!shopify._isValidPath(filepath)) {
            return done();
        }

        var api = shopify._getApi(),
            themeId = shopify._getThemeId(),
            key = shopify._makeAssetKey(filepath);

        shopify.notify('File "' + key + '" being removed.');

        function onDestroy(err) {
            if (!err) {
                shopify.notify('File "' + key + '" removed.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.destroy(themeId, key, onDestroy);
        } else {
            api.assetLegacy.destroy(key, onDestroy);
        }
    };

    /*
     * Upload a given file path to Shopify
     *
     * Assets need to be in a suitable directory.
     *      - Liquid templates => "templates/"
     *      - Liquid layouts => "layout/"
     *      - Liquid snippets => "snippets/"
     *      - Theme settings => "config/"
     *      - General assets => "assets/"
     *      - Language files => "locales/"
     *
     * Some requests may fail if those folders are ignored
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.upload = function(filepath, done) {
        if (!shopify._isValidPath(filepath)) {
            return done();
        }

        var api = shopify._getApi(),
            themeId = shopify._getThemeId(),
            key = shopify._makeAssetKey(filepath),
            isBinary = isBinaryFile(filepath),
            props = {
                asset: {
                    key: key
                }
            },
            contents;

        contents = grunt.file.read(filepath, { encoding: isBinary ? null : 'utf8' });
        shopify.notify('Uploading "'+ key +'"');

        if (isBinary) {
            props.asset.attachment = contents.toString('base64');
        } else {
            props.asset.value = contents.toString();
        }

        function onUpdate(err, resp) {
            if (err && err.type === 'ShopifyInvalidRequestError') {
                shopify.notify('Error uploading file ' + JSON.stringify(err.detail), true);
            } else if (!err) {
                shopify.notify('File "' + key + '" uploaded.');
            }

            done(err);
        }

        if (themeId) {
            api.asset.update(themeId, props, onUpdate);
        } else {
            api.assetLegacy.update(props, onUpdate);
        }
    };

    /*
     * Deploy an entire theme to Shopify.
     *
     * @param {Function} done
     */
    shopify.deploy = function(done, options) {
        var c = grunt.config('shopify');

        var basePath = shopify._getBasePath();
        var filepaths = grunt.file.expand({ cwd: basePath }, [
            'assets/*.*',
            'config/*.*',
            'layout/*.*',
            'locales/*.*',
            'snippets/*.*',
            'templates/*.*',
            'templates/customers/*.*'
        ]);

        if (options.noJson) {
            var index = filepaths.indexOf('config/settings_data.json');
            filepaths.splice(index, 1);
        }

        async.eachSeries(filepaths, function(filepath, next) {
            shopify.upload(path.join(basePath, filepath), next);
        }, function(err, resp) {
            if (err && err.type === 'ShopifyInvalidRequestError') {
                shopify.notify('Error deploying theme ' + JSON.stringify(err.detail), true);
            } else if (!err) {
                shopify.notify('Theme deploy complete.');
            }

            done(err);
        });
    };

    /*
     * Download an asset from Shopify.
     *
     * @param {string} filepath
     * @param {Function} done
     */
    shopify.download = function(filepath, done) {
        var api = shopify._getApi(),
            themeId = shopify._getThemeId(),
            key = shopify._makeAssetKey(filepath);

        function onRetrieve(err, obj) {
            if (err) {
                if (err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error downloading asset file ' + JSON.stringify(err.detail), true);
                }

                return done(err);
            }

            if (!obj.asset) {
                return done(new Error('Failed to get asset data'));
            }

            shopify._saveAsset(key, obj, done);
        }

        if (themeId) {
            api.asset.retrieve(themeId, key, onRetrieve);
        } else {
            api.assetLegacy.retrieve(key, onRetrieve);
        }
    };

    /*
     * Download an entire theme from Shopify.
     *
     * @param {Function} done
     */
    shopify.downloadTheme = function(done) {
        var api = shopify._getApi();
        var config = grunt.config('shopify');
        var themeId = shopify._getThemeId(),
            basePath = shopify._getBasePath();
        var rate_limit = config.options.rate_limit_delay ?
            config.options.rate_limit_delay :
            500 // default val

        function onRetrieve(err, obj) {
            if (err) {
                if (err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error downloading theme ' + JSON.stringify(err.detail), true);
                }

                return done(err);
            }

            if (!obj.assets) {
                return done(new Error('Failed to get theme assets list'));
            }

            async.eachSeries(obj.assets, function(asset, next) {
                setTimeout(function() {
                    shopify.download(path.join(basePath, asset.key), next);
                }, rate_limit);
            }, function(err) {
                if (!err) {
                    shopify.notify('Theme download complete.');
                }

                done(err);
            });
        }

        if (themeId) {
            api.asset.list(themeId, onRetrieve);
        } else {
            api.assetLegacy.list(onRetrieve);
        }
    };

    /*
     * Display the list of available themes.
     *
     * @param {Function} done
     */
    shopify.themes = function(done) {
        var api = shopify._getApi();

        api.theme.list(function(err, obj) {
            if (err) {
                return done(err);
            }

            if (!obj.themes) {
                return done(new Error('Failed to get themes list'));
            }

            obj.themes.forEach(function(theme) {
                var str = theme.id + ' - ' + theme.name;

                if (theme.role.length > 0) {
                    str += ' (' + theme.role + ')';
                }

                grunt.log.writeln(str);
            });

            done();
        });
    };

    /*
     * Sync local files to Shopify based on last modified date
     *
     * @param {Function} done
     */
    shopify.sync = function (done) {
        var api = shopify._getApi();
        var themeId = shopify._getThemeId(),
            basePath = shopify._getBasePath();

        var fileMap = {};

        function onRetrieve(err, obj) {
            if (err) {
                if (err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error downloading theme ' + JSON.stringify(err.detail), true);
                }

                return done(err);
            }

            if (!obj.assets) {
                return done(new Error('Failed to get theme assets list'));
            }

            for (var i = 0; i < obj.assets.length; i++) {
                var file = obj.assets[i];
                fileMap[file.key] = {
                    'updated_at': file.updated_at,
                    'size': file.size
                };
            }

            var filepaths = grunt.file.expand({cwd: basePath}, [
                'assets/*.*',
                'config/*.*',
                'layout/*.*',
                'locales/*.*',
                'snippets/*.*',
                'templates/*.*',
                'templates/customers/*.*'
            ]);

            var filesToUpdate = [];

            for (var i = 0; i < filepaths.length; i++) {
                var absPath = filepaths[i];
                var filePath = path.join(basePath, absPath);
                var date = fs.lstatSync(filePath).mtime;
                if (!fileMap[absPath]) {
                    filesToUpdate.push(filePath);
                    continue;
                }
                if (new Date(fileMap[absPath].updated_at).getTime() < date.getTime()) {
                    filesToUpdate.push(filePath);
                }
            }
            async.eachSeries(filesToUpdate, function (filepath, next) {
                shopify.upload(filepath, next);
            }, function (err, resp) {
                if (err && err.type === 'ShopifyInvalidRequestError') {
                    shopify.notify('Error syncing theme ' + JSON.stringify(err.detail), true);
                } else if (!err) {
                    shopify.notify('Theme sync complete.');
                }
                done(err);
            });
        }

        if (themeId) {
            api.asset.list(themeId, onRetrieve);
        } else {
            api.assetLegacy.list(onRetrieve);
        }
    };

    shopify.watchHandler = function(action, filepath) {
        function errorHandler(err) {
            if (err) {
                shopify.notify(err.message, true);
            }
        }

        if (!shopify._isWatchedPath(filepath)) {
            return;
        }

        if (action === 'deleted') {
            shopify.queue.push({
                action: 'remove',
                filepath: filepath,
                done: errorHandler
            });
        } else if (grunt.file.isFile(filepath)) {
            switch (action) {
                case 'added':
                case 'changed':
                case 'renamed':
                shopify.queue.push({
                    action: 'upload',
                    filepath: filepath,
                    done: errorHandler
                });
                break;
            }
        } else {
            shopify.notify('Skipping non-file ' + filepath);
        }
    };

    return shopify;
};
<|endoftext|>


<commit_before>// Can use
// chrome.devtools.*
// chrome.extension.*

// Create a tab in the devtools area. Optional callback accepted.

chrome.devtools.panels.create('DejaVue', 'assets/img/logo.png', 'index.html', function (extensionPanel) {
    let sidebarAdded = 0;
    let count;
    let oldCount;
    chrome.storage.sync.get('oldCount', function(data){oldCount = data.oldCount})

    var show = new Promise(function(resolve, reject) {
        extensionPanel.onShown.addListener(function(panelWindow) {
            resolve(panelWindow);
        });
    });

    show.then(function(_panelWindow) {
        let data = []
        function updater() {
            var poller = setInterval(function () {
                chrome.storage.sync.get('count', function(data){count = data.count})
                if (count !== oldCount) {
                    oldCount = count;
        //returns rootNodes of dom including expando properties
        chrome.devtools.inspectedWindow.eval(
            `   //USE TO SEE JS OUTSIDE OF TEMPLATE LITERAL STRING TO DEVTOOLS

domNodes = inspect($$('body'));
                    console.log('Tree rerendered')

                // main function to grab and plot data on visualization
                    function createDV() {
                // gets document.body's child nodes which are direct child html elements
                    let keysArray = Object.keys(domNodes[0].children);

                // initialize empty arrays to store node objects
                    let rootNodes = [];
                    let components = [];
                    let dvComponents = [];

                // iterate through keysArray to push only Vue root nodes into rootNodes array
                    function findRoots(node) {
                      if (!rootNodes.includes(node) &&  node.__vue__) {
                        rootNodes.push(node);
                        console.log('pushed')
                      } else {
                        let keysArray = Object.keys(node.children);
                        console.log('keysArray', keysArray);
                        for (let i = 0; i < keysArray.length; i++) {
                          console.log('node.children[keysArray[i]]', node.children[keysArray[i]]);
                          if (!rootNodes.includes(node.children[keysArray[i]])) findRoots(node.children[keysArray[i]]);
                        }
                      }
                    };
                    findRoots(domNodes[0]);
                // console.log('rootNodes', rootNodes)
                // traverses a domNode to push all vue components into components array
                    function findComponents(node) {
                        let childrenArray;
                        if (rootNodes.includes(node)) {
                // console.log('findcomponentsroot');
                // fix for apps that have a root with vue$3 instead of __vue__
                        if (components.includes(rootNodes[0].__vue__))
                        components.unshift(rootNodes[0].__vue__);
                        childrenArray = node.__vue__.$children;
                    }
                    else {
                // console.log('findcomponentschild');
                        childrenArray = node.$children;
                    }
                    childrenArray.forEach((child) => {
                        components.push(child);
                        if(child.$children.length > 0) findComponents(child)
                    });
                    };
                    for (let i = 0; i < rootNodes.length; i += 1) {
                        findComponents(rootNodes[i])
                    }
                    let count = 0;
                        while(components[0].$parent === undefined) {
                        components.shift();
                        count += 1;
                    }
                    while(components[0] === components[1]) components.shift();
                // constructor for each component to grab data DejaVue cares about
                    function CompConstructor(node) {
                // -> _uid
                        this.id = node._uid;
                //assigns name to node - if router-link attempts to get text content as name otherwise uses component tag as name - removes "vue-component-" and adds unique ID to end in case components have the same name (fixes parent/child relationships)
                        if(node.$options._componentTag !== undefined) {
                            if(node.$options._componentTag.includes("router-link")) node.$slots.default[0].text? this.name = node.$slots.default[0].text + ' link -' + node._uid : this.name = 'router-link-' + node._uid;
                            else this.name = node.$options._componentTag + '-' + node._uid;
                        }
                        else if (node.$vnode.tag) {
                            let temp = node.$vnode.tag.slice(16);
                            while(temp[0] === "-" || typeof temp[0] === 'number') {
                                temp = temp.slice(1);
                            }
                            this.name = temp + '-' + node._uid;
                        }
                // console.log(this.name)
                // if(node.$options._componentTag === undefined) this.name = "root"
                // $parent -> _uid
                        this.parentID = node.$parent._uid;

                // $parent -> $options -> _componentTag
                        //assigns parent name to node - removes "vue-component-" and adds unique ID to end in case components have the same name (fixes parent/child relationships)
                        if(node.$parent.$vnode === undefined) this.parentName = 'Vuee';
                        else if (node.$parent.$options._componentTag !== undefined) this.parentName = node.$parent.$options._componentTag + '-' + node.$parent._uid;
                        else {
                            let temp = node.$parent.$vnode.tag.slice(16);
                            while(temp[0] === "-" || typeof temp[0] === 'number') {
                                temp = temp.slice(1);
                            }
                            this.parentName = temp + '-' + node.$parent._uid;
                        }
                // to be filled by d3 object mapper
                        this.children = [];
                // grab _data object
                        this.variables = [];
                        this.props = [];
                        this.slots = [];
                // this.directives = [];
                    }
                // console.log('vue components', components)
                // run each component through the DVconstructor
                    function createDvComps(components) {
                        for (let i = 0; i < components.length; i += 1) {
                            node = components[i];
                            dvComponents.push(new CompConstructor(node));
                            let varKeys = Object.keys(node.$data).filter((key) => {
                                if (key.match(/\s/g)) return false;
                                return true;
                            });
                            if (varKeys) {
                                varKeys.forEach((variable) => {
                                    if (variable) dvComponents[dvComponents.length - 1].variables.push({ [variable]: node.$data[variable] });
                                });
                            }
                            if(node.$slots.default)
                            {
                                dvComponents[dvComponents.length - 1].slots.push(node.$slots.default[0].text);
                            }

                            compElem = Object.keys(node);
                            for (let j = 0; j < compElem.length; j++) {
                                if (compElem[j][0] !== '_' && compElem[j][0] !== '$') {
                                    dvComponents[dvComponents.length - 1].props.push(compElem[j]);
                                }
                            }
                        }
                        return dvComponents;
                    };
                    createDvComps(components);
                 console.log('rootNodes', rootNodes)
                 console.log('components', components)
                 console.log('deja vue components1', dvComponents)

                // conversion of components array to JSON object for D3 visualization
                    data = [new treeNode({name: 'Vuee', parent: undefined})]
                    function treeNode(node) {
                        this.name = node.name;
                        this.parent = node.parentName;
                        this.props = node.props;
                        this.variables = JSON.stringify(node.variables);
                        this.slots = node.slots;
                    }
                    dvComponents.forEach(function(node) {
                        data.push(new treeNode(node))
                    })

                console.log('data', data)

                    return data
                }
                createDV()`
            , function (data) {
                d3 = _panelWindow.d3;

            //append component data to sidebar
                // if (_panelWindow.document.getElementById("compdata")) {
                //     let removal = _panelWindow.document.getElementById("compdata");
                //     _panelWindow.document.getElementById("componentInfo").removeChild(removal)
                // }
                //     let nodeData = `

                //
${data[7].name}

                //
Props

                //

                //

                //
${data[7].props}

                //

                //

                //
Vars

                //

                //

${data[7].variables}

                //

                //
Slots

                //

                //

${data[7].slots}

                //

                //     `
                //     let divv = document.createElement("li");
                //     divv.setAttribute('id', 'compdata');
                //     divv.innerHTML = nodeData;
                //     _panelWindow.document.getElementById("componentInfo").appendChild(divv);

             // d3 tree creation
                    // create a name: node map
                    var dataMap = data.reduce(function (map, node) {
                        map[node.name] = node;
                        return map;
                    }, {});

                    // create the tree array
                    var treeData = [];
                    d3.select("svg#treeVisualization").remove()
                    data.forEach(function (node) {
                        // add to parent
                        var parent = dataMap[node.parent];
                        if (parent) {
                        // create child array if it doesn't exist
                            (parent.children || (parent.children = []))
                        // add node to child array if not already in it (prevents doubling of tree when switching between devtool panels)
                             parent.children.push(node);
                        } else {
                        // parent is null or missing from treeData - if not already in it (prevents doubling of tree when switching between devtool panels)
                             treeData.push(node);
                        }
                    });

                    treeData = Object.assign({}, treeData)[0];

                    // console.log(treeData)
                    var margin = { top: 20, right: 90, bottom: 30, left: 90 },
                        width = 660 - margin.left - margin.right,
                        height = 500 - margin.top - margin.bottom;

                    // declares a tree layout and assigns the size
                    var treemap = d3.tree()
                        .size([height, width]);

                    // assigns the data to a hierarchy using parent-child relationships
                    var nodes = d3.hierarchy(treeData, function (d) {
                        return d.children;
                    });

                    // maps the node data to the tree layout
                    nodes = treemap(nodes);
                    // Define the div for the tooltip
                    var div = d3.select("body").append("div")
                        .attr("class", "tooltip")
                        .style("opacity", 0);
                    // append the svg object to the body of the page
                    // appends a 'group' element to 'svg'
                    // moves the 'group' element to the top left margin
                    var svg = d3.select("#treeContainer").append("svg")
                        .attr("id", "treeVisualization")
                        .attr("width", width + margin.left + margin.right)
                        .attr("height", height + margin.top + margin.bottom),
                        g = svg.append("g")
                            .attr("transform",
                            "translate(" + margin.left + "," + margin.top + ")");

                    d3.selection.prototype.first = function () {
                        return d3.select(this[0][0]);
                    };

                    // adds the links between the nodes
                    var link = g.selectAll(".link")
                        .data(nodes.descendants().slice(1))
                        .enter().append("path")
                        .attr("class", "link")
                        .attr("d", function (d) {
                            return "M" + d.y + "," + d.x
                                + "C" + (d.y + d.parent.y) / 2 + "," + d.x
                                + " " + (d.y + d.parent.y) / 2 + "," + d.parent.x
                                + " " + d.parent.y + "," + d.parent.x;
                        });

                    // adds each node as a group
                    var node = g.selectAll(".node")
                        .data(nodes.descendants())
                        .enter().append("g")
                        .attr("class", function (d) {
                            return "node" +
                                (d.children ? " node--internal" : " node--leaf");
                        })
                        .attr("transform", function (d) {
                            return "translate(" + d.y + "," + d.x + ")";
                        });

                    // adds the circle to the node
                    node.append("circle")
                        .attr("r", 10)
                        .on("click", function (d) {
                            alert('Click handler. Use d.* to access node properties');
                        })
                        .on("mouseover", function (d) {
                            chrome.devtools.inspectedWindow.eval(`document.body.setAttribute('style', 'background-color: rgba(137, 196, 219, .4')`);
                            div.transition()
                                .duration(200)
                                .style("opacity", .9);
                            div	.html(d.data.props + "
")
                                .style("left", (d3.event.pageX) + "px")
                                .style("top", (d3.event.pageY - 28) + "px");
                            })
                        .on("mouseout", function(d) {
                            div.transition()
                                .duration(500)
                                .style("opacity", 0);
                        });

                    // adds the text to the node
                    node.append("text")
                        .attr("dy", ".35em")
                        .attr("x", function (d) { return d.children ? -13 : 13; })
                        .style("text-anchor", function (d) {
                            return d.children ? "end" : "start";
                        })
                        // remove component ID when displaying name on tree
                        .text(function (d) { return d.data.name.slice(0, d.data.name.lastIndexOf("-")) });

            })
        }
        }, 100);
    }
    updater()
        });
});

<commit_msg>click node text to populate sidebar with vars, props and slot data<commit_after>// Can use
// chrome.devtools.*
// chrome.extension.*

// Create a tab in the devtools area. Optional callback accepted.

chrome.devtools.panels.create('DejaVue', 'assets/img/logo.png', 'index.html', function (extensionPanel) {
    let sidebarAdded = 0;
    let count;
    let oldCount;
    chrome.storage.sync.get('oldCount', function(data){oldCount = data.oldCount})

    var show = new Promise(function(resolve, reject) {
        extensionPanel.onShown.addListener(function(panelWindow) {
            resolve(panelWindow);
        });
    });

    show.then(function(_panelWindow) {
        let data = []
        function updater() {
            var poller = setInterval(function () {
                chrome.storage.sync.get('count', function(data){count = data.count})
                if (count !== oldCount) {
                    oldCount = count;
        //returns rootNodes of dom including expando properties
        chrome.devtools.inspectedWindow.eval(
            `   //USE TO SEE JS OUTSIDE OF TEMPLATE LITERAL STRING TO DEVTOOLS

domNodes = inspect($$('body'));
                    console.log('Tree rerendered')

                // main function to grab and plot data on visualization
                    function createDV() {
                // gets document.body's child nodes which are direct child html elements
                    let keysArray = Object.keys(domNodes[0].children);

                // initialize empty arrays to store node objects
                    let rootNodes = [];
                    let components = [];
                    let dvComponents = [];

                // iterate through keysArray to push only Vue root nodes into rootNodes array
                    function findRoots(node) {
                      if (!rootNodes.includes(node) &&  node.__vue__) {
                        rootNodes.push(node);
                        console.log('pushed')
                      } else {
                        let keysArray = Object.keys(node.children);
                        console.log('keysArray', keysArray);
                        for (let i = 0; i < keysArray.length; i++) {
                          console.log('node.children[keysArray[i]]', node.children[keysArray[i]]);
                          if (!rootNodes.includes(node.children[keysArray[i]])) findRoots(node.children[keysArray[i]]);
                        }
                      }
                    };
                    findRoots(domNodes[0]);
                // console.log('rootNodes', rootNodes)
                // traverses a domNode to push all vue components into components array
                    function findComponents(node) {
                        let childrenArray;
                        if (rootNodes.includes(node)) {
                // console.log('findcomponentsroot');
                // fix for apps that have a root with vue$3 instead of __vue__
                        if (components.includes(rootNodes[0].__vue__))
                        components.unshift(rootNodes[0].__vue__);
                        childrenArray = node.__vue__.$children;
                    }
                    else {
                // console.log('findcomponentschild');
                        childrenArray = node.$children;
                    }
                    childrenArray.forEach((child) => {
                        components.push(child);
                        if(child.$children.length > 0) findComponents(child)
                    });
                    };
                    for (let i = 0; i < rootNodes.length; i += 1) {
                        findComponents(rootNodes[i])
                    }
                    let count = 0;
                        while(components[0].$parent === undefined) {
                        components.shift();
                        count += 1;
                    }
                    while(components[0] === components[1]) components.shift();
                // constructor for each component to grab data DejaVue cares about
                    function CompConstructor(node) {
                // -> _uid
                        this.id = node._uid;
                //assigns name to node - if router-link attempts to get text content as name otherwise uses component tag as name - removes "vue-component-" and adds unique ID to end in case components have the same name (fixes parent/child relationships)
                        if(node.$options._componentTag !== undefined) {
                            if(node.$options._componentTag.includes("router-link")) node.$slots.default[0].text? this.name = node.$slots.default[0].text + ' link -' + node._uid : this.name = 'router-link-' + node._uid;
                            else this.name = node.$options._componentTag + '-' + node._uid;
                        }
                        else if (node.$vnode.tag) {
                            let temp = node.$vnode.tag.slice(16);
                            while(temp[0] === "-" || typeof temp[0] === 'number') {
                                temp = temp.slice(1);
                            }
                            this.name = temp + '-' + node._uid;
                        }
                // console.log(this.name)
                // if(node.$options._componentTag === undefined) this.name = "root"
                // $parent -> _uid
                        this.parentID = node.$parent._uid;

                // $parent -> $options -> _componentTag
                        //assigns parent name to node - removes "vue-component-" and adds unique ID to end in case components have the same name (fixes parent/child relationships)
                        if(node.$parent.$vnode === undefined) this.parentName = 'Vuee';
                        else if (node.$parent.$options._componentTag !== undefined) this.parentName = node.$parent.$options._componentTag + '-' + node.$parent._uid;
                        else {
                            let temp = node.$parent.$vnode.tag.slice(16);
                            while(temp[0] === "-" || typeof temp[0] === 'number') {
                                temp = temp.slice(1);
                            }
                            this.parentName = temp + '-' + node.$parent._uid;
                        }
                // to be filled by d3 object mapper
                        this.children = [];
                // grab _data object
                        this.variables = [];
                        this.props = [];
                        this.slots = [];
                // this.directives = [];
                    }
                // console.log('vue components', components)
                // run each component through the DVconstructor
                    function createDvComps(components) {
                        for (let i = 0; i < components.length; i += 1) {
                            node = components[i];
                            dvComponents.push(new CompConstructor(node));
                            let varKeys = Object.keys(node.$data).filter((key) => {
                                if (key.match(/\s/g)) return false;
                                return true;
                            });

                            if (varKeys) {
                                varKeys.forEach((variable) => {
                                    if (variable) dvComponents[dvComponents.length - 1].variables.push({ [variable]: node.$data[variable] });
                                });
                            }

                            if(node.$slots.default)
                            {
                                dvComponents[dvComponents.length - 1].slots.push(node.$slots.default[0].text);
                            }

                            compElem = Object.keys(node);
                            for (let j = 0; j < compElem.length; j++) {
                                if (compElem[j][0] !== '_' && compElem[j][0] !== '$') {
                                    dvComponents[dvComponents.length - 1].props.push({[compElem[j]]: node[compElem[j]]});
                                }
                            }
                        }
                        return dvComponents;
                    };
                    createDvComps(components);
                 console.log('rootNodes', rootNodes)
                 console.log('components', components)
                 console.log('deja vue components1', dvComponents)

                // conversion of components array to JSON object for D3 visualization
                    data = [new treeNode({name: 'Vuee', parent: undefined})]
                    function treeNode(node) {
                        this.name = node.name;
                        this.parent = node.parentName;
                        this.props = node.props;
                        this.variables = node.variables;
                        this.slots = node.slots;
                    }
                    dvComponents.forEach(function(node) {
                        data.push(new treeNode(node))
                    })

                console.log('data', data)

                    return data
                }
                createDV()`
            , function (data) {
                d3 = _panelWindow.d3;

            //append component data to sidebar

             // d3 tree creation
                    // create a name: node map
                    var dataMap = data.reduce(function (map, node) {
                        map[node.name] = node;
                        return map;
                    }, {});

                    // create the tree array
                    var treeData = [];
                    d3.select("svg#treeVisualization").remove()
                    data.forEach(function (node) {
                        // add to parent
                        var parent = dataMap[node.parent];
                        if (parent) {
                        // create child array if it doesn't exist
                            (parent.children || (parent.children = []))
                        // add node to child array if not already in it (prevents doubling of tree when switching between devtool panels)
                             parent.children.push(node);
                        } else {
                        // parent is null or missing from treeData - if not already in it (prevents doubling of tree when switching between devtool panels)
                             treeData.push(node);
                        }
                    });

                    treeData = Object.assign({}, treeData)[0];

                    // console.log(treeData)
                    var margin = { top: 20, right: 90, bottom: 30, left: 90 },
                        width = 660 - margin.left - margin.right,
                        height = 500 - margin.top - margin.bottom;

                    // declares a tree layout and assigns the size
                    var treemap = d3.tree()
                        .size([height, width]);

                    // assigns the data to a hierarchy using parent-child relationships
                    var nodes = d3.hierarchy(treeData, function (d) {
                        return d.children;
                    });

                    // maps the node data to the tree layout
                    nodes = treemap(nodes);
                    // Define the div for the tooltip
                    var div = d3.select("body").append("div")
                        .attr("class", "tooltip")
                        .style("opacity", 0);
                    // append the svg object to the body of the page
                    // appends a 'group' element to 'svg'
                    // moves the 'group' element to the top left margin
                    var svg = d3.select("#treeContainer").append("svg")
                        .attr("id", "treeVisualization")
                        .attr("width", width + margin.left + margin.right)
                        .attr("height", height + margin.top + margin.bottom),
                        g = svg.append("g")
                            .attr("transform",
                            "translate(" + margin.left + "," + margin.top + ")");

                    d3.selection.prototype.first = function () {
                        return d3.select(this[0][0]);
                    };

                    // adds the links between the nodes
                    var link = g.selectAll(".link")
                        .data(nodes.descendants().slice(1))
                        .enter().append("path")
                        .attr("class", "link")
                        .attr("d", function (d) {
                            return "M" + d.y + "," + d.x
                                + "C" + (d.y + d.parent.y) / 2 + "," + d.x
                                + " " + (d.y + d.parent.y) / 2 + "," + d.parent.x
                                + " " + d.parent.y + "," + d.parent.x;
                        });

                    // adds each node as a group
                    var node = g.selectAll(".node")
                        .data(nodes.descendants())
                        .enter().append("g")
                        .attr("class", function (d) {
                            return "node" +
                                (d.children ? " node--internal" : " node--leaf");
                        })
                        .attr("transform", function (d) {
                            return "translate(" + d.y + "," + d.x + ")";
                        });

                    // adds the circle to the node
                    node.append("circle")
                        .attr("r", 10)
                        .on("mouseover", function (d) {
                            chrome.devtools.inspectedWindow.eval(`document.body.setAttribute('style', 'background-color: rgba(137, 196, 219, .4')`);
                            div.transition()
                                .duration(200)
                                .style("opacity", .9);
                            div	.html(d.data.name + "
")
                                .style("left", (d3.event.pageX) + "px")
                                .style("top", (d3.event.pageY - 28) + "px");
                            })
                        .on("mouseout", function(d) {
                            div.transition()
                                .duration(500)
                                .style("opacity", 0);
                        });

                    // adds the text to the node
                    node.append("text")
                        .attr("dy", ".35em")
                        .attr("x", function (d) { return d.children ? -13 : 13; })
                        .on("click", function (d) {
                            clickHandler(d);
                        })
                        .style("text-anchor", function (d) {
                            return d.children ? "end" : "start";
                        })
                        // remove component ID when displaying name on tree
                        .text(function (d) { return d.data.name.slice(0, d.data.name.lastIndexOf("-")) });

                    //dejavue custom d3 functionality
                    //click handler function for node text
                    function clickHandler(d) {
                        if (_panelWindow.document.getElementById("compdata")) {
                            let removal = _panelWindow.document.getElementById("compdata");
                            _panelWindow.document.getElementById("componentInfo").removeChild(removal)
                        }

                            let divv = document.createElement("div");
                            divv.setAttribute('id', 'compdata');
                            divv.innerHTML = `

${d.data.name}

Props

Vars

Slot

${(d.data.slots) ? d.data.slots : "No slot/data"}

                                `;

                            _panelWindow.document.getElementById("componentInfo").appendChild(divv);

                        //populate variables on sidebar
                            let variableList = _panelWindow.document.getElementById(d.data.name + "Variables");
                            if (d.data.variables === "undefined") {
                                    let variable = document.createElement("li");
                                    variable.setAttribute('id', d.data.name + 'VariableUndefined');
                                    variable.innerHTML = "undefined";
                                    variableList.appendChild(variable);
                            }
                            else {
                                for (let i = 0; i < d.data.variables.length; i += 1) {
                                    for (key in d.data.variables[i]) {
                                        let variable = document.createElement("li");
                                        variable.setAttribute('id', d.data.name[key] + 'Variable');
                                        variable.innerHTML = (typeof d.data.variables[i][key] === 'object') ? key + ": Function" : key + ": " + d.data.variables[i][key];
                                        variableList.appendChild(variable);
                                    }
                                }
                            };

                        //populate props on sidebar
                            let propList = _panelWindow.document.getElementById(d.data.name + "Props");
                            if (d.data.props === "undefined") {
                                    let prop = document.createElement("li");
                                    prop.setAttribute('id', d.data.name + 'PropUndefined');
                                    prop.innerHTML = "undefined";
                                    propList.appendChild(prop);
                            }
                            else {
                                for (let i = 0; i < d.data.props.length; i += 1) {
                                    for (key in d.data.props[i]) {
                                        console.log(typeof d.data.props[i][key])
                                        let prop = document.createElement("li");
                                        prop.setAttribute('id', d.data.name[key] + 'Prop');
                                        prop.innerHTML = (typeof d.data.props[i][key] === 'object') ? key + ": Function" : key + ": " + d.data.props[i][key];
                                        propList.appendChild(prop);
                                    }
                                }
                            };

                    }

            })
        }
        }, 100);
    }
    updater()
        });
});

<|endoftext|>


<commit_before>// This JS file is for the map and list view creations on the homepage of the
// Maps4All site for the user. This example requires the Places library. Include
// the libraries=places parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">

var map;
var oms;
var markers = [];
var infowindow;

// Click listener for a marker.
function markerListener(marker, event) {
  $("#map").show();
  $("#resource-info").hide();

  var contentDiv = document.createElement('div');
  var resName = document.createElement('strong');
  $(resName).html(marker.title);
  var br = document.createElement('br');

  // information pane to replace map
  // TODO: refactor how the page elements are generated
  var moreLink = document.createElement('p');
  moreLink.innerHTML = 'More Info';
  moreLink.setAttribute('class', 'more-info')
  moreLink.addEventListener('click', function() {
    // get descriptor information
    $.get('get-associations/' + marker.resourceID).done(function(associations) {
      $("#map").hide();
      $("#resource-info").empty();
      $("#resource-info").show();

      var backButton = document.createElement('button');
      backButton.innerHTML = 'Back';
      backButton.setAttribute('class', 'ui button');
      backButton.addEventListener('click', function() {
        $("#map").show();
        $("#resource-info").hide();
      });
      $("#resource-info").append(backButton);

      var associationObject = JSON.parse(associations);
      for (var key in associationObject) {
        var descriptor = document.createElement('span');
        descriptor.setAttribute('class', 'descriptor');
        var name = document.createElement('strong');
        $(name).append(key);
        var value = associationObject[key];
        $(descriptor).append(name, ': ', value);
        $("#resource-info").append(descriptor);
      }
      var a = document.createElement('a');
      $(a).attr('href', 'suggestion/' + marker.resourceID);
      $(a).html('Suggest an edit for this resource');
      $("#resource-info").append(a);
    }).fail(function() {});
  });
  $(contentDiv).append(resName, br, marker.address, moreLink);

  if (infowindow) {
    infowindow.close();
  }
  infowindow = new google.maps.InfoWindow({
    content: contentDiv
  });
  infowindow.open(map, marker);
}

/*
 * Initializes the map, the corresponding list of resources and search
 * functionality on the resources
 */
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 39.949, lng: -75.181}, // TODO(#52): Do not hardcode this.
    zoom: 13
  });

  oms = new OverlappingMarkerSpiderfier(map, {keepSpiderfied: true, nearbyDistance: 10});

  // Add click listener to marker for displaying infoWindow
  oms.addListener('click', markerListener);

  infowindow = new google.maps.InfoWindow();
  initLocationSearch(map);
  initResourceSearch();

  $.get('/get-resources').done(function(resourcesString) {
    var resources = JSON.parse(resourcesString);
    populateMarkers(resources);
  });

  google.maps.event.addListenerOnce(map, 'idle', function() {
    populateListDiv();
  });
}

/*
 * Initialize searching on the map by location input.
 * When entering a new location, re-center and zoom the map onto that location
 * and create a custom location marker.
 */
function initLocationSearch(map) {
  var input = document.getElementById('pac-input');
  var autocomplete = new google.maps.places.Autocomplete(input);
  autocomplete.bindTo('bounds', map);

  autocomplete.addListener('place_changed', function() {
    infowindow.close();
    var place = autocomplete.getPlace();
    if (!place.geometry) {
      window.alert("Autocomplete's returned place contains no geometry");
      return;
    }
    map.setCenter(place.geometry.location);
    map.setZoom(17);

    var address = '';
    if (place.address_components) {
      address = [
        ((place.address_components[0] &&
          place.address_components[0].short_name) || ''),
        ((place.address_components[1] &&
          place.address_components[1].short_name) || ''),
        ((place.address_components[2] &&
          place.address_components[2].short_name) || '')
      ].join(' ');
    }

    var marker = new google.maps.Marker({
      map: map,
      position: place.geometry.location,
      icon: {
        path: google.maps.SymbolPath.CIRCLE,
        scale: 8,
        strokeColor: 'grey',
        fillColor: 'black',
        fillOpacity: 1,
      },
    });
    var placeDiv = document.createElement('div');
    var br = document.createElement('br');
    $(placeDiv).append(place.name, br, address);
    infowindow.setContent(placeDiv);
    infowindow.open(map, marker);
  });
}

/*
 * Initialize searching on the map by resource name input
 */
function initResourceSearch() {
  $('#resources-form').submit(function(e) {
    e.preventDefault();
    var query = $('#resources-input').val();
    var endpoint = '/search-resources/'+query;
    if (query.length == 0) {
        endpoint = '/get-resources';
    }
    $.get(endpoint).done(function(resourcesString) {
      for (var i = 0; i < markers.length; i++) {
        markers[i].setMap(null);
      }
      markers = [];
      var resources = JSON.parse(resourcesString);
      if (resources.length != 0) {
        populateMarkers(resources);
      }
      populateListDiv();
    });
  });
}

/*
 * Create markers for each resource and add them to the map
 * Expand the map to show all resources
 */
function populateMarkers(resources) {
  for (var i = 0; i < resources.length; i++) {
    createMarker(resources[i]);
  }

  var bounds = new google.maps.LatLngBounds();
  for (var i = 0; i < markers.length; i++) {
    markers[i].setMap(map);
    oms.addMarker(markers[i]);
    bounds.extend(markers[i].getPosition());
  }

  map.fitBounds(bounds);
  map.setCenter(bounds.getCenter());
}

/*
 * Create a marker for each resource and handle clicking on a marker.
 * Handle clicking on more information for a resource
 */
function createMarker(resource) {
  var markerToAdd = new google.maps.Marker({
    map: map
  });
  latLng = new google.maps.LatLng(resource.latitude, resource.longitude);
  markerToAdd.setPosition(latLng);
  markerToAdd.setVisible(true);
  markerToAdd.title = resource.name;
  markerToAdd.json_data = {
    csrf_token: $('meta[name="csrf-token"]').prop('content'),
    data: resource.name
  };
  markerToAdd.resourceID = resource.id;
  markerToAdd.address = resource.address;

  markers.push(markerToAdd);

}

/*
 * Show a corresponding list of resources adjacent to the map
 */
function populateListDiv() {
  var markersToShow = markers;
  $("#list").empty();

  var list = document.getElementById('list');
  $.each(markersToShow, function(i, markerToShow) {
    var listElement = document.createElement('a');
    listElement.setAttribute('class', 'item');

    var rightArrowElement = document.createElement('div');
    rightArrowElement.setAttribute('class', 'right floated content');

    var rightArrowImage = document.createElement('i');
    rightArrowImage.setAttribute('class', 'right chevron icon');

    rightArrowElement.appendChild(rightArrowImage);
    listElement.appendChild(rightArrowElement);

    var mainContent = document.createElement('div');
    mainContent.setAttribute('class', 'content');

    var placeTitle = document.createElement('div');
    placeTitle.setAttribute('class', 'list-item-title');
    $(placeTitle).html(markerToShow.title);

    mainContent.appendChild(placeTitle);
    listElement.appendChild(mainContent);

    list.appendChild(listElement);

    listElement.addEventListener('click', function() {
      markerListener(markerToShow, null);
    });
  });
}

// When the DOM is ready, init the map.
$(document).ready(initMap);
<commit_msg>add click string literal<commit_after>// This JS file is for the map and list view creations on the homepage of the
// Maps4All site for the user. This example requires the Places library. Include
// the libraries=places parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">

var map;
var oms;
var markers = [];
var infowindow;

// Click listener for a marker.
function markerListener(marker, event) {
  $("#map").show();
  $("#resource-info").hide();

  var contentDiv = document.createElement('div');
  var resName = document.createElement('strong');
  $(resName).html(marker.title);
  var br = document.createElement('br');

  // information pane to replace map
  // TODO: refactor how the page elements are generated
  var moreLink = document.createElement('p');
  moreLink.innerHTML = 'More Info';
  moreLink.setAttribute('class', 'more-info')
  moreLink.addEventListener('click', function() {
    // get descriptor information
    $.get('get-associations/' + marker.resourceID).done(function(associations) {
      $("#map").hide();
      $("#resource-info").empty();
      $("#resource-info").show();

      var backButton = document.createElement('button');
      backButton.innerHTML = 'Back';
      backButton.setAttribute('class', 'ui button');
      backButton.addEventListener('click', function() {
        $("#map").show();
        $("#resource-info").hide();
      });
      $("#resource-info").append(backButton);

      var associationObject = JSON.parse(associations);
      for (var key in associationObject) {
        var descriptor = document.createElement('span');
        descriptor.setAttribute('class', 'descriptor');
        var name = document.createElement('strong');
        $(name).append(key);
        var value = associationObject[key];
        $(descriptor).append(name, ': ', value);
        $("#resource-info").append(descriptor);
      }
      var a = document.createElement('a');
      $(a).attr('href', 'suggestion/' + marker.resourceID);
      $(a).html('Suggest an edit for this resource');
      $("#resource-info").append(a);
    }).fail(function() {});
  });
  $(contentDiv).append(resName, br, marker.address, moreLink);

  if (infowindow) {
    infowindow.close();
  }
  infowindow = new google.maps.InfoWindow({
    content: contentDiv
  });
  infowindow.open(map, marker);
}

/*
 * Initializes the map, the corresponding list of resources and search
 * functionality on the resources
 */
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: 39.949, lng: -75.181}, // TODO(#52): Do not hardcode this.
    zoom: 13
  });

  oms = new OverlappingMarkerSpiderfier(map, {keepSpiderfied: true, nearbyDistance: 10});

  // Add click listener to marker for displaying infoWindow
  oms.addListener('click', markerListener);

  infowindow = new google.maps.InfoWindow();
  initLocationSearch(map);
  initResourceSearch();

  $.get('/get-resources').done(function(resourcesString) {
    var resources = JSON.parse(resourcesString);
    populateMarkers(resources);
  });

  google.maps.event.addListenerOnce(map, 'idle', function() {
    populateListDiv();
  });
}

/*
 * Initialize searching on the map by location input.
 * When entering a new location, re-center and zoom the map onto that location
 * and create a custom location marker.
 */
function initLocationSearch(map) {
  var input = document.getElementById('pac-input');
  var autocomplete = new google.maps.places.Autocomplete(input);
  autocomplete.bindTo('bounds', map);

  autocomplete.addListener('place_changed', function() {
    infowindow.close();
    var place = autocomplete.getPlace();
    if (!place.geometry) {
      window.alert("Autocomplete's returned place contains no geometry");
      return;
    }
    map.setCenter(place.geometry.location);
    map.setZoom(17);

    var address = '';
    if (place.address_components) {
      address = [
        ((place.address_components[0] &&
          place.address_components[0].short_name) || ''),
        ((place.address_components[1] &&
          place.address_components[1].short_name) || ''),
        ((place.address_components[2] &&
          place.address_components[2].short_name) || '')
      ].join(' ');
    }

    var marker = new google.maps.Marker({
      map: map,
      position: place.geometry.location,
      icon: {
        path: google.maps.SymbolPath.CIRCLE,
        scale: 8,
        strokeColor: 'grey',
        fillColor: 'black',
        fillOpacity: 1,
      },
    });
    var placeDiv = document.createElement('div');
    var br = document.createElement('br');
    $(placeDiv).append(place.name, br, address);
    infowindow.setContent(placeDiv);
    infowindow.open(map, marker);
  });
}

/*
 * Initialize searching on the map by resource name input
 */
function initResourceSearch() {
  $('#resources-form').submit(function(e) {
    e.preventDefault();
    var query = $('#resources-input').val();
    var endpoint = '/search-resources/'+query;
    if (query.length == 0) {
        endpoint = '/get-resources';
    }
    $.get(endpoint).done(function(resourcesString) {
      for (var i = 0; i < markers.length; i++) {
        markers[i].setMap(null);
      }
      markers = [];
      var resources = JSON.parse(resourcesString);
      if (resources.length != 0) {
        populateMarkers(resources);
      }
      populateListDiv();
    });
  });
}

/*
 * Create markers for each resource and add them to the map
 * Expand the map to show all resources
 */
function populateMarkers(resources) {
  for (var i = 0; i < resources.length; i++) {
    createMarker(resources[i]);
  }

  var bounds = new google.maps.LatLngBounds();
  for (var i = 0; i < markers.length; i++) {
    markers[i].setMap(map);
    oms.addMarker(markers[i]);
    bounds.extend(markers[i].getPosition());
  }

  map.fitBounds(bounds);
  map.setCenter(bounds.getCenter());
}

/*
 * Create a marker for each resource and handle clicking on a marker.
 * Handle clicking on more information for a resource
 */
function createMarker(resource) {
  var markerToAdd = new google.maps.Marker({
    map: map
  });
  latLng = new google.maps.LatLng(resource.latitude, resource.longitude);
  markerToAdd.setPosition(latLng);
  markerToAdd.setVisible(true);
  markerToAdd.title = resource.name;
  markerToAdd.json_data = {
    csrf_token: $('meta[name="csrf-token"]').prop('content'),
    data: resource.name
  };
  markerToAdd.resourceID = resource.id;
  markerToAdd.address = resource.address;

  markers.push(markerToAdd);

}

/*
 * Show a corresponding list of resources adjacent to the map
 */
function populateListDiv() {
  var markersToShow = markers;
  $("#list").empty();

  var list = document.getElementById('list');
  $.each(markersToShow, function(i, markerToShow) {
    var listElement = document.createElement('a');
    listElement.setAttribute('class', 'item');

    var rightArrowElement = document.createElement('div');
    rightArrowElement.setAttribute('class', 'right floated content');

    var rightArrowImage = document.createElement('i');
    rightArrowImage.setAttribute('class', 'right chevron icon');

    rightArrowElement.appendChild(rightArrowImage);
    listElement.appendChild(rightArrowElement);

    var mainContent = document.createElement('div');
    mainContent.setAttribute('class', 'content');

    var placeTitle = document.createElement('div');
    placeTitle.setAttribute('class', 'list-item-title');
    $(placeTitle).html(markerToShow.title);

    mainContent.appendChild(placeTitle);
    listElement.appendChild(mainContent);

    list.appendChild(listElement);

    listElement.addEventListener('click', function() {
      markerListener(markerToShow, 'click');
    });
  });
}

// When the DOM is ready, init the map.
$(document).ready(initMap);
<|endoftext|>


<commit_before>(function(){
'use strict';

var _             = require('lodash'),
    Typpo         = require('../../common/typpo'),
    GameStatus    = require('../../common/game-status'),
    gameConstants = require('../../common/game-constants');

var Game = function() {
  this.findingText = null;
  this.countdownText = null;
  this.lastCount = null;
  this.lagBlocks = null;

  this.gameStatus = GameStatus.NOTLIVE;
  this.startCountDown = false;
  this.dots = 0;
  this.nextDot = 0;
  this.successCounter = 0;
  this.successTreshold = 5;
  this.countdown = 3;
  this.lagBlocks = [];

  exports.gameStatus = this.gameStatus;
};

Game.prototype.create = function() {
  this.game.socketHandler.findMatch(this);
  this.game.physics.startSystem(Phaser.Physics.Arcade);
  this.game.stage.backgroundColor = '#C8F7C5';

  this.countdownText = this.add.text(
    this.world.centerX, // Temporary X (will be set)
    this.world.centerY, // Temporary Y (will be set)
    '', // Text
    { // style
      font: '90pt hallo_sansblack',
      fill: '#e67e22',
      align: 'center'
    }
  );
  this.countdownText.anchor.setTo(0.5, 0.5);

  this.findingText = this.add.text(
    this.world.centerX,
    this.world.centerY - 150,
    'Finding opponent', // Text
    { // style
      font: '45pt hallo_sansblack',
      fill: '#e67e22',
      align: 'center'
    }
  );
  this.findingText.x = this.world.width/2 - this.findingText.width/2;

  this.loader = this.game.add.sprite(
    this.world.centerX,
    this.world.centerY + 75,
    'loadingAnimation'
  );
  this.loader.anchor.setTo(0.5, 0.5);

  this.loader.animations.add('loop');
  this.loader.animations.play('loop', 15, true);

  this.lastCount = this.time.now;

  this.input.keyboard.addCallbacks(this, this.keyHandler);
};

Game.prototype.update = function() {
  if (this.startCountDown) {
    if (this.gameStatus === GameStatus.NOTLIVE) {
      this.countdownText.setText(String(this.countdown));
      this.countdownText.parent.bringToTop(this.countdownText);

      if (this.countdown > 0) {
        if (this.time.elapsedSecondsSince(this.lastCount) > 1) {
          console.log(this.countdown);
          this.countdown--;
          this.lastCount = this.time.now;
        }
      }
    }
    else {
      if (this.gameStatus === GameStatus.LIVE) {
        this.player1.tick();
        this.player2.tick();
        this.fadeLagBlocks();
      }
      else {
        console.log('Game done.');
      }
    }
  }
  else {
    if (this.time.now > this.nextDot) {
      if (this.dots === 3) {
        this.dots = 0;
      }
      this.dots++;
      var dotString = '';
      for (var i = 0; i < this.dots; i++) {
        dotString += '.';
      }

      this.findingText.text = 'Finding opponent' + dotString;
      this.nextDot = this.time.now + 500;
    }
  }
};

Game.prototype.startCountdown = function(players, wordList) {
  this.findingText.destroy();
  this.loader.destroy();

  this.player1 = new Typpo(this, true, true, _.cloneDeep(wordList), {
    width: gameConstants.WIDTH,
    height: gameConstants.HEIGHT,
    positionX: gameConstants.TILE_SIZE.x,
    positionY: 0
  });

  this.player2 = new Typpo(this, true, false, _.cloneDeep(wordList), {
    width: gameConstants.WIDTH,
    height: gameConstants.HEIGHT,
    positionX: this.player1.getEndX() + gameConstants.TILE_SIZE.x,
    positionY: 0
  });

  this.startCountDown = true;
};

Game.prototype.startGame = function(startTime) {
  console.log('starting now!', startTime);
  this.gameStatus = GameStatus.LIVE;
  this.player1.startGame(startTime);
  this.player2.startGame(startTime);
  this.countdownText.destroy();
};

Game.prototype.keyHandler = function(e) {
  if (this.gameStatus === GameStatus.LIVE) {
    var letter = String.fromCharCode(parseInt(e.keyIdentifier.slice(1), 16)).toLowerCase();
    if (/[a-z0-9]/.test(letter)) {
      // TODO: This will only work for English words, if the game should be translated this needs to be fixed

      if (this.player1.currentBlock === null) {
        _.some(this.player1.getAliveBlocks(), function(block) {
          if (letter === block.next.letter) {
            this.player1.fadeBlock(block);
            // Play good sound
            return true;
          }
        }, this);
      }
      else if (letter === this.player1.currentBlock.next.letter) {
        if (this.player1.fadeBlock()) {
          this.successCounter++;
          if (this.successCounter > this.successTreshold) {
            this.player2.greyCounter++;
            this.game.socketHandler.sendGrey();
            this.successCounter = 0;
          }
        }
      }
      else {
        this.successCounter = 0;
      }
    }

    else if (e.keyIdentifier === 'Enter') {
      this.player1.giveUpCurrentBlock();
      this.successCounter = 0;
    }
  }
};

Game.prototype.fadeLagBlocks = function() {
  // If the blocks don't exist at this exact moment the user is probably lagging behind or similar,
  // which means the blocks most likely will appear later on.
  var pre = this.lagBlocks;
  _.forEach(this.lagBlocks, function(lagBlockID, index) {
    if (lagBlockID === 'giveUp' && this.player2.currentBlock !== null) {
      this.player2.giveUpCurrentBlock();
      this.lagBlocks = _.without(this.lagBlocks, lagBlockID);
    }
    else {
      var lagBlock = _.find(this.player2.blocks, { 'id': lagBlockID });
      if (lagBlock !== undefined) {
        this.player2.fadeBlock(lagBlock);
        this.lagBlocks = _.without(this.lagBlocks, lagBlockID);
      }
    }
  }, this);
};

Game.prototype.fadeBlock = function(blockID) {
  this.lagBlocks.push(blockID);
};

Game.prototype.giveUpCurrentBlock = function() {
  this.lagBlocks.push('giveUp');
};

Game.prototype.gameDone = function(gameWon, emit) {
  if (gameWon) {
    this.gameStatus = GameStatus.WON;
  }
  else {
    this.gameStatus = GameStatus.LOST;
  }

  if (emit) {
    this.game.socketHandler.gameDone();
  }

  exports.gameStatus = this.gameStatus;
  this.game.state.start('Done');
};

Game.prototype.render = function() {
  if (false) {
    for (var i = 0; i < this.player1.blocks.length; i++) {
      for (var j = 0; j < this.player1.blocks[i].cells.length; j++) {
        this.game.debug.body(this.player1.blocks[i].cells[j].sprite);
      }
    }
  }
};

Game.prototype.addGrey = function() {
  this.player1.greyCounter++;
};

Game.prototype.centerEntity = function(entity, centerX, centerY) {
  if (centerX) {
    entity.x = this.world.width/2 - entity.width/2;
  }
  if (centerY) {
    entity.y = this.world.height/2 - entity.height/2;
  }
};

exports.constructor = Game;

})();

<commit_msg>Add an instruction to the finding game screen<commit_after>(function(){
'use strict';

var _             = require('lodash'),
    Typpo         = require('../../common/typpo'),
    GameStatus    = require('../../common/game-status'),
    gameConstants = require('../../common/game-constants');

var Game = function() {
  this.findingText = null;
  this.countdownText = null;
  this.lastCount = null;
  this.lagBlocks = null;

  this.gameStatus = GameStatus.NOTLIVE;
  this.startCountDown = false;
  this.dots = 0;
  this.nextDot = 0;
  this.successCounter = 0;
  this.successTreshold = 5;
  this.countdown = 3;
  this.lagBlocks = [];

  exports.gameStatus = this.gameStatus;
};

Game.prototype.create = function() {
  this.game.socketHandler.findMatch(this);
  this.game.physics.startSystem(Phaser.Physics.Arcade);
  this.game.stage.backgroundColor = '#C8F7C5';

  this.countdownText = this.add.text(
    this.world.centerX,
    this.world.centerY,
    '', // Text
    { // style
      font: '90pt hallo_sansblack',
      fill: '#e67e22',
      align: 'center'
    }
  );
  this.countdownText.anchor.setTo(0.5, 0.5);

  this.findingText = this.add.text(
    this.world.centerX,
    this.world.centerY - 150,
    'Finding opponent',
    {
      font: '45pt hallo_sansblack',
      fill: '#e67e22',
      align: 'center'
    }
  );
  this.findingText.x = this.world.width/2 - this.findingText.width/2;

  this.hintText = this.add.text(
    this.world.centerX,
    this.world.centerY+250,
    'Hint: Write the words \non the falling blocks!',
    {
      font: '30pt hallo_sansblack',
      fill: '#e67e22',
      align: 'center'
    }
  );
  this.hintText.anchor.setTo(0.5, 0.5);
  this.loader = this.game.add.sprite(
    this.world.centerX,
    this.world.centerY + 75,
    'loadingAnimation'
  );
  this.loader.anchor.setTo(0.5, 0.5);

  this.loader.animations.add('loop');
  this.loader.animations.play('loop', 15, true);

  this.lastCount = this.time.now;

  this.input.keyboard.addCallbacks(this, this.keyHandler);
};

Game.prototype.update = function() {
  if (this.startCountDown) {
    if (this.gameStatus === GameStatus.NOTLIVE) {
      this.countdownText.setText(String(this.countdown));
      this.countdownText.parent.bringToTop(this.countdownText);

      if (this.countdown > 0) {
        if (this.time.elapsedSecondsSince(this.lastCount) > 1) {
          console.log(this.countdown);
          this.countdown--;
          this.lastCount = this.time.now;
        }
      }
    }
    else {
      if (this.gameStatus === GameStatus.LIVE) {
        this.player1.tick();
        this.player2.tick();
        this.fadeLagBlocks();
      }
      else {
        console.log('Game done.');
      }
    }
  }
  else {
    if (this.time.now > this.nextDot) {
      if (this.dots === 3) {
        this.dots = 0;
      }
      this.dots++;
      var dotString = '';
      for (var i = 0; i < this.dots; i++) {
        dotString += '.';
      }

      this.findingText.text = 'Finding opponent' + dotString;
      this.nextDot = this.time.now + 500;
    }
  }
};

Game.prototype.startCountdown = function(players, wordList) {
  this.hintText.destroy();
  this.findingText.destroy();
  this.loader.destroy();

  this.player1 = new Typpo(this, true, true, _.cloneDeep(wordList), {
    width: gameConstants.WIDTH,
    height: gameConstants.HEIGHT,
    positionX: gameConstants.TILE_SIZE.x,
    positionY: 0
  });

  this.player2 = new Typpo(this, true, false, _.cloneDeep(wordList), {
    width: gameConstants.WIDTH,
    height: gameConstants.HEIGHT,
    positionX: this.player1.getEndX() + gameConstants.TILE_SIZE.x,
    positionY: 0
  });

  this.startCountDown = true;
};

Game.prototype.startGame = function(startTime) {
  console.log('starting now!', startTime);
  this.gameStatus = GameStatus.LIVE;
  this.player1.startGame(startTime);
  this.player2.startGame(startTime);
  this.countdownText.destroy();
};

Game.prototype.keyHandler = function(e) {
  if (this.gameStatus === GameStatus.LIVE) {
    var letter = String.fromCharCode(parseInt(e.keyIdentifier.slice(1), 16)).toLowerCase();
    if (/[a-z0-9]/.test(letter)) {
      // TODO: This will only work for English words, if the game should be translated this needs to be fixed

      if (this.player1.currentBlock === null) {
        _.some(this.player1.getAliveBlocks(), function(block) {
          if (letter === block.next.letter) {
            this.player1.fadeBlock(block);
            // Play good sound
            return true;
          }
        }, this);
      }
      else if (letter === this.player1.currentBlock.next.letter) {
        if (this.player1.fadeBlock()) {
          this.successCounter++;
          if (this.successCounter > this.successTreshold) {
            this.player2.greyCounter++;
            this.game.socketHandler.sendGrey();
            this.successCounter = 0;
          }
        }
      }
      else {
        this.successCounter = 0;
      }
    }

    else if (e.keyIdentifier === 'Enter') {
      this.player1.giveUpCurrentBlock();
      this.successCounter = 0;
    }
  }
};

Game.prototype.fadeLagBlocks = function() {
  // If the blocks don't exist at this exact moment the user is probably lagging behind or similar,
  // which means the blocks most likely will appear later on.
  var pre = this.lagBlocks;
  _.forEach(this.lagBlocks, function(lagBlockID, index) {
    if (lagBlockID === 'giveUp' && this.player2.currentBlock !== null) {
      this.player2.giveUpCurrentBlock();
      this.lagBlocks = _.without(this.lagBlocks, lagBlockID);
    }
    else {
      var lagBlock = _.find(this.player2.blocks, { 'id': lagBlockID });
      if (lagBlock !== undefined) {
        this.player2.fadeBlock(lagBlock);
        this.lagBlocks = _.without(this.lagBlocks, lagBlockID);
      }
    }
  }, this);
};

Game.prototype.fadeBlock = function(blockID) {
  this.lagBlocks.push(blockID);
};

Game.prototype.giveUpCurrentBlock = function() {
  this.lagBlocks.push('giveUp');
};

Game.prototype.gameDone = function(gameWon, emit) {
  if (gameWon) {
    this.gameStatus = GameStatus.WON;
  }
  else {
    this.gameStatus = GameStatus.LOST;
  }

  if (emit) {
    this.game.socketHandler.gameDone();
  }

  exports.gameStatus = this.gameStatus;
  this.game.state.start('Done');
};

Game.prototype.render = function() {
  if (false) {
    for (var i = 0; i < this.player1.blocks.length; i++) {
      for (var j = 0; j < this.player1.blocks[i].cells.length; j++) {
        this.game.debug.body(this.player1.blocks[i].cells[j].sprite);
      }
    }
  }
};

Game.prototype.addGrey = function() {
  this.player1.greyCounter++;
};

Game.prototype.centerEntity = function(entity, centerX, centerY) {
  if (centerX) {
    entity.x = this.world.width/2 - entity.width/2;
  }
  if (centerY) {
    entity.y = this.world.height/2 - entity.height/2;
  }
};

exports.constructor = Game;

})();

<|endoftext|>