<commit_before>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

IndigoObject = function (d, id, parent) {
	this.id = id;
	this.d = d;
	this.parent = parent;
}

/*
 * Free an object
 *
 * @method dispose
 */
IndigoObject.prototype.dispose = function () {
	if (this.id >= 0)
		if (this.d._sid >= 0 && this.d._lib != null) {
			this.d._setSessionId();
			this.d._lib.indigoFree(this.id);
		}
	this.id = -1;
}

/*
 * Clone an object
 *
 * @method clone
 */
IndigoObject.prototype.clone = function () {
	this.d._setSessionId();
	return new IndigoObject(this.d, this.d._checkResult(this.d._lib.indigoClone(this.id)));
}

/*
 * Get a 3d coordinates of an atom
 *
 * @method xyz
 * @returns {Array} the [x, y, z] coordinates
 */
IndigoObject.prototype.xyz = function () {
	this.d._setSessionId();
	var xyz_ptr = this.d._lib.indigoXYZ(this.id); /* int atom */
	if (xyz_ptr.length == 0) {
		var msg = this.d.getLastError();
		this.d.logger.error('xyz [fault]: ' + msg);
		return [0.0, 0.0, 0.0];
	}

	var xyz = xyz_ptr.deref();
	return [xyz.x, xyz.y, xyz.z];
}

/*
 * Set a 3d coordinates of an atom
 *
 * @method xyz
 * @returns {Array} the [x, y, z] coordinates
 */
IndigoObject.prototype.setXYZ = function (x, y, z) {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoSetXYZ(this.id, x, y, z));
}

/*
 * Generate molfile from IndigoObject
 *
 * @method molfile
 * @returns {string} string reprsantation of molfile
 */
IndigoObject.prototype.molfile = function () {
	this.d._setSessionId();
	return this.d._checkResultString(this.d._lib.indigoMolfile(this.id));
}

/*
 *
 * @method match
 * @param {object} query
 * @returns {object}
 */
IndigoObject.prototype.match = function (query) {
	this.d._setSessionId();
	newobj = this.d._checkResult(this.d._lib.indigoMatch(this.id, query.id));
	if (newobj === 0)
		return null;
	else
		return new IndigoObject(d, newobj, this);
}

/*
 *
 * @method match
 * @returns {string}
 */
IndigoObject.prototype.smiles = function () {
	this.d._setSessionId();
	return this.d._checkResultString(this.d._lib.indigoSmiles(this.id));
}

/*
 *
 * @method iterateAtoms
 * @returns {object}
 */
IndigoObject.prototype.iterateAtoms = function * () {
	this.d._setSessionId();
	var newobj = new IndigoObject(this.d, this.d._checkResult(this.d._lib.indigoIterateAtoms(this.id)));
	while (newobj && newobj.id !== -1) {
		yield newobj;
		newobj = new IndigoObject(this.d, this.d._checkResult(this.d._lib.indigoIterateAtoms(newobj.id)));
	}
}

/*
 *
 * @method removeConstraints
 * @returns {number}
 */
IndigoObject.prototype.removeConstraints = function (type) {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoRemoveConstraints(this.id, type));
}

/*
 *
 * @method canonicalSmiles
 * @returns {string}
 */
IndigoObject.prototype.canonicalSmiles = function () {
	this.d._setSessionId();
	return this.d._checkResultString(this.d._lib.indigoCanonicalSmiles(this.id));
}

/*
 *
 * @method unfoldHydrogens
 * @returns {number}
 */
IndigoObject.prototype.unfoldHydrogens = function () {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoUnfoldHydrogens(this.id));
}

/*
 *
 * @method resetAtom
 * @returns {number}
 */
IndigoObject.prototype.resetAtom = function (symbol) {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoResetAtom(this.id, symbol));
}

/*
 *
 * @method serialize
 * @returns {Array}
 */
IndigoObject.prototype.serialize = function () {
	this.d._setSessionId();
	var size = this.d._out.aint; // allocate a 4-byte (32-bit) chunk for the output value
	var pointer = this.d._out.apbyte;
	var status = this.d._checkResult(this.d._lib.indigoSerialize(this.id, pointer, size));
	var buf = this.d._out.read(pointer, 0, size.deref());
	res = [];
	for (i = 0; i < size.deref(); i++) {
		res.push(buf[i]);
	}
	return res;
}

module.exports = IndigoObject;
<commit_msg>add IndigoObject.charge and valence<commit_after>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

IndigoObject = function (d, id, parent) {
	this.id = id;
	this.d = d;
	this.parent = parent;
};

/*
 * Free an object
 *
 * @method dispose
 */
IndigoObject.prototype.dispose = function () {
	if (this.id >= 0)
		if (this.d._sid >= 0 && this.d._lib != null) {
			this.d._setSessionId();
			this.d._lib.indigoFree(this.id);
		}
	this.id = -1;
};

/*
 * Clone an object
 *
 * @method clone
 */
IndigoObject.prototype.clone = function () {
	this.d._setSessionId();
	return new IndigoObject(this.d, this.d._checkResult(this.d._lib.indigoClone(this.id)));
};

/*
 * Get a 3d coordinates of an atom
 *
 * @method xyz
 * @returns {Array} the [x, y, z] coordinates
 */
IndigoObject.prototype.xyz = function () {
	this.d._setSessionId();
	var xyz_ptr = this.d._lib.indigoXYZ(this.id); /* int atom */
	if (xyz_ptr.length == 0) {
		var msg = this.d.getLastError();
		this.d.logger.error('xyz [fault]: ' + msg);
		return [0.0, 0.0, 0.0];
	}

	var xyz = xyz_ptr.deref();
	return [xyz.x, xyz.y, xyz.z];
};

/*
 * Set a 3d coordinates of an atom
 *
 * @method xyz
 * @returns {Array} the [x, y, z] coordinates
 */
IndigoObject.prototype.setXYZ = function (x, y, z) {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoSetXYZ(this.id, x, y, z));
};

/*
 * Generate molfile from IndigoObject
 *
 * @method molfile
 * @returns {string} string reprsantation of molfile
 */
IndigoObject.prototype.molfile = function () {
	this.d._setSessionId();
	return this.d._checkResultString(this.d._lib.indigoMolfile(this.id));
};

/*
 *
 * @method match
 * @param {object} query
 * @returns {object}
 */
IndigoObject.prototype.match = function (query) {
	this.d._setSessionId();
	newobj = this.d._checkResult(this.d._lib.indigoMatch(this.id, query.id));
	if (newobj === 0)
		return null;
	else
		return new IndigoObject(d, newobj, this);
};

/*
 *
 * @method match
 * @returns {string}
 */
IndigoObject.prototype.smiles = function () {
	this.d._setSessionId();
	return this.d._checkResultString(this.d._lib.indigoSmiles(this.id));
};

/*
 *
 * @method iterateAtoms
 * @returns {object}
 */
IndigoObject.prototype.iterateAtoms = function* () {
	this.d._setSessionId();
	var newobj = new IndigoObject(this.d, this.d._checkResult(this.d._lib.indigoIterateAtoms(this.id)));
	while (newobj && newobj.id !== -1) {
		yield newobj;
		newobj = new IndigoObject(this.d, this.d._checkResult(this.d._lib.indigoIterateAtoms(newobj.id)));
	}
};

/*
 *
 * @method removeConstraints
 * @returns {number}
 */
IndigoObject.prototype.removeConstraints = function (type) {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoRemoveConstraints(this.id, type));
};

/*
 *
 * @method canonicalSmiles
 * @returns {string}
 */
IndigoObject.prototype.canonicalSmiles = function () {
	this.d._setSessionId();
	return this.d._checkResultString(this.d._lib.indigoCanonicalSmiles(this.id));
};

/*
 *
 * @method unfoldHydrogens
 * @returns {number}
 */
IndigoObject.prototype.unfoldHydrogens = function () {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoUnfoldHydrogens(this.id));
};

/*
 *
 * @method resetAtom
 * @returns {number}
 */
IndigoObject.prototype.resetAtom = function (symbol) {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoResetAtom(this.id, symbol));
};

/*
 *
 * @method serialize
 * @returns {Array}
 */
IndigoObject.prototype.serialize = function () {
	this.d._setSessionId();
	var size = this.d._out.aint; // allocate a 4-byte (32-bit) chunk for the output value
	var pointer = this.d._out.apbyte;
	var status = this.d._checkResult(this.d._lib.indigoSerialize(this.id, pointer, size));
	var buf = this.d._out.read(pointer, 0, size.deref());
	res = [];
	for (i = 0; i < size.deref(); i++) {
		res.push(buf[i]);
	}
	return res;
};

/*
 *
 * @method charge
 * @returns {number}
 */
IndigoObject.prototype.charge = function () {
	this.d._setSessionId();
	var value = this.d._out.aint;
	var res = this.d._checkResult(this.d._lib.indigoGetCharge(this.id, value));
	if (res === null)
		return null;
	else
		return value.deref();
};

/*
 *
 * @method valence
 * @returns {number}
 */
IndigoObject.prototype.valence = function () {
	this.d._setSessionId();
	return this.d._checkResult(this.d._lib.indigoValence(this.id));
};

module.exports = IndigoObject;
<|endoftext|>


<commit_before>define(["js/ui/View", "sprd/entity/TextConfiguration", "sprd/entity/DesignConfiguration", "sprd/model/PrintType", "js/core/Bus", "js/core/List", "js/core/I18n"], function (View, TextConfiguration, DesignConfiguration, PrintType, Bus, List, I18n) {

    var DEFAULT_DIGITAL_COLORS = {
        black: 0x000000,
        white: 0xFFFFFF,
        cream: 0xEAD080,
        yellow: 0xEAE44B,
        "neon-yellow": 0xD9FF03,
        "yellow-gold": 0xFFC028,
        "orange": 0xE35110,
        "neon-orange": 0xFF4C0D,
        "light-brown": 0xAC8249,
        "brown": 0x4B1B00,
        "burgundy-red": 0x5B0B20,
        "pink": 0xFF9090,
        "red": 0xD41C3B,
        "neon-pink": 0xFF3484,
        "magenta": 0xA92355,
        "purple": 0x521973,
        "lavender": 0xCDBFE3,
        "sky-blue": 0x9DC8D9,
        "light-blue": 0x0091D3,
        "blue": 0x192F97,
        "navy": 0x141754,
        "emerald-green": 0x007E80,
        "green": 0x007D44,
        "neon-green": 0x00FF00,
        "bright-green": 0xB7ED6F,
        "silver-metallic": 0x7C8FA3,
        "grey": 0x787878,
        "gold-metallic": 0x8B7426
    };

    return View.inherit("sprd.view.ConfigurationColorSelectorClass", {

        defaults: {
            componentClass: "configuration-color-selector",

            /***
             * the configuration, to switch the color
             * @type sprd.entity.Configuration
             */
            configuration: null,

            layers: "{layers()}",

            autoSelectLayer: false,

            selectedLayer: null
        },

        inject: {
            bus: Bus,
            i18n: I18n
        },

        events: [
            "on:colorSelected"
        ],

        _commitLayers: function(layers) {

            if (!layers) {
                this.set("selectedLayer", null);
            } else if (this.$.autoSelectLayer) {
                var configuration = this.$.configuration,
                    layerIndex = 0;
                if (configuration && configuration.$lastLayerIndex) {
                    layerIndex = configuration.$lastLayerIndex;
                }

                this.set("selectedLayer", layers[layerIndex]);
            }

        },

        selectLayer: function(e, layer) {

            e.preventDefault();
            e.stopPropagation();

            var configuration = this.$.configuration;
            if (configuration && layer) {
                configuration.$lastLayerIndex = layer.index;
            }

            if (layer !== this.$.selectedLayer) {
                this.set("selectedLayer", layer);
            } else {
                this.set("selectedLayer", null)
            }

        },

        boolean: function(v) {
            return !!v;
        },

        _handleColorSelect: function (e) {
            if (this.$.configuration) {
                this.$.configuration.setColor(this.$.selectedLayer.index, e.$);
                this.trigger("on:colorSelected");

                // TODO: trigger custom event
                this.$.bus.trigger('Application.productChanged', this.$.product);
            }
        },

        nameForColor: function (color) {
            if (color) {
                if (color.$.name) {
                    return color.$.name;
                } else if (color.$.key) {
                    return this.$.i18n.ts('color', color.$.key);
                }
            }
            return "";

        },

        layers: function () {

            var configuration = this.$.configuration;

            if (!configuration) {
                return null;
            }

            if (configuration instanceof TextConfiguration) {
                return [{
                    index: 0
                }];
            } else if (configuration instanceof DesignConfiguration) {
                if (configuration.$.printType.$.id === PrintType.Mapping.SpecialFlex) {
                    return [{
                        index: 0
                    }];
                }
                return configuration.$.printColors.toArray(function (item, index) {
                    return {
                        index: index
                    };
                });
            }
        }.onChange("configuration").on(["configuration", "change:printType"]),

        _printTypeToColors: function (printType) {

            if (printType) {
                if (printType.isPrintColorColorSpace()) {
                    return printType.$.colors;
                } else {
                    if (!this.$digitalColorsCache) {
                        this.$digitalColorsCache = new List();

                        for (var color in DEFAULT_DIGITAL_COLORS) {
                            if (DEFAULT_DIGITAL_COLORS.hasOwnProperty(color)) {
                                var printColor = printType.getClosestPrintColor(DEFAULT_DIGITAL_COLORS[color]);
                                printColor.$.key = color;
                                this.$digitalColorsCache.add(printColor);
                            }
                        }
                    }

                    return this.$digitalColorsCache;
                }
            }

            return null;
        }
    });

});<commit_msg>DEV-62948 - fixed corner case where menu is open but active state isn't set<commit_after>define(["js/ui/View", "sprd/entity/TextConfiguration", "sprd/entity/DesignConfiguration", "sprd/model/PrintType", "js/core/Bus", "js/core/List", "js/core/I18n"], function (View, TextConfiguration, DesignConfiguration, PrintType, Bus, List, I18n) {

    var DEFAULT_DIGITAL_COLORS = {
        black: 0x000000,
        white: 0xFFFFFF,
        cream: 0xEAD080,
        yellow: 0xEAE44B,
        "neon-yellow": 0xD9FF03,
        "yellow-gold": 0xFFC028,
        "orange": 0xE35110,
        "neon-orange": 0xFF4C0D,
        "light-brown": 0xAC8249,
        "brown": 0x4B1B00,
        "burgundy-red": 0x5B0B20,
        "pink": 0xFF9090,
        "red": 0xD41C3B,
        "neon-pink": 0xFF3484,
        "magenta": 0xA92355,
        "purple": 0x521973,
        "lavender": 0xCDBFE3,
        "sky-blue": 0x9DC8D9,
        "light-blue": 0x0091D3,
        "blue": 0x192F97,
        "navy": 0x141754,
        "emerald-green": 0x007E80,
        "green": 0x007D44,
        "neon-green": 0x00FF00,
        "bright-green": 0xB7ED6F,
        "silver-metallic": 0x7C8FA3,
        "grey": 0x787878,
        "gold-metallic": 0x8B7426
    };

    return View.inherit("sprd.view.ConfigurationColorSelectorClass", {

        defaults: {
            componentClass: "configuration-color-selector",

            /***
             * the configuration, to switch the color
             * @type sprd.entity.Configuration
             */
            configuration: null,

            layers: "{layers()}",

            autoSelectLayer: false,

            selectedLayer: null
        },

        inject: {
            bus: Bus,
            i18n: I18n
        },

        events: [
            "on:colorSelected"
        ],

        ctor: function() {
            this.callBase();

            this.bind("colorPicker", "change:menuVisible", function() {
                if (!this.$.colorPicker.$.menuVisible) {
                    this.set("selectedLayer", null);
                }
            }, this);
        },

        _commitLayers: function(layers) {

            this.set("selectedLayer", null);

            if (layers && this.$.autoSelectLayer) {
                var configuration = this.$.configuration,
                    layerIndex = 0;
                if (configuration && configuration.$lastLayerIndex) {
                    layerIndex = configuration.$lastLayerIndex;
                }

                this.set("selectedLayer", layers[layerIndex]);
            }

        },

        selectLayer: function(e, layer) {

            e.preventDefault();
            e.stopPropagation();

            var configuration = this.$.configuration;
            if (configuration && layer) {
                configuration.$lastLayerIndex = layer.index;
            }

            if (layer !== this.$.selectedLayer) {
                this.set("selectedLayer", layer);
            } else {
                this.set("selectedLayer", null)
            }

        },

        boolean: function(v) {
            return !!v;
        },

        _handleColorSelect: function (e) {
            if (this.$.configuration) {
                this.$.configuration.setColor(this.$.selectedLayer.index, e.$);
                this.trigger("on:colorSelected");

                // TODO: trigger custom event
                this.$.bus.trigger('Application.productChanged', this.$.product);
            }
        },

        nameForColor: function (color) {
            if (color) {
                if (color.$.name) {
                    return color.$.name;
                } else if (color.$.key) {
                    return this.$.i18n.ts('color', color.$.key);
                }
            }
            return "";

        },

        layers: function () {

            var configuration = this.$.configuration;

            if (!configuration) {
                return null;
            }

            if (configuration instanceof TextConfiguration) {
                return [{
                    index: 0
                }];
            } else if (configuration instanceof DesignConfiguration) {
                if (configuration.$.printType.$.id === PrintType.Mapping.SpecialFlex) {
                    return [{
                        index: 0
                    }];
                }
                return configuration.$.printColors.toArray(function (item, index) {
                    return {
                        index: index
                    };
                });
            }
        }.onChange("configuration").on(["configuration", "change:printType"]),

        _printTypeToColors: function (printType) {

            if (printType) {
                if (printType.isPrintColorColorSpace()) {
                    return printType.$.colors;
                } else {
                    if (!this.$digitalColorsCache) {
                        this.$digitalColorsCache = new List();

                        for (var color in DEFAULT_DIGITAL_COLORS) {
                            if (DEFAULT_DIGITAL_COLORS.hasOwnProperty(color)) {
                                var printColor = printType.getClosestPrintColor(DEFAULT_DIGITAL_COLORS[color]);
                                printColor.$.key = color;
                                this.$digitalColorsCache.add(printColor);
                            }
                        }
                    }

                    return this.$digitalColorsCache;
                }
            }

            return null;
        }
    });

});<|endoftext|>


<commit_before>/*
 * moleculer
 * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer)
 * MIT Licensed
 */
"use strict";

const _ = require("lodash");

const { random } = require("lodash");
const BaseStrategy = require("./base");

/**
 * Lowest latency invocation strategy
 *
 * Since Strategy can be instantiated multiple times, therefore,
 * we need to have a "master" instance to send ping, and each
 * individual "slave" instance will update their list dynamically
 *
 * These options can be configured in broker registry options:
 *
 * const broker = new ServiceBroker({
 * 	logger: true,
 * 	registry: {
 * 		strategy: "LatencyStrategy",
 * 		strategyOptions: {
 * 			sampleCount: 5,
 * 			lowLatency: 10,
 * 			collectCount: 5,
 * 			pingInterval: 10
 * 		}
 * 	}
 * });
 *
 * @class LatencyStrategy
 */
class LatencyStrategy extends BaseStrategy {

	constructor(registry, broker) {
		super(registry, broker);

		this.opts = _.defaultsDeep(registry.opts.strategyOptions, {
			sampleCount: 5,
			lowLatency: 10,
			collectCount: 5,
			pingInterval: 10
		});

		this.hostLatency = new Map();

		// short circuit
		if (!this.broker.transit) return;

		if (this.broker.localBus.listenerCount("$node.latencyMaster") === 0) {
			this.broker.logger.debug("Latency: We are MASTER");

			/* nodeMap contains:
				nodeID => {
					historicLatency: [],
					hostname: null
				}
			*/
			this.nodeMap = new Map();

			/* hostMap contains:
				hostname => {
					nodeList: []
				}
			*/
			this.hostMap = new Map();

			// claim as master
			this.broker.localBus.on("$node.latencyMaster", function() {});
			// respond to PONG
			this.broker.localBus.on("$node.pong", this.processPong.bind(this));
			// dynamically add new node
			this.broker.localBus.on("$node.connected", this.addNode.bind(this));
			// dynamically remove node
			this.broker.localBus.on("$node.disconnected", this.removeHostMap.bind(this));
			// try to discovery all nodes on start up
			this.broker.localBus.on("$broker.started", this.discovery.bind(this));
		} else {
			this.broker.logger.debug("Latency: We are SLAVE");
			// remove node if we are told by master
			this.broker.localBus.on("$node.latencySlave.removeHost", this.removeHostLatency.bind(this));
		}

		this.broker.localBus.on("$node.latencySlave", this.updateLatency.bind(this));
	}

	// Master
	discovery() {
		return this.broker.transit.sendPing().then(function() {
			setTimeout(this.pingHosts.bind(this), 1000 * this.opts.pingInterval);
		}.bind(this));
	}

	// Master
	pingHosts() {
		this.broker.logger.debug("Latency: Sending ping to hosts");
		/*
			Smart Ping: only ping the host, not the nodes (which may be many)

			Although, if that particular node on the host is overloaded,
			the measurement may be skewed.
		*/
		let hosts = [];
		if (this.hostMap.size > 0) {
			hosts = this.hostMap.values();
		}

		this.broker.Promise.map(hosts, function(host) {
			this.broker.logger.debug("Latency: Sending ping to", host.nodeList[0]);
			return this.broker.transit.sendPing(host.nodeList[0]);
		}.bind(this), { concurrency: 5 }).then(function() {
			setTimeout(this.pingHosts.bind(this), 1000 * this.opts.pingInterval);
		}.bind(this));
	}

	// Master
	processPong(payload) {
		let node = this.registry.nodes.get(payload.nodeID)
		if (!node) return;

		let avgLatency = null;

		this.broker.logger.debug("Latency: Process incoming pong");

		this.mapNode(node);

		let nodeMap = this.nodeMap.get(node.id);

		if (nodeMap.historicLatency.length > (this.opts.collectCount - 1))
			nodeMap.historicLatency.shift();

		nodeMap.historicLatency.push(payload.elapsedTime);

		avgLatency = nodeMap.historicLatency.reduce(function(sum, latency) {
			return sum + latency;
		}, 0) / nodeMap.historicLatency.length;

		this.broker.logger.debug("Latency: Broadcasting latency update");

		this.broker.localBus.emit("$node.latencySlave", {
			hostname: node.hostname,
			avgLatency: avgLatency
		});
	}

	// Master
	mapNode(node) {
		if (typeof this.nodeMap.get(node.id) === "undefined") {
			this.nodeMap.set(node.id, {
				historicLatency: [],
				hostname: node.hostname
			});
		}

		if (typeof this.hostMap.get(node.hostname) === "undefined") {
			this.hostMap.set(node.hostname, {
				nodeList: [ node.id ]
			});
		}
	}

	// Master
	addNode(payload) {

		this.broker.logger.debug("Latency: adding new node");

		let node = payload.node;

		this.mapNode(node);
		// each host may have multiple nodes
		let hostMap = this.hostMap.get(node.hostname);
		if (hostMap.nodeList.indexOf(node.id) === -1) {
			hostMap.nodeList.push(node.id);
		}

		this.broker.logger.debug("Latency: ", node.hostname, "has", hostMap.nodeList.length, "nodes");
	}

	// Master
	removeHostMap(payload) {
		let node = payload.node;

		let hostMap = this.hostMap.get(node.hostname);
		let nodeIndex = hostMap.nodeList.indexOf(node.id);
		if (nodeIndex > -1) {
			hostMap.nodeList.splice(nodeIndex, 1);
		}

		if (hostMap.nodeList.length > 0) return;

		// only remove the host if the last node disconnected

		this.broker.logger.debug("Latency: removing host", node.hostname);

		this.broker.localBus.emit("$node.latencySlave.removeHost", node.hostname);
		this.nodeMap.delete(node.id);
		this.hostMap.delete(node.hostname);
	}

	// Slave
	updateLatency(payload) {
		this.broker.logger.debug("Latency update received", payload);
		this.hostLatency.set(payload.hostname, payload.avgLatency);
	}

	// Slave
	removeHostLatency(hostname) {
		this.hostLatency.delete(hostname);
	}

	select(list) {
		let minEp = null;
		let minLatency = null;

		const sampleCount = this.opts.sampleCount;
		const count = sampleCount <= 0 || sampleCount > list.length ? list.length : sampleCount;
		for (let i = 0; i < count; i++) {
			let ep;
			// Get random endpoint
			if (count == list.length) {
				ep = list[i];
			} else {
				ep = list[random(0, list.length - 1)];
			}
			const epLatency = this.hostLatency.get(ep.node.hostname);

			// Check latency of endpoint
			if (typeof epLatency !== "undefined") {

				if (epLatency < this.opts.lowLatency)
					return ep;

				if (!minEp || !minLatency || epLatency < minLatency) {
					minLatency = epLatency;
					minEp = ep;
				}
			}
		}

		// Return the lowest latency
		if (minEp) {
			this.broker.logger.debug("Latency: Select", minEp.node.hostname, minLatency);
			return minEp;
		}

		this.broker.logger.debug("Latency: Select random");

		// Return a random item (no latency data)
		return list[random(0, list.length - 1)];
	}
}

module.exports = LatencyStrategy;
<commit_msg>remove necessary variables<commit_after>/*
 * moleculer
 * Copyright (c) 2018 MoleculerJS (https://github.com/moleculerjs/moleculer)
 * MIT Licensed
 */
"use strict";

const _ = require("lodash");

const { random } = require("lodash");
const BaseStrategy = require("./base");

/**
 * Lowest latency invocation strategy
 *
 * Since Strategy can be instantiated multiple times, therefore,
 * we need to have a "master" instance to send ping, and each
 * individual "slave" instance will update their list dynamically
 *
 * These options can be configured in broker registry options:
 *
 * const broker = new ServiceBroker({
 * 	logger: true,
 * 	registry: {
 * 		strategy: "LatencyStrategy",
 * 		strategyOptions: {
 * 			sampleCount: 5,
 * 			lowLatency: 10,
 * 			collectCount: 5,
 * 			pingInterval: 10
 * 		}
 * 	}
 * });
 *
 * @class LatencyStrategy
 */
class LatencyStrategy extends BaseStrategy {

	constructor(registry, broker) {
		super(registry, broker);

		this.opts = _.defaultsDeep(registry.opts.strategyOptions, {
			sampleCount: 5,
			lowLatency: 10,
			collectCount: 5,
			pingInterval: 10
		});

		this.hostLatency = new Map();

		/* hostMap contains:
			hostname => {
				historicLatency: [],
				nodeList: []
			}
		*/
		this.hostMap = new Map();

		// short circuit
		if (!this.broker.transit) return;

		if (this.broker.localBus.listenerCount("$node.latencyMaster") === 0) {
			this.broker.logger.debug("Latency: We are MASTER");

			// claim as master
			this.broker.localBus.on("$node.latencyMaster", function() {});
			// respond to PONG
			this.broker.localBus.on("$node.pong", this.processPong.bind(this));
			// dynamically add new node
			this.broker.localBus.on("$node.connected", this.addNode.bind(this));
			// dynamically remove node
			this.broker.localBus.on("$node.disconnected", this.removeHostMap.bind(this));
			// try to discovery all nodes on start up
			this.broker.localBus.on("$broker.started", this.discovery.bind(this));
		} else {
			this.broker.logger.debug("Latency: We are SLAVE");
			// remove node if we are told by master
			this.broker.localBus.on("$node.latencySlave.removeHost", this.removeHostLatency.bind(this));
		}

		this.broker.localBus.on("$node.latencySlave", this.updateLatency.bind(this));
	}

	// Master
	discovery() {
		return this.broker.transit.sendPing().then(function() {
			setTimeout(this.pingHosts.bind(this), 1000 * this.opts.pingInterval);
		}.bind(this));
	}

	// Master
	pingHosts() {
		this.broker.logger.debug("Latency: Sending ping to hosts");
		/*
			Smart Ping: only ping the host, not the nodes (which may be many)

			Although, if that particular node on the host is overloaded,
			the measurement may be skewed.
		*/
		let hosts = [];
		if (this.hostMap.size > 0) {
			hosts = this.hostMap.values();
		}

		this.broker.Promise.map(hosts, function(host) {
			this.broker.logger.debug("Latency: Sending ping to", host.nodeList[0]);
			return this.broker.transit.sendPing(host.nodeList[0]);
		}.bind(this), { concurrency: 5 }).then(function() {
			setTimeout(this.pingHosts.bind(this), 1000 * this.opts.pingInterval);
		}.bind(this));
	}

	// Master
	processPong(payload) {
		let node = this.registry.nodes.get(payload.nodeID)
		if (!node) return;

		let avgLatency = null;

		this.broker.logger.debug("Latency: Process incoming pong");

		this.mapNode(node);

		let hostMap = this.hostMap.get(node.hostname);

		if (hostMap.historicLatency.length > (this.opts.collectCount - 1))
			hostMap.historicLatency.shift();

		hostMap.historicLatency.push(payload.elapsedTime);

		avgLatency = hostMap.historicLatency.reduce(function(sum, latency) {
			return sum + latency;
		}, 0) / hostMap.historicLatency.length;

		this.broker.logger.debug("Latency: Broadcasting latency update");

		this.broker.localBus.emit("$node.latencySlave", {
			hostname: node.hostname,
			avgLatency: avgLatency
		});
	}

	// Master
	mapNode(node) {
		if (typeof this.hostMap.get(node.hostname) === "undefined") {
			this.hostMap.set(node.hostname, {
				historicLatency: [],
				nodeList: [ node.id ]
			});
		}
	}

	// Master
	addNode(payload) {

		this.broker.logger.debug("Latency: adding new node");

		let node = payload.node;

		this.mapNode(node);
		// each host may have multiple nodes
		let hostMap = this.hostMap.get(node.hostname);
		if (hostMap.nodeList.indexOf(node.id) === -1) {
			hostMap.nodeList.push(node.id);
		}

		this.broker.logger.debug("Latency: ", node.hostname, "has", hostMap.nodeList.length, "nodes");
	}

	// Master
	removeHostMap(payload) {
		let node = payload.node;

		let hostMap = this.hostMap.get(node.hostname);
		if (typeof hostMap === 'undefined') return;

		let nodeIndex = hostMap.nodeList.indexOf(node.id);
		if (nodeIndex > -1) {
			hostMap.nodeList.splice(nodeIndex, 1);
		}

		if (hostMap.nodeList.length > 0) return;

		// only remove the host if the last node disconnected

		this.broker.logger.debug("Latency: removing host", node.hostname);

		this.broker.localBus.emit("$node.latencySlave.removeHost", node.hostname);
		this.hostMap.delete(node.hostname);
	}

	// Slave
	updateLatency(payload) {
		this.broker.logger.debug("Latency update received", payload);
		this.hostLatency.set(payload.hostname, payload.avgLatency);
	}

	// Slave
	removeHostLatency(hostname) {
		this.hostLatency.delete(hostname);
	}

	select(list) {
		let minEp = null;
		let minLatency = null;

		const sampleCount = this.opts.sampleCount;
		const count = sampleCount <= 0 || sampleCount > list.length ? list.length : sampleCount;
		for (let i = 0; i < count; i++) {
			let ep;
			// Get random endpoint
			if (count == list.length) {
				ep = list[i];
			} else {
				ep = list[random(0, list.length - 1)];
			}
			const epLatency = this.hostLatency.get(ep.node.hostname);

			// Check latency of endpoint
			if (typeof epLatency !== "undefined") {

				if (epLatency < this.opts.lowLatency)
					return ep;

				if (!minEp || !minLatency || epLatency < minLatency) {
					minLatency = epLatency;
					minEp = ep;
				}
			}
		}

		// Return the lowest latency
		if (minEp) {
			this.broker.logger.debug("Latency: Select", minEp.node.hostname, minLatency);
			return minEp;
		}

		this.broker.logger.debug("Latency: Select random");

		// Return a random item (no latency data)
		return list[random(0, list.length - 1)];
	}
}

module.exports = LatencyStrategy;
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var unzip = require('unzip');
var exec = require('child_process').exec;

var apks = fs.readdirSync('./captures').filter(function(entry) {
    return entry.match('\\.apk$');
});

console.log(apks);
var ZIPS_DIR = './captures/zips';

if(!fs.existsSync(ZIPS_DIR)) {
    fs.mkdirSync(ZIPS_DIR);
}

var detectedTraits = {};
var apkIndex = 0;

scanNextFile();

function scanNextFile() {
    if(apkIndex < apks.length) {
        var apkFile = apks[apkIndex++];
        scanAPK(apkFile, function(resultTraits) {

            detectedTraits[ apkFile ] = resultTraits;

            process.nextTick(scanNextFile);

        });
    } else {
        findTotals();
    }
}

function findTotals() {
    console.log('TIME TO TOTAL!');

    var totals = [];

    for(var packageName in detectedTraits) {
        var traits = detectedTraits[packageName];

        var appEntry = {
            name: packageName,
            traits: Array.prototype.slice.call(traits, 0)
        };

        var total = 0;
        console.log('###### ' + packageName + ' #######');
        traits.forEach(function(tr) {
            console.log('+ ', tr.amount.toFixed(2), '\t', tr.reason);
            total += tr.amount;
        });

        appEntry.total = total.toFixed(2);

        console.log(total.toFixed(2) + ' TOTAL');
        console.log('\n---\n');

        totals.push(appEntry);
    }

    fs.writeFileSync('captures/totals.json', JSON.stringify(totals, null, 4));
}

function scanAPK(apkFile, doneCallback) {

    var cwd = process.cwd();
    var apkFullPath = path.join('./captures', apkFile);
    var zipPath = path.join(ZIPS_DIR, apkFile + '.zip');
    var extractDir = path.join(ZIPS_DIR, apkFile.replace('.apk', ''));
    var tmpDir = path.join(extractDir, 'TMP!!!');
    var classesDex = path.join(extractDir, 'classes.dex');
    var classesTmpDex = path.join(tmpDir, 'classes.dex');
    var classesJar = path.join(tmpDir, 'classes-dex2jar.jar');
    var ddxDir = path.join(extractDir, 'ddx');
    var ddxBin = path.join(cwd, 'ddx1.26.jar');
    var commands = [];

    if(!fs.existsSync(extractDir)) {
        commands.push('cp ' + apkFullPath + ' ' + zipPath);
        commands.push('unzip ' + zipPath + ' -d ' + extractDir);
    }

    if(!fs.existsSync(tmpDir)) {
        commands.push('mkdir ' + tmpDir);
    }

    commands.push('cp ' + classesDex + ' ' + tmpDir);

    if(!fs.existsSync(classesJar)) {

        commands.push('cd ' + tmpDir);

        // converts .dex into .jar
        commands.push('d2j-dex2jar.sh classes.dex');

        // extracts the files inside the .jar
        commands.push('jar xvf classes-dex2jar.jar');
        commands.push('cd ' + cwd);

    }

    // dex dex
    if(!fs.existsSync(ddxDir)) {
        commands.push('mkdir ' + ddxDir);
        commands.push('cp ' + classesDex + ' ' + ddxDir);
        commands.push('cd ' + ddxDir);
        commands.push('java -jar ' + ddxBin + ' -d ' + 'ddx' + ' classes.dex');
        commands.push('cd ' + cwd);
    }

    var cmd = commands.join('; ');
    console.log('Full command', cmd);

    exec(cmd, function(error, stdout, stderr) {
        // We can now actually analyse the APK contents
        detectHTML5ness(extractDir, doneCallback);
    });

}

function filterHTMLfiles(f) {
    return f.match(/\.html?$/i);
}

function filterJSfiles(f) {
    return f.match(/\.js$/i);
}

function filterClassFiles(f) {
    return f.match(/\.class$/i);
}

function filterPhonegapiness(f) {
    var expressions = [
        /com\/phonegap/,
        /com\/sencha/,
        /org\/apache\/cordova/,
        /org\/appcelerator/
    ];

    return expressions.some(function(expression) {
        return f.match(expression);
    });
}

function detectHTML5ness(apkDir, doneCallback) {
    console.log('Guessing html5-ness in', apkDir);

    var apkFiles = recursiveDirList(apkDir);
    var traits = [];

    // Possible HTML5 tell-tale signs

    // presence of html files
    var htmlFiles = apkFiles.filter(filterHTMLfiles);

    if(htmlFiles.length) {
        traits.push({
            amount: 5,
            reason: 'Presence of HTML files',
            files: htmlFiles
        });
    }

    // presence of js files
    var jsFiles = apkFiles.filter(filterJSfiles);

    if(jsFiles.length) {
        traits.push({
            amount: 15,
            reason: 'Presence of JavaScript files',
            files: jsFiles
        });
    }

    // classes.dex -> phonegap / ... / classes?
    var javaClasses = apkFiles.filter(filterClassFiles);
    var phonegapey = javaClasses.filter(filterPhonegapiness);

    if(phonegapey.length) {
        traits.push({
            amount: 50,
            reason: 'Presence of Phonegap or similar',
            files: phonegapey
        });
    }

    // webview calls?
    // the uncompiled ddx sort-of-assembly files have some metadata
    // referring to calls to WebView-classes
    // So we'll increase the chances of this being a 'web' app each time
    // a file is using WebView or derived classes
    var ddxDir = path.join(apkDir, 'ddx');

    console.log(ddxDir);

    exec('grep -lr WebView ' + path.join(ddxDir, '*'), function(error, stdout, stderr) {

        var matches = stdout.split('\n');
        var num = matches.length;
        if(num) {
            traits.push({
                amount: num * 0.1,
                reason: num.toFixed(2) + ' calls to WebView',
                files: matches
            });
        }

        doneCallback(traits);

    });

}

function recursiveDirList(dir) {

    var results = [];
    var entries = fs.readdirSync(dir);

    entries.forEach(function(entry) {

        var fullPath = path.join(dir, entry);
        var stat = fs.statSync(fullPath);

        if(stat && stat.isDirectory()) {
            results = results.concat( recursiveDirList(fullPath) );
        } else {
            results.push(fullPath);
        }

    });

    return results;
}

<commit_msg>constants<commit_after>var fs = require('fs');
var path = require('path');
var unzip = require('unzip');
var exec = require('child_process').exec;

var APKS_DIR = './captures/apks';
var ZIPS_DIR = './captures/zips';

var apks = fs.readdirSync(APKS_DIR).filter(function(entry) {
    return entry.match('\\.apk$');
});

if(!fs.existsSync(ZIPS_DIR)) {
    fs.mkdirSync(ZIPS_DIR);
}

var detectedTraits = {};
var apkIndex = 0;

scanNextFile();

function scanNextFile() {
    if(apkIndex < apks.length) {
        var apkFile = apks[apkIndex++];
        console.log('Scanning ' + Math.round(apkIndex/apks.length*100.0).toFixed(2) +'%');
        scanAPK(apkFile, function(resultTraits) {

            detectedTraits[ apkFile ] = resultTraits;

            process.nextTick(scanNextFile);

        });
    } else {
        findTotals();
    }
}

function findTotals() {
    console.log('TIME TO TOTAL!');

    var totals = [];

    for(var packageName in detectedTraits) {
        var traits = detectedTraits[packageName];

        var appEntry = {
            name: packageName,
            traits: Array.prototype.slice.call(traits, 0)
        };

        var total = 0;
        console.log('###### ' + packageName + ' #######');
        traits.forEach(function(tr) {
            console.log('+ ', tr.amount.toFixed(2), '\t', tr.reason);
            total += tr.amount;
        });

        appEntry.total = total.toFixed(2);

        console.log(total.toFixed(2) + ' TOTAL');
        console.log('\n---\n');

        totals.push(appEntry);
    }

    fs.writeFileSync('captures/totals.json', JSON.stringify(totals, null, 4));
}

function scanAPK(apkFile, doneCallback) {

    var cwd = process.cwd();
    var apkFullPath = path.join(APKS_DIR, apkFile);
    var zipPath = path.join(ZIPS_DIR, apkFile + '.zip');
    var extractDir = path.join(ZIPS_DIR, apkFile.replace('.apk', ''));
    var tmpDir = path.join(extractDir, 'TMP!!!');
    var classesDex = path.join(extractDir, 'classes.dex');
    var classesTmpDex = path.join(tmpDir, 'classes.dex');
    var classesJar = path.join(tmpDir, 'classes-dex2jar.jar');
    var ddxDir = path.join(extractDir, 'ddx');
    var ddxBin = path.join(cwd, 'ddx1.26.jar');
    var commands = [];

    if(!fs.existsSync(extractDir)) {
        commands.push('cp ' + apkFullPath + ' ' + zipPath);
        commands.push('unzip ' + zipPath + ' -d ' + extractDir);
    }

    if(!fs.existsSync(tmpDir)) {
        commands.push('mkdir ' + tmpDir);
    }

    commands.push('cp ' + classesDex + ' ' + tmpDir);

    if(!fs.existsSync(classesJar)) {

        commands.push('cd ' + tmpDir);

        // converts .dex into .jar
        commands.push('d2j-dex2jar.sh classes.dex');

        // extracts the files inside the .jar
        commands.push('jar xvf classes-dex2jar.jar');
        commands.push('cd ' + cwd);

    }

    // dex dex
    if(!fs.existsSync(ddxDir)) {
        commands.push('mkdir ' + ddxDir);
        commands.push('cp ' + classesDex + ' ' + ddxDir);
        commands.push('cd ' + ddxDir);
        commands.push('java -jar ' + ddxBin + ' -d ' + 'ddx' + ' classes.dex');
        commands.push('cd ' + cwd);
    }

    var cmd = commands.join('; ');
    console.log('Full command', cmd);

    exec(cmd, function(error, stdout, stderr) {
        // We can now actually analyse the APK contents
        detectHTML5ness(extractDir, doneCallback);
    });

}

function filterHTMLfiles(f) {
    return f.match(/\.html?$/i);
}

function filterJSfiles(f) {
    return f.match(/\.js$/i);
}

function filterClassFiles(f) {
    return f.match(/\.class$/i);
}

function filterPhonegapiness(f) {
    var expressions = [
        /com\/phonegap/,
        /com\/sencha/,
        /org\/apache\/cordova/,
        /org\/appcelerator/
    ];

    return expressions.some(function(expression) {
        return f.match(expression);
    });
}

function detectHTML5ness(apkDir, doneCallback) {
    console.log('Guessing html5-ness in', apkDir);

    var apkFiles = recursiveDirList(apkDir);
    var traits = [];

    // Possible HTML5 tell-tale signs

    // presence of html files
    var htmlFiles = apkFiles.filter(filterHTMLfiles);

    if(htmlFiles.length) {
        traits.push({
            amount: 5,
            reason: 'Presence of HTML files',
            files: htmlFiles
        });
    }

    // presence of js files
    var jsFiles = apkFiles.filter(filterJSfiles);

    if(jsFiles.length) {
        traits.push({
            amount: 15,
            reason: 'Presence of JavaScript files',
            files: jsFiles
        });
    }

    // classes.dex -> phonegap / ... / classes?
    var javaClasses = apkFiles.filter(filterClassFiles);
    var phonegapey = javaClasses.filter(filterPhonegapiness);

    if(phonegapey.length) {
        traits.push({
            amount: 50,
            reason: 'Presence of Phonegap or similar',
            files: phonegapey
        });
    }

    // webview calls?
    // the uncompiled ddx sort-of-assembly files have some metadata
    // referring to calls to WebView-classes
    // So we'll increase the chances of this being a 'web' app each time
    // a file is using WebView or derived classes
    var ddxDir = path.join(apkDir, 'ddx');

    console.log(ddxDir);

    exec('grep -lr WebView ' + path.join(ddxDir, '*'), function(error, stdout, stderr) {

        var matches = stdout.split('\n');
        var num = matches.length;
        if(num) {
            traits.push({
                amount: num * 0.1,
                reason: num.toFixed(2) + ' calls to WebView',
                files: matches
            });
        }

        doneCallback(traits);

    });

}

function recursiveDirList(dir) {

    var results = [];
    var entries = fs.readdirSync(dir);

    entries.forEach(function(entry) {

        var fullPath = path.join(dir, entry);
        var stat = fs.statSync(fullPath);

        if(stat && stat.isDirectory()) {
            results = results.concat( recursiveDirList(fullPath) );
        } else {
            results.push(fullPath);
        }

    });

    return results;
}

<|endoftext|>


<commit_before>{
  pin: {
    name: "pin",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "pin [messageid]\n\nPins a message to a channel.```");
        return;
      }
      message.channel.fetchMessage(command.params[0]).then(function(message) {
        message.pin().then(function() {
          message.channel.sendMessage("Message pinned.");
        });
      }).catch(function(error) {
        message.channel.sendMessage("Message not found.");
      });
    }
  },
  unpin: {
    name: "unpin",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "unpin [messageid]\n\nUnpins a message to a channel.```");
        return;
      }
      message.channel.fetchMessage(command.params[0]).then(function(message) {
        message.unpin().then(function() {
          message.channel.sendMessage("Message unpinned.");
        });
      }).catch(function(error) {
        message.channel.sendMessage("Message not found.");
      });
    }
  },
  topic: {
    name: "topic",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "topic [text]\n\nEdits the for a channel.```");
        return;
      }
      message.channel.setTopic(command.params.join(me.seperator));
    }
  },
  position: {
    name: "position",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1 || command.params[0].match(/^\d*$/) === null) {
        message.channel.sendMessage("```" + me.prefix + "position [position]\n\nRe-positions the channel in the channel list.```");
        return;
      }
      message.channel.setPosition(parseInt(command.params[0]));
    }
  },
  clone: {
    name: "clone",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "clone [name]\n\nClones the current channel into a new channel.```");
        return;
      }

      if (command.params[0].length > 100) {
        message.channel.sendMessage("That name is too long.");
        return;
      }

      if (command.params[0].length < 2) {
        message.channel.sendMessage("That name is too short.");
        return;
      }

      if (command.params[0].match(/^[A-Za-z_-\d]*$/) === null) {
        message.channel.sendMessage("That name is invalid.");
        return;
      }

      message.channel.clone(command.params[0]).then(function() {
        message.channel.sendMessage("Channel cloned.");
      });
    }
  },
  clear: {
    name: "clear",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "clear [amount]\n\nClears recent messages from a channel.```");
        return;
      }

      var amount = 10;
      if (command.params[0].match(/^\d*$/) !== null) {
        amount = parseInt(command.params[0]);
      }

      if (amount > 100) {
        message.channel.sendMessage("I can't delete that many messages.");
        return;
      }

      message.channel.bulkDelete(amount).catch(function(error) {
        message.channel.sendMessage("I had some trouble deleting those messages.");
      });
    }
  },
  kick: {
    name: "kick",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1 || parseMention(command.params[0]) === null) {
        message.channel.sendMessage("```" + me.prefix + "kick [user]\n\nKicks a member from this guild.```");
        return;
      }

      var target = message.guild.member(parseMention(command.params[0]));

      if (target === null) {
        message.channel.sendMessage("Member not found.");
        return;
      }

      if (target.kickable === false) {
        message.channel.sendMessage("I can't kick that member.");
        return;
      }

      target.kick().then(function() {
        message.channel.sendMessage("Member kicked.");
      });
    }
  }
}
<commit_msg>Permission & voiceclone commands added<commit_after>{
  game: {
    name: "game",
    runtime: function(message, client) {
      if (processCommand(message.content).params.length === 0) {
        message.channel.sendMessage("```" + me.prefix + "game [game]\n\nChanges the current game for this bot.```");
        return;
      }
      client.user.setGame(processCommand(message.content).params.join(me.seperator));
    }
  },
  pin: {
    name: "pin",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "pin [messageid]\n\nPins a message to a channel.```");
        return;
      }
      message.channel.fetchMessage(command.params[0]).then(function(message) {
        message.pin().then(function() {
          message.channel.sendMessage("Message pinned.");
        });
      }).catch(function(error) {
        message.channel.sendMessage("Message not found.");
      });
    }
  },
  unpin: {
    name: "unpin",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "unpin [messageid]\n\nUnpins a message to a channel.```");
        return;
      }
      message.channel.fetchMessage(command.params[0]).then(function(message) {
        message.unpin().then(function() {
          message.channel.sendMessage("Message unpinned.");
        });
      }).catch(function(error) {
        message.channel.sendMessage("Message not found.");
      });
    }
  },
  topic: {
    name: "topic",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "topic [text]\n\nEdits the for a channel.```");
        return;
      }
      message.channel.setTopic(command.params.join(me.seperator));
    }
  },
  position: {
    name: "position",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1 || command.params[0].match(/^\d*$/) === null) {
        message.channel.sendMessage("```" + me.prefix + "position [position]\n\nRe-positions the channel in the channel list.```");
        return;
      }
      message.channel.setPosition(parseInt(command.params[0]));
    }
  },
  clone: {
    name: "clone",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "clone [name]\n\nClones the current channel into a new channel.```");
        return;
      }

      if (command.params[0].length > 100) {
        message.channel.sendMessage("That name is too long.");
        return;
      }

      if (command.params[0].length < 2) {
        message.channel.sendMessage("That name is too short.");
        return;
      }

      if (command.params[0].match(/^[A-Za-z_-\d]*$/) === null) {
        message.channel.sendMessage("That name is invalid.");
        return;
      }

      message.channel.clone(command.params[0]).then(function() {
        message.channel.sendMessage("Channel cloned.");
      });
    }
  },
  voiceclone: {
    name: "voiceclone",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "voiceclone [name]\n\nClones the current connected voice channel into a new channel.```");
        return;
      }

      if (typeof message.member.voiceChannel === "undefined") {
        message.channel.sendMessage(makeMention(message.author.id) + " You don't appear to be in a voice channel.");
        return;
      }

      if (command.params[0].length > 100) {
        message.channel.sendMessage("That name is too long.");
        return;
      }

      if (command.params[0].length < 2) {
        message.channel.sendMessage("That name is too short.");
        return;
      }

      message.member.voiceChannel.clone(command.params[0]).then(function() {
        message.channel.sendMessage("Voice channel cloned.");
      });
    }
  },
  clear: {
    name: "clear",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1) {
        message.channel.sendMessage("```" + me.prefix + "clear [amount]\n\nClears recent messages from a channel.```");
        return;
      }

      var amount = 10;
      if (command.params[0].match(/^\d*$/) !== null) {
        amount = parseInt(command.params[0]);
      }

      if (amount > 100) {
        message.channel.sendMessage("I can't delete that many messages.");
        return;
      }

      message.channel.bulkDelete(amount).catch(function(error) {
        message.channel.sendMessage("I had some trouble deleting those messages.");
      });
    }
  },
  permission: {
    name: "permission",
    startup: function(client, data) {
      data.bin.perm = {};
    },
    runtime: function(message, client, data) {
      var command = processCommand(message.content);
      if (command.params.length < 2) {
        message.channel.sendMessage("```" + me.prefix + "permission [command] [role]\n\nAllows or blocks the usage of a command by role.```");
        return;
      }

      var target = findCommand(command.params[0]);
      if (target === null) {
        message.channel.sendMessage("Command not found.");
        return;
      }

      //var role = message.guild.roles.find("name", command.params[1]);
      var role = message.guild.roles.get(parseRoleMention(command.params[1]));
      console.log(role);
      if (typeof role === "undefined") {
        message.channel.sendMessage("Role not found.");
        return;
      }

      data.bin.perm[target.name] = role.id;
      message.channel.sendMessage("Permission status updated.");
    },
    commandvalidate: function(message, client, data) {
      var command = processCommand(message.content);
      if (typeof data.bin.perm[command.command] !== "string") {
        return true;
      } else if (message.member.highestRole.comparePositionTo(message.guild.roles.get(data.bin.perm[command.command])) >= 0) {
        return true;
      }
      return false;
    }
  },
  kick: {
    name: "kick",
    runtime: function(message, client) {
      var command = processCommand(message.content);
      if (command.params.length < 1 || parseMention(command.params[0]) === null) {
        message.channel.sendMessage("```" + me.prefix + "kick [user]\n\nKicks a member from this guild.```");
        return;
      }

      var target = message.guild.member(parseMention(command.params[0]));

      if (target === null) {
        message.channel.sendMessage("Member not found.");
        return;
      }

      if (target.kickable === false) {
        message.channel.sendMessage("I can't kick that member.");
        return;
      }

      target.kick().then(function() {
        message.channel.sendMessage("Member kicked.");
      });
    }
  }
}
<|endoftext|>


<commit_before>/**
 * @file AutoCompleteFilter component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';
import classNames from 'classnames';
import isNumber from 'lodash/isNumber';

import TextField from '../TextField';
import Popup from '../Popup';
import List from '../List';
import DynamicRenderList from '../DynamicRenderList';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import DropdownCalculation from '../_vendors/DropdownCalculation';
import Valid from '../_vendors/Valid';

class AutoCompleteFilter extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.triggerEl = null;

        this.state = {
            tempSelectIndex: null,
            value: props.value,
            filter: props.filterInitValue,
            popupVisible: false,
            isAbove: false,
            listData: props.data
        };

    }

    filterData = (filter = this.state.filter, data = this.props.data) => {

        const {minFilterLength} = this.props;

        if (!filter || filter.length < minFilterLength) {
            return data;
        }

        const {valueField, displayField, renderer, filterCallback} = this.props;

        if (filterCallback) {
            return filterCallback(filter, data);
        }

        return data.filter(item => {

            if (!item) {
                return false;
            }

            if (renderer) {
                return renderer(item).toString().toUpperCase().includes(filter.toUpperCase());
            }

            if (typeof item === 'object') {

                const itemDisplay = Util.getTextByDisplayField(item, displayField, valueField);

                if (itemDisplay) {
                    return itemDisplay.toString().toUpperCase().includes(filter.toUpperCase());
                }

            }

            return item.toString().toUpperCase().includes(filter.toUpperCase());

        });

    };

    filterFocusHandler = (...args) => {

        if (this.props.disabled) {
            return;
        }

        const {onFocus} = this.props,
            {filter, listData} = this.state,
            state = {
                filterFocused: true
            };

        onFocus && onFocus(...args);

        if (filter) {
            state.popupVisible = true;
            state.tempSelectIndex = listData.length > 0 ? 0 : null;
        }

        this.setState(state);

    };

    filterBlurHandler = (...args) => {
        const {disabled, onBlur} = this.props;
        !disabled && onBlur && onBlur(...args);
    };

    filterKeyDownHandler = e => {

        const {useDynamicRenderList} = this.props,
            {tempSelectIndex, listData} = this.state,
            listEl = useDynamicRenderList ? this.refs.dynamicRenderList : this.refs.list;
        let index = tempSelectIndex;

        if (e.keyCode === 38) { // up
            index--;
        } else if (e.keyCode === 40) { // down
            index++;
        }

        const newTempSelectIndex = Valid.range(index, 0, listData.length - 1);

        this.setState({
            tempSelectIndex: newTempSelectIndex
        }, () => {

            const {onFilterKeyDown} = this.props;
            onFilterKeyDown && onFilterKeyDown(e);

            listEl && listEl.adjustScroll();

        });

    };

    filterPressEnterHandler = (e, filter) => {

        const {autoClose} = this.props,
            state = {};

        if (autoClose) {
            state.popupVisible = false;
        }

        this.setState(state, () => {

            const {onFilterPressEnter} = this.props;
            onFilterPressEnter && onFilterPressEnter(filter);

            this.update();

        });

    };

    filterChangeHandler = filter => {

        const {data, minFilterLength} = this.props,
            state = {
                filter,
                popupVisible: !!filter && filter.length >= minFilterLength
            };

        if (!filter) {
            state.listData = data;
            state.value = null;
            state.tempSelectIndex = null;
        } else {
            state.listData = this.filterData(filter);
            state.tempSelectIndex = state.listData.length > 0 ? 0 : null;
        }

        this.setState(state, () => {

            const {onFilterChange} = this.props;
            onFilterChange && onFilterChange(filter);

            this.refs.popup && this.refs.popup.resetPosition();

        });

    };

    closePopup = () => {
        this.setState({
            popupVisible: false
        }, () => {
            const {onPopupClosed} = this.props;
            onPopupClosed && onPopupClosed();
        });
    };

    popupRenderHandler = popupEl => {

        const isAbove = DropdownCalculation.isAbove(this.autoCompleteFilterEl, this.triggerEl, findDOMNode(popupEl));

        if (isAbove !== this.state.isAbove) {
            this.setState({
                isAbove
            });
        }

    };

    itemClickHandler = value => {

        const {autoClose, valueField, displayField, renderer} = this.props,
            filter = renderer ? renderer(value) : Util.getTextByDisplayField(value, displayField, valueField),
            state = {
                tempSelectIndex: null,
                value,
                filter,
                listData: this.filterData(filter)
            },
            isChanged = this.state.value != value;

        if (autoClose) {
            state.popupVisible = false;
        }

        this.setState(state, () => {
            const {onItemClick, onChange} = this.props;
            onItemClick && onItemClick(value);
            isChanged && onChange && onChange(value);
        });

    };

    update = () => {

        const {displayField, valueField, renderer} = this.props,
            {filter, tempSelectIndex, listData} = this.state;
        let state = null;

        if (filter && listData && listData.length > 0) {

            state = {};

            const index = isNumber(tempSelectIndex) ? tempSelectIndex : 0;

            state.value = listData[index];

            state.filter = renderer ?
                renderer(state.value)
                :
                Util.getTextByDisplayField(state.value, displayField, valueField);

            state.listData = this.filterData(state.filter);

        }

        this.setState(state, () => {
            if (state) {
                const {onFilterChange, onChange} = this.props;
                onFilterChange && onFilterChange(state.filter);
                onChange && onChange(state.value);
            }
        });

    };

    componentDidMount() {
        this.autoCompleteFilterEl = this.refs.autoCompleteFilter;
        this.triggerEl = findDOMNode(this.refs.trigger);
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.state.value) {
            this.setState({
                value: nextProps.value
            });
        }
    }

    render() {

        const {
                className, popupClassName, style, popupStyle, theme, popupTheme, name, placeholder,
                disabled, iconCls, rightIconCls, valueField, displayField, descriptionField,
                noMatchedPopupVisible, noMatchedMsg, popupChildren, renderer,
                useDynamicRenderList, listHeight, itemHeight, scrollBuffer,
                onFilterClear, onMouseOver, onMouseOut
            } = this.props,
            {isAbove, tempSelectIndex, value, filter, popupVisible, listData} = this.state,

            emptyEl = [{
                itemRenderer() {
                    return (

                            {
                                noMatchedMsg ?
                                    noMatchedMsg
                                    :

                                        No matched value.

                            }

                    );
                },
                disableTouchRipple: true
            }],

            isEmpty = !listData || listData.length < 1,

            wrapperClassName = classNames('auto-complete-filter', {
                [className]: className
            }),
            triggerClassName = classNames('auto-complete-filter-trigger',
                isEmpty && !noMatchedPopupVisible ? '' : (popupVisible ? ' activated' : ''),
                isAbove ? ' above' : ' blow'),
            autoCompletePopupClassName = classNames('auto-complete-filter-popup',
                isAbove ? ' above' : ' blow', {
                    [popupClassName]: popupClassName
                }),
            autoCompletePopupStyle = Object.assign({
                width: this.triggerEl && getComputedStyle(this.triggerEl).width
            }, popupStyle);

        return (

                {
                    name ?
                        <input type="hidden"
                               name={name}
                               value={Util.getValueByValueField(value, valueField, displayField)}/>
                        :
                        null
                }

                <TextField ref="trigger"
                           className={triggerClassName}
                           theme={theme}
                           value={filter}
                           placeholder={placeholder}
                           disabled={disabled}
                           iconCls={iconCls}
                           rightIconCls={rightIconCls}
                           onFocus={this.filterFocusHandler}
                           onBlur={this.filterBlurHandler}
                           onMouseOver={onMouseOver}
                           onMouseOut={onMouseOut}
                           onChange={this.filterChangeHandler}
                           onKeyDown={this.filterKeyDownHandler}
                           onPressEnter={this.filterPressEnterHandler}
                           onClear={onFilterClear}
                           onRightIconClick={this.filterPressEnterHandler}/>

                {
                    isEmpty && !noMatchedPopupVisible ?
                        null
                        :
                        <Popup ref="popup"
                               className={autoCompletePopupClassName}
                               style={autoCompletePopupStyle}
                               theme={popupTheme}
                               visible={popupVisible}
                               triggerEl={this.triggerEl}
                               hasTriangle={false}
                               position={isAbove ? Popup.Position.TOP_LEFT : Popup.Position.BOTTOM_LEFT}
                               onRender={this.popupRenderHandler}
                               onRequestClose={this.closePopup}>

                            {
                                isEmpty ?
                                    <List className="auto-complete-filter-list"
                                          theme={popupTheme}
                                          data={emptyEl}/>
                                    :
                                    (
                                        useDynamicRenderList ?
                                            <DynamicRenderList ref="dynamicRenderList"
                                                               className="auto-complete-filter-list"
                                                               theme={popupTheme}
                                                               data={listData}
                                                               value={listData[tempSelectIndex]}
                                                               valueField={valueField}
                                                               displayField={displayField}
                                                               descriptionField={descriptionField}
                                                               renderer={renderer}
                                                               listHeight={listHeight}
                                                               itemHeight={itemHeight}
                                                               scrollBuffer={scrollBuffer}
                                                               onItemClick={this.itemClickHandler}/>
                                            :
                                            <List ref="list"
                                                  className="auto-complete-filter-list"
                                                  theme={popupTheme}
                                                  data={listData}
                                                  value={listData[tempSelectIndex]}
                                                  valueField={valueField}
                                                  displayField={displayField}
                                                  descriptionField={descriptionField}
                                                  renderer={renderer}
                                                  onItemClick={this.itemClickHandler}/>
                                    )
                            }

                            {popupChildren}

                        </Popup>
                }

        );
    }

}

AutoCompleteFilter.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the popup element.
     */
    popupClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Override the styles of the popup element.
     */
    popupStyle: PropTypes.object,

    /**
     * The theme.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The theme.
     */
    popupTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The name of the auto complete.
     */
    name: PropTypes.string,

    /**
     * The placeholder of the field.
     */
    placeholder: PropTypes.string,

    /**
     * Children passed into the List.
     */
    data: PropTypes.oneOfType([

        // not grouped
        PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({

            /**
             * The CSS class name of the list button.
             */
            className: PropTypes.string,

            /**
             * Override the styles of the list button.
             */
            style: PropTypes.object,

            /**
             * The theme of the list button.
             */
            theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

            /**
             * The text value of the list button.Type can be string or number.
             */
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * The desc value of the list button. Type can be string or number.
             */
            desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * If true, the list button will be disabled.
             */
            disabled: PropTypes.bool,

            /**
             * If true,the button will be have loading effect.
             */
            isLoading: PropTypes.bool,

            /**
             * If true,the element's ripple effect will be disabled.
             */
            disableTouchRipple: PropTypes.bool,

            /**
             * Use this property to display an icon. It will display on the left.
             */
            iconCls: PropTypes.string,

            /**
             * Use this property to display an icon. It will display on the right.
             */
            rightIconCls: PropTypes.string,

            /**
             * You can create a complicated renderer callback instead of value and desc prop.
             */
            itemRenderer: PropTypes.func,

            /**
             * Callback function fired when a list item touch-tapped.
             */
            onClick: PropTypes.func

        }), PropTypes.string, PropTypes.number])),

        // grouped
        PropTypes.array

    ]).isRequired,

    /**
     * If true, the auto complete will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the popup list automatically closed after selection.
     */
    autoClose: PropTypes.bool,

    minFilterLength: PropTypes.number,

    /**
     * Callback function fired when filter changed.
     */
    filterCallback: PropTypes.func,

    /**
     * Use this property to display an icon.
     */
    iconCls: PropTypes.string,

    /**
     * Use this property to display an icon.
     */
    rightIconCls: PropTypes.string,

    /**
     * The visiblity of no matched popup.
     */
    noMatchedPopupVisible: PropTypes.bool,

    /**
     * The message of no matched value.
     */
    noMatchedMsg: PropTypes.string,

    popupChildren: PropTypes.any,

    filterInitValue: PropTypes.string,

    useDynamicRenderList: PropTypes.bool,
    listHeight: PropTypes.number,
    itemHeight: PropTypes.number,
    scrollBuffer: PropTypes.number,

    /**
     * You can create a complicated renderer callback instead of value and desc prop.
     */
    renderer: PropTypes.func,

    /**
     * The function that trigger when filter changes.
     */
    onFilterChange: PropTypes.func,

    /**
     * The function that trigger when press enter.
     */
    onFilterPressEnter: PropTypes.func,

    /**
     * The function that trigger when filter cleared.
     */
    onFilterClear: PropTypes.func,

    /**
     * The function that trigger when value changed.
     */
    onChange: PropTypes.func,

    /**
     * The function that trigger when touch-tap the list items.
     */
    onItemClick: PropTypes.func,

    /**
     * Callback function fired when AutoCompleteFilter get focus.
     */
    onFocus: PropTypes.func,

    /**
     * Callback function fired when AutoCompleteFilter lose focus.
     */
    onBlur: PropTypes.func,

    onMouseOver: PropTypes.func,
    onMouseOut: PropTypes.func,
    onPopupClosed: PropTypes.func

};

AutoCompleteFilter.defaultProps = {

    theme: Theme.DEFAULT,
    popupTheme: Theme.DEFAULT,

    data: [],
    disabled: false,
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',
    autoClose: false,
    minFilterLength: 1,
    iconCls: null,
    rightIconCls: 'fas fa-search',
    noMatchedPopupVisible: true,
    filterInitValue: '',
    useDynamicRenderList: false

};

export default AutoCompleteFilter;<commit_msg>update AutoCompleteFilter<commit_after>/**
 * @file AutoCompleteFilter component
 * @author liangxiaojun(liangxiaojun@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {findDOMNode} from 'react-dom';
import classNames from 'classnames';
import isNumber from 'lodash/isNumber';

import TextField from '../TextField';
import Popup from '../Popup';
import List from '../List';
import DynamicRenderList from '../DynamicRenderList';
import Theme from '../Theme';

import Util from '../_vendors/Util';
import DropdownCalculation from '../_vendors/DropdownCalculation';
import Valid from '../_vendors/Valid';

class AutoCompleteFilter extends Component {

    static Theme = Theme;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.triggerEl = null;

        this.state = {
            tempSelectIndex: null,
            value: props.value,
            filter: props.filterInitValue,
            popupVisible: false,
            isAbove: false,
            listData: props.data
        };

    }

    filterData = (filter = this.state.filter, data = this.props.data) => {

        const {minFilterLength} = this.props;

        if (!filter || filter.length < minFilterLength) {
            return data;
        }

        const {valueField, displayField, renderer, filterCallback} = this.props;

        if (filterCallback) {
            return filterCallback(filter, data);
        }

        return data.filter(item => {

            if (!item) {
                return false;
            }

            if (renderer) {
                return renderer(item).toString().toUpperCase().includes(filter.toUpperCase());
            }

            if (typeof item === 'object') {

                const itemDisplay = Util.getTextByDisplayField(item, displayField, valueField);

                if (itemDisplay) {
                    return itemDisplay.toString().toUpperCase().includes(filter.toUpperCase());
                }

            }

            return item.toString().toUpperCase().includes(filter.toUpperCase());

        });

    };

    filterFocusHandler = (...args) => {

        if (this.props.disabled) {
            return;
        }

        const {onFocus} = this.props,
            {filter} = this.state,
            state = {
                filterFocused: true,
                tempSelectIndex: null
            };

        onFocus && onFocus(...args);

        if (filter) {
            state.popupVisible = true;
        }

        this.setState(state);

    };

    filterBlurHandler = (...args) => {
        const {disabled, onBlur} = this.props;
        !disabled && onBlur && onBlur(...args);
    };

    filterKeyDownHandler = e => {

        const {useDynamicRenderList} = this.props,
            {tempSelectIndex, listData} = this.state,
            listEl = useDynamicRenderList ? this.refs.dynamicRenderList : this.refs.list;
        let state = null;

        if (e.keyCode === 38 || e.keyCode === 40) {

            state = {};

            let index = isNumber(tempSelectIndex) && tempSelectIndex > -1 ? tempSelectIndex : -1;

            if (e.keyCode === 38) { // up
                index--;
            } else if (e.keyCode === 40) { // down
                index++;
            }

            state.tempSelectIndex = Valid.range(index, 0, listData.length - 1);

        }

        this.setState(state, () => {

            const {onFilterKeyDown} = this.props;
            onFilterKeyDown && onFilterKeyDown(e);

            listEl && listEl.adjustScroll();

        });

    };

    filterPressEnterHandler = (e, filter) => {

        const {autoClose} = this.props,
            state = {};

        if (autoClose) {
            state.popupVisible = false;
        }

        this.setState(state, () => {

            const {onFilterPressEnter} = this.props;
            onFilterPressEnter && onFilterPressEnter(filter);

            this.update();

        });

    };

    filterChangeHandler = filter => {

        const {data, minFilterLength} = this.props,
            state = {
                filter,
                popupVisible: !!filter && filter.length >= minFilterLength,
                tempSelectIndex: null
            };

        if (!filter) {
            state.listData = data;
            state.value = null;
        } else {
            state.listData = this.filterData(filter);
        }

        this.setState(state, () => {

            const {onFilterChange} = this.props;
            onFilterChange && onFilterChange(filter);

            this.refs.popup && this.refs.popup.resetPosition();

        });

    };

    closePopup = () => {
        this.setState({
            popupVisible: false
        }, () => {
            const {onPopupClosed} = this.props;
            onPopupClosed && onPopupClosed();
        });
    };

    popupRenderHandler = popupEl => {

        const isAbove = DropdownCalculation.isAbove(this.autoCompleteFilterEl, this.triggerEl, findDOMNode(popupEl));

        if (isAbove !== this.state.isAbove) {
            this.setState({
                isAbove
            });
        }

    };

    itemClickHandler = value => {

        const {autoClose, valueField, displayField, renderer} = this.props,
            filter = renderer ? renderer(value) : Util.getTextByDisplayField(value, displayField, valueField),
            state = {
                tempSelectIndex: null,
                value,
                filter,
                listData: this.filterData(filter)
            },
            isChanged = this.state.value != value;

        if (autoClose) {
            state.popupVisible = false;
        }

        this.setState(state, () => {
            const {onItemClick, onChange} = this.props;
            onItemClick && onItemClick(value);
            isChanged && onChange && onChange(value);
        });

    };

    update = () => {

        const {displayField, valueField, renderer} = this.props,
            {filter, tempSelectIndex, listData} = this.state;
        let state = null;

        if (filter && listData && listData.length > 0) {

            state = {};

            const index = isNumber(tempSelectIndex) && tempSelectIndex > -1 ? tempSelectIndex : -1;

            state.value = index > -1 ? listData[index] : null;

            if (index > -1) {
                state.filter = renderer ?
                    renderer(state.value)
                    :
                    Util.getTextByDisplayField(state.value, displayField, valueField);
            }

            state.listData = this.filterData(state.filter);

        }

        state && this.setState(state, () => {
            const {onFilterChange, onChange} = this.props;
            onFilterChange && onFilterChange(state.filter);
            onChange && onChange(state.value);
        });

    };

    componentDidMount() {
        this.autoCompleteFilterEl = this.refs.autoCompleteFilter;
        this.triggerEl = findDOMNode(this.refs.trigger);
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.state.value) {
            this.setState({
                value: nextProps.value
            });
        }
    }

    render() {

        const {
                className, popupClassName, style, popupStyle, theme, popupTheme, name, placeholder,
                disabled, iconCls, rightIconCls, valueField, displayField, descriptionField,
                noMatchedPopupVisible, noMatchedMsg, popupChildren, renderer,
                useDynamicRenderList, listHeight, itemHeight, scrollBuffer,
                onFilterClear, onMouseOver, onMouseOut
            } = this.props,
            {isAbove, tempSelectIndex, value, filter, popupVisible, listData} = this.state,

            emptyEl = [{
                itemRenderer() {
                    return (

                            {
                                noMatchedMsg ?
                                    noMatchedMsg
                                    :

                                        No matched value.

                            }

                    );
                },
                disableTouchRipple: true
            }],

            isEmpty = !listData || listData.length < 1,

            wrapperClassName = classNames('auto-complete-filter', {
                [className]: className
            }),
            triggerClassName = classNames('auto-complete-filter-trigger',
                isEmpty && !noMatchedPopupVisible ? '' : (popupVisible ? ' activated' : ''),
                isAbove ? ' above' : ' blow'),
            autoCompletePopupClassName = classNames('auto-complete-filter-popup',
                isAbove ? ' above' : ' blow', {
                    [popupClassName]: popupClassName
                }),
            autoCompletePopupStyle = Object.assign({
                width: this.triggerEl && getComputedStyle(this.triggerEl).width
            }, popupStyle);

        return (

                {
                    name ?
                        <input type="hidden"
                               name={name}
                               value={Util.getValueByValueField(value, valueField, displayField)}/>
                        :
                        null
                }

                <TextField ref="trigger"
                           className={triggerClassName}
                           theme={theme}
                           value={filter}
                           placeholder={placeholder}
                           disabled={disabled}
                           iconCls={iconCls}
                           rightIconCls={rightIconCls}
                           onFocus={this.filterFocusHandler}
                           onBlur={this.filterBlurHandler}
                           onMouseOver={onMouseOver}
                           onMouseOut={onMouseOut}
                           onChange={this.filterChangeHandler}
                           onKeyDown={this.filterKeyDownHandler}
                           onPressEnter={this.filterPressEnterHandler}
                           onClear={onFilterClear}
                           onRightIconClick={this.filterPressEnterHandler}/>

                {
                    isEmpty && !noMatchedPopupVisible ?
                        null
                        :
                        <Popup ref="popup"
                               className={autoCompletePopupClassName}
                               style={autoCompletePopupStyle}
                               theme={popupTheme}
                               visible={popupVisible}
                               triggerEl={this.triggerEl}
                               hasTriangle={false}
                               position={isAbove ? Popup.Position.TOP_LEFT : Popup.Position.BOTTOM_LEFT}
                               onRender={this.popupRenderHandler}
                               onRequestClose={this.closePopup}>

                            {
                                isEmpty ?
                                    <List className="auto-complete-filter-list"
                                          theme={popupTheme}
                                          data={emptyEl}/>
                                    :
                                    (
                                        useDynamicRenderList ?
                                            <DynamicRenderList ref="dynamicRenderList"
                                                               className="auto-complete-filter-list"
                                                               theme={popupTheme}
                                                               data={listData}
                                                               value={listData[tempSelectIndex]}
                                                               valueField={valueField}
                                                               displayField={displayField}
                                                               descriptionField={descriptionField}
                                                               renderer={renderer}
                                                               listHeight={listHeight}
                                                               itemHeight={itemHeight}
                                                               scrollBuffer={scrollBuffer}
                                                               onItemClick={this.itemClickHandler}/>
                                            :
                                            <List ref="list"
                                                  className="auto-complete-filter-list"
                                                  theme={popupTheme}
                                                  data={listData}
                                                  value={listData[tempSelectIndex]}
                                                  valueField={valueField}
                                                  displayField={displayField}
                                                  descriptionField={descriptionField}
                                                  renderer={renderer}
                                                  onItemClick={this.itemClickHandler}/>
                                    )
                            }

                            {popupChildren}

                        </Popup>
                }

        );
    }

}

AutoCompleteFilter.propTypes = {

    /**
     * The CSS class name of the root element.
     */
    className: PropTypes.string,

    /**
     * The CSS class name of the popup element.
     */
    popupClassName: PropTypes.string,

    /**
     * Override the styles of the root element.
     */
    style: PropTypes.object,

    /**
     * Override the styles of the popup element.
     */
    popupStyle: PropTypes.object,

    /**
     * The theme.
     */
    theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The theme.
     */
    popupTheme: PropTypes.oneOf(Util.enumerateValue(Theme)),

    /**
     * The name of the auto complete.
     */
    name: PropTypes.string,

    /**
     * The placeholder of the field.
     */
    placeholder: PropTypes.string,

    /**
     * Children passed into the List.
     */
    data: PropTypes.oneOfType([

        // not grouped
        PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.shape({

            /**
             * The CSS class name of the list button.
             */
            className: PropTypes.string,

            /**
             * Override the styles of the list button.
             */
            style: PropTypes.object,

            /**
             * The theme of the list button.
             */
            theme: PropTypes.oneOf(Util.enumerateValue(Theme)),

            /**
             * The text value of the list button.Type can be string or number.
             */
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * The desc value of the list button. Type can be string or number.
             */
            desc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

            /**
             * If true, the list button will be disabled.
             */
            disabled: PropTypes.bool,

            /**
             * If true,the button will be have loading effect.
             */
            isLoading: PropTypes.bool,

            /**
             * If true,the element's ripple effect will be disabled.
             */
            disableTouchRipple: PropTypes.bool,

            /**
             * Use this property to display an icon. It will display on the left.
             */
            iconCls: PropTypes.string,

            /**
             * Use this property to display an icon. It will display on the right.
             */
            rightIconCls: PropTypes.string,

            /**
             * You can create a complicated renderer callback instead of value and desc prop.
             */
            itemRenderer: PropTypes.func,

            /**
             * Callback function fired when a list item touch-tapped.
             */
            onClick: PropTypes.func

        }), PropTypes.string, PropTypes.number])),

        // grouped
        PropTypes.array

    ]).isRequired,

    /**
     * If true, the auto complete will be disabled.
     */
    disabled: PropTypes.bool,

    /**
     * The value field name in data. (default: "value")
     */
    valueField: PropTypes.string,

    /**
     * The display field name in data. (default: "text")
     */
    displayField: PropTypes.string,

    /**
     * The description field name in data. (default: "desc")
     */
    descriptionField: PropTypes.string,

    /**
     * If true, the popup list automatically closed after selection.
     */
    autoClose: PropTypes.bool,

    minFilterLength: PropTypes.number,

    /**
     * Callback function fired when filter changed.
     */
    filterCallback: PropTypes.func,

    /**
     * Use this property to display an icon.
     */
    iconCls: PropTypes.string,

    /**
     * Use this property to display an icon.
     */
    rightIconCls: PropTypes.string,

    /**
     * The visiblity of no matched popup.
     */
    noMatchedPopupVisible: PropTypes.bool,

    /**
     * The message of no matched value.
     */
    noMatchedMsg: PropTypes.string,

    popupChildren: PropTypes.any,

    filterInitValue: PropTypes.string,

    useDynamicRenderList: PropTypes.bool,
    listHeight: PropTypes.number,
    itemHeight: PropTypes.number,
    scrollBuffer: PropTypes.number,

    /**
     * You can create a complicated renderer callback instead of value and desc prop.
     */
    renderer: PropTypes.func,

    /**
     * The function that trigger when filter changes.
     */
    onFilterChange: PropTypes.func,

    /**
     * The function that trigger when press enter.
     */
    onFilterPressEnter: PropTypes.func,

    /**
     * The function that trigger when filter cleared.
     */
    onFilterClear: PropTypes.func,

    /**
     * The function that trigger when value changed.
     */
    onChange: PropTypes.func,

    /**
     * The function that trigger when touch-tap the list items.
     */
    onItemClick: PropTypes.func,

    /**
     * Callback function fired when AutoCompleteFilter get focus.
     */
    onFocus: PropTypes.func,

    /**
     * Callback function fired when AutoCompleteFilter lose focus.
     */
    onBlur: PropTypes.func,

    onMouseOver: PropTypes.func,
    onMouseOut: PropTypes.func,
    onPopupClosed: PropTypes.func

};

AutoCompleteFilter.defaultProps = {

    theme: Theme.DEFAULT,
    popupTheme: Theme.DEFAULT,

    data: [],
    disabled: false,
    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',
    autoClose: false,
    minFilterLength: 1,
    iconCls: null,
    rightIconCls: 'fas fa-search',
    noMatchedPopupVisible: true,
    filterInitValue: '',
    useDynamicRenderList: false

};

export default AutoCompleteFilter;<|endoftext|>