<commit_before>NEWSBLUR.ReaderStatistics = function(feed_id, options) {
    var defaults = {};

    this.options = $.extend({}, defaults, options);
    this.model = NEWSBLUR.assets;
    this.feed_id = feed_id;
    this.feed = this.model.get_feed(feed_id);
    this.feeds = this.model.get_feeds();
    this.first_load = true;
    this.runner();
};

NEWSBLUR.ReaderStatistics.prototype = new NEWSBLUR.Modal;
NEWSBLUR.ReaderStatistics.prototype.constructor = NEWSBLUR.ReaderStatistics;

_.extend(NEWSBLUR.ReaderStatistics.prototype, {

    runner: function() {
        var self = this;

        this.initialize_feed(this.feed_id);
        this.make_modal();
        this.open_modal();
        setTimeout(function() {
            self.get_stats();
        }, 50);

        this.$modal.bind('click', $.rescope(this.handle_click, this));
        this.$modal.bind('change', $.rescope(this.handle_change, this));
    },

    make_modal: function() {
        var self = this;

        this.$modal = $.make('div', { className: 'NB-modal-statistics NB-modal' }, [
            $.make('div', { className: 'NB-modal-feed-chooser-container'}, [
                this.make_feed_chooser()
            ]),
            $.make('div', { className: 'NB-modal-loading' }),
            $.make('h2', { className: 'NB-modal-title' }, 'Statistics & History'),
            $.make('h2', { className: 'NB-modal-subtitle' }, [
                $.make('img', { className: 'NB-modal-feed-image feed_favicon', src: $.favicon(this.feed) }),
                $.make('div', { className: 'NB-modal-feed-heading' }, [
                    $.make('span', { className: 'NB-modal-feed-title' }, this.feed.get('feed_title')),
                    $.make('span', { className: 'NB-modal-feed-subscribers ' + (_.isUndefined(this.feed.get('num_subscribers')) && 'NB-hidden') }, Inflector.pluralize(' subscriber', this.feed.get('num_subscribers'), true))
                ])
            ]),
            $.make('div', { className: 'NB-modal-statistics-info' })
        ]);

        var $stats = this.make_stats({
            'last_update': '',
            'next_update': '',
            'loading': true
        });
        $('.NB-modal-statistics-info', this.$modal).replaceWith($stats);
    },

    get_stats: function() {
        var $loading = $('.NB-modal-loading', this.$modal);
        $loading.addClass('NB-active');

        var statistics_fn = this.options.social_feed ? this.model.get_social_statistics : this.model.get_feed_statistics;
        statistics_fn.call(this.model, this.feed_id, $.rescope(this.populate_stats, this));
    },

    populate_stats: function(s, data) {
        var self = this;

        NEWSBLUR.log(['Stats', data]);

        var $loading = $('.NB-modal-loading', this.$modal);
        $loading.removeClass('NB-active');

        var $stats = this.make_stats(data);
        $('.NB-modal-statistics-info', this.$modal).replaceWith($stats);
        $(".NB-modal-feed-subscribers", this.$modal).removeClass('NB-hidden').text(Inflector.pluralize(' subscriber', data.num_subscribers, true));
        var $expires = $(".NB-statistics-push-expires", this.$modal);
        if (data['lease_expires']) {
            $expires.html("Push expires: " + data['lease_expires']);
        } else {
            $expires.html("");
        }
        setTimeout(function() {
            self.make_charts(data);
        }, this.first_load ? 200 : 50);

        setTimeout(function() {
            $.modal.impl.resize(self.$modal);
        }, 100);
    },

    make_stats: function(data) {
        var update_interval = NEWSBLUR.utils.calculate_update_interval(data['update_interval_minutes']);
        var premium_update_interval = NEWSBLUR.utils.calculate_update_interval(data['premium_update_interval_minutes']);

        var $stats = $.make('div', { className: 'NB-modal-statistics-info' }, [
            (!this.options.social_feed && $.make('div', { className: 'NB-statistics-stat NB-statistics-updates'}, [
              $.make('div', { className: 'NB-statistics-update'}, [
                $.make('div', { className: 'NB-statistics-label' }, 'Last Update'),
                $.make('div', { className: 'NB-statistics-count' }, ' ' + (data['last_update'] && (data['last_update'] + ' ago')))
              ]),
              $.make('div', { className: 'NB-statistics-update'}, [
                (data['push'] && $.make('div', { className: 'NB-statistics-realtime' }, [
                    $.make('div', { className: 'NB-statistics-label' }, [
                        $.make('img', { src: NEWSBLUR.Globals.MEDIA_URL + '/img/reader/realtime_spinner.gif', className: 'NB-statisics-realtime-spinner' }),
                        'Real-time'
                    ]),
                    $.make('div', { className: 'NB-statistics-count' }, 'Supplemented by checks every ' + update_interval)
                ])),
                (!data['push'] && $.make('div', [
                    $.make('div', { className: 'NB-statistics-label' }, 'Every'),
                    $.make('div', { className: 'NB-statistics-count' }, update_interval)
                ]))
              ]),
              $.make('div', { className: 'NB-statistics-update'}, [
                $.make('div', { className: 'NB-statistics-label' }, 'Next Update'),
                (data.active && $.make('div', { className: 'NB-statistics-count' }, ' ' + (data['next_update'] && ('in ' + data['next_update'])))),
                (!data.active && !data.loading && $.make('div', { className: 'NB-statistics-count' }, "Not active"))
              ]),
              ((data.average_stories_per_month == 0 || data.stories_last_month == 0) &&
               data.update_interval_minutes > 60 &&
                  $.make('div', { className: 'NB-statistics-update-explainer' }, [
                    $.make('b', 'Why so infrequently?'),
                    'This site has published zero stories in the past month or has averaged less than a single story a month. As soon as it starts publishing at least once a month, it will automatically fetch more frequently.'
                  ])),
              (data.errors_since_good &&
                  $.make('div', { className: 'NB-statistics-update-explainer' }, [
                    $.make('b', 'Why is the next update not at the normal rate?'),
                    'This site has is throwing exceptions and is not in a healthy state. Look at the bottom of this dialog to see the exact status codes for the feed. The more errors for the feed, the longer time taken between fetches.'
                  ])),
              (!NEWSBLUR.Globals.is_premium && $.make('div', { className: 'NB-statistics-premium-stats' }, [
                  $.make('div', { className: 'NB-statistics-update'}, [
                    $.make('div', { className: 'NB-statistics-label' }, [
                        'If you went ',
                        $.make('a', { href: '#', className: 'NB-premium-link NB-splash-link' }, 'premium'),
                        ', ',
                        $.make('br'),
                        'this site would update every'
                    ]),
                    $.make('div', { className: 'NB-statistics-count' }, premium_update_interval),
                    (data['push'] && $.make('div', { className: 'NB-statistics-realtime' }, [
                        $.make('div', { className: 'NB-statistics-label' }, [
                            'but it wouldn\'t matter because',
                            $.make('br'),
                            'this site is already in real-time'
                        ])
                    ]))
                  ])
              ]))
            ])),
            $.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [
                $.make('div', { className: 'NB-statistics-history-stat' }, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Stories per month')
                ]),
                $.make('canvas', { id: 'NB-statistics-history-chart', className: 'NB-statistics-history-chart' })
            ]),
            (data.classifier_counts && $.make('div', { className: 'NB-statistics-state NB-statistics-classifiers' }, [
                this.make_classifier_count('tag', data.classifier_counts['tag']),
                this.make_classifier_count('author', data.classifier_counts['author']),
                this.make_classifier_count('title', data.classifier_counts['title']),
                this.make_classifier_count('feed', data.classifier_counts['feed'])
            ])),
            (!this.options.social_feed && $.make('div', { className: 'NB-statistics-stat NB-statistics-fetches'}, [
                $.make('div', { className: 'NB-statistics-fetches-half'}, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Feed Fetch'),
                    $.make('div', this.make_history(data, 'feed_fetch'))
                ]),
                $.make('div', { className: 'NB-statistics-fetches-half'}, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Page Fetch'),
                    $.make('div', this.make_history(data, 'page_fetch'))
                ]),
                $.make('div', { className: 'NB-statistics-fetches-half'}, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Feed Push'),
                    $.make('div', this.make_history(data, 'feed_push')),
                    $.make('div', { className: 'NB-statistics-label NB-statistics-push-expires' })
                ])
            ]))
        ]);

        return $stats;
    },

    make_classifier_count: function(facet, data) {
        var self = this;
        if (!data) return;

        var $facets = $.make('div', { className: 'NB-statistics-facets' }, [
            $.make('div', { className: 'NB-statistics-facet-title' }, Inflector.pluralize(facet, data.length))
        ]);

        var max = 10;
        _.each(data, function(v) {
            if (v.pos > max || v.neg > max) {
                max = Math.max(v.pos, v.neg);
            }
        });

        var max_width = 100;
        var multiplier = max_width / parseFloat(max, 10);
        var calculate_width = function(count) {
            return Math.max(1, multiplier * count);
        };

        _.each(data, function(counts) {
            var pos = counts.pos || 0;
            var neg = counts.neg || 0;
            var key = counts[facet];
            if (facet == 'feed') {
                key = [$.make('div', [
                    $.make('img', { className: 'NB-modal-feed-image feed_favicon', src: $.favicon(self.feed) }),
                    $.make('span', { className: 'NB-modal-feed-title' }, self.feed.get('feed_title'))
                ])];
            }
            if (!key || (!pos && !neg)) return;
            var $facet = $.make('div', { className: 'NB-statistics-facet' }, [
                (pos && $.make('div', { className: 'NB-statistics-facet-pos' }, [
                    $.make('div', { className: 'NB-statistics-facet-bar' }).css('width', calculate_width(pos)),
                    $.make('div', { className: 'NB-statistics-facet-count' }, Inflector.pluralize(' like', pos, true)).css('margin-left', calculate_width(pos)+5)
                ])),
                (neg && $.make('div', { className: 'NB-statistics-facet-neg' }, [
                    $.make('div', { className: 'NB-statistics-facet-bar' }).css('width', calculate_width(neg)),
                    $.make('div', { className: 'NB-statistics-facet-count' }, Inflector.pluralize(' dislike', neg, true)).css('margin-right', calculate_width(neg)+5)
                ])),
                $.make('div', { className: 'NB-statistics-facet-separator' }),
                $.make('div', { className: 'NB-statistics-facet-name' }, key)
            ]);
            $facets.append($facet);
        });

        return $facets;
    },

    make_history: function(data, fetch_type) {
        var fetches = data[fetch_type+'_history'];
        var $history;

        if (!fetches || !fetches.length) {
            $history = $.make('div', { className: 'NB-history-empty' }, "Nothing recorded.");
        } else {
            $history = _.map(fetches, function(fetch) {
                var feed_ok = _.contains([200, 304], fetch.status_code) || !fetch.status_code;
                var status_class = feed_ok ? ' NB-ok ' : ' NB-errorcode ';
                return $.make('div', { className: 'NB-history-fetch' + status_class, title: feed_ok ? '' : fetch.exception }, [
                    $.make('div', { className: 'NB-history-fetch-date' }, fetch.fetch_date || fetch.push_date),
                    $.make('div', { className: 'NB-history-fetch-message' }, [
                        fetch.message,
                        (fetch.status_code && $.make('div', { className: 'NB-history-fetch-code' }, ' ('+fetch.status_code+')'))
                    ])
                ]);
            });
        }

        return $history;
    },

    make_charts: function(data) {
        var labels = _.map(data['story_count_history'], function(date) {
            var date_matched = date[0].match(/(\d{4})-(\d{1,2})/);
            var date = (new Date(parseInt(date_matched[1], 10), parseInt(date_matched[2],10)-1));
            return NEWSBLUR.utils.shortMonthNames[date.getMonth()] + " " + date.getUTCFullYear();
        });
        if (labels.length > 16) {
            var cut_size = Math.round(labels.length / 16.0);
            labels = _.map(labels, function(label, c) {
                if ((c % cut_size) == 0) return label;
                return "";
            });
        }
        var values = _.map(data['story_count_history'], function(date) {
            return date[1];
        });
        var points = {
            labels: labels,
            datasets: [
                {
                    fillColor : "rgba(151,187,205,0.5)",
                    strokeColor : "rgba(151,187,205,1)",
                    pointColor : "rgba(151,187,205,1)",
                    pointStrokeColor : "#fff",
                    data : values
                }
            ]
        };
        var $plot = $(".NB-statistics-history-chart");
        var width = $plot.width();
        var height = $plot.height();
        $plot.attr('width', width);
        $plot.attr('height', height);
        var myLine = new Chart($plot.get(0).getContext("2d")).Line(points, {
            scaleLabel : "<%= Math.round(value) %>"
        });
    },

    close_and_load_premium: function() {
      this.close(function() {
          NEWSBLUR.reader.open_feedchooser_modal();
      });
    },

    // ===========
    // = Actions =
    // ===========

    handle_change: function(elem, e) {
        var self = this;

        $.targetIs(e, { tagSelector: '.NB-modal-feed-chooser' }, function($t, $p){
            var feed_id = $t.val();
            self.first_load = false;
            self.initialize_feed(feed_id);
            self.get_stats();
        });
    },

    handle_click: function(elem, e) {
        var self = this;

        $.targetIs(e, { tagSelector: '.NB-premium-link' }, function($t, $p) {
            e.preventDefault();
            self.close_and_load_premium();
        });
    }

});<commit_msg>Fixing up push expires text.<commit_after>NEWSBLUR.ReaderStatistics = function(feed_id, options) {
    var defaults = {};

    this.options = $.extend({}, defaults, options);
    this.model = NEWSBLUR.assets;
    this.feed_id = feed_id;
    this.feed = this.model.get_feed(feed_id);
    this.feeds = this.model.get_feeds();
    this.first_load = true;
    this.runner();
};

NEWSBLUR.ReaderStatistics.prototype = new NEWSBLUR.Modal;
NEWSBLUR.ReaderStatistics.prototype.constructor = NEWSBLUR.ReaderStatistics;

_.extend(NEWSBLUR.ReaderStatistics.prototype, {

    runner: function() {
        var self = this;

        this.initialize_feed(this.feed_id);
        this.make_modal();
        this.open_modal();
        setTimeout(function() {
            self.get_stats();
        }, 50);

        this.$modal.bind('click', $.rescope(this.handle_click, this));
        this.$modal.bind('change', $.rescope(this.handle_change, this));
    },

    make_modal: function() {
        var self = this;

        this.$modal = $.make('div', { className: 'NB-modal-statistics NB-modal' }, [
            $.make('div', { className: 'NB-modal-feed-chooser-container'}, [
                this.make_feed_chooser()
            ]),
            $.make('div', { className: 'NB-modal-loading' }),
            $.make('h2', { className: 'NB-modal-title' }, 'Statistics & History'),
            $.make('h2', { className: 'NB-modal-subtitle' }, [
                $.make('img', { className: 'NB-modal-feed-image feed_favicon', src: $.favicon(this.feed) }),
                $.make('div', { className: 'NB-modal-feed-heading' }, [
                    $.make('span', { className: 'NB-modal-feed-title' }, this.feed.get('feed_title')),
                    $.make('span', { className: 'NB-modal-feed-subscribers ' + (_.isUndefined(this.feed.get('num_subscribers')) && 'NB-hidden') }, Inflector.pluralize(' subscriber', this.feed.get('num_subscribers'), true))
                ])
            ]),
            $.make('div', { className: 'NB-modal-statistics-info' })
        ]);

        var $stats = this.make_stats({
            'last_update': '',
            'next_update': '',
            'loading': true
        });
        $('.NB-modal-statistics-info', this.$modal).replaceWith($stats);
    },

    get_stats: function() {
        var $loading = $('.NB-modal-loading', this.$modal);
        $loading.addClass('NB-active');

        var statistics_fn = this.options.social_feed ? this.model.get_social_statistics : this.model.get_feed_statistics;
        statistics_fn.call(this.model, this.feed_id, $.rescope(this.populate_stats, this));
    },

    populate_stats: function(s, data) {
        var self = this;

        NEWSBLUR.log(['Stats', data]);

        var $loading = $('.NB-modal-loading', this.$modal);
        $loading.removeClass('NB-active');

        var $stats = this.make_stats(data);
        $('.NB-modal-statistics-info', this.$modal).replaceWith($stats);
        $(".NB-modal-feed-subscribers", this.$modal).removeClass('NB-hidden').text(Inflector.pluralize(' subscriber', data.num_subscribers, true));
        var $expires = $(".NB-statistics-push-expires", this.$modal);
        if (data['push_expires']) {
            $expires.html("Push expires: " + data['push_expires']);
        } else {
            $expires.html("");
        }
        setTimeout(function() {
            self.make_charts(data);
        }, this.first_load ? 200 : 50);

        setTimeout(function() {
            $.modal.impl.resize(self.$modal);
        }, 100);
    },

    make_stats: function(data) {
        var update_interval = NEWSBLUR.utils.calculate_update_interval(data['update_interval_minutes']);
        var premium_update_interval = NEWSBLUR.utils.calculate_update_interval(data['premium_update_interval_minutes']);

        var $stats = $.make('div', { className: 'NB-modal-statistics-info' }, [
            (!this.options.social_feed && $.make('div', { className: 'NB-statistics-stat NB-statistics-updates'}, [
              $.make('div', { className: 'NB-statistics-update'}, [
                $.make('div', { className: 'NB-statistics-label' }, 'Last Update'),
                $.make('div', { className: 'NB-statistics-count' }, ' ' + (data['last_update'] && (data['last_update'] + ' ago')))
              ]),
              $.make('div', { className: 'NB-statistics-update'}, [
                (data['push'] && $.make('div', { className: 'NB-statistics-realtime' }, [
                    $.make('div', { className: 'NB-statistics-label' }, [
                        $.make('img', { src: NEWSBLUR.Globals.MEDIA_URL + '/img/reader/realtime_spinner.gif', className: 'NB-statisics-realtime-spinner' }),
                        'Real-time'
                    ]),
                    $.make('div', { className: 'NB-statistics-count' }, 'Supplemented by checks every ' + update_interval)
                ])),
                (!data['push'] && $.make('div', [
                    $.make('div', { className: 'NB-statistics-label' }, 'Every'),
                    $.make('div', { className: 'NB-statistics-count' }, update_interval)
                ]))
              ]),
              $.make('div', { className: 'NB-statistics-update'}, [
                $.make('div', { className: 'NB-statistics-label' }, 'Next Update'),
                (data.active && $.make('div', { className: 'NB-statistics-count' }, ' ' + (data['next_update'] && ('in ' + data['next_update'])))),
                (!data.active && !data.loading && $.make('div', { className: 'NB-statistics-count' }, "Not active"))
              ]),
              ((data.average_stories_per_month == 0 || data.stories_last_month == 0) &&
               data.update_interval_minutes > 60 &&
                  $.make('div', { className: 'NB-statistics-update-explainer' }, [
                    $.make('b', 'Why so infrequently?'),
                    'This site has published zero stories in the past month or has averaged less than a single story a month. As soon as it starts publishing at least once a month, it will automatically fetch more frequently.'
                  ])),
              (data.errors_since_good &&
                  $.make('div', { className: 'NB-statistics-update-explainer' }, [
                    $.make('b', 'Why is the next update not at the normal rate?'),
                    'This site has is throwing exceptions and is not in a healthy state. Look at the bottom of this dialog to see the exact status codes for the feed. The more errors for the feed, the longer time taken between fetches.'
                  ])),
              (!NEWSBLUR.Globals.is_premium && $.make('div', { className: 'NB-statistics-premium-stats' }, [
                  $.make('div', { className: 'NB-statistics-update'}, [
                    $.make('div', { className: 'NB-statistics-label' }, [
                        'If you went ',
                        $.make('a', { href: '#', className: 'NB-premium-link NB-splash-link' }, 'premium'),
                        ', ',
                        $.make('br'),
                        'this site would update every'
                    ]),
                    $.make('div', { className: 'NB-statistics-count' }, premium_update_interval),
                    (data['push'] && $.make('div', { className: 'NB-statistics-realtime' }, [
                        $.make('div', { className: 'NB-statistics-label' }, [
                            'but it wouldn\'t matter because',
                            $.make('br'),
                            'this site is already in real-time'
                        ])
                    ]))
                  ])
              ]))
            ])),
            $.make('div', { className: 'NB-statistics-stat NB-statistics-history'}, [
                $.make('div', { className: 'NB-statistics-history-stat' }, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Stories per month')
                ]),
                $.make('canvas', { id: 'NB-statistics-history-chart', className: 'NB-statistics-history-chart' })
            ]),
            (data.classifier_counts && $.make('div', { className: 'NB-statistics-state NB-statistics-classifiers' }, [
                this.make_classifier_count('tag', data.classifier_counts['tag']),
                this.make_classifier_count('author', data.classifier_counts['author']),
                this.make_classifier_count('title', data.classifier_counts['title']),
                this.make_classifier_count('feed', data.classifier_counts['feed'])
            ])),
            (!this.options.social_feed && $.make('div', { className: 'NB-statistics-stat NB-statistics-fetches'}, [
                $.make('div', { className: 'NB-statistics-fetches-half'}, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Feed Fetch'),
                    $.make('div', this.make_history(data, 'feed_fetch'))
                ]),
                $.make('div', { className: 'NB-statistics-fetches-half'}, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Page Fetch'),
                    $.make('div', this.make_history(data, 'page_fetch'))
                ]),
                $.make('div', { className: 'NB-statistics-fetches-half'}, [
                    $.make('div', { className: 'NB-statistics-label' }, 'Feed Push'),
                    $.make('div', this.make_history(data, 'feed_push')),
                    $.make('div', { className: 'NB-statistics-label NB-statistics-push-expires' })
                ])
            ]))
        ]);

        return $stats;
    },

    make_classifier_count: function(facet, data) {
        var self = this;
        if (!data) return;

        var $facets = $.make('div', { className: 'NB-statistics-facets' }, [
            $.make('div', { className: 'NB-statistics-facet-title' }, Inflector.pluralize(facet, data.length))
        ]);

        var max = 10;
        _.each(data, function(v) {
            if (v.pos > max || v.neg > max) {
                max = Math.max(v.pos, v.neg);
            }
        });

        var max_width = 100;
        var multiplier = max_width / parseFloat(max, 10);
        var calculate_width = function(count) {
            return Math.max(1, multiplier * count);
        };

        _.each(data, function(counts) {
            var pos = counts.pos || 0;
            var neg = counts.neg || 0;
            var key = counts[facet];
            if (facet == 'feed') {
                key = [$.make('div', [
                    $.make('img', { className: 'NB-modal-feed-image feed_favicon', src: $.favicon(self.feed) }),
                    $.make('span', { className: 'NB-modal-feed-title' }, self.feed.get('feed_title'))
                ])];
            }
            if (!key || (!pos && !neg)) return;
            var $facet = $.make('div', { className: 'NB-statistics-facet' }, [
                (pos && $.make('div', { className: 'NB-statistics-facet-pos' }, [
                    $.make('div', { className: 'NB-statistics-facet-bar' }).css('width', calculate_width(pos)),
                    $.make('div', { className: 'NB-statistics-facet-count' }, Inflector.pluralize(' like', pos, true)).css('margin-left', calculate_width(pos)+5)
                ])),
                (neg && $.make('div', { className: 'NB-statistics-facet-neg' }, [
                    $.make('div', { className: 'NB-statistics-facet-bar' }).css('width', calculate_width(neg)),
                    $.make('div', { className: 'NB-statistics-facet-count' }, Inflector.pluralize(' dislike', neg, true)).css('margin-right', calculate_width(neg)+5)
                ])),
                $.make('div', { className: 'NB-statistics-facet-separator' }),
                $.make('div', { className: 'NB-statistics-facet-name' }, key)
            ]);
            $facets.append($facet);
        });

        return $facets;
    },

    make_history: function(data, fetch_type) {
        var fetches = data[fetch_type+'_history'];
        var $history;

        if (!fetches || !fetches.length) {
            $history = $.make('div', { className: 'NB-history-empty' }, "Nothing recorded.");
        } else {
            $history = _.map(fetches, function(fetch) {
                var feed_ok = _.contains([200, 304], fetch.status_code) || !fetch.status_code;
                var status_class = feed_ok ? ' NB-ok ' : ' NB-errorcode ';
                return $.make('div', { className: 'NB-history-fetch' + status_class, title: feed_ok ? '' : fetch.exception }, [
                    $.make('div', { className: 'NB-history-fetch-date' }, fetch.fetch_date || fetch.push_date),
                    $.make('div', { className: 'NB-history-fetch-message' }, [
                        fetch.message,
                        (fetch.status_code && $.make('div', { className: 'NB-history-fetch-code' }, ' ('+fetch.status_code+')'))
                    ])
                ]);
            });
        }

        return $history;
    },

    make_charts: function(data) {
        var labels = _.map(data['story_count_history'], function(date) {
            var date_matched = date[0].match(/(\d{4})-(\d{1,2})/);
            var date = (new Date(parseInt(date_matched[1], 10), parseInt(date_matched[2],10)-1));
            return NEWSBLUR.utils.shortMonthNames[date.getMonth()] + " " + date.getUTCFullYear();
        });
        if (labels.length > 16) {
            var cut_size = Math.round(labels.length / 16.0);
            labels = _.map(labels, function(label, c) {
                if ((c % cut_size) == 0) return label;
                return "";
            });
        }
        var values = _.map(data['story_count_history'], function(date) {
            return date[1];
        });
        var points = {
            labels: labels,
            datasets: [
                {
                    fillColor : "rgba(151,187,205,0.5)",
                    strokeColor : "rgba(151,187,205,1)",
                    pointColor : "rgba(151,187,205,1)",
                    pointStrokeColor : "#fff",
                    data : values
                }
            ]
        };
        var $plot = $(".NB-statistics-history-chart");
        var width = $plot.width();
        var height = $plot.height();
        $plot.attr('width', width);
        $plot.attr('height', height);
        var myLine = new Chart($plot.get(0).getContext("2d")).Line(points, {
            scaleLabel : "<%= Math.round(value) %>"
        });
    },

    close_and_load_premium: function() {
      this.close(function() {
          NEWSBLUR.reader.open_feedchooser_modal();
      });
    },

    // ===========
    // = Actions =
    // ===========

    handle_change: function(elem, e) {
        var self = this;

        $.targetIs(e, { tagSelector: '.NB-modal-feed-chooser' }, function($t, $p){
            var feed_id = $t.val();
            self.first_load = false;
            self.initialize_feed(feed_id);
            self.get_stats();
        });
    },

    handle_click: function(elem, e) {
        var self = this;

        $.targetIs(e, { tagSelector: '.NB-premium-link' }, function($t, $p) {
            e.preventDefault();
            self.close_and_load_premium();
        });
    }

});<|endoftext|>


<commit_before>function choiceLineGraph(nationalData) {
  var parseDate = d3.time.format("%Y-%m-%d");
  nationalData.forEach(function(choice) {
    choice.minDate = d3.min(choice.attributes.responses.map(function(d) { return parseDate.parse(d.date); }));
    choice.maxDate = d3.max(choice.attributes.responses.map(function(d) { return parseDate.parse(d.date); }));
    choice.maxPercentage = d3.max(choice.attributes.responses.map(function(d) { return d.percentage; }));
  });
  var graph = d3.select("#line-graph");
  var WIDTH = 1000;
  var HEIGHT = 500;
  var MARGINS = {
      top: 20,
      right: 20,
      bottom: 26,
      left: 50
    };
  var Xscale = d3.time.scale().range([MARGINS.left, WIDTH - MARGINS.right]);
  Xscale.domain([
      d3.min(nationalData.map(function(choice) { return choice.minDate; })),
      d3.max(nationalData.map(function(choice) { return choice.maxDate; }))
    ]);
  var Yscale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]);
  Yscale.domain([
      0,
      d3.max(nationalData.map(function(choice) { return parseFloat(choice.maxPercentage) })) + 5
    ]);

  var colorScale = d3.scale.category20();

  var Xaxis = d3.svg.axis()
      .scale(Xscale)
      .orient("bottom");

  var Yaxis = d3.svg.axis()
      .scale(Yscale)
      .orient("left");

  graph.append("svg:g")
    .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
    .call(Xaxis);

  graph.append("svg:g")
    .attr("transform", "translate(" + (MARGINS.left) + ",0)")
    .call(Yaxis);

  var lineGen = d3.svg.line()
    .x(function(d) {
      return Xscale(parseDate.parse(d.date));
    })
    .y(function(d) {
      return Yscale(parseFloat(d.percentage));
    })
    .interpolate('basis');

  nationalData.forEach(function(choice, index) {
    var line = graph.append("svg:path")
      .attr('d', lineGen(choice.attributes.responses))
      .attr("stroke", colorScale(index))
      .attr("stroke-width", 3)
      .attr("fill", "none");

    line.transition()
      .duration(4000)
      .delay(500 + index * 500)
      .ease("linear")
      .attr('d', lineGen(choice.attributes.responses));
  });

  var yaxiscords = d3.range(26, HEIGHT, 45.4);
  var xaxiscords = d3.range(50, WIDTH, 25);

  graph.selectAll("line.vertical")
    .data(xaxiscords)
    .enter().append("svg:line")
    .attr("x1", function(d) {return d;})
    .attr("y1", 26)
    .attr("x2", function(d) {return d;})
    .attr("y2", HEIGHT - 25)
    .style("stroke", "rgb(192,192,192)")
    .style("opacity", 0.3)
    .style("stroke-width", 2);

  graph.selectAll("line.horizontal")
    .data(yaxiscords)
    .enter().append("svg:line")
    .attr("x1", 50)
    .attr("y1", function(d) {return d;})
    .attr("x2", WIDTH - 25)
    .attr("y2", function(d) {return d;})
    .style("stroke", "rgb(192,192,192)")
    .style("opacity", 0.3)
    .style("stroke-width", 2);
}

<commit_msg>Fix redundant line<commit_after>function choiceLineGraph(nationalData) {
  var parseDate = d3.time.format("%Y-%m-%d");
  nationalData.forEach(function(choice) {
    choice.minDate = d3.min(choice.attributes.responses.map(function(d) { return parseDate.parse(d.date); }));
    choice.maxDate = d3.max(choice.attributes.responses.map(function(d) { return parseDate.parse(d.date); }));
    choice.maxPercentage = d3.max(choice.attributes.responses.map(function(d) { return d.percentage; }));
  });
  var graph = d3.select("#line-graph");
  var WIDTH = 1000;
  var HEIGHT = 500;
  var MARGINS = {
      top: 20,
      right: 20,
      bottom: 26,
      left: 50
    };
  var Xscale = d3.time.scale().range([MARGINS.left, WIDTH - MARGINS.right]);
  Xscale.domain([
      d3.min(nationalData.map(function(choice) { return choice.minDate; })),
      d3.max(nationalData.map(function(choice) { return choice.maxDate; }))
    ]);
  var Yscale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]);
  Yscale.domain([
      0,
      d3.max(nationalData.map(function(choice) { return parseFloat(choice.maxPercentage) })) + 5
    ]);

  var colorScale = d3.scale.category20();

  var Xaxis = d3.svg.axis()
      .scale(Xscale)
      .orient("bottom");

  var Yaxis = d3.svg.axis()
      .scale(Yscale)
      .orient("left");

  graph.append("svg:g")
    .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
    .call(Xaxis);

  graph.append("svg:g")
    .attr("transform", "translate(" + (MARGINS.left) + ",0)")
    .call(Yaxis);

  var lineGen = d3.svg.line()
    .x(function(d) {
      return Xscale(parseDate.parse(d.date));
    })
    .y(function(d) {
      return Yscale(parseFloat(d.percentage));
    })
    .interpolate('basis');

  nationalData.forEach(function(choice, index) {
    var line = graph.append("svg:path")
      .attr("stroke", colorScale(index))
      .attr("stroke-width", 3)
      .attr("fill", "none");

    line.transition()
      .duration(4000)
      .delay(500 + index * 500)
      .ease("linear")
      .attr('d', lineGen(choice.attributes.responses));
  });

  var yaxiscords = d3.range(26, HEIGHT, 45.4);
  var xaxiscords = d3.range(50, WIDTH, 25);

  graph.selectAll("line.vertical")
    .data(xaxiscords)
    .enter().append("svg:line")
    .attr("x1", function(d) {return d;})
    .attr("y1", 26)
    .attr("x2", function(d) {return d;})
    .attr("y2", HEIGHT - 25)
    .style("stroke", "rgb(192,192,192)")
    .style("opacity", 0.3)
    .style("stroke-width", 2);

  graph.selectAll("line.horizontal")
    .data(yaxiscords)
    .enter().append("svg:line")
    .attr("x1", 50)
    .attr("y1", function(d) {return d;})
    .attr("x2", WIDTH - 25)
    .attr("y2", function(d) {return d;})
    .style("stroke", "rgb(192,192,192)")
    .style("opacity", 0.3)
    .style("stroke-width", 2);
}

<|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.
 */
(function() {
  'use strict';

  /**
   * Class constructor for Textfield MDL component.
   * Implements MDL component design pattern defined at:
   * https://github.com/jasonmayes/mdl-component-design-pattern
   *
   * @constructor
   * @param {HTMLElement} element The element that will be upgraded.
   */
  var MaterialTextfield = function MaterialTextfield(element) {
    this.element_ = element;
    this.maxRows = this.Constant_.NO_MAX_ROWS;
    // Initialize instance.
    this.init();
  };
  window['MaterialTextfield'] = MaterialTextfield;

  /**
   * Store constants in one place so they can be updated easily.
   *
   * @enum {string | number}
   * @private
   */
  MaterialTextfield.prototype.Constant_ = {
    NO_MAX_ROWS: -1,
    MAX_ROWS_ATTRIBUTE: 'maxrows'
  };

  /**
   * Store strings for class names defined by this component that are used in
   * JavaScript. This allows us to simply change it in one place should we
   * decide to modify at a later date.
   *
   * @enum {string}
   * @private
   */
  MaterialTextfield.prototype.CssClasses_ = {
    LABEL: 'mdl-textfield__label',
    INPUT: 'mdl-textfield__input',
    IS_DIRTY: 'is-dirty',
    IS_FOCUSED: 'is-focused',
    IS_DISABLED: 'is-disabled',
    IS_INVALID: 'is-invalid',
    IS_UPGRADED: 'is-upgraded',
    HAS_PLACEHOLDER: 'has-placeholder'
  };

  /**
   * Handle input being entered.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onKeyDown_ = function(event) {
    var currentRowCount = event.target.value.split('\n').length;
    if (event.keyCode === 13) {
      if (currentRowCount >= this.maxRows) {
        event.preventDefault();
      }
    }
  };

  /**
   * Handle focus.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onFocus_ = function(event) {
    this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
  };

  /**
   * Handle lost focus.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onBlur_ = function(event) {
    this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
  };

  /**
   * Handle reset event from out side.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onReset_ = function(event) {
    this.updateClasses_();
  };

  /**
   * Handle class updates.
   *
   * @private
   */
  MaterialTextfield.prototype.updateClasses_ = function() {
    this.checkDisabled();
    this.checkValidity();
    this.checkDirty();
    this.checkFocus();
  };

  // Public methods.

  /**
   * Check the disabled state and update field accordingly.
   *
   * @public
   */
  MaterialTextfield.prototype.checkDisabled = function() {
    if (this.input_.disabled) {
      this.element_.classList.add(this.CssClasses_.IS_DISABLED);
    } else {
      this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
    }
  };
  MaterialTextfield.prototype['checkDisabled'] =
      MaterialTextfield.prototype.checkDisabled;

  /**
  * Check the focus state and update field accordingly.
  *
  * @public
  */
  MaterialTextfield.prototype.checkFocus = function() {
    if (Boolean(this.element_.querySelector(':focus'))) {
      this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
    } else {
      this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
    }
  };
  MaterialTextfield.prototype['checkFocus'] =
    MaterialTextfield.prototype.checkFocus;

  /**
   * Check the validity state and update field accordingly.
   *
   * @public
   */
  MaterialTextfield.prototype.checkValidity = function() {
    if (this.input_.validity) {
      if (this.input_.validity.valid) {
        this.element_.classList.remove(this.CssClasses_.IS_INVALID);
      } else {
        this.element_.classList.add(this.CssClasses_.IS_INVALID);
      }
    }
  };
  MaterialTextfield.prototype['checkValidity'] =
      MaterialTextfield.prototype.checkValidity;

  /**
   * Check the dirty state and update field accordingly.
   *
   * @public
   */
  MaterialTextfield.prototype.checkDirty = function() {
    if (this.input_.value && this.input_.value.length > 0) {
      this.element_.classList.add(this.CssClasses_.IS_DIRTY);
    } else {
      this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
    }
  };
  MaterialTextfield.prototype['checkDirty'] =
      MaterialTextfield.prototype.checkDirty;

  /**
   * Disable text field.
   *
   * @public
   */
  MaterialTextfield.prototype.disable = function() {
    this.input_.disabled = true;
    this.updateClasses_();
  };
  MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;

  /**
   * Enable text field.
   *
   * @public
   */
  MaterialTextfield.prototype.enable = function() {
    this.input_.disabled = false;
    this.updateClasses_();
  };
  MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;

  /**
   * Update text field value.
   *
   * @param {string} value The value to which to set the control (optional).
   * @public
   */
  MaterialTextfield.prototype.change = function(value) {

    this.input_.value = value || '';
    this.updateClasses_();
  };
  MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;

  /**
   * Initialize element.
   */
  MaterialTextfield.prototype.init = function() {

    if (this.element_) {
      this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL);
      this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);

      if (this.input_) {
        if (this.input_.hasAttribute(
              /** @type {string} */ (this.Constant_.MAX_ROWS_ATTRIBUTE))) {
          this.maxRows = parseInt(this.input_.getAttribute(
              /** @type {string} */ (this.Constant_.MAX_ROWS_ATTRIBUTE)), 10);
          if (isNaN(this.maxRows)) {
            this.maxRows = this.Constant_.NO_MAX_ROWS;
          }
        }

        if (this.input_.hasAttribute('placeholder')) {
          this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER);
        }

        this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
        this.boundFocusHandler = this.onFocus_.bind(this);
        this.boundBlurHandler = this.onBlur_.bind(this);
        this.boundResetHandler = this.onReset_.bind(this);
        this.input_.addEventListener('input', this.boundUpdateClassesHandler);
        this.input_.addEventListener('focus', this.boundFocusHandler);
        this.input_.addEventListener('blur', this.boundBlurHandler);
        this.input_.addEventListener('reset', this.boundResetHandler);

        if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
          // TODO: This should handle pasting multi line text.
          // Currently doesn't.
          this.boundKeyDownHandler = this.onKeyDown_.bind(this);
          this.input_.addEventListener('keydown', this.boundKeyDownHandler);
        }
        var invalid = this.element_.classList
          .contains(this.CssClasses_.IS_INVALID);
        this.updateClasses_();
        this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
        if (invalid) {
          this.element_.classList.add(this.CssClasses_.IS_INVALID);
        }
        if (this.input_.hasAttribute('autofocus')) {
          this.element_.focus();
          this.checkFocus();
        }
      }
    }
  };

  // The component registers itself. It can assume componentHandler is available
  // in the global scope.
  componentHandler.register({
    constructor: MaterialTextfield,
    classAsString: 'MaterialTextfield',
    cssClass: 'mdl-js-textfield',
    widget: true
  });
})();
<commit_msg>set is_dirty when placeholder exists<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.
 */
(function() {
  'use strict';

  /**
   * Class constructor for Textfield MDL component.
   * Implements MDL component design pattern defined at:
   * https://github.com/jasonmayes/mdl-component-design-pattern
   *
   * @constructor
   * @param {HTMLElement} element The element that will be upgraded.
   */
  var MaterialTextfield = function MaterialTextfield(element) {
    this.element_ = element;
    this.maxRows = this.Constant_.NO_MAX_ROWS;
    // Initialize instance.
    this.init();
  };
  window['MaterialTextfield'] = MaterialTextfield;

  /**
   * Store constants in one place so they can be updated easily.
   *
   * @enum {string | number}
   * @private
   */
  MaterialTextfield.prototype.Constant_ = {
    NO_MAX_ROWS: -1,
    MAX_ROWS_ATTRIBUTE: 'maxrows'
  };

  /**
   * Store strings for class names defined by this component that are used in
   * JavaScript. This allows us to simply change it in one place should we
   * decide to modify at a later date.
   *
   * @enum {string}
   * @private
   */
  MaterialTextfield.prototype.CssClasses_ = {
    LABEL: 'mdl-textfield__label',
    INPUT: 'mdl-textfield__input',
    IS_DIRTY: 'is-dirty',
    IS_FOCUSED: 'is-focused',
    IS_DISABLED: 'is-disabled',
    IS_INVALID: 'is-invalid',
    IS_UPGRADED: 'is-upgraded',
    HAS_PLACEHOLDER: 'has-placeholder'
  };

  /**
   * Handle input being entered.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onKeyDown_ = function(event) {
    var currentRowCount = event.target.value.split('\n').length;
    if (event.keyCode === 13) {
      if (currentRowCount >= this.maxRows) {
        event.preventDefault();
      }
    }
  };

  /**
   * Handle focus.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onFocus_ = function(event) {
    this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
  };

  /**
   * Handle lost focus.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onBlur_ = function(event) {
    this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
  };

  /**
   * Handle reset event from out side.
   *
   * @param {Event} event The event that fired.
   * @private
   */
  MaterialTextfield.prototype.onReset_ = function(event) {
    this.updateClasses_();
  };

  /**
   * Handle class updates.
   *
   * @private
   */
  MaterialTextfield.prototype.updateClasses_ = function() {
    this.checkDisabled();
    this.checkValidity();
    this.checkDirty();
    this.checkFocus();
  };

  // Public methods.

  /**
   * Check the disabled state and update field accordingly.
   *
   * @public
   */
  MaterialTextfield.prototype.checkDisabled = function() {
    if (this.input_.disabled) {
      this.element_.classList.add(this.CssClasses_.IS_DISABLED);
    } else {
      this.element_.classList.remove(this.CssClasses_.IS_DISABLED);
    }
  };
  MaterialTextfield.prototype['checkDisabled'] =
      MaterialTextfield.prototype.checkDisabled;

  /**
  * Check the focus state and update field accordingly.
  *
  * @public
  */
  MaterialTextfield.prototype.checkFocus = function() {
    if (Boolean(this.element_.querySelector(':focus'))) {
      this.element_.classList.add(this.CssClasses_.IS_FOCUSED);
    } else {
      this.element_.classList.remove(this.CssClasses_.IS_FOCUSED);
    }
  };
  MaterialTextfield.prototype['checkFocus'] =
    MaterialTextfield.prototype.checkFocus;

  /**
   * Check the validity state and update field accordingly.
   *
   * @public
   */
  MaterialTextfield.prototype.checkValidity = function() {
    if (this.input_.validity) {
      if (this.input_.validity.valid) {
        this.element_.classList.remove(this.CssClasses_.IS_INVALID);
      } else {
        this.element_.classList.add(this.CssClasses_.IS_INVALID);
      }
    }
  };
  MaterialTextfield.prototype['checkValidity'] =
      MaterialTextfield.prototype.checkValidity;

  /**
   * Check the dirty state and update field accordingly.
   *
   * @public
   */
  MaterialTextfield.prototype.checkDirty = function() {
    if ((this.input_.value && this.input_.value.length > 0) || this.input_.placeholder) {
      this.element_.classList.add(this.CssClasses_.IS_DIRTY);
    } else {
      this.element_.classList.remove(this.CssClasses_.IS_DIRTY);
    }
  };
  MaterialTextfield.prototype['checkDirty'] =
      MaterialTextfield.prototype.checkDirty;

  /**
   * Disable text field.
   *
   * @public
   */
  MaterialTextfield.prototype.disable = function() {
    this.input_.disabled = true;
    this.updateClasses_();
  };
  MaterialTextfield.prototype['disable'] = MaterialTextfield.prototype.disable;

  /**
   * Enable text field.
   *
   * @public
   */
  MaterialTextfield.prototype.enable = function() {
    this.input_.disabled = false;
    this.updateClasses_();
  };
  MaterialTextfield.prototype['enable'] = MaterialTextfield.prototype.enable;

  /**
   * Update text field value.
   *
   * @param {string} value The value to which to set the control (optional).
   * @public
   */
  MaterialTextfield.prototype.change = function(value) {

    this.input_.value = value || '';
    this.updateClasses_();
  };
  MaterialTextfield.prototype['change'] = MaterialTextfield.prototype.change;

  /**
   * Initialize element.
   */
  MaterialTextfield.prototype.init = function() {

    if (this.element_) {
      this.label_ = this.element_.querySelector('.' + this.CssClasses_.LABEL);
      this.input_ = this.element_.querySelector('.' + this.CssClasses_.INPUT);

      if (this.input_) {
        if (this.input_.hasAttribute(
              /** @type {string} */ (this.Constant_.MAX_ROWS_ATTRIBUTE))) {
          this.maxRows = parseInt(this.input_.getAttribute(
              /** @type {string} */ (this.Constant_.MAX_ROWS_ATTRIBUTE)), 10);
          if (isNaN(this.maxRows)) {
            this.maxRows = this.Constant_.NO_MAX_ROWS;
          }
        }

        if (this.input_.hasAttribute('placeholder')) {
          this.element_.classList.add(this.CssClasses_.HAS_PLACEHOLDER);
        }

        this.boundUpdateClassesHandler = this.updateClasses_.bind(this);
        this.boundFocusHandler = this.onFocus_.bind(this);
        this.boundBlurHandler = this.onBlur_.bind(this);
        this.boundResetHandler = this.onReset_.bind(this);
        this.input_.addEventListener('input', this.boundUpdateClassesHandler);
        this.input_.addEventListener('focus', this.boundFocusHandler);
        this.input_.addEventListener('blur', this.boundBlurHandler);
        this.input_.addEventListener('reset', this.boundResetHandler);

        if (this.maxRows !== this.Constant_.NO_MAX_ROWS) {
          // TODO: This should handle pasting multi line text.
          // Currently doesn't.
          this.boundKeyDownHandler = this.onKeyDown_.bind(this);
          this.input_.addEventListener('keydown', this.boundKeyDownHandler);
        }
        var invalid = this.element_.classList
          .contains(this.CssClasses_.IS_INVALID);
        this.updateClasses_();
        this.element_.classList.add(this.CssClasses_.IS_UPGRADED);
        if (invalid) {
          this.element_.classList.add(this.CssClasses_.IS_INVALID);
        }
        if (this.input_.hasAttribute('autofocus')) {
          this.element_.focus();
          this.checkFocus();
        }
      }
    }
  };

  // The component registers itself. It can assume componentHandler is available
  // in the global scope.
  componentHandler.register({
    constructor: MaterialTextfield,
    classAsString: 'MaterialTextfield',
    cssClass: 'mdl-js-textfield',
    widget: true
  });
})();
<|endoftext|>


<commit_before>import { addDecorator } from 'discourse/widgets/post-cooked';
import ComposerEditor from 'discourse/components/composer-editor';
import { addPosterIcon } from 'discourse/widgets/poster-name';

let _decorateId = 0;
function decorate(klass, evt, cb) {
  const mixin = {};
  mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
  klass.reopen(mixin);
}

export function decorateCooked() {
  console.warn('`decorateCooked` has been removed. Use `getPluginApi(version).decorateCooked` instead');
}

class PluginApi {
  constructor(version, container) {
    this.version = version;
    this.container = container;
  }

  /**
   * decorateCooked(callback)
   *
   * Used for decorating the `cooked` content of a post after it is rendered using
   * jQuery.
   *
   * `callback` will be called when it is time to decorate with a jQuery selector.
   *
   * For example, to add a yellow background to all posts you could do this:
   *
   * ```
   * api.decorateCooked($elem => $elem.css({ backgroundColor: 'yellow' }));
   * ```
   **/
  decorateCooked(cb) {
    addDecorator(cb);
    decorate(ComposerEditor, 'previewRefreshed', cb);
    decorate(this.container.lookupFactory('view:user-stream'), 'didInsertElement', cb);
  }

  /**
   * addPosterIcon(callback)
   *
   * This function can be used to add an icon with a link that will be displayed
   * beside a poster's name. The `callback` is called with the post's user custom
   * fields, and will render an icon if it receives an object back.
   *
   * The returned object can have the following attributes:
   *
   *   icon        (required) the font awesome icon to render
   *   className   (optional) a css class to apply to the icon
   *   url         (optional) where to link the icon
   *   title       (optional) the tooltip title for the icon on hover
   *
   * ```
   * api.addPosterIcon(cfs => {
   *   if (cfs.customer) {
   *     return { icon: 'user', className: 'customer', title: 'customer' };
   *   }
   * });
   * ```
   **/
  addPosterIcon(cb) {
    addPosterIcon(cb);
  }
}

let _pluginv01;
function getPluginApi(version) {
  if (version === "0.1") {
    if (!_pluginv01) {
      _pluginv01 = new PluginApi(version, Discourse.__container__);
    }
    return _pluginv01;
  } else {
    console.warn(`Plugin API v${version} is not supported`);
  }
}

/**
 * withPluginApi(version, apiCode, noApi)
 *
 * Helper to version our client side plugin API. Pass the version of the API that your
 * plugin is coded against. If that API is available, the `apiCodeCallback` function will
 * be called with the `PluginApi` object.
*/
export function withPluginApi(version, apiCodeCallback, opts) {
  opts = opts || {};

  const api = getPluginApi(version);
  if (api) {
    return apiCodeCallback(api);
  }
}

// This is backported so plugins in the new format will not raise errors
//
// To upgrade your plugin for backwards compatibility, you can add code in this
// form:
//
//   function newApiCode(api) {
//     // api.xyz();
//   }
//
//   function oldCode() {
//     // your pre-PluginAPI code goes here. You will be able to delete this
//     // code once the `PluginAPI` has been rolled out to all versions of
//     // Discourse you want to support.
//   }
//
//   // `newApiCode` will use API version 0.1, if no API support then
//   // `oldCode` will be called
//   withPluginApi('0.1', newApiCode, { noApi: oldCode });
//
export function withPluginApi(version, apiCodeCallback, opts) {
  console.warn(`Plugin API v${version} is not supported`);

  if (opts && opts.noApi) {
    return opts.noApi();
  }
}
<commit_msg>FIX: Bad merge<commit_after>import { addDecorator } from 'discourse/widgets/post-cooked';
import ComposerEditor from 'discourse/components/composer-editor';
import { addPosterIcon } from 'discourse/widgets/poster-name';

let _decorateId = 0;
function decorate(klass, evt, cb) {
  const mixin = {};
  mixin["_decorate_" + (_decorateId++)] = function($elem) { cb($elem); }.on(evt);
  klass.reopen(mixin);
}

export function decorateCooked() {
  console.warn('`decorateCooked` has been removed. Use `getPluginApi(version).decorateCooked` instead');
}

class PluginApi {
  constructor(version, container) {
    this.version = version;
    this.container = container;
  }

  /**
   * decorateCooked(callback)
   *
   * Used for decorating the `cooked` content of a post after it is rendered using
   * jQuery.
   *
   * `callback` will be called when it is time to decorate with a jQuery selector.
   *
   * For example, to add a yellow background to all posts you could do this:
   *
   * ```
   * api.decorateCooked($elem => $elem.css({ backgroundColor: 'yellow' }));
   * ```
   **/
  decorateCooked(cb) {
    addDecorator(cb);
    decorate(ComposerEditor, 'previewRefreshed', cb);
    decorate(this.container.lookupFactory('view:user-stream'), 'didInsertElement', cb);
  }

  /**
   * addPosterIcon(callback)
   *
   * This function can be used to add an icon with a link that will be displayed
   * beside a poster's name. The `callback` is called with the post's user custom
   * fields, and will render an icon if it receives an object back.
   *
   * The returned object can have the following attributes:
   *
   *   icon        (required) the font awesome icon to render
   *   className   (optional) a css class to apply to the icon
   *   url         (optional) where to link the icon
   *   title       (optional) the tooltip title for the icon on hover
   *
   * ```
   * api.addPosterIcon(cfs => {
   *   if (cfs.customer) {
   *     return { icon: 'user', className: 'customer', title: 'customer' };
   *   }
   * });
   * ```
   **/
  addPosterIcon(cb) {
    addPosterIcon(cb);
  }
}

let _pluginv01;
function getPluginApi(version) {
  if (version === "0.1") {
    if (!_pluginv01) {
      _pluginv01 = new PluginApi(version, Discourse.__container__);
    }
    return _pluginv01;
  } else {
    console.warn(`Plugin API v${version} is not supported`);
  }
}

/**
 * withPluginApi(version, apiCode, noApi)
 *
 * Helper to version our client side plugin API. Pass the version of the API that your
 * plugin is coded against. If that API is available, the `apiCodeCallback` function will
 * be called with the `PluginApi` object.
*/
export function withPluginApi(version, apiCodeCallback, opts) {
  opts = opts || {};

  const api = getPluginApi(version);
  if (api) {
    return apiCodeCallback(api);
  }
}
<|endoftext|>


<commit_before>const guideListConfig = {
  sections: [
    {
      name: 'Getting Started',
      guides: [
        {
          title: 'Setup Skygear',
          description: 'Signing up for Skygear hosting and' +
            ' create your first application using our iOS/Android/JavaScript SDK.',
          baseUrl: '/', // TODO: set up getting started
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'User Authentication',
      guides: [
        {
          title: 'User Authentication Basics',
          description: 'User Log-in/Log-out, user access tokens, email and password managements',
          baseUrl: '/guide/auth/basics/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Social Login',
          description: 'Third-party login, e.g. Facebook, Google',
          baseUrl: '/guide/auth/social-login/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Creating User Profile for Authenticated Users',
          description: 'Saving and retrieving custom profile attributes',
          baseUrl: '/guide/auth/social-login/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Skygear Cloud Database',
      guides: [
        {
          title: 'Cloud Database Basics',
          description: 'Storing data in the database using Records, with' +
            ' guides to basic record CRUD (create, read, update, delete)',
          baseUrl: '/guide/cloud-db/basics/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Performing Data Queries from Database',
          description: 'From the simplest queries to more complex ones' +
            ' such as AND/OR conditions and pagination',
          baseUrl: '/guide/cloud-db/queries/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Data Types',
          description: 'A guide to the supported data types:' +
            ' numbers, strings, foreign keys, locations, file uploads and more',
          baseUrl: '/guide/cloud-db/data-types/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Access Control Basics',
          description: 'Setting up restriction to certain database table' +
            ' or individual record is easy',
          baseUrl: '/guide/cloud-db/acl/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Offline Storage',
          description: 'Creating cached queries to save query results locally',
          baseUrl: '/guide/cloud-db/offline-storage/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Query Subscriptions',
          description: 'Subscribing to database data changes from the client',
          baseUrl: '/guide/cloud-db/query-subscription/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Skygear PubSub',
      guides: [
        {
          title: 'PubSub Basics',
          description: 'Adding the publish-subscribe functioality to your' +
            ' app with Skygear',
          baseUrl: '/guide/pubsub/basics/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Push Notifications',
      guides: [
        {
          title: 'Push Notification Basics',
          description: 'Sending push notifications with Skygear',
          baseUrl: '/guide/push-notifications/basics/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Cloud Functions',
      guides: [
        {
          title: 'Introduction and Deployment of Cloud Functions',
          description: 'The first step to using the Skygear cloud functions' +
            ' to have your codes running on Skygear server',
          baseUrl: '/guide/cloud-function/intro-and-deployment/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions for Database Event Hooks',
          description: 'Running codes upon database events such as' +
            ' creating, updating or deleting records',
          baseUrl: '/guide/cloud-function/database-hooks/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions for Creating Scheduled Tasks',
          description: 'Executing your server codes at specified time' +
            ' or intervals',
          baseUrl: '/guide/cloud-function/scheduled-tasks/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions for Skygear SDKs to call',
          description: 'Lambda functions can be called from the SDKs' +
            ' to execute your custom codes on Skygear server',
          baseUrl: '/guide/cloud-function/lambda/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions as HTTP Endpoint',
          description: 'Creating your own HTTP endpoint so that external' +
            ' applications can send GET/POST and other requests to your app',
          baseUrl: '/guide/cloud-function/http-endpoint/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions as Restful HTTP Endpoint',
          description: 'Creating your own set of restful HTTP endpoint easily' +
            ' through cloud functions',
          baseUrl: '/guide/cloud-function/restful-http-endpoint/',
          languages: ['python'],
        },
        {
          title: 'Serving Static Assets from Skygear Server',
          description: 'Methods to make your static assets such as images' +
            ' accessible with a URL',
          baseUrl: '/guide/cloud-function/static-assets/',
          languages: ['python'],
        },
        {
          title: 'Calling Skygear API from Cloud Functions',
          description: 'Calling Skygear API, using database connections,' +
            ' pubsub, push notifications, and using the master key to' +
            ' impersonate user for the admin',
          baseUrl: '/guide/cloud-function/calling-skygear-api/',
          languages: ['python'],
        },
        {
          title: 'Authenticating Users',
          description: 'Getting the authenticated user ID from the cloud' +
            ' functions, and resetting the password of a user',
          baseUrl: '/guide/cloud-function/authenticating-users/',
          languages: ['python'],
        },
        {
          title: 'Error Handling',
          description: 'Error handling in cloud functions',
          baseUrl: '/guide/cloud-function/error-handling/',
          languages: ['python'],
        },
      ],
    },
  ],
};

export default guideListConfig;
<commit_msg>Fix wrong link to user profile guide<commit_after>const guideListConfig = {
  sections: [
    {
      name: 'Getting Started',
      guides: [
        {
          title: 'Setup Skygear',
          description: 'Signing up for Skygear hosting and' +
            ' create your first application using our iOS/Android/JavaScript SDK.',
          baseUrl: '/', // TODO: set up getting started
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'User Authentication',
      guides: [
        {
          title: 'User Authentication Basics',
          description: 'User Log-in/Log-out, user access tokens, email and password managements',
          baseUrl: '/guide/auth/basics/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Social Login',
          description: 'Third-party login, e.g. Facebook, Google',
          baseUrl: '/guide/auth/social-login/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Creating User Profile for Authenticated Users',
          description: 'Saving and retrieving custom profile attributes',
          baseUrl: '/guide/auth/user-profile/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Skygear Cloud Database',
      guides: [
        {
          title: 'Cloud Database Basics',
          description: 'Storing data in the database using Records, with' +
            ' guides to basic record CRUD (create, read, update, delete)',
          baseUrl: '/guide/cloud-db/basics/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Performing Data Queries from Database',
          description: 'From the simplest queries to more complex ones' +
            ' such as AND/OR conditions and pagination',
          baseUrl: '/guide/cloud-db/queries/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Data Types',
          description: 'A guide to the supported data types:' +
            ' numbers, strings, foreign keys, locations, file uploads and more',
          baseUrl: '/guide/cloud-db/data-types/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Access Control Basics',
          description: 'Setting up restriction to certain database table' +
            ' or individual record is easy',
          baseUrl: '/guide/cloud-db/acl/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Offline Storage',
          description: 'Creating cached queries to save query results locally',
          baseUrl: '/guide/cloud-db/offline-storage/',
          languages: ['ios', 'android', 'js'],
        },
        {
          title: 'Query Subscriptions',
          description: 'Subscribing to database data changes from the client',
          baseUrl: '/guide/cloud-db/query-subscription/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Skygear PubSub',
      guides: [
        {
          title: 'PubSub Basics',
          description: 'Adding the publish-subscribe functioality to your' +
            ' app with Skygear',
          baseUrl: '/guide/pubsub/basics/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Push Notifications',
      guides: [
        {
          title: 'Push Notification Basics',
          description: 'Sending push notifications with Skygear',
          baseUrl: '/guide/push-notifications/basics/',
          languages: ['ios', 'android', 'js'],
        },
      ],
    },
    {
      name: 'Cloud Functions',
      guides: [
        {
          title: 'Introduction and Deployment of Cloud Functions',
          description: 'The first step to using the Skygear cloud functions' +
            ' to have your codes running on Skygear server',
          baseUrl: '/guide/cloud-function/intro-and-deployment/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions for Database Event Hooks',
          description: 'Running codes upon database events such as' +
            ' creating, updating or deleting records',
          baseUrl: '/guide/cloud-function/database-hooks/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions for Creating Scheduled Tasks',
          description: 'Executing your server codes at specified time' +
            ' or intervals',
          baseUrl: '/guide/cloud-function/scheduled-tasks/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions for Skygear SDKs to call',
          description: 'Lambda functions can be called from the SDKs' +
            ' to execute your custom codes on Skygear server',
          baseUrl: '/guide/cloud-function/lambda/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions as HTTP Endpoint',
          description: 'Creating your own HTTP endpoint so that external' +
            ' applications can send GET/POST and other requests to your app',
          baseUrl: '/guide/cloud-function/http-endpoint/',
          languages: ['python'],
        },
        {
          title: 'Cloud Functions as Restful HTTP Endpoint',
          description: 'Creating your own set of restful HTTP endpoint easily' +
            ' through cloud functions',
          baseUrl: '/guide/cloud-function/restful-http-endpoint/',
          languages: ['python'],
        },
        {
          title: 'Serving Static Assets from Skygear Server',
          description: 'Methods to make your static assets such as images' +
            ' accessible with a URL',
          baseUrl: '/guide/cloud-function/static-assets/',
          languages: ['python'],
        },
        {
          title: 'Calling Skygear API from Cloud Functions',
          description: 'Calling Skygear API, using database connections,' +
            ' pubsub, push notifications, and using the master key to' +
            ' impersonate user for the admin',
          baseUrl: '/guide/cloud-function/calling-skygear-api/',
          languages: ['python'],
        },
        {
          title: 'Authenticating Users',
          description: 'Getting the authenticated user ID from the cloud' +
            ' functions, and resetting the password of a user',
          baseUrl: '/guide/cloud-function/authenticating-users/',
          languages: ['python'],
        },
        {
          title: 'Error Handling',
          description: 'Error handling in cloud functions',
          baseUrl: '/guide/cloud-function/error-handling/',
          languages: ['python'],
        },
      ],
    },
  ],
};

export default guideListConfig;
<|endoftext|>


<commit_before><commit_msg>animation now stops upon collision<commit_after><|endoftext|>


<commit_before><commit_msg>Open resume link in new tab functionality<commit_after><|endoftext|>


<commit_before><commit_msg>Delays activity update to prevent UI lags on intensive operations (filetree update for instance)<commit_after><|endoftext|>


<commit_before><commit_msg>enhancement: Move rounds to top left<commit_after><|endoftext|>


<commit_before>'use strict';

var test = require('tape');
var plugin = require('..').parseSmartypants;

test('parses the `$content` field if no `keys` specified', function(t) {
  var parseSmartypants = plugin();
  var cb = function(err, result) {
    t.false(err);
    t.looseEqual(result, [
      {
        $content: '“foo”'
      }
    ]);
    t.end();
  };
  var files = [
    {
      $content: '"foo"'
    }
  ];
  parseSmartypants(cb, files);
});

test('parse a single field', function(t) {
  var parseSmartypants = plugin('foo');
  var cb = function(err, result) {
    t.false(err);
    t.looseEqual(result, [
      {
        foo: '“foo”'
      }
    ]);
    t.end();
  };
  var files = [
    {
      foo: '"foo"'
    }
  ];
  parseSmartypants(cb, files);
});

test('parse multiple fields', function(t) {
  var parseSmartypants = plugin(['foo', 'bar']);
  var cb = function(err, result) {
    t.false(err);
    t.looseEqual(result, [
      {
        foo: '“foo”',
        bar: '“bar”'
      }
    ]);
    t.end();
  };
  var files = [
    {
      foo: '"foo"',
      bar: '"bar"'
    }
  ];
  parseSmartypants(cb, files);
});
<commit_msg>Hygiene<commit_after>'use strict';

var test = require('tape');
var plugin = require('..').parseSmartypants;

test('parses the `$content` field if no `keys` specified', function(t) {
  var parseSmartypants = plugin();
  var cb = function(err, result) {
    t.false(err);
    t.looseEqual(result, [
      { $content: '“foo”' }
    ]);
    t.end();
  };
  var files = [
    { $content: '"foo"' }
  ];
  parseSmartypants(cb, files);
});

test('parse a single field', function(t) {
  var parseSmartypants = plugin('foo');
  var cb = function(err, result) {
    t.false(err);
    t.looseEqual(result, [
      { foo: '“foo”' }
    ]);
    t.end();
  };
  var files = [
    { foo: '"foo"' }
  ];
  parseSmartypants(cb, files);
});

test('parse multiple fields', function(t) {
  var parseSmartypants = plugin(['foo', 'bar']);
  var cb = function(err, result) {
    t.false(err);
    t.looseEqual(result, [
      {
        foo: '“foo”',
        bar: '“bar”'
      }
    ]);
    t.end();
  };
  var files = [
    {
      foo: '"foo"',
      bar: '"bar"'
    }
  ];
  parseSmartypants(cb, files);
});
<|endoftext|>


<commit_before>var TagsSettingsMenuView = Ember.View.extend({
    saveText: Ember.computed('controller.model.isNew', function () {
        return this.get('controller.model.isNew') ?
            'Add Tag' :
            'Save Tag';
    }),

    // This observer loads and resets the uploader whenever the active tag changes,
    // ensuring that we can reuse the whole settings menu.
    updateUploader: Ember.observer('controller.activeTag.image', 'controller.uploaderReference', function () {
        var uploader = this.get('controller.uploaderReference'),
            image = this.get('controller.activeTag.image');

        if (uploader && uploader[0]) {
            if (image) {
                uploader[0].uploaderUi.initWithImage();
            } else {
                uploader[0].uploaderUi.initWithDropzone();
            }
        }
    })
});

export default TagsSettingsMenuView;
<commit_msg>Reset upload component on tag switch<commit_after>var TagsSettingsMenuView = Ember.View.extend({
    saveText: Ember.computed('controller.model.isNew', function () {
        return this.get('controller.model.isNew') ?
            'Add Tag' :
            'Save Tag';
    }),

    // This observer loads and resets the uploader whenever the active tag changes,
    // ensuring that we can reuse the whole settings menu.
    updateUploader: Ember.observer('controller.activeTag.image', 'controller.uploaderReference', function () {
        var uploader = this.get('controller.uploaderReference'),
            image = this.get('controller.activeTag.image');

        if (uploader && uploader[0]) {
            if (image) {
                uploader[0].uploaderUi.initWithImage();
            } else {
                uploader[0].uploaderUi.reset();
            }
        }
    })
});

export default TagsSettingsMenuView;
<|endoftext|>


<commit_before>const { cloneDeep } = require('lodash');
/**
* @param {object} workspace - redux top level workspace state obj
*/

export function propsParser(workspace) {
  const clonedWorkspace = cloneDeep(workspace);
  const components = clonedWorkspace.components;
  const app = components.app || components.workspace;
  let state = clonedWorkspace.state;
  state = Object.assign(state, getChildProps(app, components));
  return clonedWorkspace;
}
<commit_msg>builds event map, begins building flat events.<commit_after>const { cloneDeep } = require('lodash');
const WORKSPACE_ID = 'workspace';
/**
* @param {object} workspace - redux top level workspace state obj
*/

const defaultWorkspace = {
  componentCounter: 2,
  activeComponent: '0',
  components: {
    workspace: {
      id: WORKSPACE_ID,
      children: [0, 1],
      events: {},
    },
  },
  state: {},
  methods: {},
};

defaultWorkspace.components[0] = {
  id: 0,
  name: 'BlackBox',
  children: [],
  parentID: WORKSPACE_ID,
  props: {
    style: {
      position: 'relative',
      height: '100px',
      width: '100px',
      display: 'inline-block',
      backgroundColor: 'black',
      overflow: 'auto',
    },
  },
  events: { onClick: ()=>{console.log('test')} }
};

defaultWorkspace.components[1] = {
  id: 1,
  name: 'BlueBox',
  children: [],
  parentID: WORKSPACE_ID,
  props: {
    style: {
      position: 'relative',
      height: '100px',
      width: '100px',
      display: 'inline-block',
      backgroundColor: 'blue',
      overflow: 'auto',
    },
  },
  events: { test: 'event test =]' }
};

/*export*/ function eventsParser(workspace) {
  const clonedWorkspace = cloneDeep(workspace);
  const components = clonedWorkspace.components;
  const app = components.app || components.workspace;
  let events = app.events;
  events = Object.assign(events, getChildEvents(app, components));
  return clonedWorkspace;
}

function getChildEvents(parent, components) {
  const { children } = parent;
  let events = parent.events || {};
  if (children.length === 0){
    return events;
  }
  children.forEach((child) => {
     events[child] = Object.assign({}, getChildEvents(components[child], components));
  });

  return events;
}

/**
* @param {object} events - workspace events or component events
* @param {string} component - string name of component
* @param {object} components - workspace.components regardless of first param ID
*/

/*export*/ function flattenStateProps(events, component, components) {
  const children = components[component].children;
  events = cloneDeep(events);
  return Object.keys(events).reduce((final, init) => {
      if (children.indexOf(Number(init)) === -1) {
        final[init] = events[init];
      } else {
        final = Object.assign(final, flattenStateProps(events[init], init, components));
      }
      delete final.style;
      return final;
  }, {});
}

const test = eventsParser(defaultWorkspace);

console.log(test.components.workspace.events);
<|endoftext|>


<commit_before><commit_msg>Minor update<commit_after><|endoftext|>


<commit_before> /**
  * @module opcua.transport
  */

 // system requires
 import assert from "better-assert";

 import _ from "underscore";
 import util from "util";

 // opcua requires
 import { StatusCode, StatusCodes } from "lib/datamodel/opcua_status_code";
 import { BinaryStream } from "lib/misc/binaryStream";
 import { verify_message_chunk } from "lib/misc/chunk_manager";
 import { TCPErrorMessage } from "_generated_/_auto_generated_TCPErrorMessage";
 import {
   packTcpMessage,
   decodeMessage
  } from "lib/nodeopcua";
 import { HelloMessage } from "_generated_/_auto_generated_HelloMessage";
 import { AcknowledgeMessage } from "_generated_/_auto_generated_AcknowledgeMessage";
 import utils from "lib/misc/utils";

 import { TCP_transport } from "lib/transport/tcp_transport";

 const hexDump = utils.hexDump;
 const debugLog = utils.make_debugLog(__filename);
 const doDebug = utils.checkDebugFlag(__filename);

 require("requirish")._(module);

 /**
  * @class ServerTCP_transport
  * @extends TCP_transport
  * @constructor
  *
  */
 class ServerTCP_transport extends TCP_transport {
   _abortWithError(statusCode, extraErrorDescription, callback) {
     assert(statusCode instanceof StatusCode);
     assert(_.isFunction(callback), "expecting a callback");

     const self = this;

       /* istanbul ignore else */
     if (!self.__aborted) {
       self.__aborted = 1;
           // send the error message and close the connection
       assert(Object.keys(StatusCodes).includes(statusCode.name));

       debugLog(" Server aborting because ".red + statusCode.name.cyan);
       debugLog(" extraErrorDescription   ".red + extraErrorDescription.cyan);
       const errorResponse = new TCPErrorMessage({ statusCode, reason: statusCode.description });
       const messageChunk = packTcpMessage("ERR", errorResponse);

       self.write(messageChunk);
       self.disconnect(() => {
         self.__aborted = 2;
         callback(new Error(`${extraErrorDescription} StatusCode = ${statusCode.name}`));
       });
     } else {
       console.log("xxx ignoring ", statusCode.name);
       callback(new Error(statusCode.name));
     }
   }

   _send_ACK_response(helloMessage) {
     const self = this;

     self.receiveBufferSize = clamp_value(helloMessage.receiveBufferSize, 8196, 512 * 1024);
     self.sendBufferSize    = clamp_value(helloMessage.sendBufferSize,    8196, 512 * 1024);
     self.maxMessageSize    = clamp_value(helloMessage.maxMessageSize,  100000, 16 * 1024 * 1024);
     self.maxChunkCount     = clamp_value(helloMessage.maxChunkCount,        0, 65535);

     const acknowledgeMessage = new AcknowledgeMessage({
       protocolVersion:   self.protocolVersion,
       receiveBufferSize: self.receiveBufferSize,
       sendBufferSize:    self.sendBufferSize,
       maxMessageSize:    self.maxMessageSize,
       maxChunkCount:     self.maxChunkCount
     });

     const messageChunk = packTcpMessage("ACK", acknowledgeMessage);

       /* istanbul ignore next*/
     if (doDebug) {
       verify_message_chunk(messageChunk);
       debugLog(`server send: ${"ACK".yellow}`);
       debugLog(`server send: ${hexDump(messageChunk)}`);
       debugLog("acknowledgeMessage=", acknowledgeMessage);
     }

       // send the ACK reply
     self.write(messageChunk);
   }

   _install_HEL_message_receiver(callback) {
     const self = this;

     self._install_one_time_message_receiver((err, data) => {
       if (err) {
               // err is either a timeout or connection aborted ...
         self._abortWithError(StatusCodes.BadConnectionRejected, err.message, callback);
       } else {
               // handle the HEL message
         self._on_HEL_message(data, callback);
       }
     });
   }

   _on_HEL_message(data, callback) {
     const self = this;

     assert(data instanceof Buffer);
     assert(!self._helloreceived);

     const stream = new BinaryStream(data);
     const msgType = data.slice(0, 3).toString("ascii");

       /* istanbul ignore next*/
     if (doDebug) {
       debugLog(`SERVER received ${msgType.yellow}`);
       debugLog(`SERVER received ${hexDump(data)}`);
     }

     if (msgType === "HEL") {
       assert(data.length >= 24);

       const helloMessage = decodeMessage(stream, HelloMessage);
       assert(_.isFinite(self.protocolVersion));

           // OPCUA Spec 1.03 part 6 - page 41
           // The Server shall always accept versions greater than what it supports.
       if (helloMessage.protocolVersion != self.protocolVersion) {
         console.log(`warning ! client sent helloMessage.protocolVersion = 0x${helloMessage.protocolVersion.toString(16)}`,` whereas server protocolVersion is 0x${self.protocolVersion.toString(16)}`);
       }
       if (helloMessage.protocolVersion === 0xDEADBEEF
        || helloMessage.protocolVersion < self.protocolVersion) {
               // Note: 0xDEADBEEF is our special version number
               //   to simulate BadProtocolVersionUnsupported in tests
               // invalid protocol version requested by client
         self._abortWithError(StatusCodes.BadProtocolVersionUnsupported, `Protocol Version Error${self.protocolVersion}`, callback);
       } else {
               // the helloMessage shall only be received once.
         self._helloreceived = true;

         self._send_ACK_response(helloMessage);

         callback(null); // no Error
       }
     } else {
           // invalid packet , expecting HEL
       console.log("BadCommunicationError ".red, "Expecting 'HEL' message to initiate communication");
       self._abortWithError(StatusCodes.BadCommunicationError, "Expecting 'HEL' message to initiate communication", callback);
     }
   }

   /**
    * Initialize the server transport.
    *
    *
    *  The ServerTCP_transport initialisation process starts by
    *   waiting for the client to send a "HEL" message.
    *
    *  The  ServerTCP_transport replies with a "ACK" message and then
    *   start waiting for further messages of any size.
    *
    *  The callback function received an error:
    *   - if no message from the client is received within the ```self.timeout``` period,
    *   - or, if the connection has dropped within the same interval.
    *   - if the protocol version specified within
    *    the HEL message is invalid or is greater than ```self.protocolVersion```
    *
    * @method init
    * @param socket {Socket}
    * @param callback {Function}
    * @param callback.err {Error||null} err = null if init succeeded
    *
    */
   init(socket, callback) {
     assert(!this.socket, "init already called!");
     assert(_.isFunction(callback), "expecting a valid callback ");

     const self = this;

     self._install_socket(socket);

     self._install_HEL_message_receiver(callback);
   }
 }

 ServerTCP_transport.prototype.end = "DEPRECATED";

 function clamp_value(value, min_val, max_val) {
   assert(min_val < max_val);
   if (value === 0) {
     return max_val;
   }
   if (value < min_val) {
     return min_val;
   }
     /* istanbul ignore next*/
   if (value >= max_val) {
     return max_val;
   }
   return value;
 }

 export { ServerTCP_transport };
<commit_msg>es6ed server_tcp_transport<commit_after> /**
  * @module opcua.transport
  */

 // system requires
 import assert from "better-assert";

 import _ from "underscore";
 import util from "util";

 // opcua requires
 import { StatusCode, StatusCodes } from "lib/datamodel/opcua_status_code";
 import { BinaryStream } from "lib/misc/binaryStream";
 import { verify_message_chunk } from "lib/misc/chunk_manager";
 import { TCPErrorMessage } from "_generated_/_auto_generated_TCPErrorMessage";
 import {
   packTcpMessage,
   decodeMessage
  } from "lib/nodeopcua";
 import { HelloMessage } from "_generated_/_auto_generated_HelloMessage";
 import { AcknowledgeMessage } from "_generated_/_auto_generated_AcknowledgeMessage";
 import utils from "lib/misc/utils";

 import { TCP_transport } from "lib/transport/tcp_transport";

 const hexDump = utils.hexDump;
 const debugLog = utils.make_debugLog(__filename);
 const doDebug = utils.checkDebugFlag(__filename);

 require("requirish")._(module);

 /**
  * @class ServerTCP_transport
  * @extends TCP_transport
  * @constructor
  *
  */
 class ServerTCP_transport extends TCP_transport {
   _abortWithError(statusCode, extraErrorDescription, callback) {
     assert(statusCode instanceof StatusCode);
     assert(_.isFunction(callback), "expecting a callback");

     const self = this;

       /* istanbul ignore else */
     if (!self.__aborted) {
       self.__aborted = 1;
           // send the error message and close the connection
       assert(Object.keys(StatusCodes).includes(statusCode.name));

       debugLog(" Server aborting because ".red + statusCode.name.cyan);
       debugLog(" extraErrorDescription   ".red + extraErrorDescription.cyan);
       const errorResponse = new TCPErrorMessage({ statusCode, reason: statusCode.description });
       const messageChunk = packTcpMessage("ERR", errorResponse);

       self.write(messageChunk);
       self.disconnect(() => {
         self.__aborted = 2;
         callback(new Error(`${extraErrorDescription} StatusCode = ${statusCode.name}`));
       });
     } else {
       console.log("xxx ignoring ", statusCode.name);
       callback(new Error(statusCode.name));
     }
   }

   _send_ACK_response(helloMessage) {
     const self = this;

     self.receiveBufferSize = clamp_value(helloMessage.receiveBufferSize, 8196, 512 * 1024);
     self.sendBufferSize    = clamp_value(helloMessage.sendBufferSize,    8196, 512 * 1024);
     self.maxMessageSize    = clamp_value(helloMessage.maxMessageSize,  100000, 16 * 1024 * 1024);
     self.maxChunkCount     = clamp_value(helloMessage.maxChunkCount,        0, 65535);

     const acknowledgeMessage = new AcknowledgeMessage({
       protocolVersion:   self.protocolVersion,
       receiveBufferSize: self.receiveBufferSize,
       sendBufferSize:    self.sendBufferSize,
       maxMessageSize:    self.maxMessageSize,
       maxChunkCount:     self.maxChunkCount
     });

     const messageChunk = packTcpMessage("ACK", acknowledgeMessage);

       /* istanbul ignore next*/
     if (doDebug) {
       verify_message_chunk(messageChunk);
       debugLog(`server send: ${"ACK".yellow}`);
       debugLog(`server send: ${hexDump(messageChunk)}`);
       debugLog("acknowledgeMessage=", acknowledgeMessage);
     }

       // send the ACK reply
     self.write(messageChunk);
   }

   _install_HEL_message_receiver(callback) {
     const self = this;

     self._install_one_time_message_receiver((err, data) => {
       if (err) {
               // err is either a timeout or connection aborted ...
         self._abortWithError(StatusCodes.BadConnectionRejected, err.message, callback);
       } else {
               // handle the HEL message
         self._on_HEL_message(data, callback);
       }
     });
   }

   _on_HEL_message(data, callback) {
     const self = this;

     assert(data instanceof Buffer);
     assert(!self._helloreceived);

     const stream = new BinaryStream(data);
     const msgType = data.slice(0, 3).toString("ascii");

       /* istanbul ignore next*/
     if (doDebug) {
       debugLog(`SERVER received ${msgType.yellow}`);
       debugLog(`SERVER received ${hexDump(data)}`);
     }

     if (msgType === "HEL") {
       assert(data.length >= 24);

       const helloMessage = decodeMessage(stream, HelloMessage);
       assert(_.isFinite(self.protocolVersion));

           // OPCUA Spec 1.03 part 6 - page 41
           // The Server shall always accept versions greater than what it supports.
       if (helloMessage.protocolVersion !== self.protocolVersion) {
         console.log(`warning ! client sent helloMessage.protocolVersion = 0x${helloMessage.protocolVersion.toString(16)}`,` whereas server protocolVersion is 0x${self.protocolVersion.toString(16)}`);
       }
       if (helloMessage.protocolVersion === 0xDEADBEEF
        || helloMessage.protocolVersion < self.protocolVersion) {
               // Note: 0xDEADBEEF is our special version number
               //   to simulate BadProtocolVersionUnsupported in tests
               // invalid protocol version requested by client
         self._abortWithError(StatusCodes.BadProtocolVersionUnsupported, `Protocol Version Error${self.protocolVersion}`, callback);
       } else {
               // the helloMessage shall only be received once.
         self._helloreceived = true;

         self._send_ACK_response(helloMessage);

         callback(null); // no Error
       }
     } else {
           // invalid packet , expecting HEL
       console.log("BadCommunicationError ".red, "Expecting 'HEL' message to initiate communication");
       self._abortWithError(StatusCodes.BadCommunicationError, "Expecting 'HEL' message to initiate communication", callback);
     }
   }

   /**
    * Initialize the server transport.
    *
    *
    *  The ServerTCP_transport initialisation process starts by
    *   waiting for the client to send a "HEL" message.
    *
    *  The  ServerTCP_transport replies with a "ACK" message and then
    *   start waiting for further messages of any size.
    *
    *  The callback function received an error:
    *   - if no message from the client is received within the ```self.timeout``` period,
    *   - or, if the connection has dropped within the same interval.
    *   - if the protocol version specified within
    *    the HEL message is invalid or is greater than ```self.protocolVersion```
    *
    * @method init
    * @param socket {Socket}
    * @param callback {Function}
    * @param callback.err {Error||null} err = null if init succeeded
    *
    */
   init(socket, callback) {
     assert(!this.socket, "init already called!");
     assert(_.isFunction(callback), "expecting a valid callback ");

     const self = this;

     self._install_socket(socket);

     self._install_HEL_message_receiver(callback);
   }
 }

 ServerTCP_transport.prototype.end = "DEPRECATED";

 function clamp_value(value, min_val, max_val) {
   assert(min_val < max_val);
   if (value === 0) {
     return max_val;
   }
   if (value < min_val) {
     return min_val;
   }
     /* istanbul ignore next*/
   if (value >= max_val) {
     return max_val;
   }
   return value;
 }

 export { ServerTCP_transport };
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {
    View,
    Text,
    StyleSheet,
    Image,
 }  from 'react-native';
 import * as Device from 'app/utility/Device';

 class OneRMLoggedOutView extends Component {

    render() {
        const size = Device.isSmallDevice() ? 250 : 300;

        return (
            <View>
                <Text style={styles.errorText}>
                    You must be logged in for 1rm calculation.
                </Text>
                <Image style={{width: size, height: size, marginTop: 20}} source={require('app/appearance/images/grayed_chart.png')} />
                <View style={[styles.disabledButton, {marginTop: 20}]}>
                    <Text style={styles.buttonText}>Calculate</Text>
                </View>
            </View>
        )
    }

 }

 const styles = StyleSheet.create({
    disabledButton: {
        backgroundColor: 'rgba(47, 128, 237, 1)',
        borderColor: 'rgba(47, 128, 237, 1)',
        borderWidth: 5,
        borderRadius: 15,
        opacity: 0.3
    },
    buttonText: {
        color: 'white',
        padding: 5,
        textAlign: 'center'
    },
});

export default OneRMLoggedOutView;
<commit_msg>new logged out screen<commit_after>import React, {Component} from 'react';
import {
    View,
    Text,
    StyleSheet,
    Image,
 }  from 'react-native';
 import * as Device from 'app/utility/Device';
 import { SETTINGS_PANEL_STYLES } from 'app/appearance/styles/GlobalStyles';

 class OneRMLoggedOutView extends Component {

    render() {
        const size = Device.isSmallDevice() ? 250 : 300;

        return (
            <View style={ [SETTINGS_PANEL_STYLES.panel, { flexDirection: 'column', alignItems: 'center' }] }>
                <Text style={[{marginBottom: 15}, styles.titleText]}>Estimated One-Rep Max</Text>
                <Text style={styles.errorText}>
                    You must be logged in for 1rm calculation.
                </Text>
                <Image style={{width: size, height: size, marginTop: 20}} source={require('app/appearance/images/grayed_chart.png')} />
            </View>
        )
    }

 }

 const styles = StyleSheet.create({
    titleText: {
        color: 'rgba(77, 77, 77, 1)',
        textAlign: 'center',
        fontSize: 16,
        fontWeight: 'bold'
    },
    disabledButton: {
        backgroundColor: 'rgba(47, 128, 237, 1)',
        borderColor: 'rgba(47, 128, 237, 1)',
        borderWidth: 5,
        borderRadius: 15,
        opacity: 0.3
    },
    buttonText: {
        color: 'white',
        padding: 5,
        textAlign: 'center'
    },
    errorText: {
        color: 'rgba(77, 77, 77, 1)',
        marginBottom: 20,
        fontSize: 20,
        textAlign: 'center'
    },
});

export default OneRMLoggedOutView;
<|endoftext|>