<commit_before>/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */

//
// Dalliance Genome Explorer
// (c) Thomas Down 2006-2013
//
// thub.js: support for track-hub style registries
//

"use strict";

if (typeof(require) !== 'undefined') {
    var utils = require('./utils');
    var textXHR = utils.textXHR;
    var relativeURL = utils.relativeURL;
    var shallowCopy = utils.shallowCopy;

    var das = require('./das');
    var DASStylesheet = das.DASStylesheet;
    var DASStyle = das.DASStyle;
}

var THUB_STANZA_REGEXP = /\n\s*\n/;
var THUB_PARSE_REGEXP  = /(\w+) +(.+)\n?/;
var THUB_SUBGROUP_REGEXP = /subGroup[1-9]/;

var THUB_PENNANT_PREFIX = 'http://genome.ucsc.edu/images/';

function TrackHub(url) {
    this.genomes = {};
    this.url = url;
}

function TrackHubTrack() {
}

TrackHubTrack.prototype.get = function(k) {
    if (this[k])
        return this[k];
    else if (this._parent)
        return this._parent.get(k);
}

function TrackHubDB(hub) {
    this.hub = hub;
}

TrackHubDB.prototype.getTracks = function(callback) {
    var thisB = this;
    if (this._tracks) {
        return callback(this._tracks);
    }

    textXHR(this.absURL, function(trackFile, err) {
        if (err) {
            return callback(null, err);
        }

        // trackFile = trackFile.replace(/\#.*/g, '');
        trackFile = trackFile.replace('\\\n', ' ');

        var tracks = [];
        var tracksById = {};
        var stanzas = trackFile.split(THUB_STANZA_REGEXP);
        for (var s = 0; s < stanzas.length; ++s) {
            var toks = stanzas[s].replace(/\#.*/g, '').split(THUB_PARSE_REGEXP);
            var track = new TrackHubTrack();
            track._db = thisB;
            for (var l = 0; l < toks.length - 2; l += 3) {
                var k = toks[l+1], v = toks[l+2];
                if (k.match(THUB_SUBGROUP_REGEXP)) {
                    if (!track.subgroups)
                        track.subgroups = {};
                    var sgtoks = v.split(/\s/);
                    var sgtag = sgtoks[0];
                    var sgrecord = {name: sgtoks[1], tags: [], titles: []};
                    for (var sgti = 2; sgti < sgtoks.length; ++sgti) {
                        var grp = sgtoks[sgti].split(/=/);
                        sgrecord.tags.push(grp[0]);
                        sgrecord.titles.push(grp[1]);
                    }
                    track.subgroups[sgtag] = sgrecord;
                } else if (k === 'subGroups') {
                    var sgtoks = v.split(/(\w+)=(\w+)/);
                    track.sgm = {};
                    for (var sgti = 0; sgti < sgtoks.length - 2; sgti += 3) {
                        track.sgm[sgtoks[sgti+1]] = sgtoks[sgti + 2];
                    }
                } else {
                    track[toks[l+1]] = toks[l+2];
                }
            }

            if (track.track && (track.type || track.container || track.view)) {
                tracks.push(track);
                tracksById[track.track] = track;
            }
        }

        var toplevels = [];
        var composites = [];
        for (var ti = 0; ti < tracks.length; ++ti) {
            var track = tracks[ti];
            var top = true;
            if (track.parent) {
                var ptoks = track.parent.split(/\s+/);
                var parent = tracksById[ptoks[0]];
                if (parent) {
                    track._parent = parent;

                    if (!parent.children)
                        parent.children = [];
                    parent.children.push(track);

                    if (parent)
                        top = false;
                } else {
                    // console.log("Couldn't find parent " + ptoks[0] + '(' + track.parent + ')');
                }

            }
            if (track.compositeTrack) {
                composites.push(track);
            } else if (top) {
                toplevels.push(track);
            }
        }

        for (var ci = 0; ci < composites.length; ++ci) {
            var comp = composites[ci];
            if (!comp.children)
                continue;

            var parentOfViews = false;
            for (var ki = 0; ki < comp.children.length; ++ki) {
                var k = comp.children[ki];
                if (k.view) {
                    k.shortLabel = comp.shortLabel + ": " + k.shortLabel;
                    toplevels.push(k);
                    parentOfViews = true;
                }
            }
            if (!parentOfViews)
                toplevels.push(comp);
        }

        thisB._tracks = toplevels;
        return callback(thisB._tracks, null);
    }, {credentials: this.credentials, salt: true});
}

function connectTrackHub(hubURL, callback, opts) {
    opts = opts || {};
    opts.salt = true;

    textXHR(hubURL, function(hubFile, err) {
        if (err) {
            return callback(null, err);
        }

        var toks = hubFile.split(THUB_PARSE_REGEXP);
        var hub = new TrackHub(hubURL);
        if (opts.credentials) {
            hub.credentials = opts.credentials;
        }
        for (var l = 0; l < toks.length - 2; l += 3) {
            hub[toks[l+1]] = toks[l+2];
        }

        if (hub.genomesFile) {
            var genURL = relativeURL(hubURL, hub.genomesFile);
            textXHR(genURL, function(genFile, err) {
                if (err) {
                    return callback(null, err);
                }

                var stanzas = genFile.split(THUB_STANZA_REGEXP);
                for (var s = 0; s < stanzas.length; ++s) {
                    var toks = stanzas[s].split(THUB_PARSE_REGEXP);
                    var gprops = new TrackHubDB(hub);
                    if (opts.credentials) {
                        gprops.credentials = opts.credentials;
                    }

                    for (var l = 0; l < toks.length - 2; l += 3) {
                        gprops[toks[l+1]] = toks[l+2];
                    }

                    if (gprops.twoBitPath) {
                        gprops.twoBitPath = relativeURL(genURL, gprops.twoBitPath);
                    }

                    if (gprops.genome && gprops.trackDb) {
                        gprops.absURL = relativeURL(genURL, gprops.trackDb);
                        hub.genomes[gprops.genome] = gprops;
                    }
                }

                callback(hub);

            }, opts);
        } else {
            callback(null, 'No genomesFile');
        }
    }, opts);
}

TrackHubTrack.prototype.toDallianceSource = function() {
    var source = {
        name: this.shortLabel,
        desc: this.longLabel
    };
    if (this._db.mapping) {
        source.mapping = this._db.mapping;
    }

    var pennantIcon = this.get('pennantIcon');
    if (pennantIcon) {
        var ptoks = pennantIcon.split(/\s+/);
        source.pennant = THUB_PENNANT_PREFIX + ptoks[0];
    }

    var searchTrix = this.get('searchTrix');
    if (searchTrix) {
        source.trixURI = relativeURL(this._db.absURL, searchTrix);
    }

    if (this.container == 'multiWig') {
        source.merge = 'concat';
        source.overlay = [];
        var children = this.children || [];
        source.style = [];
        source.noDownsample = true;

        for (var ci = 0; ci < children.length; ++ci) {
            var ch = children[ci];
            var cs = ch.toDallianceSource()
            source.overlay.push(cs);

            if (cs.style) {
                for (var si = 0; si < cs.style.length; ++si) {
                    var style = cs.style[si];
                    style.method = ch.shortLabel;  // FIXME
                    if (this.aggregate == 'transparentOverlay')
                        style.style.ALPHA = 0.5;
                    source.style.push(style);
                }
            }
        }
        return source;

    } else {
        var typeToks = this.type.split(/\s+/);
        if (typeToks[0] == 'bigBed') {
            var bedTokens = typeToks[1]|0
            var bedPlus = typeToks[2] == '+';

            source.bwgURI = relativeURL(this._db.absURL, this.bigDataUrl);
            source.style = this.bigbedStyles();
            if (this._db.credentials) {
                source.credentials = true;
            }
            if (bedTokens >= 12 && bedPlus)
                source.collapseSuperGroups = true;
            return source;
        } else if (typeToks[0] == 'bigWig') {
            source.bwgURI = relativeURL(this._db.absURL, this.bigDataUrl);
            source.style = this.bigwigStyles();
            source.noDownsample = true;     // FIXME seems like a blunt instrument...

            if (this.yLineOnOff && this.yLineOnOff == 'on') {
                source.quantLeapThreshold = this.yLineMark !== undefined ? (1.0 * this.yLineMark) : 0.0;
            }

            if (this._db.credentials) {
                source.credentials = true;
            }

            return source;
        } else if (typeToks[0] == 'bam') {
            source.bamURI = relativeURL(this._db.absURL, this.bigDataUrl);
            if (this._db.credentials) {
                source.credentials = true;
            }
            return source;
        } else {
            console.log('Unsupported ' + this.type);
        }
    }
}

TrackHubTrack.prototype.bigwigStyles = function() {
    var typeToks = this.type.split(/\s+/);
    var min, max;
    if (typeToks.length >= 3) {
        min = 1.0 * typeToks[1];
        max = 1.0 * typeToks[2];
    }

    var height;
    if (this.maxHeightPixels) {
        var mhpToks = this.maxHeightPixels.split(/:/);
        if (mhpToks.length == 3) {
            height = mhpToks[1] | 0;
        } else {
            console.log('maxHeightPixels should be of the form max:default:min');
        }
    }

    var gtype = 'bars';
    if (this.graphTypeDefault) {
        gtype = this.graphTypeDefault;
    }

    var color = 'black';
    var altColor = null;
    if (this.color) {
        color = 'rgb(' + this.color + ')';
    }
    if (this.altColor) {
        altColor = 'rgb(' + this.altColor + ')';
    }

    var stylesheet = new DASStylesheet();
    var wigStyle = new DASStyle();
    if (gtype == 'points') {
        wigStyle.glyph = 'POINT';
    } else {
        wigStyle.glyph = 'HISTOGRAM';
    }

    if (altColor) {
        wigStyle.COLOR1 = color;
        wigStyle.COLOR2 = altColor;
    } else {
        wigStyle.BGCOLOR = color;
    }
    wigStyle.HEIGHT = height || 30;
    if (min || max) {
        wigStyle.MIN = min;
        wigStyle.MAX = max;
    }
    stylesheet.pushStyle({type: 'default'}, null, wigStyle);
    return stylesheet.styles;
}

TrackHubTrack.prototype.bigbedStyles = function() {
    var itemRgb = (''+this.get('itemRgb')).toLowerCase() == 'on';
    var visibility = this.get('visibility') || 'full';
    var color = this.get('color');
    if (color)
        color = 'rgb(' + color + ')';
    else
        color = 'blue';

    var stylesheet = new DASStylesheet();
    var wigStyle = new DASStyle();
    wigStyle.glyph = 'BOX';
    wigStyle.FGCOLOR = 'black';
    wigStyle.BGCOLOR = color;
    wigStyle.HEIGHT = (visibility == 'full' || visibility == 'pack') ? 12 : 8;
    wigStyle.BUMP = (visibility == 'full' || visibility == 'pack');
    wigStyle.LABEL = (visibility == 'full' || visibility == 'pack');
    wigStyle.ZINDEX = 20;
    if (itemRgb)
        wigStyle.BGITEM = true;

    var cbs = this.get('colorByStrand');
    if (cbs) {
        var cbsToks = cbs.split(/\s+/);

        var plus = shallowCopy(wigStyle);
        plus.BGCOLOR = 'rgb(' + cbsToks[0] + ')';
        stylesheet.pushStyle({type: 'bigwig', orientation: '+'}, null, plus);

        var minus = shallowCopy(wigStyle);
        minus.BGCOLOR = 'rgb(' + cbsToks[1] + ')';
        stylesheet.pushStyle({type: 'bigwig', orientation: '-'}, null, minus);
    } else {
        stylesheet.pushStyle({type: 'bigwig'}, null, wigStyle);
    }

    var tlStyle = new DASStyle();
    tlStyle.glyph = 'BOX';
    tlStyle.FGCOLOR = 'black';
    if (itemRgb)
        tlStyle.BGITEM = true;
    tlStyle.BGCOLOR = 'red'
    tlStyle.HEIGHT = 10;
    tlStyle.BUMP = true;
    tlStyle.ZINDEX = 20;
    stylesheet.pushStyle({type: 'bb-translation'}, null, tlStyle);

    var tsStyle = new DASStyle();
    tsStyle.glyph = 'BOX';
    tsStyle.FGCOLOR = 'black';
    tsStyle.BGCOLOR = 'white';
    tsStyle.HEIGHT = 10;
    tsStyle.ZINDEX = 10;
    tsStyle.BUMP = true;
    tsStyle.LABEL = true;
    stylesheet.pushStyle({type: 'bb-transcript'}, null, tsStyle);

/*
    var densStyle = new DASStyle();
    densStyle.glyph = 'HISTOGRAM';
    densStyle.COLOR1 = 'white';
    densStyle.COLOR2 = 'black';
    densStyle.HEIGHT=30;
    stylesheet.pushStyle({type: 'density'}, null, densStyle); */

    return stylesheet.styles;
}

function THUB_COMPARE(g, h) {
    if (g.priority && h.priority) {
        return (1.0 * g.priority) - (1.0 * h.priority)
    } else if (g.priority) {
        return 1;
    } else if (h.priority) {
        return -1;
    } else {
        return g.shortLabel.localeCompare(h.shortLabel);
    }
}

if (typeof(module) !== 'undefined') {
    module.exports = {
        connectTrackHub: connectTrackHub,
        THUB_COMPARE: THUB_COMPARE
    };
}
<commit_msg>Support vcfTabix tracks from track-hubs.<commit_after>/* -*- mode: javascript; c-basic-offset: 4; indent-tabs-mode: nil -*- */

//
// Dalliance Genome Explorer
// (c) Thomas Down 2006-2013
//
// thub.js: support for track-hub style registries
//

"use strict";

if (typeof(require) !== 'undefined') {
    var utils = require('./utils');
    var textXHR = utils.textXHR;
    var relativeURL = utils.relativeURL;
    var shallowCopy = utils.shallowCopy;

    var das = require('./das');
    var DASStylesheet = das.DASStylesheet;
    var DASStyle = das.DASStyle;
}

var THUB_STANZA_REGEXP = /\n\s*\n/;
var THUB_PARSE_REGEXP  = /(\w+) +(.+)\n?/;
var THUB_SUBGROUP_REGEXP = /subGroup[1-9]/;

var THUB_PENNANT_PREFIX = 'http://genome.ucsc.edu/images/';

function TrackHub(url) {
    this.genomes = {};
    this.url = url;
}

function TrackHubTrack() {
}

TrackHubTrack.prototype.get = function(k) {
    if (this[k])
        return this[k];
    else if (this._parent)
        return this._parent.get(k);
}

function TrackHubDB(hub) {
    this.hub = hub;
}

TrackHubDB.prototype.getTracks = function(callback) {
    var thisB = this;
    if (this._tracks) {
        return callback(this._tracks);
    }

    textXHR(this.absURL, function(trackFile, err) {
        if (err) {
            return callback(null, err);
        }

        // trackFile = trackFile.replace(/\#.*/g, '');
        trackFile = trackFile.replace('\\\n', ' ');

        var tracks = [];
        var tracksById = {};
        var stanzas = trackFile.split(THUB_STANZA_REGEXP);
        for (var s = 0; s < stanzas.length; ++s) {
            var toks = stanzas[s].replace(/\#.*/g, '').split(THUB_PARSE_REGEXP);
            var track = new TrackHubTrack();
            track._db = thisB;
            for (var l = 0; l < toks.length - 2; l += 3) {
                var k = toks[l+1], v = toks[l+2];
                if (k.match(THUB_SUBGROUP_REGEXP)) {
                    if (!track.subgroups)
                        track.subgroups = {};
                    var sgtoks = v.split(/\s/);
                    var sgtag = sgtoks[0];
                    var sgrecord = {name: sgtoks[1], tags: [], titles: []};
                    for (var sgti = 2; sgti < sgtoks.length; ++sgti) {
                        var grp = sgtoks[sgti].split(/=/);
                        sgrecord.tags.push(grp[0]);
                        sgrecord.titles.push(grp[1]);
                    }
                    track.subgroups[sgtag] = sgrecord;
                } else if (k === 'subGroups') {
                    var sgtoks = v.split(/(\w+)=(\w+)/);
                    track.sgm = {};
                    for (var sgti = 0; sgti < sgtoks.length - 2; sgti += 3) {
                        track.sgm[sgtoks[sgti+1]] = sgtoks[sgti + 2];
                    }
                } else {
                    track[toks[l+1]] = toks[l+2];
                }
            }

            if (track.track && (track.type || track.container || track.view)) {
                tracks.push(track);
                tracksById[track.track] = track;
            }
        }

        var toplevels = [];
        var composites = [];
        for (var ti = 0; ti < tracks.length; ++ti) {
            var track = tracks[ti];
            var top = true;
            if (track.parent) {
                var ptoks = track.parent.split(/\s+/);
                var parent = tracksById[ptoks[0]];
                if (parent) {
                    track._parent = parent;

                    if (!parent.children)
                        parent.children = [];
                    parent.children.push(track);

                    if (parent)
                        top = false;
                } else {
                    // console.log("Couldn't find parent " + ptoks[0] + '(' + track.parent + ')');
                }

            }
            if (track.compositeTrack) {
                composites.push(track);
            } else if (top) {
                toplevels.push(track);
            }
        }

        for (var ci = 0; ci < composites.length; ++ci) {
            var comp = composites[ci];
            if (!comp.children)
                continue;

            var parentOfViews = false;
            for (var ki = 0; ki < comp.children.length; ++ki) {
                var k = comp.children[ki];
                if (k.view) {
                    k.shortLabel = comp.shortLabel + ": " + k.shortLabel;
                    toplevels.push(k);
                    parentOfViews = true;
                }
            }
            if (!parentOfViews)
                toplevels.push(comp);
        }

        thisB._tracks = toplevels;
        return callback(thisB._tracks, null);
    }, {credentials: this.credentials, salt: true});
}

function connectTrackHub(hubURL, callback, opts) {
    opts = opts || {};
    opts.salt = true;

    textXHR(hubURL, function(hubFile, err) {
        if (err) {
            return callback(null, err);
        }

        var toks = hubFile.split(THUB_PARSE_REGEXP);
        var hub = new TrackHub(hubURL);
        if (opts.credentials) {
            hub.credentials = opts.credentials;
        }
        for (var l = 0; l < toks.length - 2; l += 3) {
            hub[toks[l+1]] = toks[l+2];
        }

        if (hub.genomesFile) {
            var genURL = relativeURL(hubURL, hub.genomesFile);
            textXHR(genURL, function(genFile, err) {
                if (err) {
                    return callback(null, err);
                }

                var stanzas = genFile.split(THUB_STANZA_REGEXP);
                for (var s = 0; s < stanzas.length; ++s) {
                    var toks = stanzas[s].split(THUB_PARSE_REGEXP);
                    var gprops = new TrackHubDB(hub);
                    if (opts.credentials) {
                        gprops.credentials = opts.credentials;
                    }

                    for (var l = 0; l < toks.length - 2; l += 3) {
                        gprops[toks[l+1]] = toks[l+2];
                    }

                    if (gprops.twoBitPath) {
                        gprops.twoBitPath = relativeURL(genURL, gprops.twoBitPath);
                    }

                    if (gprops.genome && gprops.trackDb) {
                        gprops.absURL = relativeURL(genURL, gprops.trackDb);
                        hub.genomes[gprops.genome] = gprops;
                    }
                }

                callback(hub);

            }, opts);
        } else {
            callback(null, 'No genomesFile');
        }
    }, opts);
}

TrackHubTrack.prototype.toDallianceSource = function() {
    var source = {
        name: this.shortLabel,
        desc: this.longLabel
    };
    if (this._db.mapping) {
        source.mapping = this._db.mapping;
    }

    var pennantIcon = this.get('pennantIcon');
    if (pennantIcon) {
        var ptoks = pennantIcon.split(/\s+/);
        source.pennant = THUB_PENNANT_PREFIX + ptoks[0];
    }

    var searchTrix = this.get('searchTrix');
    if (searchTrix) {
        source.trixURI = relativeURL(this._db.absURL, searchTrix);
    }

    if (this.container == 'multiWig') {
        source.merge = 'concat';
        source.overlay = [];
        var children = this.children || [];
        source.style = [];
        source.noDownsample = true;

        for (var ci = 0; ci < children.length; ++ci) {
            var ch = children[ci];
            var cs = ch.toDallianceSource()
            source.overlay.push(cs);

            if (cs.style) {
                for (var si = 0; si < cs.style.length; ++si) {
                    var style = cs.style[si];
                    style.method = ch.shortLabel;  // FIXME
                    if (this.aggregate == 'transparentOverlay')
                        style.style.ALPHA = 0.5;
                    source.style.push(style);
                }
            }
        }
        return source;

    } else {
        var typeToks = this.type.split(/\s+/);
        if (typeToks[0] == 'bigBed') {
            var bedTokens = typeToks[1]|0
            var bedPlus = typeToks[2] == '+';

            source.bwgURI = relativeURL(this._db.absURL, this.bigDataUrl);
            source.style = this.bigbedStyles();
            if (this._db.credentials) {
                source.credentials = true;
            }
            if (bedTokens >= 12 && bedPlus)
                source.collapseSuperGroups = true;
            return source;
        } else if (typeToks[0] == 'bigWig') {
            source.bwgURI = relativeURL(this._db.absURL, this.bigDataUrl);
            source.style = this.bigwigStyles();
            source.noDownsample = true;     // FIXME seems like a blunt instrument...

            if (this.yLineOnOff && this.yLineOnOff == 'on') {
                source.quantLeapThreshold = this.yLineMark !== undefined ? (1.0 * this.yLineMark) : 0.0;
            }

            if (this._db.credentials) {
                source.credentials = true;
            }

            return source;
        } else if (typeToks[0] == 'bam') {
            source.bamURI = relativeURL(this._db.absURL, this.bigDataUrl);
            if (this._db.credentials) {
                source.credentials = true;
            }
            return source;
        } else if (typeToks[0] == 'vcfTabix') {
            source.uri = relativeURL(this._db.absURL, this.bigDataUrl);
            source.tier_type = 'tabix';
            source.payload = 'vcf';
            if (this._db.credentials) {
                source.credentials = true;
            }
            return source;
        } else {
            console.log('Unsupported ' + this.type);
        }
    }
}

TrackHubTrack.prototype.bigwigStyles = function() {
    var typeToks = this.type.split(/\s+/);
    var min, max;
    if (typeToks.length >= 3) {
        min = 1.0 * typeToks[1];
        max = 1.0 * typeToks[2];
    }

    var height;
    if (this.maxHeightPixels) {
        var mhpToks = this.maxHeightPixels.split(/:/);
        if (mhpToks.length == 3) {
            height = mhpToks[1] | 0;
        } else {
            console.log('maxHeightPixels should be of the form max:default:min');
        }
    }

    var gtype = 'bars';
    if (this.graphTypeDefault) {
        gtype = this.graphTypeDefault;
    }

    var color = 'black';
    var altColor = null;
    if (this.color) {
        color = 'rgb(' + this.color + ')';
    }
    if (this.altColor) {
        altColor = 'rgb(' + this.altColor + ')';
    }

    var stylesheet = new DASStylesheet();
    var wigStyle = new DASStyle();
    if (gtype == 'points') {
        wigStyle.glyph = 'POINT';
    } else {
        wigStyle.glyph = 'HISTOGRAM';
    }

    if (altColor) {
        wigStyle.COLOR1 = color;
        wigStyle.COLOR2 = altColor;
    } else {
        wigStyle.BGCOLOR = color;
    }
    wigStyle.HEIGHT = height || 30;
    if (min || max) {
        wigStyle.MIN = min;
        wigStyle.MAX = max;
    }
    stylesheet.pushStyle({type: 'default'}, null, wigStyle);
    return stylesheet.styles;
}

TrackHubTrack.prototype.bigbedStyles = function() {
    var itemRgb = (''+this.get('itemRgb')).toLowerCase() == 'on';
    var visibility = this.get('visibility') || 'full';
    var color = this.get('color');
    if (color)
        color = 'rgb(' + color + ')';
    else
        color = 'blue';

    var stylesheet = new DASStylesheet();
    var wigStyle = new DASStyle();
    wigStyle.glyph = 'BOX';
    wigStyle.FGCOLOR = 'black';
    wigStyle.BGCOLOR = color;
    wigStyle.HEIGHT = (visibility == 'full' || visibility == 'pack') ? 12 : 8;
    wigStyle.BUMP = (visibility == 'full' || visibility == 'pack');
    wigStyle.LABEL = (visibility == 'full' || visibility == 'pack');
    wigStyle.ZINDEX = 20;
    if (itemRgb)
        wigStyle.BGITEM = true;

    var cbs = this.get('colorByStrand');
    if (cbs) {
        var cbsToks = cbs.split(/\s+/);

        var plus = shallowCopy(wigStyle);
        plus.BGCOLOR = 'rgb(' + cbsToks[0] + ')';
        stylesheet.pushStyle({type: 'bigwig', orientation: '+'}, null, plus);

        var minus = shallowCopy(wigStyle);
        minus.BGCOLOR = 'rgb(' + cbsToks[1] + ')';
        stylesheet.pushStyle({type: 'bigwig', orientation: '-'}, null, minus);
    } else {
        stylesheet.pushStyle({type: 'bigwig'}, null, wigStyle);
    }

    var tlStyle = new DASStyle();
    tlStyle.glyph = 'BOX';
    tlStyle.FGCOLOR = 'black';
    if (itemRgb)
        tlStyle.BGITEM = true;
    tlStyle.BGCOLOR = 'red'
    tlStyle.HEIGHT = 10;
    tlStyle.BUMP = true;
    tlStyle.ZINDEX = 20;
    stylesheet.pushStyle({type: 'bb-translation'}, null, tlStyle);

    var tsStyle = new DASStyle();
    tsStyle.glyph = 'BOX';
    tsStyle.FGCOLOR = 'black';
    tsStyle.BGCOLOR = 'white';
    tsStyle.HEIGHT = 10;
    tsStyle.ZINDEX = 10;
    tsStyle.BUMP = true;
    tsStyle.LABEL = true;
    stylesheet.pushStyle({type: 'bb-transcript'}, null, tsStyle);

/*
    var densStyle = new DASStyle();
    densStyle.glyph = 'HISTOGRAM';
    densStyle.COLOR1 = 'white';
    densStyle.COLOR2 = 'black';
    densStyle.HEIGHT=30;
    stylesheet.pushStyle({type: 'density'}, null, densStyle); */

    return stylesheet.styles;
}

function THUB_COMPARE(g, h) {
    if (g.priority && h.priority) {
        return (1.0 * g.priority) - (1.0 * h.priority)
    } else if (g.priority) {
        return 1;
    } else if (h.priority) {
        return -1;
    } else {
        return g.shortLabel.localeCompare(h.shortLabel);
    }
}

if (typeof(module) !== 'undefined') {
    module.exports = {
        connectTrackHub: connectTrackHub,
        THUB_COMPARE: THUB_COMPARE
    };
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2008-2009  Arne Goedeke

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
/**
 * Some helpful utility functions.
 * @namespace
 */
UTIL = new Object();
/**
 * Flexible RegExp-based replace function. Calls a callback for every match and replaced it by the returned string.
 * @param {Object} reg RegExp Object to be used.
 * @param {String} s String to perform the replace on.
 * @param {Function} cb Callback to be called for every match. Parameters to the callback will be the result returned by the call to RegExp.exec and possible extra arguments that were passed to replace.
 * @returns The resulting string.
 */
UTIL.replace = function(reg, s, cb) {
	var res;
	var last = 0;
	var ret = "";
	var extra;

	if (arguments.length > 3) {
		extra = new Array();
		for (var i = 3; i < arguments.length; i++) extra[i-3] = arguments[i];
	}

	while (null != (res = reg.exec(s))) {
		ret += s.substr(last, reg.lastIndex - res[0].length - last);
		ret += (extra ? cb.apply(null, [res].concat(extra)) : cb(res));
		last = reg.lastIndex;
	}

	if (last < s.length) {
		ret += s.substr(last);
	}

	return ret;
};
UTIL.split_replace = function(reg, s, cb) {
	var res;
	var last = 0;
	var ret = new Array();
	var extra;

	if (arguments.length > 3) {
		extra = new Array();
		for (var i = 3; i < arguments.length; i++) extra[i-3] = arguments[i];
	}

	while (null != (res = reg.exec(s))) {
		ret.push(s.substr(last, reg.lastIndex - res[0].length - last));
		ret.push(extra ? cb.apply(null, [res].concat(extra)) : cb(res));
		last = reg.lastIndex;
	}

	if (last < s.length) {
		ret.push(s.substr(last));
	}

	return ret;
};
UTIL.has_prefix = function(s, n) {
	if (s.length < n.length) return false;
	return (n == s.substr(0, n.length));
};
UTIL.has_suffix = function(s, n) {
	if (s.length < n.length) return false;
	return (n == s.substr(s.length-n.length, n.length));
};
UTIL.search_array = function(a, n) {
	for (var i = 0; i < a.length; i++) {
		if (n == a[i]) return i;
	}

	return -1;
};
UTIL.replaceClass = function(o, cl1, cl2) {
	var classes = o.className.length ? o.className.split(' ') : [];
	var i = UTIL.search_array(classes, cl1);
	var j = UTIL.search_array(classes, cl2);

	if (i == -1 && j == -1) {
		if (cl2) classes.push(cl2);
	} else if (i == -1) {
		return;
	} else if (j == -1 && cl2) {
		classes[i] = cl2;
	} else {
		classes.splice(i, 1);
	}
	o.className = classes.join(" ");
};
UTIL.addClass = function(o, cl) {
	var classes = o.className.length ? o.className.split(' ') : [];
	var i = UTIL.search_array(classes, cl);

	if (i == -1) {
		classes.push(cl);
		o.className = classes.join(" ");
	}
};
UTIL.hasClass = function(o, cl) {
	var classes = o.className.split(' ');
	return (-1 != UTIL.search_array(classes, cl));
};
UTIL.removeClass = function(o, cl) {
	UTIL.replaceClass(o, cl);
};
UTIL.url_escape = function(s) {
    	s = escape(s);
	s.replace(/\+/g, "%2B");
	s.replace(/\//g, "%2F");
	return s;
};
UTIL.make_url = function(url, vars) {
    	var list = [];
	var key;
	if (vars) for (key in vars) if (vars.haѕOwnProperty(key)) {
	    list.push(key + "=" + UTIL.url_escape(vars[key]));
	} else return url;

	return url + "?" + list.join("&");
};
<commit_msg>weirdo char<commit_after>/*
Copyright (C) 2008-2009  Arne Goedeke

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
/**
 * Some helpful utility functions.
 * @namespace
 */
UTIL = new Object();
/**
 * Flexible RegExp-based replace function. Calls a callback for every match and replaced it by the returned string.
 * @param {Object} reg RegExp Object to be used.
 * @param {String} s String to perform the replace on.
 * @param {Function} cb Callback to be called for every match. Parameters to the callback will be the result returned by the call to RegExp.exec and possible extra arguments that were passed to replace.
 * @returns The resulting string.
 */
UTIL.replace = function(reg, s, cb) {
	var res;
	var last = 0;
	var ret = "";
	var extra;

	if (arguments.length > 3) {
		extra = new Array();
		for (var i = 3; i < arguments.length; i++) extra[i-3] = arguments[i];
	}

	while (null != (res = reg.exec(s))) {
		ret += s.substr(last, reg.lastIndex - res[0].length - last);
		ret += (extra ? cb.apply(null, [res].concat(extra)) : cb(res));
		last = reg.lastIndex;
	}

	if (last < s.length) {
		ret += s.substr(last);
	}

	return ret;
};
UTIL.split_replace = function(reg, s, cb) {
	var res;
	var last = 0;
	var ret = new Array();
	var extra;

	if (arguments.length > 3) {
		extra = new Array();
		for (var i = 3; i < arguments.length; i++) extra[i-3] = arguments[i];
	}

	while (null != (res = reg.exec(s))) {
		ret.push(s.substr(last, reg.lastIndex - res[0].length - last));
		ret.push(extra ? cb.apply(null, [res].concat(extra)) : cb(res));
		last = reg.lastIndex;
	}

	if (last < s.length) {
		ret.push(s.substr(last));
	}

	return ret;
};
UTIL.has_prefix = function(s, n) {
	if (s.length < n.length) return false;
	return (n == s.substr(0, n.length));
};
UTIL.has_suffix = function(s, n) {
	if (s.length < n.length) return false;
	return (n == s.substr(s.length-n.length, n.length));
};
UTIL.search_array = function(a, n) {
	for (var i = 0; i < a.length; i++) {
		if (n == a[i]) return i;
	}

	return -1;
};
UTIL.replaceClass = function(o, cl1, cl2) {
	var classes = o.className.length ? o.className.split(' ') : [];
	var i = UTIL.search_array(classes, cl1);
	var j = UTIL.search_array(classes, cl2);

	if (i == -1 && j == -1) {
		if (cl2) classes.push(cl2);
	} else if (i == -1) {
		return;
	} else if (j == -1 && cl2) {
		classes[i] = cl2;
	} else {
		classes.splice(i, 1);
	}
	o.className = classes.join(" ");
};
UTIL.addClass = function(o, cl) {
	var classes = o.className.length ? o.className.split(' ') : [];
	var i = UTIL.search_array(classes, cl);

	if (i == -1) {
		classes.push(cl);
		o.className = classes.join(" ");
	}
};
UTIL.hasClass = function(o, cl) {
	var classes = o.className.split(' ');
	return (-1 != UTIL.search_array(classes, cl));
};
UTIL.removeClass = function(o, cl) {
	UTIL.replaceClass(o, cl);
};
UTIL.url_escape = function(s) {
    	s = escape(s);
	s.replace(/\+/g, "%2B");
	s.replace(/\//g, "%2F");
	return s;
};
UTIL.make_url = function(url, vars) {
    	var list = [];
	var key;
	if (vars) for (key in vars) if (vars.hasOwnProperty(key)) {
	    list.push(key + "=" + UTIL.url_escape(vars[key]));
	} else return url;

	return url + "?" + list.join("&");
};
<|endoftext|>


<commit_before>/*
 * Laconica - a distributed open-source microblogging tool
 * Copyright (C) 2008, Controlez-Vous, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function(){
        // count character on keyup
        function counter(event){
            var maxLength     = 140;
            var currentLength = $("#status_textarea").val().length;
            var remaining = maxLength - currentLength;
            var counter   = $("#counter");
            counter.text(remaining);

            if (remaining <= 0) {
                counter.attr("class", "toomuch");
            } else {
                counter.attr("class", "");
            }
        }

        function submitonreturn(event) {
             if (event.keyCode == 13) {
                  $("#status_form").submit();
                  event.preventDefault();
                  event.stopPropagation();
                  return false;
             }
             return true;
        }

        if ($("#status_textarea").length) {
             $("#status_textarea").bind("keyup", counter);
             $("#status_textarea").bind("keydown", submitonreturn);

            // run once in case there's something in there
            counter();

             // set the focus
             $("#status_textarea").focus();
        }

     var options = {dataType: 'xml',
               success: function(xml) {
                    // replace the form with what's returned
               };

     $("form.favor").ajaxForm(options);
     $("form.disfavor").ajaxForm(options);
});

function doreply(nick) {
     rgx_username = /^[0-9a-zA-Z\-_.]*$/;
     if (nick.match(rgx_username)) {
          replyto = "@" + nick + " ";
          if ($("#status_textarea").length) {
               $("#status_textarea").val(replyto);
               $("#status_textarea").focus();
			   return false;
		  }
     }
     return true;
}

<commit_msg>missed a closing curly<commit_after>/*
 * Laconica - a distributed open-source microblogging tool
 * Copyright (C) 2008, Controlez-Vous, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

$(document).ready(function(){
        // count character on keyup
        function counter(event){
            var maxLength     = 140;
            var currentLength = $("#status_textarea").val().length;
            var remaining = maxLength - currentLength;
            var counter   = $("#counter");
            counter.text(remaining);

            if (remaining <= 0) {
                counter.attr("class", "toomuch");
            } else {
                counter.attr("class", "");
            }
        }

        function submitonreturn(event) {
             if (event.keyCode == 13) {
                  $("#status_form").submit();
                  event.preventDefault();
                  event.stopPropagation();
                  return false;
             }
             return true;
        }

        if ($("#status_textarea").length) {
             $("#status_textarea").bind("keyup", counter);
             $("#status_textarea").bind("keydown", submitonreturn);

            // run once in case there's something in there
            counter();

             // set the focus
             $("#status_textarea").focus();
        }

     var options = {dataType: 'xml',
               success: function(xml) {
                    // replace the form with what's returned
               }};

     $("form.favor").ajaxForm(options);
     $("form.disfavor").ajaxForm(options);
});

function doreply(nick) {
     rgx_username = /^[0-9a-zA-Z\-_.]*$/;
     if (nick.match(rgx_username)) {
          replyto = "@" + nick + " ";
          if ($("#status_textarea").length) {
               $("#status_textarea").val(replyto);
               $("#status_textarea").focus();
			   return false;
		  }
     }
     return true;
}

<|endoftext|>


<commit_before>'use strict';

var React = require('react-native');

var {
  ActivityIndicatorIOS,
  AppRegistry,
  Image,
  ListView,
  StyleSheet,
  Text,
  TouchableHighlight,
  View,
  Navigator,
  PixelRatio,
} = React;

var AppReloader = require('NativeModules').AppReloader;
var Api = require("../Api/Core");
var NoResults = require('../Components/NoResults');
var generateAppURL = require('../Utilities/generateAppURL');

var AppList = React.createClass({
  getInitialState() {
    return {
      page: 1,
      data: [],
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  },

  componentDidMount() {
    this.fetchApps();
  },

  componentDidUpdate: function(prevProps) {
    if (prevProps.url !== this.props.url) {
      this.setState({ loaded: false });
      this.fetchApps();
    }
  },

  fetchApps() {
    if (!this.requestInFlight) {
      this.requestInFlight = true;
      var separator = this.props.url.indexOf('?') !== -1 ?
        '&' :
        '?';
      var url = `${this.props.url}${separator}page=${this.state.page}`;

      Api.get(url)
        .then((data) => {
          if (data.error) {
            // TODO: check for 401 Unauthorized
            if (Navigator.getContext(this)) {
              Navigator.getContext(this).replace({id: "login", error: data.error});
            }

            return;
          }

          var page = data.length ?
            this.state.page + 1:
            this.state.page;

          var newData = this.state.data.concat(data);
          this.setState({
            data: newData,
            page,
            dataSource: this.state.dataSource.cloneWithRows(newData),
            loaded: true
          });
          this.requestInFlight = false;
        })
        .done();

    }
  },

  renderAppList() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderApp}
          onEndReached={this.fetchApps}
          style={styles.listView} />
      </View>
    );
  },

  renderCreator(app) {
    if (app.creator && !this.props.hideCreator) {
      var avatarUrl = 'https://rnplay.org/' + app.creator.avatar_url + "?v=1";

      return (
        <View style={styles.creator}>
          <Image style={styles.avatar} resizeMode="contain" source={{uri: avatarUrl}} />
          <Text style={styles.username} numberOfLines={1}>{app.creator.username || 'guest'}</Text>
        </View>
      )
    }
  },

  renderApp(app) {
    return (
      <View style={{marginBottom: 15}}>
        <TouchableHighlight underlayColor="#F5F5F5" onPress={() => this.selectApp(app)}>
          <View style={styles.appContainer}>
            <View style={styles.appTextDescription}>
              <Text style={styles.appTitle} numberOfLines={1}>{app.name || app.module_name}</Text>
              <View style={styles.targetBuild}>
                <Text style={styles.targetBuildText}>Targets <Text>{app.build_name}</Text></Text>
              </View>

              <View style={styles.viewCount}>
                <Text style={styles.viewCountText}>{app.view_count} <Text>views</Text></Text>
              </View>
            </View>

            { this.renderCreator(app) }
          </View>
        </TouchableHighlight>
      </View>
    );
  },

  selectApp(app) {
    AppReloader.reloadAppWithURLString(generateAppURL(app), app.module_name);
  },

  renderLoading() {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicatorIOS color={'#712FA9'} style={styles.spinner} size="large" />
      </View>
    );
  },

  renderNoResults() {
    return(
      <NoResults />
    );
  },

  render() {
    if (!this.state.loaded) {
      return this.renderLoading();
    } else {
      if (this.state.dataSource.getRowCount() > 0) {
        return this.renderAppList();
      }
      else {
        return this.renderNoResults();
      }
    }
  }
});

var deviceWidth = require('Dimensions').get('window').width;

var styles = StyleSheet.create({
  listView: {
    marginTop: -9,
    paddingTop: 0,
  },
  container: {
    flex: 1,
    backgroundColor: 'transparent',
  },
  loadingContainer: {
    flex: 1,
    paddingBottom: 80,
  },
  appContainer: {
    marginTop: -18,
    flexDirection: 'row',
    flex: 1,
    paddingBottom: 8,
    paddingTop: 15,
    borderBottomWidth: 3 / PixelRatio.get(),
    borderBottomColor: "#eee"
  },
  appTextDescription: {
    flexDirection: 'column',
    marginLeft: 10,
  },
  targetBuild: {
    opacity: 0.4,
    paddingBottom: 2,
  },
  viewCount: {
    opacity: 0.4,
  },
  targetBuildText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },
  viewCountText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },
  creator: {
    position: 'absolute',
    right: 0,
    width: 70,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
  },
  username: {
    fontSize: 11,
    opacity: 0.4,
    width: 50,
    textAlign: 'center',
  },
  avatar: {
    width: 30,
    height: 30,
    marginBottom: 5,
    borderRadius: 15,
    backgroundColor: "#000"
  },
  cancelButton: {
    color: '#fff',
    flex: 1,
    fontSize: 25,
    marginLeft: 20
  },
  appTitle: {
    fontSize: 18,
    fontFamily: 'Avenir Next',
    color: '#712FA9',
    width: deviceWidth - 70,
    flex: 1,
  },
  spinner: {
    flex: 1,
  },
});

module.exports = AppList;
<commit_msg>Added AlertView<commit_after>'use strict';

var React = require('react-native');

var {
  ActivityIndicatorIOS,
  AppRegistry,
  AlertIOS,
  Image,
  ListView,
  StyleSheet,
  Text,
  TouchableHighlight,
  View,
  Navigator,
  PixelRatio,
} = React;

var AppReloader = require('NativeModules').AppReloader;
var Api = require("../Api/Core");
var NoResults = require('../Components/NoResults');
var generateAppURL = require('../Utilities/generateAppURL');

var AppList = React.createClass({
  getInitialState() {
    return {
      page: 1,
      data: [],
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
    };
  },

  componentDidMount() {
    this.fetchApps();
  },

  componentDidUpdate: function(prevProps) {
    if (prevProps.url !== this.props.url) {
      this.setState({ loaded: false });
      this.fetchApps();
    }
  },

  fetchApps() {
    if (!this.requestInFlight) {
      this.setState({
        hasError: false
      });

      this.requestInFlight = true;
      var separator = this.props.url.indexOf('?') !== -1 ?
        '&' :
        '?';
      var url = `${this.props.url}${separator}page=${this.state.page}`;

      Api.get(url)
        .then((data) => {
          if (data.error) {
            // TODO: check for 401 Unauthorized
            if (Navigator.getContext(this)) {
              Navigator.getContext(this).replace({id: "login", error: data.error});
            }

            return;
          }

          var page = data.length ?
            this.state.page + 1:
            this.state.page;

          var newData = this.state.data.concat(data);
          this.setState({
            data: newData,
            page,
            dataSource: this.state.dataSource.cloneWithRows(newData),
            loaded: true,
            hasError: false
          });
        })
        .catch((e) => {
          AlertIOS.alert(
            'Aww :(',
            e.message,
            [
              {text: 'ok'},
              {text: 'retry', onPress: () => this.fetchApps()},
            ]
          );

          this.setState({
            hasError: true
          });

        })
        .finally(() => {
          this.requestInFlight = false;
        })
        .done();

    }
  },

  renderAppList() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this.renderApp}
          onEndReached={this.fetchApps}
          style={styles.listView} />
      </View>
    );
  },

  renderCreator(app) {
    if (app.creator && !this.props.hideCreator) {
      var avatarUrl = 'https://rnplay.org/' + app.creator.avatar_url + "?v=1";

      return (
        <View style={styles.creator}>
          <Image style={styles.avatar} resizeMode="contain" source={{uri: avatarUrl}} />
          <Text style={styles.username} numberOfLines={1}>{app.creator.username || 'guest'}</Text>
        </View>
      )
    }
  },

  renderApp(app) {
    return (
      <View style={{marginBottom: 15}}>
        <TouchableHighlight underlayColor="#F5F5F5" onPress={() => this.selectApp(app)}>
          <View style={styles.appContainer}>
            <View style={styles.appTextDescription}>
              <Text style={styles.appTitle} numberOfLines={1}>{app.name || app.module_name}</Text>
              <View style={styles.targetBuild}>
                <Text style={styles.targetBuildText}>Targets <Text>{app.build_name}</Text></Text>
              </View>

              <View style={styles.viewCount}>
                <Text style={styles.viewCountText}>{app.view_count} <Text>views</Text></Text>
              </View>
            </View>

            { this.renderCreator(app) }
          </View>
        </TouchableHighlight>
      </View>
    );
  },

  selectApp(app) {
    AppReloader.reloadAppWithURLString(generateAppURL(app), app.module_name);
  },

  renderLoading() {
    return (
      <View style={styles.loadingContainer}>
        <ActivityIndicatorIOS color={'#712FA9'} style={styles.spinner} size="large" />
      </View>
    );
  },

  renderNoResults() {
    return(
      <NoResults />
    );
  },

  render() {
    if (!this.state.loaded && !this.state.hasError) {
      return this.renderLoading();
    } else {
      if (this.state.dataSource.getRowCount() > 0) {
        return this.renderAppList();
      }
      else {
        return this.renderNoResults();
      }
    }
  }
});

var deviceWidth = require('Dimensions').get('window').width;

var styles = StyleSheet.create({
  listView: {
    marginTop: -9,
    paddingTop: 0,
  },
  container: {
    flex: 1,
    backgroundColor: 'transparent',
  },
  loadingContainer: {
    flex: 1,
    paddingBottom: 80,
  },
  appContainer: {
    marginTop: -18,
    flexDirection: 'row',
    flex: 1,
    paddingBottom: 8,
    paddingTop: 15,
    borderBottomWidth: 3 / PixelRatio.get(),
    borderBottomColor: "#eee"
  },
  appTextDescription: {
    flexDirection: 'column',
    marginLeft: 10,
  },
  targetBuild: {
    opacity: 0.4,
    paddingBottom: 2,
  },
  viewCount: {
    opacity: 0.4,
  },
  targetBuildText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },
  viewCountText: {
    fontSize: 12,
    fontFamily: 'Avenir Next',
  },
  creator: {
    position: 'absolute',
    right: 0,
    width: 70,
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
  },
  username: {
    fontSize: 11,
    opacity: 0.4,
    width: 50,
    textAlign: 'center',
  },
  avatar: {
    width: 30,
    height: 30,
    marginBottom: 5,
    borderRadius: 15,
    backgroundColor: "#000"
  },
  cancelButton: {
    color: '#fff',
    flex: 1,
    fontSize: 25,
    marginLeft: 20
  },
  appTitle: {
    fontSize: 18,
    fontFamily: 'Avenir Next',
    color: '#712FA9',
    width: deviceWidth - 70,
    flex: 1,
  },
  spinner: {
    flex: 1,
  },
});

module.exports = AppList;
<|endoftext|>


<commit_before>/*
 * xWeb (v1.0) by EvolSoft
 * Developer: EvolSoft
 * Website: http://www.evolsoft.tk
 * Date: 20/03/2015 12:30 AM (UTC)
 * Copyright & License: (C) 2015 EvolSoft
 * Licensed under MIT (https://github.com/EvolSoft/xWeb/blob/master/LICENSE)
 */

//**** Alerts ****//

$(document).on("click", ".close", function() {
	if($(this).parent().hasClass("alert")){
		$(this).parent().remove();
	}
});

//**** Menus ****//

$(document).on("mousedown", function() {
	if($(event.target).parent().hasClass("open") && $(event.target).parent().hasClass("menu-group")){
		$(".menu-group").removeClass("open"); //Closes all other menus
	}else if($(event.target).parent().hasClass("menu-group") && $(event.target).attr("openmenu") == ""){
		$(".menu-group").removeClass("open"); //Closes all other menus
		$(event.target).parent().toggleClass("open");
	}else{ //Close all opened menus
		$(".menu-group").removeClass("open"); //Closes all other menus
	}
});

//**** Modals ****//

/**
 * Toggles a modal
 *
 * @param target The target modal
 */
function toggleModal(target) {
	if($(target).hasClass("modal")){
	    $(target).toggleClass("modal-open");
	}
}

$(document).on("click", ".close", function() {
	if($(this).parent().parent().hasClass("modal")){ //.modal > .modal-window > .close
		$(this).parent().parent().removeClass("modal-open");
	}
});

$(document).on("click", ".modal-background", function() {
	if($(this).parent().hasClass("modal")){ //.modal > .modal-background
	    if($(this).parent().attr("static") == "false" || typeof $(this).parent().attr("static") === typeof undefined){
		    $(this).parent().removeClass("modal-open");
		}
	}
});
<commit_msg>Update xWeb.js<commit_after>/*
 * xWeb (v1.0) by EvolSoft
 * Developer: EvolSoft
 * Website: http://www.evolsoft.tk
 * Date: 22/03/2015 10:09 AM (UTC)
 * Copyright & License: (C) 2015 EvolSoft
 * Licensed under MIT (https://github.com/EvolSoft/xWeb/blob/master/LICENSE)
 */

//**** xWeb Core ****//

/** @var string XWEB_VERSION **/
var XWEB_VERSION = "1.0";

/**
 * Changes the Webpage title
 *
 * @param title The new title
 */
function changeTitle(title){
	if(checkElement("title")){
		$("title").text(title);
	}else{
		$("head").append("<title>" + title + "</title>");
	}
}

/**
 * Checks if a element exists
 *
 * @param element The element tag, class, name or id to check
 *
 * @return int
 */
function checkElement(element){
	return $(element).length;
}

/**
 * Get xWeb version
 *
 * @return string xWebVersion
 */
function getVersion(){
	return XWEB_VERSION;
}

//**** Alerts ****//

$(document).on("click", ".close", function() {
	if($(this).parent().hasClass("alert")){
		$(this).parent().remove();
	}
});

//**** Menus ****//

$(document).on("mousedown", function() {
	if($(event.target).parent().hasClass("open") && $(event.target).parent().hasClass("menu-group")){
		$(".menu-group").removeClass("open"); //Closes all other menus
	}else if($(event.target).parent().hasClass("menu-group") && $(event.target).attr("openmenu") == ""){
		$(".menu-group").removeClass("open"); //Closes all other menus
		$(event.target).parent().toggleClass("open");
	}else{ //Close all opened menus
		$(".menu-group").removeClass("open"); //Closes all other menus
	}
});

//**** Modals ****//

/**
 * Toggles a modal
 *
 * @param target The target modal
 */
function toggleModal(target) {
	if($(target).hasClass("modal")){
	    $(target).toggleClass("modal-open");
	}
}

$(document).on("click", ".close", function() {
	if($(this).parent().parent().hasClass("modal")){ //.modal > .modal-window > .close
		$(this).parent().parent().removeClass("modal-open");
	}
});

$(document).on("click", ".modal-background", function() {
	if($(this).parent().hasClass("modal")){ //.modal > .modal-background
	    if($(this).parent().attr("static") == "false" || typeof $(this).parent().attr("static") === typeof undefined){
		    $(this).parent().removeClass("modal-open");
		}
	}
});
<|endoftext|>


<commit_before>/*
   All emon_widgets code is released under the GNU General Public License v3.
   See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org
    Author: Trystan Lea: trystan.lea@googlemail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */
function addOption(widget, optionKey, optionType, optionName, optionHint, optionData){
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function cylinder_widgetlist()
{
  var widgets = {
    "cylinder":
    {
      "offsetx":-80,"offsety":-165,"width":160,"height":330,
      "menu":"Widgets",
      "options":[],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  }
  var decimalsDropBoxOptions = [
        [-1,   "Automatic"],
        [0,    "0"],
        [1,    "1"],
        [2,    "2"],
        [3,    "3"],
        [4,    "4"],
        [5,    "5"],
        [6,    "6"]
    ];
  var unitDropBoxOptions = [
          [0, "Back"],
          [1, "Front"],
          [2, "No display"]
        ];
    var tempDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
        [0,    "ºC"],
        [1,    "ºF"]
    ];
  addOption(widgets["cylinder"], "topfeedid",    "feedid",  _Tr("Feed Top"),      _Tr("Top feed value"),                []);
  addOption(widgets["cylinder"], "botfeedid",    "feedid",  _Tr("Feed Bottom"),   _Tr("Bottom feed value"),             []);
  addOption(widgets["cylinder"], "temptype",     "dropbox", _Tr("Temp unit"),     _Tr("Units of the choosen temp feed"),tempDropBoxOptions);
  addOption(widgets["cylinder"], "decimals",     "dropbox", _Tr("Decimals"),      _Tr("Decimals to show"),               decimalsDropBoxOptions);
  addOption(widgets["cylinder"], "unitend",      "dropbox", _Tr("Unit position"), _Tr("Where should the unit be shown"), unitDropBoxOptions);
  return widgets;
}

  function get_color(temperature,temptype)
  {
    if (temptype === "1") {
    temperature = (temperature - 32) * (5 / 9); // Fahrenheit to Celsius
    }
    var red = (32+(temperature*3.95)).toFixed(0);
    var green = 40;
    var blue = (191-(temperature*3.65)).toFixed(0);
    return "rgb("+red+","+green+","+blue+")";
  }

  function draw_cylinder(ctx,cyl_bot,cyl_top,width,height,temptype,unitend,decimals)
  {

    // console.log("Draw cylinder");
    if (!ctx) console.log("No CTX");
    if (!ctx) return;

    var midx = width / 2;
    var cyl_width = width - 8;
    var cyl_left = midx - (cyl_width/2);
    var top_pos = midx;
    var bot_pos = height - 4 - (cyl_width/2);

    ctx.clearRect(0,0,width,500);
    cyl_top = cyl_top || 0;
    cyl_bot = cyl_bot || 0;
    ctx.strokeStyle = "#fff";
    ctx.lineWidth = 8;

    ctx.fillStyle = get_color(cyl_top,temptype);
    ctx.beginPath();
    ctx.arc(midx,top_pos,cyl_width/2,Math.PI,0,false);
    ctx.closePath();
    ctx.fill();

    var gradient = ctx.createLinearGradient(0, top_pos, 0, bot_pos);
    gradient.addColorStop(0, get_color(cyl_top,temptype));
    gradient.addColorStop(1, get_color(cyl_bot,temptype));
    ctx.fillStyle = gradient;
    ctx.fillRect(cyl_left, bot_pos, cyl_width, top_pos-bot_pos);

    ctx.fillStyle = get_color(cyl_bot,temptype);
    ctx.beginPath();

    ctx.arc(midx,bot_pos,cyl_width/2,0,Math.PI,false);
    ctx.closePath();
    ctx.fill();

    ctx.beginPath();
    ctx.arc(midx,top_pos,cyl_width/2,Math.PI,0,false);
    ctx.arc(midx,bot_pos,cyl_width/2,0,Math.PI,false);

    ctx.closePath();
    ctx.stroke();

    ctx.fillStyle = "#fff";
    ctx.textAlign    = "center";
    ctx.font = "bold "+((width/168)*30)+"px arial";

    if (isNaN(cyl_top)){
    cyl_top = 0;}
    else if(Number(decimals)>=0){ //specified decimals
    cyl_top = cyl_top.toFixed(decimals);}
    else { //automatic decimals
     if (cyl_top>=100){
     cyl_top = cyl_top.toFixed(0);}
     else if (cyl_top>=10){
     cyl_top = cyl_top.toFixed(1);}
     else if (cyl_top<=-100){
     cyl_top = cyl_top.toFixed(0);}
     else if (cyl_top<=-10){
     cyl_top = cyl_top.toFixed(1);}
     else{
     cyl_top = cyl_top.toFixed(2);}

     cyl_top = parseFloat(cyl_top);
    }

    if (isNaN(cyl_bot)){
    cyl_bot = 0;}
    else if(Number(decimals)>=0){ //specified decimals
    cyl_bot = cyl_bot.toFixed(decimals);}
    else { //automatic decimals
     if (cyl_bot>=100){
     cyl_bot = cyl_bot.toFixed(0);}
     else if (cyl_bot>=10){
     cyl_bot = cyl_bot.toFixed(1);}
     else if (cyl_bot<=-100){
     cyl_bot = cyl_bot.toFixed(0);}
     else if (cyl_bot<=-10){
     cyl_bot = cyl_bot.toFixed(1);}
     else{
     cyl_bot = cyl_bot.toFixed(2);}

     cyl_bot = parseFloat(cyl_bot);
    }
    var unit;
    if (temptype === "0") {
    unit = "ºC";
    } else {
    unit = "ºF";
    }
    if (unitend ==="0"){
      ctx.fillText(cyl_top+unit,midx,top_pos);
      ctx.fillText(cyl_bot+unit,midx,bot_pos+15);}
    if (unitend ==="1"){
      ctx.fillText(unit+cyl_top,midx,top_pos);
      ctx.fillText(unit+cyl_bot,midx,bot_pos+15);}
    if (unitend ==="2"){
      ctx.fillText(cyl_top,midx,top_pos);
      ctx.fillText(cyl_bot,midx,bot_pos+15);}
  }

function cylinder_draw()
{
  $(".cylinder").each(function(index)
  {
    var feedid1 = $(this).attr("topfeedid");
    var feedid2 = $(this).attr("botfeedid");
    if ((associd[feedid1] === undefined) || (associd[feedid2] === undefined)) { console.log("Review config for feed id of " + $(this).attr("class")); return; }
    var cyl_top = associd[feedid1]["value"]*1;
    var cyl_bot = associd[feedid2]["value"]*1;
    var unitend = $(this).attr("unitend") || "0";
    var temptype= $(this).attr("temptype") || "0";
    var decimals = $(this).attr("decimals") || "-1";

    var id = "can-"+$(this).attr("id");
    draw_cylinder(widgetcanvas[id],cyl_bot,cyl_top,$(this).width(),$(this).height(),temptype,unitend,decimals);
  });
}

function cylinder_init()
{
  setup_widget_canvas("cylinder");
}

function cylinder_slowupdate()
{
  cylinder_draw();
}

function cylinder_fastupdate()
{
  cylinder_draw();
}
<commit_msg>Code optimization (#97)<commit_after>/*
   All emon_widgets code is released under the GNU General Public License v3.
   See COPYRIGHT.txt and LICENSE.txt.
    ---------------------------------------------------------------------
    Part of the OpenEnergyMonitor project:
    http://openenergymonitor.org
    Author: Trystan Lea: trystan.lea@googlemail.com
    If you have any questions please get in touch, try the forums here:
    http://openenergymonitor.org/emon/forum
 */
function addOption(widget, optionKey, optionType, optionName, optionHint, optionData){
  widget["options"    ].push(optionKey);
  widget["optionstype"].push(optionType);
  widget["optionsname"].push(optionName);
  widget["optionshint"].push(optionHint);
  widget["optionsdata"].push(optionData);
}

function cylinder_widgetlist()
{
  var widgets = {
    "cylinder":
    {
      "offsetx":-80,"offsety":-165,"width":160,"height":330,
      "menu":"Widgets",
      "options":[],
      "optionstype":[],
      "optionsname":[],
      "optionshint":[],
      "optionsdata":[]
    }
  }
  var decimalsDropBoxOptions = [
        [-1,   "Automatic"],
        [0,    "0"],
        [1,    "1"],
        [2,    "2"],
        [3,    "3"],
        [4,    "4"],
        [5,    "5"],
        [6,    "6"]
    ];
  var unitDropBoxOptions = [
          [0, "Back"],
          [1, "Front"],
          [2, "No display"]
        ];
    var tempDropBoxOptions = [        // Options for the type combobox. Each item is [typeID, "description"]
        [0,    "ºC"],
        [1,    "ºF"]
    ];
  addOption(widgets["cylinder"], "topfeedid",    "feedid",  _Tr("Feed Top"),      _Tr("Top feed value"),                []);
  addOption(widgets["cylinder"], "botfeedid",    "feedid",  _Tr("Feed Bottom"),   _Tr("Bottom feed value"),             []);
  addOption(widgets["cylinder"], "temptype",     "dropbox", _Tr("Temp unit"),     _Tr("Units of the choosen temp feed"),tempDropBoxOptions);
  addOption(widgets["cylinder"], "decimals",     "dropbox", _Tr("Decimals"),      _Tr("Decimals to show"),               decimalsDropBoxOptions);
  addOption(widgets["cylinder"], "unitend",      "dropbox", _Tr("Unit position"), _Tr("Where should the unit be shown"), unitDropBoxOptions);
  return widgets;
}

  function get_color(temperature,temptype)
  {
    if (temptype === "1") {
    temperature = (temperature - 32) * (5 / 9); // Fahrenheit to Celsius
    }
    var red = (32+(temperature*3.95)).toFixed(0);
    var green = 40;
    var blue = (191-(temperature*3.65)).toFixed(0);
    return "rgb("+red+","+green+","+blue+")";
  }

  function drawCylinder(ctx,cylBot,cylTop,width,height,temptype,unitend,decimals)
  {

    // console.log("Draw cylinder");
    if (!ctx) console.log("No CTX");
    if (!ctx) return;

    var midx = width / 2;
    var cylWidth = width - 8;
    var cylLeft = midx - (cylWidth/2);
    var topPos = midx;
    var botPos = height - 4 - (cylWidth/2);

    ctx.clearRect(0,0,width,500);
    cylTop = cylTop || 0;
    cylBot = cylBot || 0;
    ctx.strokeStyle = "#fff";
    ctx.lineWidth = 8;

    ctx.fillStyle = get_color(cylTop,temptype);
    ctx.beginPath();
    ctx.arc(midx,topPos,cylWidth/2,Math.PI,0,false);
    ctx.closePath();
    ctx.fill();

    var gradient = ctx.createLinearGradient(0, topPos, 0, botPos);
    gradient.addColorStop(0, get_color(cylTop,temptype));
    gradient.addColorStop(1, get_color(cylBot,temptype));
    ctx.fillStyle = gradient;
    ctx.fillRect(cylLeft, botPos, cylWidth, topPos-botPos);

    ctx.fillStyle = get_color(cylBot,temptype);
    ctx.beginPath();

    ctx.arc(midx,botPos,cylWidth/2,0,Math.PI,false);
    ctx.closePath();
    ctx.fill();

    ctx.beginPath();
    ctx.arc(midx,topPos,cylWidth/2,Math.PI,0,false);
    ctx.arc(midx,botPos,cylWidth/2,0,Math.PI,false);

    ctx.closePath();
    ctx.stroke();

    ctx.fillStyle = "#fff";
    ctx.textAlign    = "center";
    ctx.font = "bold "+((width/168)*30)+"px arial";

    if (isNaN(cylTop)){
    cylTop = 0;}
    else if(Number(decimals)>=0){ //specified decimals
    cylTop = cylTop.toFixed(decimals);}
    else { //automatic decimals
     if (cylTop>=100){
     cylTop = cylTop.toFixed(0);}
     else if (cylTop>=10){
     cylTop = cylTop.toFixed(1);}
     else if (cylTop<=-100){
     cylTop = cylTop.toFixed(0);}
     else if (cylTop<=-10){
     cylTop = cylTop.toFixed(1);}
     else{
     cylTop = cylTop.toFixed(2);}

     cylTop = parseFloat(cylTop);
    }

    if (isNaN(cylBot)){
    cylBot = 0;}
    else if(Number(decimals)>=0){ //specified decimals
    cylBot = cylBot.toFixed(decimals);}
    else { //automatic decimals
     if (cylBot>=100){
     cylBot = cylBot.toFixed(0);}
     else if (cylBot>=10){
     cylBot = cylBot.toFixed(1);}
     else if (cylBot<=-100){
     cylBot = cylBot.toFixed(0);}
     else if (cylBot<=-10){
     cylBot = cylBot.toFixed(1);}
     else{
     cylBot = cylBot.toFixed(2);}

     cylBot = parseFloat(cylBot);
    }
    var unit;
    if (temptype === "0") {
    unit = "ºC";
    } else {
    unit = "ºF";
    }
    if (unitend ==="0"){
      ctx.fillText(cylTop+unit,midx,topPos);
      ctx.fillText(cylBot+unit,midx,botPos+15);}
    if (unitend ==="1"){
      ctx.fillText(unit+cylTop,midx,topPos);
      ctx.fillText(unit+cylBot,midx,botPos+15);}
    if (unitend ==="2"){
      ctx.fillText(cylTop,midx,topPos);
      ctx.fillText(cylBot,midx,botPos+15);}
  }

function cylinder_draw()
{
  $(".cylinder").each(function(index)
  {
    var feedid1 = $(this).attr("topfeedid");
    var feedid2 = $(this).attr("botfeedid");
    if ((associd[feedid1] === undefined) || (associd[feedid2] === undefined)) { console.log("Review config for feed id of " + $(this).attr("class")); return; }
    var cylTop = associd[feedid1]["value"]*1;
    var cylBot = associd[feedid2]["value"]*1;
    var unitend = $(this).attr("unitend") || "0";
    var temptype= $(this).attr("temptype") || "0";
    var decimals = $(this).attr("decimals") || "-1";

    var id = "can-"+$(this).attr("id");
    drawCylinder(widgetcanvas[id],cylBot,cylTop,$(this).width(),$(this).height(),temptype,unitend,decimals);
  });
}

function cylinder_init()
{
  setup_widget_canvas("cylinder");
}

function cylinder_slowupdate()
{
  cylinder_draw();
}

function cylinder_fastupdate()
{
  cylinder_draw();
}
<|endoftext|>


<commit_before>describe('context', function() {

  var Observable = require('../../../../../lib/observable')

  function contextObservable() {
    var cnt = {
      total: 0
    }

    var a = new Observable({
      $key:'a',
      $trackInstances: true,
      //no on so on default no trackinstances
      b: {
        $on: {
          $change:function() {
            var key = this.$path[0]
            console.error('\nFIRE IT--->????', cnt[key], key, this._$context &&  this._$context.$path)
            cnt[key] = cnt[key] ? cnt[key]+1 : 1
            cnt.total++
          }
        }
      }
    })

    var b = new a.$Constructor({
      $key:'aInstance'
    })

    return {
      cnt: cnt,
      a: a,
      aInstance: b
    }
  }

  describe( 'emit on instance', function() {
    var test = contextObservable()
    //dit is resolve context shit
    test.aInstance.b.$emit('$change') // = 'b change'
    it( 'should fire once for "aInstance" context' , function() {
      expect( test.cnt.aInstance ).to.equal( 1 )
    })
    it( 'should fire once in total' , function() {
      expect( test.cnt.total ).to.equal( 1 )
    })
  })

  describe( 'set on instance', function() {
    var test = contextObservable()
    //dit is resolve context shit
    test.aInstance.b.$val = 'b change'
    it( 'should fire once for "aInstance" context' , function() {
      expect( test.cnt.aInstance ).to.equal( 1 )
    })
    it( 'should fire once in total' , function() {
      expect( test.cnt.total ).to.equal( 1 )
    })
  })

  describe( 'multiple instances', function() {
    var test = contextObservable()

    var c = new test.a.$Constructor({
      $key:'c'
    })

    test.a.b.$val = 'a change'
    it( 'should fire once for "a" context' , function() {
      expect( test.cnt.a ).to.equal( 1 )
    })
    it( 'should fire once for "aInstance" context' , function() {
      expect( test.cnt.aInstance ).to.equal( 1 )
    })
    it( 'should fire once for "c" context' , function() {
      expect( test.cnt.c ).to.equal( 1 )
    })
    it( 'should fire 3 times in total' , function() {
      expect( test.cnt.total ).to.equal( 3 )
    })
  })

  describe( 'multiple contexts and instances', function() {
    var test = contextObservable()

    test.c = new test.a.$Constructor({
      $key:'c'
    })
    test.d = new test.a.$Constructor({
      $key:'d'
    })

    it( 'creates "e" sets b and should fire once for "e" instance' , function() {
      test.d = new test.a.$Constructor({
        $key: 'e',
        b:'b' //you should not fire for original!
        //different field name makes it extra hard
      })
      expect( test.cnt.e ).to.equal( 1 )
    })

    it( 'sets a.b and should fire once for "a"' , function() {
      console.clear()
      test.a.b.$val = 'a'
      expect( test.cnt.a ).to.equal( 1 )
    })

    it( 'should fire once for "c"' , function() {
      //problem -- has instances never update contexts anymore
      expect( test.cnt.c ).to.equal( 1 )
    })

    it( 'should fire once for "d"' , function() {
      //problem -- has instances never update contexts anymore
      expect( test.cnt.d ).to.equal( 1 )
    })

    it( 'should fire once for "aInstance"' , function() {
      //problem -- has instances never update contexts anymore
      expect( test.cnt.aInstance ).to.equal( 1 )
    })

    it.skip( 'should not fire for "e"' , function() {
      //TOOD: disconnected cactch irrelevant change
      //now update for update on val a (althgouht its not shared)
      expect( test.cnt.e ).to.equal( 1 )
    })

  })

  describe( 'instance with a different parent', function() {
    var test = contextObservable()

    it( 'creates a new a.b (c) should fire once for c', function() {
      test.c = new test.a.b.$Constructor({
        $key:'c'
      })
      expect( test.cnt.c ).to.equal( 1 )
    })

    it( 'sets a.b, should fire once for "a"' , function() {
      test.a.b.$val = 'a change'
      expect( test.cnt.a ).to.equal( 1 )
    })

    it( 'should fire once for "aInstance"' , function() {
      //fire one too many for test.c (does context twice?)
      expect( test.cnt.aInstance ).to.equal( 1 )
    })

    it( 'should fire once for "c"' , function() {
      expect( test.cnt.c ).to.equal( 2 )
    })

    it( 'should fire 4 times in total' , function() {
      expect( test.cnt.total ).to.equal( 4 )
    })
  })

  describe( 'different instances, different contexts', function() {
    var test = contextObservable()

    it('creates a new "a.b" --> "c" should fire once for "c"', function() {
      console.clear()
      test.c = new test.a.b.$Constructor({
        $key:'c'
      })
      expect( test.cnt.c ).msg('c').to.equal( 1 )
    })

    it( 'creates a new "a" --> "d" (nest observable) should fire once for "d"', function() {
      test.d = new Observable({
        $key:'d',
        nest: new test.a.$Constructor() //really mested up case
      })
      expect( test.cnt.c ).msg('c').to.equal( 1 )
      expect( test.cnt.total ).msg('total').to.equal( 1 )
    })
    //now do stuff with d
  })

  //now the test for cusotm emits (hard case -- sets are relativly easy)
  //for this you need to do emits to contexts to contexts -- really strange
  //within my context search for instance but not if im emitted from context
  //maybe add a thing for that?

})
<commit_msg>added tests for different parent/contexts/instances<commit_after>describe('context', function() {

  var Observable = require('../../../../../lib/observable')

  function contextObservable() {
    var cnt = {
      total: 0
    }

    var a = new Observable({
      $key:'a',
      $trackInstances: true,
      //no on so on default no trackinstances
      b: {
        $on: {
          $change:function() {
            var key = this.$path[0]
            console.error('\nFIRE IT--->????', cnt[key], key, this._$context &&  this._$context.$path)
            cnt[key] = cnt[key] ? cnt[key]+1 : 1
            cnt.total++
          }
        }
      }
    })

    var b = new a.$Constructor({
      $key:'aInstance'
    })

    return {
      cnt: cnt,
      a: a,
      aInstance: b
    }
  }

  describe( 'emit on instance', function() {
    var test = contextObservable()
    //dit is resolve context shit
    test.aInstance.b.$emit('$change') // = 'b change'
    it( 'should fire once for "aInstance" context' , function() {
      expect( test.cnt.aInstance ).to.equal( 1 )
    })
    it( 'should fire once in total' , function() {
      expect( test.cnt.total ).to.equal( 1 )
    })
  })

  describe( 'set on instance', function() {
    var test = contextObservable()
    //dit is resolve context shit
    test.aInstance.b.$val = 'b change'
    it( 'should fire once for "aInstance" context' , function() {
      expect( test.cnt.aInstance ).to.equal( 1 )
    })
    it( 'should fire once in total' , function() {
      expect( test.cnt.total ).to.equal( 1 )
    })
  })

  describe( 'multiple instances', function() {
    var test = contextObservable()

    var c = new test.a.$Constructor({
      $key:'c'
    })

    test.a.b.$val = 'a change'
    it( 'should fire once for "a" context' , function() {
      expect( test.cnt.a ).to.equal( 1 )
    })
    it( 'should fire once for "aInstance" context' , function() {
      expect( test.cnt.aInstance ).to.equal( 1 )
    })
    it( 'should fire once for "c" context' , function() {
      expect( test.cnt.c ).to.equal( 1 )
    })
    it( 'should fire 3 times in total' , function() {
      expect( test.cnt.total ).to.equal( 3 )
    })
  })

  describe( 'multiple contexts and instances', function() {
    var test = contextObservable()

    test.c = new test.a.$Constructor({
      $key:'c'
    })
    test.d = new test.a.$Constructor({
      $key:'d'
    })

    it( 'creates "e" sets b and should fire once for "e" instance' , function() {
      test.d = new test.a.$Constructor({
        $key: 'e',
        b:'b' //you should not fire for original!
        //different field name makes it extra hard
      })
      expect( test.cnt.e ).to.equal( 1 )
    })

    it( 'sets a.b and should fire once for "a"' , function() {
      console.clear()
      test.a.b.$val = 'a'
      expect( test.cnt.a ).to.equal( 1 )
    })

    it( 'should fire once for "c"' , function() {
      //problem -- has instances never update contexts anymore
      expect( test.cnt.c ).to.equal( 1 )
    })

    it( 'should fire once for "d"' , function() {
      //problem -- has instances never update contexts anymore
      expect( test.cnt.d ).to.equal( 1 )
    })

    it( 'should fire once for "aInstance"' , function() {
      //problem -- has instances never update contexts anymore
      expect( test.cnt.aInstance ).to.equal( 1 )
    })

    it.skip( 'should not fire for "e"' , function() {
      //TOOD: disconnected cactch irrelevant change
      //now update for update on val a (althgouht its not shared)
      expect( test.cnt.e ).to.equal( 1 )
    })

  })

  describe( 'instance with a different parent', function() {
    var test = contextObservable()

    it( 'creates a new a.b (c) should fire once for c', function() {
      test.c = new test.a.b.$Constructor({
        $key:'c'
      })
      expect( test.cnt.c ).to.equal( 1 )
    })

    it( 'sets a.b, should fire once for "a"' , function() {
      test.a.b.$val = 'a change'
      expect( test.cnt.a ).to.equal( 1 )
    })

    it( 'should fire once for "aInstance"' , function() {
      //fire one too many for test.c (does context twice?)
      expect( test.cnt.aInstance ).to.equal( 1 )
    })

    it( 'should fire once for "c"' , function() {
      expect( test.cnt.c ).to.equal( 2 )
    })

    it( 'should fire 4 times in total' , function() {
      expect( test.cnt.total ).to.equal( 4 )
    })
  })

  describe( 'different instances, different contexts', function() {
    var test = contextObservable()

    it('creates a new "a.b" --> "c" should fire once for "c"', function() {
      console.clear()
      test.c = new test.a.b.$Constructor({
        $key:'c'
      })
      expect( test.cnt.c ).msg('c').to.equal( 1 )
    })

    it( 'creates a new "a" --> "d" (nest observable) should fire once for "d"', function() {
      test.d = new Observable({
        $key:'d',
        nest: new test.a.$Constructor() //really mested up case
      })
      expect( test.cnt.c ).msg('c').to.equal( 1 )
      expect( test.cnt.total ).msg('total').to.equal( 1 )
      expect( test.cnt.a ).msg('no update on a').to.be.not.ok
    })

    it( 'sets a, should fire for c, a, aIsntance, d', function() {
      test.a.b.$val = 'marcus'
      expect( test.cnt.a ).msg('a').to.equal( 1 )
      expect( test.cnt.c ).msg('c').to.equal( 2 )
      expect( test.cnt.d ).msg('d').to.equal( 1 )
      expect( test.cnt.aInstance ).msg('aInstance').to.equal( 1 )
    })
    //now do stuff with d
  })

  //now the test for cusotm emits (hard case -- sets are relativly easy)
  //for this you need to do emits to contexts to contexts -- really strange
  //within my context search for instance but not if im emitted from context
  //maybe add a thing for that?

})
<|endoftext|>


<commit_before>function merge(a, b) {
  var r = {};
  for (var i=0;i<a.length;++i) {
    r[a[i]] = b[i];
  }
  return r;
}

function Houkou(pattern) {
  if (!(this instanceof Houkou)) {
    return new Houkou(pattern);
  }

  this.parameters = (pattern.match(/:([a-z_]+)/gi) || []).map(function(p) { return p.substr(1); });
  this.pattern = new RegExp(("^" + pattern.replace(/:[a-z_]+?!\(|:[a-z_]+$/gi, "(.+?)").replace(/:[a-z_]+/gi, "") + "$").replace(/\//g, "\\/"));
  this.build = new Function("v", 'return "' + pattern.replace(/:([a-z_]+)(?:\(.*?\))?/gi, '" + v["$1"] + "') + '";');
}

Houkou.prototype.match = function(url) {
  var matches;

  if (matches = this.pattern.exec(url)) {
    matches.shift();
    return merge(this.parameters, matches);
  }
};

(typeof module === "object") && (module.exports = Houkou);
<commit_msg>better defined return type for failed matches<commit_after>function merge(a, b) {
  var r = {};
  for (var i=0;i<a.length;++i) {
    r[a[i]] = b[i];
  }
  return r;
}

function Houkou(pattern) {
  if (!(this instanceof Houkou)) {
    return new Houkou(pattern);
  }

  this.parameters = (pattern.match(/:([a-z_]+)/gi) || []).map(function(p) { return p.substr(1); });
  this.pattern = new RegExp(("^" + pattern.replace(/:[a-z_]+?!\(|:[a-z_]+$/gi, "(.+?)").replace(/:[a-z_]+/gi, "") + "$").replace(/\//g, "\\/"));
  this.build = new Function("v", 'return "' + pattern.replace(/:([a-z_]+)(?:\(.*?\))?/gi, '" + v["$1"] + "') + '";');
}

Houkou.prototype.match = function(url) {
  var matches;

  if (matches = this.pattern.exec(url)) {
    matches.shift();
    return merge(this.parameters, matches);
  } else {
    return false;
  }
};

(typeof module === "object") && (module.exports = Houkou);
<|endoftext|>


<commit_before>const co = require('co')
const agent = require('superagent-promise')(require('superagent'), Promise)
const fs = require('fs')
const _ = require('lodash')
const { throttledGetUsersInfo, stillAdmin, reduceAdminStatus } = require('./scanner')
const configBuffer = fs.readFileSync('./config.json')
const config = JSON.parse(configBuffer)
const SUBREDDIT_URL = 'https://www.reddit.com/r/'
const TOPIC_URL = 'https://www.reddit.com/comments/'
const SUBREDDIT_URL_SUFFIX = '.json'
const { mongoUsername, mongoPassword, mongoHost, mongoPath, mongoAdminCollection } = config.private[process.env.NODE_ENV]
const maxConnections = config.maxConnections || 3
const { emoji } = config
const { connect } = require('mongodb').MongoClient
const mongoUrl = `mongodb://${mongoUsername}:${mongoPassword}@${mongoHost}/${mongoPath}`

const requestSubreddit = (subreddit) => agent.get(`${SUBREDDIT_URL}${subreddit}${SUBREDDIT_URL_SUFFIX}`)
const requestTopic = (topic) => agent.get(`${TOPIC_URL}${topic}${SUBREDDIT_URL_SUFFIX}`)
const requestMods = (subreddit) => agent.get(`${SUBREDDIT_URL}${subreddit}${MOD_URL_SUFFIX}`)

const scanSubreddits = (subreddits, maxTopics, logger, replyCB, reactCB) => (
  co(function * () {
    const requestNextSubreddit = (index) => {
      if (subreddits[index]) {
        return scanSubreddit(subreddits[index], maxTopics, logger, replyCB, reactCB).then(requestNextSubreddit.bind(this, index+1))
      }
    }
    requestNextSubreddit(0)
  }).catch((err) => logger.error(err, replyCB))
)

const scanSubreddit = (subreddit, maxTopics, logger, replyCB, reactCB) => (
  co(function * () {
    logger.info(`start subreddit scan of ${subreddit}`)
    const data = yield requestSubreddit(subreddit)
    const posts = _.get(data, 'body.data.children', [])
    const promises = []
    for(let i = 0; i < maxTopics && i < posts.length; i++) {
      logger.info(`start scan of topic ${posts[i].data.id}`)
      promises.push(_scanTopic(posts[i].data.id, logger, replyCB, reactCB))
    }
    const topicAdmins = yield Promise.all(promises)
    const allAdmins = _.chain(topicAdmins)
      .flatten()
      .uniq()
      .map((admin) => admin.toLowerCase())
      .value()
    logger.info(`*overall*: ${allAdmins.join(', ')}`)
    updateDB(allAdmins, `/r/${subreddit}`, logger, replyCB, reactCB)
    return yield Promise.resolve(true)
  }).catch((err) => logger.error(err, replyCB))
)

const scanTopic = (topic, logger, replyCB, reactCB) => (
  co(function * () {
    logger.info(`start scan of topic ${topic}`)
    let admins = yield _scanTopic(topic, logger, replyCB, reactCB)
    admins = admins.map((admin) => admin.toLowerCase())
    updateDB(admins, `topic ${topic}`, logger, replyCB, reactCB)
  })
)

const _scanTopic = (topic, logger, replyCB, reactCB) => {
  return new Promise((resolve, reject) => {
    co(function * () {
      const data = yield requestTopic(topic)
      let admins = []
      const postAdminField = _.get(data, 'body.0.data.children.0.data.distinguished', null)
      if (postAdminField === 'admin') {
        const author = _.get(data, 'body.0.data.children.0.data.author', false)
        if (author) {
          admins.push(author)
        }
      }
      findAdmins(_.get(data, 'body.1.data.children', []), admins)
      admins = _.uniq(admins)
      console.log('admins', admins)
      if (admins.length > 0) {
        reactCB(emoji.adminFound)
      }
      logger.info(`*${topic}*: ${admins.join(', ')}`)
      resolve(admins)
    }).catch((err) => logger.error(err, replyCB))
  })
}

const findAdmins = (children, admins) => {
  for (let i = 0; i < children.length; i++) {
    const child = _.get(children, [i, 'data'])
    if (child.distinguished === 'admin') {
      admins.push(child.author)
    }
    const childChildren = _.get(child, 'replies.data.children', [])
    findAdmins(childChildren, admins)
  }
}

const updateDB = (foundAdmins, location, logger, replyCB, reactCB) => (
  co(function * () {
    logger.info('start update')
    const db = yield connect(mongoUrl)
    const collection = db.collection(mongoAdminCollection)
    const docs = yield collection.find({}).toArray()
    const [currentAdmins, exAdmins] = docs.reduce(reduceAdminStatus, [[],[]])
    const needsActionAdmins = foundAdmins.filter((current) => !currentAdmins.includes(current))
    logger.info(`*needs action*: ${needsActionAdmins.join(', ')}`)

    if (needsActionAdmins.length <= 0) {
      logger.info(`did not discover any new admins or exreddits in ${location}`)
      reactCB(emoji.end)
      return
    }

    const usersInfo = yield throttledGetUsersInfo(needsActionAdmins, maxConnections)
    const users = usersInfo
      .map(stillAdmin)
      .map((user, index) => {
        const obj = {}
        Object.assign(obj, { username: needsActionAdmins[index]}, user)
        return obj
      })
    const newsworthyAdmins = users.filter((current) => current.isAdmin || !exAdmins.includes(current.username))
    if (newsworthyAdmins.length > 0) {
      const [newAdmins, newExreddits] = newsworthyAdmins.reduce(reduceAdminStatus, [[], []])
      if (newAdmins.length > 0) {
        logger.result(`*new admins discovered in ${location}*: ${newAdmins.join(', ')}`, replyCB)
      }
      if (newExreddits.length > 0) {
        logger.result(`*new exreddits discovered in ${location}*: ${newExreddits.join(', ')}`, replyCB)
      }
      const result = yield collection.insertMany(newsworthyAdmins)
      logger.result(`wrote to ${mongoAdminCollection}`)
    } else {
      logger.info(`did not discover any new admins or exreddits in ${location}`)
      reactCB(emoji.end)
    }
  }).catch((err) => logger.error(err, replyCB))
)

module.exports = {
  scanSubreddit,
  scanSubreddits,
  scanTopic
}
<commit_msg>add high upper limit to requests<commit_after>const co = require('co')
const agent = require('superagent-promise')(require('superagent'), Promise)
const fs = require('fs')
const _ = require('lodash')
const { throttledGetUsersInfo, stillAdmin, reduceAdminStatus } = require('./scanner')
const configBuffer = fs.readFileSync('./config.json')
const config = JSON.parse(configBuffer)
const SUBREDDIT_URL = 'https://www.reddit.com/r/'
const TOPIC_URL = 'https://www.reddit.com/comments/'
const SUBREDDIT_URL_SUFFIX = '.json?limit=500'
const { mongoUsername, mongoPassword, mongoHost, mongoPath, mongoAdminCollection } = config.private[process.env.NODE_ENV]
const maxConnections = config.maxConnections || 3
const { emoji } = config
const { connect } = require('mongodb').MongoClient
const mongoUrl = `mongodb://${mongoUsername}:${mongoPassword}@${mongoHost}/${mongoPath}`

const requestSubreddit = (subreddit) => agent.get(`${SUBREDDIT_URL}${subreddit}${SUBREDDIT_URL_SUFFIX}`)
const requestTopic = (topic) => agent.get(`${TOPIC_URL}${topic}${SUBREDDIT_URL_SUFFIX}`)
const requestMods = (subreddit) => agent.get(`${SUBREDDIT_URL}${subreddit}${MOD_URL_SUFFIX}`)

const scanSubreddits = (subreddits, maxTopics, logger, replyCB, reactCB) => (
  co(function * () {
    const requestNextSubreddit = (index) => {
      if (subreddits[index]) {
        return scanSubreddit(subreddits[index], maxTopics, logger, replyCB, reactCB).then(requestNextSubreddit.bind(this, index+1))
      }
    }
    requestNextSubreddit(0)
  }).catch((err) => logger.error(err, replyCB))
)

const scanSubreddit = (subreddit, maxTopics, logger, replyCB, reactCB) => (
  co(function * () {
    logger.info(`start subreddit scan of ${subreddit}`)
    const data = yield requestSubreddit(subreddit)
    const posts = _.get(data, 'body.data.children', [])
    const promises = []
    for(let i = 0; i < maxTopics && i < posts.length; i++) {
      logger.info(`start scan of topic ${posts[i].data.id}`)
      promises.push(_scanTopic(posts[i].data.id, logger, replyCB, reactCB))
    }
    const topicAdmins = yield Promise.all(promises)
    const allAdmins = _.chain(topicAdmins)
      .flatten()
      .uniq()
      .map((admin) => admin.toLowerCase())
      .value()
    logger.info(`*overall*: ${allAdmins.join(', ')}`)
    updateDB(allAdmins, `/r/${subreddit}`, logger, replyCB, reactCB)
    return yield Promise.resolve(true)
  }).catch((err) => logger.error(err, replyCB))
)

const scanTopic = (topic, logger, replyCB, reactCB) => (
  co(function * () {
    logger.info(`start scan of topic ${topic}`)
    let admins = yield _scanTopic(topic, logger, replyCB, reactCB)
    admins = admins.map((admin) => admin.toLowerCase())
    updateDB(admins, `topic ${topic}`, logger, replyCB, reactCB)
  })
)

const _scanTopic = (topic, logger, replyCB, reactCB) => {
  return new Promise((resolve, reject) => {
    co(function * () {
      const data = yield requestTopic(topic)
      let admins = []
      const postAdminField = _.get(data, 'body.0.data.children.0.data.distinguished', null)
      if (postAdminField === 'admin') {
        const author = _.get(data, 'body.0.data.children.0.data.author', false)
        if (author) {
          admins.push(author)
        }
      }
      findAdmins(_.get(data, 'body.1.data.children', []), admins)
      admins = _.uniq(admins)
      console.log('admins', admins)
      if (admins.length > 0) {
        reactCB(emoji.adminFound)
      }
      logger.info(`*${topic}*: ${admins.join(', ')}`)
      resolve(admins)
    }).catch((err) => logger.error(err, replyCB))
  })
}

const findAdmins = (children, admins) => {
  for (let i = 0; i < children.length; i++) {
    const child = _.get(children, [i, 'data'])
    if (child.distinguished === 'admin') {
      admins.push(child.author)
    }
    const childChildren = _.get(child, 'replies.data.children', [])
    findAdmins(childChildren, admins)
  }
}

const updateDB = (foundAdmins, location, logger, replyCB, reactCB) => (
  co(function * () {
    logger.info('start update')
    const db = yield connect(mongoUrl)
    const collection = db.collection(mongoAdminCollection)
    const docs = yield collection.find({}).toArray()
    const [currentAdmins, exAdmins] = docs.reduce(reduceAdminStatus, [[],[]])
    const needsActionAdmins = foundAdmins.filter((current) => !currentAdmins.includes(current))
    logger.info(`*needs action*: ${needsActionAdmins.join(', ')}`)

    if (needsActionAdmins.length <= 0) {
      logger.info(`did not discover any new admins or exreddits in ${location}`)
      reactCB(emoji.end)
      return
    }

    const usersInfo = yield throttledGetUsersInfo(needsActionAdmins, maxConnections)
    const users = usersInfo
      .map(stillAdmin)
      .map((user, index) => {
        const obj = {}
        Object.assign(obj, { username: needsActionAdmins[index]}, user)
        return obj
      })
    const newsworthyAdmins = users.filter((current) => current.isAdmin || !exAdmins.includes(current.username))
    if (newsworthyAdmins.length > 0) {
      const [newAdmins, newExreddits] = newsworthyAdmins.reduce(reduceAdminStatus, [[], []])
      if (newAdmins.length > 0) {
        logger.result(`*new admins discovered in ${location}*: ${newAdmins.join(', ')}`, replyCB)
      }
      if (newExreddits.length > 0) {
        logger.result(`*new exreddits discovered in ${location}*: ${newExreddits.join(', ')}`, replyCB)
      }
      const result = yield collection.insertMany(newsworthyAdmins)
      logger.result(`wrote to ${mongoAdminCollection}`)
    } else {
      logger.info(`did not discover any new admins or exreddits in ${location}`)
      reactCB(emoji.end)
    }
  }).catch((err) => logger.error(err, replyCB))
)

module.exports = {
  scanSubreddit,
  scanSubreddits,
  scanTopic
}
<|endoftext|>