<commit_before>define(function(require) {
    'use strict';

    var angular = require('angular'),
        _ = require('underscore'),
        appController = require('js/app-controller'),
        aes = require('cryptoLib/aes-cbc'),
        util = require('cryptoLib/util'),
        str = require('js/app-config').string,
        crypto, emailDao, outbox, keychainDao;

    //
    // Controller
    //

    var WriteCtrl = function($scope, $filter) {
        crypto = appController._crypto;
        emailDao = appController._emailDao,
        outbox = appController._outboxBo;
        keychainDao = appController._keychain;

        // set default value so that the popover height is correct on init
        $scope.keyId = 'XXXXXXXX';

        //
        // Init
        //

        $scope.state.writer = {
            write: function(replyTo, replyAll, forward) {
                $scope.state.lightbox = 'write';
                $scope.replyTo = replyTo;

                resetFields();

                // fill fields depending on replyTo
                fillFields(replyTo, replyAll, forward);
                $scope.updatePreview();

                $scope.verify($scope.to[0]);
            },
            close: function() {
                $scope.state.lightbox = undefined;
            }
        };

        function resetFields() {
            $scope.writerTitle = 'New email';
            $scope.to = [{
                address: ''
            }];
            $scope.showCC = false;
            $scope.cc = [{
                address: ''
            }];
            $scope.showBCC = false;
            $scope.bcc = [{
                address: ''
            }];
            $scope.subject = '';
            $scope.body = '';
            $scope.ciphertextPreview = '';
            $scope.attachments = [];
        }

        function fillFields(re, replyAll, forward) {
            var replyTo, from, sentDate, body;

            if (!re) {
                return;
            }

            $scope.writerTitle = (forward) ? 'Forward' : 'Reply';

            replyTo = re.replyTo && re.replyTo[0] && re.replyTo[0].address || re.from[0].address;

            // fill recipient field and references
            if (!forward) {
                $scope.to.unshift({
                    address: replyTo
                });
                $scope.to.forEach($scope.verify);
                if ((re.references || []).indexOf(re.id) < 0) {
                    // references might not exist yet, so use the double concat
                    $scope.references = [].concat(re.references || []).concat(re.id);
                } else {
                    $scope.references = re.references;
                }
                $scope.inReplyTo = re.id;
            }
            if (replyAll) {
                re.to.concat(re.cc).forEach(function(recipient) {
                    var me = emailDao._account.emailAddress;
                    if (recipient.address === me && replyTo !== me) {
                        // don't reply to yourself
                        return;
                    }
                    $scope.cc.unshift({
                        address: recipient.address
                    });
                });

                // filter duplicates
                $scope.cc = _.uniq($scope.cc, function(recipient) {
                    return recipient.address;
                });
                $scope.showCC = true;
                $scope.cc.forEach($scope.verify);
            }

            // fill attachments and references on forward
            if (forward) {
                // create a new array, otherwise removing an attachment will also
                // remove it from the original in the mail list as a side effect
                $scope.attachments = [].concat(re.attachments);
                $scope.references = [re.id];
            }

            // fill subject
            if (forward) {
                $scope.subject = 'Fwd: ' + re.subject;
            } else {
                $scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : '');
            }

            // fill text body
            from = re.from[0].name || replyTo;
            sentDate = $filter('date')(re.sentDate, 'EEEE, MMM d, yyyy h:mm a');

            function createString(array) {
                var str = '';
                array.forEach(function(to) {
                    str += (str) ? ', ' : '';
                    str += ((to.name) ? to.name : to.address) + ' <' + to.address + '>';
                });
                return str;
            }

            if (forward) {
                body = '\n\n' +
                    '---------- Forwarded message ----------\n' +
                    'From: ' + re.from[0].name + ' <' + re.from[0].address + '>\n' +
                    'Date: ' + sentDate + '\n' +
                    'Subject: ' + re.subject + '\n' +
                    'To: ' + createString(re.to) + '\n' +
                    ((re.cc && re.cc.length > 0) ? 'Cc: ' + createString(re.cc) + '\n' : '') +
                    '\n\n';

            } else {
                body = '\n\n' + sentDate + ' ' + from + ' wrote:\n> ';
            }

            // only display non html mails in reply part
            if (!re.html) {
                body += re.body.trim().split('\n').join('\n> ').replace(/ >/g, '>');
                $scope.body = body;
            }
        }

        //
        // Editing headers
        //

        /**
         * This event is fired when editing the email address headers. It checks is space is pressed and if so, creates a new address field.
         */
        $scope.onAddressUpdate = function(field, index) {
            var recipient = field[index];
            $scope.verify(recipient);
        };

        /**
         * Verify and email address and fetch its public key
         */
        $scope.verify = function(recipient) {
            // set display to insecure while fetching keys
            recipient.key = undefined;
            recipient.secure = false;
            $scope.checkSendStatus();

            // verify email address
            if (!util.validateEmailAddress(recipient.address)) {
                recipient.secure = undefined;
                $scope.checkSendStatus();
                return;
            }

            // check if to address is contained in known public keys
            // when we write an email, we always need to work with the latest keys available
            keychainDao.refreshKeyForUserId(recipient.address, function(err, key) {
                if (err) {
                    $scope.onError(err);
                    return;
                }

                // compare again since model could have changed during the roundtrip
                if (key && key.userId === recipient.address) {
                    recipient.key = key;
                    recipient.secure = true;
                }

                $scope.checkSendStatus();
                $scope.$digest();
            });
        };

        $scope.getKeyId = function(recipient) {
            $scope.keyId = 'Key not found for that user.';

            if (!recipient.key) {
                return;
            }

            var fpr = crypto.getFingerprint(recipient.key.publicKey);
            var formatted = fpr.slice(32);

            $scope.keyId = formatted;
        };

        /**
         * Check if it is ok to send an email depending on the invitation state of the addresses
         */
        $scope.checkSendStatus = function() {
            $scope.okToSend = false;
            $scope.sendBtnText = undefined;
            $scope.sendBtnSecure = undefined;

            var allSecure = true;
            var numReceivers = 0;

            // count number of receivers and check security
            $scope.to.forEach(check);
            $scope.cc.forEach(check);
            $scope.bcc.forEach(check);

            function check(recipient) {
                // validate address
                if (!util.validateEmailAddress(recipient.address)) {
                    return;
                }
                numReceivers++;
                if (!recipient.secure) {
                    allSecure = false;
                }
            }

            // only allow sending if receviers exist
            if (numReceivers < 1) {
                return;
            }

            // bcc automatically disables secure sending
            if ($scope.bcc.filter(filterEmptyAddresses).length > 0) {
                allSecure = false;
            }

            if (allSecure) {
                // send encrypted if all secure
                $scope.okToSend = true;
                $scope.sendBtnText = str.sendBtnSecure;
                $scope.sendBtnSecure = true;
            } else {
                // send plaintext
                $scope.okToSend = true;
                $scope.sendBtnText = str.sendBtnClear;
                $scope.sendBtnSecure = false;
            }
        };

        //
        // Editing attachments
        //

        $scope.remove = function(attachment) {
            $scope.attachments.splice($scope.attachments.indexOf(attachment), 1);
        };

        //
        // Editing email body
        //

        // generate key,iv for encryption preview
        var key = util.random(128),
            iv = util.random(128);

        $scope.updatePreview = function() {
            var body = $scope.body.trim();

            // Although this does encrypt live using AES, this is just for show. The plaintext is encrypted seperately before sending the email.
            $scope.ciphertextPreview = (body) ? aes.encrypt(body, key, iv) : '';
        };

        $scope.sendToOutbox = function() {
            var email;

            // build email model for smtp-client
            email = {
                from: [{
                    address: emailDao._account.emailAddress
                }],
                to: $scope.to.filter(filterEmptyAddresses),
                cc: $scope.cc.filter(filterEmptyAddresses),
                bcc: $scope.bcc.filter(filterEmptyAddresses),
                subject: $scope.subject.trim() ? $scope.subject.trim() : str.fallbackSubject, // Subject line, or the fallback subject, if nothing valid was entered
                body: $scope.body.trim() + (!$scope.sendBtnSecure ? str.signature : ''), // use parsed plaintext body
                attachments: $scope.attachments,
                sentDate: new Date(),
                headers: {}
            };

            if ($scope.inReplyTo) {
                email.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
            }

            if ($scope.references && $scope.references.length) {
                email.headers.references = $scope.references.map(function(reference) {
                    return '<' + reference + '>';
                }).join(' ');
            }

            // close the writer
            $scope.state.writer.close();

            // persist the email to disk for later sending
            outbox.put(email, function(err) {
                if (err) {
                    $scope.onError(err);
                    return;
                }

                // if we need to synchronize replyTo.answered = true to imap,
                // let's do that. otherwise, we're done
                if (!$scope.replyTo || $scope.replyTo.answered) {
                    return;
                }

                $scope.replyTo.answered = true;
                emailDao.setFlags({
                    folder: currentFolder(),
                    message: $scope.replyTo
                }, function(err) {
                    if (err && err.code !== 42) {
                        $scope.onError(err);
                        return;
                    }

                    // offline or no error, let's apply the ui changes
                    $scope.$apply();
                });
            });

        };

        //
        // Helpers
        //

        function currentFolder() {
            return $scope.state.nav.currentFolder;
        }

        /*
         * Visitor to filter out objects without an address property, i.e. empty addresses
         */
        function filterEmptyAddresses(addr) {
            return !!addr.address;
        }
    };

    //
    // Directives
    //

    var ngModule = angular.module('write', []);
    ngModule.directive('contenteditable', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                // view -> model
                elm.on('keyup keydown', function() {
                    // set model
                    ctrl.$setViewValue(elm[0].innerText);
                    scope.$digest();
                });

                // model -> view
                ctrl.$render = function() {
                    elm[0].innerText = ctrl.$viewValue;
                };

                // load init value from DOM
                ctrl.$setViewValue(elm[0].innerText);
            }
        };
    });

    ngModule.directive('focusMe', function($timeout, $parse) {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element, attrs) {
                var model = $parse(attrs.focusMe);
                scope.$watch(model, function(value) {
                    if (value === true) {
                        $timeout(function() {
                            element[0].focus();
                        }, 100);
                    }
                });
            }
        };
    });

    ngModule.directive('focusChild', function() {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element) {
                element.on('click', function() {
                    element[0].children[0].focus();
                });
            }
        };
    });

    ngModule.directive('autoSize', function($parse) {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs) {
                // resize text input depending on value length
                var model = $parse(attrs.autoSize);
                scope.$watch(model, function(value) {
                    var width;

                    if (value.length < 12) {
                        width = (14 * 8) + 'px';
                    } else {
                        width = ((value.length + 2) * 8) + 'px';
                    }

                    elm.css('width', width);
                });
            }
        };
    });

    function addInput(field, scope) {
        field.push({
            address: ''
        });
        scope.$apply();
    }

    function removeInput(field, index, scope) {
        field.splice(index, 1);
        scope.$apply();
    }

    function checkForEmptyInput(field) {
        var emptyFieldExists = false;
        field.forEach(function(recipient) {
            if (!recipient.address) {
                emptyFieldExists = true;
            }
        });

        return emptyFieldExists;
    }

    function cleanupEmptyInputs(field, scope) {
        var i;

        for (i = field.length - 2; i >= 0; i--) {
            if (!field[i].address) {
                field.splice(i, 1);
            }
        }

        scope.$apply();
    }

    ngModule.directive('field', function() {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element, attrs) {
                element.on('click', function(e) {
                    if(e.target.nodeName === 'INPUT') {
                        return;
                    }

                    var fieldName = attrs.field;
                    var field = scope[fieldName];

                    if (!checkForEmptyInput(field)) {
                        // create new field input if no empy one exists
                        addInput(field, scope);
                    }

                    // focus on last input when clicking on field
                    var id = fieldName + (field.length - 1);
                    document.getElementById(id).focus();
                });
            }
        };
    });

    ngModule.directive('addressInput', function() {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element, attrs) {
                // get prefix for id
                var fieldName = attrs.addressInput;
                var field = scope[fieldName];
                var index = parseInt(attrs.id.replace(fieldName, ''), 10);

                element.on('blur', function() {
                    if (!checkForEmptyInput(field)) {
                        // create new field input
                        addInput(field, scope);
                    }

                    cleanupEmptyInputs(field, scope);
                });

                element.on('keydown', function(e) {
                    var code = e.keyCode;

                    scope.$digest();

                    if (code === 32 || code === 188 || code === 186) {
                        // catch space, comma, semicolon
                        e.preventDefault();

                        // add next field only if current input is not empty
                        if(field[index].address) {
                            // create new field input
                            addInput(field, scope);
                            // find next input and focus
                            var nextId = fieldName + (index + 1);
                            document.getElementById(nextId).focus();
                        }
                    } else if ((code === 8 || code === 46) && !field[index].address && field.length > 1) {
                        // backspace, delete on empty input
                        // remove input
                        e.preventDefault();
                        removeInput(field, index, scope);
                        // focus on previous id
                        var previousId = fieldName + (index - 1);
                        document.getElementById(previousId).focus();
                    }
                });
            }
        };
    });

    ngModule.directive('attachmentInput', function() {
        return function(scope, elm) {
            elm.on('change', function(e) {
                for (var i = 0; i < e.target.files.length; i++) {
                    addAttachment(e.target.files.item(i));
                }
            });

            function addAttachment(file) {
                var reader = new FileReader();
                reader.onload = function(e) {
                    scope.attachments.push({
                        filename: file.name,
                        mimeType: file.type,
                        content: new Uint8Array(e.target.result)
                    });
                    scope.$digest();
                };
                reader.readAsArrayBuffer(file);
            }
        };
    });

    ngModule.directive('attachmentBtn', function() {
        return function(scope, elm) {
            elm.on('click touchstart', function(e) {
                e.preventDefault();
                document.querySelector('#attachment-input').click();
            });
        };
    });

    return WriteCtrl;
});<commit_msg>Fix bug that catches empty string in writer email-header auto-size directice<commit_after>define(function(require) {
    'use strict';

    var angular = require('angular'),
        _ = require('underscore'),
        appController = require('js/app-controller'),
        aes = require('cryptoLib/aes-cbc'),
        util = require('cryptoLib/util'),
        str = require('js/app-config').string,
        crypto, emailDao, outbox, keychainDao;

    //
    // Controller
    //

    var WriteCtrl = function($scope, $filter) {
        crypto = appController._crypto;
        emailDao = appController._emailDao,
        outbox = appController._outboxBo;
        keychainDao = appController._keychain;

        // set default value so that the popover height is correct on init
        $scope.keyId = 'XXXXXXXX';

        //
        // Init
        //

        $scope.state.writer = {
            write: function(replyTo, replyAll, forward) {
                $scope.state.lightbox = 'write';
                $scope.replyTo = replyTo;

                resetFields();

                // fill fields depending on replyTo
                fillFields(replyTo, replyAll, forward);
                $scope.updatePreview();

                $scope.verify($scope.to[0]);
            },
            close: function() {
                $scope.state.lightbox = undefined;
            }
        };

        function resetFields() {
            $scope.writerTitle = 'New email';
            $scope.to = [{
                address: ''
            }];
            $scope.showCC = false;
            $scope.cc = [{
                address: ''
            }];
            $scope.showBCC = false;
            $scope.bcc = [{
                address: ''
            }];
            $scope.subject = '';
            $scope.body = '';
            $scope.ciphertextPreview = '';
            $scope.attachments = [];
        }

        function fillFields(re, replyAll, forward) {
            var replyTo, from, sentDate, body;

            if (!re) {
                return;
            }

            $scope.writerTitle = (forward) ? 'Forward' : 'Reply';

            replyTo = re.replyTo && re.replyTo[0] && re.replyTo[0].address || re.from[0].address;

            // fill recipient field and references
            if (!forward) {
                $scope.to.unshift({
                    address: replyTo
                });
                $scope.to.forEach($scope.verify);
                if ((re.references || []).indexOf(re.id) < 0) {
                    // references might not exist yet, so use the double concat
                    $scope.references = [].concat(re.references || []).concat(re.id);
                } else {
                    $scope.references = re.references;
                }
                $scope.inReplyTo = re.id;
            }
            if (replyAll) {
                re.to.concat(re.cc).forEach(function(recipient) {
                    var me = emailDao._account.emailAddress;
                    if (recipient.address === me && replyTo !== me) {
                        // don't reply to yourself
                        return;
                    }
                    $scope.cc.unshift({
                        address: recipient.address
                    });
                });

                // filter duplicates
                $scope.cc = _.uniq($scope.cc, function(recipient) {
                    return recipient.address;
                });
                $scope.showCC = true;
                $scope.cc.forEach($scope.verify);
            }

            // fill attachments and references on forward
            if (forward) {
                // create a new array, otherwise removing an attachment will also
                // remove it from the original in the mail list as a side effect
                $scope.attachments = [].concat(re.attachments);
                $scope.references = [re.id];
            }

            // fill subject
            if (forward) {
                $scope.subject = 'Fwd: ' + re.subject;
            } else {
                $scope.subject = 'Re: ' + ((re.subject) ? re.subject.replace('Re: ', '') : '');
            }

            // fill text body
            from = re.from[0].name || replyTo;
            sentDate = $filter('date')(re.sentDate, 'EEEE, MMM d, yyyy h:mm a');

            function createString(array) {
                var str = '';
                array.forEach(function(to) {
                    str += (str) ? ', ' : '';
                    str += ((to.name) ? to.name : to.address) + ' <' + to.address + '>';
                });
                return str;
            }

            if (forward) {
                body = '\n\n' +
                    '---------- Forwarded message ----------\n' +
                    'From: ' + re.from[0].name + ' <' + re.from[0].address + '>\n' +
                    'Date: ' + sentDate + '\n' +
                    'Subject: ' + re.subject + '\n' +
                    'To: ' + createString(re.to) + '\n' +
                    ((re.cc && re.cc.length > 0) ? 'Cc: ' + createString(re.cc) + '\n' : '') +
                    '\n\n';

            } else {
                body = '\n\n' + sentDate + ' ' + from + ' wrote:\n> ';
            }

            // only display non html mails in reply part
            if (!re.html) {
                body += re.body.trim().split('\n').join('\n> ').replace(/ >/g, '>');
                $scope.body = body;
            }
        }

        //
        // Editing headers
        //

        /**
         * This event is fired when editing the email address headers. It checks is space is pressed and if so, creates a new address field.
         */
        $scope.onAddressUpdate = function(field, index) {
            var recipient = field[index];
            $scope.verify(recipient);
        };

        /**
         * Verify and email address and fetch its public key
         */
        $scope.verify = function(recipient) {
            // set display to insecure while fetching keys
            recipient.key = undefined;
            recipient.secure = false;
            $scope.checkSendStatus();

            // verify email address
            if (!util.validateEmailAddress(recipient.address)) {
                recipient.secure = undefined;
                $scope.checkSendStatus();
                return;
            }

            // check if to address is contained in known public keys
            // when we write an email, we always need to work with the latest keys available
            keychainDao.refreshKeyForUserId(recipient.address, function(err, key) {
                if (err) {
                    $scope.onError(err);
                    return;
                }

                // compare again since model could have changed during the roundtrip
                if (key && key.userId === recipient.address) {
                    recipient.key = key;
                    recipient.secure = true;
                }

                $scope.checkSendStatus();
                $scope.$digest();
            });
        };

        $scope.getKeyId = function(recipient) {
            $scope.keyId = 'Key not found for that user.';

            if (!recipient.key) {
                return;
            }

            var fpr = crypto.getFingerprint(recipient.key.publicKey);
            var formatted = fpr.slice(32);

            $scope.keyId = formatted;
        };

        /**
         * Check if it is ok to send an email depending on the invitation state of the addresses
         */
        $scope.checkSendStatus = function() {
            $scope.okToSend = false;
            $scope.sendBtnText = undefined;
            $scope.sendBtnSecure = undefined;

            var allSecure = true;
            var numReceivers = 0;

            // count number of receivers and check security
            $scope.to.forEach(check);
            $scope.cc.forEach(check);
            $scope.bcc.forEach(check);

            function check(recipient) {
                // validate address
                if (!util.validateEmailAddress(recipient.address)) {
                    return;
                }
                numReceivers++;
                if (!recipient.secure) {
                    allSecure = false;
                }
            }

            // only allow sending if receviers exist
            if (numReceivers < 1) {
                return;
            }

            // bcc automatically disables secure sending
            if ($scope.bcc.filter(filterEmptyAddresses).length > 0) {
                allSecure = false;
            }

            if (allSecure) {
                // send encrypted if all secure
                $scope.okToSend = true;
                $scope.sendBtnText = str.sendBtnSecure;
                $scope.sendBtnSecure = true;
            } else {
                // send plaintext
                $scope.okToSend = true;
                $scope.sendBtnText = str.sendBtnClear;
                $scope.sendBtnSecure = false;
            }
        };

        //
        // Editing attachments
        //

        $scope.remove = function(attachment) {
            $scope.attachments.splice($scope.attachments.indexOf(attachment), 1);
        };

        //
        // Editing email body
        //

        // generate key,iv for encryption preview
        var key = util.random(128),
            iv = util.random(128);

        $scope.updatePreview = function() {
            var body = $scope.body.trim();

            // Although this does encrypt live using AES, this is just for show. The plaintext is encrypted seperately before sending the email.
            $scope.ciphertextPreview = (body) ? aes.encrypt(body, key, iv) : '';
        };

        $scope.sendToOutbox = function() {
            var email;

            // build email model for smtp-client
            email = {
                from: [{
                    address: emailDao._account.emailAddress
                }],
                to: $scope.to.filter(filterEmptyAddresses),
                cc: $scope.cc.filter(filterEmptyAddresses),
                bcc: $scope.bcc.filter(filterEmptyAddresses),
                subject: $scope.subject.trim() ? $scope.subject.trim() : str.fallbackSubject, // Subject line, or the fallback subject, if nothing valid was entered
                body: $scope.body.trim() + (!$scope.sendBtnSecure ? str.signature : ''), // use parsed plaintext body
                attachments: $scope.attachments,
                sentDate: new Date(),
                headers: {}
            };

            if ($scope.inReplyTo) {
                email.headers['in-reply-to'] = '<' + $scope.inReplyTo + '>';
            }

            if ($scope.references && $scope.references.length) {
                email.headers.references = $scope.references.map(function(reference) {
                    return '<' + reference + '>';
                }).join(' ');
            }

            // close the writer
            $scope.state.writer.close();

            // persist the email to disk for later sending
            outbox.put(email, function(err) {
                if (err) {
                    $scope.onError(err);
                    return;
                }

                // if we need to synchronize replyTo.answered = true to imap,
                // let's do that. otherwise, we're done
                if (!$scope.replyTo || $scope.replyTo.answered) {
                    return;
                }

                $scope.replyTo.answered = true;
                emailDao.setFlags({
                    folder: currentFolder(),
                    message: $scope.replyTo
                }, function(err) {
                    if (err && err.code !== 42) {
                        $scope.onError(err);
                        return;
                    }

                    // offline or no error, let's apply the ui changes
                    $scope.$apply();
                });
            });

        };

        //
        // Helpers
        //

        function currentFolder() {
            return $scope.state.nav.currentFolder;
        }

        /*
         * Visitor to filter out objects without an address property, i.e. empty addresses
         */
        function filterEmptyAddresses(addr) {
            return !!addr.address;
        }
    };

    //
    // Directives
    //

    var ngModule = angular.module('write', []);
    ngModule.directive('contenteditable', function() {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs, ctrl) {
                // view -> model
                elm.on('keyup keydown', function() {
                    // set model
                    ctrl.$setViewValue(elm[0].innerText);
                    scope.$digest();
                });

                // model -> view
                ctrl.$render = function() {
                    elm[0].innerText = ctrl.$viewValue;
                };

                // load init value from DOM
                ctrl.$setViewValue(elm[0].innerText);
            }
        };
    });

    ngModule.directive('focusMe', function($timeout, $parse) {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element, attrs) {
                var model = $parse(attrs.focusMe);
                scope.$watch(model, function(value) {
                    if (value === true) {
                        $timeout(function() {
                            element[0].focus();
                        }, 100);
                    }
                });
            }
        };
    });

    ngModule.directive('focusChild', function() {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element) {
                element.on('click', function() {
                    element[0].children[0].focus();
                });
            }
        };
    });

    ngModule.directive('autoSize', function($parse) {
        return {
            require: 'ngModel',
            link: function(scope, elm, attrs) {
                // resize text input depending on value length
                var model = $parse(attrs.autoSize);
                scope.$watch(model, function(value) {
                    var width;

                    if (!value || value.length < 12) {
                        width = (14 * 8) + 'px';
                    } else {
                        width = ((value.length + 2) * 8) + 'px';
                    }

                    elm.css('width', width);
                });
            }
        };
    });

    function addInput(field, scope) {
        field.push({
            address: ''
        });
        scope.$apply();
    }

    function removeInput(field, index, scope) {
        field.splice(index, 1);
        scope.$apply();
    }

    function checkForEmptyInput(field) {
        var emptyFieldExists = false;
        field.forEach(function(recipient) {
            if (!recipient.address) {
                emptyFieldExists = true;
            }
        });

        return emptyFieldExists;
    }

    function cleanupEmptyInputs(field, scope) {
        var i;

        for (i = field.length - 2; i >= 0; i--) {
            if (!field[i].address) {
                field.splice(i, 1);
            }
        }

        scope.$apply();
    }

    ngModule.directive('field', function() {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element, attrs) {
                element.on('click', function(e) {
                    if (e.target.nodeName === 'INPUT') {
                        return;
                    }

                    var fieldName = attrs.field;
                    var field = scope[fieldName];

                    if (!checkForEmptyInput(field)) {
                        // create new field input if no empy one exists
                        addInput(field, scope);
                    }

                    // focus on last input when clicking on field
                    var id = fieldName + (field.length - 1);
                    document.getElementById(id).focus();
                });
            }
        };
    });

    ngModule.directive('addressInput', function() {
        return {
            //scope: true,   // optionally create a child scope
            link: function(scope, element, attrs) {
                // get prefix for id
                var fieldName = attrs.addressInput;
                var field = scope[fieldName];
                var index = parseInt(attrs.id.replace(fieldName, ''), 10);

                element.on('blur', function() {
                    if (!checkForEmptyInput(field)) {
                        // create new field input
                        addInput(field, scope);
                    }

                    cleanupEmptyInputs(field, scope);
                });

                element.on('keydown', function(e) {
                    var code = e.keyCode;

                    scope.$digest();

                    if (code === 32 || code === 188 || code === 186) {
                        // catch space, comma, semicolon
                        e.preventDefault();

                        // add next field only if current input is not empty
                        if (field[index].address) {
                            // create new field input
                            addInput(field, scope);
                            // find next input and focus
                            var nextId = fieldName + (index + 1);
                            document.getElementById(nextId).focus();
                        }
                    } else if ((code === 8 || code === 46) && !field[index].address && field.length > 1) {
                        // backspace, delete on empty input
                        // remove input
                        e.preventDefault();
                        removeInput(field, index, scope);
                        // focus on previous id
                        var previousId = fieldName + (index - 1);
                        document.getElementById(previousId).focus();
                    }
                });
            }
        };
    });

    ngModule.directive('attachmentInput', function() {
        return function(scope, elm) {
            elm.on('change', function(e) {
                for (var i = 0; i < e.target.files.length; i++) {
                    addAttachment(e.target.files.item(i));
                }
            });

            function addAttachment(file) {
                var reader = new FileReader();
                reader.onload = function(e) {
                    scope.attachments.push({
                        filename: file.name,
                        mimeType: file.type,
                        content: new Uint8Array(e.target.result)
                    });
                    scope.$digest();
                };
                reader.readAsArrayBuffer(file);
            }
        };
    });

    ngModule.directive('attachmentBtn', function() {
        return function(scope, elm) {
            elm.on('click touchstart', function(e) {
                e.preventDefault();
                document.querySelector('#attachment-input').click();
            });
        };
    });

    return WriteCtrl;
});<|endoftext|>


<commit_before>// configure angular
require(['angular', 'jquery', 'bootstrap', 'heracles-d3', 'jasny', 'heracles_common', 'monster', 'bootstrap-select', 'ohdsi_common',
        '../js/charts/dashboard', '../js/charts/person', '../js/charts/conditions', '../js/charts/drugExposures',
        '../js/charts/conditionEras', '../js/charts/drugEras', '../js/charts/cohortSpecific',
        '../js/charts/observations', '../js/charts/observationPeriod', '../js/charts/dataDensity',
        '../js/charts/death', '../js/charts/procedures', '../js/charts/visits', '../js/charts/measurements',
        '../js/charts/heraclesHeel', '../js/charts/conditionByIndex', '../js/charts/drugByIndex', '../js/charts/procedureByIndex'],
	function (angular, $, b, HeraclesD3, j, heraclesCommon, monster, SelectPicker, OHDSICommon,
		DashboardRenderer, PersonRenderer, ConditionRenderer, DrugExposureRenderer,
		ConditionErasRenderer, DrugErasRenderer, CohortSpecificRenderer,
		ObservationsRenderer, ObservationPeriodRenderer, DataDensityRenderer,
		DeathDataRenderer, ProceduresRenderer, VisitsRenderer, MeasurementsRenderer,
		HeraclesHeelRenderer, ConditionsByIndexRenderer, DrugsByIndexRenderer,
		ProceduresByIndexRenderer) {
		var renderers = {
			'dashboard': DashboardRenderer,
			'person': PersonRenderer,
			'condition': ConditionRenderer,
			'drugExposures': DrugExposureRenderer,
			'conditionEras': ConditionErasRenderer,
			'drugEras': DrugErasRenderer,
			'cohortSpecific': CohortSpecificRenderer,
			'observations': ObservationsRenderer,
			'observationPeriods': ObservationPeriodRenderer,
			'dataDensity': DataDensityRenderer,
			'death': DeathDataRenderer,
			'procedures': ProceduresRenderer,
			'visits': VisitsRenderer,
			'measurements': MeasurementsRenderer,
			'heraclesHeel': HeraclesHeelRenderer,
			'conditionByIndex': ConditionsByIndexRenderer,
			'drugByIndex': DrugsByIndexRenderer,
			'procedureByIndex': ProceduresByIndexRenderer
		};
		angular.element().ready(function () {
			// setup angular controller on angular ready
			var app = angular.module('HeraclesResults', []);
			app.service('CohortService', function () {
				this.cohort = {};

				this.setCohort = function (c) {
					this.cohort = c;
				};
				this.getCohort = function () {
					return this.cohort;
				};
			});

			app.controller('CohortViewerCtrl', function ($scope, $http, CohortService) {

                $scope.sources = {};
                $scope.selectedSource = {};
                $scope.selectedSourceString = "";
                $scope.template = 'src/templates/empty.html';

				$scope.summary = undefined;

				$scope.refreshCommonData = function () {
					if ($scope.active === "dashboard" || $scope.active === "person") {
						$.getJSON(getWebApiUrl($scope.selectedSource) + "cohortresults/" + $scope.cohort.id + "/raw/person/population", function (data) {
							if (data === undefined) {
                                return;
                            }
							var summary = {};
							$.each(data, function () {
                                if (this.ATTRIBUTE_NAME) {
                                    if (this.ATTRIBUTE_NAME.toLowerCase() === "source name") {
                                        summary.sourceName = this.ATTRIBUTE_VALUE;
                                    } else if (this.ATTRIBUTE_NAME.toLowerCase() === "number of persons") {
                                        summary.numPersons = (this.ATTRIBUTE_VALUE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                                    }
                                }
							});
							$scope.summary = summary;
							$scope.$apply();
						});
					} else {
						$scope.summary = $scope.defaultSummary;
						$scope.$apply();
					}
				};

				$scope.refreshCohortVisualization = function (evt) {
					// TODO
				};

				$scope.goBack = function (evt) {
					$("#viewer-container").slideUp("fast", function () {
						$("#searcher-container").slideDown('fast', function () {
							$("#cohorts")
								.val("")
								.focus();
						});
					});
				};

                $scope.hasOneSource = function () {
                    var keys = _.keys($scope.sources);
                    return keys.length <= 1;
                };

				$scope.setupAndDisplayCohort = function (datum, animate, sources) {

                    $scope.sources = {};
                    $.each(sources, function() {
                        $scope.sources[this.sourceKey] = this;
                    });
                    $scope.selectedSource = sources[0];
                    $scope.selectedSourceString = OHDSICommon.generateSourceString($scope.selectedSource);
                    $scope.$apply();

					$("#cohorts").val(datum.name);
					$scope.cohort = datum;
					//console.log(datum);
					CohortService.setCohort(datum);

					if (animate) {
						$("#searcher-container").slideUp("fast", function () {
							$("#viewer-container").slideDown("slow", function() {
                                setTimeout(function() {
                                    $('.selectpicker').selectpicker('refresh');
                                    $('.selectpicker').selectpicker('render');
                                }, 500);
                            });
						});
					} else {
						$("#searcher-container").hide();
						$("#viewer-container").show('fast', function() {
                            setTimeout(function() {
                                $('.selectpicker').selectpicker('refresh');
                                $('.selectpicker').selectpicker('render');
                            }, 500);
                        });

					}

				};

				$scope.renderVisualizationSection = function (id) {
					$("#chart-container").hide();
					$scope.active = id;
					$scope.template = 'src/templates/' + id + '.html';
					$scope.refreshCommonData();

					var renderer = renderers[id];
					if (renderer) {
						renderer.render(CohortService.getCohort());
					}
					$("#chart-container").show();
				};

				// include other scripts
				require(['cohort-searcher']);

				$(".chartTypes").click(function () {
					var self = $(this);
					$(".active").removeClass("active");
					self.parent("li").addClass("active");

                    $('#chart-wrapper').empty();
					var id = $(this).attr("id");
					$scope.renderVisualizationSection(id);

				});

				$("#cohorts-viewer-typeahead").bind('typeahead:selected', function (obj, datum, name) {
                    getSources(true, function(sources) {
                        $scope.setupAndDisplayCohort(datum, true, sources);
                    });

				});

				$(document).ready(function () {
                    $('.selectpicker').selectpicker({
                        width: '170px'
                    });

                    $('#sourcepicker').on('change', function(){
                        var sel = $(this).find("option:selected");
                        $scope.selectedSource = ($scope.sources[$(sel).val()]);

                        $scope.selectedSourceString = OHDSICommon.generateSourceString($scope.selectedSource);
                        $scope.$apply();

                        $('#sourcepicker').selectpicker('refresh');
                        $('#' + $scope.active).trigger('click');
                    });

					function doDefault() {
						setTimeout(function () {
							$("#cohorts").focus();
						}, 300);
					}

					var param = urlParam('cohortId');
					if (param && param !== '') {
						var lastWebApi = monster.get('last-webapi');
						if (lastWebApi) {
							setSelectedWebApiUrl(+lastWebApi);
						}
						$http.get(getWebApiUrl() + 'cohortdefinition/' + param).
						success(function (data, status, headers, config) {
							if (data) {
                                getSources(true, function(sources) {
                                    $scope.setupAndDisplayCohort(data, true, sources);

                                });
							}
						}).
						error(function (data, status, headers, config) {
							console.log("unable to retrieve cohort");
							doDefault();
						});
					} else {
						doDefault();
					}

				});
			});

			// manually boostrap angular since using amd
			angular.bootstrap(document, ['HeraclesResults']);

		});

	}
);
<commit_msg>reset template on 'back'<commit_after>// configure angular
require(['angular', 'jquery', 'bootstrap', 'heracles-d3', 'jasny', 'heracles_common', 'monster', 'bootstrap-select', 'ohdsi_common',
        '../js/charts/dashboard', '../js/charts/person', '../js/charts/conditions', '../js/charts/drugExposures',
        '../js/charts/conditionEras', '../js/charts/drugEras', '../js/charts/cohortSpecific',
        '../js/charts/observations', '../js/charts/observationPeriod', '../js/charts/dataDensity',
        '../js/charts/death', '../js/charts/procedures', '../js/charts/visits', '../js/charts/measurements',
        '../js/charts/heraclesHeel', '../js/charts/conditionByIndex', '../js/charts/drugByIndex', '../js/charts/procedureByIndex'],
	function (angular, $, b, HeraclesD3, j, heraclesCommon, monster, SelectPicker, OHDSICommon,
		DashboardRenderer, PersonRenderer, ConditionRenderer, DrugExposureRenderer,
		ConditionErasRenderer, DrugErasRenderer, CohortSpecificRenderer,
		ObservationsRenderer, ObservationPeriodRenderer, DataDensityRenderer,
		DeathDataRenderer, ProceduresRenderer, VisitsRenderer, MeasurementsRenderer,
		HeraclesHeelRenderer, ConditionsByIndexRenderer, DrugsByIndexRenderer,
		ProceduresByIndexRenderer) {
		var renderers = {
			'dashboard': DashboardRenderer,
			'person': PersonRenderer,
			'condition': ConditionRenderer,
			'drugExposures': DrugExposureRenderer,
			'conditionEras': ConditionErasRenderer,
			'drugEras': DrugErasRenderer,
			'cohortSpecific': CohortSpecificRenderer,
			'observations': ObservationsRenderer,
			'observationPeriods': ObservationPeriodRenderer,
			'dataDensity': DataDensityRenderer,
			'death': DeathDataRenderer,
			'procedures': ProceduresRenderer,
			'visits': VisitsRenderer,
			'measurements': MeasurementsRenderer,
			'heraclesHeel': HeraclesHeelRenderer,
			'conditionByIndex': ConditionsByIndexRenderer,
			'drugByIndex': DrugsByIndexRenderer,
			'procedureByIndex': ProceduresByIndexRenderer
		};
		angular.element().ready(function () {
			// setup angular controller on angular ready
			var app = angular.module('HeraclesResults', []);
			app.service('CohortService', function () {
				this.cohort = {};

				this.setCohort = function (c) {
					this.cohort = c;
				};
				this.getCohort = function () {
					return this.cohort;
				};
			});

			app.controller('CohortViewerCtrl', function ($scope, $http, CohortService) {

                $scope.sources = {};
                $scope.selectedSource = {};
                $scope.selectedSourceString = "";
                $scope.template = 'src/templates/empty.html';

				$scope.summary = undefined;

				$scope.refreshCommonData = function () {
					if ($scope.active === "dashboard" || $scope.active === "person") {
						$.getJSON(getWebApiUrl($scope.selectedSource) + "cohortresults/" + $scope.cohort.id + "/raw/person/population", function (data) {
							if (data === undefined) {
                                return;
                            }
							var summary = {};
							$.each(data, function () {
                                if (this.ATTRIBUTE_NAME) {
                                    if (this.ATTRIBUTE_NAME.toLowerCase() === "source name") {
                                        summary.sourceName = this.ATTRIBUTE_VALUE;
                                    } else if (this.ATTRIBUTE_NAME.toLowerCase() === "number of persons") {
                                        summary.numPersons = (this.ATTRIBUTE_VALUE).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                                    }
                                }
							});
							$scope.summary = summary;
							$scope.$apply();
						});
					} else {
						$scope.summary = $scope.defaultSummary;
						$scope.$apply();
					}
				};

				$scope.refreshCohortVisualization = function (evt) {
					// TODO
				};

				$scope.goBack = function (evt) {
                    $("#chart-wrapper").empty();
                    $scope.template = 'src/templates/empty.html';
					$("#viewer-container").slideUp("fast", function () {
						$("#searcher-container").slideDown('fast', function () {
							$("#cohorts")
								.val("")
								.focus();
						});
					});
				};

                $scope.hasOneSource = function () {
                    var keys = _.keys($scope.sources);
                    return keys.length <= 1;
                };

				$scope.setupAndDisplayCohort = function (datum, animate, sources) {

                    $scope.sources = {};
                    $.each(sources, function() {
                        $scope.sources[this.sourceKey] = this;
                    });
                    $scope.selectedSource = sources[0];
                    $scope.selectedSourceString = OHDSICommon.generateSourceString($scope.selectedSource);
                    $scope.$apply();

					$("#cohorts").val(datum.name);
					$scope.cohort = datum;
					//console.log(datum);
					CohortService.setCohort(datum);

					if (animate) {
						$("#searcher-container").slideUp("fast", function () {
							$("#viewer-container").slideDown("slow", function() {
                                setTimeout(function() {
                                    $('.selectpicker').selectpicker('refresh');
                                    $('.selectpicker').selectpicker('render');
                                }, 500);
                            });
						});
					} else {
						$("#searcher-container").hide();
						$("#viewer-container").show('fast', function() {
                            setTimeout(function() {
                                $('.selectpicker').selectpicker('refresh');
                                $('.selectpicker').selectpicker('render');
                            }, 500);
                        });

					}

				};

				$scope.renderVisualizationSection = function (id) {
					$("#chart-container").hide();
					$scope.active = id;
					$scope.template = 'src/templates/' + id + '.html';
					$scope.refreshCommonData();

					var renderer = renderers[id];
					if (renderer) {
						renderer.render(CohortService.getCohort());
					}
					$("#chart-container").show();
				};

				// include other scripts
				require(['cohort-searcher']);

				$(".chartTypes").click(function () {
					var self = $(this);
					$(".active").removeClass("active");
					self.parent("li").addClass("active");

                    $('#chart-wrapper').empty();
					var id = $(this).attr("id");
					$scope.renderVisualizationSection(id);

				});

				$("#cohorts-viewer-typeahead").bind('typeahead:selected', function (obj, datum, name) {
                    getSources(true, function(sources) {
                        $scope.setupAndDisplayCohort(datum, true, sources);
                    });

				});

				$(document).ready(function () {
                    $('.selectpicker').selectpicker({
                        width: '170px'
                    });

                    $('#sourcepicker').on('change', function(){
                        var sel = $(this).find("option:selected");
                        $scope.selectedSource = ($scope.sources[$(sel).val()]);

                        $scope.selectedSourceString = OHDSICommon.generateSourceString($scope.selectedSource);
                        $scope.$apply();

                        $('#sourcepicker').selectpicker('refresh');
                        $('#' + $scope.active).trigger('click');
                    });

					function doDefault() {
						setTimeout(function () {
							$("#cohorts").focus();
						}, 300);
					}

					var param = urlParam('cohortId');
					if (param && param !== '') {
						var lastWebApi = monster.get('last-webapi');
						if (lastWebApi) {
							setSelectedWebApiUrl(+lastWebApi);
						}
						$http.get(getWebApiUrl() + 'cohortdefinition/' + param).
						success(function (data, status, headers, config) {
							if (data) {
                                getSources(true, function(sources) {
                                    $scope.setupAndDisplayCohort(data, true, sources);

                                });
							}
						}).
						error(function (data, status, headers, config) {
							console.log("unable to retrieve cohort");
							doDefault();
						});
					} else {
						doDefault();
					}

				});
			});

			// manually boostrap angular since using amd
			angular.bootstrap(document, ['HeraclesResults']);

		});

	}
);
<|endoftext|>


<commit_before>import i18n from '../../i18n';
import loginModal from '../loginModal';
import popupWidget from '../shared/popup';
import router from '../../router';
import * as helper from '../helper';
import * as m from 'mithril';
import * as Chart from 'chart.js';
import formWidgets from '../shared/form';

Chart.defaults.global.animation = false;

export default {

  controller: function(root) {
    let isOpen = false;

    function open() {
      router.backbutton.stack.push(close);
      isOpen = true;
    }

    function close(fromBB) {
      if (fromBB !== 'backbutton' && isOpen) router.backbutton.stack.pop();
      isOpen = false;
    }

    return {
      open: open,
      close: close,
      isOpen: function() {
        return isOpen;
      },
      root
    };
  },

  view: function(ctrl) {
    return popupWidget(
      'trainingMenu',
      null,
      renderTrainingMenu.bind(undefined, ctrl.root),
      ctrl.isOpen(),
      ctrl.close
    );
  }
};

export function renderUserInfos(ctrl) {
  return [
    m('p', m.trust(i18n('yourPuzzleRatingX', `<strong>${ctrl.data.user.rating}</strong>`))),
    helper.isPortrait() ? m('br') : null,
    ctrl.data.user.history ? m('canvas', {
      oncreate(vnode) {
        const ctx = vnode.dom.getContext('2d');
        drawChart(ctrl, ctx);
        vnode.state.hash = chartHash(ctrl);
      },
      onupdate(vnode) {
        const hash = chartHash(ctrl);
        if (hash === vnode.state.hash) return;
        vnode.state.hash = hash;
        const ctx = vnode.dom.getContext('2d');
        drawChart(ctrl, ctx);
      }
    }) : null
  ];
}

export function renderSigninBox() {
  return m('div', [
    m('p', i18n('toTrackYourProgress')),
    m('p',
      m('button', {
        oncreate: helper.ontap(loginModal.open)
      }, [m('span.fa.fa-user'), i18n('signIn')])
    ),
    m('p', i18n('trainingSignupExplanation'))
  ]);
}

function chartHash(ctrl) {
  return ctrl.data.user.history.join('') + (helper.isPortrait() ? 'portrait' : 'landscape');
}

function drawChart(ctrl, ctx) {
  const canvas = ctx.canvas;
  if (helper.isPortrait()) {
    canvas.width = canvas.style.width = canvas.parentElement.offsetWidth - 20;
    canvas.height = canvas.style.height = 150;
  } else {
    canvas.width = canvas.style.width = canvas.parentElement.offsetWidth;
    canvas.height = canvas.style.height = canvas.parentElement.offsetHeight - 20;
  }
  console.log(ctrl.data.user.history);
  new Chart(ctx, {
    type: 'line',
    data: {
      labels: ctrl.data.user.history.map(() => ''),
      datasets: [{
        data: ctrl.data.user.history,
        backgroundColor: 'rgba(196, 168, 111, 0.4)',
        borderColor: 'rgba(196, 168, 111, 0.8)',
        pointRadius: 0
      }]
    },
    options: {
      scales: {
        xAxes: [{
          display: false
        }],
        yAxes: [{
          display: false
        }]
      },
      legend: {
        display: false
      }
    }
  });
}

function renderDifficulty(ctrl) {
  const opts = ctrl.data.difficulty.choices.map(c => ['difficulty' + c[1], c[0]]);
  return m('div.select_input.puzzleDifficulty',
    formWidgets.renderSelect('level', 'difficulty', opts,
      () => ctrl.data.difficulty.current, false, ctrl.setDifficulty)
  );
}

function renderTrainingMenu(ctrl) {
  if (ctrl.data.user) {
    if (helper.isPortrait())
      return renderUserInfos(ctrl).concat(renderDifficulty(ctrl));
    else
      return renderDifficulty(ctrl);
  } else {
    return renderSigninBox();
  }
}
<commit_msg>Remove console.log<commit_after>import i18n from '../../i18n';
import loginModal from '../loginModal';
import popupWidget from '../shared/popup';
import router from '../../router';
import * as helper from '../helper';
import * as m from 'mithril';
import * as Chart from 'chart.js';
import formWidgets from '../shared/form';

Chart.defaults.global.animation = false;

export default {

  controller: function(root) {
    let isOpen = false;

    function open() {
      router.backbutton.stack.push(close);
      isOpen = true;
    }

    function close(fromBB) {
      if (fromBB !== 'backbutton' && isOpen) router.backbutton.stack.pop();
      isOpen = false;
    }

    return {
      open: open,
      close: close,
      isOpen: function() {
        return isOpen;
      },
      root
    };
  },

  view: function(ctrl) {
    return popupWidget(
      'trainingMenu',
      null,
      renderTrainingMenu.bind(undefined, ctrl.root),
      ctrl.isOpen(),
      ctrl.close
    );
  }
};

export function renderUserInfos(ctrl) {
  return [
    m('p', m.trust(i18n('yourPuzzleRatingX', `<strong>${ctrl.data.user.rating}</strong>`))),
    helper.isPortrait() ? m('br') : null,
    ctrl.data.user.history ? m('canvas', {
      oncreate(vnode) {
        const ctx = vnode.dom.getContext('2d');
        drawChart(ctrl, ctx);
        vnode.state.hash = chartHash(ctrl);
      },
      onupdate(vnode) {
        const hash = chartHash(ctrl);
        if (hash === vnode.state.hash) return;
        vnode.state.hash = hash;
        const ctx = vnode.dom.getContext('2d');
        drawChart(ctrl, ctx);
      }
    }) : null
  ];
}

export function renderSigninBox() {
  return m('div', [
    m('p', i18n('toTrackYourProgress')),
    m('p',
      m('button', {
        oncreate: helper.ontap(loginModal.open)
      }, [m('span.fa.fa-user'), i18n('signIn')])
    ),
    m('p', i18n('trainingSignupExplanation'))
  ]);
}

function chartHash(ctrl) {
  return ctrl.data.user.history.join('') + (helper.isPortrait() ? 'portrait' : 'landscape');
}

function drawChart(ctrl, ctx) {
  const canvas = ctx.canvas;
  if (helper.isPortrait()) {
    canvas.width = canvas.style.width = canvas.parentElement.offsetWidth - 20;
    canvas.height = canvas.style.height = 150;
  } else {
    canvas.width = canvas.style.width = canvas.parentElement.offsetWidth;
    canvas.height = canvas.style.height = canvas.parentElement.offsetHeight - 20;
  }
  new Chart(ctx, {
    type: 'line',
    data: {
      labels: ctrl.data.user.history.map(() => ''),
      datasets: [{
        data: ctrl.data.user.history,
        backgroundColor: 'rgba(196, 168, 111, 0.4)',
        borderColor: 'rgba(196, 168, 111, 0.8)',
        pointRadius: 0
      }]
    },
    options: {
      scales: {
        xAxes: [{
          display: false
        }],
        yAxes: [{
          display: false
        }]
      },
      legend: {
        display: false
      }
    }
  });
}

function renderDifficulty(ctrl) {
  const opts = ctrl.data.difficulty.choices.map(c => ['difficulty' + c[1], c[0]]);
  return m('div.select_input.puzzleDifficulty',
    formWidgets.renderSelect('level', 'difficulty', opts,
      () => ctrl.data.difficulty.current, false, ctrl.setDifficulty)
  );
}

function renderTrainingMenu(ctrl) {
  if (ctrl.data.user) {
    if (helper.isPortrait())
      return renderUserInfos(ctrl).concat(renderDifficulty(ctrl));
    else
      return renderDifficulty(ctrl);
  } else {
    return renderSigninBox();
  }
}
<|endoftext|>


<commit_before>var SideMenu = (function (document, hyperaudio) {

	function SideMenu (options) {
		this.options = {
			el: '#sidemenu',
			transcripts: '#panel-media',
			music: '#panel-bgm',
			stage: null // Points at a Stage instance
		};

		for ( var i in options ) {
			this.options[i] = options[i];
		}

		// Might rename the transcripts and music vars/options since rather ambiguous.

		this.el = typeof this.options.el == 'string' ? document.querySelector(this.options.el) : this.options.el;
		this.transcripts = typeof this.options.transcripts == 'string' ? document.querySelector(this.options.transcripts) : this.options.transcripts;
		this.music = typeof this.options.music == 'string' ? document.querySelector(this.options.music) : this.options.music;
		this.mediaCallback = this.options.callback;

		var handle = document.querySelector('#sidemenu-handle');
		handle._tap = new Tap({el: handle});
		handle.addEventListener('tap', this.toggleMenu.bind(this), false);

		this.updateStatus();

		// handle the tab bar
		var tabs = document.querySelectorAll('#sidemenu .tabbar li');
		for ( i = tabs.length-1; i >= 0; i-- ) {
			tabs[i]._tap = new Tap({el: tabs[i]});
			tabs[i].addEventListener('tap', this.selectPanel.bind(this), false);
		}

		this.initTranscripts();
		this.initMusic();
	}

	SideMenu.prototype.makeMenuFolder = function(parent, title) {
		var li = document.createElement('li'),
			div = document.createElement('div'),
			ul = document.createElement('ul');
		hyperaudio.addClass(li, 'folder');
		div.innerHTML = title;
		li.appendChild(div);
		li.appendChild(ul);
		parent.appendChild(li);
		return ul;
	};

	SideMenu.prototype.initTranscripts = function () {
		var self = this;

		hyperaudio.api.getUsername(function(success) {

			var username = '';
			var filter = false;

			if(success) {
				username = this.username;
				filter = !this.guest;
			}

			hyperaudio.api.getTranscripts(function(success) {
				if(success) {
					var yourTrans, otherTrans, userTrans, elem, trans;

					if(username) {
						yourTrans = self.makeMenuFolder(self.transcripts, 'Your Media');
					}
					otherTrans = self.makeMenuFolder(self.transcripts, 'Media');

					// Nesting not supported ATM.
					// userTrans = self.makeMenuFolder(self.transcripts, 'By User');
					// self.makeMenuFolder(userTrans, 'Scooby');
					// self.makeMenuFolder(userTrans, 'Mark');

					for(var i = 0, l = this.transcripts.length; i < l; i++) {
						trans = this.transcripts[i];
						if(trans.type === 'html') {
							elem = document.createElement('li');
							elem.setAttribute('data-id', trans._id);
							elem.innerHTML = trans.label;
							// self.transcripts.appendChild(elem);

							if(trans.owner === username) {
								yourTrans.appendChild(elem);
							} else {
								otherTrans.appendChild(elem);
							}
						}
					}

					self.transcripts._tap = new Tap({el: self.transcripts});
					self.transcripts.addEventListener('tap', self.selectMedia.bind(self), false);
				}
			});
		});
	};

	SideMenu.prototype.initMusic = function () {
		var self = this,
			stage = this.options.stage;

		function onDragStart (e) {
			hyperaudio.addClass(stage.target, 'dragdrop');

			hyperaudio.gaEvent({
				type: 'SIDEMENU',
				action: 'bgmstartdrag: Began dragging BGM effect'
			});
		}

		function onDrop (el) {
			hyperaudio.removeClass(stage.target, 'dragdrop');
			if ( !el ) {	// we dropped outside the stage
				return;
			}

			var title = el.innerHTML;
			hyperaudio.addClass(el, 'effect');
			el.setAttribute('data-effect', 'bgm');

			var id = this.handle.getAttribute('data-id'),
				mp3 = this.handle.getAttribute('data-mp3'),
				mp4 = this.handle.getAttribute('data-mp4'),
				ogg = this.handle.getAttribute('data-ogg');

			if(id) el.setAttribute('data-id', id);
			if(mp3) el.setAttribute('data-mp3', mp3);
			if(mp4) el.setAttribute('data-mp4', mp4);
			if(ogg) el.setAttribute('data-ogg', ogg);

			var html = '<form>
' + title + '
' +
				'Delay: 0s<input id="effect-delay" type="range" value="0" min="0" max="30" step="0.5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'Start At: 0s<input id="effect-start" type="range" value="0" min="0" max="30" step="0.5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'Duration: 60s<input id="effect-duration" type="range" value="60" min="0" max="120" step="0.5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'Volume: 80%<input id="effect-volume" type="range" value="80" min="10" max="100" step="5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'</form>';
			el.innerHTML = html;
			stage.dropped(el, '' + title + '');

			hyperaudio.gaEvent({
				type: 'SIDEMENU',
				action: 'bgmdrop: Dropped BGM effect on to stage'
			});
		}

		if(stage.target) {
			// add drag and drop to BGM
/*
			var items = document.querySelectorAll('#panel-bgm li');
			for (var i = items.length-1; i >= 0; i-- ) {
				if ( !this.isFolder(items[i]) ) {
					items[i]._dragInstance = new DragDrop({
						handle: items[i],
						dropArea: stage.target,
						draggableClass: 'draggableEffect',
						onDragStart: onDragStart,
						onDrop: onDrop
					});
				}
			}
			self.music._tap = new Tap({el: self.music});
			self.music.addEventListener('tap', self.toggleFolder.bind(self), false);
*/

			hyperaudio.api.getBGM(function(success) {
				if(success) {
					var elem, bgms;

					for(var i = 0, l = this.bgm.length; i < l; i++) {
						bgms = this.bgm[i];
						if(bgms.type === 'audio') {
							elem = document.createElement('li');
							elem.setAttribute('data-id', bgms._id);
							if(bgms.source.mp3) elem.setAttribute('data-mp3', bgms.source.mp3.url);
							if(bgms.source.mp4) elem.setAttribute('data-mp4', bgms.source.mp4.url);
							if(bgms.source.ogg) elem.setAttribute('data-ogg', bgms.source.ogg.url);
							elem.innerHTML = bgms.label;
							elem._dragInstance = new DragDrop({
								handle: elem,
								html: '' + bgms.label + '',
								dropArea: stage.target,
								draggableClass: 'draggableEffect',
								onDragStart: onDragStart,
								onDrop: onDrop
							});
							self.music.appendChild(elem);
						}
					}

					self.music._tap = new Tap({el: self.music});
					self.music.addEventListener('tap', self.toggleFolder.bind(self), false);
				}
			});
		}
	};

	SideMenu.prototype.updateStatus = function () {
		this.opened = hyperaudio.hasClass(this.el, 'opened');
	};

	SideMenu.prototype.toggleMenu = function () {
		if ( this.opened ) {
			this.close();
		} else {
			this.open();
		}
	};

	SideMenu.prototype.open = function () {
		if ( this.opened ) {
			return;
		}

		hyperaudio.addClass(this.el, 'opened');
		this.opened = true;

		hyperaudio.gaEvent({
			type: 'SIDEMENU',
			action: 'togglemenu: Opened'
		});
	};

	SideMenu.prototype.close = function () {
		if ( !this.opened ) {
			return;
		}

		hyperaudio.removeClass(this.el, 'opened');
		this.opened = false;

		hyperaudio.gaEvent({
			type: 'SIDEMENU',
			action: 'togglemenu: Closed'
		});
	};

	SideMenu.prototype.selectPanel = function (e) {
		var current = document.querySelector('#sidemenu .tabbar li.selected');
		var incoming = e.currentTarget;
		hyperaudio.removeClass(current, 'selected');
		hyperaudio.addClass(incoming, 'selected');

		var panelID = 'panel' + incoming.id.replace('sidemenu', '');
		current = document.querySelector('#sidemenu .panel.selected');
		hyperaudio.removeClass(current, 'selected');
		incoming = document.querySelector('#' + panelID);
		hyperaudio.addClass(incoming, 'selected');

		var name = e.currentTarget.querySelector('span').innerHTML;
		hyperaudio.gaEvent({
			type: 'SIDEMENU',
			action: 'selectpanel: Switched tab -> ' + name
		});
	};

	SideMenu.prototype.selectMedia = function (e) {
		e.stopPropagation();	// just in case [Not sure this does anything with a tap event.]

		var item = e.target;

		if(this.toggleFolder(e)) {
			return;
		}

		if ( !item.getAttribute('data-id') || !this.mediaCallback ) {
			return;
		}

		this.mediaCallback(item);
	};

	SideMenu.prototype.isFolder = function (target) {
		// Copes with clicks on Folder div text and the li

		if ( hyperaudio.hasClass(target.parentNode, 'folder') ) {
			target = target.parentNode;
		}

		if ( hyperaudio.hasClass(target, 'folder') ) {
			return target;
		}
		return false;
	};

	SideMenu.prototype.toggleFolder = function (e) {

		var folder = this.isFolder(e.target);
		if(folder) {
			hyperaudio.toggleClass(folder, 'open');

			var name = folder.querySelector('div').innerHTML;
			hyperaudio.gaEvent({
				type: 'SIDEMENU',
				action: 'togglefolder: ' + (hyperaudio.hasClass(folder, 'open') ? 'Opened' : 'Closed') + ' -> ' + name
			});

			return true;
		}
		return false;
	};

	return SideMenu;
})(document, hyperaudio);<commit_msg>Refactored SideMenu toggle GA. Now only when user initiated toggle.<commit_after>var SideMenu = (function (document, hyperaudio) {

	function SideMenu (options) {
		this.options = {
			el: '#sidemenu',
			transcripts: '#panel-media',
			music: '#panel-bgm',
			stage: null // Points at a Stage instance
		};

		for ( var i in options ) {
			this.options[i] = options[i];
		}

		// Might rename the transcripts and music vars/options since rather ambiguous.

		this.el = typeof this.options.el == 'string' ? document.querySelector(this.options.el) : this.options.el;
		this.transcripts = typeof this.options.transcripts == 'string' ? document.querySelector(this.options.transcripts) : this.options.transcripts;
		this.music = typeof this.options.music == 'string' ? document.querySelector(this.options.music) : this.options.music;
		this.mediaCallback = this.options.callback;

		var handle = document.querySelector('#sidemenu-handle');
		handle._tap = new Tap({el: handle});
		handle.addEventListener('tap', this.toggleMenu.bind(this), false);

		this.updateStatus();

		// handle the tab bar
		var tabs = document.querySelectorAll('#sidemenu .tabbar li');
		for ( i = tabs.length-1; i >= 0; i-- ) {
			tabs[i]._tap = new Tap({el: tabs[i]});
			tabs[i].addEventListener('tap', this.selectPanel.bind(this), false);
		}

		this.initTranscripts();
		this.initMusic();
	}

	SideMenu.prototype.makeMenuFolder = function(parent, title) {
		var li = document.createElement('li'),
			div = document.createElement('div'),
			ul = document.createElement('ul');
		hyperaudio.addClass(li, 'folder');
		div.innerHTML = title;
		li.appendChild(div);
		li.appendChild(ul);
		parent.appendChild(li);
		return ul;
	};

	SideMenu.prototype.initTranscripts = function () {
		var self = this;

		hyperaudio.api.getUsername(function(success) {

			var username = '';
			var filter = false;

			if(success) {
				username = this.username;
				filter = !this.guest;
			}

			hyperaudio.api.getTranscripts(function(success) {
				if(success) {
					var yourTrans, otherTrans, userTrans, elem, trans;

					if(username) {
						yourTrans = self.makeMenuFolder(self.transcripts, 'Your Media');
					}
					otherTrans = self.makeMenuFolder(self.transcripts, 'Media');

					// Nesting not supported ATM.
					// userTrans = self.makeMenuFolder(self.transcripts, 'By User');
					// self.makeMenuFolder(userTrans, 'Scooby');
					// self.makeMenuFolder(userTrans, 'Mark');

					for(var i = 0, l = this.transcripts.length; i < l; i++) {
						trans = this.transcripts[i];
						if(trans.type === 'html') {
							elem = document.createElement('li');
							elem.setAttribute('data-id', trans._id);
							elem.innerHTML = trans.label;
							// self.transcripts.appendChild(elem);

							if(trans.owner === username) {
								yourTrans.appendChild(elem);
							} else {
								otherTrans.appendChild(elem);
							}
						}
					}

					self.transcripts._tap = new Tap({el: self.transcripts});
					self.transcripts.addEventListener('tap', self.selectMedia.bind(self), false);
				}
			});
		});
	};

	SideMenu.prototype.initMusic = function () {
		var self = this,
			stage = this.options.stage;

		function onDragStart (e) {
			hyperaudio.addClass(stage.target, 'dragdrop');

			hyperaudio.gaEvent({
				type: 'SIDEMENU',
				action: 'bgmstartdrag: Began dragging BGM effect'
			});
		}

		function onDrop (el) {
			hyperaudio.removeClass(stage.target, 'dragdrop');
			if ( !el ) {	// we dropped outside the stage
				return;
			}

			var title = el.innerHTML;
			hyperaudio.addClass(el, 'effect');
			el.setAttribute('data-effect', 'bgm');

			var id = this.handle.getAttribute('data-id'),
				mp3 = this.handle.getAttribute('data-mp3'),
				mp4 = this.handle.getAttribute('data-mp4'),
				ogg = this.handle.getAttribute('data-ogg');

			if(id) el.setAttribute('data-id', id);
			if(mp3) el.setAttribute('data-mp3', mp3);
			if(mp4) el.setAttribute('data-mp4', mp4);
			if(ogg) el.setAttribute('data-ogg', ogg);

			var html = '<form>
' + title + '
' +
				'Delay: 0s<input id="effect-delay" type="range" value="0" min="0" max="30" step="0.5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'Start At: 0s<input id="effect-start" type="range" value="0" min="0" max="30" step="0.5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'Duration: 60s<input id="effect-duration" type="range" value="60" min="0" max="120" step="0.5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'Volume: 80%<input id="effect-volume" type="range" value="80" min="10" max="100" step="5" onchange="this.setAttribute(\'value\', this.value); this.previousSibling.querySelector(\'span\').innerHTML = this.value">' +
				'</form>';
			el.innerHTML = html;
			stage.dropped(el, '' + title + '');

			hyperaudio.gaEvent({
				type: 'SIDEMENU',
				action: 'bgmdrop: Dropped BGM effect on to stage'
			});
		}

		if(stage.target) {
			// add drag and drop to BGM
/*
			var items = document.querySelectorAll('#panel-bgm li');
			for (var i = items.length-1; i >= 0; i-- ) {
				if ( !this.isFolder(items[i]) ) {
					items[i]._dragInstance = new DragDrop({
						handle: items[i],
						dropArea: stage.target,
						draggableClass: 'draggableEffect',
						onDragStart: onDragStart,
						onDrop: onDrop
					});
				}
			}
			self.music._tap = new Tap({el: self.music});
			self.music.addEventListener('tap', self.toggleFolder.bind(self), false);
*/

			hyperaudio.api.getBGM(function(success) {
				if(success) {
					var elem, bgms;

					for(var i = 0, l = this.bgm.length; i < l; i++) {
						bgms = this.bgm[i];
						if(bgms.type === 'audio') {
							elem = document.createElement('li');
							elem.setAttribute('data-id', bgms._id);
							if(bgms.source.mp3) elem.setAttribute('data-mp3', bgms.source.mp3.url);
							if(bgms.source.mp4) elem.setAttribute('data-mp4', bgms.source.mp4.url);
							if(bgms.source.ogg) elem.setAttribute('data-ogg', bgms.source.ogg.url);
							elem.innerHTML = bgms.label;
							elem._dragInstance = new DragDrop({
								handle: elem,
								html: '' + bgms.label + '',
								dropArea: stage.target,
								draggableClass: 'draggableEffect',
								onDragStart: onDragStart,
								onDrop: onDrop
							});
							self.music.appendChild(elem);
						}
					}

					self.music._tap = new Tap({el: self.music});
					self.music.addEventListener('tap', self.toggleFolder.bind(self), false);
				}
			});
		}
	};

	SideMenu.prototype.updateStatus = function () {
		this.opened = hyperaudio.hasClass(this.el, 'opened');
	};

	SideMenu.prototype.toggleMenu = function () {
		var state;

		if ( this.opened ) {
			this.close();
			state = 'Closed';
		} else {
			this.open();
			state = 'Opened';
		}

		hyperaudio.gaEvent({
			type: 'SIDEMENU',
			action: 'togglemenu: ' + state
		});
	};

	SideMenu.prototype.open = function () {
		if ( this.opened ) {
			return;
		}

		hyperaudio.addClass(this.el, 'opened');
		this.opened = true;
	};

	SideMenu.prototype.close = function () {
		if ( !this.opened ) {
			return;
		}

		hyperaudio.removeClass(this.el, 'opened');
		this.opened = false;
	};

	SideMenu.prototype.selectPanel = function (e) {
		var current = document.querySelector('#sidemenu .tabbar li.selected');
		var incoming = e.currentTarget;
		hyperaudio.removeClass(current, 'selected');
		hyperaudio.addClass(incoming, 'selected');

		var panelID = 'panel' + incoming.id.replace('sidemenu', '');
		current = document.querySelector('#sidemenu .panel.selected');
		hyperaudio.removeClass(current, 'selected');
		incoming = document.querySelector('#' + panelID);
		hyperaudio.addClass(incoming, 'selected');

		var name = e.currentTarget.querySelector('span').innerHTML;
		hyperaudio.gaEvent({
			type: 'SIDEMENU',
			action: 'selectpanel: Switched tab -> ' + name
		});
	};

	SideMenu.prototype.selectMedia = function (e) {
		e.stopPropagation();	// just in case [Not sure this does anything with a tap event.]

		var item = e.target;

		if(this.toggleFolder(e)) {
			return;
		}

		if ( !item.getAttribute('data-id') || !this.mediaCallback ) {
			return;
		}

		this.mediaCallback(item);
	};

	SideMenu.prototype.isFolder = function (target) {
		// Copes with clicks on Folder div text and the li

		if ( hyperaudio.hasClass(target.parentNode, 'folder') ) {
			target = target.parentNode;
		}

		if ( hyperaudio.hasClass(target, 'folder') ) {
			return target;
		}
		return false;
	};

	SideMenu.prototype.toggleFolder = function (e) {

		var folder = this.isFolder(e.target);
		if(folder) {
			hyperaudio.toggleClass(folder, 'open');

			var name = folder.querySelector('div').innerHTML;
			hyperaudio.gaEvent({
				type: 'SIDEMENU',
				action: 'togglefolder: ' + (hyperaudio.hasClass(folder, 'open') ? 'Opened' : 'Closed') + ' -> ' + name
			});

			return true;
		}
		return false;
	};

	return SideMenu;
})(document, hyperaudio);<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at https://raw.github.com/mozilla/butter/master/LICENSE */

"use strict";

define( [ "util/uri" ],
  function( URI ) {

  var REGEX_MAP = {
        YouTube: /(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)youtu/,
        Vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
        SoundCloud: /(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)(soundcloud)/,
        // supports #t=<start>,<duration>
        // where start or duration can be: X, X.X or XX:XX
        "null": /^\s*#t=(?:\d*(?:(?:\.|\:)?\d+)?),?(\d+(?:(?:\.|\:)\d+)?)\s*$/
      },
      YOUTUBE_EMBED_DISABLED = "Embedding of this YouTube video is disabled",
      SOUNDCLOUD_EMBED_DISABLED = "Embedding of this SoundCloud audio source is disabled";

  return {
    checkUrl: function( url ) {
      for ( var type in REGEX_MAP ) {
        if ( REGEX_MAP.hasOwnProperty( type ) ) {
          if ( REGEX_MAP[ type ].test( url ) ) {
            return type;
          }
        }
      }
      return "HTML5";
    },
    getMetaData: function( baseUrl, successCallback, errorCallback ) {
      var id,
          parsedUri,
          splitUriDirectory,
          xhrURL,
          type = this.checkUrl( baseUrl ),
          videoElem;

      successCallback = successCallback || function(){};
      errorCallback = errorCallback || function(){};

      if ( type === "YouTube" ) {
        parsedUri = URI.parse( baseUrl );
        // youtube id can either be a query under v, example:
        // http://www.youtube.com/watch?v=p_7Qi3mprKQ
        // Or at the end of the url like this:
        // http://youtu.be/p_7Qi3mprKQ
        id = parsedUri.queryKey.v || parsedUri.directory.replace( "/", "" );
        if ( !id ) {
          return;
        }

        xhrURL = "https://gdata.youtube.com/feeds/api/videos/" + id + "?v=2&alt=jsonc&callback=?";
        Popcorn.getJSONP( xhrURL, function( resp ) {
          var respData = resp.data,
              from = parsedUri.queryKey.t,
              popcorn,
              div = document.createElement( "div" ),
              source;

          div.style.height = "400px";
          div.style.width = "400px";
          div.style.left = "-400px";
          div.style.position = "absolute";

          document.body.appendChild( div );

          if ( !respData ) {
            return;
          }

          if ( respData.accessControl.embed === "denied" ) {
            errorCallback( YOUTUBE_EMBED_DISABLED );
            return;
          }

          function readyEvent() {
            popcorn.off( "loadedmetadata", readyEvent );
            document.body.removeChild( div );
            popcorn.destroy();

            successCallback({
              source: source,
              title: respData.title,
              type: type,
              thumbnail: respData.thumbnail.hqDefault,
              author: respData.uploader,
              duration: popcorn.duration(),
              from: from
            });
          }

          if ( from ) {
            from = from.replace( /(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/, function( all, hours, minutes, seconds ) {
              // Make sure we have real zeros
              hours = hours | 0; // bit-wise OR
              minutes = minutes | 0; // bit-wise OR
              seconds = seconds | 0; // bit-wise OR
              return ( +seconds + ( ( ( hours * 60 ) + minutes ) * 60 ) );
            });
          }

          source = "http://www.youtube.com/watch?v=" + id;
          popcorn = Popcorn.smart( div, source );
          if ( popcorn.media.readyState >= 1 ) {
            readyEvent();
          } else {
            popcorn.on( "loadedmetadata", readyEvent );
          }
        });
      } else if ( type === "SoundCloud" ) {
        parsedUri = URI.parse( baseUrl );
        splitUriDirectory = parsedUri.directory.split( "/" );
        id = splitUriDirectory[ splitUriDirectory.length - 1 ];
        xhrURL = "https://api.soundcloud.com/tracks/" + id + ".json?callback=?&client_id=PRaNFlda6Bhf5utPjUsptg";
        Popcorn.getJSONP( xhrURL, function( respData ) {
          if ( !respData ) {
            return;
          }

          if ( respData.sharing === "private" || respData.embeddable_by === "none" ) {
            errorCallback( SOUNDCLOUD_EMBED_DISABLED );
            return;
          }
          successCallback({
            source: baseUrl,
            type: type,
            thumbnail: respData.artwork_url || "../../resources/icons/soundcloud-small.png",
            duration: respData.duration / 1000,
            title: respData.title,
            hidden: true
          });
        });
      } else if ( type === "Vimeo" ) {
        parsedUri = URI.parse( baseUrl );
        splitUriDirectory = parsedUri.directory.split( "/" );
        id = splitUriDirectory[ splitUriDirectory.length - 1 ];
        xhrURL = "https://vimeo.com/api/v2/video/" + id + ".json?callback=?";
        Popcorn.getJSONP( xhrURL, function( respData ) {
          respData = respData && respData[ 0 ];
          if ( !respData ) {
            return;
          }
          successCallback({
            source: baseUrl,
            type: type,
            thumbnail: respData.thumbnail_small,
            duration: respData.duration,
            title: respData.title
          });
        });
      } else if ( type === "null" ) {
        successCallback({
          source: baseUrl,
          type: type,
          title: baseUrl,
          duration: REGEX_MAP[ "null" ].exec( baseUrl )[ 1 ]
        });
      } else if ( type === "HTML5" ) {
        videoElem = document.createElement( "video" );
        videoElem.addEventListener( "loadedmetadata", function() {
          successCallback ({
            source: baseUrl,
            type: type,
            title: baseUrl.substring( baseUrl.lastIndexOf( "/" ) + 1 ),
            thumbnail: videoElem,
            duration: videoElem.duration
          });
        }, false );
        videoElem.src = URI.makeUnique( baseUrl ).toString();
      }
    }
  };
});
<commit_msg>[#859295] Convert duration data from a null regex to an int, and not a string.<commit_after>/* This Source Code Form is subject to the terms of the MIT license
 * If a copy of the MIT license was not distributed with this file, you can
 * obtain one at https://raw.github.com/mozilla/butter/master/LICENSE */

"use strict";

define( [ "util/uri" ],
  function( URI ) {

  var REGEX_MAP = {
        YouTube: /(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)youtu/,
        Vimeo: /https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/,
        SoundCloud: /(?:https?:\/\/www\.|https?:\/\/|www\.|\.|^)(soundcloud)/,
        // supports #t=<start>,<duration>
        // where start or duration can be: X, X.X or XX:XX
        "null": /^\s*#t=(?:\d*(?:(?:\.|\:)?\d+)?),?(\d+(?:(?:\.|\:)\d+)?)\s*$/
      },
      YOUTUBE_EMBED_DISABLED = "Embedding of this YouTube video is disabled",
      SOUNDCLOUD_EMBED_DISABLED = "Embedding of this SoundCloud audio source is disabled";

  return {
    checkUrl: function( url ) {
      for ( var type in REGEX_MAP ) {
        if ( REGEX_MAP.hasOwnProperty( type ) ) {
          if ( REGEX_MAP[ type ].test( url ) ) {
            return type;
          }
        }
      }
      return "HTML5";
    },
    getMetaData: function( baseUrl, successCallback, errorCallback ) {
      var id,
          parsedUri,
          splitUriDirectory,
          xhrURL,
          type = this.checkUrl( baseUrl ),
          videoElem;

      successCallback = successCallback || function(){};
      errorCallback = errorCallback || function(){};

      if ( type === "YouTube" ) {
        parsedUri = URI.parse( baseUrl );
        // youtube id can either be a query under v, example:
        // http://www.youtube.com/watch?v=p_7Qi3mprKQ
        // Or at the end of the url like this:
        // http://youtu.be/p_7Qi3mprKQ
        id = parsedUri.queryKey.v || parsedUri.directory.replace( "/", "" );
        if ( !id ) {
          return;
        }

        xhrURL = "https://gdata.youtube.com/feeds/api/videos/" + id + "?v=2&alt=jsonc&callback=?";
        Popcorn.getJSONP( xhrURL, function( resp ) {
          var respData = resp.data,
              from = parsedUri.queryKey.t,
              popcorn,
              div = document.createElement( "div" ),
              source;

          div.style.height = "400px";
          div.style.width = "400px";
          div.style.left = "-400px";
          div.style.position = "absolute";

          document.body.appendChild( div );

          if ( !respData ) {
            return;
          }

          if ( respData.accessControl.embed === "denied" ) {
            errorCallback( YOUTUBE_EMBED_DISABLED );
            return;
          }

          function readyEvent() {
            popcorn.off( "loadedmetadata", readyEvent );
            document.body.removeChild( div );
            popcorn.destroy();

            successCallback({
              source: source,
              title: respData.title,
              type: type,
              thumbnail: respData.thumbnail.hqDefault,
              author: respData.uploader,
              duration: popcorn.duration(),
              from: from
            });
          }

          if ( from ) {
            from = from.replace( /(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/, function( all, hours, minutes, seconds ) {
              // Make sure we have real zeros
              hours = hours | 0; // bit-wise OR
              minutes = minutes | 0; // bit-wise OR
              seconds = seconds | 0; // bit-wise OR
              return ( +seconds + ( ( ( hours * 60 ) + minutes ) * 60 ) );
            });
          }

          source = "http://www.youtube.com/watch?v=" + id;
          popcorn = Popcorn.smart( div, source );
          if ( popcorn.media.readyState >= 1 ) {
            readyEvent();
          } else {
            popcorn.on( "loadedmetadata", readyEvent );
          }
        });
      } else if ( type === "SoundCloud" ) {
        parsedUri = URI.parse( baseUrl );
        splitUriDirectory = parsedUri.directory.split( "/" );
        id = splitUriDirectory[ splitUriDirectory.length - 1 ];
        xhrURL = "https://api.soundcloud.com/tracks/" + id + ".json?callback=?&client_id=PRaNFlda6Bhf5utPjUsptg";
        Popcorn.getJSONP( xhrURL, function( respData ) {
          if ( !respData ) {
            return;
          }

          if ( respData.sharing === "private" || respData.embeddable_by === "none" ) {
            errorCallback( SOUNDCLOUD_EMBED_DISABLED );
            return;
          }
          successCallback({
            source: baseUrl,
            type: type,
            thumbnail: respData.artwork_url || "../../resources/icons/soundcloud-small.png",
            duration: respData.duration / 1000,
            title: respData.title,
            hidden: true
          });
        });
      } else if ( type === "Vimeo" ) {
        parsedUri = URI.parse( baseUrl );
        splitUriDirectory = parsedUri.directory.split( "/" );
        id = splitUriDirectory[ splitUriDirectory.length - 1 ];
        xhrURL = "https://vimeo.com/api/v2/video/" + id + ".json?callback=?";
        Popcorn.getJSONP( xhrURL, function( respData ) {
          respData = respData && respData[ 0 ];
          if ( !respData ) {
            return;
          }
          successCallback({
            source: baseUrl,
            type: type,
            thumbnail: respData.thumbnail_small,
            duration: respData.duration,
            title: respData.title
          });
        });
      } else if ( type === "null" ) {
        successCallback({
          source: baseUrl,
          type: type,
          title: baseUrl,
          duration: +REGEX_MAP[ "null" ].exec( baseUrl )[ 1 ]
        });
      } else if ( type === "HTML5" ) {
        videoElem = document.createElement( "video" );
        videoElem.addEventListener( "loadedmetadata", function() {
          successCallback ({
            source: baseUrl,
            type: type,
            title: baseUrl.substring( baseUrl.lastIndexOf( "/" ) + 1 ),
            thumbnail: videoElem,
            duration: videoElem.duration
          });
        }, false );
        videoElem.src = URI.makeUnique( baseUrl ).toString();
      }
    }
  };
});
<|endoftext|>