<commit_before>import * as ConnectionQualityEvents
    from "../../service/connectivity/ConnectionQualityEvents";
import * as ConferenceEvents from "../../JitsiConferenceEvents";
var MediaType = require('../../service/RTC/MediaType');

// webrtc table describing simulcast resolutions and used bandwidth
// https://chromium.googlesource.com/external/webrtc/+/master/webrtc/media/engine/simulcast.cc#42
const _bandwidthMap = [
    { width: 1920, height: 1080, layers:3, max: 5000, min: 800 },
    { width: 1280, height: 720,  layers:3, max: 2500, min: 600 },
    { width: 960,  height: 540,  layers:3, max: 900,  min: 450 },
    { width: 640,  height: 360,  layers:2, max: 700,  min: 150 },
    { width: 480,  height: 270,  layers:2, max: 450,  min: 150 },
    { width: 320,  height: 180,  layers:1, max: 200,  min: 30 }
];

/**
 * Calculates the quality percent based on passed new and old value.
 * @param newVal the new value
 * @param oldVal the old value
 */
function calculateQuality(newVal, oldVal) {
    return (newVal <= oldVal) ? newVal : (9*oldVal + newVal) / 10;
}

/**
 * Calculates the quality percentage based on the input resolution height and
 * the upload reported by the client. The value is based on the interval from
 * _bandwidthMap.
 * @param inputHeight the resolution used to open the camera.
 * @param upload the upload rate reported by client.
 * @returns {int} the percent of upload based on _bandwidthMap and maximum value
 * of 100, as values of the map are approximate and clients can stream above
 * those values. Returns undefined if no result is found.
 */
function calculateQualityUsingUpload(inputHeight, upload) {
    // found resolution from _bandwidthMap which height is equal or less than
    // the inputHeight
    let foundResolution = _bandwidthMap.find((r) => (r.height <= inputHeight));

    if (!foundResolution)
        return undefined;

    if (upload <= foundResolution.min)
        return 0;

    return Math.min(
        ((upload - foundResolution.min)*100)
            / (foundResolution.max - foundResolution.min),
        100);
}

export default class ConnectionQuality {
    constructor(conference, eventEmitter, options) {
        this.eventEmitter = eventEmitter;

        this.disableQualityBasedOnBandwidth =
            options.forceQualityBasedOnBandwidth
                    ? false : !!options.disableSimulcast;
        /**
         * local stats
         * @type {{}}
         */
        this.localStats = {};

        /**
         * remote stats
         * @type {{}}
         */
        this.remoteStats = {};

        /**
         * Quality percent( 100% - good, 0% - bad.) for the local user.
         */
        this.localConnectionQuality = 100;

        /**
         * Quality percent( 100% - good, 0% - bad.) stored per id.
         */
        this.remoteConnectionQuality = {};

        conference.on(ConferenceEvents.CONNECTION_INTERRUPTED,
                      () => { this._updateLocalConnectionQuality(0); });

        conference.on(
            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
            (participant, payload) => {
                // TODO move "stats" to a constant.
                if(payload.type === "stats") {
                    let remoteVideo = participant.getTracks()
                        .find(tr => tr.isVideoTrack());
                    this.updateRemoteStats(
                        participant.getId(),
                        payload.values,
                        remoteVideo ? remoteVideo.videoType : undefined,
                        remoteVideo ? remoteVideo.isMuted() : undefined);
                }
        });

        conference.on(
            ConferenceEvents.CONNECTION_STATS,
            (stats) => {
                let localVideoTracks = conference.getLocalTracks(MediaType.VIDEO);
                let localVideoTrack
                    = localVideoTracks.length > 0 ? localVideoTracks[0] : null;

                // if we say video muted we will use old method of calculating
                // quality and will not depend on localVideo if it is missing
                this.updateLocalStats(
                    stats,
                    conference.isConnectionInterrupted(),
                    localVideoTrack ? localVideoTrack.videoType : undefined,
                    localVideoTrack ? localVideoTrack.isMuted() : true,
                    localVideoTrack ? localVideoTrack.resolution : null);
        });
    }

    /**
     * Returns the new quality value based on the input parameters.
     * Used to calculate remote and local values.
     * @param data the data
     * @param lastQualityValue the last value we calculated
     * @param videoType need to check whether we are screen sharing
     * @param isMuted is video muted
     * @param resolution the input resolution used by the camera
     * @returns {*} the newly calculated value or undefined if no result
     * @private
     */
    _getNewQualityValue(
        data, lastQualityValue, videoType, isMuted, resolution) {
        if (this.disableQualityBasedOnBandwidth
            || isMuted
            || videoType === 'desktop'
            || !resolution) {
            return calculateQuality(
                100 - data.packetLoss.total,
                lastQualityValue || 100);
        } else {
            return calculateQualityUsingUpload(
                resolution,
                data.bitrate.upload);
        }
    }

    /**
     * Updates only the localConnectionQuality value
     * @param values {int} the new value. should be from 0 - 100.
     */
    _updateLocalConnectionQuality(value) {
        this.localConnectionQuality = value;
        this.eventEmitter.emit(
            ConnectionQualityEvents.LOCAL_STATS_UPDATED,
            this.localConnectionQuality,
            this.localStats);
    }

    /**
     * Updates the local statistics
     * @param data new statistics
     * @param updateLocalConnectionQuality {boolean} weather to recalculate
     * localConnectionQuality or not.
     * @param videoType the local video type
     * @param isMuted current state of local video, whether it is muted
     * @param resolution the current resolution used by local video
     */
    updateLocalStats(data, updateLocalConnectionQuality,
                  videoType, isMuted, resolution) {
            this.localStats = data;
            if(updateLocalConnectionQuality) {
                let val = this._getNewQualityValue(
                    this.localStats,
                    this.localConnectionQuality,
                    videoType,
                    isMuted,
                    resolution);
                if (val !== undefined)
                    this.localConnectionQuality = val;
            }
            this.eventEmitter.emit(
                ConnectionQualityEvents.LOCAL_STATS_UPDATED,
                this.localConnectionQuality,
                this.localStats);
    }

    /**
     * Updates remote statistics
     * @param id the id associated with the statistics
     * @param data the statistics received
     * @param remoteVideoType the video type of the remote video
     * @param isRemoteVideoMuted whether remote video is muted
     */
    updateRemoteStats(id, data, remoteVideoType, isRemoteVideoMuted) {
            if (!data ||
                !("packetLoss" in data) ||
                !("total" in data.packetLoss)) {
                this.eventEmitter.emit(
                    ConnectionQualityEvents.REMOTE_STATS_UPDATED,
                    id,
                    null,
                    null);
                return;
            }

            let inputResolution = data.resolution;
            // Use only the fields we need
            data = {bitrate: data.bitrate, packetLoss: data.packetLoss};

            this.remoteStats[id] = data;

            let val = this._getNewQualityValue(
                data,
                this.remoteConnectionQuality[id],
                remoteVideoType,
                isRemoteVideoMuted,
                inputResolution);
            if (val !== undefined)
                this.remoteConnectionQuality[id] = val;

            this.eventEmitter.emit(
                ConnectionQualityEvents.REMOTE_STATS_UPDATED,
                id,
                this.remoteConnectionQuality[id],
                this.remoteStats[id]);
    }

    /**
     * Returns the local statistics.
     */
    getStats() {
        return this.localStats;
    }
}
<commit_msg>refactor: Moves more code to lib-jitsi-meet.<commit_after>import * as ConnectionQualityEvents
    from "../../service/connectivity/ConnectionQualityEvents";
import * as ConferenceEvents from "../../JitsiConferenceEvents";
import {getLogger} from "jitsi-meet-logger";

var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
var MediaType = require('../../service/RTC/MediaType');

const logger = getLogger(__filename);

/**
 * The value to use for the "type" field for messages sent by ConnectionQuality
 * over the data channel.
 */
const STATS_MESSAGE_TYPE = "stats";

// webrtc table describing simulcast resolutions and used bandwidth
// https://chromium.googlesource.com/external/webrtc/+/master/webrtc/media/engine/simulcast.cc#42
const _bandwidthMap = [
    { width: 1920, height: 1080, layers:3, max: 5000, min: 800 },
    { width: 1280, height: 720,  layers:3, max: 2500, min: 600 },
    { width: 960,  height: 540,  layers:3, max: 900,  min: 450 },
    { width: 640,  height: 360,  layers:2, max: 700,  min: 150 },
    { width: 480,  height: 270,  layers:2, max: 450,  min: 150 },
    { width: 320,  height: 180,  layers:1, max: 200,  min: 30 }
];

/**
 * Calculates the quality percent based on passed new and old value.
 * @param newVal the new value
 * @param oldVal the old value
 */
function calculateQuality(newVal, oldVal) {
    return (newVal <= oldVal) ? newVal : (9*oldVal + newVal) / 10;
}

/**
 * Calculates the quality percentage based on the input resolution height and
 * the upload reported by the client. The value is based on the interval from
 * _bandwidthMap.
 * @param inputHeight the resolution used to open the camera.
 * @param upload the upload rate reported by client.
 * @returns {int} the percent of upload based on _bandwidthMap and maximum value
 * of 100, as values of the map are approximate and clients can stream above
 * those values. Returns undefined if no result is found.
 */
function calculateQualityUsingUpload(inputHeight, upload) {
    // found resolution from _bandwidthMap which height is equal or less than
    // the inputHeight
    let foundResolution = _bandwidthMap.find((r) => (r.height <= inputHeight));

    if (!foundResolution)
        return undefined;

    if (upload <= foundResolution.min)
        return 0;

    return Math.min(
        ((upload - foundResolution.min)*100)
            / (foundResolution.max - foundResolution.min),
        100);
}

export default class ConnectionQuality {
    constructor(conference, eventEmitter, options) {
        this.eventEmitter = eventEmitter;

        /**
         * The owning JitsiConference.
         */
        this.conference = conference;

        this.disableQualityBasedOnBandwidth =
            options.forceQualityBasedOnBandwidth
                    ? false : !!options.disableSimulcast;
        /**
         * local stats
         * @type {{}}
         */
        this.localStats = {};

        /**
         * remote stats
         * @type {{}}
         */
        this.remoteStats = {};

        /**
         * Quality percent( 100% - good, 0% - bad.) stored per id.
         * TODO remove, read from the received remote stats
         */
        this.remoteConnectionQuality = {};

        conference.on(ConferenceEvents.CONNECTION_INTERRUPTED,
                      () => { this._updateLocalConnectionQuality(0); });

        conference.on(
            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
            (participant, payload) => {
                if (payload.type === STATS_MESSAGE_TYPE) {
                    let remoteVideo = participant.getTracks()
                        .find(tr => tr.isVideoTrack());
                    this.updateRemoteStats(
                        participant.getId(),
                        payload.values,
                        remoteVideo ? remoteVideo.videoType : undefined,
                        remoteVideo ? remoteVideo.isMuted() : undefined);
                }
        });

        conference.on(
            ConferenceEvents.CONNECTION_STATS,
            (stats) => {
                let localVideoTracks = conference.getLocalTracks(MediaType.VIDEO);
                let localVideoTrack
                    = localVideoTracks.length > 0 ? localVideoTracks[0] : null;

                // if we say video muted we will use old method of calculating
                // quality and will not depend on localVideo if it is missing
                this.updateLocalStats(
                    stats,
                    conference.isConnectionInterrupted(),
                    localVideoTrack ? localVideoTrack.videoType : undefined,
                    localVideoTrack ? localVideoTrack.isMuted() : true,
                    localVideoTrack ? localVideoTrack.resolution : null);
        });
    }

    /**
     * Returns the new quality value based on the input parameters.
     * Used to calculate remote and local values.
     * @param data the data
     * @param lastQualityValue the last value we calculated
     * @param videoType need to check whether we are screen sharing
     * @param isMuted is video muted
     * @param resolution the input resolution used by the camera
     * @returns {*} the newly calculated value or undefined if no result
     * @private
     */
    _getNewQualityValue(
        data, lastQualityValue, videoType, isMuted, resolution) {
        if (this.disableQualityBasedOnBandwidth
            || isMuted
            || videoType === 'desktop'
            || !resolution) {
            return calculateQuality(
                100 - data.packetLoss.total,
                lastQualityValue || 100);
        } else {
            return calculateQualityUsingUpload(
                resolution,
                data.bitrate.upload);
        }
    }

    /**
     * Updates only the localConnectionQuality value
     * @param values {int} the new value. should be from 0 - 100.
     */
    _updateLocalConnectionQuality(value) {
        this.localStats.connectionQuality = value;
        this.eventEmitter.emit(
            ConnectionQualityEvents.LOCAL_STATS_UPDATED,
            this.localStats);
        this._broadcastLocalStats();
    }

    /**
     * Broadcasts the local statistics to all other participants in the
     * conference.
     */
    _broadcastLocalStats() {
        // Send only the data that remote participants care about.
        let data = {
            bitrate: this.localStats.bitrate,
            packetLoss: this.localStats.packetLoss,
            connectionQuality: this.localStats.connectionQuality
        };

        let localVideoTracks = this.conference.getLocalTracks(MediaType.VIDEO);
        let localVideoTrack
                    = localVideoTracks.length > 0 ? localVideoTracks[0] : null;
        if (localVideoTrack && localVideoTrack.resolution) {
            data.resolution = localVideoTrack.resolution;
        }

        try {
            this.conference.broadcastEndpointMessage({
                type: STATS_MESSAGE_TYPE,
                values: data });
        } catch (e) {
            let errorMsg = "Failed to broadcast local stats";
            logger.error(errorMsg, e);
            GlobalOnErrorHandler.callErrorHandler(
                new Error(errorMsg + ": " + e));
        }
    }

    /**
     * Updates the local statistics
     * @param data new statistics
     * @param updateLocalConnectionQuality {boolean} weather to recalculate
     * localConnectionQuality or not.
     * @param videoType the local video type
     * @param isMuted current state of local video, whether it is muted
     * @param resolution the current resolution used by local video
     */
    updateLocalStats(data, updateLocalConnectionQuality,
                  videoType, isMuted, resolution) {
            this.localStats = data;
            if(updateLocalConnectionQuality) {
                let val = this._getNewQualityValue(
                    this.localStats,
                    this.localStats.connectionQuality,
                    videoType,
                    isMuted,
                    resolution);
                if (val !== undefined) {
                    this.localStats.connectionQuality = val;
                }
            }
            this.eventEmitter.emit(
                ConnectionQualityEvents.LOCAL_STATS_UPDATED,
                this.localStats);
            this._broadcastLocalStats();
    }

    /**
     * Updates remote statistics
     * @param id the id associated with the statistics
     * @param data the statistics received
     * @param remoteVideoType the video type of the remote video
     * @param isRemoteVideoMuted whether remote video is muted
     */
    updateRemoteStats(id, data, remoteVideoType, isRemoteVideoMuted) {
            if (!data ||
                !("packetLoss" in data) ||
                !("total" in data.packetLoss)) {
                this.eventEmitter.emit(
                    ConnectionQualityEvents.REMOTE_STATS_UPDATED,
                    id,
                    null,
                    null);
                return;
            }

            let inputResolution = data.resolution;
            // Use only the fields we need
            data = {bitrate: data.bitrate, packetLoss: data.packetLoss};

            this.remoteStats[id] = data;

            let val = this._getNewQualityValue(
                data,
                this.remoteConnectionQuality[id],
                remoteVideoType,
                isRemoteVideoMuted,
                inputResolution);
            if (val !== undefined)
                this.remoteConnectionQuality[id] = val;

            this.eventEmitter.emit(
                ConnectionQualityEvents.REMOTE_STATS_UPDATED,
                id,
                this.remoteConnectionQuality[id],
                this.remoteStats[id]);
    }

    /**
     * Returns the local statistics.
     */
    getStats() {
        return this.localStats;
    }
}
<|endoftext|>


<commit_before>var LightningVisualization = require('lightning-visualization');
var d3 = require('d3');
var _ = require('lodash')
var MultiaxisZoom = require('d3-multiaxis-zoom');
var utils = require('lightning-client-utils');
var fs = require('fs');
var css = fs.readFileSync(__dirname + '/style.css');

var Visualization = LightningVisualization.extend({

    getDefaultStyles: function() {
        return {
            color: '#68a1e5',
            stroke: 'white',
            size: 6
        }
    },

    getDefaultOptions: function() {
        return {
            brush: true,
            tooltips: true
        }
    },

    init: function() {
        MultiaxisZoom(d3);
        this.margin = {top: 0, right: 0, bottom: 0, left: 0};
        this.render();
    },

    css: css,

    render: function() {

        var data = this.data;
        var width = this.width;
        var height = this.height;
        var selector = this.selector;
        var options = this.options;
        var self = this;

        var nodes = data.nodes;
        var links = data.links;

        // if points are colored use gray, otherwise use our default
        var linkStrokeColor = (data.group || data.color) ? '#999' : '#A38EF3';

        // set opacity inversely proportional to number of links
        var linkStrokeOpacity = Math.max(1 - 0.0005 * links.length, 0.5);

        // set circle stroke thickness based on number of nodes
        var strokeWidth = nodes.length > 500 ? 1 : 1.1

        this.makeScales()
        this.setScales()

        var zoom = d3.behavior.zoom()
            .x(self.x)
            .y(self.y)
            .on('zoom', zoomed);

        var container = d3.select(selector)
            .append('div')
            .style('position', 'relative')
            .style('overflow', 'hidden')
            .style('width', width + "px")
            .style('height', height + "px")

        var canvas = container
            .append('canvas')
            .attr('class', 'graph-plot canvas')
            .attr('width', width)
            .attr('height', height)
            .call(zoom)
            .on("click", mouseHandler)
            .on("dblclick.zoom", null)
            .node().getContext("2d")

        function mouseHandler() {
            if (d3.event.defaultPrevented) return;
            var pos = d3.mouse(this)
            var found = utils.nearestPoint(nodes, pos, self.x, self.y)

            if (found) {
                highlighted = []
                highlighted.push(found.i)
                self.emit('hover', found);
            } else {
                highlighted = []
                selected = []
                self.removeTooltip();
            };
            redraw();
        }

        var selected = [];
        var highlighted = [];
        var shiftKey;

        // setup brushing
        if (options.brush) {

            var brush = d3.svg.brush()
                .x(self.x)
                .y(self.y)
                .on("brushstart", function() {
                    // remove any highlighting
                    highlighted = []
                    self.removeTooltip();
                    // select a point if we click without extent
                    var pos = d3.mouse(this)
                    var found = utils.nearestPoint(nodes, pos, self.x, self.y)
                    if (found) {
                        if (_.indexOf(selected, found.i) == -1) {
                            selected.push(found.i)
                        } else {
                            _.remove(selected, function(d) {return d == found.i})
                        }
                        redraw();
                    }
                })
                .on("brush", function() {
                    var extent = d3.event.target.extent();
                    if (Math.abs(extent[0][0] - extent[1][0]) > 0 & Math.abs(extent[0][1] - extent[1][1]) > 0) {
                        selected = []
                        var x = self.x
                        var y = self.y
                        _.forEach(nodes, function(n) {
                            var cond1 = (n.x > extent[0][0] & n.x < extent[1][0])
                            var cond2 = (n.y > extent[0][1] & n.y < extent[1][1])
                            if (cond1 & cond2) {
                                selected.push(n.i)
                            }
                        })
                        redraw();
                    }
                })
                .on("brushend", function() {
                    d3.event.target.clear();
                    d3.select(this).call(d3.event.target);
                })

            var brushrect = container
                .append('svg:svg')
                .attr('class', 'graph-plot brush-container')
                .attr('width', width)
                .attr('height', height)
            .append("g")
                .attr('class', 'brush')
                .call(brush)

            d3.selectAll('.brush .background')
                .style('cursor', 'default')
            d3.selectAll('.brush')
                .style('pointer-events', 'none')

            d3.select(selector).on("keydown", function() {
                shiftKey = d3.event.shiftKey;
                if (shiftKey) {
                    d3.selectAll('.brush').style('pointer-events', 'all')
                    d3.selectAll('.brush .background').style('cursor', 'crosshair')
                }
            });

            d3.select(selector).on("keyup", function() {
                if (shiftKey) {
                    d3.selectAll('.brush').style('pointer-events', 'none')
                    d3.selectAll('.brush .background').style('cursor', 'default')
                }
                shiftKey = false
            });

        }

        function zoomed() {
            redraw();
        }

        // array indicating links
        var linkedByIndex = {};
        var i
        for (i = 0; i < nodes.length; i++) {
            linkedByIndex[i + ',' + i] = 1;
        };
        links.forEach(function (l) {
            linkedByIndex[self.getSource(l) + ',' + self.getTarget(l)] = 1;
        });

        // look up neighbor pairs
        function neighboring(a, b) {
            return linkedByIndex[a + ',' + b];
        }

        d3.select(selector).attr("tabindex", -1)

        function redraw() {
            canvas.clearRect(0, 0, width, height);
            draw()
        }

        function draw() {

            _.forEach(links, function(l) {
                var alpha
                var source = self.getSource(l)
                var target = self.getTarget(l)
                if (selected.length > 0) {
                    if (_.indexOf(selected, source) > -1 & _.indexOf(selected, target) > -1) {
                        alpha = 0.9
                    } else {
                        alpha = 0.05
                    }
                }
                if (highlighted.length > 0) {
                    if (_.indexOf(highlighted, source) > -1 | _.indexOf(highlighted, target) > -1) {
                        alpha = 0.9
                    } else {
                        alpha = 0.05
                    }
                }
                if (selected.length == 0 & highlighted.length == 0) {
                    alpha = linkStrokeOpacity
                }

                var line = self.getLine(l)
                canvas.strokeStyle = utils.buildRGBA(linkStrokeColor, alpha);
                canvas.lineWidth = 1 * Math.sqrt(l.value);
                canvas.lineJoin = 'round';
                canvas.beginPath();
                canvas.moveTo(line[0][0], line[0][1])
                var i
                for (i = 1; i < line.length; i++) {
                    canvas.lineTo(line[i][0], line[i][1]);
                }
                canvas.stroke()

            })

            _.forEach(nodes, function(n) {
                var alpha, stroke;
                if (selected.length > 0) {
                    if (_.indexOf(selected, n.i) >= 0) {
                        alpha = 0.9
                    } else {
                        alpha = 0.1
                    }
                } else {
                    alpha = 0.9
                }
                if (highlighted.length > 0) {
                    if (neighboring(nodes[highlighted[0]].i, n.i) | neighboring(n.i, nodes[highlighted[0]].i)) {
                        alpha = 0.9
                    } else {
                        alpha = 0.1
                    }
                }
                if (_.indexOf(highlighted, n.i) >= 0) {
                    stroke = "black"
                } else {
                    stroke = n.k
                }

                canvas.beginPath();
                canvas.arc(self.x(n.x), self.y(n.y), n.s, 0, 2 * Math.PI, false);
                canvas.fillStyle = utils.buildRGBA(n.c, alpha)
                canvas.lineWidth = strokeWidth
                canvas.strokeStyle = utils.buildRGBA(stroke, alpha)
                canvas.fill()
                canvas.stroke()
            })

            if(options.tooltips && highlighted.length) {
                self.showTooltip(self.data.nodes[highlighted[0]]);
            }

        }

        draw();

    },

    formatData: function(data) {
        var retColor = utils.getColorFromData(data);
        var retSize = data.size || [];
        var retName = data.name || [];
        var styles = this.styles

        var c, s

        data.nodes = data.nodes.map(function (d,i) {
            d.x = d[0]
            d.y = d[1]
            d.i = i
            c = retColor.length > 1 ? retColor[i] : retColor[0]
            s = retSize.length > 1 ? retSize[i] : retSize[0]
            d.c = c ? c : styles.color
            d.k = c ? c.darker(0.75) : styles.stroke
            d.s = s ? s : styles.size
            d.l = (data.labels || []).length > i ? data.labels[i] : null;
            return d;
        });

        data.links = data.links.map(function (d) {
            d.source = d[0];
            d.target = d[1];
            d.value = d[2];
            return d;
        });

        return data;
    },

    makeScales: function() {
        var self = this

        var xDomain = d3.extent(self.data.nodes, function(d) {
            return d.x;
        });

        var yDomain = d3.extent(self.data.nodes, function(d) {
            return d.y;
        });

        var sizeMax = d3.max(self.data.nodes, function(d) {
                return d.s;
            });

        if (sizeMax) {
            var padding = sizeMax * 2
        } else {
            var padding = 8 * 2 + 10
        }

        var xRng = Math.abs(xDomain[1] - xDomain[0])
        var yRng = Math.abs(yDomain[1] - yDomain[0])

        xDomain[0] -= xRng * 0.025
        xDomain[1] += xRng * 0.025
        yDomain[0] -= yRng * 0.025
        yDomain[1] += yRng * 0.025

        this.x = d3.scale.linear()
            .domain(xDomain)

        this.y = d3.scale.linear()
            .domain(yDomain)
    },

    setScales: function() {
        this.x.range([0, this.width])
        this.y.range([this.height, 0])
    },

    getSource: function(l) {
        return l.source
    },

    getTarget: function(l) {
        return l.target
    },

    getLine: function(link) {
        var self = this;
        var start = self.data.nodes[link.source]
        var end = self.data.nodes[link.target]
        return [[self.x(start.x), self.y(start.y)], [self.x(end.x), self.y(end.y)]]
    },

    getLabelForDataPoint: function(d) {
        if(!_.isNull(d.l) && !_.isUndefined(d.l)) {
            return d.l;
        }
        return ('id: ' + d.i);
    },

    buildTooltip: function(d) {

        var label = this.getLabelForDataPoint(d);
        this.removeTooltip();

        var cx = this.x(d.x);
        var cy = this.y(d.y);
        if(cx < 0 || cx > (this.width - this.margin.left - this.margin.right)) {
            return;
        }
        if(cy < 0 || cy > (this.height - this.margin.top - this.margin.bottom)) {
            return;
        }

        this.tooltipEl = document.createElement('div');
        this.tooltipEl.innerHTML = label;

        var styles = {
            left: (this.x(d.x) + this.margin.left - 50) + 'px',
            bottom: (this.height - this.y(d.y) + d.s + 5) + 'px',
            position: 'absolute',
            backgroundColor: 'rgba(0, 0, 0, 0.65)',
            textAlign: 'center',
            color: 'white',
            paddingTop: '5px',
            paddingBottom: '5px',
            fontSize: '12px',
            borderRadius: '4px',
            width: '100px',
            zIndex: '999'
        }
        _.extend(this.tooltipEl.style, styles)

    },

    renderTooltip: function() {
        var container = this.qwery(this.selector + ' div')[0];
        if(this.tooltipEl && container) {
            container.appendChild(this.tooltipEl);
        }
    },

    showTooltip: function(d) {
        this.buildTooltip(d);
        this.renderTooltip();
    },

    removeTooltip: function() {
        if(this.tooltipEl) {
            this.tooltipEl.remove();
            this.tooltipEl = null;
        }
    }

});

module.exports = Visualization;
<commit_msg>Add zoom option<commit_after>var LightningVisualization = require('lightning-visualization');
var d3 = require('d3');
var _ = require('lodash')
var MultiaxisZoom = require('d3-multiaxis-zoom');
var utils = require('lightning-client-utils');
var fs = require('fs');
var css = fs.readFileSync(__dirname + '/style.css');

var Visualization = LightningVisualization.extend({

    getDefaultStyles: function() {
        return {
            color: '#68a1e5',
            stroke: 'white',
            size: 6
        }
    },

    getDefaultOptions: function() {
        return {
            brush: true,
            tooltips: true,
            zoom: true
        }
    },

    init: function() {
        MultiaxisZoom(d3);
        this.margin = {top: 0, right: 0, bottom: 0, left: 0};
        this.render();
    },

    css: css,

    render: function() {

        var data = this.data;
        var width = this.width;
        var height = this.height;
        var selector = this.selector;
        var options = this.options;
        var self = this;

        var nodes = data.nodes;
        var links = data.links;

        // if points are colored use gray, otherwise use our default
        var linkStrokeColor = (data.group || data.color) ? '#999' : '#A38EF3';

        // set opacity inversely proportional to number of links
        var linkStrokeOpacity = Math.max(1 - 0.0005 * links.length, 0.5);

        // set circle stroke thickness based on number of nodes
        var strokeWidth = nodes.length > 500 ? 1 : 1.1

        this.makeScales()
        this.setScales()

        var zoom = d3.behavior.zoom()
            .x(self.x)
            .y(self.y)
            .on('zoom', zoomed);

        var container = d3.select(selector)
            .append('div')
            .style('position', 'relative')
            .style('overflow', 'hidden')
            .style('width', width + "px")
            .style('height', height + "px")

        var canvas = container
            .append('canvas')
            .attr('class', 'graph-plot canvas')
            .attr('width', width)
            .attr('height', height)
            .call(zoom)
            .on("click", mouseHandler)
            .on("dblclick.zoom", null)
            .node().getContext("2d")

        if (!self.options.zoom) {
            canvas.on("wheel.zoom", null);
            canvas.on("mousewheel.zoom", null);
        }

        function mouseHandler() {
            if (d3.event.defaultPrevented) return;
            var pos = d3.mouse(this)
            var found = utils.nearestPoint(nodes, pos, self.x, self.y)

            if (found) {
                highlighted = []
                highlighted.push(found.i)
                self.emit('hover', found);
            } else {
                highlighted = []
                selected = []
                self.removeTooltip();
            };
            redraw();
        }

        var selected = [];
        var highlighted = [];
        var shiftKey;

        // setup brushing
        if (options.brush) {

            var brush = d3.svg.brush()
                .x(self.x)
                .y(self.y)
                .on("brushstart", function() {
                    // remove any highlighting
                    highlighted = []
                    self.removeTooltip();
                    // select a point if we click without extent
                    var pos = d3.mouse(this)
                    var found = utils.nearestPoint(nodes, pos, self.x, self.y)
                    if (found) {
                        if (_.indexOf(selected, found.i) == -1) {
                            selected.push(found.i)
                        } else {
                            _.remove(selected, function(d) {return d == found.i})
                        }
                        redraw();
                    }
                })
                .on("brush", function() {
                    var extent = d3.event.target.extent();
                    if (Math.abs(extent[0][0] - extent[1][0]) > 0 & Math.abs(extent[0][1] - extent[1][1]) > 0) {
                        selected = []
                        var x = self.x
                        var y = self.y
                        _.forEach(nodes, function(n) {
                            var cond1 = (n.x > extent[0][0] & n.x < extent[1][0])
                            var cond2 = (n.y > extent[0][1] & n.y < extent[1][1])
                            if (cond1 & cond2) {
                                selected.push(n.i)
                            }
                        })
                        redraw();
                    }
                })
                .on("brushend", function() {
                    d3.event.target.clear();
                    d3.select(this).call(d3.event.target);
                })

            var brushrect = container
                .append('svg:svg')
                .attr('class', 'graph-plot brush-container')
                .attr('width', width)
                .attr('height', height)
            .append("g")
                .attr('class', 'brush')
                .call(brush)

            d3.selectAll('.brush .background')
                .style('cursor', 'default')
            d3.selectAll('.brush')
                .style('pointer-events', 'none')

            d3.select(selector).on("keydown", function() {
                shiftKey = d3.event.shiftKey;
                if (shiftKey) {
                    d3.selectAll('.brush').style('pointer-events', 'all')
                    d3.selectAll('.brush .background').style('cursor', 'crosshair')
                }
            });

            d3.select(selector).on("keyup", function() {
                if (shiftKey) {
                    d3.selectAll('.brush').style('pointer-events', 'none')
                    d3.selectAll('.brush .background').style('cursor', 'default')
                }
                shiftKey = false
            });

        }

        function zoomed() {
            redraw();
        }

        // array indicating links
        var linkedByIndex = {};
        var i
        for (i = 0; i < nodes.length; i++) {
            linkedByIndex[i + ',' + i] = 1;
        };
        links.forEach(function (l) {
            linkedByIndex[self.getSource(l) + ',' + self.getTarget(l)] = 1;
        });

        // look up neighbor pairs
        function neighboring(a, b) {
            return linkedByIndex[a + ',' + b];
        }

        d3.select(selector).attr("tabindex", -1)

        function redraw() {
            canvas.clearRect(0, 0, width, height);
            draw()
        }

        function draw() {

            _.forEach(links, function(l) {
                var alpha
                var source = self.getSource(l)
                var target = self.getTarget(l)
                if (selected.length > 0) {
                    if (_.indexOf(selected, source) > -1 & _.indexOf(selected, target) > -1) {
                        alpha = 0.9
                    } else {
                        alpha = 0.05
                    }
                }
                if (highlighted.length > 0) {
                    if (_.indexOf(highlighted, source) > -1 | _.indexOf(highlighted, target) > -1) {
                        alpha = 0.9
                    } else {
                        alpha = 0.05
                    }
                }
                if (selected.length == 0 & highlighted.length == 0) {
                    alpha = linkStrokeOpacity
                }

                var line = self.getLine(l)
                canvas.strokeStyle = utils.buildRGBA(linkStrokeColor, alpha);
                canvas.lineWidth = 1 * Math.sqrt(l.value);
                canvas.lineJoin = 'round';
                canvas.beginPath();
                canvas.moveTo(line[0][0], line[0][1])
                var i
                for (i = 1; i < line.length; i++) {
                    canvas.lineTo(line[i][0], line[i][1]);
                }
                canvas.stroke()

            })

            _.forEach(nodes, function(n) {
                var alpha, stroke;
                if (selected.length > 0) {
                    if (_.indexOf(selected, n.i) >= 0) {
                        alpha = 0.9
                    } else {
                        alpha = 0.1
                    }
                } else {
                    alpha = 0.9
                }
                if (highlighted.length > 0) {
                    if (neighboring(nodes[highlighted[0]].i, n.i) | neighboring(n.i, nodes[highlighted[0]].i)) {
                        alpha = 0.9
                    } else {
                        alpha = 0.1
                    }
                }
                if (_.indexOf(highlighted, n.i) >= 0) {
                    stroke = "black"
                } else {
                    stroke = n.k
                }

                canvas.beginPath();
                canvas.arc(self.x(n.x), self.y(n.y), n.s, 0, 2 * Math.PI, false);
                canvas.fillStyle = utils.buildRGBA(n.c, alpha)
                canvas.lineWidth = strokeWidth
                canvas.strokeStyle = utils.buildRGBA(stroke, alpha)
                canvas.fill()
                canvas.stroke()
            })

            if(options.tooltips && highlighted.length) {
                self.showTooltip(self.data.nodes[highlighted[0]]);
            }

        }

        draw();

    },

    formatData: function(data) {
        var retColor = utils.getColorFromData(data);
        var retSize = data.size || [];
        var retName = data.name || [];
        var styles = this.styles

        var c, s

        data.nodes = data.nodes.map(function (d,i) {
            d.x = d[0]
            d.y = d[1]
            d.i = i
            c = retColor.length > 1 ? retColor[i] : retColor[0]
            s = retSize.length > 1 ? retSize[i] : retSize[0]
            d.c = c ? c : styles.color
            d.k = c ? c.darker(0.75) : styles.stroke
            d.s = s ? s : styles.size
            d.l = (data.labels || []).length > i ? data.labels[i] : null;
            return d;
        });

        data.links = data.links.map(function (d) {
            d.source = d[0];
            d.target = d[1];
            d.value = d[2];
            return d;
        });

        return data;
    },

    makeScales: function() {
        var self = this

        var xDomain = d3.extent(self.data.nodes, function(d) {
            return d.x;
        });

        var yDomain = d3.extent(self.data.nodes, function(d) {
            return d.y;
        });

        var sizeMax = d3.max(self.data.nodes, function(d) {
                return d.s;
            });

        if (sizeMax) {
            var padding = sizeMax * 2
        } else {
            var padding = 8 * 2 + 10
        }

        var xRng = Math.abs(xDomain[1] - xDomain[0])
        var yRng = Math.abs(yDomain[1] - yDomain[0])

        xDomain[0] -= xRng * 0.025
        xDomain[1] += xRng * 0.025
        yDomain[0] -= yRng * 0.025
        yDomain[1] += yRng * 0.025

        this.x = d3.scale.linear()
            .domain(xDomain)

        this.y = d3.scale.linear()
            .domain(yDomain)
    },

    setScales: function() {
        this.x.range([0, this.width])
        this.y.range([this.height, 0])
    },

    getSource: function(l) {
        return l.source
    },

    getTarget: function(l) {
        return l.target
    },

    getLine: function(link) {
        var self = this;
        var start = self.data.nodes[link.source]
        var end = self.data.nodes[link.target]
        return [[self.x(start.x), self.y(start.y)], [self.x(end.x), self.y(end.y)]]
    },

    getLabelForDataPoint: function(d) {
        if(!_.isNull(d.l) && !_.isUndefined(d.l)) {
            return d.l;
        }
        return ('id: ' + d.i);
    },

    buildTooltip: function(d) {

        var label = this.getLabelForDataPoint(d);
        this.removeTooltip();

        var cx = this.x(d.x);
        var cy = this.y(d.y);
        if(cx < 0 || cx > (this.width - this.margin.left - this.margin.right)) {
            return;
        }
        if(cy < 0 || cy > (this.height - this.margin.top - this.margin.bottom)) {
            return;
        }

        this.tooltipEl = document.createElement('div');
        this.tooltipEl.innerHTML = label;

        var styles = {
            left: (this.x(d.x) + this.margin.left - 50) + 'px',
            bottom: (this.height - this.y(d.y) + d.s + 5) + 'px',
            position: 'absolute',
            backgroundColor: 'rgba(0, 0, 0, 0.65)',
            textAlign: 'center',
            color: 'white',
            paddingTop: '5px',
            paddingBottom: '5px',
            fontSize: '12px',
            borderRadius: '4px',
            width: '100px',
            zIndex: '999'
        }
        _.extend(this.tooltipEl.style, styles)

    },

    renderTooltip: function() {
        var container = this.qwery(this.selector + ' div')[0];
        if(this.tooltipEl && container) {
            container.appendChild(this.tooltipEl);
        }
    },

    showTooltip: function(d) {
        this.buildTooltip(d);
        this.renderTooltip();
    },

    removeTooltip: function() {
        if(this.tooltipEl) {
            this.tooltipEl.remove();
            this.tooltipEl = null;
        }
    }

});

module.exports = Visualization;
<|endoftext|>


<commit_before>const configReducer = require('./reducers')

/**
 *
 *     HotkeyCommander - takes an options object and returns an event emitter
 *
 *  @param {Object} options - {
 *    @param {HotkeyDefinitions} hotkeys - hotkey definitions
 *    @param {Bool} displayToggle - whether or not to display the on/off toggle
 *    @param {Element} configTarget - element to mount configurator
 *    @param {Element} - engineTarget - the element the main keylistener attaches to
 *                                         - defaults to window
 *   @returns {Promise} - resolves with an EventEmitter2 instance
 *         i.e: hotkeyCommander.init(options)
 *                .then((emitter))
 * }
 */
exports.init = (options) => {
  return new Promise((resolve, reject) => {
    // instantiate a redux store and pass it to our configurator and commander objects
    configReducer(options.hotkeys, options.displayToggle || false)
      .then((result) => {
        const store = result

        // import configurator and commander
        const configurator = require('./configurator')(store, options.displayToggle || false)
        const commander = require('./commander')(store)

        // init configurator
        configurator.init(options.configTarget, options.displayToggle || false)

        // init commander inside the resolved promise which returns the commander event emitter
        resolve(commander(options.engineTarget || window))
      })
  })
}

/**
 *      DUAL CONTEXT API
 *
 *  use this api to create commander in
 *  one context and configurator in another
 *
 *  When using this method, each component will have
 *  its own store and so - commanders store
 *  will generate a new keymap when getting
 *  messages from chrome.storage.onChanged
 **/

 /**
 *  Commander
 *  @param {Object} options - {
 *    @param {Bool} displayToggle - whether or not to display the on/off toggle
 *    @param {HTML Element} target - element to mount configurator
 *   @returns {Promise} - resolves with an EventEmitter2 instance
 *         i.e: Commander(options)
 *                .then((emitter))
 */
exports.Commander = (options) => {
  return new Promise((resolve, reject) => {
    // instantiate a redux store and pass it to our configurator and commander objects
    configReducer(options.hotkeys, options.displayToggle || false)
      .then((result) => {
        const store = result

        // import the commander object
        const commander = require('./commander')(store)

        // init commander and return his event emitter
        resolve(commander(options.target))
      })
  })
}

/**
 * Configurator
 *  @param {Object} options - {
 *    @param {Bool} displayToggle - whether or not to display the on/off toggle
 *    @param {HTML Element} target - element to mount configurator
 *  @returns {Promise}
 **/
exports.Configurator = (options) => {
  return new Promise((resolve, reject) => {
    // instantiate a redux store and pass it to our configurator and commander objects
    configReducer(options.hotkeys, options.displayToggle || false)
      .then((result) => {
        const store = result

        // import configurator
        const configurator = require('./configurator')(store, options.displayToggle || false)

        // init confiturator
        resolve(configurator.init(options.target))
      })
  })
}
<commit_msg>Corrected a word in the inline documentation.<commit_after>const configReducer = require('./reducers')

/**
 *
 *     HotkeyCommander - takes an options object and returns an event emitter
 *
 *  @param {Object} options - {
 *    @param {HotkeyDefinitions} hotkeys - hotkey definitions
 *    @param {Bool} displayToggle - whether or not to display the on/off toggle
 *    @param {Element} configTarget - element to mount configurator
 *    @param {Element} - engineTarget - the element the main keylistener attaches to
 *                                         - defaults to window
 *   @returns {Promise} - resolves with an EventEmitter2 instance
 *         i.e: hotkeyCommander.init(options)
 *                .then((emitter))
 * }
 */
exports.init = (options) => {
  return new Promise((resolve, reject) => {
    // instantiate a redux store and pass it to our configurator and commander objects
    configReducer(options.hotkeys, options.displayToggle || false)
      .then((result) => {
        const store = result

        // import configurator and commander
        const configurator = require('./configurator')(store, options.displayToggle || false)
        const commander = require('./commander')(store)

        // init configurator
        configurator.init(options.configTarget, options.displayToggle || false)

        // init commander inside the resolved promise which returns the commander event emitter
        resolve(commander(options.engineTarget || window))
      })
  })
}

/**
 *      DUAL CONTEXT API
 *
 *  use this api to create commander in
 *  one context and configurator in another
 *
 *  When using this method, each component will have
 *  its own store and so - commanders store
 *  will generate a new keymap when getting
 *  messages from chrome.storage.onChanged
 **/

 /**
 *  Commander
 *  @param {Object} options - {
 *    @param {Bool} displayToggle - whether or not to display the on/off toggle
 *    @param {HTML Element} target - element to mount commander
 *   @returns {Promise} - resolves with an EventEmitter2 instance
 *         i.e: Commander(options)
 *                .then((emitter))
 */
exports.Commander = (options) => {
  return new Promise((resolve, reject) => {
    // instantiate a redux store and pass it to our configurator and commander objects
    configReducer(options.hotkeys, options.displayToggle || false)
      .then((result) => {
        const store = result

        // import the commander object
        const commander = require('./commander')(store)

        // init commander and return his event emitter
        resolve(commander(options.target))
      })
  })
}

/**
 * Configurator
 *  @param {Object} options - {
 *    @param {Bool} displayToggle - whether or not to display the on/off toggle
 *    @param {HTML Element} target - element to mount configurator
 *  @returns {Promise}
 **/
exports.Configurator = (options) => {
  return new Promise((resolve, reject) => {
    // instantiate a redux store and pass it to our configurator and commander objects
    configReducer(options.hotkeys, options.displayToggle || false)
      .then((result) => {
        const store = result

        // import configurator
        const configurator = require('./configurator')(store, options.displayToggle || false)

        // init confiturator
        resolve(configurator.init(options.target))
      })
  })
}
<|endoftext|>


<commit_before>import { elements } from './elements';

/**
 * getDecorator
 * @param decoratorName
 * @returns {object|null}
 */
let getDecorator = function(decoratorName) {

    let decorators = this.decorators || [];

    for(let decorator of decorators){
        let { name } = decorator.expression.callee;
        if(decoratorName === name){
            return decorator;
        }
    }

    return null;
};

/**
 * getElementClassByName
 * @param name {string}
 * @param elements {object}
 * @returns {*|string}
 */
let getElementClassByName = function(name, elements) {

    let element = elements[name] || 'HTMLElement';
    return element;
};

/**
 * getArguments
 * @param t {object}
 * @returns {{extends: string}}
 */
let getArguments = function(t){

    // set default
    let args = {
        extends: 'div',
    };
    let [ firstArgument ] = this.expression.arguments;

    if(!t.isObjectExpression(firstArgument)){
        return args;
    }

    let { properties } = firstArgument;
    for(let property of properties){
        args[property.key.name] = property.value.value
    }

    return args;
};

/**
 * addSuperClass
 * @param element {string}
 * @param t {object}
 */
let addSuperClass = function(element, t) {

    let identifier = t.identifier(element);
    this.superClass = identifier;
};

/**
 * addStaticGetterProperty
 * @param type {string}
 * @param superClass {string}
 * @param t {object}
 */
let addStaticGetterProperty = function(type, superClass, t) {

    let classMethodNode = t.classMethod(
        'get',
        t.identifier(type),
        [],
        t.blockStatement([
            t.returnStatement(t.stringLiteral(superClass))
        ]),
        false,
        true
    );

    this.body.body.push(classMethodNode);
};

/**
 * plugin
 * @astexplorer: https://astexplorer.net
 * @param t
 * @returns {{visitor: {ClassDeclaration: (function(*=))}}}
 */
let plugin = ({types: t}) => {

    return {
        visitor: {

            /**
             * ClassDeclaration
             * @param node {object}
             * @param opts {object}
             */
            ClassDeclaration({ node } = path, { opts } = state) {

                let component = node::getDecorator('component');
                if(!component){
                    return;
                }

                let superClass = component::getArguments(t).extends;

                let element = getElementClassByName(superClass, elements);
                node::addSuperClass(element, t);

                if(/div/.test(superClass)){
                    return;
                }

                node::addStaticGetterProperty('extends', superClass, t);
            },
        },
    };
};

export default plugin;
<commit_msg>add and merge default options<commit_after>import { elements } from './elements';

/**
 * getDecorator
 * @param decoratorName
 * @returns {object|null}
 */
let getDecorator = function(decoratorName) {

    let decorators = this.decorators || [];

    for(let decorator of decorators){
        let { name } = decorator.expression.callee;
        if(decoratorName === name){
            return decorator;
        }
    }

    return null;
};

/**
 * getElementClassByName
 * @param name {string}
 * @param elements {object}
 * @returns {*|string}
 */
let getElementClassByName = function(name, elements) {

    let element = elements[name] || 'HTMLElement';
    return element;
};

/**
 * getArguments
 * @param t {object}
 * @returns {{extends: string}}
 */
let getArguments = function(t){

    // set default
    let args = {
        extends: 'div',
    };
    let [ firstArgument ] = this.expression.arguments;

    if(!t.isObjectExpression(firstArgument)){
        return args;
    }

    let { properties } = firstArgument;
    for(let property of properties){
        args[property.key.name] = property.value.value
    }

    return args;
};

/**
 * addSuperClass
 * @param element {string}
 * @param t {object}
 */
let addSuperClass = function(element, t) {

    let identifier = t.identifier(element);
    this.superClass = identifier;
};

/**
 * addStaticGetterProperty
 * @param type {string}
 * @param superClass {string}
 * @param t {object}
 */
let addStaticGetterProperty = function(type, superClass, t) {

    let classMethodNode = t.classMethod(
        'get',
        t.identifier(type),
        [],
        t.blockStatement([
            t.returnStatement(t.stringLiteral(superClass))
        ]),
        false,
        true
    );

    this.body.body.push(classMethodNode);
};

/**
 * defaultOptions
 * @type {{customElements: boolean}}
 */
let defaultOptions = {

    /**
     * possible values: false, v0, v1
     * customElements {boolean|string}
     */
    customElements: false,
};

/**
 * plugin
 * @astexplorer: https://astexplorer.net
 * @param t
 * @returns {{visitor: {ClassDeclaration: (function(*=))}}}
 */
let plugin = ({types: t}) => {

    return {
        visitor: {

            /**
             * ClassDeclaration
             * @param node {object}
             * @param opts {object}
             */
            ClassDeclaration({ node } = path, { opts } = state) {

                let options = Object.assign({}, defaultOptions, opts);

                let component = node::getDecorator('component');
                if(!component){
                    return;
                }

                let superClass = component::getArguments(t).extends;

                let element = getElementClassByName(superClass, elements);
                node::addSuperClass(element, t);

                if(/div/.test(superClass)){
                    return;
                }

                node::addStaticGetterProperty('extends', superClass, t);
            },
        },
    };
};

export default plugin;
<|endoftext|>


<commit_before>/**
 * OmitJSforCSSPlugin
 * @description : This plugin will omit bundled JS files, for dependencies that are exclusively CSS which become obsolete once extract-text-plugin extracts inlined CSS into its own .css file
 */

/**
 * @param {Object} options - Configurable options
 * @constructor
 */
function OmitJSforCSSPlugin(options) {
	const defaults = {
		preview: false, // OPTIONAL - {Boolean} - A preview of the files that are to be omitted (Will not actually omit)
		verbose: false // OPTIONAL - {Boolean} - Whether it should display which files will be omitted
	};

	if ((typeof options !== 'undefined' && (options === null || typeof options !== 'object')) || Array.isArray(options)) {
		throw new Error('OmitJSforCSSPlugin only takes an options "object" as an argument');
	}

	this.options = Object.assign({}, defaults, options || {});
}

/**
 * @function omitFiles
 * @param {Object} omitted - The omitted file's details
 * @param {Object} compilation
 */
OmitJSforCSSPlugin.prototype.omitFiles = function (omitted, compilation) {
	if (this.options.preview) {
		console.log(`PREVIEW File to be omitted for ${omitted.chunkName} : ${omitted.filename}`);
	} else {
		this.options.verbose && console.log(`File omitted for ${omitted.chunkName} : ${omitted.filename}`);
		delete compilation.assets[omitted.filename];
	}
};

/**
 * @function findOmissibleFiles
 * @param {Object} compilation
 */
/**
 * @function findOmissibleFiles
 * @param {Object} compilation
 */
OmitJSforCSSPlugin.prototype.findOmissibleFiles = function (compilation) {
	// Every chunk / entry point
	compilation.chunks.forEach(chunk => {
		let resourceOrigin = {};
		let assetTypeCount = { internal: 0, css: 0 };

		if (chunk.entryModule) {
			resourceOrigin[chunk.entryModule.resource] = true;
		}

		// Each entry point will have its own dependencies, based on the files inner deps or the array deps in entry
		Array.from(chunk.modulesIterable, module => {
			module.dependencies.forEach(({ module }) => {
				if (module) {
					const { resource } = module;
					if (resource && !resourceOrigin[resource]) {
						/\.(css|less|scss|sass)$/i.test(resource) ? assetTypeCount.css++ : assetTypeCount.internal++;
					}
				}
			});
		});

		// Get the filenames that will be emitted, generated by the chunk, and omit JS if applicable
		chunk.files.forEach(filename => {
			// If all dependencies of this entry were CSS, then a JS version of this file will be created
			// This js file will be empty due to extract-text-webpack-plugin
			if (assetTypeCount.css > 0 && assetTypeCount.internal === 0 && (/\.(js)$/i.test(filename) || /\.(js).map$/i.test(filename))) {
				let omitted = {
					filename: filename,
					chunkName: chunk.name
				};

				this.omitFiles(omitted, compilation);
			}
		});
	});
};

/**
 * Hook into the webpack compiler
 * @param {Object} compiler - The webpack compiler object
 */
OmitJSforCSSPlugin.prototype.apply = function (compiler) {
	compiler.hooks.emit.tap('OmitJSforCSSPlugin', (compilation) => {
		this.findOmissibleFiles(compilation);
	});
};

module.exports = OmitJSforCSSPlugin;
<commit_msg>Remove omitted filenames from chunk.files<commit_after>/**
 * OmitJSforCSSPlugin
 * @description : This plugin will omit bundled JS files, for dependencies that are exclusively CSS which become obsolete once extract-text-plugin extracts inlined CSS into its own .css file
 */

/**
 * @param {Object} options - Configurable options
 * @constructor
 */
function OmitJSforCSSPlugin(options) {
	const defaults = {
		preview: false, // OPTIONAL - {Boolean} - A preview of the files that are to be omitted (Will not actually omit)
		verbose: false // OPTIONAL - {Boolean} - Whether it should display which files will be omitted
	};

	if ((typeof options !== 'undefined' && (options === null || typeof options !== 'object')) || Array.isArray(options)) {
		throw new Error('OmitJSforCSSPlugin only takes an options "object" as an argument');
	}

	this.options = Object.assign({}, defaults, options || {});
}

/**
 * @function omitFiles
 * @param {String} filename - The omitted file name
 * @param {Object} chunk - The chunk containing the file to omit
 * @param {Object} compilation
 */
OmitJSforCSSPlugin.prototype.omitFiles = function (filename, chunk, compilation) {
	if (this.options.preview) {
		console.log(`PREVIEW File to be omitted for ${chunk.name} : ${filename}`);
	} else {
		this.options.verbose && console.log(`File omitted for ${chunk.name} : ${filename}`);
		chunk.files = chunk.files.filter(name => name !== filename);
		delete compilation.assets[filename];
	}
};

/**
 * @function findOmissibleFiles
 * @param {Object} compilation
 */
/**
 * @function findOmissibleFiles
 * @param {Object} compilation
 */
OmitJSforCSSPlugin.prototype.findOmissibleFiles = function (compilation) {
	// Every chunk / entry point
	compilation.chunks.forEach(chunk => {
		let resourceOrigin = {};
		let assetTypeCount = { internal: 0, css: 0 };

		if (chunk.entryModule) {
			resourceOrigin[chunk.entryModule.resource] = true;
		}

		// Each entry point will have its own dependencies, based on the files inner deps or the array deps in entry
		Array.from(chunk.modulesIterable, module => {
			module.dependencies.forEach(({ module }) => {
				if (module) {
					const { resource } = module;
					if (resource && !resourceOrigin[resource]) {
						/\.(css|less|scss|sass)$/i.test(resource) ? assetTypeCount.css++ : assetTypeCount.internal++;
					}
				}
			});
		});

		// Get the filenames that will be emitted, generated by the chunk, and omit JS if applicable
		chunk.files.forEach(filename => {
			// If all dependencies of this entry were CSS, then a JS version of this file will be created
			// This js file will be empty due to extract-text-webpack-plugin
			if (assetTypeCount.css > 0 && assetTypeCount.internal === 0 && (/\.(js)$/i.test(filename) || /\.(js).map$/i.test(filename))) {
				this.omitFiles(filename, chunk, compilation);
			}
		});
	});
};

/**
 * Hook into the webpack compiler
 * @param {Object} compiler - The webpack compiler object
 */
OmitJSforCSSPlugin.prototype.apply = function (compiler) {
	compiler.hooks.emit.tap('OmitJSforCSSPlugin', (compilation) => {
		this.findOmissibleFiles(compilation);
	});
};

module.exports = OmitJSforCSSPlugin;
<|endoftext|>


<commit_before>// ~~~~~~ REQUEST POOL MANAGER  ~~~~~ // // TODO: Should this be top level? ¯\_(ツ)_/¯
import requestPoolManager from './requestPool/requestPoolManager.js';

export { default as init } from './init.js';

// ~~~~~~ TOOLS ~~~~~ //
export { default as ArrowAnnotateTool } from './tools/ArrowAnnotateTool.js';
export { default as AngleTool } from './tools/AngleTool.js';
export { default as CobbAngleTool } from './tools/CobbAngleTool.js';
export { default as DoubleTapFitToWindowTool } from './tools/DoubleTapFitToWindowTool.js';
export { default as DragProbeTool } from './tools/DragProbeTool.js';
export { default as EllipticalRoiTool } from './tools/EllipticalRoiTool.js';
export { default as EraserTool } from './tools/EraserTool.js';
export { default as FreehandMouseTool } from './tools/FreehandMouseTool.js';
export { default as FreehandSculpterMouseTool } from './tools/FreehandSculpterMouseTool.js';
export { default as TextMarkerTool } from './tools/TextMarkerTool.js';
export { default as LengthTool } from './tools/LengthTool.js';
export { default as MagnifyTool } from './tools/MagnifyTool.js';
export { default as ProbeTool } from './tools/ProbeTool.js';
export { default as PanTool } from './tools/PanTool.js';
export { default as PanMultiTouchTool } from './tools/PanMultiTouchTool.js';
export { default as RectangleRoiTool } from './tools/RectangleRoiTool.js';
export { default as ReferenceLinesTool } from './tools/ReferenceLinesTool.js';
export { default as RotateTool } from './tools/RotateTool.js';
export { default as RotateTouchTool } from './tools/RotateTouchTool.js';
export { default as ScaleOverlayTool } from './tools/ScaleOverlayTool.js';
export { default as StackScrollTool } from './tools/StackScrollTool.js';
export { default as StackScrollMultiTouchTool } from './tools/StackScrollMultiTouchTool.js';
export { default as StackScrollMouseWheelTool } from './tools/StackScrollMouseWheelTool.js';
export { default as WwwcTool } from './tools/WwwcTool.js';
export { default as WwwcRegionTool } from './tools/WwwcRegionTool.js';
export { default as ZoomTool } from './tools/ZoomTool.js';
export { default as ZoomTouchPinchTool } from './tools/ZoomTouchPinchTool.js';
export { default as ZoomMouseWheelTool } from './tools/ZoomMouseWheelTool.js';
export { default as CrosshairsTool } from './tools/CrosshairsTool.js';
export { default as BrushTool } from './tools/BrushTool.js';
export { default as BidirectionalTool } from './tools/BidirectionalTool.js';

// ~~~~~~ STACK TOOLS ~~~~~ //
export { default as stackPrefetch } from './stackTools/stackPrefetch.js';
export { default as stackRenderers } from './stackTools/stackRenderers.js';
export { playClip, stopClip } from './stackTools/playClip.js';

// ~~~~~~ STATE MANAGEMENT ~~~~~ //
export { default as store } from './store/index.js';
export { default as getToolForElement } from './store/getToolForElement.js';
export { addTool, addToolForElement } from './store/addTool.js';
export { removeTool, removeToolForElement } from './store/removeTool.js';
export { setToolOptions, setToolOptionsForElement } from './store/setToolOptions.js';
export {
  setToolActive,
  setToolActiveForElement,
  setToolEnabled,
  setToolEnabledForElement,
  setToolDisabled,
  setToolDisabledForElement,
  setToolPassive,
  setToolPassiveForElement
} from './store/setToolMode.js';
export {
  addToolState,
  getToolState,
  removeToolState,
  clearToolState,
  setElementToolStateManager,
  getElementToolStateManager
} from './stateManagement/toolState.js';
export { default as textStyle } from './stateManagement/textStyle.js';
export { default as toolStyle } from './stateManagement/toolStyle.js';
export { default as toolColors } from './stateManagement/toolColors.js';
export { default as toolCoordinates } from './stateManagement/toolCoordinates.js';
export {
  stackSpecificStateManager,
  newStackSpecificToolStateManager,
  addStackStateManager
} from './stateManagement/stackSpecificStateManager.js';
export { default as loadHandlerManager } from './stateManagement/loadHandlerManager.js';
export {
  newImageIdSpecificToolStateManager,
  globalImageIdSpecificToolStateManager
} from './stateManagement/imageIdSpecificStateManager.js';
export {
  newFrameOfReferenceSpecificToolStateManager,
  globalFrameOfReferenceSpecificToolStateManager
} from './stateManagement/frameOfReferenceStateManager.js';
export {
  forceEnabledElementResize
} from './eventListeners/windowResizeHandler.js';

// ~~~~~~ ORIENTATION  ~~~~~ //
export { default as orientation } from './orientation/index.js';

// ~~~~~~ CANVAS EXPORT  ~~~~~ //
export { default as SaveAs } from './util/SaveAs.js';

// ~~~~~~ THIRD PARTY SUPPORT  ~~~~~ //
export { default as import } from './import.js';
export { default as register } from './thirdParty/register.js';
export { default as registerSome } from './thirdParty/registerSome.js';

// ~~~~~~ SYNCHRONIZERS ~~~~~ //
export {
  default as wwwcSynchronizer
} from './synchronization/wwwcSynchronizer.js';
export {
  default as updateImageSynchronizer
} from './synchronization/updateImageSynchronizer.js';
export {
  default as Synchronizer
} from './synchronization/Synchronizer.js';
export {
  default as stackScrollSynchronizer
} from './synchronization/stackScrollSynchronizer.js';
export {
  default as stackImagePositionSynchronizer
} from './synchronization/stackImagePositionSynchronizer.js';
export {
  default as stackImagePositionOffsetSynchronizer
} from './synchronization/stackImagePositionOffsetSynchronizer.js';
export {
  default as stackImageIndexSynchronizer
} from './synchronization/stackImageIndexSynchronizer.js';
export {
  default as panZoomSynchronizer
} from './synchronization/panZoomSynchronizer.js';

// ~~~~~~ REQUEST POOL MANAGER  ~~~~~ //
export { default as requestPoolManager } from './requestPool/requestPoolManager.js';

export { default as external } from './externalModules.js';
export { default as EVENTS } from './events.js';
export { default as version } from './version.js';
<commit_msg>Add CornerstoneTools namespace<commit_after>/**
 * @namespace CornerstoneTools
 */

// ~~~~~~ REQUEST POOL MANAGER  ~~~~~ // // TODO: Should this be top level? ¯\_(ツ)_/¯
import requestPoolManager from './requestPool/requestPoolManager.js';

export { default as init } from './init.js';

// ~~~~~~ TOOLS ~~~~~ //
export { default as ArrowAnnotateTool } from './tools/ArrowAnnotateTool.js';
export { default as AngleTool } from './tools/AngleTool.js';
export { default as CobbAngleTool } from './tools/CobbAngleTool.js';
export { default as DoubleTapFitToWindowTool } from './tools/DoubleTapFitToWindowTool.js';
export { default as DragProbeTool } from './tools/DragProbeTool.js';
export { default as EllipticalRoiTool } from './tools/EllipticalRoiTool.js';
export { default as EraserTool } from './tools/EraserTool.js';
export { default as FreehandMouseTool } from './tools/FreehandMouseTool.js';
export { default as FreehandSculpterMouseTool } from './tools/FreehandSculpterMouseTool.js';
export { default as TextMarkerTool } from './tools/TextMarkerTool.js';
export { default as LengthTool } from './tools/LengthTool.js';
export { default as MagnifyTool } from './tools/MagnifyTool.js';
export { default as ProbeTool } from './tools/ProbeTool.js';
export { default as PanTool } from './tools/PanTool.js';
export { default as PanMultiTouchTool } from './tools/PanMultiTouchTool.js';
export { default as RectangleRoiTool } from './tools/RectangleRoiTool.js';
export { default as ReferenceLinesTool } from './tools/ReferenceLinesTool.js';
export { default as RotateTool } from './tools/RotateTool.js';
export { default as RotateTouchTool } from './tools/RotateTouchTool.js';
export { default as ScaleOverlayTool } from './tools/ScaleOverlayTool.js';
export { default as StackScrollTool } from './tools/StackScrollTool.js';
export { default as StackScrollMultiTouchTool } from './tools/StackScrollMultiTouchTool.js';
export { default as StackScrollMouseWheelTool } from './tools/StackScrollMouseWheelTool.js';
export { default as WwwcTool } from './tools/WwwcTool.js';
export { default as WwwcRegionTool } from './tools/WwwcRegionTool.js';
export { default as ZoomTool } from './tools/ZoomTool.js';
export { default as ZoomTouchPinchTool } from './tools/ZoomTouchPinchTool.js';
export { default as ZoomMouseWheelTool } from './tools/ZoomMouseWheelTool.js';
export { default as CrosshairsTool } from './tools/CrosshairsTool.js';
export { default as BrushTool } from './tools/BrushTool.js';
export { default as BidirectionalTool } from './tools/BidirectionalTool.js';

// ~~~~~~ STACK TOOLS ~~~~~ //
export { default as stackPrefetch } from './stackTools/stackPrefetch.js';
export { default as stackRenderers } from './stackTools/stackRenderers.js';
export { playClip, stopClip } from './stackTools/playClip.js';

// ~~~~~~ STATE MANAGEMENT ~~~~~ //
export { default as store } from './store/index.js';
export { default as getToolForElement } from './store/getToolForElement.js';
export { addTool, addToolForElement } from './store/addTool.js';
export { removeTool, removeToolForElement } from './store/removeTool.js';
export { setToolOptions, setToolOptionsForElement } from './store/setToolOptions.js';
export {
  setToolActive,
  setToolActiveForElement,
  setToolEnabled,
  setToolEnabledForElement,
  setToolDisabled,
  setToolDisabledForElement,
  setToolPassive,
  setToolPassiveForElement
} from './store/setToolMode.js';
export {
  addToolState,
  getToolState,
  removeToolState,
  clearToolState,
  setElementToolStateManager,
  getElementToolStateManager
} from './stateManagement/toolState.js';
export { default as textStyle } from './stateManagement/textStyle.js';
export { default as toolStyle } from './stateManagement/toolStyle.js';
export { default as toolColors } from './stateManagement/toolColors.js';
export { default as toolCoordinates } from './stateManagement/toolCoordinates.js';
export {
  stackSpecificStateManager,
  newStackSpecificToolStateManager,
  addStackStateManager
} from './stateManagement/stackSpecificStateManager.js';
export { default as loadHandlerManager } from './stateManagement/loadHandlerManager.js';
export {
  newImageIdSpecificToolStateManager,
  globalImageIdSpecificToolStateManager
} from './stateManagement/imageIdSpecificStateManager.js';
export {
  newFrameOfReferenceSpecificToolStateManager,
  globalFrameOfReferenceSpecificToolStateManager
} from './stateManagement/frameOfReferenceStateManager.js';
export {
  forceEnabledElementResize
} from './eventListeners/windowResizeHandler.js';

// ~~~~~~ ORIENTATION  ~~~~~ //
export { default as orientation } from './orientation/index.js';

// ~~~~~~ CANVAS EXPORT  ~~~~~ //
export { default as SaveAs } from './util/SaveAs.js';

// ~~~~~~ THIRD PARTY SUPPORT  ~~~~~ //
export { default as import } from './import.js';
export { default as register } from './thirdParty/register.js';
export { default as registerSome } from './thirdParty/registerSome.js';

// ~~~~~~ SYNCHRONIZERS ~~~~~ //
export {
  default as wwwcSynchronizer
} from './synchronization/wwwcSynchronizer.js';
export {
  default as updateImageSynchronizer
} from './synchronization/updateImageSynchronizer.js';
export {
  default as Synchronizer
} from './synchronization/Synchronizer.js';
export {
  default as stackScrollSynchronizer
} from './synchronization/stackScrollSynchronizer.js';
export {
  default as stackImagePositionSynchronizer
} from './synchronization/stackImagePositionSynchronizer.js';
export {
  default as stackImagePositionOffsetSynchronizer
} from './synchronization/stackImagePositionOffsetSynchronizer.js';
export {
  default as stackImageIndexSynchronizer
} from './synchronization/stackImageIndexSynchronizer.js';
export {
  default as panZoomSynchronizer
} from './synchronization/panZoomSynchronizer.js';

// ~~~~~~ REQUEST POOL MANAGER  ~~~~~ //
export { default as requestPoolManager } from './requestPool/requestPoolManager.js';

export { default as external } from './externalModules.js';
export { default as EVENTS } from './events.js';
export { default as version } from './version.js';
<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

(function() {
    // special patch to correctly work on Ripple emulator (CB-9760)
    if (window.parent && !!window.parent.ripple) { // https://gist.github.com/triceam/4658021
        module.exports = window.open.bind(window); // fallback to default window.open behaviour
        return;
    }

    var exec = require('cordova/exec');
    var channel = require('cordova/channel');
    var modulemapper = require('cordova/modulemapper');
    var urlutil = require('cordova/urlutil');

    var InAppBrowser = function(url, windowName, windowFeatures, callbacks) {
        var me = this,
            hidden = false,
            backChannels = {
                preventexitonhide : channel.create('preventexitonhide')
            }
            polling = false,
            lastUrl = url,
            lastWindowName = windowName,
            lastWindowFeatures = windowFeatures
            lastPollIntervalToRestore = null,
            lastPollFunctionToRestore = null;

        function clearPolling () {
            lastPollIntervalToRestore = null;
            lastPollFunctionToRestore = null;
        }

        function releaseListeners(){
            for(var eventname in me.channels)
            {
                for(var listenerObserverId in me.channels[eventname].handlers)
                {
                    me.removeEventListener(eventname, me.channels[eventname].handlers[listenerObserverId]);
                }
            }
        }

        function eventCallback (event) {
            if (event && (event.type in backChannels)) {
                backChannels[event.type].fire(event);
            }
            if (event && (event.type in me.channels)) {
                me.channels[event.type].fire(event);
            }
        }

        backChannels['preventexitonhide'].subscribe(function(){
                var exitHandlersToRestore = {},
                exitChannel = me.channels['exit'],
                exitRestoreCallBack = function(){
                    // This cleans up the current handler
                    if(exitRestoreCallBack.observer_guid){
                        me.removeEventListener('exit', exitChannel.handlers[exitRestoreCallBack.observer_guid]);
                    }

                    for(var exitCallbackObserverId in exitHandlersToRestore) {
                        var eventHandler = exitHandlersToRestore[exitCallbackObserverId];
                        me.addEventListener('exit', eventHandler);
                    }
                };

                // Need to set this here as it is possible to hide via native code "directly" by calling hide via the
                // command infrastructure and not the hide method
                hidden = true;
                if(exitChannel.numHandlers >0){
                    for(var exitCallbackObserverId in exitChannel.handlers) {
                        var eventHandler = exitChannel.handlers[exitCallbackObserverId];
                        exitHandlersToRestore[exitCallbackObserverId] = exitChannel.handlers[exitCallbackObserverId];
                        me.removeEventListener('exit', eventHandler);
                    }
                    me.addEventListener('exit', exitRestoreCallBack);
                }
        });

        me.channels = {
            'loadstart': channel.create('loadstart'),
            'loadstop' : channel.create('loadstop'),
            'loaderror' : channel.create('loaderror'),
            'hidden' : channel.create('hidden'),
            'unhidden' : channel.create('unhidden'),
            'pollresult' : channel.create('pollresult'),
            'exit' : channel.create('exit')
        }

        me.isHidden = function(){
            return hidden;
        }

        me.isPolling = function(){
            return polling;
        }

        me.close = function(eventname) {
            exec(null, null, "InAppBrowser", "close", []);
            me.stopPoll();
            if(hidden){
                me.channels['exit'].fire();
            }
            hidden = false;
        }

        me.show = function(eventname) {
            exec(null, null, "InAppBrowser", "show", []);
            hidden = false;
        }

        me.startPoll = function(pollFunction, pollInterval){
           lastPollIntervalToRestore = pollInterval;
           lastPollFunctionToRestore = pollFunction;
           exec(null, null, "InAppBrowser", "startPoll", [pollFunction, pollInterval])
           polling = true;
        }

        me.stopPoll = function() {
           exec(null, null, "InAppBrowser", "stopPoll", []);
           clearPolling();
           polling = false;
        }

        me.hide = function(releaseResources, blankPage){
            //blankPage has no effect in iOS - the view is destroyed
            if(hidden){
                return;
            }

            if(releaseResources){
                me.stopPoll();
                releaseListeners();
            }

            // Release resources has no effect in native iOS - the IAB
            // Is fully closed & the JS pretends it isn't
            exec(null,null,"InAppBrowser", "hide", [releaseResources]);
            hidden = true;
        }

        me.unHide = function(strUrl, eventname){
            if(!hidden){
                return;
            }

            if(strUrl){
                lastUrl = urlutil.makeAbsolute(strUrl) || lastUrl;
            }

            console.log(lastUrl);
            console.log(lastWindowName);
            console.log(lastWindowFeatures);

            me.startPoll(lastPollFunctionToRestore, lastPollIntervalToRestore);
            exec(eventCallback, eventCallback, "InAppBrowser", "unHide", [lastUrl, lastWindowName, lastWindowFeatures]);
            hidden = false;
        }

        me.addEventListener = function (eventname,f) {
            if (eventname in me.channels) {
                me.channels[eventname].subscribe(f);
            }
        }

        me.removeEventListener = function (eventname, f) {
            if (eventname in me.channels) {
                me.channels[eventname].unsubscribe(f);
            }
        }

        me.executeScript = function (injectDetails, cb) {
            if (injectDetails.code) {
                exec(cb, null, "InAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]);
            } else if (injectDetails.file) {
                exec(cb, null, "InAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]);
            } else {
                throw new Error('executeScript requires exactly one of code or file to be specified');
            }
        }

        me.insertCSS = function (injectDetails, cb) {
            if (injectDetails.code) {
                exec(cb, null, "InAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]);
            } else if (injectDetails.file) {
                exec(cb, null, "InAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]);
            } else {
                throw new Error('insertCSS requires exactly one of code or file to be specified');
            }
        }

        for (var callbackName in callbacks) {
            me.addEventListener(callbackName, callbacks[callbackName]);
        }

        exec(eventCallback, eventCallback, "InAppBrowser", "open", [lastUrl, lastWindowName, lastWindowFeatures]);
    }

    module.exports = function(strUrl, strWindowName, strWindowFeatures, callbacks) {
        // Don't catch calls that write to existing frames (e.g. named iframes).
        if (window.frames && window.frames[strWindowName]) {
            var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open');
            return origOpenFunc.apply(window, arguments);
        }

        strUrl = urlutil.makeAbsolute(strUrl);
        strWindowFeatures = strWindowFeatures || "";

        if(strWindowName === '_system') {
            console.log('*************SYSTEM BROWSER *************')
            exec(null, null, "SystemBrowser", "open", [strUrl, strWindowName, strWindowFeatures]);
        } else {
            return new InAppBrowser(strUrl, strWindowName, strWindowFeatures, callbacks || {});
        }
    };
})();
<commit_msg>K&R native code up as per style guide<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

(function() {
    // special patch to correctly work on Ripple emulator (CB-9760)
    if (window.parent && !!window.parent.ripple) { // https://gist.github.com/triceam/4658021
        module.exports = window.open.bind(window); // fallback to default window.open behaviour
        return;
    }

    var exec = require('cordova/exec');
    var channel = require('cordova/channel');
    var modulemapper = require('cordova/modulemapper');
    var urlutil = require('cordova/urlutil');

    var InAppBrowser = function(url, windowName, windowFeatures, callbacks) {
        var me = this,
            hidden = false,
            backChannels = {
                preventexitonhide : channel.create('preventexitonhide')
            }
            polling = false,
            lastUrl = url,
            lastWindowName = windowName,
            lastWindowFeatures = windowFeatures
            lastPollIntervalToRestore = null,
            lastPollFunctionToRestore = null;

        function clearPolling () {
            lastPollIntervalToRestore = null;
            lastPollFunctionToRestore = null;
        }

        function releaseListeners(){
            for(var eventname in me.channels)
            {
                for(var listenerObserverId in me.channels[eventname].handlers)
                {
                    me.removeEventListener(eventname, me.channels[eventname].handlers[listenerObserverId]);
                }
            }
        }

        function eventCallback (event) {
            if (event && (event.type in backChannels)) {
                backChannels[event.type].fire(event);
            }
            if (event && (event.type in me.channels)) {
                me.channels[event.type].fire(event);
            }
        }

        backChannels['preventexitonhide'].subscribe(function(){
                var exitHandlersToRestore = {},
                exitChannel = me.channels['exit'],
                exitRestoreCallBack = function(){
                    // This cleans up the current handler
                    if(exitRestoreCallBack.observer_guid){
                        me.removeEventListener('exit', exitChannel.handlers[exitRestoreCallBack.observer_guid]);
                    }

                    for(var exitCallbackObserverId in exitHandlersToRestore) {
                        var eventHandler = exitHandlersToRestore[exitCallbackObserverId];
                        me.addEventListener('exit', eventHandler);
                    }
                };

                // Need to set this here as it is possible to hide via native code "directly" by calling hide via the
                // command infrastructure and not the hide method
                hidden = true;
                if(exitChannel.numHandlers >0){
                    for(var exitCallbackObserverId in exitChannel.handlers) {
                        var eventHandler = exitChannel.handlers[exitCallbackObserverId];
                        exitHandlersToRestore[exitCallbackObserverId] = exitChannel.handlers[exitCallbackObserverId];
                        me.removeEventListener('exit', eventHandler);
                    }
                    me.addEventListener('exit', exitRestoreCallBack);
                }
        });

        me.channels = {
            'loadstart': channel.create('loadstart'),
            'loadstop' : channel.create('loadstop'),
            'loaderror' : channel.create('loaderror'),
            'hidden' : channel.create('hidden'),
            'unhidden' : channel.create('unhidden'),
            'pollresult' : channel.create('pollresult'),
            'exit' : channel.create('exit')
        }

        me.isHidden = function(){
            return hidden;
        }

        me.isPolling = function(){
            return polling;
        }

        me.close = function(eventname) {
            exec(null, null, "InAppBrowser", "close", []);
            me.stopPoll();
            if(hidden){
                me.channels['exit'].fire();
            }
            hidden = false;
        }

        me.show = function(eventname) {
            exec(null, null, "InAppBrowser", "show", []);
            hidden = false;
        }

        me.startPoll = function(pollFunction, pollInterval){
           lastPollIntervalToRestore = pollInterval;
           lastPollFunctionToRestore = pollFunction;
           exec(null, null, "InAppBrowser", "startPoll", [pollFunction, pollInterval])
           polling = true;
        }

        me.stopPoll = function() {
           exec(null, null, "InAppBrowser", "stopPoll", []);
           clearPolling();
           polling = false;
        }

        me.hide = function(releaseResources, blankPage){

            if(releaseResources){
                me.stopPoll();
                releaseListeners();
            }

            // Release resources has no effect in native iOS - the IAB
            // Is fully closed & the JS pretends it isn't
            exec(null,null,"InAppBrowser", "hide", [releaseResources]);
            hidden = true;
        }

        me.unHide = function(strUrl, eventname){

            if(strUrl){
                lastUrl = urlutil.makeAbsolute(strUrl) || lastUrl;
            }

            me.startPoll(lastPollFunctionToRestore, lastPollIntervalToRestore);
            exec(eventCallback, eventCallback, "InAppBrowser", "unHide", [lastUrl, lastWindowName, lastWindowFeatures]);
            hidden = false;
        }

        me.addEventListener = function (eventname,f) {
            if (eventname in me.channels) {
                me.channels[eventname].subscribe(f);
            }
        }

        me.removeEventListener = function (eventname, f) {
            if (eventname in me.channels) {
                me.channels[eventname].unsubscribe(f);
            }
        }

        me.executeScript = function (injectDetails, cb) {
            if (injectDetails.code) {
                exec(cb, null, "InAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]);
            } else if (injectDetails.file) {
                exec(cb, null, "InAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]);
            } else {
                throw new Error('executeScript requires exactly one of code or file to be specified');
            }
        }

        me.insertCSS = function (injectDetails, cb) {
            if (injectDetails.code) {
                exec(cb, null, "InAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]);
            } else if (injectDetails.file) {
                exec(cb, null, "InAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]);
            } else {
                throw new Error('insertCSS requires exactly one of code or file to be specified');
            }
        }

        for (var callbackName in callbacks) {
            me.addEventListener(callbackName, callbacks[callbackName]);
        }

        exec(eventCallback, eventCallback, "InAppBrowser", "open", [lastUrl, lastWindowName, lastWindowFeatures]);
    }

    module.exports = function(strUrl, strWindowName, strWindowFeatures, callbacks) {
        // Don't catch calls that write to existing frames (e.g. named iframes).
        if (window.frames && window.frames[strWindowName]) {
            var origOpenFunc = modulemapper.getOriginalSymbol(window, 'open');
            return origOpenFunc.apply(window, arguments);
        }

        strUrl = urlutil.makeAbsolute(strUrl);
        strWindowFeatures = strWindowFeatures || "";

        if(strWindowName === '_system') {
            // This is now separate as more-or-less fire and forget system browser was re-utilising
            // Code for blank/self. This caused problems with browser crashes etc.
            exec(null, null, "SystemBrowser", "open", [strUrl, strWindowName, strWindowFeatures]);
        } else {
            return new InAppBrowser(strUrl, strWindowName, strWindowFeatures, callbacks || {});
        }
    };
})();
<|endoftext|>