<commit_before>/* jshint -W117 */
function TraceablePeerConnection(ice_config, constraints) {
    var self = this;
    var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
    this.peerconnection = new RTCPeerconnection(ice_config, constraints);
    this.updateLog = [];
    this.stats = {};
    this.statsinterval = null;
    this.maxstats = 300; // limit to 300 values, i.e. 5 minutes; set to 0 to disable

    // override as desired
    this.trace = function(what, info) {
        //console.warn('WTRACE', what, info);
        self.updateLog.push({
            time: new Date(),
            type: what,
            value: info || ""
        });
    };
    this.onicecandidate = null;
    this.peerconnection.onicecandidate = function (event) {
        self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
        if (self.onicecandidate !== null) {
            self.onicecandidate(event);
        }
    };
    this.onaddstream = null;
    this.peerconnection.onaddstream = function (event) {
        self.trace('onaddstream', event.stream.id);
        if (self.onaddstream !== null) {
            self.onaddstream(event);
        }
    };
    this.onremovestream = null;
    this.peerconnection.onremovestream = function (event) {
        self.trace('onremovestream', event.stream.id);
        if (self.onremovestream !== null) {
            self.onremovestream(event);
        }
    };
    this.onsignalingstatechange = null;
    this.peerconnection.onsignalingstatechange = function (event) {
        self.trace('onsignalingstatechange', self.signalingState);
        if (self.onsignalingstatechange !== null) {
            self.onsignalingstatechange(event);
        }
    };
    this.oniceconnectionstatechange = null;
    this.peerconnection.oniceconnectionstatechange = function (event) {
        self.trace('oniceconnectionstatechange', self.iceConnectionState);
        if (self.oniceconnectionstatechange !== null) {
            self.oniceconnectionstatechange(event);
        }
    };
    this.onnegotiationneeded = null;
    this.peerconnection.onnegotiationneeded = function (event) {
        self.trace('onnegotiationneeded');
        if (self.onnegotiationneeded !== null) {
            self.onnegotiationneeded(event);
        }
    };
    self.ondatachannel = null;
    this.peerconnection.ondatachannel = function (event) {
        self.trace('ondatachannel', event);
        if (self.ondatachannel !== null) {
            self.ondatachannel(event);
        }
    };
    if (!navigator.mozGetUserMedia) {
        this.statsinterval = window.setInterval(function() {
            self.peerconnection.getStats(function(stats) {
                var results = stats.result();
                for (var i = 0; i < results.length; ++i) {
                    //console.log(results[i].type, results[i].id, results[i].names())
                    var now = new Date();
                    results[i].names().forEach(function (name) {
                        var id = results[i].id + '-' + name;
                        if (!self.stats[id]) {
                            self.stats[id] = {
                                startTime: now,
                                endTime: now,
                                values: [],
                                times: []
                            };
                        }
                        self.stats[id].values.push(results[i].stat(name));
                        self.stats[id].times.push(now.getTime());
                        if (self.stats[id].values.length > self.maxstats) {
                            self.stats[id].values.shift();
                            self.stats[id].times.shift();
                        }
                        self.stats[id].endTime = now;
                    });
                }
            });

        }, 1000);
    }
}

dumpSDP = function(description) {
    return 'type: ' + description.type + '\r\n' + description.sdp;
};

Object.defineProperty(TraceablePeerConnection.prototype, 'signalingState', {
    get: function () {
        return this.peerconnection.signalingState;
    }
});

Object.defineProperty(PeerConnection.prototype, 'iceConnectionState', {
    get: function () {
        return this.pc.iceConnectionState;
    }
});

Object.defineProperty(PeerConnection.prototype, 'localDescription', {
    get: function () {
        return this.peerconnection.localDescription;
    }
});

Object.defineProperty(PeerConnection.prototype, 'remoteDescription', {
    get: function () {
        return this.peerconnection.remoteDescription;
    }
});

TraceablePeerConnection.prototype.addStream = function (stream) {
    this.trace('addStream', stream.id);
    this.peerconnection.addStream(stream);
};

TraceablePeerConnection.prototype.removeStream = function (stream) {
    this.trace('removeStream', stream.id);
    this.peerconnection.removeStream(stream);
};

TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
    this.trace('createDataChannel', label, opts);
    this.peerconnection.createDataChannel(label, opts);
};

TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
    var self = this;
    this.trace('setLocalDescription', dumpSDP(description));
    this.peerconnection.setLocalDescription(description,
        function () {
            self.trace('setLocalDescriptionOnSuccess');
            successCallback();
        },
        function (err) {
            self.trace('setLocalDescriptionOnFailure', err);
            failureCallback(err);
        }
    );
    /*
    if (this.statsinterval === null && this.maxstats > 0) {
        // start gathering stats
    }
    */
};

TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
    var self = this;
    this.trace('setRemoteDescription', dumpSDP(description));
    this.peerconnection.setRemoteDescription(description,
        function () {
            self.trace('setRemoteDescriptionOnSuccess');
            successCallback();
        },
        function (err) {
            self.trace('setRemoteDescriptionOnFailure', err);
            failureCallback(err);
        }
    );
    /*
    if (this.statsinterval === null && this.maxstats > 0) {
        // start gathering stats
    }
    */
};

TraceablePeerConnection.prototype.close = function () {
    this.trace('stop');
    if (this.statsinterval !== null) {
        window.clearInterval(this.statsinterval);
        this.statsinterval = null;
    }
    this.peerconnection.close();
};

TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
    var self = this;
    this.trace('createOffer', JSON.stringify(constraints, null, ' '));
    this.peerconnection.createOffer(
        function (offer) {
            self.trace('createOfferOnSuccess', dumpSDP(offer));
            successCallback(offer);
        },
        function(err) {
            self.trace('createOfferOnFailure', err);
            failureCallback(err);
        },
        constraints
    );
};

TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
    var self = this;
    this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
    this.peerconnection.createAnswer(
        function (answer) {
            self.trace('createAnswerOnSuccess', dumpSDP(answer));
            successCallback(answer);
        },
        function(err) {
            self.trace('createAnswerOnFailure', err);
            failureCallback(err);
        },
        constraints
    );
};

TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
    var self = this;
    this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
    this.peerconnection.addIceCandidate(candidate);
    this.peerconnection.addIceCandidate(candidate,
        function () {
            self.trace('addIceCandidateOnSuccess');
            if (successCallback) successCallback();
        },
        function (err) {
            self.trace('addIceCandidateOnFailure', err);
            if (failureCallback) failureCallback(err);
        }
    );
};

TraceablePeerConnection.prototype.getStats = function(callback) {
    if (navigator.mozGetUserMedia) {
        // ignore for now...
    } else {
        this.peerconnection.getStats(callback);
    }
};

// mozilla chrome compat layer -- very similar to adapter.js
function setupRTC() {
    var RTC = null;
    if (navigator.mozGetUserMedia && mozRTCPeerConnection) {
        console.log('This appears to be Firefox');
        var version = navigator.userAgent.match(/Firefox/) ? parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10) : 0;
        if (version >= 22) {
            RTC = {
                peerconnection: mozRTCPeerConnection,
                browser: 'firefox',
                getUserMedia: navigator.mozGetUserMedia.bind(navigator),
                attachMediaStream: function (element, stream) {
                    element[0].mozSrcObject = stream;
                    element[0].play();
                },
                pc_constraints: {}
            };
            RTCSessionDescription = mozRTCSessionDescription;
            RTCIceCandidate = mozRTCIceCandidate;
        }
    } else if (navigator.webkitGetUserMedia) {
        console.log('This appears to be Chrome');
        RTC = {
            peerconnection: webkitRTCPeerConnection,
            browser: 'chrome',
            getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
            attachMediaStream: function (element, stream) {
                element.attr('src', webkitURL.createObjectURL(stream));
            },
            // DTLS should now be enabled by default but..
            pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}
        };
    }
    if (RTC === null) {
        try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
    }
    return RTC;
}

function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
    var constraints = {audio: false, video: false};

    if (um.indexOf('video') >= 0) {
        constraints.video = {mandatory: {}};// same behaviour as true
    }
    if (um.indexOf('audio') >= 0) {
        constraints.audio = {};// same behaviour as true
    }
    if (um.indexOf('screen') >= 0) {
        constraints.video = {
            "mandatory": {
                "chromeMediaSource": "screen"
            }
        };
    }

    if (resolution && !constraints.video) {
        constraints.video = {mandatory: {}};// same behaviour as true
    }
    // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
    switch (resolution) {
    // 16:9 first
    case '1080':
    case 'fullhd':
        constraints.video.mandatory.minWidth = 1920;
        constraints.video.mandatory.minHeight = 1080;
        break;
    case '720':
    case 'hd':
        constraints.video.mandatory.minWidth = 1280;
        constraints.video.mandatory.minHeight = 720;
        break;
    case '360':
        constraints.video.mandatory.minWidth = 640;
        constraints.video.mandatory.minHeight = 360;
        break;
    case '180':
        constraints.video.mandatory.minWidth = 320;
        constraints.video.mandatory.minHeight = 180;
        break;
        // 4:3
    case '960':
        constraints.video.mandatory.minWidth = 960;
        constraints.video.mandatory.minHeight = 720;
        break;
    case '640':
    case 'vga':
        constraints.video.mandatory.minWidth = 640;
        constraints.video.mandatory.minHeight = 480;
        break;
    case '320':
        constraints.video.mandatory.minWidth = 320;
        constraints.video.mandatory.minHeight = 240;
        break;
    default:
        if (navigator.userAgent.indexOf('Android') != -1) {
            constraints.video.mandatory.minWidth = 320;
            constraints.video.mandatory.minHeight = 240;
            constraints.video.mandatory.maxFrameRate = 15;
        }
        break;
    }

    // take what is configured and try not to be more intelligent
    if (constraints.video.minWidth) constraints.video.maxWidth = constraints.video.minWidth;
    if (constraints.video.minHeight) constraints.video.maxHeight = constraints.video.minHeight;

    if (bandwidth) { // doesn't work currently, see webrtc issue 1846
        if (!constraints.video) constraints.video = {mandatory: {}};//same behaviour as true
        constraints.video.optional = [{bandwidth: bandwidth}];
    }
    if (fps) { // for some cameras it might be necessary to request 30fps
        // so they choose 30fps mjpg over 10fps yuy2
        if (!constraints.video) constraints.video = {mandatory: {}};// same behaviour as tru;
        constraints.video.mandatory.minFrameRate = fps;
    }

    try {
        RTC.getUserMedia(constraints,
                function (stream) {
                    console.log('onUserMediaSuccess');
                    $(document).trigger('mediaready.jingle', [stream]);
                },
                function (error) {
                    console.warn('Failed to get access to local media. Error ', error);
                    $(document).trigger('mediafailure.jingle');
                });
    } catch (e) {
        console.error('GUM failed: ', e);
        $(document).trigger('mediafailure.jingle');
    }
}
<commit_msg>fix copy paste; refactor away<commit_after>/* jshint -W117 */
function TraceablePeerConnection(ice_config, constraints) {
    var self = this;
    var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
    this.peerconnection = new RTCPeerconnection(ice_config, constraints);
    this.updateLog = [];
    this.stats = {};
    this.statsinterval = null;
    this.maxstats = 300; // limit to 300 values, i.e. 5 minutes; set to 0 to disable

    // override as desired
    this.trace = function(what, info) {
        //console.warn('WTRACE', what, info);
        self.updateLog.push({
            time: new Date(),
            type: what,
            value: info || ""
        });
    };
    this.onicecandidate = null;
    this.peerconnection.onicecandidate = function (event) {
        self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
        if (self.onicecandidate !== null) {
            self.onicecandidate(event);
        }
    };
    this.onaddstream = null;
    this.peerconnection.onaddstream = function (event) {
        self.trace('onaddstream', event.stream.id);
        if (self.onaddstream !== null) {
            self.onaddstream(event);
        }
    };
    this.onremovestream = null;
    this.peerconnection.onremovestream = function (event) {
        self.trace('onremovestream', event.stream.id);
        if (self.onremovestream !== null) {
            self.onremovestream(event);
        }
    };
    this.onsignalingstatechange = null;
    this.peerconnection.onsignalingstatechange = function (event) {
        self.trace('onsignalingstatechange', self.signalingState);
        if (self.onsignalingstatechange !== null) {
            self.onsignalingstatechange(event);
        }
    };
    this.oniceconnectionstatechange = null;
    this.peerconnection.oniceconnectionstatechange = function (event) {
        self.trace('oniceconnectionstatechange', self.iceConnectionState);
        if (self.oniceconnectionstatechange !== null) {
            self.oniceconnectionstatechange(event);
        }
    };
    this.onnegotiationneeded = null;
    this.peerconnection.onnegotiationneeded = function (event) {
        self.trace('onnegotiationneeded');
        if (self.onnegotiationneeded !== null) {
            self.onnegotiationneeded(event);
        }
    };
    self.ondatachannel = null;
    this.peerconnection.ondatachannel = function (event) {
        self.trace('ondatachannel', event);
        if (self.ondatachannel !== null) {
            self.ondatachannel(event);
        }
    };
    if (!navigator.mozGetUserMedia) {
        this.statsinterval = window.setInterval(function() {
            self.peerconnection.getStats(function(stats) {
                var results = stats.result();
                for (var i = 0; i < results.length; ++i) {
                    //console.log(results[i].type, results[i].id, results[i].names())
                    var now = new Date();
                    results[i].names().forEach(function (name) {
                        var id = results[i].id + '-' + name;
                        if (!self.stats[id]) {
                            self.stats[id] = {
                                startTime: now,
                                endTime: now,
                                values: [],
                                times: []
                            };
                        }
                        self.stats[id].values.push(results[i].stat(name));
                        self.stats[id].times.push(now.getTime());
                        if (self.stats[id].values.length > self.maxstats) {
                            self.stats[id].values.shift();
                            self.stats[id].times.shift();
                        }
                        self.stats[id].endTime = now;
                    });
                }
            });

        }, 1000);
    }
}

dumpSDP = function(description) {
    return 'type: ' + description.type + '\r\n' + description.sdp;
};

['signalingState', 'iceConnectionState', 'localDescription', 'remoteDescription'].forEach(function (prop) {
    Object.defineProperty(TraceablePeerConnection.prototype, prop, {
        get: function () {
            return this.peerconnection[prop]
        }
    });
});

TraceablePeerConnection.prototype.addStream = function (stream) {
    this.trace('addStream', stream.id);
    this.peerconnection.addStream(stream);
};

TraceablePeerConnection.prototype.removeStream = function (stream) {
    this.trace('removeStream', stream.id);
    this.peerconnection.removeStream(stream);
};

TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
    this.trace('createDataChannel', label, opts);
    this.peerconnection.createDataChannel(label, opts);
};

TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
    var self = this;
    this.trace('setLocalDescription', dumpSDP(description));
    this.peerconnection.setLocalDescription(description,
        function () {
            self.trace('setLocalDescriptionOnSuccess');
            successCallback();
        },
        function (err) {
            self.trace('setLocalDescriptionOnFailure', err);
            failureCallback(err);
        }
    );
    /*
    if (this.statsinterval === null && this.maxstats > 0) {
        // start gathering stats
    }
    */
};

TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
    var self = this;
    this.trace('setRemoteDescription', dumpSDP(description));
    this.peerconnection.setRemoteDescription(description,
        function () {
            self.trace('setRemoteDescriptionOnSuccess');
            successCallback();
        },
        function (err) {
            self.trace('setRemoteDescriptionOnFailure', err);
            failureCallback(err);
        }
    );
    /*
    if (this.statsinterval === null && this.maxstats > 0) {
        // start gathering stats
    }
    */
};

TraceablePeerConnection.prototype.close = function () {
    this.trace('stop');
    if (this.statsinterval !== null) {
        window.clearInterval(this.statsinterval);
        this.statsinterval = null;
    }
    this.peerconnection.close();
};

TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
    var self = this;
    this.trace('createOffer', JSON.stringify(constraints, null, ' '));
    this.peerconnection.createOffer(
        function (offer) {
            self.trace('createOfferOnSuccess', dumpSDP(offer));
            successCallback(offer);
        },
        function(err) {
            self.trace('createOfferOnFailure', err);
            failureCallback(err);
        },
        constraints
    );
};

TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
    var self = this;
    this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
    this.peerconnection.createAnswer(
        function (answer) {
            self.trace('createAnswerOnSuccess', dumpSDP(answer));
            successCallback(answer);
        },
        function(err) {
            self.trace('createAnswerOnFailure', err);
            failureCallback(err);
        },
        constraints
    );
};

TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
    var self = this;
    this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
    this.peerconnection.addIceCandidate(candidate);
    this.peerconnection.addIceCandidate(candidate,
        function () {
            self.trace('addIceCandidateOnSuccess');
            if (successCallback) successCallback();
        },
        function (err) {
            self.trace('addIceCandidateOnFailure', err);
            if (failureCallback) failureCallback(err);
        }
    );
};

TraceablePeerConnection.prototype.getStats = function(callback) {
    if (navigator.mozGetUserMedia) {
        // ignore for now...
    } else {
        this.peerconnection.getStats(callback);
    }
};

// mozilla chrome compat layer -- very similar to adapter.js
function setupRTC() {
    var RTC = null;
    if (navigator.mozGetUserMedia && mozRTCPeerConnection) {
        console.log('This appears to be Firefox');
        var version = navigator.userAgent.match(/Firefox/) ? parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10) : 0;
        if (version >= 22) {
            RTC = {
                peerconnection: mozRTCPeerConnection,
                browser: 'firefox',
                getUserMedia: navigator.mozGetUserMedia.bind(navigator),
                attachMediaStream: function (element, stream) {
                    element[0].mozSrcObject = stream;
                    element[0].play();
                },
                pc_constraints: {}
            };
            RTCSessionDescription = mozRTCSessionDescription;
            RTCIceCandidate = mozRTCIceCandidate;
        }
    } else if (navigator.webkitGetUserMedia) {
        console.log('This appears to be Chrome');
        RTC = {
            peerconnection: webkitRTCPeerConnection,
            browser: 'chrome',
            getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
            attachMediaStream: function (element, stream) {
                element.attr('src', webkitURL.createObjectURL(stream));
            },
            // DTLS should now be enabled by default but..
            pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}
        };
    }
    if (RTC === null) {
        try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
    }
    return RTC;
}

function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
    var constraints = {audio: false, video: false};

    if (um.indexOf('video') >= 0) {
        constraints.video = {mandatory: {}};// same behaviour as true
    }
    if (um.indexOf('audio') >= 0) {
        constraints.audio = {};// same behaviour as true
    }
    if (um.indexOf('screen') >= 0) {
        constraints.video = {
            "mandatory": {
                "chromeMediaSource": "screen"
            }
        };
    }

    if (resolution && !constraints.video) {
        constraints.video = {mandatory: {}};// same behaviour as true
    }
    // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
    switch (resolution) {
    // 16:9 first
    case '1080':
    case 'fullhd':
        constraints.video.mandatory.minWidth = 1920;
        constraints.video.mandatory.minHeight = 1080;
        break;
    case '720':
    case 'hd':
        constraints.video.mandatory.minWidth = 1280;
        constraints.video.mandatory.minHeight = 720;
        break;
    case '360':
        constraints.video.mandatory.minWidth = 640;
        constraints.video.mandatory.minHeight = 360;
        break;
    case '180':
        constraints.video.mandatory.minWidth = 320;
        constraints.video.mandatory.minHeight = 180;
        break;
        // 4:3
    case '960':
        constraints.video.mandatory.minWidth = 960;
        constraints.video.mandatory.minHeight = 720;
        break;
    case '640':
    case 'vga':
        constraints.video.mandatory.minWidth = 640;
        constraints.video.mandatory.minHeight = 480;
        break;
    case '320':
        constraints.video.mandatory.minWidth = 320;
        constraints.video.mandatory.minHeight = 240;
        break;
    default:
        if (navigator.userAgent.indexOf('Android') != -1) {
            constraints.video.mandatory.minWidth = 320;
            constraints.video.mandatory.minHeight = 240;
            constraints.video.mandatory.maxFrameRate = 15;
        }
        break;
    }

    // take what is configured and try not to be more intelligent
    if (constraints.video.minWidth) constraints.video.maxWidth = constraints.video.minWidth;
    if (constraints.video.minHeight) constraints.video.maxHeight = constraints.video.minHeight;

    if (bandwidth) { // doesn't work currently, see webrtc issue 1846
        if (!constraints.video) constraints.video = {mandatory: {}};//same behaviour as true
        constraints.video.optional = [{bandwidth: bandwidth}];
    }
    if (fps) { // for some cameras it might be necessary to request 30fps
        // so they choose 30fps mjpg over 10fps yuy2
        if (!constraints.video) constraints.video = {mandatory: {}};// same behaviour as tru;
        constraints.video.mandatory.minFrameRate = fps;
    }

    try {
        RTC.getUserMedia(constraints,
                function (stream) {
                    console.log('onUserMediaSuccess');
                    $(document).trigger('mediaready.jingle', [stream]);
                },
                function (error) {
                    console.warn('Failed to get access to local media. Error ', error);
                    $(document).trigger('mediafailure.jingle');
                });
    } catch (e) {
        console.error('GUM failed: ', e);
        $(document).trigger('mediafailure.jingle');
    }
}
<|endoftext|>


<commit_before>/*
Copyright 2017 David Reed, Joshua Humpherys, and Spencer Dent.

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.
*/

function arrangeSingers(singers, width, height) {
  console.log(singers);
  console.log(width);
  console.log(height);
  return singers;
}

module.exports = {
  arrangeSingers
};
<commit_msg>Add algorithm<commit_after>/*
Copyright 2017 David Reed, Joshua Humpherys, and Spencer Dent.

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.
*/

function arrangeSingers(singers, width, height) {
    let sopranoSingers = singers.filter(s => s.voice.startsWith('Soprano')).sort((a, b) => b.height - a.height);
    let tenorSingers = singers.filter(s => s.voice.startsWith('Tenor')).sort((a, b) => b.height - a.height);
    let bassSingers = singers.filter(s => s.voice.startsWith('Bass')).sort((a, b) => b.height - a.height);
    let altoSingers = singers.filter(s => s.voice.startsWith('Alto')).sort((a, b) => b.height - a.height);

    if (singers.length > width * height) throw 'Grid too small';
    let newWidth = Math.ceil(singers.length / height);

    let singersRemainder = singers.length % height;
    let leftRemainder = Math.floor(singersRemainder / 2);
    let rightRemainder = Math.ceil(singersRemainder / 2);

    //Sopranos
    let sopranoLeftRemainderWidth = leftRemainder === 0 ? 0 : 1;
    let sopranoRightRemainder = (sopranoSingers.length - leftRemainder) % height;
    let sopranoWidth = sopranoLeftRemainderWidth + Math.ceil((sopranoSingers.length - leftRemainder) / height);
    let sopranoIndex = 0;
    for (let y = 0; y < height; y++) {
        let leftX = leftRemainder === 0 || y < leftRemainder ? 0 : 1;
        let rightX = (sopranoWidth - 1) - (sopranoRightRemainder === 0 || y >= height - sopranoRightRemainder ? 0 : 1);
        for (let x = rightX; x >= leftX; x--) {
            sopranoSingers[sopranoIndex].x = x;
            sopranoSingers[sopranoIndex].y = y;
            sopranoIndex++;
        }
    }

    //Altos
    let altoRightRemainderWidth = rightRemainder === 0 ? 0 : 1;
    let altoLeftRemainder = (altoSingers.length - rightRemainder) % height;
    let altoWidth = altoRightRemainderWidth + Math.ceil((altoSingers.length - rightRemainder) / height);
    let altoIndex = 0;
    for (let y = 0; y < height; y++) {
        let rightX = (newWidth - 1) - (rightRemainder === 0 || y < rightRemainder ? 0 : 1);
        let leftX = (newWidth - 1) - (altoWidth - 1) + (altoLeftRemainder === 0 || y >= height - altoLeftRemainder ? 0 : 1);
        for (let x = leftX; x <= rightX; x++) {
            altoSingers[altoIndex].x = x;
            altoSingers[altoIndex].y = y;
            altoIndex++;
        }
    }

    //Tenors and Basses
    let tenorAndBassSingers = tenorSingers.concat(bassSingers);
    let tenorAndBassIndex = 0;
    for (let y = 0; y < height; y++) {
        let leftX = sopranoWidth - (sopranoRightRemainder === 0 || y >= height - sopranoRightRemainder ? 0 : 1);
        let rightX = newWidth - 1 - altoWidth + (altoLeftRemainder === 0 || y >= height - altoLeftRemainder ? 0 : 1);
        let sign = 1;
        let offset = 1;
        let x = Math.floor((leftX + rightX) / 2);
        while (x >= leftX && x <= rightX) {
            tenorAndBassSingers[tenorAndBassIndex].x = x;
            tenorAndBassSingers[tenorAndBassIndex].y = y;
            tenorAndBassIndex++;
            x += sign * offset;
            sign *= -1;
            offset++;
        }
    }

    return sopranoSingers.concat(altoSingers).concat(tenorAndBassSingers).map(s => {
        return {
            singerId: s.singerId,
            x: s.x,
            y: s.y
        }
    });
}

module.exports = {
  arrangeSingers
};
<|endoftext|>


<commit_before>/*
Simple Accessible Tab control v6 11-07-2016
Author: Mike Foskett
Incept: 09-03-2016
Article: http://websemantics.uk/articles/accessible-tab-navigation/
Furtherence of: Heydon Pickering - http://heydonworks.com/practical_aria_examples/#tab-interface

  Uses ARIA attributes (via CSS) to control what's visible.

  Requires (tested):
    addEventListener (IE8+)
    preventDefault (IE9+)
    classList (IE10+)
    querySelectorAll (IE9+)

  Required parameter:
    none

  Optional parameters:

    tabListClass : "tl_list" (default)

    onClass : "ON" (default)
            Customisable, set default tab on.
            Used for initialisation only.

    hoverableClass : "tl-hoverable" (default)
            Customisable, allow mouse-hover to switch tabs.
            Apply to the navigation ul.

  Version 6.1 - 16-08-2016
  Version 6 - 10-07-2016
  Version 5.2 - 17-03-2016
  Version 5 - 09-03-2016
  Version 4 - 27-01-2012
  Version 3 - 25-01-2012
  Version 1 - 27-05-2010
*/
var accessibleTabs6 = (function () {

  "use strict";

  var d = document;
  var tabListClass;
  var tabs;
  var onClass;
  var hoverableClass;
  //var useHover;

  var _setTab = function (tab, switchOn) {
    d.getElementById(tab.panelId).setAttribute("aria-hidden", !switchOn);
    tab.setAttribute("aria-selected", switchOn);
    tab.setAttribute("tabindex", switchOn ? "0" : "-1");
  };

  var _setTabsOff = function () {
    var i = tabs.length;
    while (i--) {
      _setTab(tabs[i], false);
    }
  };

  var _activateTab = function (e) {
    var tab = e.target;
    if (e.preventDefault) {
      e.preventDefault();
      _setTabsOff();
      _setTab(tab, true);
      d.getElementById(tab.panelId).children[0].focus();
    }
  };

  var _keypressed = function (e) {

    var tab = e.target;
    var newNo = -1;
    var maxNo = tabs.length - 1;

    if (e.keyCode === 37) { // left arrow
      newNo = (tab.no === 0) ? maxNo : tab.no - 1;
    }
    if (e.keyCode === 39) { // right arrow
      newNo = (tab.no === maxNo) ? 0 : tab.no + 1;
    }
    if (newNo > -1) {
      _setTabsOff();
      _setTab(tabs[newNo], true);
      tabs[newNo].focus();
    }
  };

  var _tabHovered = function (e) {
    var a = e.target;
    var useHover = a.tabList.classList.contains(hoverableClass);
    if (useHover) {
      _activateTab(e);
    }
  };

  var _events = function (tab) {
    tab.addEventListener("click", _activateTab, false);
    tab.addEventListener("keydown", _keypressed, false);
    tab.addEventListener("mouseover", _tabHovered, false);
  };

  var _initialiseAriaAttributes = function (tab) {

    var tabPanel = d.getElementById(tab.panelId);

    tab.parentNode.setAttribute("role", "presentation");
    tab.setAttribute("role", "tab");
    tab.setAttribute("aria-controls", tab.panelId);

    tabPanel.setAttribute("role", "tabpanel");
    tabPanel.setAttribute("aria-labelledby", tab.id);

    // Make first <section> object keyboard focussable
    // preferably a heading
    tabPanel.children[0].setAttribute("tabindex", "0");
  };

  var _setUpConfig = function (cfg) {
    tabListClass = cfg.tabListClass || "tl_list";
    onClass = cfg.onClass || "tl_link-on";
    hoverableClass = cfg.hoverableClass || "tl-hoverable";
  };

  var _setUpTab = function (tab, panelId, count) {
    tab.no = count;
    tab.id = "tab-" + panelId;
    tab.panelId = panelId;
    _initialiseAriaAttributes(tab);
    _events(tab);
  };

  var _initialiseTabList = function (tabList) {

    var defaultTab = 0;
    var panelId;
    var tabPanel;
    var i;

    if (tabList) {

      tabList.setAttribute("role", "tablist");
      tabs = tabList.getElementsByTagName("a");
      i = tabs.length;

      while (i--) {

        tabs[i].tabList = tabList;

        panelId = tabs[i].href.slice(tabs[i].href.lastIndexOf("#") + 1);
        tabPanel = d.getElementById(panelId);

        if (tabPanel) {
          _setUpTab(tabs[i], panelId, i);
          if (tabs[i].classList.contains(onClass)) {
            defaultTab = i;

            // onClass only used to declare intial state, so remove from DOM
            tabs[i].classList.remove(onClass);
            d.getElementById(panelId).classList.remove(onClass);
          }
        }
      }
      _setTabsOff();
      _setTab(tabs[defaultTab], true);
    }
  };

  var _isMustardCut = function (e) {
    // check browser feature support (IE10+)
    return (
      (typeof d.querySelectorAll === "function") &&
      d.addEventListener &&
      !!d.documentElement.classList // IE10+
    );
  };

  var init = function (cfg) {
    var tabLists;
    var i;

    if (_isMustardCut()) {
      _setUpConfig(cfg);
      tabLists = d.getElementsByClassName(tabListClass);
      i = tabLists.length;
      while (i--) {
        _initialiseTabList(tabLists[i]);
      }
    }
  };

  return {
    init: init
  };

}());

accessibleTabs6.init({
  tabListClass : "tl_list",         // default, may omit
  onClass : "tl_link-on",            // default, may omit
  hoverableClass : "tl-hoverable"   // default, may omit
});
<commit_msg>Delete accessible-tabs.1.0.js<commit_after><|endoftext|>


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

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import DropdownSelect from '../DropdownSelect';
import MaterialProvider from '../MaterialProvider';
import Theme from '../Theme';

import SelectMode from '../_statics/SelectMode';

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

class MaterialDropdownSelect extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;
    static Position = DropdownSelect.Position;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            value: props.value,
            isFocus: false
        };

    }

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

    closePopup = () => {
        this.refs.dropdownSelect && this.refs.dropdownSelect.closePopup();
    };

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

    render() {

        const {
                className, style, theme, label, isLabelAnimate, disabled, required, selectMode,
                ...restProps
            } = this.props,
            {value} = this.state,

            wrapperClassName = classNames('material-dropdown-select', {
                [className]: className
            });

        return (
            <MaterialProvider className={wrapperClassName}
                              style={style}
                              theme={theme}
                              label={label}
                              isLabelAnimate={isLabelAnimate}
                              hasValue={selectMode === SelectMode.MULTI_SELECT ? value && value.length > 0 : !!value}
                              disabled={disabled}
                              required={required}>
                <DropdownSelect {...restProps}
                                ref="dropdownSelect"
                                value={value}
                                selectMode={selectMode}
                                disabled={disabled}
                                onChange={this.triggerChangeHandler}/>
            </MaterialProvider>
        );

    }
}

MaterialDropdownSelect.propTypes = {

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

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

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

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

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

    position: PropTypes.oneOf(Util.enumerateValue(DropdownSelect.Position)),

    /**
     * The name of the dropDownSelect.
     */
    name: PropTypes.string,

    /**
     * The label of the text field.
     */
    label: PropTypes.any,

    /**
     * The animate of the text field.
     */
    isLabelAnimate: PropTypes.bool,

    /**
     * The value of the dropDownSelect.
     */
    value: PropTypes.any,

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

    title: PropTypes.string,

    rightIconCls: PropTypes.string,

    /**
     * The options data.
     */
    data: PropTypes.oneOfType([

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        // grouped
        PropTypes.array

    ]).isRequired,

    /**
     * The invalid message of dropDownSelect.
     */
    invalidMsg: PropTypes.string,

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

    /**
     * The mode of listItem.Can be normal,checkbox.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

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

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

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

    /**
     * The message of the dropDownSelect.
     */
    infoMsg: PropTypes.string,

    /**
     * Use this function to format the options's text.
     */
    textFormat: PropTypes.func,

    /**
     * If true,the drop-down box automatically closed after selection.
     */
    autoClose: PropTypes.bool,

    /**
     * If true,the drop-down box will have search input.
     */
    useFilter: PropTypes.bool,

    useSelectAll: PropTypes.bool,
    selectAllText: PropTypes.string,

    /**
     * The message of no matching option.
     */
    noMatchedMsg: PropTypes.string,

    /**
     * If true,the drop-down box will be have group selection.
     */
    isGrouped: PropTypes.bool,

    required: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * Callback function fired when the button is touch-tapped.
     */
    onItemClick: PropTypes.func,

    /**
     * Callback function fired when the popup is closed.
     */
    onClosePopup: PropTypes.func,

    /**
     * Callback function fired when a menu item is selected.
     */
    onChange: PropTypes.func,

    onWheel: PropTypes.func

};

MaterialDropdownSelect.defaultProps = {

    theme: Theme.DEFAULT,

    position: DropdownSelect.Position.LEFT,
    isLabelAnimate: true,
    placeholder: 'Please select ...',
    rightIconCls: 'fas fa-angle-down',
    data: [],
    disabled: false,
    selectMode: SelectMode.SINGLE_SELECT,

    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',

    autoClose: true,
    useFilter: false,
    useSelectAll: false,
    selectAllText: 'Select All',
    isGrouped: false,
    required: false,

    shouldPreventContainerScroll: true

};

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

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import DropdownSelect from '../DropdownSelect';
import MaterialProvider from '../MaterialProvider';
import Theme from '../Theme';

import SelectMode from '../_statics/SelectMode';

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

class MaterialDropdownSelect extends Component {

    static SelectMode = SelectMode;
    static Theme = Theme;
    static Position = DropdownSelect.Position;

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.state = {
            value: props.value,
            isFocus: false
        };

    }

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

    closePopup = () => {
        this.refs.dropdownSelect && this.refs.dropdownSelect.closePopup();
    };

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

    render() {

        const {
                className, style, theme, label, isLabelAnimate, disabled, required, selectMode,
                ...restProps
            } = this.props,
            {value} = this.state,

            wrapperClassName = classNames('material-dropdown-select', {
                [className]: className
            });

        return (
            <MaterialProvider className={wrapperClassName}
                              style={style}
                              theme={theme}
                              label={label}
                              isLabelAnimate={isLabelAnimate}
                              hasValue={selectMode === SelectMode.MULTI_SELECT ? value && value.length > 0 : !!value}
                              disabled={disabled}
                              required={required}>
                <DropdownSelect {...restProps}
                                ref="dropdownSelect"
                                value={value}
                                selectMode={selectMode}
                                disabled={disabled}
                                onChange={this.triggerChangeHandler}/>
            </MaterialProvider>
        );

    }
}

MaterialDropdownSelect.propTypes = {

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

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

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

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

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

    position: PropTypes.oneOf(Util.enumerateValue(DropdownSelect.Position)),

    /**
     * The name of the dropDownSelect.
     */
    name: PropTypes.string,

    /**
     * The label of the text field.
     */
    label: PropTypes.any,

    /**
     * The animate of the text field.
     */
    isLabelAnimate: PropTypes.bool,

    /**
     * The value of the dropDownSelect.
     */
    value: PropTypes.any,

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

    title: PropTypes.string,

    rightIconCls: PropTypes.string,

    /**
     * The options data.
     */
    data: PropTypes.oneOfType([

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        // grouped
        PropTypes.array

    ]).isRequired,

    /**
     * The invalid message of dropDownSelect.
     */
    invalidMsg: PropTypes.string,

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

    /**
     * The mode of listItem.Can be normal,checkbox.
     */
    selectMode: PropTypes.oneOf(Util.enumerateValue(SelectMode)),

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

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

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

    /**
     * The message of the dropDownSelect.
     */
    infoMsg: PropTypes.string,

    /**
     * Use this function to format the options's text.
     */
    textFormat: PropTypes.func,

    /**
     * If true,the drop-down box automatically closed after selection.
     */
    autoClose: PropTypes.bool,

    /**
     * If true,the drop-down box will have search input.
     */
    useFilter: PropTypes.bool,

    filterIconCls: PropTypes.string,
    useSelectAll: PropTypes.bool,
    selectAllText: PropTypes.string,

    /**
     * The message of no matching option.
     */
    noMatchedMsg: PropTypes.string,

    /**
     * If true,the drop-down box will be have group selection.
     */
    isGrouped: PropTypes.bool,

    required: PropTypes.bool,

    shouldPreventContainerScroll: PropTypes.bool,

    /**
     * Callback function fired when the button is touch-tapped.
     */
    onItemClick: PropTypes.func,

    /**
     * Callback function fired when the popup is closed.
     */
    onClosePopup: PropTypes.func,

    /**
     * Callback function fired when a menu item is selected.
     */
    onChange: PropTypes.func,

    onWheel: PropTypes.func

};

MaterialDropdownSelect.defaultProps = {

    theme: Theme.DEFAULT,

    position: DropdownSelect.Position.LEFT,
    isLabelAnimate: true,
    placeholder: 'Please select ...',
    rightIconCls: 'fas fa-angle-down',
    data: [],
    disabled: false,
    selectMode: SelectMode.SINGLE_SELECT,

    valueField: 'value',
    displayField: 'text',
    descriptionField: 'desc',

    autoClose: true,
    useFilter: false,
    filterIconCls: 'fas fa-search',
    useSelectAll: false,
    selectAllText: 'Select All',
    isGrouped: false,
    required: false,

    shouldPreventContainerScroll: true

};

export default MaterialDropdownSelect;<|endoftext|>


<commit_before>var assert = require("assert");

var vumigo = require("../../lib");
var api = vumigo.contacts.api;
var Contact = api.Contact;
var ContactError = api.ContactError;

describe("contact.api", function() {
    describe("Contact", function() {
        describe(".do.validate", function() {
            it("should throw an error for non-string msisdns", function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1'
                });

                contact.msisdn = null;

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact has an msisdn of type 'object' instead of",
                             "'string': null"].join(' '));

                        return true;
                    });
            });

            it("should throw an error for non-string groups", function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1',
                });

                contact.groups.push(null);

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact has a group of type 'object' instead of",
                             "'string': null"].join(' '));

                        return true;
                    });
            });

            it("should throw an error for non-string extra values", function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1',
                });

                contact.extra.spam = null;

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact extra 'spam' has a value of type 'object'",
                             "instead of 'string': null"].join(' '));

                        return true;
                    });
            });

            it("should throw an error for non-string subscription values",
            function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1',
                });

                contact.subscriptions.conv3 = null;

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact subscription 'conv3' has a value of type",
                             "'object' instead of 'string': null"].join(' '));

                        return true;
                    });
            });
        });
    });
});
<commit_msg>contact.api -> contacts.api in test describe<commit_after>var assert = require("assert");

var vumigo = require("../../lib");
var api = vumigo.contacts.api;
var Contact = api.Contact;
var ContactError = api.ContactError;

describe("contacts.api", function() {
    describe("Contact", function() {
        describe(".do.validate", function() {
            it("should throw an error for non-string msisdns", function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1'
                });

                contact.msisdn = null;

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact has an msisdn of type 'object' instead of",
                             "'string': null"].join(' '));

                        return true;
                    });
            });

            it("should throw an error for non-string groups", function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1',
                });

                contact.groups.push(null);

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact has a group of type 'object' instead of",
                             "'string': null"].join(' '));

                        return true;
                    });
            });

            it("should throw an error for non-string extra values", function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1',
                });

                contact.extra.spam = null;

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact extra 'spam' has a value of type 'object'",
                             "instead of 'string': null"].join(' '));

                        return true;
                    });
            });

            it("should throw an error for non-string subscription values",
            function() {
                var contact = new Contact({
                    key: '123',
                    msisdn: '+27123',
                    user_account: 'user1',
                });

                contact.subscriptions.conv3 = null;

                assert.throws(
                    function() { contact.do.validate(); },
                    function(error) {
                        assert(error instanceof ContactError);
                        assert.equal(
                            error.message,
                            ["Contact subscription 'conv3' has a value of type",
                             "'object' instead of 'string': null"].join(' '));

                        return true;
                    });
            });
        });
    });
});
<|endoftext|>


<commit_before>/**
 *		Name: Ad Insertion
 *
 *		Requires: app, app.util, jQuery
 */

var admanager = ( function( app, $ ) {

	app.insertion = ( function( $ ) {

		var _name = 'Insertion',
			debug = null,

			$target = null,
			_inventory = [],
			last_position = 0,
			odd = true
		;

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function init() {

			debug = admanager.util.debug ? admanager.util.debug : function(){};
			debug( _name + ': initialized' );

			$target = $( app.config.insertion_selector ).first();

			if ( $target.length < 1 ) {
				_broadcast();

				return app;
			}

			_inventory = app.manager.get_dynamic_inventory();

			_insert_ad_units();

			return app;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Ad units have been inserted / proceed
		 */
		function _broadcast() {

			$.event.trigger( 'GPT:unitsInserted' );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _insert_ad_units() {

			_denote_valid_insertions();

			_insert_primary_unit();
			_insert_secondary_units();

			_broadcast();

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _denote_valid_insertions() {

			var $nodes = $target.children(),
				excluded = [
					'img',
					'iframe',
					'video',
					'audio',
					'.video',
					'.audio',
					'.app_ad_unit'
				]
			;

			$nodes.each( function( i ) {

				var $element = $(this),
					$prev = i > 0 ? $nodes.eq( i - 1 ) : false,
					valid = true
				;

				$.each( excluded, function( index, item ) {

					if ( $element.is( item ) || $element.find( item ).length > 0 ) {

						// not valid
						valid = false;

						// break loop
						return false;
					}

				} );

				if ( $prev && $prev.is('p') && $prev.find('img').length === 1 ) valid = false;

				$element.data('valid-location', valid);

			} );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Check against of list of elements to skip
		 *
		 * @param  object $element
		 * @return bool
		 */
		function _is_valid_insertion_location( $element ) {

			return $element.data('valid-location');

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * _ad_unit_markup
		 *
		 * @param object unit
		 * @param bool disable_float
		 * @return string
		 */
		function _ad_unit_markup( unit, disable_float ) {

			disable_float = disable_float || false;

			var type = admanager.util.is_mobile() ? 'mobile' : 'desktop',
				alignment = odd ? 'odd' : 'even',
				html = '

',
				html_disable_float =	'

'
			;

			if ( ! disable_float ) odd = ! odd;

			return disable_float ? html_disable_float : html;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _insert_primary_unit() {

			var location = _location_to_insert_ad_unit({
					'limit' : 1000
				}),
				unit = _get_primary_unit(),
				markup = _ad_unit_markup( unit.type, location.disable_float )
			;

			location.$insert_before.before( markup );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _insert_secondary_units() {

			$.each( _inventory, function( index, unit ) {

				var location = _location_to_insert_ad_unit(),
					markup = null
				;

				if ( ! location ) {
					return false;
				}

				markup = _ad_unit_markup( unit.type, location.disable_float );
				location.$insert_before.before( markup );

			} );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _get_primary_unit() {

			var primary_unit = false
			;

			$.each( _inventory, function( index, unit ) {

				if ( unit.primary === true ) {
					primary_unit = unit;
					_inventory.remove(index);

					return false;
				}

			} );

			if ( ! primary_unit ) {
				primary_unit = _inventory[0];
				_inventory.remove(0);
			}

			return primary_unit;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * _location_to_insert_ad_unit
		 *
		 * @param object options
		 * @return object
		 */
		function _location_to_insert_ad_unit( options ) {

			options = options || {};

			var $nodes = _get_nodes(),
				$insert_before = null,
				inserted = [],

				total_height = 0,
				valid_height = 0,
				limit = options.limit ? options.limit : false,
				needed_height = 560,
				between_units = 800,

				location_found = false,
				disable_float = false,
				maybe_more = true
			;

			if ( $nodes.length < 1 ) return false;

			$nodes.each(function(i) {

				var $this = $(this),
					$prev = i > 0 ? $nodes.eq( i - 1 ) : false,
					offset = $this.offset().top,
					since = offset - last_position,
					height = $this.outerHeight(),
					is_last = ($nodes.length - 1) === i
				;

				total_height += height;

				if ( limit && (total_height >= limit || is_last) ) {
					$insert_before = $this;
					disable_float = true;
					location_found = true;

					return false;
				}

				if ( _is_valid_insertion_location($this) ) {
					valid_height += height;

					inserted.push($this);

					if ( $insert_before === null ) {
						$insert_before = $this;
					}

					if ( valid_height >= needed_height ) {
						if ( limit === false && (since < between_units) ) {
							valid_height = 0;
							$insert_before = null;
							return true;
						}

						location_found = true;
						return false;
					}
				}
				else {
					valid_height = 0;
					$insert_before = null;
				}

			});

			if ( ! location_found ) {
				return false;
			}

			if ( inserted.length > 0 ) {
				$.each( inserted, function( index, item ) {
					$(item).data('valid-location', false);
				} );
			}

			last_position = $insert_before.offset().top + needed_height;

			return {
				'$insert_before' : $insert_before,
				'disable_float' : disable_float
			};

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Is Element an Ad Unit
		 *
		 * @param mixed $el
		 * @return bool
		 */
		function _is_this_an_ad( $el ) {

			if ( ! $el ) return false;

			return $el.is('.app_ad_unit');

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Get Nodes to Loop Through
		 *
		 * @return array $nodes
		 */
		function _get_nodes() {

			var $prev_unit = $target.find('.app_ad_unit').last(),
				$nodes = null
			;

			// nodes after previous unit or all nodes
			$nodes = ( $prev_unit.length > 0 ) ? $prev_unit.nextAll( $target ) : $target.children();
			// $nodes = $target.children();

			return $nodes;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		return {
			init : init
		};

	}( $ ) );

	return app;

}( admanager || {}, jQuery ) );<commit_msg>Helper function to get tallest size for unit<commit_after>/**
 *		Name: Ad Insertion
 *
 *		Requires: app, app.util, jQuery
 */

var admanager = ( function( app, $ ) {

	app.insertion = ( function( $ ) {

		var _name = 'Insertion',
			debug = null,

			$target = null,
			_inventory = [],
			last_position = 0,
			odd = true
		;

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function init() {

			debug = admanager.util.debug ? admanager.util.debug : function(){};
			debug( _name + ': initialized' );

			$target = $( app.config.insertion_selector ).first();

			if ( $target.length < 1 ) {
				_broadcast();

				return app;
			}

			_inventory = app.manager.get_dynamic_inventory();

			_insert_ad_units();

			return app;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Ad units have been inserted / proceed
		 */
		function _broadcast() {

			$.event.trigger( 'GPT:unitsInserted' );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _insert_ad_units() {

			_denote_valid_insertions();

			_insert_primary_unit();
			_insert_secondary_units();

			_broadcast();

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _denote_valid_insertions() {

			var $nodes = $target.children(),
				excluded = [
					'img',
					'iframe',
					'video',
					'audio',
					'.video',
					'.audio',
					'.app_ad_unit'
				]
			;

			$nodes.each( function( i ) {

				var $element = $(this),
					$prev = i > 0 ? $nodes.eq( i - 1 ) : false,
					valid = true
				;

				$.each( excluded, function( index, item ) {

					if ( $element.is( item ) || $element.find( item ).length > 0 ) {

						// not valid
						valid = false;

						// break loop
						return false;
					}

				} );

				if ( $prev && $prev.is('p') && $prev.find('img').length === 1 ) valid = false;

				$element.data('valid-location', valid);

			} );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Check against of list of elements to skip
		 *
		 * @param  object $element
		 * @return bool
		 */
		function _is_valid_insertion_location( $element ) {

			return $element.data('valid-location');

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * _ad_unit_markup
		 *
		 * @param object unit
		 * @param bool disable_float
		 * @return string
		 */
		function _ad_unit_markup( unit, disable_float ) {

			disable_float = disable_float || false;

			var type = admanager.util.is_mobile() ? 'mobile' : 'desktop',
				alignment = odd ? 'odd' : 'even',
				html = '

',
				html_disable_float =	'

'
			;

			if ( ! disable_float ) odd = ! odd;

			return disable_float ? html_disable_float : html;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _insert_primary_unit() {

			var location = _location_to_insert_ad_unit({
					'limit' : 1000
				}),
				unit = _get_primary_unit(),
				markup = _ad_unit_markup( unit.type, location.disable_float )
			;

			location.$insert_before.before( markup );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _insert_secondary_units() {

			$.each( _inventory, function( index, unit ) {

				var location = _location_to_insert_ad_unit(),
					markup = null
				;

				if ( ! location ) {
					return false;
				}

				markup = _ad_unit_markup( unit.type, location.disable_float );
				location.$insert_before.before( markup );

			} );

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		function _get_primary_unit() {

			var primary_unit = false
			;

			$.each( _inventory, function( index, unit ) {

				if ( unit.primary === true ) {
					primary_unit = unit;
					_inventory.remove(index);

					return false;
				}

			} );

			if ( ! primary_unit ) {
				primary_unit = _inventory[0];
				_inventory.remove(0);
			}

			return primary_unit;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * _location_to_insert_ad_unit
		 *
		 * @param object options
		 * @return object
		 */
		function _location_to_insert_ad_unit( options ) {

			options = options || {};

			var $nodes = _get_nodes(),
				$insert_before = null,
				inserted = [],

				total_height = 0,
				valid_height = 0,
				limit = options.limit ? options.limit : false,
				needed_height = 560,
				between_units = 800,

				location_found = false,
				disable_float = false,
				maybe_more = true
			;

			if ( $nodes.length < 1 ) return false;

			$nodes.each(function(i) {

				var $this = $(this),
					$prev = i > 0 ? $nodes.eq( i - 1 ) : false,
					offset = $this.offset().top,
					since = offset - last_position,
					height = $this.outerHeight(),
					is_last = ($nodes.length - 1) === i
				;

				total_height += height;

				if ( limit && (total_height >= limit || is_last) ) {
					$insert_before = $this;
					disable_float = true;
					location_found = true;

					return false;
				}

				if ( _is_valid_insertion_location($this) ) {
					valid_height += height;

					inserted.push($this);

					if ( $insert_before === null ) {
						$insert_before = $this;
					}

					if ( valid_height >= needed_height ) {
						if ( limit === false && (since < between_units) ) {
							valid_height = 0;
							$insert_before = null;
							return true;
						}

						location_found = true;
						return false;
					}
				}
				else {
					valid_height = 0;
					$insert_before = null;
				}

			});

			if ( ! location_found ) {
				return false;
			}

			if ( inserted.length > 0 ) {
				$.each( inserted, function( index, item ) {
					$(item).data('valid-location', false);
				} );
			}

			last_position = $insert_before.offset().top + needed_height;

			return {
				'$insert_before' : $insert_before,
				'disable_float' : disable_float
			};

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Is Element an Ad Unit
		 *
		 * @param mixed $el
		 * @return bool
		 */
		function _is_this_an_ad( $el ) {

			if ( ! $el ) return false;

			return $el.is('.app_ad_unit');

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Get Nodes to Loop Through
		 *
		 * @return array $nodes
		 */
		function _get_nodes() {

			var $prev_unit = $target.find('.app_ad_unit').last(),
				$nodes = null
			;

			// nodes after previous unit or all nodes
			$nodes = ( $prev_unit.length > 0 ) ? $prev_unit.nextAll( $target ) : $target.children();
			// $nodes = $target.children();

			return $nodes;

		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		/**
		 * Get Tallest Possible Size for Unit
		 *
		 * @param object unit
		 * @return integer
		 */
		function _tallest_available( unit ) {
			var tallest = 0;

			$.each( units.sizes, function( index, sizes ) {
				if ( sizes[1] > tallest ) tallest = sizes[1];
			});

			return tallest;
		}

		/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

		return {
			init : init
		};

	}( $ ) );

	return app;

}( admanager || {}, jQuery ) );<|endoftext|>


<commit_before>/*
Copyright 2016 First People's Cultural Council

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.
*/
import React, {Component, PropTypes} from 'react';
import Immutable, {List, Map} from 'immutable';

import provide from 'react-redux-provide';
import selectn from 'selectn';
import classNames from 'classnames';

import ProviderHelpers from 'common/ProviderHelpers';

import PortalList from 'views/components/Browsing/portal-list'
import PromiseWrapper from 'views/components/Document/PromiseWrapper';

// Operations
import DirectoryOperations from 'operations/DirectoryOperations';

import Checkbox from 'material-ui/lib/checkbox';
import TextField from 'material-ui/lib/text-field';
import RaisedButton from 'material-ui/lib/raised-button';
import SelectField from 'material-ui/lib/select-field';
import MenuItem from 'material-ui/lib/menus/menu-item';

import withPagination from 'views/hoc/grid-list/with-pagination';
import withFilter from 'views/hoc/grid-list/with-filter';
import IntlService from 'views/services/intl';

const intl = IntlService.instance;
//const FilteredPortalList = withFilter(PortalList);

/**
 * Explore Archive page shows all the families in the archive
 */
@provide
export default class ExploreDialects extends Component {

    static propTypes = {
        properties: PropTypes.object.isRequired,
        fetchPortals: PropTypes.func.isRequired,
        computePortals: PropTypes.object.isRequired,
        computeLogin: PropTypes.object.isRequired,
        pushWindowPath: PropTypes.func.isRequired,
        routeParams: PropTypes.object.isRequired
    };

    /*static contextTypes = {
        muiTheme: React.PropTypes.object.isRequired
    };*/

    constructor(props, context) {
        super(props, context);

        this.state = {
            filteredList: null,
            open: false
        };

        // Bind methods to 'this'
        ['_onNavigateRequest', 'fixedListFetcher', '_getParentPath'].forEach((method => this[method] = this[method].bind(this)));
    }

    fetchData(newProps) {
        newProps.fetchPortals(this._getQueryPath(newProps));
    }

    fixedListFetcher(list) {
        this.setState({
            filteredList: list
        });
    }

    // Fetch data on initial render
    componentDidMount() {
        this.fetchData(this.props);
    }

    // Refetch data on URL change
    componentWillReceiveProps(nextProps) {
        if (nextProps.routeParams.area != this.props.routeParams.area) {
            this.fetchData(nextProps);
        }
    }

    _onNavigateRequest(path) {
        this.props.pushWindowPath('/' + this.props.routeParams.theme + path);
    }

    _getParentPath(props = this.props) {
        return '/' + props.properties.domain + '/' + props.routeParams.area;
    }

    _getQueryPath(props = this.props) {

        // Perform an API query for sections
        if (props.routeParams.area == 'sections') {
            // From s3 (static) (NOTE: when fetchPortals is fully switched remove headers from FVPortal to save OPTIONS call)
            return 'https://api.firstvoices.com/v1/s3dialects/?area=' + props.routeParams.area;

            // Proxy (not cached at the moment)
            //return 'https://api.firstvoices.com/v1/api/v1/query/get_dialects?queryParams=' + props.routeParams.area;
        }
        else {
            // Direct method
            return '/api/v1/query/get_dialects?queryParams=' + props.routeParams.area;
        }
    }

    render() {

        const isKidsTheme = this.props.routeParams.theme === 'kids';

        const computeEntities = Immutable.fromJS([{
            'id': this._getQueryPath(),
            'entity': this.props.computePortals
        }])

        const computePortals = ProviderHelpers.getEntry(this.props.computePortals, this._getQueryPath());
        let isLoggedIn = this.props.computeLogin.success && this.props.computeLogin.isConnected;

        let portalsEntries = selectn('response.entries', computePortals) || [];

        let titleFieldMapping = 'contextParameters.ancestry.dialect.dc:title';
        let logoFieldMapping = 'contextParameters.portal.fv-portal:logo';

        if (this.props.routeParams.area == 'sections') {
            titleFieldMapping = 'title';
            logoFieldMapping = 'logo';
        }

        // Sort based on dialect name (all FVPortals have dc:title 'Portal')
        let sortedPortals = portalsEntries.sort(function (a, b) {

            let a2 = selectn(titleFieldMapping, a);
            let b2 = selectn(titleFieldMapping, b);

            if (a2 < b2) return -1;
            if (a2 > b2) return 1;
            return 0;
        });

        let portalListProps = {
            action: this._onNavigateRequest,
            filterOptionsKey: (isLoggedIn) ? 'Portals' : 'Default',
            fixedList: true,
            area: this.props.routeParams.area,
            fixedListFetcher: this.fixedListFetcher,
            filteredItems: this.state.filteredList,
            fieldMapping: {
                title: titleFieldMapping,
                logo: logoFieldMapping
            },
            metadata: selectn('response', computePortals),
            items: sortedPortals
        };

        let portalList = <PortalList {...portalListProps} cols={4}/>;

        if (isKidsTheme) {
            portalList = <PortalList {...portalListProps} cols={6}/>
        }

        return <PromiseWrapper computeEntities={computeEntities}>

{intl.translate({key: 'general.explore', default: 'Explore Languages', case: 'title'})}

                    {portalList}

        </PromiseWrapper>;
    }
}<commit_msg>Dynamic API url<commit_after>/*
Copyright 2016 First People's Cultural Council

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.
*/
import React, {Component, PropTypes} from 'react';
import Immutable, {List, Map} from 'immutable';

import ConfGlobal from 'conf/local.json';

import provide from 'react-redux-provide';
import selectn from 'selectn';
import classNames from 'classnames';

import ProviderHelpers from 'common/ProviderHelpers';

import PortalList from 'views/components/Browsing/portal-list'
import PromiseWrapper from 'views/components/Document/PromiseWrapper';

// Operations
import DirectoryOperations from 'operations/DirectoryOperations';

import Checkbox from 'material-ui/lib/checkbox';
import TextField from 'material-ui/lib/text-field';
import RaisedButton from 'material-ui/lib/raised-button';
import SelectField from 'material-ui/lib/select-field';
import MenuItem from 'material-ui/lib/menus/menu-item';

import withPagination from 'views/hoc/grid-list/with-pagination';
import withFilter from 'views/hoc/grid-list/with-filter';
import IntlService from 'views/services/intl';

const intl = IntlService.instance;
//const FilteredPortalList = withFilter(PortalList);

/**
 * Explore Archive page shows all the families in the archive
 */
@provide
export default class ExploreDialects extends Component {

    static propTypes = {
        properties: PropTypes.object.isRequired,
        fetchPortals: PropTypes.func.isRequired,
        computePortals: PropTypes.object.isRequired,
        computeLogin: PropTypes.object.isRequired,
        pushWindowPath: PropTypes.func.isRequired,
        routeParams: PropTypes.object.isRequired
    };

    /*static contextTypes = {
        muiTheme: React.PropTypes.object.isRequired
    };*/

    constructor(props, context) {
        super(props, context);

        this.state = {
            filteredList: null,
            open: false
        };

        // Bind methods to 'this'
        ['_onNavigateRequest', 'fixedListFetcher', '_getParentPath'].forEach((method => this[method] = this[method].bind(this)));
    }

    fetchData(newProps) {
        newProps.fetchPortals(this._getQueryPath(newProps));
    }

    fixedListFetcher(list) {
        this.setState({
            filteredList: list
        });
    }

    // Fetch data on initial render
    componentDidMount() {
        this.fetchData(this.props);
    }

    // Refetch data on URL change
    componentWillReceiveProps(nextProps) {
        if (nextProps.routeParams.area != this.props.routeParams.area) {
            this.fetchData(nextProps);
        }
    }

    _onNavigateRequest(path) {
        this.props.pushWindowPath('/' + this.props.routeParams.theme + path);
    }

    _getParentPath(props = this.props) {
        return '/' + props.properties.domain + '/' + props.routeParams.area;
    }

    _getQueryPath(props = this.props) {

        // Perform an API query for sections
        if (props.routeParams.area == 'sections') {
            // From s3 (static) (NOTE: when fetchPortals is fully switched remove headers from FVPortal to save OPTIONS call)
            return ConfGlobal.apiURL + 's3dialects/?area=' + props.routeParams.area;

            // Proxy (not cached at the moment)
            //return 'https://api.firstvoices.com/v1/api/v1/query/get_dialects?queryParams=' + props.routeParams.area;
        }
        else {
            // Direct method
            return '/api/v1/query/get_dialects?queryParams=' + props.routeParams.area;
        }
    }

    render() {

        const isKidsTheme = this.props.routeParams.theme === 'kids';

        const computeEntities = Immutable.fromJS([{
            'id': this._getQueryPath(),
            'entity': this.props.computePortals
        }])

        const computePortals = ProviderHelpers.getEntry(this.props.computePortals, this._getQueryPath());
        let isLoggedIn = this.props.computeLogin.success && this.props.computeLogin.isConnected;

        let portalsEntries = selectn('response.entries', computePortals) || [];

        let titleFieldMapping = 'contextParameters.ancestry.dialect.dc:title';
        let logoFieldMapping = 'contextParameters.portal.fv-portal:logo';

        if (this.props.routeParams.area == 'sections') {
            titleFieldMapping = 'title';
            logoFieldMapping = 'logo';
        }

        // Sort based on dialect name (all FVPortals have dc:title 'Portal')
        let sortedPortals = portalsEntries.sort(function (a, b) {

            let a2 = selectn(titleFieldMapping, a);
            let b2 = selectn(titleFieldMapping, b);

            if (a2 < b2) return -1;
            if (a2 > b2) return 1;
            return 0;
        });

        let portalListProps = {
            action: this._onNavigateRequest,
            filterOptionsKey: (isLoggedIn) ? 'Portals' : 'Default',
            fixedList: true,
            area: this.props.routeParams.area,
            fixedListFetcher: this.fixedListFetcher,
            filteredItems: this.state.filteredList,
            fieldMapping: {
                title: titleFieldMapping,
                logo: logoFieldMapping
            },
            metadata: selectn('response', computePortals),
            items: sortedPortals
        };

        let portalList = <PortalList {...portalListProps} cols={4}/>;

        if (isKidsTheme) {
            portalList = <PortalList {...portalListProps} cols={6}/>
        }

        return <PromiseWrapper computeEntities={computeEntities}>

{intl.translate({key: 'general.explore', default: 'Explore Languages', case: 'title'})}

                    {portalList}

        </PromiseWrapper>;
    }
}<|endoftext|>