<commit_before>/*
Please do not edit this file directly (unless you intend to fork).
  It's been open-sourced here:
    http://github.com/dgouldin/django-hashsignal

Requires
  * jQuery hashchange event - v1.2 - 2/11/2010
  * http://benalman.com/projects/jquery-hashchange-plugin/
*/

(function(window, $, undefined){
    var activeOpts, defaultOpts;

    function log() {
        if (!(activeOpts && activeOpts.debug)) {
            return;
        }
        var args = [new Date(), "hashsignal"].concat(Array.prototype.slice.apply(arguments));
        if (window.console) {
            window.console.log(args);
        } else {
         return; // TODO: replacement for console.log
        }
    }

    defaultOpts = {
        excludeSelector: '.no-ajax',
        beforeUpdate: function() { log('beforeUpdate'); },
        afterUpdate: function() { log('afterUpdate'); },
        errorUpdate: function() { log('errorUpdate'); },
        debug: false
    };

    var methods, ALWAYS_RELOAD = '__all__', HASH_REPLACEMENT = ':',
        previousLocation = null, upcomingLocation = null,
        transitions = {}, liveForms, document = window.document,
        location = window.location, history = window.history;

    function blockAction(actionName, blockName) {
        /* DRYs up _unloadBlock and _loadBlock below */
        var transition = transitions[blockName];
        if (!transition) {
            return;
        }
        for (var name in transition) {
            if (transition.hasOwnProperty(name)) {
                transition[name][actionName]();
                /* Clean up old transitions which are no longer needed. */
                if( actionName === 'unload' && !transition[name].o.runOnce && blockName != ALWAYS_RELOAD) {
                    delete transition[name];
                }
            }
        }
    }

    function getOldBlocks(doc) {
        function walker(root, handle) {
          handle(root);
          for (var i=0, c=root.childNodes.length; i < c; i++) {
            walker(root.childNodes[i], handle);
          }
        }
        var blockRe = /^ (end)?block ([^ ]*) ([0-9a-f]{32} )?$/;
        function blockWalker(root, handle) { //handle(name, isStart, node)
          walker(root, function(node) {
            if (node.nodeType === 8) { // comment node
              var match = blockRe.exec(node.nodeValue);
              if (match) {
                handle(match[2], match[3], !match[1], node);
              }
            }
          });
        }

        var blocks = {};
        doc = doc || document;
        blockWalker(doc, function(name, signature, isStart, node) {
            if (blocks[name] === undefined) {
                blocks[name] = {
                    nodes: [null, null],
                    signature: signature
                };
            }
            blocks[name].nodes[isStart ? 0 : 1] = node;
        });
        return blocks;
    }

    function getNewBlocks(html) {
        var blocker = /<!-- (end)?block ([^ ]*) ([0123456789abcdef]{32} )?-->/gi;
        var starts = []; //stack of {name:a, signature:x, start:y};
        var closing;
        var blocks = {}; //name: {signature:x, html:z}

        function last() {
          return starts[starts.length-1];
        }

        html.replace(blocker, function(matched, ending, blockName, signatureMaybe, offset, fullString) {
          if (ending && starts.length === 0) {
            throw "Unexpected block nesting on match: " + matched;
          }
          if (!ending && !signatureMaybe) {
            throw "Expected signature on start of block";
          }

          if (ending) {
            closing = last();
            starts.length = starts.length-1;
            blocks[closing.name] = {
              html: fullString.slice(closing.start, offset),
              signature: closing.signature
            };
          } else {
            starts.push({
              name: blockName,
              start: offset + matched.length,
              signature: signatureMaybe
            });
          }
        });
        if (0 !== starts.length) {
          throw "Unclosed block: " + last().name;
        }
        return blocks;
    }

    function replaceBlocks(html) {
        log('replaceBlocks');

        function siblingsBetween(start, end) {
            var siblings = [];
            var current = start;
            while (current !== end) {
                if (current !== start) {
                    siblings.push(current);
                }
                current = current.nextSibling;
            }
            return siblings;
        }

        var oldBlocks = getOldBlocks();
        var newBlocks = getNewBlocks(html);

        methods._unloadBlock(ALWAYS_RELOAD);

        for (var blockName in newBlocks) {
            if (blockName in oldBlocks) {
                var oldBlock = oldBlocks[blockName];
                var newBlock = newBlocks[blockName];
                if (oldBlock.signature === newBlock.signature) {
                    log('Not replacing block, signatures match.', blockName, oldBlock.signature);
                    continue; // The block is the same, no need to swap out the content.
                }

                methods._unloadBlock(blockName);
                $(siblingsBetween(oldBlock.nodes[0], oldBlock.nodes[1])).remove();

                log('Replacing block', blockName, newBlock.html);
                // methods._loadBlock must be called from inside newBlock.html so that mutations block as
                //   would normally happen with inline scripts.
                $(oldBlock.nodes[0]).after(newBlock.html +
                '<script type="text/javascript">' +
                '  jQuery.hashsignal._loadBlock("' + blockName.replace('"', '\\"') + '");' +
                '</scr' + 'ipt>');

                // update block signature
                $(oldBlock.nodes[0]).replaceWith("<!-- block " + blockName + " " + newBlock.signature + "-->");
            } else {
                log('WARNING: unmatched block', blockName);
            }
        }
        methods._loadBlock(ALWAYS_RELOAD);

        // update title
        var titleRe = /<title>(.*)<\/title>/;
        var titleMatch = titleRe.exec(html);
        if (titleMatch) {
            document.title = titleMatch[1];
        }
    }

    function updatePage(url, type, data, opts) {
        type = type || 'GET';
        data = data || '';
        var callbacks = $.extend({
            beforeUpdate: function() { return; },
            afterUpdate: function() { return; },
            errorUpdate: function() { return; }
        }, opts);

        var urlParts = url.split(HASH_REPLACEMENT);
        url = urlParts[0];

        var subhash = urlParts[1] || '';
        if (url == previousLocation && subhash && type.toLowerCase() === 'get' && !data) {
            // Only hash, not page, needs to be updated
            $(window).trigger('hashsignal.hashchange', [subhash]);
            return;
        }

        //deal with multiple pending requests by always having the
        // last-requested win, rather than last-responded.
        upcomingLocation = url;
        function makeSuccessor(url) {
          return function(data, status, xhr) {
              if (url != upcomingLocation) {
                log("Success for ", url, " fired but last-requested was ", upcomingLocation, " - aborting");
                return;
              }

              log('updatePage onSuccess');
              replaceBlocks(data);

              if (subhash) {
                  $(window).trigger('hashsignal.hashchange', [subhash]);
              }
              previousLocation = url;
              callbacks.afterUpdate();
          };
        }

        callbacks.beforeUpdate();
        $.ajax({
            data: data,
            error: function(xhr, status, error) {
                log('updatePage error');
                callbacks.errorUpdate(xhr, status, error);
                history.back();
            },
            success: makeSuccessor(url),
            type: type,
            url: url
        });
    }

    function Transition(opts) {
        this.hasRun = false;
        this.o = $.extend({
            load: function(){ return; },
            unload: function() { return; },
            runOnce: false
        }, opts);

        this.events = [];
        this.timeouts = [];
        this.intervals = [];
        this.scripts = {};

        // shims
        this.bind = function(obj, eventType, eventData, handler) {
            this.events.push([obj, eventType, handler]);
            return $(obj).bind(eventType, eventData, handler);
        };
        this.setTimeout = function(callback, timeout) {
            this.timeouts.push(window.setTimeout(callback, timeout));
        };
        this.setInterval = function(callback, timeout) {
            this.intervals.push(window.setInterval(callback, timeout));
        };
        this.clearTimeout = window.clearTimeout;
        this.clearInterval = window.clearInterval;

        this.addScript = function(src, loadOnce) {
            loadOnce = loadOnce === undefined ? true : loadOnce;
            if (!(loadOnce && this.scripts[src])) {
                var script = document.createElement('script');
                script.type = 'text/javascript';
                script.src = src;
                script = $(script);

                var that = this;
                script.load(function(){
                    that.scripts[src] = true;
                    $(this).unbind('load');
                });

                $('body').append(script);
            }
        };

        this.load = function() {
            if (!(this.hasRun && this.runOnce)) {
                this.o.load(this);
            }
            this.hasRun = true;
        };
        this.unload = function() {
            if (!this.runOnce) {
                var i;
                for (i = 0; i < this.events.length; i++) {
                    var e = this.events[i];
                    $(e[0]).unbind(e[1], e[2]);
                }
                for (i = 0; i < this.timeouts.length; i++) {
                    window.clearTimeout(this.timeouts[i]);
                }
                for (i = 0; i < this.intervals.length; i++) {
                    window.clearInterval(this.intervals[i]);
                }
                this.o.unload(this);
            }
        };
    }

    methods = {
        init: function(explicitOpts) {
            activeOpts = $.extend(defaultOpts, explicitOpts);

            $(window).bind('hashchange', function(h){
                log('hashchange');
                updatePage(location.hash.substr(1), 'GET', '', activeOpts);
            });

            if (location.hash && location.hash !== '#') {
                log('existing hash', location.hash);
                updatePage(location.hash.substr(1), 'GET', '', activeOpts);
            }
            $('a:not(' + activeOpts.excludeSelector + ')').live('click', function(){
                var href = $(this).attr('href').replace('#', HASH_REPLACEMENT);
                if (href.indexOf(HASH_REPLACEMENT) === 0) {
                    // link is relative to the current page
                    href = location.hash.substr(1).split(HASH_REPLACEMENT)[0] + href;
                }
                location.hash = '#' + href;
                return false;
            });
            liveForms = $('form:not(' + activeOpts.excludeSelector + ')');
            liveForms.live('submit', function(event){
                var url = $(this).attr('action');
                var type = $(this).attr('method');
                var data = $(this).serialize();
                var submitter = this.submitter;
                if (submitter) {
                    data += (data.length === 0 ? "?" : "&") + (
                        encodeURIComponent($(submitter).attr("name"))
                        + "=" + encodeURIComponent($(submitter).attr("value"))
                    );
                }
                log("form submission:", data);
                if (url === '.') {
                    url = location.hash.substr(1);
                }
                if (type.toLowerCase() === 'get') {
                    url = url.substring(0, url.indexOf('?')) || url;
                    url += '?' + data;
                    location.hash = '#' + url;
                } else {
                    // TODO: how does a post affect the hash fragment?
                    activeOpts.beforeUpdate();
                    updatePage(url, type, data, activeOpts);
                }
                return false;
            });
            //make sure the submitting button is included in the form data.
            liveForms.find('input[type=submit],button[type=submit]').live('click', function(event) {
              $(this).closest("form").get(0).submitter = this;
            });
            return this;
        },
        hashchange: function(callback) { // callback = function(e, hash) { ... }
            $(window).bind('hashsignal.hashchange', callback);
            return this;
        },
        hash: function(subhash) {
            var ret;
            if (undefined === subhash) {
                /* window.location.hash is a bit fiddly - if hash is empty, it returns "",
                  but if it is non-empty, it returns "#hash".

                  And when assigning, "foo" and "#foo" are treated as equivalent.
                */
                ret = location.hash.split(HASH_REPLACEMENT)[1];
                return ret ? ("#" + ret) : "";
            }
            if (0 === subhash.indexOf("#")) {
                subhash = subhash.substr(1);
            }
            var parts = location.hash.substr(1).split(HASH_REPLACEMENT);
            if (0 === subhash.length) {
                location.hash = parts[0];
            } else {
                parts[1] = subhash;
                location.hash = parts.join(HASH_REPLACEMENT);
            }
            return subhash;
        },
        registerTransition: function(name, blockNames, opts) {
            log('hashsignal.registerTransition', name, blockNames);
            var transition = new Transition(opts);
            if (!!opts.alwaysReload) {
                blockNames = [ALWAYS_RELOAD];
            }
            for (var i = 0; i < blockNames.length; i++) {
                var blockName = blockNames[i];
                if (transitions[blockName] === undefined) {
                    transitions[blockName] = {};
                }
                if (transitions[blockName][name] === undefined) {
                    transitions[blockName][name] = transition;
                }
            }
            return this;
        },
        _unloadBlock: function(blockName) {
            log('hashsignal.unloadBlock', blockName);
            blockAction('unload', blockName);
        },
        _loadBlock: function(blockName) {
            log('hashsignal.loadBlock', blockName);
            blockAction('load', blockName);
        }
    };
    $.hashsignal = methods;
})(window, jQuery);
<commit_msg>Add opts.disabled, which can be used to flag init as a no-op (allowing pages to opt-out simply).<commit_after>/*
Please do not edit this file directly (unless you intend to fork).
  It's been open-sourced here:
    http://github.com/dgouldin/django-hashsignal

Requires
  * jQuery hashchange event - v1.2 - 2/11/2010
  * http://benalman.com/projects/jquery-hashchange-plugin/
*/

(function(window, $, undefined){
    var activeOpts, defaultOpts;

    function log() {
        if (!(activeOpts && activeOpts.debug)) {
            return;
        }
        var args = [new Date(), "hashsignal"].concat(Array.prototype.slice.apply(arguments));
        if (window.console) {
            window.console.log(args);
        } else {
         return; // TODO: replacement for console.log
        }
    }

    defaultOpts = {
        excludeSelector: '.no-ajax',
        beforeUpdate: function() { log('beforeUpdate'); },
        afterUpdate: function() { log('afterUpdate'); },
        errorUpdate: function() { log('errorUpdate'); },
        debug: false,
        disabled: false
    };

    var methods, ALWAYS_RELOAD = '__all__', HASH_REPLACEMENT = ':',
        previousLocation = null, upcomingLocation = null,
        transitions = {}, liveForms, document = window.document,
        location = window.location, history = window.history;

    function blockAction(actionName, blockName) {
        /* DRYs up _unloadBlock and _loadBlock below */
        var transition = transitions[blockName];
        if (!transition) {
            return;
        }
        for (var name in transition) {
            if (transition.hasOwnProperty(name)) {
                transition[name][actionName]();
                /* Clean up old transitions which are no longer needed. */
                if( actionName === 'unload' && !transition[name].o.runOnce && blockName != ALWAYS_RELOAD) {
                    delete transition[name];
                }
            }
        }
    }

    function getOldBlocks(doc) {
        function walker(root, handle) {
          handle(root);
          for (var i=0, c=root.childNodes.length; i < c; i++) {
            walker(root.childNodes[i], handle);
          }
        }
        var blockRe = /^ (end)?block ([^ ]*) ([0-9a-f]{32} )?$/;
        function blockWalker(root, handle) { //handle(name, isStart, node)
          walker(root, function(node) {
            if (node.nodeType === 8) { // comment node
              var match = blockRe.exec(node.nodeValue);
              if (match) {
                handle(match[2], match[3], !match[1], node);
              }
            }
          });
        }

        var blocks = {};
        doc = doc || document;
        blockWalker(doc, function(name, signature, isStart, node) {
            if (blocks[name] === undefined) {
                blocks[name] = {
                    nodes: [null, null],
                    signature: signature
                };
            }
            blocks[name].nodes[isStart ? 0 : 1] = node;
        });
        return blocks;
    }

    function getNewBlocks(html) {
        var blocker = /<!-- (end)?block ([^ ]*) ([0123456789abcdef]{32} )?-->/gi;
        var starts = []; //stack of {name:a, signature:x, start:y};
        var closing;
        var blocks = {}; //name: {signature:x, html:z}

        function last() {
          return starts[starts.length-1];
        }

        html.replace(blocker, function(matched, ending, blockName, signatureMaybe, offset, fullString) {
          if (ending && starts.length === 0) {
            throw "Unexpected block nesting on match: " + matched;
          }
          if (!ending && !signatureMaybe) {
            throw "Expected signature on start of block";
          }

          if (ending) {
            closing = last();
            starts.length = starts.length-1;
            blocks[closing.name] = {
              html: fullString.slice(closing.start, offset),
              signature: closing.signature
            };
          } else {
            starts.push({
              name: blockName,
              start: offset + matched.length,
              signature: signatureMaybe
            });
          }
        });
        if (0 !== starts.length) {
          throw "Unclosed block: " + last().name;
        }
        return blocks;
    }

    function replaceBlocks(html) {
        log('replaceBlocks');

        function siblingsBetween(start, end) {
            var siblings = [];
            var current = start;
            while (current !== end) {
                if (current !== start) {
                    siblings.push(current);
                }
                current = current.nextSibling;
            }
            return siblings;
        }

        var oldBlocks = getOldBlocks();
        var newBlocks = getNewBlocks(html);

        methods._unloadBlock(ALWAYS_RELOAD);

        for (var blockName in newBlocks) {
            if (blockName in oldBlocks) {
                var oldBlock = oldBlocks[blockName];
                var newBlock = newBlocks[blockName];
                if (oldBlock.signature === newBlock.signature) {
                    log('Not replacing block, signatures match.', blockName, oldBlock.signature);
                    continue; // The block is the same, no need to swap out the content.
                }

                methods._unloadBlock(blockName);
                $(siblingsBetween(oldBlock.nodes[0], oldBlock.nodes[1])).remove();

                log('Replacing block', blockName, newBlock.html);
                // methods._loadBlock must be called from inside newBlock.html so that mutations block as
                //   would normally happen with inline scripts.
                $(oldBlock.nodes[0]).after(newBlock.html +
                '<script type="text/javascript">' +
                '  jQuery.hashsignal._loadBlock("' + blockName.replace('"', '\\"') + '");' +
                '</scr' + 'ipt>');

                // update block signature
                $(oldBlock.nodes[0]).replaceWith("<!-- block " + blockName + " " + newBlock.signature + "-->");
            } else {
                log('WARNING: unmatched block', blockName);
            }
        }
        methods._loadBlock(ALWAYS_RELOAD);

        // update title
        var titleRe = /<title>(.*)<\/title>/;
        var titleMatch = titleRe.exec(html);
        if (titleMatch) {
            document.title = titleMatch[1];
        }
    }

    function updatePage(url, type, data, opts) {
        type = type || 'GET';
        data = data || '';
        var callbacks = $.extend({
            beforeUpdate: function() { return; },
            afterUpdate: function() { return; },
            errorUpdate: function() { return; }
        }, opts);

        var urlParts = url.split(HASH_REPLACEMENT);
        url = urlParts[0];

        var subhash = urlParts[1] || '';
        if (url == previousLocation && subhash && type.toLowerCase() === 'get' && !data) {
            // Only hash, not page, needs to be updated
            $(window).trigger('hashsignal.hashchange', [subhash]);
            return;
        }

        //deal with multiple pending requests by always having the
        // last-requested win, rather than last-responded.
        upcomingLocation = url;
        function makeSuccessor(url) {
          return function(data, status, xhr) {
              if (url != upcomingLocation) {
                log("Success for ", url, " fired but last-requested was ", upcomingLocation, " - aborting");
                return;
              }

              log('updatePage onSuccess');
              replaceBlocks(data);

              if (subhash) {
                  $(window).trigger('hashsignal.hashchange', [subhash]);
              }
              previousLocation = url;
              callbacks.afterUpdate();
          };
        }

        callbacks.beforeUpdate();
        $.ajax({
            data: data,
            error: function(xhr, status, error) {
                log('updatePage error');
                callbacks.errorUpdate(xhr, status, error);
                history.back();
            },
            success: makeSuccessor(url),
            type: type,
            url: url
        });
    }

    function Transition(opts) {
        this.hasRun = false;
        this.o = $.extend({
            load: function(){ return; },
            unload: function() { return; },
            runOnce: false
        }, opts);

        this.events = [];
        this.timeouts = [];
        this.intervals = [];
        this.scripts = {};

        // shims
        this.bind = function(obj, eventType, eventData, handler) {
            this.events.push([obj, eventType, handler]);
            return $(obj).bind(eventType, eventData, handler);
        };
        this.setTimeout = function(callback, timeout) {
            this.timeouts.push(window.setTimeout(callback, timeout));
        };
        this.setInterval = function(callback, timeout) {
            this.intervals.push(window.setInterval(callback, timeout));
        };
        this.clearTimeout = window.clearTimeout;
        this.clearInterval = window.clearInterval;

        this.addScript = function(src, loadOnce) {
            loadOnce = loadOnce === undefined ? true : loadOnce;
            if (!(loadOnce && this.scripts[src])) {
                var script = document.createElement('script');
                script.type = 'text/javascript';
                script.src = src;
                script = $(script);

                var that = this;
                script.load(function(){
                    that.scripts[src] = true;
                    $(this).unbind('load');
                });

                $('body').append(script);
            }
        };

        this.load = function() {
            if (!(this.hasRun && this.runOnce)) {
                this.o.load(this);
            }
            this.hasRun = true;
        };
        this.unload = function() {
            if (!this.runOnce) {
                var i;
                for (i = 0; i < this.events.length; i++) {
                    var e = this.events[i];
                    $(e[0]).unbind(e[1], e[2]);
                }
                for (i = 0; i < this.timeouts.length; i++) {
                    window.clearTimeout(this.timeouts[i]);
                }
                for (i = 0; i < this.intervals.length; i++) {
                    window.clearInterval(this.intervals[i]);
                }
                this.o.unload(this);
            }
        };
    }

    methods = {
        init: function(explicitOpts) {
            activeOpts = $.extend(defaultOpts, explicitOpts);

            if (activeOpts.disabled) {
                // shortcut event binding
                return this;
            }

            $(window).bind('hashchange', function(h){
                log('hashchange');
                updatePage(location.hash.substr(1), 'GET', '', activeOpts);
            });

            if (location.hash && location.hash !== '#') {
                log('existing hash', location.hash);
                updatePage(location.hash.substr(1), 'GET', '', activeOpts);
            }
            $('a:not(' + activeOpts.excludeSelector + ')').live('click', function(){
                var href = $(this).attr('href').replace('#', HASH_REPLACEMENT);
                if (href.indexOf(HASH_REPLACEMENT) === 0) {
                    // link is relative to the current page
                    href = location.hash.substr(1).split(HASH_REPLACEMENT)[0] + href;
                }
                location.hash = '#' + href;
                return false;
            });
            liveForms = $('form:not(' + activeOpts.excludeSelector + ')');
            liveForms.live('submit', function(event){
                var url = $(this).attr('action');
                var type = $(this).attr('method');
                var data = $(this).serialize();
                var submitter = this.submitter;
                if (submitter) {
                    data += (data.length === 0 ? "?" : "&") + (
                        encodeURIComponent($(submitter).attr("name"))
                        + "=" + encodeURIComponent($(submitter).attr("value"))
                    );
                }
                log("form submission:", data);
                if (url === '.') {
                    url = location.hash.substr(1);
                }
                if (type.toLowerCase() === 'get') {
                    url = url.substring(0, url.indexOf('?')) || url;
                    url += '?' + data;
                    location.hash = '#' + url;
                } else {
                    // TODO: how does a post affect the hash fragment?
                    activeOpts.beforeUpdate();
                    updatePage(url, type, data, activeOpts);
                }
                return false;
            });
            //make sure the submitting button is included in the form data.
            liveForms.find('input[type=submit],button[type=submit]').live('click', function(event) {
              $(this).closest("form").get(0).submitter = this;
            });
            return this;
        },
        hashchange: function(callback) { // callback = function(e, hash) { ... }
            $(window).bind('hashsignal.hashchange', callback);
            return this;
        },
        hash: function(subhash) {
            var ret;
            if (undefined === subhash) {
                /* window.location.hash is a bit fiddly - if hash is empty, it returns "",
                  but if it is non-empty, it returns "#hash".

                  And when assigning, "foo" and "#foo" are treated as equivalent.
                */
                ret = location.hash.split(HASH_REPLACEMENT)[1];
                return ret ? ("#" + ret) : "";
            }
            if (0 === subhash.indexOf("#")) {
                subhash = subhash.substr(1);
            }
            var parts = location.hash.substr(1).split(HASH_REPLACEMENT);
            if (0 === subhash.length) {
                location.hash = parts[0];
            } else {
                parts[1] = subhash;
                location.hash = parts.join(HASH_REPLACEMENT);
            }
            return subhash;
        },
        registerTransition: function(name, blockNames, opts) {
            log('hashsignal.registerTransition', name, blockNames);
            var transition = new Transition(opts);
            if (!!opts.alwaysReload) {
                blockNames = [ALWAYS_RELOAD];
            }
            for (var i = 0; i < blockNames.length; i++) {
                var blockName = blockNames[i];
                if (transitions[blockName] === undefined) {
                    transitions[blockName] = {};
                }
                if (transitions[blockName][name] === undefined) {
                    transitions[blockName][name] = transition;
                }
            }
            return this;
        },
        _unloadBlock: function(blockName) {
            log('hashsignal.unloadBlock', blockName);
            blockAction('unload', blockName);
        },
        _loadBlock: function(blockName) {
            log('hashsignal.loadBlock', blockName);
            blockAction('load', blockName);
        }
    };
    $.hashsignal = methods;
})(window, jQuery);
<|endoftext|>


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

/**
 * @ngdoc module
 * @name material.components.radioButton
 * @description radioButton module!
 */
angular.module('material.components.radioButton', [
  'material.core'
])
  .directive('mdRadioGroup', mdRadioGroupDirective)
  .directive('mdRadioButton', mdRadioButtonDirective);

/**
 * @ngdoc directive
 * @module material.components.radioButton
 * @name mdRadioGroup
 *
 * @restrict E
 *
 * @description
 * The `<md-radio-group>` directive identifies a grouping
 * container for the 1..n grouped radio buttons; specified using nested
 * `<md-radio-button>` tags.
 *
 * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
 * than the native `<input type='radio'>` controls. Whereas the native controls
 * force the user to tab through all the radio buttons, `<md-radio-group>`
 * is focusable, and by default the `<md-radio-button>`s are not.
 *
 * @param {string} ng-model Assignable angular expression to data-bind to.
 * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
 *
 * @usage
 * <hljs lang="html">
 * <md-radio-group ng-model="selected">
 *
 *   <md-radio-button
 *        ng-repeat="d in colorOptions"
 *        ng-value="d.value" aria-label="{{ d.label }}">
 *
 *          {{ d.label }}
 *
 *   </md-radio-button>
 *
 * </md-radio-group>
 * </hljs>
 *
 */
function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming) {
  RadioGroupController.prototype = createRadioGroupControllerProto();

  return {
    restrict: 'E',
    controller: ['$element', RadioGroupController],
    require: ['mdRadioGroup', '?ngModel'],
    link: linkRadioGroup
  };

  function linkRadioGroup(scope, element, attr, ctrls) {
    $mdTheming(element);
    var rgCtrl = ctrls[0];
    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();

    function keydownListener(ev) {
      switch(ev.keyCode) {
        case $mdConstant.KEY_CODE.LEFT_ARROW:
        case $mdConstant.KEY_CODE.UP_ARROW:
          ev.preventDefault();
          rgCtrl.selectPrevious();
          break;

        case $mdConstant.KEY_CODE.RIGHT_ARROW:
        case $mdConstant.KEY_CODE.DOWN_ARROW:
          ev.preventDefault();
          rgCtrl.selectNext();
          break;

        case $mdConstant.KEY_CODE.ENTER:
          var form = angular.element($mdUtil.getClosest(element[0], 'form'));
          if (form.length > 0) {
            form.triggerHandler('submit');
          }
          break;
      }
    }

    rgCtrl.init(ngModelCtrl);

    element.attr({
              'role': 'radiogroup',
              'tabIndex': element.attr('tabindex') || '0'
            })
            .on('keydown', keydownListener);

  }

  function RadioGroupController($element) {
    this._radioButtonRenderFns = [];
    this.$element = $element;
  }

  function createRadioGroupControllerProto() {
    return {
      init: function(ngModelCtrl) {
        this._ngModelCtrl = ngModelCtrl;
        this._ngModelCtrl.$render = angular.bind(this, this.render);
      },
      add: function(rbRender) {
        this._radioButtonRenderFns.push(rbRender);
      },
      remove: function(rbRender) {
        var index = this._radioButtonRenderFns.indexOf(rbRender);
        if (index !== -1) {
          this._radioButtonRenderFns.splice(index, 1);
        }
      },
      render: function() {
        this._radioButtonRenderFns.forEach(function(rbRender) {
          rbRender();
        });
      },
      setViewValue: function(value, eventType) {
        this._ngModelCtrl.$setViewValue(value, eventType);
        // update the other radio buttons as well
        this.render();
      },
      getViewValue: function() {
        return this._ngModelCtrl.$viewValue;
      },
      selectNext: function() {
        return changeSelectedButton(this.$element, 1);
      },
      selectPrevious : function() {
        return changeSelectedButton(this.$element, -1);
      },
      setActiveDescendant: function (radioId) {
        this.$element.attr('aria-activedescendant', radioId);
      }
    };
  }
  /**
   * Change the radio group's selected button by a given increment.
   * If no button is selected, select the first button.
   */
  function changeSelectedButton(parent, increment) {
    // Coerce all child radio buttons into an array, then wrap then in an iterator
    var buttons = $mdUtil.iterator(
      Array.prototype.slice.call(parent[0].querySelectorAll('md-radio-button')),
      true
    );

    if (buttons.count()) {
      var validate = function (button) {
        // If disabled, then NOT valid
        return !angular.element(button).attr("disabled");
      };
      var selected = parent[0].querySelector('md-radio-button.md-checked');
      var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
      // Activate radioButton's click listener (triggerHandler won't create a real click event)
      angular.element(target).triggerHandler('click');

    }
  }

}

/**
 * @ngdoc directive
 * @module material.components.radioButton
 * @name mdRadioButton
 *
 * @restrict E
 *
 * @description
 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
 *
 * While similar to the `<input type="radio" ng-model="" value="">` directive,
 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
 * supports use within named radio groups.
 *
 * @param {string} ngModel Assignable angular expression to data-bind to.
 * @param {string=} ngChange Angular expression to be executed when input changes due to user
 *    interaction with the input element.
 * @param {string} ngValue Angular expression which sets the value to which the expression should
 *    be set when selected.*
 * @param {string} value The value to which the expression should be set when selected.
 * @param {string=} name Property name of the form under which the control is published.
 * @param {string=} ariaLabel Adds label to radio button for accessibility.
 * Defaults to radio button's text. If no default text is found, a warning will be logged.
 *
 * @usage
 * <hljs lang="html">
 *
 * <md-radio-button value="1" aria-label="Label 1">
 *   Label 1
 * </md-radio-button>
 *
 * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
 *   Green
 * </md-radio-button>
 *
 * </hljs>
 *
 */
function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {

  var CHECKED_CSS = 'md-checked';

  return {
    restrict: 'E',
    require: '^mdRadioGroup',
    transclude: true,
    template: '
' +
                '

' +
                '

' +
              '
' +
              '

',
    link: link
  };

  function link(scope, element, attr, rgCtrl) {
    var lastChecked;

    $mdTheming(element);
    configureAria(element, scope);

    rgCtrl.add(render);
    attr.$observe('value', render);

    element
      .on('click', listener)
      .on('$destroy', function() {
        rgCtrl.remove(render);
      });

    function listener(ev) {
      if (element[0].hasAttribute('disabled')) return;

      scope.$apply(function() {
        rgCtrl.setViewValue(attr.value, ev && ev.type);
      });
    }

    function render() {
      var checked = (rgCtrl.getViewValue() == attr.value);
      if (checked === lastChecked) {
        return;
      }
      lastChecked = checked;
      element.attr('aria-checked', checked);
      if (checked) {
        element.addClass(CHECKED_CSS);
        rgCtrl.setActiveDescendant(element.attr('id'));
      } else {
        element.removeClass(CHECKED_CSS);
      }
    }
    /**
     * Inject ARIA-specific attributes appropriate for each radio button
     */
    function configureAria( element, scope ){
      scope.ariaId = buildAriaID();

      element.attr({
        'id' :  scope.ariaId,
        'role' : 'radio',
        'aria-checked' : 'false'
      });

      $mdAria.expectWithText(element, 'aria-label');

      /**
       * Build a unique ID for each radio button that will be used with aria-activedescendant.
       * Preserve existing ID if already specified.
       * @returns {*|string}
       */
      function buildAriaID() {
        return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
      }
    }
  }
}

})();
<commit_msg>fix(radioGroup): fix render call happing before radioGroup init<commit_after>(function() {
'use strict';

/**
 * @ngdoc module
 * @name material.components.radioButton
 * @description radioButton module!
 */
angular.module('material.components.radioButton', [
  'material.core'
])
  .directive('mdRadioGroup', mdRadioGroupDirective)
  .directive('mdRadioButton', mdRadioButtonDirective);

/**
 * @ngdoc directive
 * @module material.components.radioButton
 * @name mdRadioGroup
 *
 * @restrict E
 *
 * @description
 * The `<md-radio-group>` directive identifies a grouping
 * container for the 1..n grouped radio buttons; specified using nested
 * `<md-radio-button>` tags.
 *
 * Note: `<md-radio-group>` and `<md-radio-button>` handle tabindex differently
 * than the native `<input type='radio'>` controls. Whereas the native controls
 * force the user to tab through all the radio buttons, `<md-radio-group>`
 * is focusable, and by default the `<md-radio-button>`s are not.
 *
 * @param {string} ng-model Assignable angular expression to data-bind to.
 * @param {boolean=} md-no-ink Use of attribute indicates flag to disable ink ripple effects.
 *
 * @usage
 * <hljs lang="html">
 * <md-radio-group ng-model="selected">
 *
 *   <md-radio-button
 *        ng-repeat="d in colorOptions"
 *        ng-value="d.value" aria-label="{{ d.label }}">
 *
 *          {{ d.label }}
 *
 *   </md-radio-button>
 *
 * </md-radio-group>
 * </hljs>
 *
 */
function mdRadioGroupDirective($mdUtil, $mdConstant, $mdTheming) {
  RadioGroupController.prototype = createRadioGroupControllerProto();

  return {
    restrict: 'E',
    controller: ['$element', RadioGroupController],
    require: ['mdRadioGroup', '?ngModel'],
    link: { pre: linkRadioGroup }
  };

  function linkRadioGroup(scope, element, attr, ctrls) {
    $mdTheming(element);
    var rgCtrl = ctrls[0];
    var ngModelCtrl = ctrls[1] || $mdUtil.fakeNgModel();

    function keydownListener(ev) {
      switch(ev.keyCode) {
        case $mdConstant.KEY_CODE.LEFT_ARROW:
        case $mdConstant.KEY_CODE.UP_ARROW:
          ev.preventDefault();
          rgCtrl.selectPrevious();
          break;

        case $mdConstant.KEY_CODE.RIGHT_ARROW:
        case $mdConstant.KEY_CODE.DOWN_ARROW:
          ev.preventDefault();
          rgCtrl.selectNext();
          break;

        case $mdConstant.KEY_CODE.ENTER:
          var form = angular.element($mdUtil.getClosest(element[0], 'form'));
          if (form.length > 0) {
            form.triggerHandler('submit');
          }
          break;
      }
    }

    rgCtrl.init(ngModelCtrl);

    element.attr({
              'role': 'radiogroup',
              'tabIndex': element.attr('tabindex') || '0'
            })
            .on('keydown', keydownListener);

  }

  function RadioGroupController($element) {
    this._radioButtonRenderFns = [];
    this.$element = $element;
  }

  function createRadioGroupControllerProto() {
    return {
      init: function(ngModelCtrl) {
        this._ngModelCtrl = ngModelCtrl;
        this._ngModelCtrl.$render = angular.bind(this, this.render);
      },
      add: function(rbRender) {
        this._radioButtonRenderFns.push(rbRender);
      },
      remove: function(rbRender) {
        var index = this._radioButtonRenderFns.indexOf(rbRender);
        if (index !== -1) {
          this._radioButtonRenderFns.splice(index, 1);
        }
      },
      render: function() {
        this._radioButtonRenderFns.forEach(function(rbRender) {
          rbRender();
        });
      },
      setViewValue: function(value, eventType) {
        this._ngModelCtrl.$setViewValue(value, eventType);
        // update the other radio buttons as well
        this.render();
      },
      getViewValue: function() {
        return this._ngModelCtrl.$viewValue;
      },
      selectNext: function() {
        return changeSelectedButton(this.$element, 1);
      },
      selectPrevious : function() {
        return changeSelectedButton(this.$element, -1);
      },
      setActiveDescendant: function (radioId) {
        this.$element.attr('aria-activedescendant', radioId);
      }
    };
  }
  /**
   * Change the radio group's selected button by a given increment.
   * If no button is selected, select the first button.
   */
  function changeSelectedButton(parent, increment) {
    // Coerce all child radio buttons into an array, then wrap then in an iterator
    var buttons = $mdUtil.iterator(
      Array.prototype.slice.call(parent[0].querySelectorAll('md-radio-button')),
      true
    );

    if (buttons.count()) {
      var validate = function (button) {
        // If disabled, then NOT valid
        return !angular.element(button).attr("disabled");
      };
      var selected = parent[0].querySelector('md-radio-button.md-checked');
      var target = buttons[increment < 0 ? 'previous' : 'next'](selected, validate) || buttons.first();
      // Activate radioButton's click listener (triggerHandler won't create a real click event)
      angular.element(target).triggerHandler('click');

    }
  }

}

/**
 * @ngdoc directive
 * @module material.components.radioButton
 * @name mdRadioButton
 *
 * @restrict E
 *
 * @description
 * The `<md-radio-button>`directive is the child directive required to be used within `<md-radio-group>` elements.
 *
 * While similar to the `<input type="radio" ng-model="" value="">` directive,
 * the `<md-radio-button>` directive provides ink effects, ARIA support, and
 * supports use within named radio groups.
 *
 * @param {string} ngModel Assignable angular expression to data-bind to.
 * @param {string=} ngChange Angular expression to be executed when input changes due to user
 *    interaction with the input element.
 * @param {string} ngValue Angular expression which sets the value to which the expression should
 *    be set when selected.*
 * @param {string} value The value to which the expression should be set when selected.
 * @param {string=} name Property name of the form under which the control is published.
 * @param {string=} ariaLabel Adds label to radio button for accessibility.
 * Defaults to radio button's text. If no default text is found, a warning will be logged.
 *
 * @usage
 * <hljs lang="html">
 *
 * <md-radio-button value="1" aria-label="Label 1">
 *   Label 1
 * </md-radio-button>
 *
 * <md-radio-button ng-model="color" ng-value="specialValue" aria-label="Green">
 *   Green
 * </md-radio-button>
 *
 * </hljs>
 *
 */
function mdRadioButtonDirective($mdAria, $mdUtil, $mdTheming) {

  var CHECKED_CSS = 'md-checked';

  return {
    restrict: 'E',
    require: '^mdRadioGroup',
    transclude: true,
    template: '
' +
                '

' +
                '

' +
              '
' +
              '

',
    link: link
  };

  function link(scope, element, attr, rgCtrl) {
    var lastChecked;

    $mdTheming(element);
    configureAria(element, scope);

    rgCtrl.add(render);
    attr.$observe('value', render);

    element
      .on('click', listener)
      .on('$destroy', function() {
        rgCtrl.remove(render);
      });

    function listener(ev) {
      if (element[0].hasAttribute('disabled')) return;

      scope.$apply(function() {
        rgCtrl.setViewValue(attr.value, ev && ev.type);
      });
    }

    function render() {
      var checked = (rgCtrl.getViewValue() == attr.value);
      if (checked === lastChecked) {
        return;
      }
      lastChecked = checked;
      element.attr('aria-checked', checked);
      if (checked) {
        element.addClass(CHECKED_CSS);
        rgCtrl.setActiveDescendant(element.attr('id'));
      } else {
        element.removeClass(CHECKED_CSS);
      }
    }
    /**
     * Inject ARIA-specific attributes appropriate for each radio button
     */
    function configureAria( element, scope ){
      scope.ariaId = buildAriaID();

      element.attr({
        'id' :  scope.ariaId,
        'role' : 'radio',
        'aria-checked' : 'false'
      });

      $mdAria.expectWithText(element, 'aria-label');

      /**
       * Build a unique ID for each radio button that will be used with aria-activedescendant.
       * Preserve existing ID if already specified.
       * @returns {*|string}
       */
      function buildAriaID() {
        return attr.id || ( 'radio' + "_" + $mdUtil.nextUid() );
      }
    }
  }
}

})();
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import * as Matrix from 'matrix-js-sdk';
import React from 'react';

import KeyCode from '../../KeyCode';
import Notifier from '../../Notifier';
import PageTypes from '../../PageTypes';
import sdk from '../../index';
import dis from '../../dispatcher';

/**
 * This is what our MatrixChat shows when we are logged in. The precise view is
 * determined by the page_type property.
 *
 * Currently it's very tightly coupled with MatrixChat. We should try to do
 * something about that.
 *
 * Components mounted below us can access the matrix client via the react context.
 */
export default React.createClass({
    displayName: 'LoggedInView',

    propTypes: {
        matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
        page_type: React.PropTypes.string.isRequired,
        onRoomIdResolved: React.PropTypes.func,
        onRoomCreated: React.PropTypes.func,
        onUserSettingsClose: React.PropTypes.func,

        teamToken: React.PropTypes.string,

        // and lots and lots of other stuff.
    },

    childContextTypes: {
        matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient),
    },

    getChildContext: function() {
        return {
            matrixClient: this._matrixClient,
        };
    },

    componentWillMount: function() {
        // stash the MatrixClient in case we log out before we are unmounted
        this._matrixClient = this.props.matrixClient;

        // _scrollStateMap is a map from room id to the scroll state returned by
        // RoomView.getScrollState()
        this._scrollStateMap = {};

        document.addEventListener('keydown', this._onKeyDown);
    },

    componentWillUnmount: function() {
        document.removeEventListener('keydown', this._onKeyDown);
    },

    getScrollStateForRoom: function(roomId) {
        return this._scrollStateMap[roomId];
    },

    _onKeyDown: function(ev) {
            /*
            // Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
            // Will need to find a better meta key if anyone actually cares about using this.
            if (ev.altKey && ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
                dis.dispatch({
                    action: 'view_indexed_room',
                    roomIndex: ev.keyCode - 49,
                });
                ev.stopPropagation();
                ev.preventDefault();
                return;
            }
            */

        var handled = false;

        switch (ev.keyCode) {
            case KeyCode.UP:
            case KeyCode.DOWN:
                if (ev.altKey) {
                    var action = ev.keyCode == KeyCode.UP ?
                        'view_prev_room' : 'view_next_room';
                    dis.dispatch({action: action});
                    handled = true;
                }
                break;

            case KeyCode.PAGE_UP:
            case KeyCode.PAGE_DOWN:
                this._onScrollKeyPressed(ev);
                handled = true;
                break;

            case KeyCode.HOME:
            case KeyCode.END:
                if (ev.ctrlKey) {
                    this._onScrollKeyPressed(ev);
                    handled = true;
                }
                break;
        }

        if (handled) {
            ev.stopPropagation();
            ev.preventDefault();
        }
    },

    /** dispatch a page-up/page-down/etc to the appropriate component */
    _onScrollKeyPressed: function(ev) {
        if (this.refs.roomView) {
            this.refs.roomView.handleScrollKey(ev);
        }
    },

    render: function() {
        var LeftPanel = sdk.getComponent('structures.LeftPanel');
        var RightPanel = sdk.getComponent('structures.RightPanel');
        var RoomView = sdk.getComponent('structures.RoomView');
        var UserSettings = sdk.getComponent('structures.UserSettings');
        var CreateRoom = sdk.getComponent('structures.CreateRoom');
        var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
        var HomePage = sdk.getComponent('structures.HomePage');
        var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
        var GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
        var NewVersionBar = sdk.getComponent('globals.NewVersionBar');

        var page_element;
        var right_panel = '';

        switch (this.props.page_type) {
            case PageTypes.RoomView:
                page_element = <RoomView
                        ref='roomView'
                        roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
                        autoJoin={this.props.autoJoin}
                        onRoomIdResolved={this.props.onRoomIdResolved}
                        eventId={this.props.initialEventId}
                        thirdPartyInvite={this.props.thirdPartyInvite}
                        oobData={this.props.roomOobData}
                        highlightedEventId={this.props.highlightedEventId}
                        eventPixelOffset={this.props.initialEventPixelOffset}
                        key={this.props.currentRoomAlias || this.props.currentRoomId}
                        opacity={this.props.middleOpacity}
                        collapsedRhs={this.props.collapse_rhs}
                        ConferenceHandler={this.props.ConferenceHandler}
                        scrollStateMap={this._scrollStateMap}
                    />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.sideOpacity} />;
                break;

            case PageTypes.UserSettings:
                page_element = <UserSettings
                    onClose={this.props.onUserSettingsClose}
                    brand={this.props.config.brand}
                    collapsedRhs={this.props.collapse_rhs}
                    enableLabs={this.props.config.enableLabs}
                    referralBaseUrl={this.props.config.referralBaseUrl}
                />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
                break;

            case PageTypes.CreateRoom:
                page_element = <CreateRoom
                    onRoomCreated={this.props.onRoomCreated}
                    collapsedRhs={this.props.collapse_rhs}
                />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
                break;

            case PageTypes.RoomDirectory:
                page_element = <RoomDirectory
                    collapsedRhs={this.props.collapse_rhs}
                    config={this.props.config.roomDirectory}
                />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
                break;

            case PageTypes.HomePage:
                page_element = <HomePage
                    collapsedRhs={this.props.collapse_rhs}
                    teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
                    teamToken={this.props.teamToken}
                />
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>
                break;

            case PageTypes.UserView:
                page_element = null; // deliberately null for now
                right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.sideOpacity} />;
                break;
        }

        var topBar;
        if (this.props.hasNewVersion) {
            topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
                releaseNotes={this.props.newVersionReleaseNotes}
            />;
        }
        else if (this.props.matrixClient.isGuest()) {
            topBar = <GuestWarningBar />;
        }
        else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
            topBar = <MatrixToolbar />;
        }

        var bodyClasses = 'mx_MatrixChat';
        if (topBar) {
            bodyClasses += ' mx_MatrixChat_toolbarShowing';
        }

        return (

                {topBar}

                    <LeftPanel selectedRoom={this.props.currentRoomId} collapsed={this.props.collapse_lhs || false} opacity={this.props.sideOpacity}/>

                        {page_element}

                    {right_panel}

        );
    },
});
<commit_msg>Thread teamToken through to LeftPanel for "Home" button<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import * as Matrix from 'matrix-js-sdk';
import React from 'react';

import KeyCode from '../../KeyCode';
import Notifier from '../../Notifier';
import PageTypes from '../../PageTypes';
import sdk from '../../index';
import dis from '../../dispatcher';

/**
 * This is what our MatrixChat shows when we are logged in. The precise view is
 * determined by the page_type property.
 *
 * Currently it's very tightly coupled with MatrixChat. We should try to do
 * something about that.
 *
 * Components mounted below us can access the matrix client via the react context.
 */
export default React.createClass({
    displayName: 'LoggedInView',

    propTypes: {
        matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
        page_type: React.PropTypes.string.isRequired,
        onRoomIdResolved: React.PropTypes.func,
        onRoomCreated: React.PropTypes.func,
        onUserSettingsClose: React.PropTypes.func,

        teamToken: React.PropTypes.string,

        // and lots and lots of other stuff.
    },

    childContextTypes: {
        matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient),
    },

    getChildContext: function() {
        return {
            matrixClient: this._matrixClient,
        };
    },

    componentWillMount: function() {
        // stash the MatrixClient in case we log out before we are unmounted
        this._matrixClient = this.props.matrixClient;

        // _scrollStateMap is a map from room id to the scroll state returned by
        // RoomView.getScrollState()
        this._scrollStateMap = {};

        document.addEventListener('keydown', this._onKeyDown);
    },

    componentWillUnmount: function() {
        document.removeEventListener('keydown', this._onKeyDown);
    },

    getScrollStateForRoom: function(roomId) {
        return this._scrollStateMap[roomId];
    },

    _onKeyDown: function(ev) {
            /*
            // Remove this for now as ctrl+alt = alt-gr so this breaks keyboards which rely on alt-gr for numbers
            // Will need to find a better meta key if anyone actually cares about using this.
            if (ev.altKey && ev.ctrlKey && ev.keyCode > 48 && ev.keyCode < 58) {
                dis.dispatch({
                    action: 'view_indexed_room',
                    roomIndex: ev.keyCode - 49,
                });
                ev.stopPropagation();
                ev.preventDefault();
                return;
            }
            */

        var handled = false;

        switch (ev.keyCode) {
            case KeyCode.UP:
            case KeyCode.DOWN:
                if (ev.altKey) {
                    var action = ev.keyCode == KeyCode.UP ?
                        'view_prev_room' : 'view_next_room';
                    dis.dispatch({action: action});
                    handled = true;
                }
                break;

            case KeyCode.PAGE_UP:
            case KeyCode.PAGE_DOWN:
                this._onScrollKeyPressed(ev);
                handled = true;
                break;

            case KeyCode.HOME:
            case KeyCode.END:
                if (ev.ctrlKey) {
                    this._onScrollKeyPressed(ev);
                    handled = true;
                }
                break;
        }

        if (handled) {
            ev.stopPropagation();
            ev.preventDefault();
        }
    },

    /** dispatch a page-up/page-down/etc to the appropriate component */
    _onScrollKeyPressed: function(ev) {
        if (this.refs.roomView) {
            this.refs.roomView.handleScrollKey(ev);
        }
    },

    render: function() {
        var LeftPanel = sdk.getComponent('structures.LeftPanel');
        var RightPanel = sdk.getComponent('structures.RightPanel');
        var RoomView = sdk.getComponent('structures.RoomView');
        var UserSettings = sdk.getComponent('structures.UserSettings');
        var CreateRoom = sdk.getComponent('structures.CreateRoom');
        var RoomDirectory = sdk.getComponent('structures.RoomDirectory');
        var HomePage = sdk.getComponent('structures.HomePage');
        var MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
        var GuestWarningBar = sdk.getComponent('globals.GuestWarningBar');
        var NewVersionBar = sdk.getComponent('globals.NewVersionBar');

        var page_element;
        var right_panel = '';

        switch (this.props.page_type) {
            case PageTypes.RoomView:
                page_element = <RoomView
                        ref='roomView'
                        roomAddress={this.props.currentRoomAlias || this.props.currentRoomId}
                        autoJoin={this.props.autoJoin}
                        onRoomIdResolved={this.props.onRoomIdResolved}
                        eventId={this.props.initialEventId}
                        thirdPartyInvite={this.props.thirdPartyInvite}
                        oobData={this.props.roomOobData}
                        highlightedEventId={this.props.highlightedEventId}
                        eventPixelOffset={this.props.initialEventPixelOffset}
                        key={this.props.currentRoomAlias || this.props.currentRoomId}
                        opacity={this.props.middleOpacity}
                        collapsedRhs={this.props.collapse_rhs}
                        ConferenceHandler={this.props.ConferenceHandler}
                        scrollStateMap={this._scrollStateMap}
                    />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel roomId={this.props.currentRoomId} opacity={this.props.sideOpacity} />;
                break;

            case PageTypes.UserSettings:
                page_element = <UserSettings
                    onClose={this.props.onUserSettingsClose}
                    brand={this.props.config.brand}
                    collapsedRhs={this.props.collapse_rhs}
                    enableLabs={this.props.config.enableLabs}
                    referralBaseUrl={this.props.config.referralBaseUrl}
                />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
                break;

            case PageTypes.CreateRoom:
                page_element = <CreateRoom
                    onRoomCreated={this.props.onRoomCreated}
                    collapsedRhs={this.props.collapse_rhs}
                />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
                break;

            case PageTypes.RoomDirectory:
                page_element = <RoomDirectory
                    collapsedRhs={this.props.collapse_rhs}
                    config={this.props.config.roomDirectory}
                />;
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>;
                break;

            case PageTypes.HomePage:
                page_element = <HomePage
                    collapsedRhs={this.props.collapse_rhs}
                    teamServerUrl={this.props.config.teamServerConfig.teamServerURL}
                    teamToken={this.props.teamToken}
                />
                if (!this.props.collapse_rhs) right_panel = <RightPanel opacity={this.props.sideOpacity}/>
                break;

            case PageTypes.UserView:
                page_element = null; // deliberately null for now
                right_panel = <RightPanel userId={this.props.viewUserId} opacity={this.props.sideOpacity} />;
                break;
        }

        var topBar;
        if (this.props.hasNewVersion) {
            topBar = <NewVersionBar version={this.props.version} newVersion={this.props.newVersion}
                releaseNotes={this.props.newVersionReleaseNotes}
            />;
        }
        else if (this.props.matrixClient.isGuest()) {
            topBar = <GuestWarningBar />;
        }
        else if (Notifier.supportsDesktopNotifications() && !Notifier.isEnabled() && !Notifier.isToolbarHidden()) {
            topBar = <MatrixToolbar />;
        }

        var bodyClasses = 'mx_MatrixChat';
        if (topBar) {
            bodyClasses += ' mx_MatrixChat_toolbarShowing';
        }

        return (

                {topBar}

                    <LeftPanel
                        selectedRoom={this.props.currentRoomId}
                        collapsed={this.props.collapse_lhs || false}
                        opacity={this.props.sideOpacity}
                        teamToken={this.props.teamToken}
                    />

                        {page_element}

                    {right_panel}

        );
    },
});
<|endoftext|>


<commit_before>import React, { Component } from "react";
import gql from "graphql-tag";
import { Container, Row, Col, Button } from "reactstrap";
import { graphql, withApollo } from "react-apollo";
import { OutputField } from "../../generic/core";

import "./style.scss";

const STEALTH_SUB = gql`
  subscription StealthFieldUpdate($simulatorId: ID!) {
    stealthFieldUpdate(simulatorId: $simulatorId) {
      id
      name
      state
      charge
      activated
      quadrants {
        fore
        aft
        port
        starboard
      }
    }
  }
`;
/*
const SYSTEMS_SUB = gql`
  subscription SystemsUpdate($simulatorId: ID, $type: String) {
    systemsUpdate(simulatorId: $simulatorId, type: $type) {
      id
      name
      type
      stealthFactor
    }
  }
`;*/

class StealthFieldCore extends Component {
  constructor(props) {
    super(props);
    this.subscription = null;
    this.systemsSubscription = null;
  }
  componentWillReceiveProps(nextProps) {
    if (!this.subscription && !nextProps.data.loading) {
      this.subscription = nextProps.data.subscribeToMore({
        document: STEALTH_SUB,
        variables: {
          simulatorId: nextProps.simulator.id
        },
        updateQuery: (previousResult, { subscriptionData }) => {
          return Object.assign({}, previousResult, {
            stealthField: subscriptionData.data.stealthFieldUpdate
          });
        }
      });
    }
    if (!this.systemsSubscription && !nextProps.data.loading) {
      this.props.data.startPolling(1000);
      /*this.systemsSubscription = nextProps.data.subscribeToMore({
        document: SYSTEMS_SUB,
        variables: {
          simulatorId: nextProps.simulator.id
        },
        updateQuery: (previousResult, { subscriptionData }) => {
          const returnResult = Immutable.Map(previousResult);
          return returnResult
            .merge({ systems: subscriptionData.data.systemsUpdate })
            .toJS();
        }
      });*/
    }
  }
  componentWillUnmount() {
    this.props.data.stopPolling(1000);
    this.subscription && this.subscription();
  }
  systemName(sys) {
    if (sys.type === "Shield") {
      return `${sys.name} Shields`;
    }
    if (sys.type === "Engine") {
      return `${sys.name} Engines`;
    }
    return sys.name;
  }
  toggleStealth = () => {
    const { id, state } = this.props.data.stealthField[0];
    let mutation;
    if (!state) {
      mutation = gql`
        mutation ActivateStealth($id: ID!) {
          activateStealth(id: $id)
        }
      `;
    } else {
      mutation = gql`
        mutation DeactivateStealth($id: ID!) {
          deactivateStealth(id: $id)
        }
      `;
    }
    const variables = { id };
    this.props.client.mutate({
      mutation,
      variables
    });
  };
  updateActivated(e) {
    const { id } = this.props.data.stealthField[0];
    const mutation = gql`
      mutation ToggleActivated($id: ID!, $state: Boolean!) {
        setStealthActivated(id: $id, state: $state)
      }
    `;
    const variables = {
      id,
      state: e.target.checked
    };
    this.props.client.mutate({
      mutation,
      variables
    });
  }
  updateCharge(e) {
    const { id } = this.props.data.stealthField[0];
    const mutation = gql`
      mutation ToggleCharge($id: ID!, $state: Boolean!) {
        setStealthCharge(id: $id, state: $state)
      }
    `;
    const variables = {
      id,
      state: e.target.checked
    };
    this.props.client.mutate({
      mutation,
      variables
    });
  }
  fluxCharge = () => {
    const mutation = gql`
      mutation FluxStealth($id: ID) {
        fluxStealthQuadrants(id: $id)
      }
    `;
    const variables = {
      id: this.props.data.stealthField[0].id
    };
    this.props.client.mutate({
      mutation,
      variables
    });
  };
  render() {
    if (this.props.data.loading || !this.props.data.stealthField) return null;
    const stealthField = this.props.data.stealthField[0];
    if (!stealthField) return
No Stealth Field Systems
;

    // Calculate the systems that are highest
    let alertHigh = false;
    const highSystems = this.props.data.systems
      .filter(s => s.stealthFactor > 0.5)
      .map(s => {
        if (s.stealthFactor > 0.8) {
          alertHigh = true;
        }
        return s;
      });
    const sysStyle = {};
    if (highSystems.length > 0) {
      sysStyle.backgroundColor = "yellow";
      sysStyle.borderColor = "#CCCC00";
    }
    const highSystemsText = highSystems.reduce((prev, s, i) => {
      let returnString = `${this.systemName(s)} (${Math.round(
        s.stealthFactor * 100
      )})`;
      if (i < highSystems.length - 1) returnString += ", ";
      return prev + returnString;
    }, "");
    return (
      <Container className="targeting-core">

          {" "}
          Activate{" "}
          <input
            checked={stealthField.activated}
            onChange={this.updateActivated.bind(this)}
            type="checkbox"
          />

          Charge{" "}
          <input
            checked={stealthField.charge}
            onChange={this.updateCharge.bind(this)}
            type="checkbox"
          />

        <Button color="warning" size="sm" onClick={this.fluxCharge}>
          Flux
        </Button>
        <Row>
          <Col sm="12">
            <OutputField
              onClick={this.toggleStealth}
              alert={stealthField.state}
            >
              {stealthField.state ? "Activated" : "Deactivated"}
            </OutputField>
          </Col>
          <Col sm="12">
            <OutputField
              id="stealth-systems"
              style={sysStyle}
              alert={alertHigh}
              title={highSystemsText}
            >
              {highSystems.length > 1
                ? `${highSystems.length} Alert Systems`
                : `${highSystems[0] && highSystems[0].name} (${Math.round(
                    highSystems[0] ? highSystems[0].stealthFactor * 100 : 0
                  )})`}
            </OutputField>
          </Col>
        </Row>
      </Container>
    );
  }
}

const STEALTH_QUERY = gql`
  query StealthField($simulatorId: ID!) {
    stealthField(simulatorId: $simulatorId) {
      id
      name
      state
      charge
      activated
      quadrants {
        fore
        aft
        port
        starboard
      }
    }
    systems(simulatorId: $simulatorId) {
      id
      name
      type
      stealthFactor
    }
  }
`;

export default graphql(STEALTH_QUERY, {
  options: ownProps => ({
    fetchPolicy: "cache-and-network",

    variables: {
      simulatorId: ownProps.simulator.id,
      names: ["Icons", "Pictures"]
    }
  })
})(withApollo(StealthFieldCore));
<commit_msg>Minor improvements to the stealth system. Closes #1012<commit_after>import React, { Component } from "react";
import gql from "graphql-tag";
import { Container, Row, Col, Button } from "reactstrap";
import { graphql, withApollo } from "react-apollo";
import { OutputField } from "../../generic/core";

import "./style.scss";

const STEALTH_SUB = gql`
  subscription StealthFieldUpdate($simulatorId: ID!) {
    stealthFieldUpdate(simulatorId: $simulatorId) {
      id
      name
      state
      charge
      activated
      quadrants {
        fore
        aft
        port
        starboard
      }
    }
  }
`;
/*
const SYSTEMS_SUB = gql`
  subscription SystemsUpdate($simulatorId: ID, $type: String) {
    systemsUpdate(simulatorId: $simulatorId, type: $type) {
      id
      name
      type
      stealthFactor
    }
  }
`;*/

class StealthFieldCore extends Component {
  constructor(props) {
    super(props);
    this.subscription = null;
    this.systemsSubscription = null;
  }
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (!this.subscription && !nextProps.data.loading) {
      this.subscription = nextProps.data.subscribeToMore({
        document: STEALTH_SUB,
        variables: {
          simulatorId: nextProps.simulator.id
        },
        updateQuery: (previousResult, { subscriptionData }) => {
          return Object.assign({}, previousResult, {
            stealthField: subscriptionData.data.stealthFieldUpdate
          });
        }
      });
    }
    if (!this.systemsSubscription && !nextProps.data.loading) {
      this.props.data.startPolling(1000);
      /*this.systemsSubscription = nextProps.data.subscribeToMore({
        document: SYSTEMS_SUB,
        variables: {
          simulatorId: nextProps.simulator.id
        },
        updateQuery: (previousResult, { subscriptionData }) => {
          const returnResult = Immutable.Map(previousResult);
          return returnResult
            .merge({ systems: subscriptionData.data.systemsUpdate })
            .toJS();
        }
      });*/
    }
  }
  componentWillUnmount() {
    this.props.data.stopPolling(1000);
    this.subscription && this.subscription();
  }
  systemName(sys) {
    if (sys.type === "Shield") {
      return `${sys.name} Shields`;
    }
    if (sys.type === "Engine") {
      return `${sys.name} Engines`;
    }
    return sys.name;
  }
  toggleStealth = () => {
    const { id, state } = this.props.data.stealthField[0];
    let mutation;
    if (!state) {
      mutation = gql`
        mutation ActivateStealth($id: ID!) {
          activateStealth(id: $id)
        }
      `;
    } else {
      mutation = gql`
        mutation DeactivateStealth($id: ID!) {
          deactivateStealth(id: $id)
        }
      `;
    }
    const variables = { id };
    this.props.client.mutate({
      mutation,
      variables
    });
  };
  updateActivated(e) {
    const { id } = this.props.data.stealthField[0];
    const mutation = gql`
      mutation ToggleActivated($id: ID!, $state: Boolean!) {
        setStealthActivated(id: $id, state: $state)
      }
    `;
    const variables = {
      id,
      state: e.target.checked
    };
    this.props.client.mutate({
      mutation,
      variables
    });
  }
  updateCharge(e) {
    const { id } = this.props.data.stealthField[0];
    const mutation = gql`
      mutation ToggleCharge($id: ID!, $state: Boolean!) {
        setStealthCharge(id: $id, state: $state)
      }
    `;
    const variables = {
      id,
      state: e.target.checked
    };
    this.props.client.mutate({
      mutation,
      variables
    });
  }
  fluxCharge = () => {
    const mutation = gql`
      mutation FluxStealth($id: ID) {
        fluxStealthQuadrants(id: $id)
      }
    `;
    const variables = {
      id: this.props.data.stealthField[0].id
    };
    this.props.client.mutate({
      mutation,
      variables
    });
  };
  render() {
    if (this.props.data.loading || !this.props.data.stealthField) return null;
    const stealthField = this.props.data.stealthField[0];
    if (!stealthField) return
No Stealth Field Systems
;

    // Calculate the systems that are highest
    let alertHigh = false;
    const highSystems = this.props.data.systems
      .filter(s => s.stealthFactor > 0.5)
      .map(s => {
        if (s.stealthFactor > 0.8) {
          alertHigh = true;
        }
        return s;
      });

    const sysStyle = {};
    if (highSystems.length > 0) {
      sysStyle.backgroundColor = "yellow";
      sysStyle.borderColor = "#CCCC00";
    }
    const highSystemsText = highSystems.reduce((prev, s, i) => {
      let returnString = `${this.systemName(s)} (${Math.round(
        s.stealthFactor * 100
      )})`;
      if (i < highSystems.length - 1) returnString += ", ";
      return prev + returnString;
    }, "");
    return (
      <Container className="targeting-core">

          {" "}
          Activate{" "}
          <input
            checked={stealthField.activated}
            onChange={this.updateActivated.bind(this)}
            type="checkbox"
          />

          Charge{" "}
          <input
            checked={stealthField.charge}
            onChange={this.updateCharge.bind(this)}
            type="checkbox"
          />

        <Button color="warning" size="sm" onClick={this.fluxCharge}>
          Flux
        </Button>
        <Row>
          <Col sm="12">
            <OutputField
              onClick={this.toggleStealth}
              alert={stealthField.state}
            >
              {stealthField.state ? "Activated" : "Deactivated"}
            </OutputField>
          </Col>
          <Col sm="12">
            <OutputField
              id="stealth-systems"
              style={sysStyle}
              alert={alertHigh}
              title={highSystemsText}
            >
              {highSystems.length === 0
                ? "No Alert Systems"
                : highSystems.length > 1
                  ? `${highSystems.length} Alert Systems`
                  : `${highSystems[0] && highSystems[0].name} (${Math.round(
                      highSystems[0] ? highSystems[0].stealthFactor * 100 : 0
                    )})`}
            </OutputField>
          </Col>
        </Row>
      </Container>
    );
  }
}

const STEALTH_QUERY = gql`
  query StealthField($simulatorId: ID!) {
    stealthField(simulatorId: $simulatorId) {
      id
      name
      state
      charge
      activated
      quadrants {
        fore
        aft
        port
        starboard
      }
    }
    systems(simulatorId: $simulatorId) {
      id
      name
      type
      stealthFactor
    }
  }
`;

export default graphql(STEALTH_QUERY, {
  options: ownProps => ({
    variables: {
      simulatorId: ownProps.simulator.id,
      names: ["Icons", "Pictures"]
    }
  })
})(withApollo(StealthFieldCore));
<|endoftext|>


<commit_before>define(
  ['./platform', './util'],
  function (platform, util) {

    "use strict";

    var logger = util.getLogger('getputdelete');

    var defaultContentType = 'application/octet-stream';

    function realDoCall(method, url, body, mimeType, token) {
      return util.makePromise(function(promise) {
        logger.info(method, url);
        var platformObj = {
          url: url,
          method: method,
          timeout: 10000,
          headers: {}
        };

        if(token) {
          platformObj.headers['Authorization'] = 'Bearer ' + token;
        }
        if(mimeType) {
          if(typeof(body) == 'object' && body instanceof ArrayBuffer) {
            mimeType += '; charset=binary';
          }
          platformObj.headers['Content-Type'] = mimeType;
        }

        platformObj.fields = {withCredentials: 'true'};
        if(method != 'GET') {
          platformObj.data = body;
        }

        platform.ajax(platformObj).
          then(function(data, headers) {
            var contentType = headers['content-type'] || defaultContentType;
            var mimeType = contentType.split(';')[0]

            if(contentType.match(/charset=binary/)) {
              data = util.rawToBuffer(data);
            } else if(mimeType === 'application/json') {
              try {
                data = JSON.parse(data);
              } catch(exc) {
                // ignore invalid JSON
              }
            }

            promise.fulfill(data, mimeType);
          }, function(error) {
            if(error === 404) {
              return promise.fulfill(undefined);
            } else if(error === 401 || error === 403) {
              error = 'unauthorized';
            };
            promise.fail(error);
          });
      });
    }

    var inProgress = 0;
    var maxInProgress = 10;
    var pendingQueue = [];

    function doCall() {
      var args = util.toArray(arguments);
      function finishOne() {
        inProgress--;
        if(pendingQueue.length > 0) {
          var c = pendingQueue.shift();
          doCall.apply(this, c.a).then(c.p.fulfill.bind(c.p), c.p.fail.bind(c.p));
        } else {
        }
      }
      if(inProgress < maxInProgress) {
        inProgress++;
        return realDoCall.apply(this, arguments).then(function(data, mimeType) {
          finishOne();
          return util.makePromise(function(p) { p.fulfill(data, mimeType); });
        }, function(error) {
          finishOne();
          throw error;
        });
      } else {
        return util.makePromise(function(p) {
          pendingQueue.push({
            a: args,
            p: p
          });
        });
      }
    }

    function get(url, token) {
      return doCall('GET', url, null, null, token);
    }

    function put(url, value, mimeType, token) {
      if(! (typeof(value) === 'string' || (typeof(value) === 'object' &&
                                           value instanceof ArrayBuffer))) {
        throw new Error("invalid value given to PUT, only strings or ArrayBuffers allowed, got " + typeof(value));
      }
      return doCall('PUT', url, value, mimeType, token);
    }

    function set(url, valueStr, mimeType, token) {
      if(typeof(valueStr) == 'undefined') {
        return doCall('DELETE', url, null, null, token);
      } else {
        return put(url, valueStr, mimeType, token);
      }
    }

    // Namespace: getputdelete
    return {
      //
      // Method: get
      //
      // Send a GET request to a given path.
      //
      // Parameters:
      //   url      - url to send request to
      //   token    - bearer token used to authorize the request
      //   callback - callback called to signal success or failure
      //
      // Callback parameters:
      //   err      - error message(s). if no error occured, err is null.
      //   data     - raw response data
      //   mimeType - value of the response's Content-Type header. If none was returned, this defaults to application/octet-stream.
      get:    get,

      //
      // Method: set
      //
      // Send a PUT or DELETE request to given path.
      //
      // Parameters:
      //   url      - url to send request to
      //   data     - optional data to send. if data is undefined (not null!), a DELETE request is used.
      //   mimeType - MIME type to set for the data via the Content-Type header. Only relevant for PUT.
      //   token    - bearer token used to authorize the request
      //   callback - callback called to signal success or failure
      //
      // Callback parameters:
      //   err      - error message(s). if no error occured, err is null.
      //   data     - raw response data
      //   mimeType - value of the response's Content-Type header. If none was returned, this defaults to application/octet-stream.
      //
      set:    set
    };
});
<commit_msg>timeout handling for getputdelete<commit_after>define(
  ['./platform', './util'],
  function (platform, util) {

    "use strict";

    var logger = util.getLogger('getputdelete');

    var defaultContentType = 'application/octet-stream';

    var timeout = 10000;

    function realDoCall(method, url, body, mimeType, token) {
      return util.makePromise(function(promise) {
        logger.info(method, url);
        var platformObj = {
          url: url,
          method: method,
          timeout: timeout,
          headers: {}
        };

        if(token) {
          platformObj.headers['Authorization'] = 'Bearer ' + token;
        }
        if(mimeType) {
          if(typeof(body) == 'object' && body instanceof ArrayBuffer) {
            mimeType += '; charset=binary';
          }
          platformObj.headers['Content-Type'] = mimeType;
        }

        platformObj.fields = {withCredentials: 'true'};
        if(method != 'GET') {
          platformObj.data = body;
        }

        platform.ajax(platformObj).
          then(function(data, headers) {
            var contentType = headers['content-type'] || defaultContentType;
            var mimeType = contentType.split(';')[0]

            if(contentType.match(/charset=binary/)) {
              data = util.rawToBuffer(data);
            } else if(mimeType === 'application/json') {
              try {
                data = JSON.parse(data);
              } catch(exc) {
                // ignore invalid JSON
              }
            }

            promise.fulfill(data, mimeType);
          }, function(error) {
            if(error === 404) {
              return promise.fulfill(undefined);
            } else if(error === 401 || error === 403) {
              error = 'unauthorized';
            };
            promise.fail(error);
          });
      });
    }

    var inProgress = 0;
    var maxInProgress = 10;
    var pendingQueue = [];

    function doCall() {
      var args = util.toArray(arguments);
      function finishOne() {
        inProgress--;
        if(pendingQueue.length > 0) {
          var c = pendingQueue.shift();
          doCall.apply(this, c.a).then(c.p.fulfill.bind(c.p), c.p.fail.bind(c.p));
        } else {
        }
      }
      if(inProgress < maxInProgress) {
        inProgress++;
        return realDoCall.apply(this, arguments).then(function(data, mimeType) {
          finishOne();
          return util.makePromise(function(p) { p.fulfill(data, mimeType); });
        }, function(error) {
          finishOne();
          throw error;
        });
      } else {
        return util.makePromise(function(p) {
          pendingQueue.push({
            a: args,
            p: p
          });
        });
      }
    }

    function get(url, token) {
      return doCall('GET', url, null, null, token);
    }

    function put(url, value, mimeType, token) {
      if(! (typeof(value) === 'string' || (typeof(value) === 'object' &&
                                           value instanceof ArrayBuffer))) {
        throw new Error("invalid value given to PUT, only strings or ArrayBuffers allowed, got " + typeof(value));
      }
      return doCall('PUT', url, value, mimeType, token);
    }

    function set(url, valueStr, mimeType, token) {
      if(typeof(valueStr) == 'undefined') {
        return doCall('DELETE', url, null, null, token);
      } else {
        return put(url, valueStr, mimeType, token);
      }
    }

    // Namespace: getputdelete
    return {
      //
      // Method: get
      //
      // Send a GET request to a given path.
      //
      // Parameters:
      //   url      - url to send request to
      //   token    - bearer token used to authorize the request
      //   callback - callback called to signal success or failure
      //
      // Callback parameters:
      //   err      - error message(s). if no error occured, err is null.
      //   data     - raw response data
      //   mimeType - value of the response's Content-Type header. If none was returned, this defaults to application/octet-stream.
      get:    get,

      //
      // Method: set
      //
      // Send a PUT or DELETE request to given path.
      //
      // Parameters:
      //   url      - url to send request to
      //   data     - optional data to send. if data is undefined (not null!), a DELETE request is used.
      //   mimeType - MIME type to set for the data via the Content-Type header. Only relevant for PUT.
      //   token    - bearer token used to authorize the request
      //   callback - callback called to signal success or failure
      //
      // Callback parameters:
      //   err      - error message(s). if no error occured, err is null.
      //   data     - raw response data
      //   mimeType - value of the response's Content-Type header. If none was returned, this defaults to application/octet-stream.
      //
      set:    set,

      setTimeout: function(t) {
        timeout = t;
      },

      getTimeout: function() {
        return timeout;
      }

    };
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'PopupView',
  extends: 'foam.ui.SimpleView',

  imports: [
    'document'
  ],

  topics: [
    'closed'
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function() {/*
      ^ {
        background: #999;
        box-shadow: 3px 3px 6px 0 gray;
        color: white;
        font-size: 18px;
        opacity: 0.9;
        padding: 20px;
        position: absolute;
        box-sizing: border-box;
      }
      */}
    })
  ],

  properties: [
    {
      name: 'view',
      // type: 'foam.ui.View',
    },
    {
      name: 'x'
    },
    {
      name: 'y'
    },
    {
      name: 'width'
    },
    {
      name: 'maxWidth'
    },
    {
      name: 'maxHeight'
    },
    {
      name: 'height'
    }
  ],

  methods: {
    open: function() {
      if ( this.$ ) return;
      var document = this.X.document;
      var div      = document.createElement('div');
      div.style.left = this.x + 'px';
      div.style.top  = this.y + 'px';
      if ( this.width )     div.style.width     = this.width     + 'px';
      if ( this.height )    div.style.height    = this.height    + 'px';
      if ( this.maxWidth )  div.style.maxWidth  = this.maxWidth  + 'px';
      if ( this.maxHeight ) div.style.maxHeight = this.maxHeight + 'px';
      div.style.position = 'absolute';
      div.id = this.id;
      div.innerHTML = this.view.toHTML();

      document.body.appendChild(div);
      this.view.initHTML();
    },

    openOn: function(parent) {
      var self     = this;
      var document = this.document;
      var bg       = document.createElement('div');
      var div      = document.createElement('div');

      bg.style.width = bg.style.height = '10000px';
      bg.style.opacity = 0;
      bg.style.position = 'fixed';
      bg.style.top = '0';
      bg.style.zIndex = 998;
      div.style.zIndex = 999;

      if ( ! this.y ) this.y = (parent.clientHeight - this.height)/2;
      if ( ! this.x ) this.x = (parent.clientWidth - this.width)/2;
      div.className = 'popup';
      div.style.left = this.x + 'px';
      div.style.top  = this.y + 'px';

      if ( this.width )     div.style.width     = this.width     + 'px';
      if ( this.height )    div.style.height    = this.height    + 'px';
      if ( this.maxWidth )  div.style.maxWidth  = this.maxWidth  + 'px';
      if ( this.maxHeight ) div.style.maxHeight = this.maxHeight + 'px';

      parent.style.position = 'relative';
      div.id = this.id;
      div.innerHTML = this.view.toHTML();

      document.body.appendChild(bg);
      bg.addEventListener('click', function() {
        div.remove();
        bg.remove();
        self.destroy();
        self.closed.pub();
      });

      parent.appendChild(div);
      this.view.initHTML();
    },

    close: function() {
      this.$ && this.$.remove();
    },

    destroy: function( isParentDestroyed ) {
      this.SUPER(isParentDestroyed);
      this.close();
      this.view.destroy();
    }
  }
});
<commit_msg>Porting PopupView.<commit_after>/**
 * @license
 * Copyright 2014 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.u2',
  name: 'PopupView',
  extends: 'foam.ui.SimpleView',

  imports: [
    'document'
  ],

  topics: [
    'closed'
  ],

  axioms: [
    foam.u2.CSS.create({
      code: function() {/*
      ^ {
        background: #999;
        box-shadow: 3px 3px 6px 0 gray;
        color: white;
        font-size: 18px;
        opacity: 0.9;
        padding: 20px;
        position: absolute;
        box-sizing: border-box;
      }
      */}
    })
  ],

  properties: [
    {
      name: 'view',
      // type: 'foam.ui.View',
    },
    {
      name: 'x'
    },
    {
      name: 'y'
    },
    {
      name: 'width'
    },
    {
      name: 'maxWidth'
    },
    {
      name: 'maxHeight'
    },
    {
      name: 'height'
    }
  ],

  methods: {
    open: function() {
      if ( this.$ ) return;
      var document = this.X.document;
      var div      = document.createElement('div');
      div.style.left = this.x + 'px';
      div.style.top  = this.y + 'px';
      if ( this.width )     div.style.width     = this.width     + 'px';
      if ( this.height )    div.style.height    = this.height    + 'px';
      if ( this.maxWidth )  div.style.maxWidth  = this.maxWidth  + 'px';
      if ( this.maxHeight ) div.style.maxHeight = this.maxHeight + 'px';
      div.style.position = 'absolute';
      div.id = this.id;
      div.innerHTML = this.view.toHTML();

      document.body.appendChild(div);
      this.view.initHTML();
    },

    openOn: function(parent) {
      var self     = this;
      var document = this.document;

      var div = this.E('div').
        cssClass(this.myCls()).
        style({
          zIndex: 999
        });

      var bg = this.E('div').
        style({
          width: '10000px',
          height: '10000px',
          opacity: 0,
          top: 0,
          zIndex: 998
        });

      if ( ! this.y ) this.y = (parent.clientHeight - this.height)/2;
      if ( ! this.x ) this.x = (parent.clientWidth  - this.width )/2;

      div.style({
        left: this.x + 'px',
        top: this.y + 'px'
      });

      if ( this.width )     div.style({width    : this.width     + 'px'});
      if ( this.height )    div.style({height   : this.height    + 'px'});
      if ( this.maxWidth )  div.style({maxWidth : this.maxWidth  + 'px'});
      if ( this.maxHeight ) div.style({maxHeight: this.maxHeight + 'px'});

      parent.style({position: 'relative'});

      div.id = this.id;
      div.innerHTML = this.view.toHTML();

      document.body.appendChild(bg);
      bg.on('click', function() {
        div.remove();
        bg.remove();
        self.destroy();
        self.closed.pub();
      });

      parent.appendChild(div);
      this.view.initHTML();
    },

    close: function() {
      this.remove();
    },

    destroy: function( isParentDestroyed ) {
      this.SUPER(isParentDestroyed);
      this.close();
      this.view.destroy();
    }
  }
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO: Add datalist support.

foam.CLASS({
  package: 'foam.u2.tag',
  name: 'Input',
  extends: 'foam.u2.View',

  properties: [
    [ 'nodeName', 'input' ],
    {
      name: 'data',
      assertValue: function(d) {
        // TODO: put back
        this.assert(! d || typeof d === 'string' || typeof d === 'number' || typeof d === 'boolean' || foam.Date.is(d), 'Set Input data to non-primitive.');
      }
    },
    {
      class: 'Boolean',
      name: 'onKey',
      attribute: true,
      // documentation: 'When true, $$DOC{ref:".data"} is updated on every keystroke, rather than on blur.'
    }
  ],

  templates: [
    function CSS() {/*
      ^:read-only { border-width: 0; }
    */}
  ],

  methods: [
    function initE() {
      this.cssClass(this.myCls());

      this.attrSlot(null, this.onKey ? 'input' : null).linkFrom(this.data$);
    },

    function updateMode_(mode) {
      // TODO: make sure that DOM is updated if values don't change
      this.setAttribute('readonly', mode === 'ro');
      this.setAttribute('disabled', mode === 'disabled');
    }
  ]
});
<commit_msg>Add template link() method to Input.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// TODO: Add datalist support.

foam.CLASS({
  package: 'foam.u2.tag',
  name: 'Input',
  extends: 'foam.u2.View',

  properties: [
    [ 'nodeName', 'input' ],
    {
      name: 'data',
      assertValue: function(d) {
        // TODO: put back
        this.assert(! d || typeof d === 'string' || typeof d === 'number' || typeof d === 'boolean' || foam.Date.is(d), 'Set Input data to non-primitive.');
      }
    },
    {
      class: 'Boolean',
      name: 'onKey',
      attribute: true,
      // documentation: 'When true, $$DOC{ref:".data"} is updated on every keystroke, rather than on blur.'
    }
  ],

  templates: [
    function CSS() {/*
      ^:read-only { border-width: 0; }
    */}
  ],

  methods: [
    function initE() {
      this.cssClass(this.myCls());
      this.link();
    },

    function link() {
      // Template method, can be overriden by sub-classes
      this.attrSlot(null, this.onKey ? 'input' : null).linkFrom(this.data$);
    },

    function updateMode_(mode) {
      // TODO: make sure that DOM is updated if values don't change
      this.setAttribute('readonly', mode === 'ro');
      this.setAttribute('disabled', mode === 'disabled');
    }
  ]
});
<|endoftext|>