<commit_before>// mongodatabank.js
//
// Implementation of Databank interface for MongoDB
//
// Copyright 2011, StatusNet Inc.
//
// Licensed 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.

var databank = require('./databank'),
    Databank = databank.Databank,
    DatabankError = databank.DatabankError,
    AlreadyExistsError = databank.AlreadyExistsError,
    NoSuchThingError = databank.NoSuchThingError,
    mongodb = require('mongodb'),
    Db = mongodb.Db,
    Server = mongodb.Server;

var MongoDatabank = function(params) {
    this.db = null;
    this.schema = params.schema || {};
};

MongoDatabank.prototype = new Databank();
MongoDatabank.prototype.constructor = MongoDatabank;

MongoDatabank.prototype.connect = function(params, onCompletion) {

    var host = params.host || 'localhost',
        port = params.port || 27017,
        dbname = params.db || 'test',
        server = new Server(host, port, params),
        checkSchema = params.checkSchema || true,
        bank = this;

    if (this.db) {
        if (onCompletion) {
            onCompletion(new AlreadyConnectedError());
        }
        return;
    }

    this.db = new Db(dbname, server);

    this.db.open(function(err, newDb) {
        if (err) {
            if (onCompletion) {
                onCompletion(err);
            }
        } else {
            if (checkSchema) {
                bank.checkSchema(function(err) {
                    if (err) {
                        if (onCompletion) {
                            onCompletion(err);
                        }
                    } else {
                        if (onCompletion) {
                            onCompletion(null);
                        }
                    }
                });
            } else {
                if (onCompletion) {
                    onCompletion(null);
                }
            }
        }
    });
};

// Disconnect yourself.
// onCompletion(err): function to call on completion

MongoDatabank.prototype.disconnect = function(onCompletion) {
    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }
    this.db.close(function() {
        this.db     = null;
        this.server = null;
        if (onCompletion) {
            onCompletion(null);
        }
    });
};

// Create a new thing
// type: string, type of thing, usually 'user' or 'activity'
// id: a unique ID, like a nickname or a UUID
// value: JavaScript value; will be JSONified
// onCompletion(err, value): function to call on completion

MongoDatabank.prototype.create = function(type, id, value, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    if (!value[pkey] || value[pkey] !== id) {
        value[pkey] = id;
    }

    this.db.collection(type, function(err, coll) {
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        coll.insert(value, function(err, newValues) {
            if (err) {
                // FIXME: find unique key errors and convert to AlreadyExistsError
                if (onCompletion) {
                    onCompletion(err, null);
                }
            } else {
                if (onCompletion) {
                    // Mongo returns an array of values
                    onCompletion(null, newValues[0]);
                }
            }
        });
    });
};

// Read an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// onCompletion(err, value): function to call on completion

MongoDatabank.prototype.read = function(type, id, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    this.db.collection(type, function(err, coll) {
        var sel = {};
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        sel[pkey] = id;
        coll.findOne(sel, function(err, value) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                if (onCompletion) {
                    onCompletion(err, null);
                }
            } else if (!value) {
                if (onCompletion) {
                    onCompletion(new NoSuchThingError(type, id), null);
                }
            } else {
                if (onCompletion) {
                    onCompletion(null, value);
                }
            }
        });
    });
};

// Update an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// onCompletion(err, value): function to call on completion

MongoDatabank.prototype.update = function(type, id, value, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    if (!value[pkey] || value[pkey] !== id) {
        value[pkey] = id;
    }

    this.db.collection(type, function(err, coll) {
        var sel = {};
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        sel[pkey] = id;
        coll.update(sel, value, {}, function(err, newValue) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                if (onCompletion) {
                    onCompletion(err, null);
                }
            } else {
                if (onCompletion) {
                    onCompletion(null, newValue);
                }
            }
        });
    });
};

// Delete an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// onCompletion(err): function to call on completion

MongoDatabank.prototype.del = function(type, id, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    this.db.collection(type, function(err, coll) {
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        var sel;
        sel[pkey] = id;
        coll.remove(sel, {}, function(err) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                if (onCompletion) {
                    onCompletion(err);
                }
            } else {
                if (onCompletion) {
                    onCompletion(null);
                }
            }
        });
    });
};

// Search for things
// type: type of thing
// criteria: map of criteria, with exact matches, like {'subject.id':'tag:example.org,2011:evan' }
// onResult(value): called once per result found
// onCompletion(err): called once at the end of results

MongoDatabank.prototype.search = function(type, criteria, onResult, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    this.db.collection(type, function(err, coll) {
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        coll.find(criteria, function(err, cursor) {
            if (err) {
                if (onCompletion) {
                    onCompletion(err);
                }
            } else {
                var lastErr = null;

                cursor.each(function(err, value) {
                    if (err) {
                        lastErr = err;
                    } else {
                        if (onResult) {
                            onResult(value);
                        }
                    }
                });

                onCompletion(lastErr);
            }
        });
    });
};

MongoDatabank.prototype.getPrimaryKey = function(type) {
    return (this.schema && this.schema[type]) ? this.schema[type].pkey : '_id';
};

MongoDatabank.prototype.checkSchema = function(onCompletion) {

    var pairs = [];
    var pair;
    var type;
    var bank = this;

    var checkType = function(type, schema, next) {
        var keys = {};
        bank.db.collection(type, function(err, coll) {
            if (err) {
                next(err);
            } else {
                keys[schema.pkey] = 1;
                coll.ensureIndex(keys, {unique: true}, function(err) {
                    if (err) {
                        next(err);
                    } else {
                        next(null);
                    }
                });
            }
        });
    };

    var checkNextType = function(pairsLeft, next) {
        var pair;
        if (pairsLeft.length === 0) {
            next(null);
        } else {
            pair = pairsLeft.pop();
            checkType(pair[0], pair[1], function(err) {
                if (err) {
                    next(err);
                } else {
                    checkNextType(pairsLeft, next);
                }
            });
        }
    };

    for (type in this.schema) {
        pair = [type, this.schema[type]];
        pairs.push(pair);
    }

    checkNextType(pairs, onCompletion);
};

exports.MongoDatabank = MongoDatabank;

<commit_msg>correct results of update, params for del<commit_after>// mongodatabank.js
//
// Implementation of Databank interface for MongoDB
//
// Copyright 2011, StatusNet Inc.
//
// Licensed 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.

var databank = require('./databank'),
    Databank = databank.Databank,
    DatabankError = databank.DatabankError,
    AlreadyExistsError = databank.AlreadyExistsError,
    NoSuchThingError = databank.NoSuchThingError,
    mongodb = require('mongodb'),
    Db = mongodb.Db,
    Server = mongodb.Server;

var MongoDatabank = function(params) {
    this.db = null;
    this.schema = params.schema || {};
};

MongoDatabank.prototype = new Databank();
MongoDatabank.prototype.constructor = MongoDatabank;

MongoDatabank.prototype.connect = function(params, onCompletion) {

    var host = params.host || 'localhost',
        port = params.port || 27017,
        dbname = params.db || 'test',
        server = new Server(host, port, params),
        checkSchema = params.checkSchema || true,
        bank = this;

    if (this.db) {
        if (onCompletion) {
            onCompletion(new AlreadyConnectedError());
        }
        return;
    }

    this.db = new Db(dbname, server);

    this.db.open(function(err, newDb) {
        if (err) {
            if (onCompletion) {
                onCompletion(err);
            }
        } else {
            if (checkSchema) {
                bank.checkSchema(function(err) {
                    if (err) {
                        if (onCompletion) {
                            onCompletion(err);
                        }
                    } else {
                        if (onCompletion) {
                            onCompletion(null);
                        }
                    }
                });
            } else {
                if (onCompletion) {
                    onCompletion(null);
                }
            }
        }
    });
};

// Disconnect yourself.
// onCompletion(err): function to call on completion

MongoDatabank.prototype.disconnect = function(onCompletion) {
    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }
    this.db.close(function() {
        this.db     = null;
        this.server = null;
        if (onCompletion) {
            onCompletion(null);
        }
    });
};

// Create a new thing
// type: string, type of thing, usually 'user' or 'activity'
// id: a unique ID, like a nickname or a UUID
// value: JavaScript value; will be JSONified
// onCompletion(err, value): function to call on completion

MongoDatabank.prototype.create = function(type, id, value, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    if (!value[pkey] || value[pkey] !== id) {
        value[pkey] = id;
    }

    this.db.collection(type, function(err, coll) {
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        coll.insert(value, function(err, newValues) {
            if (err) {
                // FIXME: find unique key errors and convert to AlreadyExistsError
                if (onCompletion) {
                    onCompletion(err, null);
                }
            } else {
                if (onCompletion) {
                    // Mongo returns an array of values
                    onCompletion(null, newValues[0]);
                }
            }
        });
    });
};

// Read an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// onCompletion(err, value): function to call on completion

MongoDatabank.prototype.read = function(type, id, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    this.db.collection(type, function(err, coll) {
        var sel = {};
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        sel[pkey] = id;
        coll.findOne(sel, function(err, value) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                if (onCompletion) {
                    onCompletion(err, null);
                }
            } else if (!value) {
                if (onCompletion) {
                    onCompletion(new NoSuchThingError(type, id), null);
                }
            } else {
                if (onCompletion) {
                    onCompletion(null, value);
                }
            }
        });
    });
};

// Update an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// onCompletion(err, value): function to call on completion

MongoDatabank.prototype.update = function(type, id, value, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    if (!value[pkey] || value[pkey] !== id) {
        value[pkey] = id;
    }

    this.db.collection(type, function(err, coll) {
        var sel = {};
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        sel[pkey] = id;
        coll.update(sel, value, {}, function(err) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                if (onCompletion) {
                    onCompletion(err, null);
                }
            } else {
                if (onCompletion) {
                    onCompletion(null, value);
                }
            }
        });
    });
};

// Delete an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// onCompletion(err): function to call on completion

MongoDatabank.prototype.del = function(type, id, onCompletion) {

    var sel = {};

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    var pkey = this.getPrimaryKey(type);

    this.db.collection(type, function(err, coll) {
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }

        sel[pkey] = id;
        coll.remove(sel, {}, function(err) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                if (onCompletion) {
                    onCompletion(err);
                }
            } else {
                if (onCompletion) {
                    onCompletion(null);
                }
            }
        });
    });
};

// Search for things
// type: type of thing
// criteria: map of criteria, with exact matches, like {'subject.id':'tag:example.org,2011:evan' }
// onResult(value): called once per result found
// onCompletion(err): called once at the end of results

MongoDatabank.prototype.search = function(type, criteria, onResult, onCompletion) {

    if (!this.db) {
        if (onCompletion) {
            onCompletion(new NotConnectedError());
        }
        return;
    }

    this.db.collection(type, function(err, coll) {
        if (err) {
            if (onCompletion) {
                onCompletion(err, null);
            }
        }
        coll.find(criteria, function(err, cursor) {
            if (err) {
                if (onCompletion) {
                    onCompletion(err);
                }
            } else {
                var lastErr = null;

                cursor.each(function(err, value) {
                    if (err) {
                        lastErr = err;
                    } else {
                        if (onResult) {
                            onResult(value);
                        }
                    }
                });

                onCompletion(lastErr);
            }
        });
    });
};

MongoDatabank.prototype.getPrimaryKey = function(type) {
    return (this.schema && this.schema[type]) ? this.schema[type].pkey : '_id';
};

MongoDatabank.prototype.checkSchema = function(onCompletion) {

    var pairs = [];
    var pair;
    var type;
    var bank = this;

    var checkType = function(type, schema, next) {
        var keys = {};
        bank.db.collection(type, function(err, coll) {
            if (err) {
                next(err);
            } else {
                keys[schema.pkey] = 1;
                coll.ensureIndex(keys, {unique: true}, function(err) {
                    if (err) {
                        next(err);
                    } else {
                        next(null);
                    }
                });
            }
        });
    };

    var checkNextType = function(pairsLeft, next) {
        var pair;
        if (pairsLeft.length === 0) {
            next(null);
        } else {
            pair = pairsLeft.pop();
            checkType(pair[0], pair[1], function(err) {
                if (err) {
                    next(err);
                } else {
                    checkNextType(pairsLeft, next);
                }
            });
        }
    };

    for (type in this.schema) {
        pair = [type, this.schema[type]];
        pairs.push(pair);
    }

    checkNextType(pairs, onCompletion);
};

exports.MongoDatabank = MongoDatabank;

<|endoftext|>


<commit_before>/*jslint node: true */
/*jshint laxbreak: true */
/*jshint laxcomma: true */
"use strict";

var d3 = require("d3");
var _ = require("underscore");
var Evidence = require('./Evidence');

var createTooltipBox = function(container) {
    d3.select('.up_pftv_tooltip-container').remove();
    var tooltipContainer = container.append('div')
        .attr('class','up_pftv_tooltip-container');
    tooltipContainer.append('span')
        .text('x')
        .attr('class','up_pftv_tooltip-close')
        .on('click',function(){
            tooltipContainer.transition(20)
                .style('opacity',0)
                .style('display','none');
            tooltipContainer.remove();
        });
    return tooltipContainer;
};

var getEvidenceText = function(tooltip, code, sources) {
    var acronym = Evidence.acronym[code];
    var publications = _.where(sources, {name: 'PubMed'}).length;
    publications += _.where(sources, {name: 'Citation'}).length;
    if ((acronym === 'EXP') || (acronym === 'NAS')) {
        return publications + (publications > 1 ? ' Publications' : ' Publication');
    } else if (acronym === 'IC') {
        return publications === 0
            ? 'Curated'
            : publications + (publications > 1 ? ' Publications' : ' Publication');
    } else if (acronym === 'ISS') {
        return 'By similarity';
    } else if (acronym === 'ISM') {
        return !sources || sources.length === 0 ? 'Sequence Analysis': sources[0].name + ' annotation';
    } else if ((acronym === 'MIXM') || (acronym === 'MIXA')) {
        return 'Combined sources';
    } else if ((acronym === 'MI') || (acronym === 'AI')){
        return 'Imported';
    } else if (acronym === 'AA') {
        var unirule = sources
            ? _.find(sources, function(source) {
            return source.url && (sources.url.indexOf('unirule') !== -1);
        })
            : false;
        var saas = sources
            ? _.find(sources, function(source) {
            return source.url && (sources.url.indexOf('SAAS') !== -1);
        })
            : false;
        return unirule
            ? 'UniRule annotation'
            : saas
            ? 'SAAS annotation'
            : sources ? sources[0].name + ' annotation' : 'Automatic annotation';
    } else {
        return code;
    }
};

var Tooltip = function(fv, catTitle, d, container) {
    var tooltip = this;
    tooltip.data = d;
    tooltip.pinData = {
        category: catTitle,
        id: d.internalId,
        ordering: {
            type: tooltip.data.type.name,
            start: +tooltip.data.begin,
            end: tooltip.data.end ? +tooltip.data.end : +tooltip.data.begin
        },
        sections: []
    };
    tooltip.sequence = fv.sequence;
    tooltip.tooltipViewer = undefined;
    tooltip.tooltipViewer = undefined;

    var tooltipContainer = createTooltipBox(container);

    tooltipContainer.style('left', (d3.mouse(container.node())[0] + 10) + 'px')
                .style('top', d3.mouse(container.node())[1] + 'px')
                .transition(200)
                .style('opacity',1)
                .style('display','block');

    d3.select('.up_pftv_tooltip-container table').remove();

    tooltip.table = d3.select('.up_pftv_tooltip-container').append('table');
    tooltip.table
        .on('mousedown', function() {
            fv.overTooltip = true;
        })
        .on('mouseup', function() {
            fv.overTooltip = false;
        });

    var descRow = tooltip.table.append('tr');

    var tooltipTitle = tooltip.data.type.label + ' ' + tooltip.data.begin +
        (tooltip.data.end ? '-' + tooltip.data.end : '');
    tooltip.pinData.sections.push({title: tooltipTitle, information: {}});
    if (fv.pinPad) {
        var th = descRow.append('th')
            .attr('colspan',2);
        var pinClass = 'up_pftv_iconContainer-unpinned';
        var pinTitle = 'Pin tooltip';
        if (tooltip.data.pinned === true) {
            pinClass = 'up_pftv_iconContainer-pinned';
            pinTitle = 'Unpin tooltip';
        }
        var pinContainer = th.append('div').classed('up_pftv_tooltip-pin-container', true)
            .append('div').attr('class', 'up_pp_iconContainer ' + pinClass);
        pinContainer.attr('title', pinTitle);

        pinContainer.append('div').attr('class', 'up-pp-icon-pin up_pp_icon up_pp_clickable-icon')
            .on('click', function() {
                if (tooltip.data.pinned === true) {
                    tooltip.data.pinned = false;
                    pinContainer.classed('up_pftv_iconContainer-unpinned', true);
                    pinContainer.classed('up_pftv_iconContainer-pinned', false);
                    pinContainer.attr('title', 'Pin tooltip');
                    fv.pinPad.removeElement(tooltip.pinData.id);
                } else {
                    tooltip.data.pinned = true;
                    fv.pinPad.addElement(tooltip.pinData);
                    pinContainer.classed('up_pftv_iconContainer-unpinned', false);
                    pinContainer.classed('up_pftv_iconContainer-pinned', true);
                    pinContainer.attr('title', 'Unpin tooltip');
                }
            });
        th.append('div').style('display', 'inline-block')
            .text(tooltipTitle);
    } else {
        descRow.append('th')
            .attr('colspan',2)
            .text(tooltipTitle);
    }

    if (tooltip.data.sp !== undefined) {
        var dataSource = tooltip.table.append('tr');
        dataSource.append('td').text('Source');
        dataSource.append('td').text(function() {
            if (tooltip.data.sp && Evidence.isLSS(tooltip.data.evidences)) {
                tooltip.pinData.sections[0].information.source = 'UniProt and large scale studies';
                return 'UniProt and large scale studies';
            } else if (tooltip.data.sp) {
                tooltip.pinData.sections[0].information.source = 'UniProt';
                return 'UniProt';
            } else {
                tooltip.pinData.sections[0].information.source = 'Large scale studies';
                return 'Large scale studies';
            }
        });
    }

    if (tooltip.data.description) {
        var dataDes = tooltip.table.append('tr');
        dataDes.append('td').text('Description');
        dataDes.append('td').text(tooltip.data.description);
        tooltip.pinData.sections[0].information.description = tooltip.data.description;
    }
};

Tooltip.prototype.addEvidences = function(evidences, section) {
    var tooltip = this;
    _.each(evidences, function(e, counter) {
        var typeRow = tooltip.table.append('tr')
            .attr('class','up_pftv_evidence-col');
        typeRow.append('td')
            .text('Evidence');
        var evidenceText = getEvidenceText(tooltip, e.code, e.sources);
        typeRow.append('td')
            .text(evidenceText);
        section.information.evidence = evidenceText;

        var groupedSources = _.groupBy(e.sources, 'name');

        _.each(groupedSources, function(elem, index) {
            var sourceRow = tooltip.table.append('tr')
                .attr('class','up_pftv_evidence-source');

            sourceRow.append('td')
                .text('');

            section.information['styled_evidenceArray_' + index + '_' + counter] = {
                key: index,
                key_right: true,
                value: []
            };
            var list = sourceRow.append('td').text(index + ' ');
            _.each(elem, function(el, i) {
                list.append('span').append('a')
                    .attr('href', el.url)
                    .attr('target', '_blank')
                    .text(el.id);
                if (i !== (elem.length-1)) {
                    list.append('span').text(', ');
                }
                section.information['styled_evidenceArray_' + index + '_' + counter].value.push({
                    value: el.id,
                    link: el.url
                });
            });
        });
    });
};

var BasicTooltipViewer = function(tooltip) {
    tooltip.addEvidences(tooltip.data.evidences, tooltip.pinData.sections[0]);
};

var AlternativeTooltipViewer = function(tooltip, change, field) {
    if (tooltip.data[field]) {
        var seqRow = tooltip.table.append('tr');
        seqRow.append('td').text(change);
        seqRow.append('td')
            .text(function() {
                var end = tooltip.data.end ? tooltip.data.end : tooltip.data.begin;
                var original = tooltip.sequence.substring(+tooltip.data.begin - 1, +end);
                var text = original + ' > ' + tooltip.data[field];
                tooltip.pinData.sections[0].information[change] = text;
                return text;
            });
    }
    tooltip.addEvidences(tooltip.data.evidences, tooltip.pinData.sections[0]);
};

var VariantTooltipViewer = function(tooltip) {
    var addPredictions = function(section) {
        if (tooltip.data.frequency && (tooltip.data.frequency !== 0)) {
            var freqRow = tooltip.table.append('tr');
            freqRow.append('td').text('Frequency');
            freqRow.append('td').text(tooltip.data.frequency);
            section.information.frequency = tooltip.data.frequency;
        }
        if (tooltip.data.polyphenPrediction && (tooltip.data.polyphenPrediction !== '-')
            && (tooltip.data.polyphenPrediction !== 'unknown')) {
            var polyRow = tooltip.table.append('tr');
            polyRow.append('td').text('Polyphen');
            var text = tooltip.data.polyphenPrediction + ', score ' + tooltip.data.polyphenScore;
            polyRow.append('td').text(text);
            section.information.polyphen = text;
        }
        if (tooltip.data.siftPrediction && (tooltip.data.siftPrediction !== '-')
            && (tooltip.data.siftPrediction !== 'unknown')) {
            var siftRow = tooltip.table.append('tr');
            siftRow.append('td').text('SIFT');
            var text = tooltip.data.siftPrediction + ', score ' + tooltip.data.siftScore;
            siftRow.append('td').text(text);
            section.information.sift = text;
        }
    };

    var addAssociation = function(section) {
        if (tooltip.data.association) {
            var assocRow = tooltip.table.append('tr');
            assocRow.append('td').attr('colspan', 2).classed('up_pftv_subsection',true).text('Disease Association');
            section.information.subsection_disease = {
                title: 'Disease Association',
                information: {}
            };
            _.each(tooltip.data.association, function(association, counter){
                if (association.name) {
                    var diseaseRow = tooltip.table.append('tr');
                    diseaseRow.append('td').text('Disease');
                    diseaseRow.append('td').append('span').append('a')
                        .attr('href', 'http://www.uniprot.org/diseases/?query=' + association.name)
                        .attr('target', '_blank').text(association.name);
                    section.information.subsection_disease.information.disease = {
                        value: association.name,
                        link: 'http://www.uniprot.org/diseases/?query=' + association.name
                    };
                }
                if (association.description) {
                    var descRow = tooltip.table.append('tr');
                    descRow.append('td').text('Description');
                    descRow.append('td').text(association.description);
                    section.information.subsection_disease.information.description = association.description;
                }
                if (association.moreInfo) {
                    var groupedSources = _.groupBy(association.moreInfo, 'name');
                    _.each(groupedSources, function(elem, index) {
                        var moreInfo = tooltip.table.append('tr');
                        moreInfo.append('td');
                        var list = moreInfo.append('td').text(index + ' ');

                        section.information.subsection_disease.information
                            ['styled_associationArray_' + index + '_' + counter] = {
                            key: index,
                            key_right: true,
                            value: []
                        };

                        _.each(elem, function(el, i) {
                            list.append('span').append('a').attr('href', el.url)
                                .attr('target', '_blank').text(el.id);
                            section.information.subsection_disease.information
                                ['styled_associationArray_' + index + '_' + counter].value.push({
                                value: el.id,
                                link: el.url
                            });
                        });
                    });
                }
            });
        }
    };

    var addMutation = function(section) {
        var mutRow = tooltip.table.append('tr');
        mutRow.append('td').text('Mutation');
        var text = tooltip.data.wildType + ' > ' + tooltip.data.mutation;
        mutRow.append('td').text(text);
        section.information.mutation = text;
    };

    if (tooltip.data.sp && Evidence.isLSS(tooltip.data.evidences)) {
        var upEvidences = [], lssEvidences = [];
        _.each(tooltip.data.evidences, function(e) {
            _.contains(Evidence.manual, e.code) ? upEvidences.push(e) : lssEvidences.push(e);
        });

        addMutation(tooltip.pinData.sections[0]);

        var upRow = tooltip.table.append('tr').classed('up_pftv_section', true);
        upRow.append('td').attr('colspan',2).text('UniProt');
        var length = tooltip.pinData.sections.push({title: 'UniProt', information: {}});
        tooltip.addEvidences(upEvidences, tooltip.pinData.sections[length-1]);
        addAssociation(tooltip.pinData.sections[length-1]);

        var lssRow = tooltip.table.append('tr').classed('up_pftv_section', true);
        lssRow.append('td').attr('colspan',2).text('Large Scale Studies');
        length = tooltip.pinData.sections.push({title: 'Large Scale Studies', information: {}});
        addPredictions(tooltip.pinData.sections[length-1]);
        tooltip.addEvidences(lssEvidences, tooltip.pinData.sections[length-1]);
    } else {
        addMutation(tooltip.pinData.sections[0]);
        addPredictions(tooltip.pinData.sections[0]);
        tooltip.addEvidences(tooltip.data.evidences, tooltip.pinData.sections[0]);
        addAssociation(tooltip.pinData.sections[0]);
    }
};

Tooltip.basic = function() {
    this.tooltipViewer = new BasicTooltipViewer(this);
};
Tooltip.mutagen = function() {
    this.tooltipViewer = new AlternativeTooltipViewer(this, 'Mutation', 'mutation');
};
Tooltip.conflict = function() {
    this.tooltipViewer = new AlternativeTooltipViewer(this, 'Conflict', 'alternativeSequence');
};
Tooltip.missense = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.ms_del = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.insdel = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.stop_lost = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.stop_gained = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.init_codon = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.variant = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};

var TooltipFactory = function() {
    return {
        createTooltip: function(fv, catTitle, data, container) {
            var tooltip
                , type = data.type.name.toLowerCase();
            // error if the constructor doesn't exist
            if (typeof Tooltip[type] !== "function") {
                Tooltip.basic.prototype = new Tooltip(fv, catTitle, data, container);
                tooltip = new Tooltip.basic();
            } else {
                Tooltip[type].prototype = new Tooltip(fv, catTitle, data, container);
                tooltip = new Tooltip[type]();
            }
            return tooltip;
        }
    };
}();

module.exports = TooltipFactory;<commit_msg>Add predictors link.<commit_after>/*jslint node: true */
/*jshint laxbreak: true */
/*jshint laxcomma: true */
"use strict";

var d3 = require("d3");
var _ = require("underscore");
var Evidence = require('./Evidence');

var createTooltipBox = function(container) {
    d3.select('.up_pftv_tooltip-container').remove();
    var tooltipContainer = container.append('div')
        .attr('class','up_pftv_tooltip-container');
    tooltipContainer.append('span')
        .text('x')
        .attr('class','up_pftv_tooltip-close')
        .on('click',function(){
            tooltipContainer.transition(20)
                .style('opacity',0)
                .style('display','none');
            tooltipContainer.remove();
        });
    return tooltipContainer;
};

var getEvidenceText = function(tooltip, code, sources) {
    var acronym = Evidence.acronym[code];
    var publications = _.where(sources, {name: 'PubMed'}).length;
    publications += _.where(sources, {name: 'Citation'}).length;
    if ((acronym === 'EXP') || (acronym === 'NAS')) {
        return publications + (publications > 1 ? ' Publications' : ' Publication');
    } else if (acronym === 'IC') {
        return publications === 0
            ? 'Curated'
            : publications + (publications > 1 ? ' Publications' : ' Publication');
    } else if (acronym === 'ISS') {
        return 'By similarity';
    } else if (acronym === 'ISM') {
        return !sources || sources.length === 0 ? 'Sequence Analysis': sources[0].name + ' annotation';
    } else if ((acronym === 'MIXM') || (acronym === 'MIXA')) {
        return 'Combined sources';
    } else if ((acronym === 'MI') || (acronym === 'AI')){
        return 'Imported';
    } else if (acronym === 'AA') {
        var unirule = sources
            ? _.find(sources, function(source) {
            return source.url && (sources.url.indexOf('unirule') !== -1);
        })
            : false;
        var saas = sources
            ? _.find(sources, function(source) {
            return source.url && (sources.url.indexOf('SAAS') !== -1);
        })
            : false;
        return unirule
            ? 'UniRule annotation'
            : saas
            ? 'SAAS annotation'
            : sources ? sources[0].name + ' annotation' : 'Automatic annotation';
    } else {
        return code;
    }
};

var Tooltip = function(fv, catTitle, d, container) {
    var tooltip = this;
    tooltip.data = d;
    tooltip.pinData = {
        category: catTitle,
        id: d.internalId,
        ordering: {
            type: tooltip.data.type.name,
            start: +tooltip.data.begin,
            end: tooltip.data.end ? +tooltip.data.end : +tooltip.data.begin
        },
        sections: []
    };
    tooltip.sequence = fv.sequence;
    tooltip.tooltipViewer = undefined;
    tooltip.tooltipViewer = undefined;

    var tooltipContainer = createTooltipBox(container);

    tooltipContainer.style('left', (d3.mouse(container.node())[0] + 10) + 'px')
                .style('top', d3.mouse(container.node())[1] + 'px')
                .transition(200)
                .style('opacity',1)
                .style('display','block');

    d3.select('.up_pftv_tooltip-container table').remove();

    tooltip.table = d3.select('.up_pftv_tooltip-container').append('table');
    tooltip.table
        .on('mousedown', function() {
            fv.overTooltip = true;
        })
        .on('mouseup', function() {
            fv.overTooltip = false;
        });

    var descRow = tooltip.table.append('tr');

    var tooltipTitle = tooltip.data.type.label + ' ' + tooltip.data.begin +
        (tooltip.data.end ? '-' + tooltip.data.end : '');
    tooltip.pinData.sections.push({title: tooltipTitle, information: {}});
    if (fv.pinPad) {
        var th = descRow.append('th')
            .attr('colspan',2);
        var pinClass = 'up_pftv_iconContainer-unpinned';
        var pinTitle = 'Pin tooltip';
        if (tooltip.data.pinned === true) {
            pinClass = 'up_pftv_iconContainer-pinned';
            pinTitle = 'Unpin tooltip';
        }
        var pinContainer = th.append('div').classed('up_pftv_tooltip-pin-container', true)
            .append('div').attr('class', 'up_pp_iconContainer ' + pinClass);
        pinContainer.attr('title', pinTitle);

        pinContainer.append('div').attr('class', 'up-pp-icon-pin up_pp_icon up_pp_clickable-icon')
            .on('click', function() {
                if (tooltip.data.pinned === true) {
                    tooltip.data.pinned = false;
                    pinContainer.classed('up_pftv_iconContainer-unpinned', true);
                    pinContainer.classed('up_pftv_iconContainer-pinned', false);
                    pinContainer.attr('title', 'Pin tooltip');
                    fv.pinPad.removeElement(tooltip.pinData.id);
                } else {
                    tooltip.data.pinned = true;
                    fv.pinPad.addElement(tooltip.pinData);
                    pinContainer.classed('up_pftv_iconContainer-unpinned', false);
                    pinContainer.classed('up_pftv_iconContainer-pinned', true);
                    pinContainer.attr('title', 'Unpin tooltip');
                }
            });
        th.append('div').style('display', 'inline-block')
            .text(tooltipTitle);
    } else {
        descRow.append('th')
            .attr('colspan',2)
            .text(tooltipTitle);
    }

    if (tooltip.data.sp !== undefined) {
        var dataSource = tooltip.table.append('tr');
        dataSource.append('td').text('Source');
        dataSource.append('td').text(function() {
            if (tooltip.data.sp && Evidence.isLSS(tooltip.data.evidences)) {
                tooltip.pinData.sections[0].information.source = 'UniProt and large scale studies';
                return 'UniProt and large scale studies';
            } else if (tooltip.data.sp) {
                tooltip.pinData.sections[0].information.source = 'UniProt';
                return 'UniProt';
            } else {
                tooltip.pinData.sections[0].information.source = 'Large scale studies';
                return 'Large scale studies';
            }
        });
    }

    if (tooltip.data.description) {
        var dataDes = tooltip.table.append('tr');
        dataDes.append('td').text('Description');
        dataDes.append('td').text(tooltip.data.description);
        tooltip.pinData.sections[0].information.description = tooltip.data.description;
    }
};

Tooltip.prototype.addEvidences = function(evidences, section) {
    var tooltip = this;
    _.each(evidences, function(e, counter) {
        var typeRow = tooltip.table.append('tr')
            .attr('class','up_pftv_evidence-col');
        typeRow.append('td')
            .text('Evidence');
        var evidenceText = getEvidenceText(tooltip, e.code, e.sources);
        typeRow.append('td')
            .text(evidenceText);
        section.information.evidence = evidenceText;

        var groupedSources = _.groupBy(e.sources, 'name');

        _.each(groupedSources, function(elem, index) {
            var sourceRow = tooltip.table.append('tr')
                .attr('class','up_pftv_evidence-source');

            sourceRow.append('td')
                .text('');

            section.information['styled_evidenceArray_' + index + '_' + counter] = {
                key: index,
                key_right: true,
                value: []
            };
            var list = sourceRow.append('td').text(index + ' ');
            _.each(elem, function(el, i) {
                list.append('span').append('a')
                    .attr('href', el.url)
                    .attr('target', '_blank')
                    .text(el.id);
                if (i !== (elem.length-1)) {
                    list.append('span').text(', ');
                }
                section.information['styled_evidenceArray_' + index + '_' + counter].value.push({
                    value: el.id,
                    link: el.url
                });
            });
        });
    });
};

var BasicTooltipViewer = function(tooltip) {
    tooltip.addEvidences(tooltip.data.evidences, tooltip.pinData.sections[0]);
};

var AlternativeTooltipViewer = function(tooltip, change, field) {
    if (tooltip.data[field]) {
        var seqRow = tooltip.table.append('tr');
        seqRow.append('td').text(change);
        seqRow.append('td')
            .text(function() {
                var end = tooltip.data.end ? tooltip.data.end : tooltip.data.begin;
                var original = tooltip.sequence.substring(+tooltip.data.begin - 1, +end);
                var text = original + ' > ' + tooltip.data[field];
                tooltip.pinData.sections[0].information[change] = text;
                return text;
            });
    }
    tooltip.addEvidences(tooltip.data.evidences, tooltip.pinData.sections[0]);
};

var VariantTooltipViewer = function(tooltip) {
    var addPredictions = function(section) {
        if (tooltip.data.frequency && (tooltip.data.frequency !== 0)) {
            var freqRow = tooltip.table.append('tr');
            freqRow.append('td').text('Frequency');
            freqRow.append('td').text(tooltip.data.frequency);
            section.information.frequency = tooltip.data.frequency;
        }
        if (tooltip.data.polyphenPrediction && (tooltip.data.polyphenPrediction !== '-')
            && (tooltip.data.polyphenPrediction !== 'unknown')) {
            var polyRow = tooltip.table.append('tr');
            polyRow.append('td').append('span').append('a')
                .attr('href', 'http://genetics.bwh.harvard.edu/pph2/dokuwiki/about')
                .attr('target', '_blank').text('Polyphen');
            var text = tooltip.data.polyphenPrediction + ', score ' + tooltip.data.polyphenScore;
            polyRow.append('td').text(text);
            section.information.polyphen = text;
        }
        if (tooltip.data.siftPrediction && (tooltip.data.siftPrediction !== '-')
            && (tooltip.data.siftPrediction !== 'unknown')) {
            var siftRow = tooltip.table.append('tr');
            siftRow.append('td').append('span').append('a')
                .attr('href', 'http://sift.jcvi.org/')
                .attr('target', '_blank').text('SIFT');
            var text = tooltip.data.siftPrediction + ', score ' + tooltip.data.siftScore;
            siftRow.append('td').text(text);
            section.information.sift = text;
        }
    };

    var addAssociation = function(section) {
        if (tooltip.data.association) {
            var assocRow = tooltip.table.append('tr');
            assocRow.append('td').attr('colspan', 2).classed('up_pftv_subsection',true).text('Disease Association');
            section.information.subsection_disease = {
                title: 'Disease Association',
                information: {}
            };
            _.each(tooltip.data.association, function(association, counter){
                if (association.name) {
                    var diseaseRow = tooltip.table.append('tr');
                    diseaseRow.append('td').text('Disease');
                    diseaseRow.append('td').append('span').append('a')
                        .attr('href', 'http://www.uniprot.org/diseases/?query=' + association.name)
                        .attr('target', '_blank').text(association.name);
                    section.information.subsection_disease.information.disease = {
                        value: association.name,
                        link: 'http://www.uniprot.org/diseases/?query=' + association.name
                    };
                }
                if (association.description) {
                    var descRow = tooltip.table.append('tr');
                    descRow.append('td').text('Description');
                    descRow.append('td').text(association.description);
                    section.information.subsection_disease.information.description = association.description;
                }
                if (association.moreInfo) {
                    var groupedSources = _.groupBy(association.moreInfo, 'name');
                    _.each(groupedSources, function(elem, index) {
                        var moreInfo = tooltip.table.append('tr');
                        moreInfo.append('td');
                        var list = moreInfo.append('td').text(index + ' ');

                        section.information.subsection_disease.information
                            ['styled_associationArray_' + index + '_' + counter] = {
                            key: index,
                            key_right: true,
                            value: []
                        };

                        _.each(elem, function(el, i) {
                            list.append('span').append('a').attr('href', el.url)
                                .attr('target', '_blank').text(el.id);
                            section.information.subsection_disease.information
                                ['styled_associationArray_' + index + '_' + counter].value.push({
                                value: el.id,
                                link: el.url
                            });
                        });
                    });
                }
            });
        }
    };

    var addMutation = function(section) {
        var mutRow = tooltip.table.append('tr');
        mutRow.append('td').text('Mutation');
        var text = tooltip.data.wildType + ' > ' + tooltip.data.mutation;
        mutRow.append('td').text(text);
        section.information.mutation = text;
    };

    if (tooltip.data.sp && Evidence.isLSS(tooltip.data.evidences)) {
        var upEvidences = [], lssEvidences = [];
        _.each(tooltip.data.evidences, function(e) {
            _.contains(Evidence.manual, e.code) ? upEvidences.push(e) : lssEvidences.push(e);
        });

        addMutation(tooltip.pinData.sections[0]);

        var upRow = tooltip.table.append('tr').classed('up_pftv_section', true);
        upRow.append('td').attr('colspan',2).text('UniProt');
        var length = tooltip.pinData.sections.push({title: 'UniProt', information: {}});
        tooltip.addEvidences(upEvidences, tooltip.pinData.sections[length-1]);
        addAssociation(tooltip.pinData.sections[length-1]);

        var lssRow = tooltip.table.append('tr').classed('up_pftv_section', true);
        lssRow.append('td').attr('colspan',2).text('Large Scale Studies');
        length = tooltip.pinData.sections.push({title: 'Large Scale Studies', information: {}});
        addPredictions(tooltip.pinData.sections[length-1]);
        tooltip.addEvidences(lssEvidences, tooltip.pinData.sections[length-1]);
    } else {
        addMutation(tooltip.pinData.sections[0]);
        addPredictions(tooltip.pinData.sections[0]);
        tooltip.addEvidences(tooltip.data.evidences, tooltip.pinData.sections[0]);
        addAssociation(tooltip.pinData.sections[0]);
    }
};

Tooltip.basic = function() {
    this.tooltipViewer = new BasicTooltipViewer(this);
};
Tooltip.mutagen = function() {
    this.tooltipViewer = new AlternativeTooltipViewer(this, 'Mutation', 'mutation');
};
Tooltip.conflict = function() {
    this.tooltipViewer = new AlternativeTooltipViewer(this, 'Conflict', 'alternativeSequence');
};
Tooltip.missense = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.ms_del = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.insdel = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.stop_lost = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.stop_gained = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.init_codon = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};
Tooltip.variant = function() {
    this.tooltipViewer = new VariantTooltipViewer(this);
};

var TooltipFactory = function() {
    return {
        createTooltip: function(fv, catTitle, data, container) {
            var tooltip
                , type = data.type.name.toLowerCase();
            // error if the constructor doesn't exist
            if (typeof Tooltip[type] !== "function") {
                Tooltip.basic.prototype = new Tooltip(fv, catTitle, data, container);
                tooltip = new Tooltip.basic();
            } else {
                Tooltip[type].prototype = new Tooltip(fv, catTitle, data, container);
                tooltip = new Tooltip[type]();
            }
            return tooltip;
        }
    };
}();

module.exports = TooltipFactory;<|endoftext|>


<commit_before>import React, { Component } from 'react'

import { callPlayer, getSDK, randomString } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://player.twitch.tv/js/embed/v1.js'
const SDK_GLOBAL = 'Twitch'
const MATCH_VIDEO_URL = /(?:www\.|go\.)?twitch\.tv\/videos\/(\d+)($|\?)/
const MATCH_CHANNEL_URL = /(?:www\.|go\.)?twitch\.tv\/([a-z0-9_]+)($|\?)/
const PLAYER_ID_PREFIX = 'twitch-player-'

export class Twitch extends Component {
  static displayName = 'Twitch'
  static canPlay = url => MATCH_VIDEO_URL.test(url) || MATCH_CHANNEL_URL.test(url)
  static loopOnEnded = true

  callPlayer = callPlayer
  playerID = PLAYER_ID_PREFIX + randomString()
  load (url, isReady) {
    const { playsinline, onError, config } = this.props
    const isChannel = MATCH_CHANNEL_URL.test(url)
    const id = isChannel ? url.match(MATCH_CHANNEL_URL)[1] : url.match(MATCH_VIDEO_URL)[1]
    if (isReady) {
      if (isChannel) {
        this.player.setChannel(id)
      } else {
        this.player.setVideo('v' + id)
      }
      return
    }
    getSDK(SDK_URL, SDK_GLOBAL).then(Twitch => {
      this.player = new Twitch.Player(this.playerID, {
        video: isChannel ? '' : id,
        channel: isChannel ? id : '',
        height: '100%',
        width: '100%',
        playsinline: playsinline,
        autoplay: this.props.playing,
        muted: this.props.muted,
        // controls property must be true if url is for channel. See #733
        controls: isChannel ? true : this.props.controls,
        ...config.twitch.options
      })
      const { READY, PLAYING, PAUSE, ENDED, ONLINE, OFFLINE } = Twitch.Player
      this.player.addEventListener(READY, this.props.onReady)
      this.player.addEventListener(PLAYING, this.props.onPlay)
      this.player.addEventListener(PAUSE, this.props.onPause)
      this.player.addEventListener(ENDED, this.props.onEnded)

      // Prevent weird isLoading behaviour when streams are offline
      this.player.addEventListener(ONLINE, this.props.onLoaded)
      this.player.addEventListener(OFFLINE, this.props.onLoaded)
    }, onError)
  }

  play () {
    this.callPlayer('play')
  }

  pause () {
    this.callPlayer('pause')
  }

  stop () {
    this.callPlayer('pause')
  }

  seekTo (seconds) {
    this.callPlayer('seek', seconds)
  }

  setVolume (fraction) {
    this.callPlayer('setVolume', fraction)
  }

  mute = () => {
    this.callPlayer('setMuted', true)
  }

  unmute = () => {
    this.callPlayer('setMuted', false)
  }

  getDuration () {
    return this.callPlayer('getDuration')
  }

  getCurrentTime () {
    return this.callPlayer('getCurrentTime')
  }

  getSecondsLoaded () {
    return null
  }

  render () {
    const style = {
      width: '100%',
      height: '100%'
    }
    return (

    )
  }
}

export default createSinglePlayer(Twitch)
<commit_msg>Tweak Twitch controls bug comment<commit_after>import React, { Component } from 'react'

import { callPlayer, getSDK, randomString } from '../utils'
import createSinglePlayer from '../singlePlayer'

const SDK_URL = 'https://player.twitch.tv/js/embed/v1.js'
const SDK_GLOBAL = 'Twitch'
const MATCH_VIDEO_URL = /(?:www\.|go\.)?twitch\.tv\/videos\/(\d+)($|\?)/
const MATCH_CHANNEL_URL = /(?:www\.|go\.)?twitch\.tv\/([a-z0-9_]+)($|\?)/
const PLAYER_ID_PREFIX = 'twitch-player-'

export class Twitch extends Component {
  static displayName = 'Twitch'
  static canPlay = url => MATCH_VIDEO_URL.test(url) || MATCH_CHANNEL_URL.test(url)
  static loopOnEnded = true

  callPlayer = callPlayer
  playerID = PLAYER_ID_PREFIX + randomString()
  load (url, isReady) {
    const { playsinline, onError, config, controls } = this.props
    const isChannel = MATCH_CHANNEL_URL.test(url)
    const id = isChannel ? url.match(MATCH_CHANNEL_URL)[1] : url.match(MATCH_VIDEO_URL)[1]
    if (isReady) {
      if (isChannel) {
        this.player.setChannel(id)
      } else {
        this.player.setVideo('v' + id)
      }
      return
    }
    getSDK(SDK_URL, SDK_GLOBAL).then(Twitch => {
      this.player = new Twitch.Player(this.playerID, {
        video: isChannel ? '' : id,
        channel: isChannel ? id : '',
        height: '100%',
        width: '100%',
        playsinline: playsinline,
        autoplay: this.props.playing,
        muted: this.props.muted,
        // https://github.com/CookPete/react-player/issues/733#issuecomment-549085859
        controls: isChannel ? true : controls,
        ...config.twitch.options
      })
      const { READY, PLAYING, PAUSE, ENDED, ONLINE, OFFLINE } = Twitch.Player
      this.player.addEventListener(READY, this.props.onReady)
      this.player.addEventListener(PLAYING, this.props.onPlay)
      this.player.addEventListener(PAUSE, this.props.onPause)
      this.player.addEventListener(ENDED, this.props.onEnded)

      // Prevent weird isLoading behaviour when streams are offline
      this.player.addEventListener(ONLINE, this.props.onLoaded)
      this.player.addEventListener(OFFLINE, this.props.onLoaded)
    }, onError)
  }

  play () {
    this.callPlayer('play')
  }

  pause () {
    this.callPlayer('pause')
  }

  stop () {
    this.callPlayer('pause')
  }

  seekTo (seconds) {
    this.callPlayer('seek', seconds)
  }

  setVolume (fraction) {
    this.callPlayer('setVolume', fraction)
  }

  mute = () => {
    this.callPlayer('setMuted', true)
  }

  unmute = () => {
    this.callPlayer('setMuted', false)
  }

  getDuration () {
    return this.callPlayer('getDuration')
  }

  getCurrentTime () {
    return this.callPlayer('getCurrentTime')
  }

  getSecondsLoaded () {
    return null
  }

  render () {
    const style = {
      width: '100%',
      height: '100%'
    }
    return (

    )
  }
}

export default createSinglePlayer(Twitch)
<|endoftext|>


<commit_before>/**
 * @externs
 * @see http://trac.osgeo.org/proj4js/
 */

/**
 * @type {Object}
 */
var Proj4js = {};

/**
 * @type {Object.<string, string>}
 */
Proj4js.defs;

/**
 * @type {function(string)}
 */
Proj4js.reportError;

/**
 * @constructor
 * @param {number} x
 * @param {number} y
 */
Proj4js.Point = function(x, y) {};

/**
 * @type {number}
 */
Proj4js.Point.prototype.x;

/**
 * @type {number}
 */
Proj4js.Point.prototype.y;

/**
 * @constructor
 * @param {string} srsCode
 * @param {Function=} callback
 */
Proj4js.Proj = function(srsCode, callback) {};

/**
 * @type {string}
 */
Proj4js.Proj.prototype.title;

/**
 * @type {string}
 */
Proj4js.Proj.prototype.units;

/**
 * @param {Proj4js.Proj} source
 * @param {Proj4js.Proj} dest
 * @param {Proj4js.Point|{x:number, y:number}} point
 * @return {Proj4js.Point}
 */
Proj4js.transform = function(source, dest, point) {};
<commit_msg>Satisfy more recent gjslint<commit_after>/**
 * @externs
 * @see http://trac.osgeo.org/proj4js/
 */

/**
 * @type {Object}
 */
var Proj4js = {};

/**
 * @type {Object.<string, string>}
 */
Proj4js.defs;

/**
 * @type {function(string)}
 */
Proj4js.reportError;

/**
 * @constructor
 * @param {number} x
 * @param {number} y
 */
Proj4js.Point = function(x, y) {};

/**
 * @type {number}
 */
Proj4js.Point.prototype.x;

/**
 * @type {number}
 */
Proj4js.Point.prototype.y;

/**
 * @constructor
 * @param {string} srsCode
 * @param {Function=} opt_callback
 */
Proj4js.Proj = function(srsCode, opt_callback) {};

/**
 * @type {string}
 */
Proj4js.Proj.prototype.title;

/**
 * @type {string}
 */
Proj4js.Proj.prototype.units;

/**
 * @param {Proj4js.Proj} source
 * @param {Proj4js.Proj} dest
 * @param {Proj4js.Point|{x:number, y:number}} point
 * @return {Proj4js.Point}
 */
Proj4js.transform = function(source, dest, point) {};
<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Ajax.org Code Editor (ACE).
 *
 * The Initial Developer of the Original Code is
 * Ajax.org Services B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *      Fabian Jakobs <fabian AT ajax DOT org>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

define(function(require, exports, module) {

var core = require("./lib/core");
var event = require("./lib/event");
var default_mac = require("./conf/keybindings/default_mac");
var default_win = require("./conf/keybindings/default_win");
var PluginManager = require("./plugin_manager");

var KeyBinding = function(element, editor, config) {
    this.setConfig(config);

    var _self = this;
    event.addKeyListener(element, function(e) {
        var hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0)
            | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
        var key = _self.keyNames[e.keyCode];

        var commandName = (_self.config.reverse[hashId] || {})[(key
            || String.fromCharCode(e.keyCode)).toLowerCase()];
        var command = PluginManager.commands[commandName];

        if (command) {
            command(editor, editor.getSelection());
            return event.stopEvent(e);
        }
    });
};

(function() {
    this.keyMods = {"ctrl": 1, "alt": 2, "option" : 2, "shift": 4, "meta": 8, "command": 8};

    this.keyNames = {
        "8"  : "Backspace",
        "9"  : "Tab",
        "13" : "Enter",
        "27" : "Esc",
        "32" : "Space",
        "33" : "PageUp",
        "34" : "PageDown",
        "35" : "End",
        "36" : "Home",
        "37" : "Left",
        "38" : "Up",
        "39" : "Right",
        "40" : "Down",
        "45" : "Insert",
        "46" : "Delete",
        "107": "+",
        "112": "F1",
        "113": "F2",
        "114": "F3",
        "115": "F4",
        "116": "F5",
        "117": "F6",
        "118": "F7",
        "119": "F8",
        "120": "F9",
        "121": "F10",
        "122": "F11",
        "123": "F12"
    };

    function splitSafe(s, separator, limit, bLowerCase) {
        return (bLowerCase && s.toLowerCase() || s)
            .replace(/(?:^\s+|\n|\s+$)/g, "")
            .split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999);
    }

    function parseKeys(keys, val, ret) {
        var key,
            hashId = 0,
            parts  = splitSafe(keys, "\\-", null, true),
            i      = 0,
            l      = parts.length;

        for (; i < l; ++i) {
            if (this.keyMods[parts[i]])
                hashId = hashId | this.keyMods[parts[i]];
            else
                key = parts[i] || "-"; //when empty, the splitSafe removed a '-'
        }

        (ret[hashId] || (ret[hashId] = {}))[key] = val;
        return ret;
    }

    function objectReverse(obj, keySplit) {
        var i, j, l, key,
            ret = {};
        for (i in obj) {
            key = obj[i];
            if (keySplit && typeof key == "string") {
                key = key.split(keySplit);
                for (j = 0, l = key.length; j < l; ++j)
                    parseKeys.call(this, key[j], i, ret);
            }
            else {
                parseKeys.call(this, key, i, ret);
            }
        }
        return ret;
    }

    this.setConfig = function(config) {
        this.config = config || (core.isMac
            ? default_mac
            : default_win);
        if (typeof this.config.reverse == "undefined")
            this.config.reverse = objectReverse.call(this, this.config, "|");
    };

}).call(KeyBinding.prototype);

return KeyBinding;
});
<commit_msg>pre load default commands<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Ajax.org Code Editor (ACE).
 *
 * The Initial Developer of the Original Code is
 * Ajax.org Services B.V.
 * Portions created by the Initial Developer are Copyright (C) 2010
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *      Fabian Jakobs <fabian AT ajax DOT org>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

define(function(require, exports, module) {

var core = require("./lib/core");
var event = require("./lib/event");
var default_mac = require("./conf/keybindings/default_mac");
var default_win = require("./conf/keybindings/default_win");
var PluginManager = require("./plugin_manager");
require("./commands/default_commands");

var KeyBinding = function(element, editor, config) {
    this.setConfig(config);

    var _self = this;
    event.addKeyListener(element, function(e) {
        var hashId = 0 | (e.ctrlKey ? 1 : 0) | (e.altKey ? 2 : 0)
            | (e.shiftKey ? 4 : 0) | (e.metaKey ? 8 : 0);
        var key = _self.keyNames[e.keyCode];

        var commandName = (_self.config.reverse[hashId] || {})[(key
            || String.fromCharCode(e.keyCode)).toLowerCase()];
        var command = PluginManager.commands[commandName];

        if (command) {
            command(editor, editor.getSelection());
            return event.stopEvent(e);
        }
    });
};

(function() {
    this.keyMods = {"ctrl": 1, "alt": 2, "option" : 2, "shift": 4, "meta": 8, "command": 8};

    this.keyNames = {
        "8"  : "Backspace",
        "9"  : "Tab",
        "13" : "Enter",
        "27" : "Esc",
        "32" : "Space",
        "33" : "PageUp",
        "34" : "PageDown",
        "35" : "End",
        "36" : "Home",
        "37" : "Left",
        "38" : "Up",
        "39" : "Right",
        "40" : "Down",
        "45" : "Insert",
        "46" : "Delete",
        "107": "+",
        "112": "F1",
        "113": "F2",
        "114": "F3",
        "115": "F4",
        "116": "F5",
        "117": "F6",
        "118": "F7",
        "119": "F8",
        "120": "F9",
        "121": "F10",
        "122": "F11",
        "123": "F12"
    };

    function splitSafe(s, separator, limit, bLowerCase) {
        return (bLowerCase && s.toLowerCase() || s)
            .replace(/(?:^\s+|\n|\s+$)/g, "")
            .split(new RegExp("[\\s ]*" + separator + "[\\s ]*", "g"), limit || 999);
    }

    function parseKeys(keys, val, ret) {
        var key,
            hashId = 0,
            parts  = splitSafe(keys, "\\-", null, true),
            i      = 0,
            l      = parts.length;

        for (; i < l; ++i) {
            if (this.keyMods[parts[i]])
                hashId = hashId | this.keyMods[parts[i]];
            else
                key = parts[i] || "-"; //when empty, the splitSafe removed a '-'
        }

        (ret[hashId] || (ret[hashId] = {}))[key] = val;
        return ret;
    }

    function objectReverse(obj, keySplit) {
        var i, j, l, key,
            ret = {};
        for (i in obj) {
            key = obj[i];
            if (keySplit && typeof key == "string") {
                key = key.split(keySplit);
                for (j = 0, l = key.length; j < l; ++j)
                    parseKeys.call(this, key[j], i, ret);
            }
            else {
                parseKeys.call(this, key, i, ret);
            }
        }
        return ret;
    }

    this.setConfig = function(config) {
        this.config = config || (core.isMac
            ? default_mac
            : default_win);
        if (typeof this.config.reverse == "undefined")
            this.config.reverse = objectReverse.call(this, this.config, "|");
    };

}).call(KeyBinding.prototype);

return KeyBinding;
});
<|endoftext|>


<commit_before>/*eslint no-console: ["error", { allow: ["warn"] }] */
/**
 * @typedef {Object} MinimapPluginParams
 * @desc Extends the `WavesurferParams` wavesurfer was initialised with
 * @property {?string|HTMLElement} container CSS selector or HTML element where
 * the map should be rendered. By default it is simply appended
 * after the waveform.
 * @property {?boolean} deferInit Set to true to manually call
 * `initPlugin('minimap')`
 */

/**
 * Renders a smaller version waveform as a minimap of the main waveform.
 *
 * @implements {PluginClass}
 * @extends {Observer}
 * @example
 * // es6
 * import MinimapPlugin from 'wavesurfer.minimap.js';
 *
 * // commonjs
 * var MinimapPlugin = require('wavesurfer.minimap.js');
 *
 * // if you are using <script> tags
 * var MinimapPlugin = window.WaveSurfer.minimap;
 *
 * // ... initialising wavesurfer with the plugin
 * var wavesurfer = WaveSurfer.create({
 *   // wavesurfer options ...
 *   plugins: [
 *     MinimapPlugin.create({
 *       // plugin options ...
 *     })
 *   ]
 * });
 */
export default class MinimapPlugin {
    /**
     * Minimap plugin definition factory
     *
     * This function must be used to create a plugin definition which can be
     * used by wavesurfer to correctly instantiate the plugin.
     *
     * @param  {MinimapPluginParams} params parameters use to initialise the plugin
     * @return {PluginDefinition} an object representing the plugin
     */
    static create(params) {
        return {
            name: 'minimap',
            deferInit: params && params.deferInit ? params.deferInit : false,
            params: params,
            staticProps: {},
            instance: MinimapPlugin
        };
    }

    constructor(params, ws) {
        this.params = ws.util.extend(
            {},
            ws.params,
            {
                showRegions: false,
                showOverview: false,
                overviewBorderColor: 'green',
                overviewBorderSize: 2,
                // the container should be different
                container: false,
                height: Math.max(Math.round(ws.params.height / 4), 20)
            },
            params,
            {
                scrollParent: false,
                fillParent: true
            }
        );
        // if container is a selector, get the element
        if (typeof params.container === 'string') {
            const el = document.querySelector(params.container);
            if (!el) {
                console.warn(
                    `Wavesurfer minimap container ${params.container} was not found! The minimap will be automatically appended below the waveform.`
                );
            }
            this.params.container = el;
        }
        // if no container is specified add a new element and insert it
        if (!params.container) {
            this.params.container = ws.util.style(
                document.createElement('minimap'),
                {
                    display: 'block'
                }
            );
        }
        this.drawer = new ws.Drawer(this.params.container, this.params);
        this.wavesurfer = ws;
        this.util = ws.util;
        /**
         * Minimap needs to listen for the `ready` and `waveform-ready` events
         * to work with the `MediaElement` backend. The moment the `ready` event
         * is called is different (and peaks would not load).
         *
         * @type {string}
         * @see https://github.com/katspaugh/wavesurfer.js/issues/736
         */
        this.renderEvent =
            ws.params.backend === 'MediaElement' ? 'waveform-ready' : 'ready';
        this.overviewRegion = null;

        this.drawer.createWrapper();
        this.createElements();
        let isInitialised = false;

        // ws ready event listener
        this._onShouldRender = () => {
            // only bind the events in the first run
            if (!isInitialised) {
                this.bindWavesurferEvents();
                this.bindMinimapEvents();
                isInitialised = true;
            }
            // if there is no such element, append it to the container (below
            // the waveform)
            if (!document.body.contains(this.params.container)) {
                ws.container.insertBefore(this.params.container, null);
            }

            if (this.wavesurfer.regions && this.params.showRegions) {
                this.regions();
            }
            this.render();
        };

        this._onAudioprocess = currentTime => {
            this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
        };

        // ws seek event listener
        this._onSeek = () =>
            this.drawer.progress(ws.backend.getPlayedPercents());

        // event listeners for the overview region
        this._onScroll = e => {
            if (!this.draggingOverview) {
                this.moveOverviewRegion(e.target.scrollLeft / this.ratio);
            }
        };
        this._onMouseover = e => {
            if (this.draggingOverview) {
                this.draggingOverview = false;
            }
        };
        let prevWidth = 0;
        this._onResize = ws.util.debounce(() => {
            if (prevWidth != this.drawer.wrapper.clientWidth) {
                prevWidth = this.drawer.wrapper.clientWidth;
                this.render();
                this.drawer.progress(
                    this.wavesurfer.backend.getPlayedPercents()
                );
            }
        });
        this._onZoom = e => {
            this.render();
        };
        this.wavesurfer.on('zoom', this._onZoom);
    }

    init() {
        if (this.wavesurfer.isReady) {
            this._onShouldRender();
        }
        this.wavesurfer.on(this.renderEvent, this._onShouldRender);
    }

    destroy() {
        window.removeEventListener('resize', this._onResize, true);
        window.removeEventListener('orientationchange', this._onResize, true);
        this.wavesurfer.drawer.wrapper.removeEventListener(
            'mouseover',
            this._onMouseover
        );
        this.wavesurfer.un(this.renderEvent, this._onShouldRender);
        this.wavesurfer.un('seek', this._onSeek);
        this.wavesurfer.un('scroll', this._onScroll);
        this.wavesurfer.un('audioprocess', this._onAudioprocess);
        this.wavesurfer.un('zoom', this._onZoom);
        this.drawer.destroy();
        this.overviewRegion = null;
        this.unAll();
    }

    regions() {
        this.regions = {};

        this.wavesurfer.on('region-created', region => {
            this.regions[region.id] = region;
            this.renderRegions();
        });

        this.wavesurfer.on('region-updated', region => {
            this.regions[region.id] = region;
            this.renderRegions();
        });

        this.wavesurfer.on('region-removed', region => {
            delete this.regions[region.id];
            this.renderRegions();
        });
    }

    renderRegions() {
        const regionElements = this.drawer.wrapper.querySelectorAll('region');
        let i;
        for (i = 0; i < regionElements.length; ++i) {
            this.drawer.wrapper.removeChild(regionElements[i]);
        }

        Object.keys(this.regions).forEach(id => {
            const region = this.regions[id];
            const width =
                this.drawer.width *
                ((region.end - region.start) / this.wavesurfer.getDuration());
            const left =
                this.drawer.width *
                (region.start / this.wavesurfer.getDuration());
            const regionElement = this.util.style(
                document.createElement('region'),
                {
                    height: 'inherit',
                    backgroundColor: region.color,
                    width: width + 'px',
                    left: left + 'px',
                    display: 'block',
                    position: 'absolute'
                }
            );
            regionElement.classList.add(id);
            this.drawer.wrapper.appendChild(regionElement);
        });
    }

    createElements() {
        this.drawer.createElements();
        if (this.params.showOverview) {
            this.overviewRegion = this.util.style(
                document.createElement('overview'),
                {
                    top: 0,
                    bottom: 0,
                    width: '0px',
                    display: 'block',
                    position: 'absolute',
                    cursor: 'move',
                    border:
                        this.params.overviewBorderSize +
                        'px solid ' +
                        this.params.overviewBorderColor,
                    zIndex: 2,
                    opacity: this.params.overviewOpacity
                }
            );
            this.drawer.wrapper.appendChild(this.overviewRegion);
        }
    }

    bindWavesurferEvents() {
        window.addEventListener('resize', this._onResize, true);
        window.addEventListener('orientationchange', this._onResize, true);
        this.wavesurfer.on('audioprocess', this._onAudioprocess);
        this.wavesurfer.on('seek', this._onSeek);
        if (this.params.showOverview) {
            this.wavesurfer.on('scroll', this._onScroll);
            this.wavesurfer.drawer.wrapper.addEventListener(
                'mouseover',
                this._onMouseover
            );
        }
    }

    bindMinimapEvents() {
        const positionMouseDown = {
            clientX: 0,
            clientY: 0
        };
        let relativePositionX = 0;
        let seek = true;

        // the following event listeners will be destroyed by using
        // this.unAll() and nullifying the DOM node references after
        // removing them
        if (this.params.interact) {
            this.drawer.wrapper.addEventListener('click', event => {
                this.fireEvent('click', event, this.drawer.handleEvent(event));
            });

            this.on('click', (event, position) => {
                if (seek) {
                    this.drawer.progress(position);
                    this.wavesurfer.seekAndCenter(position);
                } else {
                    seek = true;
                }
            });
        }

        if (this.params.showOverview) {
            this.overviewRegion.addEventListener('mousedown', event => {
                this.draggingOverview = true;
                relativePositionX = event.layerX;
                positionMouseDown.clientX = event.clientX;
                positionMouseDown.clientY = event.clientY;
            });

            this.drawer.wrapper.addEventListener('mousemove', event => {
                if (this.draggingOverview) {
                    this.moveOverviewRegion(
                        event.clientX -
                            this.drawer.container.getBoundingClientRect().left -
                            relativePositionX
                    );
                }
            });

            this.drawer.wrapper.addEventListener('mouseup', event => {
                if (
                    positionMouseDown.clientX - event.clientX === 0 &&
                    positionMouseDown.clientX - event.clientX === 0
                ) {
                    seek = true;
                    this.draggingOverview = false;
                } else if (this.draggingOverview) {
                    seek = false;
                    this.draggingOverview = false;
                }
            });
        }
    }

    render() {
        const len = this.drawer.getWidth();
        const peaks = this.wavesurfer.backend.getPeaks(len, 0, len);
        this.drawer.drawPeaks(peaks, len, 0, len);
        this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());

        if (this.params.showOverview) {
            //get proportional width of overview region considering the respective
            //width of the drawers
            this.ratio = this.wavesurfer.drawer.width / this.drawer.width;
            this.waveShowedWidth = this.wavesurfer.drawer.width / this.ratio;
            this.waveWidth = this.wavesurfer.drawer.width;
            this.overviewWidth = this.drawer.container.offsetWidth / this.ratio;
            this.overviewPosition = 0;
            this.moveOverviewRegion(
                this.wavesurfer.drawer.wrapper.scrollLeft / this.ratio
            );
            this.overviewRegion.style.width = this.overviewWidth + 'px';
        }
    }

    moveOverviewRegion(pixels) {
        if (pixels < 0) {
            this.overviewPosition = 0;
        } else if (
            pixels + this.overviewWidth <
            this.drawer.container.offsetWidth
        ) {
            this.overviewPosition = pixels;
        } else {
            this.overviewPosition =
                this.drawer.container.offsetWidth - this.overviewWidth;
        }
        this.overviewRegion.style.left = this.overviewPosition + 'px';
        if (this.draggingOverview) {
            this.wavesurfer.drawer.wrapper.scrollLeft =
                this.overviewPosition * this.ratio;
        }
    }
}
<commit_msg>minimap plugin: fix left/width calculations for regions on retina/4k screens (#1743)<commit_after>/*eslint no-console: ["error", { allow: ["warn"] }] */
/**
 * @typedef {Object} MinimapPluginParams
 * @desc Extends the `WavesurferParams` wavesurfer was initialised with
 * @property {?string|HTMLElement} container CSS selector or HTML element where
 * the map should be rendered. By default it is simply appended
 * after the waveform.
 * @property {?boolean} deferInit Set to true to manually call
 * `initPlugin('minimap')`
 */

/**
 * Renders a smaller version waveform as a minimap of the main waveform.
 *
 * @implements {PluginClass}
 * @extends {Observer}
 * @example
 * // es6
 * import MinimapPlugin from 'wavesurfer.minimap.js';
 *
 * // commonjs
 * var MinimapPlugin = require('wavesurfer.minimap.js');
 *
 * // if you are using <script> tags
 * var MinimapPlugin = window.WaveSurfer.minimap;
 *
 * // ... initialising wavesurfer with the plugin
 * var wavesurfer = WaveSurfer.create({
 *   // wavesurfer options ...
 *   plugins: [
 *     MinimapPlugin.create({
 *       // plugin options ...
 *     })
 *   ]
 * });
 */
export default class MinimapPlugin {
    /**
     * Minimap plugin definition factory
     *
     * This function must be used to create a plugin definition which can be
     * used by wavesurfer to correctly instantiate the plugin.
     *
     * @param  {MinimapPluginParams} params parameters use to initialise the plugin
     * @return {PluginDefinition} an object representing the plugin
     */
    static create(params) {
        return {
            name: 'minimap',
            deferInit: params && params.deferInit ? params.deferInit : false,
            params: params,
            staticProps: {},
            instance: MinimapPlugin
        };
    }

    constructor(params, ws) {
        this.params = ws.util.extend(
            {},
            ws.params,
            {
                showRegions: false,
                showOverview: false,
                overviewBorderColor: 'green',
                overviewBorderSize: 2,
                // the container should be different
                container: false,
                height: Math.max(Math.round(ws.params.height / 4), 20)
            },
            params,
            {
                scrollParent: false,
                fillParent: true
            }
        );
        // if container is a selector, get the element
        if (typeof params.container === 'string') {
            const el = document.querySelector(params.container);
            if (!el) {
                console.warn(
                    `Wavesurfer minimap container ${params.container} was not found! The minimap will be automatically appended below the waveform.`
                );
            }
            this.params.container = el;
        }
        // if no container is specified add a new element and insert it
        if (!params.container) {
            this.params.container = ws.util.style(
                document.createElement('minimap'),
                {
                    display: 'block'
                }
            );
        }
        this.drawer = new ws.Drawer(this.params.container, this.params);
        this.wavesurfer = ws;
        this.util = ws.util;
        /**
         * Minimap needs to listen for the `ready` and `waveform-ready` events
         * to work with the `MediaElement` backend. The moment the `ready` event
         * is called is different (and peaks would not load).
         *
         * @type {string}
         * @see https://github.com/katspaugh/wavesurfer.js/issues/736
         */
        this.renderEvent =
            ws.params.backend === 'MediaElement' ? 'waveform-ready' : 'ready';
        this.overviewRegion = null;

        this.drawer.createWrapper();
        this.createElements();
        let isInitialised = false;

        // ws ready event listener
        this._onShouldRender = () => {
            // only bind the events in the first run
            if (!isInitialised) {
                this.bindWavesurferEvents();
                this.bindMinimapEvents();
                isInitialised = true;
            }
            // if there is no such element, append it to the container (below
            // the waveform)
            if (!document.body.contains(this.params.container)) {
                ws.container.insertBefore(this.params.container, null);
            }

            if (this.wavesurfer.regions && this.params.showRegions) {
                this.regions();
            }
            this.render();
        };

        this._onAudioprocess = currentTime => {
            this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());
        };

        // ws seek event listener
        this._onSeek = () =>
            this.drawer.progress(ws.backend.getPlayedPercents());

        // event listeners for the overview region
        this._onScroll = e => {
            if (!this.draggingOverview) {
                this.moveOverviewRegion(e.target.scrollLeft / this.ratio);
            }
        };
        this._onMouseover = e => {
            if (this.draggingOverview) {
                this.draggingOverview = false;
            }
        };
        let prevWidth = 0;
        this._onResize = ws.util.debounce(() => {
            if (prevWidth != this.drawer.wrapper.clientWidth) {
                prevWidth = this.drawer.wrapper.clientWidth;
                this.render();
                this.drawer.progress(
                    this.wavesurfer.backend.getPlayedPercents()
                );
            }
        });
        this._onZoom = e => {
            this.render();
        };
        this.wavesurfer.on('zoom', this._onZoom);
    }

    init() {
        if (this.wavesurfer.isReady) {
            this._onShouldRender();
        }
        this.wavesurfer.on(this.renderEvent, this._onShouldRender);
    }

    destroy() {
        window.removeEventListener('resize', this._onResize, true);
        window.removeEventListener('orientationchange', this._onResize, true);
        this.wavesurfer.drawer.wrapper.removeEventListener(
            'mouseover',
            this._onMouseover
        );
        this.wavesurfer.un(this.renderEvent, this._onShouldRender);
        this.wavesurfer.un('seek', this._onSeek);
        this.wavesurfer.un('scroll', this._onScroll);
        this.wavesurfer.un('audioprocess', this._onAudioprocess);
        this.wavesurfer.un('zoom', this._onZoom);
        this.drawer.destroy();
        this.overviewRegion = null;
        this.unAll();
    }

    regions() {
        this.regions = {};

        this.wavesurfer.on('region-created', region => {
            this.regions[region.id] = region;
            this.renderRegions();
        });

        this.wavesurfer.on('region-updated', region => {
            this.regions[region.id] = region;
            this.renderRegions();
        });

        this.wavesurfer.on('region-removed', region => {
            delete this.regions[region.id];
            this.renderRegions();
        });
    }

    renderRegions() {
        const regionElements = this.drawer.wrapper.querySelectorAll('region');
        let i;
        for (i = 0; i < regionElements.length; ++i) {
            this.drawer.wrapper.removeChild(regionElements[i]);
        }

        Object.keys(this.regions).forEach(id => {
            const region = this.regions[id];
            const width =
                this.getWidth() *
                ((region.end - region.start) / this.wavesurfer.getDuration());
            const left =
                this.getWidth() *
                (region.start / this.wavesurfer.getDuration());
            const regionElement = this.util.style(
                document.createElement('region'),
                {
                    height: 'inherit',
                    backgroundColor: region.color,
                    width: width + 'px',
                    left: left + 'px',
                    display: 'block',
                    position: 'absolute'
                }
            );
            regionElement.classList.add(id);
            this.drawer.wrapper.appendChild(regionElement);
        });
    }

    createElements() {
        this.drawer.createElements();
        if (this.params.showOverview) {
            this.overviewRegion = this.util.style(
                document.createElement('overview'),
                {
                    top: 0,
                    bottom: 0,
                    width: '0px',
                    display: 'block',
                    position: 'absolute',
                    cursor: 'move',
                    border:
                        this.params.overviewBorderSize +
                        'px solid ' +
                        this.params.overviewBorderColor,
                    zIndex: 2,
                    opacity: this.params.overviewOpacity
                }
            );
            this.drawer.wrapper.appendChild(this.overviewRegion);
        }
    }

    bindWavesurferEvents() {
        window.addEventListener('resize', this._onResize, true);
        window.addEventListener('orientationchange', this._onResize, true);
        this.wavesurfer.on('audioprocess', this._onAudioprocess);
        this.wavesurfer.on('seek', this._onSeek);
        if (this.params.showOverview) {
            this.wavesurfer.on('scroll', this._onScroll);
            this.wavesurfer.drawer.wrapper.addEventListener(
                'mouseover',
                this._onMouseover
            );
        }
    }

    bindMinimapEvents() {
        const positionMouseDown = {
            clientX: 0,
            clientY: 0
        };
        let relativePositionX = 0;
        let seek = true;

        // the following event listeners will be destroyed by using
        // this.unAll() and nullifying the DOM node references after
        // removing them
        if (this.params.interact) {
            this.drawer.wrapper.addEventListener('click', event => {
                this.fireEvent('click', event, this.drawer.handleEvent(event));
            });

            this.on('click', (event, position) => {
                if (seek) {
                    this.drawer.progress(position);
                    this.wavesurfer.seekAndCenter(position);
                } else {
                    seek = true;
                }
            });
        }

        if (this.params.showOverview) {
            this.overviewRegion.addEventListener('mousedown', event => {
                this.draggingOverview = true;
                relativePositionX = event.layerX;
                positionMouseDown.clientX = event.clientX;
                positionMouseDown.clientY = event.clientY;
            });

            this.drawer.wrapper.addEventListener('mousemove', event => {
                if (this.draggingOverview) {
                    this.moveOverviewRegion(
                        event.clientX -
                            this.drawer.container.getBoundingClientRect().left -
                            relativePositionX
                    );
                }
            });

            this.drawer.wrapper.addEventListener('mouseup', event => {
                if (
                    positionMouseDown.clientX - event.clientX === 0 &&
                    positionMouseDown.clientX - event.clientX === 0
                ) {
                    seek = true;
                    this.draggingOverview = false;
                } else if (this.draggingOverview) {
                    seek = false;
                    this.draggingOverview = false;
                }
            });
        }
    }

    render() {
        const len = this.drawer.getWidth();
        const peaks = this.wavesurfer.backend.getPeaks(len, 0, len);
        this.drawer.drawPeaks(peaks, len, 0, len);
        this.drawer.progress(this.wavesurfer.backend.getPlayedPercents());

        if (this.params.showOverview) {
            //get proportional width of overview region considering the respective
            //width of the drawers
            this.ratio = this.wavesurfer.drawer.width / this.drawer.width;
            this.waveShowedWidth = this.wavesurfer.drawer.width / this.ratio;
            this.waveWidth = this.wavesurfer.drawer.width;
            this.overviewWidth = this.drawer.container.offsetWidth / this.ratio;
            this.overviewPosition = 0;
            this.moveOverviewRegion(
                this.wavesurfer.drawer.wrapper.scrollLeft / this.ratio
            );
            this.overviewRegion.style.width = this.overviewWidth + 'px';
        }
    }

    moveOverviewRegion(pixels) {
        if (pixels < 0) {
            this.overviewPosition = 0;
        } else if (
            pixels + this.overviewWidth <
            this.drawer.container.offsetWidth
        ) {
            this.overviewPosition = pixels;
        } else {
            this.overviewPosition =
                this.drawer.container.offsetWidth - this.overviewWidth;
        }
        this.overviewRegion.style.left = this.overviewPosition + 'px';
        if (this.draggingOverview) {
            this.wavesurfer.drawer.wrapper.scrollLeft =
                this.overviewPosition * this.ratio;
        }
    }

    getWidth() {
        return this.drawer.width / this.params.pixelRatio;
    }
}
<|endoftext|>