<commit_before>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { reverse } from "../helpers";

export const title = "Day 16: Aunt Sue";

export function parse(input) {
    return _.map((line) => {
        const sentence = line.replace("Sue", "index");
        const properties = sentence.match(/[a-z]+/g);
        const values = sentence.match(/\d+/g);

        return _.reduce((attributes, property, index) => _.assign(attributes, {
            [property]: parseInt(values[index], 10)
        }), {}, properties);
    }, input);
}

export function getMatchScore(subject, sample) {
    const matches = _.map((key) => {
        const a = subject[key];
        const b = sample[key];

        if (_.isUndefined(b))
            return false;

        return (_.isEqual(a, b)) ? true : null;
    }, _.keys(subject));

    if (_.includes(null, matches))
        return null;

    if (!matches.length)
        return 0;

    return (_.filter(_.isEqual(true), matches).length / matches.length) * 100;
}

export function getMatchScoreV2(subject, sample) {
    const matches = _.map((key) => {
        const a = subject[key];
        const b = sample[key];

        if (_.isUndefined(b))
            return false;

        if (_.includes(key, ["cats", "trees"]) && _.gt(a, b))
            return true;

        if (_.includes(key, ["pomeranians", "goldfish"]) && _.lt(a, b))
            return true;

        return _.isEqual(a, b) ? true : null;
    }, _.keys(subject));

    if (_.includes(null, matches))
        return null;

    if (!matches.length)
        return 0;

    return (_.filter(_.isEqual(true), matches).length / matches.length) * 100;
}

export function analysisMachine(matcher, entries, subject) {
    const isNull = _.compose(_.isEqual(null), _.get("score"));
    const matches = _.map((sample) => ({
        index: sample.index,
        score: matcher(subject, sample)
    }), entries);

    return reverse(_.sortBy("score", _.filter(_.negate(isNull), matches)));
}

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim().split("\n");
    const entries = parse(input);
    const matches = analysisMachine(getMatchScore, entries, {
        children: 3,
        cats: 7,
        samoyeds: 2,
        pomeranians: 3,
        akitas: 0,
        vizslas: 0,
        goldfish: 5,
        trees: 3,
        cars: 2,
        perfumes: 1
    });

    console.log("What is the number of the Sue that got you the gift?", _.get("index", _.first(matches)));
}
<commit_msg>Completed part 2. - Spoiler alert: the answer is 405.<commit_after>import FS from "fs";
import Path from "path";
import _ from "lodash-fp";
import { reverse } from "../helpers";

export const title = "Day 16: Aunt Sue";

export function parse(input) {
    return _.map((line) => {
        const sentence = line.replace("Sue", "index");
        const properties = sentence.match(/[a-z]+/g);
        const values = sentence.match(/\d+/g);

        return _.reduce((attributes, property, index) => _.assign(attributes, {
            [property]: parseInt(values[index], 10)
        }), {}, properties);
    }, input);
}

export function getMatchScore(subject, sample) {
    const matches = _.map((key) => {
        const a = subject[key];
        const b = sample[key];

        if (_.isUndefined(b))
            return false;

        return (_.isEqual(a, b)) ? true : null;
    }, _.keys(subject));

    if (_.includes(null, matches))
        return null;

    if (!matches.length)
        return 0;

    return (_.filter(_.isEqual(true), matches).length / matches.length) * 100;
}

export function getMatchScoreV2(subject, sample) {
    const matches = _.map((key) => {
        const a = subject[key];
        const b = sample[key];

        if (_.isUndefined(b))
            return false;

        if (_.includes(key, ["cats", "trees"]) && _.gt(a, b))
            return true;

        if (_.includes(key, ["pomeranians", "goldfish"]) && _.lt(a, b))
            return true;

        return _.isEqual(a, b) ? true : null;
    }, _.keys(subject));

    if (_.includes(null, matches))
        return null;

    if (!matches.length)
        return 0;

    return (_.filter(_.isEqual(true), matches).length / matches.length) * 100;
}

export function analysisMachine(matcher, entries, subject) {
    const isNull = _.compose(_.isEqual(null), _.get("score"));
    const matches = _.map((sample) => ({
        index: sample.index,
        score: matcher(subject, sample)
    }), entries);

    return reverse(_.sortBy("score", _.filter(_.negate(isNull), matches)));
}

export function run() {
    const inputPath = Path.join(__dirname, "input.txt");
    const input = FS.readFileSync(inputPath, "utf-8").trim().split("\n");
    const getIndex = _.compose(_.get("index"), _.first);
    const entries = parse(input);
    const profile = {
        children: 3,
        cats: 7,
        samoyeds: 2,
        pomeranians: 3,
        akitas: 0,
        vizslas: 0,
        goldfish: 5,
        trees: 3,
        cars: 2,
        perfumes: 1
    };

    console.log("What is the number of the Sue that got you the gift?", getIndex(analysisMachine(getMatchScore, entries, profile)));
    console.log("What is the number of the real Aunt Sue?", getIndex(analysisMachine(getMatchScoreV2, entries, profile)));
}
<|endoftext|>


<commit_before>/**
 * @file Check entity definition
 * @since 0.1.6
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GpfEntityDefinition*/ // Entity definition
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfFunc*/ // Create a new function using the source
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*exported _gpfDefineGenerate$Keys*/ // Generate an array of names prefixed with $ from a comma separated list
/*#endif*/

/**
 * Generate an array of names prefixed with $ from a comma separated list
 *
 * @param {String} names Comma separated list of name
 * @return {String[]} Array of names prefixed with "$"
 * @since 0.1.6
 */
function _gpfDefineGenerate$Keys (names) {
    return names.split(",").map(function (name) {
        return "$" + name;
    });
}

_gpfErrorDeclare("define/check", {
    /**
     * ### Summary
     *
     * One of the $ properties is invalid in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * The list of possible $ properties is fixed and depends on the entity type.
     * This error is thrown when one $ property is not allowed.
     * @since 0.1.6
     */
    invalidEntity$Property: "Invalid entity $ property",

    /**
     * ### Summary
     *
     * Entity name is missing in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * This error is thrown when the entity name is missing
     * @since 0.1.6
     */
    missingEntityName: "Missing entity name",

    /**
     * ### Summary
     *
     * Entity namespace is invalid in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * This error is thrown when the namespace is invalid
     * @since 0.1.6
     */
    invalidEntityNamespace: "Invalid entity namespace"

});

function _gpfDefineEntityCheckNameIsNotEmpty () {
    /*jshint validthis:true*/ // constructor
    /*eslint-disable no-invalid-this*/
    if (!this._name) {
        gpf.Error.missingEntityName();
    }
    /*eslint-enable no-invalid-this*/
}

function _gpfDefineEntityCheckProperty (value, name) {
    _gpfIgnore(value);
    /*jshint -W040*/ /*eslint-disable no-invalid-this*/ // bound through thisArg
    if (name.charAt(0) === "$") {
        this._check$Property(name);
    } else {
        this._checkProperty(name);
    }
    /*jshint -W040*/ /*eslint-enable no-invalid-this*/
}

Object.assign(_GpfEntityDefinition.prototype, /** @lends _GpfEntityDefinition.prototype */ {

    /**
     * Entity type (class...)
     *
     * @readonly
     * @since 0.1.6
     */
    _type: "",

    /**
     * List of allowed $ properties
     *
     * @type {String[]}
     * @readonly
     * @since 0.1.6
     */
    _allowed$Properties: _gpfDefineGenerate$Keys("type,name,namespace"),

    /**
     * Check if the $ property is allowed
     *
     * @param {String} name $ Property name
     * @see _GpfEntityDefinition.prototype._allowed$Properties
     * @throws {gpf.Error.InvalidEntity$Property}
     * @since 0.1.6
     */
    _check$Property: function (name) {
        if (-1 === this._allowed$Properties.indexOf(name)) {
            gpf.Error.invalidEntity$Property();
        }
    },

    /**
     * Check if the property is allowed
     * NOTE: $ properties are handled by {@link _check$Property}
     *
     * @param {String} name Property name
     * @since 0.1.6
     */
    _checkProperty: _gpfFunc(["name"], " "),

    /**
     * Check the properties contained in the definition passed to {@link gpf.define}
     * @since 0.1.6
     */
    _checkProperties: function () {
        _gpfObjectForEach(this._initialDefinition, _gpfDefineEntityCheckProperty, this);
    },

    /**
     * Entity name
     * @since 0.1.6
     */
    _name: "",

    /**
     * Compute name property
     * @since 0.1.6
     */
    _readName: function () {
        var definition = this._initialDefinition;
        this._name = definition["$" + this._type] || definition.$name;
    },

    /**
     * Check if name property is not empty (throw the error otherwise)
     *
     * @throws {gpf.Error.MissingEntityName}
     * @since 0.1.6
     */
    _checkNameIsNotEmpty: _gpfDefineEntityCheckNameIsNotEmpty,

    /**
     * Check name property (content)
     *
     * @throws {gpf.Error.MissingEntityName}
     * @since 0.1.6
     */
    _checkName: _gpfDefineEntityCheckNameIsNotEmpty,

    /**
     * Entity namespace
     * @since 0.1.6
     */
    _namespace: "",

    /**
     * If the name is prefixed with a namespace, isolate it and update name property
     *
     * @return {String|undefined} Namespace contained in the name or undefined if none
     * @since 0.1.6
     */
    _extractRelativeNamespaceFromName: function () {
        var parts = new RegExp("(.*)\\.([^\\.]+)$").exec(this._name);
        if (parts) {
            this._name = parts[2];
            return parts[1];
        }
    },

    /**
     * Compute namespace property
     * @since 0.1.6
     */
    _readNamespace: function () {
        var namespaces = [
            this._initialDefinition.$namespace,
            this._extractRelativeNamespaceFromName()
        ].filter(function (namespacePart) {
            return namespacePart;
        });
        if (namespaces.length > 0) {
            this._namespace = namespaces.join(".");
        }
    },

    /**
     * Check namespace property
     *
     * @throws {gpf.Error.InvalidEntityNamespace}
     * @since 0.1.6
     */
    _checkNamespace: function () {
        if (!new RegExp("^(:?[a-z_$][a-zA-Z0-9]+(:?\\.[a-z_$][a-zA-Z0-9]+)*)?$").exec(this._namespace)) {
            gpf.Error.invalidEntityNamespace();
        }
    },

    check: function () {
        this._checkProperties();
        this._readName();
        this._checkNameIsNotEmpty();
        this._readNamespace();
        this._checkName();
        this._checkNamespace();
    }

});
<commit_msg>FIX #136<commit_after>/**
 * @file Check entity definition
 * @since 0.1.6
 */
/*#ifndef(UMD)*/
"use strict";
/*global _GpfEntityDefinition*/ // Entity definition
/*global _gpfErrorDeclare*/ // Declare new gpf.Error names
/*global _gpfFunc*/ // Create a new function using the source
/*global _gpfIgnore*/ // Helper to remove unused parameter warning
/*global _gpfObjectForEach*/ // Similar to [].forEach but for objects
/*exported _gpfDefineGenerate$Keys*/ // Generate an array of names prefixed with $ from a comma separated list
/*#endif*/

/**
 * Generate an array of names prefixed with $ from a comma separated list
 *
 * @param {String} names Comma separated list of name
 * @return {String[]} Array of names prefixed with "$"
 * @since 0.1.6
 */
function _gpfDefineGenerate$Keys (names) {
    return names.split(",").map(function (name) {
        return "$" + name;
    });
}

_gpfErrorDeclare("define/check", {
    /**
     * ### Summary
     *
     * One of the $ properties is invalid in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * The list of possible $ properties is fixed and depends on the entity type.
     * This error is thrown when one $ property is not allowed.
     * @since 0.1.6
     */
    invalidEntity$Property: "Invalid entity $ property",

    /**
     * ### Summary
     *
     * Entity name is missing in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * This error is thrown when the entity name is missing
     * @since 0.1.6
     */
    missingEntityName: "Missing entity name",

    /**
     * ### Summary
     *
     * Entity namespace is invalid in the definition passed to {@link gpf.define}
     *
     * ### Description
     *
     * This error is thrown when the namespace is invalid
     * @since 0.1.6
     */
    invalidEntityNamespace: "Invalid entity namespace"

});

function _gpfDefineEntityCheckNameIsNotEmpty () {
    /*jshint validthis:true*/ // constructor
    /*eslint-disable no-invalid-this*/
    if (!this._name) {
        gpf.Error.missingEntityName();
    }
    /*eslint-enable no-invalid-this*/
}

function _gpfDefineEntityCheckProperty (value, name) {
    _gpfIgnore(value);
    /*jshint -W040*/ /*eslint-disable no-invalid-this*/ // bound through thisArg
    if (name.charAt(0) === "$") {
        this._check$Property(name, value);
    } else {
        this._checkProperty(name, value);
    }
    /*jshint -W040*/ /*eslint-enable no-invalid-this*/
}

Object.assign(_GpfEntityDefinition.prototype, /** @lends _GpfEntityDefinition.prototype */ {

    /**
     * Entity type (class...)
     *
     * @readonly
     * @since 0.1.6
     */
    _type: "",

    /**
     * List of allowed $ properties
     *
     * @type {String[]}
     * @readonly
     * @since 0.1.6
     */
    _allowed$Properties: _gpfDefineGenerate$Keys("type,name,namespace"),

    /**
     * Check if the $ property is allowed
     *
     * @param {String} name $ Property name
     * @param {*} value $ Property value
     * @see _GpfEntityDefinition.prototype._allowed$Properties
     * @throws {gpf.Error.InvalidEntity$Property}
     * @since 0.1.6
     */
    _check$Property: function (name, value) {
        _gpfIgnore(value);
        if (-1 === this._allowed$Properties.indexOf(name)) {
            gpf.Error.invalidEntity$Property();
        }
    },

    /**
     * Check if the property is allowed
     * NOTE: $ properties are handled by {@link _check$Property}
     *
     * @param {String} name Property name
     * @param {*} value Property value
     * @since 0.1.6
     */
    _checkProperty: _gpfFunc(["name", "value"], " "),

    /**
     * Check the properties contained in the definition passed to {@link gpf.define}
     * @since 0.1.6
     */
    _checkProperties: function () {
        _gpfObjectForEach(this._initialDefinition, _gpfDefineEntityCheckProperty, this);
    },

    /**
     * Entity name
     * @since 0.1.6
     */
    _name: "",

    /**
     * Compute name property
     * @since 0.1.6
     */
    _readName: function () {
        var definition = this._initialDefinition;
        this._name = definition["$" + this._type] || definition.$name;
    },

    /**
     * Check if name property is not empty (throw the error otherwise)
     *
     * @throws {gpf.Error.MissingEntityName}
     * @since 0.1.6
     */
    _checkNameIsNotEmpty: _gpfDefineEntityCheckNameIsNotEmpty,

    /**
     * Check name property (content)
     *
     * @throws {gpf.Error.MissingEntityName}
     * @since 0.1.6
     */
    _checkName: _gpfDefineEntityCheckNameIsNotEmpty,

    /**
     * Entity namespace
     * @since 0.1.6
     */
    _namespace: "",

    /**
     * If the name is prefixed with a namespace, isolate it and update name property
     *
     * @return {String|undefined} Namespace contained in the name or undefined if none
     * @since 0.1.6
     */
    _extractRelativeNamespaceFromName: function () {
        var parts = new RegExp("(.*)\\.([^\\.]+)$").exec(this._name);
        if (parts) {
            this._name = parts[2];
            return parts[1];
        }
    },

    /**
     * Compute namespace property
     * @since 0.1.6
     */
    _readNamespace: function () {
        var namespaces = [
            this._initialDefinition.$namespace,
            this._extractRelativeNamespaceFromName()
        ].filter(function (namespacePart) {
            return namespacePart;
        });
        if (namespaces.length > 0) {
            this._namespace = namespaces.join(".");
        }
    },

    /**
     * Check namespace property
     *
     * @throws {gpf.Error.InvalidEntityNamespace}
     * @since 0.1.6
     */
    _checkNamespace: function () {
        if (!new RegExp("^(:?[a-z_$][a-zA-Z0-9]+(:?\\.[a-z_$][a-zA-Z0-9]+)*)?$").exec(this._namespace)) {
            gpf.Error.invalidEntityNamespace();
        }
    },

    check: function () {
        this._checkProperties();
        this._readName();
        this._checkNameIsNotEmpty();
        this._readNamespace();
        this._checkName();
        this._checkNamespace();
    }

});
<|endoftext|>


<commit_before>'use strict';

var NAME = 'webpack-runtime-analyzer';
var parseQuery = require('loader-utils/lib/parseQuery.js');
var webpack = require('webpack');
var RequestShortener = require('webpack/lib/RequestShortener');
var fork = require('child_process').fork;
var rempl = require('rempl');
var path = require('path');
var fs = require('fs');
var opn = require('opn');

var requestShortener;

function isObject(obj) {
    return typeof obj == 'object' && obj;
}

function cloneArray(array) {
    return array.map(function(el) {
        if (Array.isArray(el)) {
            return cloneArray(el);
        }

        if (isObject(el) && el.constructor == Object) {
            return deepExtend({}, el);
        }

        return el;
    });
}

function deepExtend(target) {
    var sources = Array.prototype.slice.call(arguments, 1);

    if (typeof target != 'object' || !target) {
        return;
    }

    for (var i = 0; i < sources.length; i++) {
        var source = sources[i];

        if (isObject(source)) {
            for (var sourceKey in source) {
                if (source.hasOwnProperty(sourceKey)) {
                    var value = source[sourceKey];

                    if (Array.isArray(value)) {
                        target[sourceKey] = cloneArray(value);
                    } else if (isObject(value) && value.constructor == Object) {
                        target[sourceKey] = deepExtend({}, value);
                    } else {
                        target[sourceKey] = value;
                    }
                }
            }
        }
    }

    return target;
}

function getRetained(module, modulesMap, exclude) {
    var stack = [module.id];
    var visited = {};

    while (stack.length) {
        var currentModuleId = stack.pop();
        var currentModule = modulesMap[currentModuleId];

        if (!exclude || currentModule != exclude) {
            for (var i = 0; i < currentModule.dependencies.length; i++) {
                var dependency = currentModule.dependencies[i];

                if (!visited[dependency]) {
                    stack.push(dependency);
                    visited[dependency] = true;
                }
            }
        }
    }

    return Object.keys(visited);
}

function getModuleId(module) {
    return module.index + '_' + module.readableIdentifier(requestShortener);
}

function handleFile(file, handledFiles) {
    if (handledFiles.hasOwnProperty(file)) {
        return handledFiles[file];
    }

    if (fs.existsSync(file)) {
        var stats = fs.statSync(file);

        if (!stats.isFile) {
            return null;
        }

        handledFiles[file] = {
            name: file,
            short: requestShortener.shorten(file),
            size: stats.size
        };

        return handledFiles[file];
    }

    return null;
}

function getModuleLoaders(module, handledFiles) {
    return (module.loaders || []).map(function(loaderInfo) {
        var options = loaderInfo.options;

        if (typeof loaderInfo.options == 'string') {
            if (loaderInfo.options[0] != '?') {
                loaderInfo.options = '?' + loaderInfo.options;
            }

            options = parseQuery(loaderInfo.options);
        }

        return {
            file: handleFile(loaderInfo.loader, handledFiles),
            options: options
        };
    });
}

function startRemplServer(plugin) {
    console.info('Starting rempl server...');

    fork(path.resolve(__dirname, 'server.js'), { silent: true })
        .once('exit', function(code) {
            console.error('\n[ERROR] RuntimeAnalyzerPlugin exited with code', code);
            process.exit(code);
        })
        .on('message', function(data) {
            if (data && data.event === 'server-started') {
                plugin.publisher.wsendpoint = data.endpoint;
                plugin.publisher.sync();

                if (plugin.options.open) {
                    opn(data.endpoint);
                }
            }
        })
        .send({
            name: NAME,
            port: plugin.options.port
        });
}

function createPublisher(compiler, options) {
    // use require.main.require to get version of inspecting webpack
    // but not a webpack version uses as dependency
    var webpackVersion = require.main.require('webpack/package.json').version;
    var modulesTypeMap = {
        NormalModule: 'normal',
        MultiModule: 'multi',
        ContextModule: 'context',
        DelegatedModule: 'delegated',
        ExternalModule: 'external'
    };

    var getWebUI = rempl.scriptFromFile(options.ui);
    var publisher = rempl.createPublisher(NAME, function(settings, callback) {
        if (settings.dev) {
            return callback(null, 'url', 'http://localhost:8001/src/ui/');
        }

        getWebUI(settings, callback);
    }, {
        manualSync: options.mode === 'standalone'
    });

    var status = publisher.ns('status');
    var progress = publisher.ns('progress');
    var profile = publisher.ns('profile');
    var stats;

    compiler.plugin('compilation', function(compilation) {
        stats = compilation;
        requestShortener = new RequestShortener(compilation.options.context || process.cwd());
    });

    compiler.apply(new webpack.ProgressPlugin(function(percent) {
        status.publish('compiling');
        progress.publish(percent);
    }));

    compiler.plugin('emit', function(compilation, done) {
        var assets = [];
        var modulesMap = {};
        var entryPoints = {};
        var modules;
        var handledFiles = {};

        for (var assetName in compilation.assets) {
            if (compilation.assets.hasOwnProperty(assetName)) {
                assets.push({
                    name: assetName,
                    size: compilation.assets[assetName].size()
                });
            }
        }

        modules = compilation.modules.map(function(module) {
            var moduleType = modulesTypeMap[module.constructor.name] || 'unknown';
            var resource = module.resource ? handleFile(module.resource, handledFiles) : null;

            // webpack 1.x capability
            if (/^1\./.test(webpackVersion)) {
                var identifier = module.identifier();

                if (!identifier.indexOf('multi')) {
                    moduleType = 'multi';
                } else if (module.regExp) {
                    moduleType = 'context';
                } else if (!identifier.indexOf('delegated')) {
                    moduleType = 'delegated';
                } else if (!identifier.indexOf('external')) {
                    moduleType = 'external';
                } else if (resource) {
                    moduleType = 'normal';
                } else {
                    moduleType = 'unknown';
                }
            }

            var moduleInfo = {
                id: getModuleId(module),
                index: module.index,
                type: moduleType,
                name: module.readableIdentifier(requestShortener),
                size: module.size(),
                rawRequest: module.rawRequest,
                userRequest: module.userRequest,
                context: module.context,
                resource: resource,
                loaders: getModuleLoaders(module, handledFiles),
                dependencies: module.dependencies
                    .filter(function(dependency) {
                        return dependency.module;
                    })
                    .map(function(dependency) {
                        return getModuleId(dependency.module);
                    }),
                reasons: module.reasons
                    .filter(function(reason) {
                        return reason.dependency && reason.module;
                    })
                    .map(function(reason) {
                        return getModuleId(reason.module);
                    })
            };

            modulesMap[moduleInfo.id] = moduleInfo;
            moduleInfo.isEntry = !moduleInfo.reasons.length;

            if (moduleInfo.isEntry) {
                entryPoints[moduleInfo.id] = true;
            }

            return moduleInfo;
        });

        entryPoints = Object.keys(entryPoints);

        // console.time('Retained + Exclusive');

        modules.forEach(function(module) {
            var allSubtreeExceptSelected = {};

            module.retained = getRetained(module, modulesMap);

            if (module.retained.indexOf(module.id) == -1) {
                module.retained.push(module.id);
            }

            for (var i = 0; i < entryPoints.length; i++) {
                var nodes = getRetained(modulesMap[entryPoints[i]], modulesMap, module);

                for (var j = 0; j < nodes.length; j++) {
                    allSubtreeExceptSelected[nodes[j]] = true;
                }
            }

            module.exclusive = module.retained.filter(function(exclusive) {
                return exclusive != module.id && !allSubtreeExceptSelected[exclusive];
            });

            var ix = module.retained.indexOf(module.id);

            if (ix > -1) {
                module.retained.splice(ix, 1)
            }
        });

        // console.timeEnd('Retained + Exclusive');

        profile.publish({
            version: webpackVersion,
            hash: compilation.hash,
            context: compilation.compiler.context,
            assets: assets,
            chunks: compilation.chunks.map(function(chunk) {
                return {
                    id: chunk.id,
                    name: chunk.name,
                    size: chunk.size({}),
                    hash: chunk.renderedHash,
                    files: chunk.files,
                    modules: chunk.modules.map(function(module) {
                        return getModuleId(module);
                    }),
                    rendered: chunk.rendered,
                    // webpack 1.x capability
                    initial: typeof chunk.isInitial == 'function' ? chunk.isInitial() : chunk.initial,
                    // webpack 1.x capability
                    entry: typeof chunk.hasRuntime == 'function' ? chunk.hasRuntime() : chunk.entry
                };
            }),
            modules: modules,
            links: modules.reduce(function(prev, current) {
                var links = current.dependencies.map(function(dependency) {
                    return {
                        from: current.id,
                        to: dependency
                    };
                });

                return prev.concat(links);
            }, []),
            errors: compilation.errors.map(function(error) {
                return {
                    message: error.message,
                    module: error.module && getModuleId(error.module)
                };
            }),
            warnings: compilation.warnings.map(function(warning) {
                return {
                    message: warning.message,
                    module: warning.module && getModuleId(warning.module)
                };
            })
        });

        done();

        if (publisher.wsendpoint) {
            // use timer to output link after all the stats is shown
            setTimeout(function() {
                console.log('\nWebpack Runtime Analyzer web interface:', publisher.wsendpoint);
            });
        }
    });

    compiler.plugin('compile', function() {
        status.publish('compiling');
    });

    compiler.plugin('invalid', function() {
        status.publish('invalidated');
    });

    compiler.plugin('done', function() {
        status.publish(stats.errors.length ? 'failed' : 'success');
    });

    compiler.plugin('failed', function() {
        status.publish('failed');
    });

    return publisher;
}

function RuntimeAnalyzerPlugin(options) {
    this.publisher = null;

    // mix with default options
    this.options = deepExtend({
        mode: 'publisher',
        port: 0,
        watchModeOnly: true,
        ui: path.resolve(__dirname, '../../dist/script.js')
    }, options);
}

RuntimeAnalyzerPlugin.prototype.apply = function(compiler) {
    var options = this.options;
    var pluginMode = options.watchModeOnly ? 'watch-run' : 'run';

    compiler.plugin(pluginMode, function(watching, done) {
        if (!this.publisher) {
            this.publisher = createPublisher(compiler, options);

            // run server after publisher is created
            // since server uses publisher when started
            if (options.mode === 'standalone') {
                startRemplServer(this);
            }
        }

        done();
    }.bind(this));
};

module.exports = RuntimeAnalyzerPlugin;
<commit_msg>set standalone as default mode<commit_after>'use strict';

var NAME = 'webpack-runtime-analyzer';
var parseQuery = require('loader-utils/lib/parseQuery.js');
var webpack = require('webpack');
var RequestShortener = require('webpack/lib/RequestShortener');
var fork = require('child_process').fork;
var rempl = require('rempl');
var path = require('path');
var fs = require('fs');
var opn = require('opn');

var requestShortener;

function isObject(obj) {
    return typeof obj == 'object' && obj;
}

function cloneArray(array) {
    return array.map(function(el) {
        if (Array.isArray(el)) {
            return cloneArray(el);
        }

        if (isObject(el) && el.constructor == Object) {
            return deepExtend({}, el);
        }

        return el;
    });
}

function deepExtend(target) {
    var sources = Array.prototype.slice.call(arguments, 1);

    if (typeof target != 'object' || !target) {
        return;
    }

    for (var i = 0; i < sources.length; i++) {
        var source = sources[i];

        if (isObject(source)) {
            for (var sourceKey in source) {
                if (source.hasOwnProperty(sourceKey)) {
                    var value = source[sourceKey];

                    if (Array.isArray(value)) {
                        target[sourceKey] = cloneArray(value);
                    } else if (isObject(value) && value.constructor == Object) {
                        target[sourceKey] = deepExtend({}, value);
                    } else {
                        target[sourceKey] = value;
                    }
                }
            }
        }
    }

    return target;
}

function getRetained(module, modulesMap, exclude) {
    var stack = [module.id];
    var visited = {};

    while (stack.length) {
        var currentModuleId = stack.pop();
        var currentModule = modulesMap[currentModuleId];

        if (!exclude || currentModule != exclude) {
            for (var i = 0; i < currentModule.dependencies.length; i++) {
                var dependency = currentModule.dependencies[i];

                if (!visited[dependency]) {
                    stack.push(dependency);
                    visited[dependency] = true;
                }
            }
        }
    }

    return Object.keys(visited);
}

function getModuleId(module) {
    return module.index + '_' + module.readableIdentifier(requestShortener);
}

function handleFile(file, handledFiles) {
    if (handledFiles.hasOwnProperty(file)) {
        return handledFiles[file];
    }

    if (fs.existsSync(file)) {
        var stats = fs.statSync(file);

        if (!stats.isFile) {
            return null;
        }

        handledFiles[file] = {
            name: file,
            short: requestShortener.shorten(file),
            size: stats.size
        };

        return handledFiles[file];
    }

    return null;
}

function getModuleLoaders(module, handledFiles) {
    return (module.loaders || []).map(function(loaderInfo) {
        var options = loaderInfo.options;

        if (typeof loaderInfo.options == 'string') {
            if (loaderInfo.options[0] != '?') {
                loaderInfo.options = '?' + loaderInfo.options;
            }

            options = parseQuery(loaderInfo.options);
        }

        return {
            file: handleFile(loaderInfo.loader, handledFiles),
            options: options
        };
    });
}

function startRemplServer(plugin) {
    console.info('Starting rempl server...');

    fork(path.resolve(__dirname, 'server.js'), { silent: true })
        .once('exit', function(code) {
            console.error('\n[ERROR] RuntimeAnalyzerPlugin exited with code', code);
            process.exit(code);
        })
        .on('message', function(data) {
            if (data && data.event === 'server-started') {
                plugin.publisher.wsendpoint = data.endpoint;
                plugin.publisher.sync();

                if (plugin.options.open) {
                    opn(data.endpoint);
                }
            }
        })
        .send({
            name: NAME,
            port: plugin.options.port
        });
}

function createPublisher(compiler, options) {
    // use require.main.require to get version of inspecting webpack
    // but not a webpack version uses as dependency
    var webpackVersion = require.main.require('webpack/package.json').version;
    var modulesTypeMap = {
        NormalModule: 'normal',
        MultiModule: 'multi',
        ContextModule: 'context',
        DelegatedModule: 'delegated',
        ExternalModule: 'external'
    };

    var getWebUI = rempl.scriptFromFile(options.ui);
    var publisher = rempl.createPublisher(NAME, function(settings, callback) {
        if (settings.dev) {
            return callback(null, 'url', 'http://localhost:8001/src/ui/');
        }

        getWebUI(settings, callback);
    }, {
        manualSync: options.mode === 'standalone'
    });

    var status = publisher.ns('status');
    var progress = publisher.ns('progress');
    var profile = publisher.ns('profile');
    var stats;

    compiler.plugin('compilation', function(compilation) {
        stats = compilation;
        requestShortener = new RequestShortener(compilation.options.context || process.cwd());
    });

    compiler.apply(new webpack.ProgressPlugin(function(percent) {
        status.publish('compiling');
        progress.publish(percent);
    }));

    compiler.plugin('emit', function(compilation, done) {
        var assets = [];
        var modulesMap = {};
        var entryPoints = {};
        var modules;
        var handledFiles = {};

        for (var assetName in compilation.assets) {
            if (compilation.assets.hasOwnProperty(assetName)) {
                assets.push({
                    name: assetName,
                    size: compilation.assets[assetName].size()
                });
            }
        }

        modules = compilation.modules.map(function(module) {
            var moduleType = modulesTypeMap[module.constructor.name] || 'unknown';
            var resource = module.resource ? handleFile(module.resource, handledFiles) : null;

            // webpack 1.x capability
            if (/^1\./.test(webpackVersion)) {
                var identifier = module.identifier();

                if (!identifier.indexOf('multi')) {
                    moduleType = 'multi';
                } else if (module.regExp) {
                    moduleType = 'context';
                } else if (!identifier.indexOf('delegated')) {
                    moduleType = 'delegated';
                } else if (!identifier.indexOf('external')) {
                    moduleType = 'external';
                } else if (resource) {
                    moduleType = 'normal';
                } else {
                    moduleType = 'unknown';
                }
            }

            var moduleInfo = {
                id: getModuleId(module),
                index: module.index,
                type: moduleType,
                name: module.readableIdentifier(requestShortener),
                size: module.size(),
                rawRequest: module.rawRequest,
                userRequest: module.userRequest,
                context: module.context,
                resource: resource,
                loaders: getModuleLoaders(module, handledFiles),
                dependencies: module.dependencies
                    .filter(function(dependency) {
                        return dependency.module;
                    })
                    .map(function(dependency) {
                        return getModuleId(dependency.module);
                    }),
                reasons: module.reasons
                    .filter(function(reason) {
                        return reason.dependency && reason.module;
                    })
                    .map(function(reason) {
                        return getModuleId(reason.module);
                    })
            };

            modulesMap[moduleInfo.id] = moduleInfo;
            moduleInfo.isEntry = !moduleInfo.reasons.length;

            if (moduleInfo.isEntry) {
                entryPoints[moduleInfo.id] = true;
            }

            return moduleInfo;
        });

        entryPoints = Object.keys(entryPoints);

        // console.time('Retained + Exclusive');

        modules.forEach(function(module) {
            var allSubtreeExceptSelected = {};

            module.retained = getRetained(module, modulesMap);

            if (module.retained.indexOf(module.id) == -1) {
                module.retained.push(module.id);
            }

            for (var i = 0; i < entryPoints.length; i++) {
                var nodes = getRetained(modulesMap[entryPoints[i]], modulesMap, module);

                for (var j = 0; j < nodes.length; j++) {
                    allSubtreeExceptSelected[nodes[j]] = true;
                }
            }

            module.exclusive = module.retained.filter(function(exclusive) {
                return exclusive != module.id && !allSubtreeExceptSelected[exclusive];
            });

            var ix = module.retained.indexOf(module.id);

            if (ix > -1) {
                module.retained.splice(ix, 1)
            }
        });

        // console.timeEnd('Retained + Exclusive');

        profile.publish({
            version: webpackVersion,
            hash: compilation.hash,
            context: compilation.compiler.context,
            assets: assets,
            chunks: compilation.chunks.map(function(chunk) {
                return {
                    id: chunk.id,
                    name: chunk.name,
                    size: chunk.size({}),
                    hash: chunk.renderedHash,
                    files: chunk.files,
                    modules: chunk.modules.map(function(module) {
                        return getModuleId(module);
                    }),
                    rendered: chunk.rendered,
                    // webpack 1.x capability
                    initial: typeof chunk.isInitial == 'function' ? chunk.isInitial() : chunk.initial,
                    // webpack 1.x capability
                    entry: typeof chunk.hasRuntime == 'function' ? chunk.hasRuntime() : chunk.entry
                };
            }),
            modules: modules,
            links: modules.reduce(function(prev, current) {
                var links = current.dependencies.map(function(dependency) {
                    return {
                        from: current.id,
                        to: dependency
                    };
                });

                return prev.concat(links);
            }, []),
            errors: compilation.errors.map(function(error) {
                return {
                    message: error.message,
                    module: error.module && getModuleId(error.module)
                };
            }),
            warnings: compilation.warnings.map(function(warning) {
                return {
                    message: warning.message,
                    module: warning.module && getModuleId(warning.module)
                };
            })
        });

        done();

        if (publisher.wsendpoint) {
            // use timer to output link after all the stats is shown
            setTimeout(function() {
                console.log('\nWebpack Runtime Analyzer web interface:', publisher.wsendpoint);
            });
        }
    });

    compiler.plugin('compile', function() {
        status.publish('compiling');
    });

    compiler.plugin('invalid', function() {
        status.publish('invalidated');
    });

    compiler.plugin('done', function() {
        status.publish(stats.errors.length ? 'failed' : 'success');
    });

    compiler.plugin('failed', function() {
        status.publish('failed');
    });

    return publisher;
}

function RuntimeAnalyzerPlugin(options) {
    this.publisher = null;

    // mix with default options
    this.options = deepExtend({
        mode: 'standalone',
        port: 0,
        watchModeOnly: true,
        ui: path.resolve(__dirname, '../../dist/script.js')
    }, options);
}

RuntimeAnalyzerPlugin.prototype.apply = function(compiler) {
    var options = this.options;
    var pluginMode = options.watchModeOnly ? 'watch-run' : 'run';

    compiler.plugin(pluginMode, function(watching, done) {
        if (!this.publisher) {
            this.publisher = createPublisher(compiler, options);

            // run server after publisher is created
            // since server uses publisher when started
            if (options.mode === 'standalone') {
                startRemplServer(this);
            }
        }

        done();
    }.bind(this));
};

module.exports = RuntimeAnalyzerPlugin;
<|endoftext|>


<commit_before>/* globals $, chrome, navigator, console*/
/*jshint esnext: true */

/**
 * Екранирует HTML
 * @return {String}	Преобразованная строка
 */
function escapeHtml = function (str) {
	'use strict';
	const map = {
		'&': '&',
		'<': '<',
		'>': '>',
		'"': '"',
		"'": '''
	};
	return str.replace(/[&<>"']/g, m => map[m]);
};

/**
 * Формат даты Вконтакте
 * @return {String}
 */
Date.prototype.toStringVkFormat = function () {
	"use strict";
	const app = new App();
	const now = new Date();
	const ago = (now.getTime() - this.getTime())/1000; // Количество секунд прошедших с момента публикации
	if (ago < 10)		  return app.loc('just now');
	else if (ago < 60)	  return `${getCase(Math.floor(ago), app.loc('seconds'))} ${app.loc('ago')}`;
	else if (ago < 3600)  return `${getCase(Math.floor(ago/60), app.loc('minutes'))} ${app.loc('ago')}`;
	else if (ago < 10800) return `${getCase(Math.floor(ago/3600), app.loc('hours'))} ${app.loc('ago')}`;
	else {
		const h = this.getHours()	< 10 ? `0${this.getHours()}`   : this.getHours();
		const m = this.getMinutes() < 10 ? `0${this.getMinutes()}` : this.getMinutes();
		const at = `${app.loc('at')} ${h}:${m}`;
		if (ago < 172800) {
			return `${app.loc(this.getDate() === now.getDate() ? 'today' : 'tomorrow')} ${at}`;
		} else {
			const y = this.getFullYear() != now.getFullYear() ? this.getFullYear() : '';
			return `${this.getDate()} ${app.loc('months')[this.getMonth()]} ${y} ${at}`;
		}
	}
};

/**
 * Возвращает правильный падеж фразы
 * @param  {Number}	number	число
 * @param  {Array}	words	массив фраз
 * @return {String}			строка
 * @example
 * getCase(5, [слово, слова, слов]); // "5 слов"
 */
function getCase (number, words) {
	"use strict";
	return number!==0? `${number} ${words[number%10===1&&number%100!==11?0:number%10>=2&&number%10<=4&&(number%100<10||number%100>=20)?1:2]}` : '';
}
<commit_msg>escapeHtml переписана как независимая функция<commit_after>/* globals $, chrome, navigator, console*/
/*jshint esnext: true */

/**
 * Екранирует HTML
 * @return {String}	Преобразованная строка
 */
function escapeHtml(str) {
	'use strict';
	const map = {
		'&': '&',
		'<': '<',
		'>': '>',
		'"': '"',
		"'": '''
	};
	return str.replace(/[&<>"']/g, m => map[m]);
};

/**
 * Формат даты Вконтакте
 * @return {String}
 */
Date.prototype.toStringVkFormat = function () {
	"use strict";
	const app = new App();
	const now = new Date();
	const ago = (now.getTime() - this.getTime())/1000; // Количество секунд прошедших с момента публикации
	if (ago < 10)		  return app.loc('just now');
	else if (ago < 60)	  return `${getCase(Math.floor(ago), app.loc('seconds'))} ${app.loc('ago')}`;
	else if (ago < 3600)  return `${getCase(Math.floor(ago/60), app.loc('minutes'))} ${app.loc('ago')}`;
	else if (ago < 10800) return `${getCase(Math.floor(ago/3600), app.loc('hours'))} ${app.loc('ago')}`;
	else {
		const h = this.getHours()	< 10 ? `0${this.getHours()}`   : this.getHours();
		const m = this.getMinutes() < 10 ? `0${this.getMinutes()}` : this.getMinutes();
		const at = `${app.loc('at')} ${h}:${m}`;
		if (ago < 172800) {
			return `${app.loc(this.getDate() === now.getDate() ? 'today' : 'tomorrow')} ${at}`;
		} else {
			const y = this.getFullYear() != now.getFullYear() ? this.getFullYear() : '';
			return `${this.getDate()} ${app.loc('months')[this.getMonth()]} ${y} ${at}`;
		}
	}
};

/**
 * Возвращает правильный падеж фразы
 * @param  {Number}	number	число
 * @param  {Array}	words	массив фраз
 * @return {String}			строка
 * @example
 * getCase(5, [слово, слова, слов]); // "5 слов"
 */
function getCase (number, words) {
	"use strict";
	return number!==0? `${number} ${words[number%10===1&&number%100!==11?0:number%10>=2&&number%10<=4&&(number%100<10||number%100>=20)?1:2]}` : '';
}
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2019 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Determines the video support of the browser running this Phaser Game instance.
 * These values are read-only and populated during the boot sequence of the game.
 * They are then referenced by internal game systems and are available for you to access
 * via `this.sys.game.device.video` from within any Scene.
 *
 * @typedef {object} Phaser.Device.Video
 * @since 3.0.0
 *
 * @property {boolean} h264 - Can this device play h264 mp4 video files?
 * @property {boolean} hls - Can this device play hls video files?
 * @property {boolean} mp4 - Can this device play h264 mp4 video files?
 * @property {boolean} ogg - Can this device play ogg video files?
 * @property {boolean} vp9 - Can this device play vp9 video files?
 * @property {boolean} webm - Can this device play webm video files?
 */
var Video = {

    h264: false,
    hls: false,
    mp4: false,
    ogg: false,
    vp9: false,
    webm: false

};

function init ()
{
    var videoElement = document.createElement('video');
    var result = !!videoElement.canPlayType;

    try
    {
        if (result)
        {
            if (videoElement.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, ''))
            {
                Video.ogg = true;
            }

            if (videoElement.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, ''))
            {
                // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
                Video.h264 = true;
                Video.mp4 = true;
            }

            if (videoElement.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, ''))
            {
                Video.webm = true;
            }

            if (videoElement.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, ''))
            {
                Video.vp9 = true;
            }

            if (videoElement.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, ''))
            {
                Video.hls = true;
            }
        }
    }
    catch (e)
    {
        //  Nothing to do
    }

    return Video;
}

module.exports = init();
<commit_msg>Updated docs<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2019 Photon Storm Ltd.
 * @license      {@link https://opensource.org/licenses/MIT|MIT License}
 */

/**
 * Determines the video support of the browser running this Phaser Game instance.
 * These values are read-only and populated during the boot sequence of the game.
 * They are then referenced by internal game systems and are available for you to access
 * via `this.sys.game.device.video` from within any Scene.
 *
 * In Phaser 3.20 the properties were renamed to drop the 'Video' suffix.
 *
 * @typedef {object} Phaser.Device.Video
 * @since 3.0.0
 *
 * @property {boolean} h264 - Can this device play h264 mp4 video files?
 * @property {boolean} hls - Can this device play hls video files?
 * @property {boolean} mp4 - Can this device play h264 mp4 video files?
 * @property {boolean} ogg - Can this device play ogg video files?
 * @property {boolean} vp9 - Can this device play vp9 video files?
 * @property {boolean} webm - Can this device play webm video files?
 */
var Video = {

    h264: false,
    hls: false,
    mp4: false,
    ogg: false,
    vp9: false,
    webm: false

};

function init ()
{
    var videoElement = document.createElement('video');
    var result = !!videoElement.canPlayType;

    try
    {
        if (result)
        {
            if (videoElement.canPlayType('video/ogg; codecs="theora"').replace(/^no$/, ''))
            {
                Video.ogg = true;
            }

            if (videoElement.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/, ''))
            {
                // Without QuickTime, this value will be `undefined`. github.com/Modernizr/Modernizr/issues/546
                Video.h264 = true;
                Video.mp4 = true;
            }

            if (videoElement.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/, ''))
            {
                Video.webm = true;
            }

            if (videoElement.canPlayType('video/webm; codecs="vp9"').replace(/^no$/, ''))
            {
                Video.vp9 = true;
            }

            if (videoElement.canPlayType('application/x-mpegURL; codecs="avc1.42E01E"').replace(/^no$/, ''))
            {
                Video.hls = true;
            }
        }
    }
    catch (e)
    {
        //  Nothing to do
    }

    return Video;
}

module.exports = init();
<|endoftext|>


<commit_before>'use strict'

const varint = require('varint')
const pull = require('pull-stream')
const pullLP = require('pull-length-prefixed')
const Connection = require('interface-connection').Connection
const util = require('../util')
const select = require('../select')
const once = require('once')

const PROTOCOL_ID = require('./../constants').PROTOCOL_ID

/**
 *
 */
class Dialer {
  /**
   * Create a new Dialer.
   */
  constructor () {
    this.conn = null
    this.log = util.log.dialer()
  }

  /**
   * Perform the multistream handshake.
   *
   * @param {Connection} rawConn - The connection on which
   * to perform the handshake.
   * @param {function(Error)} callback - Called when the handshake completed.
   * @returns {undefined}
   */
  handle (rawConn, callback) {
    this.log('dialer handle conn')
    callback = once(callback)
    const s = select(PROTOCOL_ID, (err, conn) => {
      if (err) {
        return callback(err)
      }
      this.log('handshake success')

      this.conn = new Connection(conn, rawConn)

      callback()
    }, this.log)

    pull(
      rawConn,
      s,
      rawConn
    )
  }

  /**
   * Select a protocol
   *
   * @param {string} protocol - A string of the protocol that we want to handshake.
   * @param {function(Error, Connection)} callback - `err` is
   * an error object that gets passed if something wrong happ
   * end (e.g: if the protocol selected is not supported by
   * the other end) and conn is the connection handshaked
   * with the other end.
   *
   * @returns {undefined}
   */
  select (protocol, callback) {
    this.log('dialer select ' + protocol)
    if (!this.conn) {
      return callback(new Error('multistream handshake has not finalized yet'))
    }

    const s = select(protocol, (err, conn) => {
      if (err) {
        this.conn = new Connection(conn, this.conn)
        return callback(err)
      }
      callback(null, new Connection(conn, this.conn))
    }, this.log)

    pull(
      this.conn,
      s,
      this.conn
    )
  }

  /**
   * List all available protocols.
   *
   * @param {function(Error, Array<string>)} callback - If
   * something wrong happend `Error` exists, otherwise
   * `protocols` is a list of the supported
   * protocols on the other end.
   *
   * @returns {undefined}
   */
  ls (callback) {
    const lsStream = select('ls', (err, conn) => {
      if (err) {
        return callback(err)
      }

      pull(
        conn,
        pullLP.decode(),
        collectLs(conn),
        pull.map(stringify),
        pull.collect((err, list) => {
          if (err) {
            return callback(err)
          }
          callback(null, list.slice(1))
        })
      )
    }, this.log)

    pull(
      this.conn,
      lsStream,
      this.conn
    )
  }
}

function stringify (buf) {
  return buf.toString().slice(0, -1)
}

function collectLs (conn) {
  let first = true
  let counter = 0

  return pull.take((msg) => {
    if (first) {
      varint.decode(msg)
      counter = varint.decode(msg, varint.decode.bytes)
      return true
    }

    return counter-- > 0
  })
}

module.exports = Dialer
<commit_msg>fix: once in all<commit_after>'use strict'

const varint = require('varint')
const pull = require('pull-stream')
const pullLP = require('pull-length-prefixed')
const Connection = require('interface-connection').Connection
const util = require('../util')
const select = require('../select')
const once = require('once')

const PROTOCOL_ID = require('./../constants').PROTOCOL_ID

/**
 *
 */
class Dialer {
  /**
   * Create a new Dialer.
   */
  constructor () {
    this.conn = null
    this.log = util.log.dialer()
  }

  /**
   * Perform the multistream handshake.
   *
   * @param {Connection} rawConn - The connection on which
   * to perform the handshake.
   * @param {function(Error)} callback - Called when the handshake completed.
   * @returns {undefined}
   */
  handle (rawConn, callback) {
    this.log('dialer handle conn')
    callback = once(callback)
    const s = select(PROTOCOL_ID, (err, conn) => {
      if (err) {
        return callback(err)
      }
      this.log('handshake success')

      this.conn = new Connection(conn, rawConn)

      callback()
    }, this.log)

    pull(
      rawConn,
      s,
      rawConn
    )
  }

  /**
   * Select a protocol
   *
   * @param {string} protocol - A string of the protocol that we want to handshake.
   * @param {function(Error, Connection)} callback - `err` is
   * an error object that gets passed if something wrong happ
   * end (e.g: if the protocol selected is not supported by
   * the other end) and conn is the connection handshaked
   * with the other end.
   *
   * @returns {undefined}
   */
  select (protocol, callback) {
    this.log('dialer select ' + protocol)
    callback = once(callback)
    if (!this.conn) {
      return callback(new Error('multistream handshake has not finalized yet'))
    }

    const s = select(protocol, (err, conn) => {
      if (err) {
        this.conn = new Connection(conn, this.conn)
        return callback(err)
      }
      callback(null, new Connection(conn, this.conn))
    }, this.log)

    pull(
      this.conn,
      s,
      this.conn
    )
  }

  /**
   * List all available protocols.
   *
   * @param {function(Error, Array<string>)} callback - If
   * something wrong happend `Error` exists, otherwise
   * `protocols` is a list of the supported
   * protocols on the other end.
   *
   * @returns {undefined}
   */
  ls (callback) {
    callback = once(callback)

    const lsStream = select('ls', (err, conn) => {
      if (err) {
        return callback(err)
      }

      pull(
        conn,
        pullLP.decode(),
        collectLs(conn),
        pull.map(stringify),
        pull.collect((err, list) => {
          if (err) {
            return callback(err)
          }
          callback(null, list.slice(1))
        })
      )
    }, this.log)

    pull(
      this.conn,
      lsStream,
      this.conn
    )
  }
}

function stringify (buf) {
  return buf.toString().slice(0, -1)
}

function collectLs (conn) {
  let first = true
  let counter = 0

  return pull.take((msg) => {
    if (first) {
      varint.decode(msg)
      counter = varint.decode(msg, varint.decode.bytes)
      return true
    }

    return counter-- > 0
  })
}

module.exports = Dialer
<|endoftext|>


<commit_before>PLUGINS.svg = function(api, svg, corner)
{
	var doc = $(document),
		elem = svg[0],
		root = $(elem.ownerSVGElement),
		xScale = 1, yScale = 1,
		complex = true,
		rootWidth, rootHeight,
		mtx, transformed, viewBox,
		len, next, i, points,
		result, position, dimensions;

	// Ascend the parentNode chain until we find an element with getBBox()
	while(!elem.getBBox) { elem = elem.parentNode; }
	if(!elem.getBBox || !elem.parentNode) { return FALSE; }

	// Determine dimensions where possible
	rootWidth = root.attr('width') || root.width() || parseInt(root.css('width'), 10);
	rootHeight = root.attr('height') || root.height() || parseInt(root.css('height'), 10);

	// Add stroke characteristics to scaling
	var strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2;
	if(strokeWidth2) {
		xScale += strokeWidth2 / rootWidth;
		yScale += strokeWidth2 / rootHeight;
	}

	// Determine which shape calculation to use
	switch(elem.nodeName) {
		case 'ellipse':
		case 'circle':
			result = PLUGINS.polys.ellipse(
				elem.cx.baseVal.value,
				elem.cy.baseVal.value,
				(elem.rx || elem.r).baseVal.value + strokeWidth2,
				(elem.ry || elem.r).baseVal.value + strokeWidth2,
				corner
			);
		break;

		case 'line':
		case 'polygon':
		case 'polyline':
			// Determine points object (line has none, so mimic using array)
			points = elem.points || [
				{ x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
				{ x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
			];

			for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
				next = points.getItem ? points.getItem(i) : points[i];
				result.push.apply(result, [next.x, next.y]);
			}

			result = PLUGINS.polys.polygon(result, corner);
		break;

		// Unknown shape or rectangle? Use bounding box
		default:
			result = elem.getBoundingClientRect();
			result = {
				width: result.width, height: result.height,
				position: {
					left: result.left,
					top: result.top
				}
			};
			complex = false;
		break;
	}

	// Shortcut assignments
	position = result.position;
	root = root[0];

	// If the shape was complex (i.e. not using bounding box calculations)
	if(complex) {
		// Convert position into a pixel value
		if(root.createSVGPoint) {
			mtx = elem.getScreenCTM();
			points = root.createSVGPoint();

			points.x = position.left;
			points.y = position.top;
			transformed = points.matrixTransform( mtx );
			position.left = transformed.x;
			position.top = transformed.y;
		}

		// Calculate viewBox characteristics
		if(root.viewBox && (viewBox = root.viewBox.baseVal) && viewBox.width && viewBox.height) {
			xScale *= rootWidth / viewBox.width;
			yScale *= rootHeight / viewBox.height;
		}
	}

	// Adjust by scroll offset
	position.left += doc.scrollLeft();
	position.top += doc.scrollTop();

	return result;
};<commit_msg>Fix embedded SVG document positioning. Fixes #625<commit_after>PLUGINS.svg = function(api, svg, corner)
{
	var doc = $(document),
		elem = svg[0],
		root = $(elem.ownerSVGElement),
		ownerDocument = elem.ownerDocument,
		xScale = 1, yScale = 1,
		complex = true,
		frameOffset, rootWidth, rootHeight,
		mtx, transformed, viewBox,
		len, next, i, points,
		result, position, dimensions;

	// Ascend the parentNode chain until we find an element with getBBox()
	while(!elem.getBBox) { elem = elem.parentNode; }
	if(!elem.getBBox || !elem.parentNode) { return FALSE; }

	// Determine dimensions where possible
	rootWidth = root.attr('width') || root.width() || parseInt(root.css('width'), 10);
	rootHeight = root.attr('height') || root.height() || parseInt(root.css('height'), 10);

	// Add stroke characteristics to scaling
	var strokeWidth2 = (parseInt(svg.css('stroke-width'), 10) || 0) / 2;
	if(strokeWidth2) {
		xScale += strokeWidth2 / rootWidth;
		yScale += strokeWidth2 / rootHeight;
	}

	// Determine which shape calculation to use
	switch(elem.nodeName) {
		case 'ellipse':
		case 'circle':
			result = PLUGINS.polys.ellipse(
				elem.cx.baseVal.value,
				elem.cy.baseVal.value,
				(elem.rx || elem.r).baseVal.value + strokeWidth2,
				(elem.ry || elem.r).baseVal.value + strokeWidth2,
				corner
			);
		break;

		case 'line':
		case 'polygon':
		case 'polyline':
			// Determine points object (line has none, so mimic using array)
			points = elem.points || [
				{ x: elem.x1.baseVal.value, y: elem.y1.baseVal.value },
				{ x: elem.x2.baseVal.value, y: elem.y2.baseVal.value }
			];

			for(result = [], i = -1, len = points.numberOfItems || points.length; ++i < len;) {
				next = points.getItem ? points.getItem(i) : points[i];
				result.push.apply(result, [next.x, next.y]);
			}

			result = PLUGINS.polys.polygon(result, corner);
		break;

		// Unknown shape or rectangle? Use bounding box
		default:
			result = elem.getBoundingClientRect();
			result = {
				width: result.width, height: result.height,
				position: {
					left: result.left,
					top: result.top
				}
			};
			complex = false;
		break;
	}

	// Shortcut assignments
	position = result.position;
	root = root[0];

	// If the shape was complex (i.e. not using bounding box calculations)
	if(complex) {
		// Convert position into a pixel value
		if(root.createSVGPoint) {
			mtx = elem.getScreenCTM();
			points = root.createSVGPoint();

			points.x = position.left;
			points.y = position.top;
			transformed = points.matrixTransform( mtx );
			position.left = transformed.x;
			position.top = transformed.y;
		}

		// Calculate viewBox characteristics
		if(root.viewBox && (viewBox = root.viewBox.baseVal) && viewBox.width && viewBox.height) {
			xScale *= rootWidth / viewBox.width;
			yScale *= rootHeight / viewBox.height;
		}
	}

	// Check the element is not in a child document, and if so, adjust for frame elements offset
	if(ownerDocument !== document) {
		var frameOffset = $((ownerDocument.defaultView || ownerDocument.parentWindow).frameElement).offset();
		position.left += frameOffset.left;
		position.top += frameOffset.top;
	}

	// Adjust by scroll offset of owner document
	ownerDocument = $(ownerDocument);
	position.left += ownerDocument.scrollLeft();
	position.top += ownerDocument.scrollTop();

	return result;
};<|endoftext|>


<commit_before>/* global cytoscape:true */
class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsAPI, $stateParams, mcstate, $filter, $mdDialog, mcshow) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsAPI = experimentsAPI;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.mcshow = mcshow;
        this.removedNodes = null;
        this.navigator = null;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = null;
            let matches = this.$filter('filter')(this.processes.plain(), search);
            if (!matches.length) {
                return;
            }
            let matchesById = _.indexBy(matches, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        };

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            if (!samples.length) {
                return;
            }

            let matchingProcesses = [];
            samples.forEach(sample => {
                let matches = this.$filter('filter')(this.processes.plain(), sample.id);
                matchingProcesses = matchingProcesses.concat(matches.map(p => ({id: p.id, seen: false})));
            });

            let matchesById = _.indexBy(matchingProcesses, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        if (this.navigator !== null) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = cytoscape({
            container: document.getElementById('processesGraph'),
            elements: g.elements,
            style: [
                {
                    selector: 'node',
                    style: {
                        'content': 'data(name)',
                        'text-valign': 'center',
                        'text-halign': 'center',
                        'background-color': 'data(color)',
                        color: 'black',
                        //'text-outline-color': 'data(color)',
                        'text-outline-color': (ele) => {
                            let c = ele.data('color');
                            return c ? c : '#fff';
                        },
                        'font-size': '18px',
                        'font-weight': 'bold',
                        'text-outline-width': '5px',
                        'text-outline-opacity': 1,
                        'border-width': '4px',
                        //'border-color': 'data(highlight)',
                        'border-color': (ele) => {
                            let highlight = ele.data('highlight');
                            return highlight ? highlight : '#fff';
                        },
                        //shape: 'data(shape)',
                        shape: (ele) => {
                            let shape = ele.data('shape');
                            return shape ? shape : 'triangle';
                        },
                        width: '80px',
                        height: '80px'
                    }
                },
                {
                    selector: 'edge',
                    style: {
                        'width': 4,
                        'target-arrow-shape': 'triangle',
                        //'target-arrow-color': '#9dbaea',
                        'curve-style': 'bezier',
                        'content': 'data(name)',
                        'font-weight': 'bold',
                        'text-outline-color': '#555',
                        'text-outline-width': '3px',
                        'color': '#fff'
                    }
                },
                {
                    selector: 'node:selected',
                    style: {
                        'border-width': '8px',
                        'border-color': '#2196f3',
                        color: '#2196f3'
                    }
                },
                {
                    selector: 'edge:selected',
                    style: {
                        'background-color': '#2196f3',
                        'text-outline-color': '#2196f3'
                    }
                }
            ]
        });

        this.cy.on('click', event => {
            let target = event.cyTarget;
            if (!target.isNode && !target.isEdge) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            } else if (target.isNode()) {
                //let edges = target.connectedEdges();
                //edges.forEach((e) => console.log('source is ' + e.data('source')));
                //console.log(target.connectedEdges());
                let processId = target.data('id');
                let process = this.processes.filter((p) => p.id === processId)[0];
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, process);
                this.mcProcessesWorkflow.setSelectedProcess(processId, (target.outgoers().length > 0));
            } else if (target.isEdge()) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            }
        });
        // Use this to show/hide certain menu items
        // this.cy.on('cxttap', event => {
        //     console.log('cxttap');
        //     let target = event.cyTarget;
        //     if (target.isNode()) {
        //         console.log(' -- target is node', target.data('name'));
        //     }
        // });

        //this.cy.on('mouseover', function(event) {
        //    let target = event.cyTarget;
        //    if (target.data) {
        //        //log('target', target.data('name'));
        //    }
        //    // Need to install qtip or some other
        //    //target.qtip({
        //    //    content: target.data('name')
        //    //});
        //});
        this.cy.layout({name: 'dagre', fit: true});
        this.cy.panzoom();
        this.ctxMenu = this.setupContextMenus();
        let edgeConfig = {
            toggleOffOnLeave: true,
            handleNodes: 'node',
            handleSize: 10,
            edgeType: (source, target) => {
                let sourceProcess = source.data('details');
                let targetProcess = target.data('details');
                if (sourceProcess.output_samples.length === 0) {
                    // source process has not output samples
                    return null;
                } else if (targetProcess.template_name === 'Create Samples') {
                    // Create samples cannot be a target
                    return null;
                } else if (this.targetHasAllSourceSamples(targetProcess.input_samples, sourceProcess.output_samples)) {
                    // Target already has all the source samples
                    return null;
                }
                return 'flat';
            },
            complete: (source, target, addedEntities) => {
                let sourceProcess = source.data('details');
                let targetProcess = target.data('details');
                if (sourceProcess.output_samples.length === 1) {
                    this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, sourceProcess.output_samples).then(
                        (process) => {
                            this.replaceProcess(process);
                            target.data('details', process);
                        }
                    );
                } else {
                    this.workflowService.chooseSamplesFromSource(sourceProcess).then(
                        (samples) => {
                            if (!samples.length) {
                                // No samples brought over, delete edge
                                addedEntities[0].remove();
                            } else {
                                this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, samples).then(
                                    (process) => {
                                        this.replaceProcess(process);
                                        target.data('details', process);
                                    }
                                );
                            }
                        }
                    );
                }
                //console.log('addedEntities', addedEntities, addedEntities[0].isEdge());
            }
        };

        this.cy.edgehandles(edgeConfig);
    }

    replaceProcess(process) {
        let i = _.findIndex(this.processes, p => p.id === process.id);
        if (i !== -1) {
            this.processes.splice(i, 1);
            this.processes.push(process);
        }
    }

    targetHasAllSourceSamples(targetSamples, sourceSamples) {
        let sourceSamplesSeen = {};
        sourceSamples.forEach((s) => {
            sourceSamplesSeen[`${s.sample_id}/${s.property_set_id}`] = false;
        });

        targetSamples.forEach(s => {
            let key = `${s.sample_id}/${s.property_set_id}`;
            if (key in sourceSamplesSeen) {
                sourceSamplesSeen[key] = true;
            }
        });
        let hasAllSamples = true;
        _.forOwn(sourceSamplesSeen, (value) => {
            if (!value) {
                hasAllSamples = false;
            }
        });

        return hasAllSamples;
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    title: 'Show Details',
                    selector: 'node, edge',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    title: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'add-child',
                    title: 'Add Child',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._addChild(event)
                },
                {
                    id: 'delete-process',
                    title: 'Delete Process',
                    selector: 'node',
                    onClickFunction: (event) => this._deleteProcess(event)
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.experimentsAPI.getProcessForExperiment(this.projectId, this.experimentId, process.id).then(
                p => this.mcshow.processDetailsDialog(p, false).then(
                    updatedProcess => target.data('name', updatedProcess.name)
                )
            );
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, process.id);
    }

    _addChild(event) {
        let process = this.getProcessFromEvent(event);
        this.$mdDialog.show({
            templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-templates-dialog.html',
            controller: SelectProcessTemplateDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            multiple: true,
            locals: {
                parentProcess: process
            }
        });
    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class SelectProcessTemplateDialogController {
    /*@ngInject*/
    constructor($stateParams, $mdDialog, workflowService) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
        this.keepOpen = false;
    }

    addSelectedProcessTemplate(templateId) {
        this.workflowService.addChildProcessFromTemplate(templateId, this.projectId, this.experimentId,
            this.parentProcess, this.keepOpen)
    }

    done() {
        this.$mdDialog.hide();
    }

}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    },
    require: {
        mcProcessesWorkflow: '^mcProcessesWorkflow'
    }
});
<commit_msg>Add dialogs to inform user why they cannot connect nodes<commit_after>/* global cytoscape:true */
class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsAPI, $stateParams, mcstate, $filter, $mdDialog, mcshow) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsAPI = experimentsAPI;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.mcshow = mcshow;
        this.removedNodes = null;
        this.navigator = null;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = null;
            let matches = this.$filter('filter')(this.processes.plain(), search);
            if (!matches.length) {
                return;
            }
            let matchesById = _.indexBy(matches, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        };

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            if (!samples.length) {
                return;
            }

            let matchingProcesses = [];
            samples.forEach(sample => {
                let matches = this.$filter('filter')(this.processes.plain(), sample.id);
                matchingProcesses = matchingProcesses.concat(matches.map(p => ({id: p.id, seen: false})));
            });

            let matchesById = _.indexBy(matchingProcesses, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                return (!(processId in matchesById));

            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        if (this.navigator !== null) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = cytoscape({
            container: document.getElementById('processesGraph'),
            elements: g.elements,
            style: [
                {
                    selector: 'node',
                    style: {
                        'content': 'data(name)',
                        'text-valign': 'center',
                        'text-halign': 'center',
                        'background-color': 'data(color)',
                        color: 'black',
                        //'text-outline-color': 'data(color)',
                        'text-outline-color': (ele) => {
                            let c = ele.data('color');
                            return c ? c : '#fff';
                        },
                        'font-size': '18px',
                        'font-weight': 'bold',
                        'text-outline-width': '5px',
                        'text-outline-opacity': 1,
                        'border-width': '4px',
                        //'border-color': 'data(highlight)',
                        'border-color': (ele) => {
                            let highlight = ele.data('highlight');
                            return highlight ? highlight : '#fff';
                        },
                        //shape: 'data(shape)',
                        shape: (ele) => {
                            let shape = ele.data('shape');
                            return shape ? shape : 'triangle';
                        },
                        width: '80px',
                        height: '80px'
                    }
                },
                {
                    selector: 'edge',
                    style: {
                        'width': 4,
                        'target-arrow-shape': 'triangle',
                        //'target-arrow-color': '#9dbaea',
                        'curve-style': 'bezier',
                        'content': 'data(name)',
                        'font-weight': 'bold',
                        'text-outline-color': '#555',
                        'text-outline-width': '3px',
                        'color': '#fff'
                    }
                },
                {
                    selector: 'node:selected',
                    style: {
                        'border-width': '8px',
                        'border-color': '#2196f3',
                        color: '#2196f3'
                    }
                },
                {
                    selector: 'edge:selected',
                    style: {
                        'background-color': '#2196f3',
                        'text-outline-color': '#2196f3'
                    }
                }
            ]
        });

        this.cy.on('click', event => {
            let target = event.cyTarget;
            if (!target.isNode && !target.isEdge) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            } else if (target.isNode()) {
                //let edges = target.connectedEdges();
                //edges.forEach((e) => console.log('source is ' + e.data('source')));
                //console.log(target.connectedEdges());
                let processId = target.data('id');
                let process = this.processes.filter((p) => p.id === processId)[0];
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, process);
                this.mcProcessesWorkflow.setSelectedProcess(processId, (target.outgoers().length > 0));
            } else if (target.isEdge()) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            }
        });
        // Use this to show/hide certain menu items
        // this.cy.on('cxttap', event => {
        //     console.log('cxttap');
        //     let target = event.cyTarget;
        //     if (target.isNode()) {
        //         console.log(' -- target is node', target.data('name'));
        //     }
        // });

        //this.cy.on('mouseover', function(event) {
        //    let target = event.cyTarget;
        //    if (target.data) {
        //        //log('target', target.data('name'));
        //    }
        //    // Need to install qtip or some other
        //    //target.qtip({
        //    //    content: target.data('name')
        //    //});
        //});
        this.cy.layout({name: 'dagre', fit: true});
        this.cy.panzoom();
        this.ctxMenu = this.setupContextMenus();
        let edgeConfig = {
            toggleOffOnLeave: true,
            handleNodes: 'node',
            handleSize: 10,
            edgeType: (source, target) => {
                let sourceProcess = source.data('details');
                let targetProcess = target.data('details');
                if (sourceProcess.output_samples.length === 0) {
                    // source process has not output samples
                    this.showAlert('No output samples to connect to process.');
                    return null;
                } else if (targetProcess.template_name === 'Create Samples') {
                    // Create samples cannot be a target
                    this.showAlert('Create Samples process type cannot be a target.');
                    return null;
                } else if (this.targetHasAllSourceSamples(targetProcess.input_samples, sourceProcess.output_samples)) {
                    // Target already has all the source samples
                    this.showAlert('Processes are already connected.');
                    return null;
                }
                return 'flat';
            },
            complete: (source, target, addedEntities) => {
                let sourceProcess = source.data('details');
                let targetProcess = target.data('details');
                if (sourceProcess.output_samples.length === 1) {
                    this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, sourceProcess.output_samples).then(
                        (process) => {
                            this.replaceProcess(process);
                            target.data('details', process);
                        }
                    );
                } else {
                    this.workflowService.chooseSamplesFromSource(sourceProcess).then(
                        (samples) => {
                            if (!samples.length) {
                                // No samples brought over, delete edge
                                addedEntities[0].remove();
                            } else {
                                this.workflowService.addSamplesToProcess(this.projectId, this.experimentId, targetProcess, samples).then(
                                    (process) => {
                                        this.replaceProcess(process);
                                        target.data('details', process);
                                    }
                                );
                            }
                        }
                    );
                }
                //console.log('addedEntities', addedEntities, addedEntities[0].isEdge());
            }
        };

        this.cy.edgehandles(edgeConfig);
    }

    replaceProcess(process) {
        let i = _.findIndex(this.processes, p => p.id === process.id);
        if (i !== -1) {
            this.processes.splice(i, 1);
            this.processes.push(process);
        }
    }

    showAlert(message) {
        this.$mdDialog.show(this.$mdDialog.alert()
            .title('Invalid Target Process')
            .textContent(message)
            .ok('dismiss'));
    }

    targetHasAllSourceSamples(targetSamples, sourceSamples) {
        let sourceSamplesSeen = {};
        sourceSamples.forEach((s) => {
            sourceSamplesSeen[`${s.sample_id}/${s.property_set_id}`] = false;
        });

        targetSamples.forEach(s => {
            let key = `${s.sample_id}/${s.property_set_id}`;
            if (key in sourceSamplesSeen) {
                sourceSamplesSeen[key] = true;
            }
        });
        let hasAllSamples = true;
        _.forOwn(sourceSamplesSeen, (value) => {
            if (!value) {
                hasAllSamples = false;
            }
        });

        return hasAllSamples;
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    title: 'Show Details',
                    selector: 'node, edge',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    title: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'add-child',
                    title: 'Add Child',
                    selector: 'node',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._addChild(event)
                },
                {
                    id: 'delete-process',
                    title: 'Delete Process',
                    selector: 'node',
                    onClickFunction: (event) => this._deleteProcess(event)
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.experimentsAPI.getProcessForExperiment(this.projectId, this.experimentId, process.id).then(
                p => this.mcshow.processDetailsDialog(p, false).then(
                    updatedProcess => target.data('name', updatedProcess.name)
                )
            );
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, process.id);
    }

    _addChild(event) {
        let process = this.getProcessFromEvent(event);
        this.$mdDialog.show({
            templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-templates-dialog.html',
            controller: SelectProcessTemplateDialogController,
            controllerAs: '$ctrl',
            bindToController: true,
            multiple: true,
            locals: {
                parentProcess: process
            }
        });
    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class SelectProcessTemplateDialogController {
    /*@ngInject*/
    constructor($stateParams, $mdDialog, workflowService) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
        this.keepOpen = false;
    }

    addSelectedProcessTemplate(templateId) {
        this.workflowService.addChildProcessFromTemplate(templateId, this.projectId, this.experimentId,
            this.parentProcess, this.keepOpen)
    }

    done() {
        this.$mdDialog.hide();
    }

}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    },
    require: {
        mcProcessesWorkflow: '^mcProcessesWorkflow'
    }
});
<|endoftext|>