<commit_before>/* jshint eqnull:true */

define(function(require, exports, module) {
    "use strict";

    var app = require("app");
    var DateUtil = require("modules/util/DateUtil");
    var AbstractView = require("modules/view/AbstractView");
    var ModalCalendarView = require("modules/view/news/common/ModalCalendarView");
    var TutorialView = require("modules/view/tutorial/TutorialView");
    var PersonalModel = require("modules/model/personal/PersonalModel");
    var Snap = require("snap");

    var GlobalNavView = AbstractView.extend({
        template : require("ldsh!templates/{mode}/common/global-nav"),
        templateMap : {
            "news" : require("ldsh!templates/news/common/global-nav")
        },

        /**
         * 文字サイズ変更後のタイマー
         */
        fontTimer : null,

        /**
         *  ViewのテンプレートHTMLの描画処理が完了する前に呼び出される。
         *  @memberOf GlobalNavView#
         */
        beforeRendered : function() {
        },

        /**
         *  ViewのテンプレートHTMLの描画処理が完了した後に呼び出される。
         *  @memberOf GlobalNavView#
         */
        afterRendered : function() {
            var self = this;

            this.updateBackHomeButton();
            this.updateDateLabel();
            var $target = $("[value='" + app.user.get("fontSize") + "']");
            var size = parseInt($target.attr('data-font-size'), 10);
            $('html, body').css('font-size', size + 'px');
            this.setDate(this.targetDate || new Date(app.currentDate));
        },

        /**
         *  初期化処理
         *  @memberOf GlobalNavView#
         */
        initialize : function() {
            var self = this;

            // snap.jsを初期化する
            this.snapper = new Snap({
                element: $('#snap-content')[0],
                tapToClose: true,
                touchToDrag: false
            });
            $('#snap-content').data("snap",this.snapper);

            // ルーティングのイベントハンドラを登録する
            app.router.on('route:globalNav', this.onRoute.bind(this));

            // プレビューモードの場合は自身を表示しない
            if (app.preview) {
                $(".contents-wrapper").css("padding-top", "0");
                $("#global-nav").hide();
            }

            // TODO appに刺さない
            $(document).on("backnumber-date", function (ev, data) {
                app.backnumberDateAreaHtml = data.dateAreaHtml;

                $('.global-nav__date .nav-content').empty().html(app.backnumberDateAreaHtml);
            });
        },

        /**
         *  viewがremoveされる時に呼ばれる
         *  @memberOf GlobalNavView#
         */
        cleanup: function () {
            app.router.off('route:globalNav');
        },

        /**
         * RadiationエンティティセットへのFetch成功時のイベントハンドラ。
         * @memberOf GlobalNavView#
         */
        onFetchRadiation: function() {
            var model = this.collection.at(0);
            var value = "-";
            if (model) {
                value = model.get("value");
            }
            $("#radiationValue").text(value + "μSv/h");
        },

        events : {
            'click [data-drawer-opener]': 'onClickDrawerOpener',
            'click [data-back-home]': 'onClickBackHome',
            'change #selectRadiation' : "onChangeRadiationStation",
            'click [data-font-size]': 'onClickFontSize',

            'click .global-nav__menubutton a': 'onClickMenuButton',
            'click #help' : 'onClickHelp',
            'click #backno' : 'onClickBackno',
            'click #setting' : 'onClickSetting',
            "click #calendar" : "onClickCalendar"
        },

        /**
         * 発行日を設定する。
         * @memberOf GlobalNavView#
         * @param {Date} date 設定する日付
         */
        setDate : function(date) {
            if (date) {
                $("#naviPublishDate").show();
                $("#naviPublishDate").find(".date--year").text(DateUtil.formatDate(date, "yyyy"));
                $("#naviPublishDate").find(".date--month").text(date.getMonth() + 1);
                $("#naviPublishDate").find(".date--day").text(date.getDate());
                $("#naviPublishDate").find(".date--weekday").text(DateUtil.formatDate(date, "ddd"));
            } else {
                $("#naviPublishDate").hide();
            }
        },

        /**
         *  今日の新聞に戻るボタンは
         *  topでは表示しない
         *  @memberOf GlobalNavView#
         */
        updateBackHomeButton: function () {
            var fragment = Backbone.history.fragment;

            if (fragment && fragment.match(/^top/)) {
                $('.global-nav__menu').hide();
                $('.global-nav__date').addClass('no-backbutton');
            } else {
                $('.global-nav__menu').show();
                $('.global-nav__date').removeClass('no-backbutton');
            }
        },

        /**
         *  日付の表記を変更
         *  @memberOf GlobalNavView#
         */
        updateDateLabel: function () {
            var self = this;
            var fragments = Backbone.history.fragment.split('/');
            if (fragments[0] == 'backnumber') {
                //$('.global-nav__date .nav-content').html('バックナンバー');
                if (app.backnumberDateAreaHtml) $('.global-nav__date .nav-content').empty().html(app.backnumberDateAreaHtml);
            }
        },

        /**
         *  サンドイッチボタンがクリックされたら呼ばれる
         *  @memberOf GlobalNavView#
         *  @param {Event} ev
         */
        onClickDrawerOpener: function (ev) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー","");
            ev.preventDefault();
            this.snapper.open('left');
        },

        /**
         *  今日の新聞に戻るボタンが押されたら呼ばれる
         *  @memberOf GlobalNavView#
         */
        onClickBackHome: function (ev) {
            ev.preventDefault();
            app.router.back();
        },

        /**
         *  フォントサイズ変更ボタンが押されたら呼ばれる
         *  @memberOf GlobalNavView#
         */
        onClickFontSize: function (ev) {
            $(document).trigger('willChangeFontSize');

            var $target = $(ev.currentTarget);
            var size = parseInt($target.attr('data-font-size'), 10);
            app.ga.trackEvent("新聞アプリ/全ページ共通", "文字サイズ選択", parseInt(size));
            $('html, body').css('font-size', size + 'px');

            $(document).trigger('didChangeFontSize');

            // 連続で押下された場合にリクエストが多数飛ばないようにするため
            // 数秒後に文字サイズ登録リクエストを飛ばす。
            // この間に再度文字サイズを変更されると前回の登録処理は無効とする
            if (this.fontTimer !== null) {
                clearTimeout(this.fontTimer);
            }
            this.fontTimer = setTimeout($.proxy(function() {
                this.saveFontSize($target.attr("value"));
            },this),1500);
        },

        /**
         *  文字サイズの保存処理
         *  @memberOf GlobalNavView#
         *  @param {String} fontSize 文字サイズ
         */
        saveFontSize: function (fontSize) {
            var model = app.user;
            model.set("fontSize",fontSize);
            model.set("etag","*");
            // 文字サイズのみの保存処理のため、成功時や失敗時に
            // その都度ユーザに通知しない
            model.save(null,{});
        },

        /**
         * メニュー項目ボタンのaタグをクリックした際の挙動を
         * ブラウザデフォルトではなく
         * pushStateに変更する
         * @memberOf GlobalNavView#
         */
        onClickMenuButton: function (evt) {
            var $target = $(evt.currentTarget);
            var href = { prop: $target.prop("href"), attr: $target.attr("href") };
            var root = location.protocol + "//" + location.host + app.root;

            if (href.prop && href.prop.slice(0, root.length) === root) {
                evt.preventDefault();
                app.router.navigate(href.attr, {
                    trigger: true,
                    replace: false
                });
            }
            $('#snap-content').data("snap").close();
        },

        /**
         * ヘルプがクリックされたら呼ばれる
         * @memberOf GlobalNavView#
         */
        onClickHelp : function(evt) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー内の項目「ヘルプ」","");

            var tutorialView = new TutorialView();

            this.setView(GlobalNavView.SELECTOR_GLOBAL_HELP, tutorialView);
            this.listenTo(tutorialView, "closeGlobalHelp", function () {
                tutorialView.remove();
                // URLを元に戻す
                app.router.back();
            });
            tutorialView.render();

            // ヘルプ画面用URLに遷移
            app.router.navigate("help", {
                trigger: true,
                replace: false
            });
        },

        onClickBackno : function(evt) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー内の項目「バックナンバー」","");
        },
        onClickSetting : function(evt) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー内の項目「設定」","");
        },

        /**
         * カレンダーがクリックされたら呼ばれる
         * @memberOf GlobalNavView#
         */
        onClickCalendar : function (evt) {
            var modalCalendarView = new ModalCalendarView();

            this.setView(GlobalNavView.SELECTOR_MODAL_CALENDAR, modalCalendarView);
            this.listenTo(modalCalendarView, "closeModalCalendar", function () {
                modalCalendarView.remove();
            });
            modalCalendarView.render();
        },

        /**
         * ルーティングによって呼ばれる
         *
         * @memberOf GlobalNavView#
         * @param {Event} ev
         */
        onRoute: function (ev) {
            app.logger.debug(ev);
        }
    }, {
        /**
         * modal-calendarを挿入する部分のセレクタ
         */
        SELECTOR_MODAL_CALENDAR: "#modal-calendar-container",

        /**
         * ヘルプを挿入する部分のセレクタ
         */
        SELECTOR_GLOBAL_HELP: "#global-help-container"
    });

    module.exports = GlobalNavView;
});
<commit_msg>NAM-879 StoryTest1：新聞：詳細画面でヘルプモーダル表示中OSの戻るでTOP画面にいく。<commit_after>/* jshint eqnull:true */

define(function(require, exports, module) {
    "use strict";

    var app = require("app");
    var DateUtil = require("modules/util/DateUtil");
    var AbstractView = require("modules/view/AbstractView");
    var ModalCalendarView = require("modules/view/news/common/ModalCalendarView");
    var TutorialView = require("modules/view/tutorial/TutorialView");
    var PersonalModel = require("modules/model/personal/PersonalModel");
    var Snap = require("snap");

    var GlobalNavView = AbstractView.extend({
        template : require("ldsh!templates/{mode}/common/global-nav"),
        templateMap : {
            "news" : require("ldsh!templates/news/common/global-nav")
        },

        /**
         * 文字サイズ変更後のタイマー
         */
        fontTimer : null,

        /**
         *  ViewのテンプレートHTMLの描画処理が完了する前に呼び出される。
         *  @memberOf GlobalNavView#
         */
        beforeRendered : function() {
        },

        /**
         *  ViewのテンプレートHTMLの描画処理が完了した後に呼び出される。
         *  @memberOf GlobalNavView#
         */
        afterRendered : function() {
            var self = this;

            this.updateBackHomeButton();
            this.updateDateLabel();
            var $target = $("[value='" + app.user.get("fontSize") + "']");
            var size = parseInt($target.attr('data-font-size'), 10);
            $('html, body').css('font-size', size + 'px');
            this.setDate(this.targetDate || new Date(app.currentDate));
        },

        /**
         *  初期化処理
         *  @memberOf GlobalNavView#
         */
        initialize : function() {
            var self = this;

            // snap.jsを初期化する
            this.snapper = new Snap({
                element: $('#snap-content')[0],
                tapToClose: true,
                touchToDrag: false
            });
            $('#snap-content').data("snap",this.snapper);

            // ルーティングのイベントハンドラを登録する
            app.router.on('route:globalNav', this.onRoute.bind(this));

            // プレビューモードの場合は自身を表示しない
            if (app.preview) {
                $(".contents-wrapper").css("padding-top", "0");
                $("#global-nav").hide();
            }

            // TODO appに刺さない
            $(document).on("backnumber-date", function (ev, data) {
                app.backnumberDateAreaHtml = data.dateAreaHtml;

                $('.global-nav__date .nav-content').empty().html(app.backnumberDateAreaHtml);
            });
        },

        /**
         *  viewがremoveされる時に呼ばれる
         *  @memberOf GlobalNavView#
         */
        cleanup: function () {
            app.router.off('route:globalNav');
        },

        /**
         * RadiationエンティティセットへのFetch成功時のイベントハンドラ。
         * @memberOf GlobalNavView#
         */
        onFetchRadiation: function() {
            var model = this.collection.at(0);
            var value = "-";
            if (model) {
                value = model.get("value");
            }
            $("#radiationValue").text(value + "μSv/h");
        },

        events : {
            'click [data-drawer-opener]': 'onClickDrawerOpener',
            'click [data-back-home]': 'onClickBackHome',
            'change #selectRadiation' : "onChangeRadiationStation",
            'click [data-font-size]': 'onClickFontSize',

            'click .global-nav__menubutton a': 'onClickMenuButton',
            'click #help' : 'onClickHelp',
            'click #backno' : 'onClickBackno',
            'click #setting' : 'onClickSetting',
            "click #calendar" : "onClickCalendar"
        },

        /**
         * 発行日を設定する。
         * @memberOf GlobalNavView#
         * @param {Date} date 設定する日付
         */
        setDate : function(date) {
            if (date) {
                $("#naviPublishDate").show();
                $("#naviPublishDate").find(".date--year").text(DateUtil.formatDate(date, "yyyy"));
                $("#naviPublishDate").find(".date--month").text(date.getMonth() + 1);
                $("#naviPublishDate").find(".date--day").text(date.getDate());
                $("#naviPublishDate").find(".date--weekday").text(DateUtil.formatDate(date, "ddd"));
            } else {
                $("#naviPublishDate").hide();
            }
        },

        /**
         *  今日の新聞に戻るボタンは
         *  topでは表示しない
         *  @memberOf GlobalNavView#
         */
        updateBackHomeButton: function () {
            var fragment = Backbone.history.fragment;

            if (fragment && fragment.match(/^top/)) {
                $('.global-nav__menu').hide();
                $('.global-nav__date').addClass('no-backbutton');
            } else {
                $('.global-nav__menu').show();
                $('.global-nav__date').removeClass('no-backbutton');
            }
        },

        /**
         *  日付の表記を変更
         *  @memberOf GlobalNavView#
         */
        updateDateLabel: function () {
            var self = this;
            var fragments = Backbone.history.fragment.split('/');
            if (fragments[0] == 'backnumber') {
                //$('.global-nav__date .nav-content').html('バックナンバー');
                if (app.backnumberDateAreaHtml) $('.global-nav__date .nav-content').empty().html(app.backnumberDateAreaHtml);
            }
        },

        /**
         *  サンドイッチボタンがクリックされたら呼ばれる
         *  @memberOf GlobalNavView#
         *  @param {Event} ev
         */
        onClickDrawerOpener: function (ev) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー","");
            ev.preventDefault();
            this.snapper.open('left');
        },

        /**
         *  今日の新聞に戻るボタンが押されたら呼ばれる
         *  @memberOf GlobalNavView#
         */
        onClickBackHome: function (ev) {
            ev.preventDefault();
            app.router.back();
        },

        /**
         *  フォントサイズ変更ボタンが押されたら呼ばれる
         *  @memberOf GlobalNavView#
         */
        onClickFontSize: function (ev) {
            $(document).trigger('willChangeFontSize');

            var $target = $(ev.currentTarget);
            var size = parseInt($target.attr('data-font-size'), 10);
            app.ga.trackEvent("新聞アプリ/全ページ共通", "文字サイズ選択", parseInt(size));
            $('html, body').css('font-size', size + 'px');

            $(document).trigger('didChangeFontSize');

            // 連続で押下された場合にリクエストが多数飛ばないようにするため
            // 数秒後に文字サイズ登録リクエストを飛ばす。
            // この間に再度文字サイズを変更されると前回の登録処理は無効とする
            if (this.fontTimer !== null) {
                clearTimeout(this.fontTimer);
            }
            this.fontTimer = setTimeout($.proxy(function() {
                this.saveFontSize($target.attr("value"));
            },this),1500);
        },

        /**
         *  文字サイズの保存処理
         *  @memberOf GlobalNavView#
         *  @param {String} fontSize 文字サイズ
         */
        saveFontSize: function (fontSize) {
            var model = app.user;
            model.set("fontSize",fontSize);
            model.set("etag","*");
            // 文字サイズのみの保存処理のため、成功時や失敗時に
            // その都度ユーザに通知しない
            model.save(null,{});
        },

        /**
         * メニュー項目ボタンのaタグをクリックした際の挙動を
         * ブラウザデフォルトではなく
         * pushStateに変更する
         * @memberOf GlobalNavView#
         */
        onClickMenuButton: function (evt) {
            var $target = $(evt.currentTarget);
            var href = { prop: $target.prop("href"), attr: $target.attr("href") };
            var root = location.protocol + "//" + location.host + app.root;

            if (href.prop && href.prop.slice(0, root.length) === root) {
                evt.preventDefault();
                app.router.navigate(href.attr, {
                    trigger: true,
                    replace: false
                });
            }
            $('#snap-content').data("snap").close();
        },

        /**
         * ヘルプがクリックされたら呼ばれる
         * @memberOf GlobalNavView#
         */
        onClickHelp : function(evt) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー内の項目「ヘルプ」","");

            var tutorialView = new TutorialView();

            this.setView(GlobalNavView.SELECTOR_GLOBAL_HELP, tutorialView);
            this.listenTo(tutorialView, "closeGlobalHelp", function () {
                tutorialView.remove();
                // URLを元に戻す
                app.router.back();
            });
            tutorialView.render();

            // ヘルプ画面用URLに遷移
            app.router.navigate("help", {
                trigger: true,
                replace: false
            });
        },

        onClickBackno : function(evt) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー内の項目「バックナンバー」","");
        },
        onClickSetting : function(evt) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー内の項目「設定」","");
        },

        /**
         * カレンダーがクリックされたら呼ばれる
         * @memberOf GlobalNavView#
         */
        onClickCalendar : function (evt) {
            var modalCalendarView = new ModalCalendarView();

            this.setView(GlobalNavView.SELECTOR_MODAL_CALENDAR, modalCalendarView);
            this.listenTo(modalCalendarView, "closeModalCalendar", function () {
                modalCalendarView.remove();
                // URLを元に戻す
                app.router.back();
            });
            modalCalendarView.render();

            // ヘルプ画面用URLに遷移
            app.router.navigate("calendar", {
                trigger: true,
                replace: false
            });
        },

        /**
         * ルーティングによって呼ばれる
         *
         * @memberOf GlobalNavView#
         * @param {Event} ev
         */
        onRoute: function (ev) {
            app.logger.debug(ev);
        }
    }, {
        /**
         * modal-calendarを挿入する部分のセレクタ
         */
        SELECTOR_MODAL_CALENDAR: "#modal-calendar-container",

        /**
         * ヘルプを挿入する部分のセレクタ
         */
        SELECTOR_GLOBAL_HELP: "#global-help-container"
    });

    module.exports = GlobalNavView;
});
<|endoftext|>


<commit_before>// @flow
import {remote} from 'electron'
import {List, Record} from 'immutable'
import {bootstrap} from '../actions/config'
import {logoutDone} from '../actions/login'
import {badgeApp} from '../actions/notifications'
import {badgeAppForChat} from '../actions/chat'
import {kbfsNotification} from '../util/kbfs-notifications'
import {pgpKeyInSecretStoreFile} from '../constants/pgp'
import {ConversationBadgeStateRecord} from '../constants/chat'
import type {Dispatch} from '../constants/types/flux'
import type {incomingCallMapType} from '../constants/types/flow-types'

// Keep track of the last time we notified and ignore if its the same
let lastLoggedInNotifyUsername = null

// TODO(mm) Move these to their own actions
export default function (dispatch: Dispatch, getState: () => Object, notify: any): incomingCallMapType {
  return {
    'keybase.1.NotifySession.loggedOut': params => {
      lastLoggedInNotifyUsername = null

      // Do we actually think we're logged in?
      if (getState().config &&
        getState().config.status &&
        getState().config.status.loggedIn) {
        notify('Logged out of Keybase')
        dispatch(logoutDone())
      }
    },
    'keybase.1.NotifySession.loggedIn': ({username}, response) => {
      if (lastLoggedInNotifyUsername !== username) {
        lastLoggedInNotifyUsername = username
        notify('Logged in to Keybase as: ' + username)
      }

      dispatch(bootstrap())
      response.result()
    },
    'keybase.1.NotifySession.clientOutOfDate': ({upgradeTo, upgradeURI, upgradeMsg}) => {
      const body = upgradeMsg || `Please update to ${upgradeTo} by going to ${upgradeURI}`
      notify('Client out of date!', {body}, 60 * 60)
    },
    'keybase.1.NotifyFS.FSActivity': ({notification}) => {
      kbfsNotification(notification, notify, getState)
    },
    'keybase.1.NotifyBadges.badgeState': ({badgeState}) => {
      const {conversations, newTlfs} = badgeState
      const convos = List(conversations.map(conversation => ConversationBadgeStateRecord(conversation)))
      console.warn('convos is ', convos)
      console.warn(convos)
      dispatch(badgeAppForChat(convos))
      dispatch(badgeApp('newTLFs', newTlfs > 0, newTlfs))
    },
    'keybase.1.NotifyService.shutdown': () => {
      // console.log('Quitting due to service shutdown')
      // App quiting will call ctl stop, which will stop the service
      // remote.app.quit()
    },
    'keybase.1.NotifyApp.exit': () => {
      console.log('App exit requested')
      remote.app.exit(0)
    },
    'keybase.1.NotifyPGP.pgpKeyInSecretStoreFile': () => {
      dispatch({type: pgpKeyInSecretStoreFile, payload: undefined})
    },
  }
}
<commit_msg>restyle<commit_after>// @flow
import {remote} from 'electron'
import {List, Record} from 'immutable'
import {bootstrap} from '../actions/config'
import {logoutDone} from '../actions/login'
import {badgeApp} from '../actions/notifications'
import {badgeAppForChat} from '../actions/chat'
import {kbfsNotification} from '../util/kbfs-notifications'
import {pgpKeyInSecretStoreFile} from '../constants/pgp'
import {ConversationBadgeStateRecord} from '../constants/chat'

import type {Dispatch} from '../constants/types/flux'
import type {incomingCallMapType} from '../constants/types/flow-types'

// Keep track of the last time we notified and ignore if its the same
let lastLoggedInNotifyUsername = null

// TODO(mm) Move these to their own actions
export default function (dispatch: Dispatch, getState: () => Object, notify: any): incomingCallMapType {
  return {
    'keybase.1.NotifySession.loggedOut': params => {
      lastLoggedInNotifyUsername = null

      // Do we actually think we're logged in?
      if (getState().config &&
        getState().config.status &&
        getState().config.status.loggedIn) {
        notify('Logged out of Keybase')
        dispatch(logoutDone())
      }
    },
    'keybase.1.NotifySession.loggedIn': ({username}, response) => {
      if (lastLoggedInNotifyUsername !== username) {
        lastLoggedInNotifyUsername = username
        notify('Logged in to Keybase as: ' + username)
      }

      dispatch(bootstrap())
      response.result()
    },
    'keybase.1.NotifySession.clientOutOfDate': ({upgradeTo, upgradeURI, upgradeMsg}) => {
      const body = upgradeMsg || `Please update to ${upgradeTo} by going to ${upgradeURI}`
      notify('Client out of date!', {body}, 60 * 60)
    },
    'keybase.1.NotifyFS.FSActivity': ({notification}) => {
      kbfsNotification(notification, notify, getState)
    },
    'keybase.1.NotifyBadges.badgeState': ({badgeState}) => {
      const {conversations, newTlfs} = badgeState
      const convos = List(conversations.map(conversation => ConversationBadgeStateRecord(conversation)))
      console.warn('convos is ', convos)
      console.warn(convos)
      dispatch(badgeAppForChat(convos))
      dispatch(badgeApp('newTLFs', newTlfs > 0, newTlfs))
    },
    'keybase.1.NotifyService.shutdown': () => {
      // console.log('Quitting due to service shutdown')
      // App quiting will call ctl stop, which will stop the service
      // remote.app.quit()
    },
    'keybase.1.NotifyApp.exit': () => {
      console.log('App exit requested')
      remote.app.exit(0)
    },
    'keybase.1.NotifyPGP.pgpKeyInSecretStoreFile': () => {
      dispatch({type: pgpKeyInSecretStoreFile, payload: undefined})
    },
  }
}
<|endoftext|>


<commit_before>/* jshint eqnull:true */

define(function(require, exports, module) {
    "use strict";

    var app = require("app");
    var DateUtil = require("modules/util/DateUtil");
    var AbstractView = require("modules/view/AbstractView");
    var PersonalModel = require("modules/model/personal/PersonalModel");
    var Snap = require("snap");

    var GlobalNavView = AbstractView.extend({
        template : require("ldsh!templates/{mode}/common/global-nav"),

        /**
         * 文字サイズ変更後のタイマー
         */
        fontTimer : null,

        /**
         *  ViewのテンプレートHTMLの描画処理が完了する前に呼び出される。
         */
        beforeRendered : function() {
        },

        /**
         *  ViewのテンプレートHTMLの描画処理が完了した後に呼び出される。
         */
        afterRendered : function() {
            this.updateBackHomeButton();
            this.updateDateLabel();
            var $target = $("[value='" + app.user.get("fontSize") + "']");
            var size = parseInt($target.attr('data-font-size'), 10);
            $('html, body').css('font-size', size + 'px');
            this.setDate(this.targetDate)
        },

        /**
         *  初期化処理
         */
        initialize : function() {
            // snap.jsを初期化する
            this.snapper = new Snap({
                element: $('#snap-content')[0],
                tapToClose: true,
                touchToDrag: false
            });
            $('#snap-content').data("snap",this.snapper);

            // ルーティングのイベントハンドラを登録する
            app.router.on('route:globalNav', this.onRoute.bind(this));
        },

        /**
         *  viewがremoveされる時に呼ばれる
         */
        cleanup: function () {
            app.router.off('route:globalNav');
        },

        /**
         * RadiationエンティティセットへのFetch成功時のイベントハンドラ。
         */
        onFetchRadiation: function() {
            var model = this.collection.at(0);
            var value = "-";
            if (model) {
                value = model.get("value");
            }
            $("#radiationValue").text(value + "μSv/h");
        },

        events : {
            'click [data-drawer-opener]': 'onClickDrawerOpener',
            'click [data-back-home]': 'onClickBackHome',
            'change #selectRadiation' : "onChangeRadiationStation",
            'click [data-font-size]': 'onClickFontSize'
        },

        /**
         * 発行日を設定する。
         * @param {Date} date 設定する日付
         */
        setDate : function(date) {
            if (date) {
                $("#naviPublishDate").show();
                $("#naviPublishDate").find(".date--year").text(date.getFullYear());
                $("#naviPublishDate").find(".date--month").text(date.getMonth() + 1);
                $("#naviPublishDate").find(".date--day").text(date.getDate());
                $("#naviPublishDate").find(".date--weekday").text(DateUtil.formatDate(date, "ddd"));
            } else {
                $("#naviPublishDate").hide();
            }
        },

        /**
         *  今日の新聞に戻るボタンは
         *  topでは表示しない
         */
        updateBackHomeButton: function () {
            if (Backbone.history.fragment == 'top') {
                $('[data-back-home]').hide();
            } else {
                $('[data-back-home]').show();
            }
        },

        /**
         *  日付の表記を変更
         */
        updateDateLabel: function () {
            var fragments = Backbone.history.fragment.split('/');
            if (fragments[0] == 'backnumber') {
                $('.global-nav__date .nav-content').html('バックナンバー');
            }
        },

        /**
         *  サンドイッチボタンがクリックされたら呼ばれる
         *  @param {Event} ev
         */
        onClickDrawerOpener: function (ev) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー","");
            ev.preventDefault();
            this.snapper.open('left');
        },

        /**
         *  今日の新聞に戻るボタンが押されたら呼ばれる
         */
        onClickBackHome: function (ev) {
            ev.preventDefault();
            app.router.back();
        },

        /**
         *  フォントサイズ変更ボタンが押されたら呼ばれる
         */
        onClickFontSize: function (ev) {
            $(document).trigger('willChangeFontSize');

            var $target = $(ev.currentTarget);
            var size = parseInt($target.attr('data-font-size'), 10);
            app.ga.trackEvent("新聞アプリ/全ページ共通", "文字サイズ選択", parseInt(size));
            $('html, body').css('font-size', size + 'px');

            $(document).trigger('didChangeFontSize');

            // 連続で押下された場合にリクエストが多数飛ばないようにするため
            // 数秒後に文字サイズ登録リクエストを飛ばす。
            // この間に再度文字サイズを変更されると前回の登録処理は無効とする
            if (this.fontTimer !== null) {
                clearTimeout(this.fontTimer);
            }
            this.fontTimer = setTimeout($.proxy(function() {
                this.saveFontSize($target.attr("value"));
            },this),1500);
        },

        /**
         *  文字サイズの保存処理
         *  @param {String} fontSize 文字サイズ
         */
        saveFontSize: function (fontSize) {
            var model = app.user;
            model.set("fontSize",fontSize);
            model.set("etag","*");
            // 文字サイズのみの保存処理のため、成功時や失敗時に
            // その都度ユーザに通知しない
            model.save(null,{});
        },

        /**
         * ルーティングによって呼ばれる
         *
         * @param {Event} ev
         */
        onRoute: function (ev) {
            console.log(ev);
        }
    });

    module.exports = GlobalNavView;
});
<commit_msg>NAM-355 JSHintのエラーを解消する<commit_after>/* jshint eqnull:true */

define(function(require, exports, module) {
    "use strict";

    var app = require("app");
    var DateUtil = require("modules/util/DateUtil");
    var AbstractView = require("modules/view/AbstractView");
    var PersonalModel = require("modules/model/personal/PersonalModel");
    var Snap = require("snap");

    var GlobalNavView = AbstractView.extend({
        template : require("ldsh!templates/{mode}/common/global-nav"),

        /**
         * 文字サイズ変更後のタイマー
         */
        fontTimer : null,

        /**
         *  ViewのテンプレートHTMLの描画処理が完了する前に呼び出される。
         */
        beforeRendered : function() {
        },

        /**
         *  ViewのテンプレートHTMLの描画処理が完了した後に呼び出される。
         */
        afterRendered : function() {
            this.updateBackHomeButton();
            this.updateDateLabel();
            var $target = $("[value='" + app.user.get("fontSize") + "']");
            var size = parseInt($target.attr('data-font-size'), 10);
            $('html, body').css('font-size', size + 'px');
            this.setDate(this.targetDate);
        },

        /**
         *  初期化処理
         */
        initialize : function() {
            // snap.jsを初期化する
            this.snapper = new Snap({
                element: $('#snap-content')[0],
                tapToClose: true,
                touchToDrag: false
            });
            $('#snap-content').data("snap",this.snapper);

            // ルーティングのイベントハンドラを登録する
            app.router.on('route:globalNav', this.onRoute.bind(this));
        },

        /**
         *  viewがremoveされる時に呼ばれる
         */
        cleanup: function () {
            app.router.off('route:globalNav');
        },

        /**
         * RadiationエンティティセットへのFetch成功時のイベントハンドラ。
         */
        onFetchRadiation: function() {
            var model = this.collection.at(0);
            var value = "-";
            if (model) {
                value = model.get("value");
            }
            $("#radiationValue").text(value + "μSv/h");
        },

        events : {
            'click [data-drawer-opener]': 'onClickDrawerOpener',
            'click [data-back-home]': 'onClickBackHome',
            'change #selectRadiation' : "onChangeRadiationStation",
            'click [data-font-size]': 'onClickFontSize'
        },

        /**
         * 発行日を設定する。
         * @param {Date} date 設定する日付
         */
        setDate : function(date) {
            if (date) {
                $("#naviPublishDate").show();
                $("#naviPublishDate").find(".date--year").text(date.getFullYear());
                $("#naviPublishDate").find(".date--month").text(date.getMonth() + 1);
                $("#naviPublishDate").find(".date--day").text(date.getDate());
                $("#naviPublishDate").find(".date--weekday").text(DateUtil.formatDate(date, "ddd"));
            } else {
                $("#naviPublishDate").hide();
            }
        },

        /**
         *  今日の新聞に戻るボタンは
         *  topでは表示しない
         */
        updateBackHomeButton: function () {
            if (Backbone.history.fragment == 'top') {
                $('[data-back-home]').hide();
            } else {
                $('[data-back-home]').show();
            }
        },

        /**
         *  日付の表記を変更
         */
        updateDateLabel: function () {
            var fragments = Backbone.history.fragment.split('/');
            if (fragments[0] == 'backnumber') {
                $('.global-nav__date .nav-content').html('バックナンバー');
            }
        },

        /**
         *  サンドイッチボタンがクリックされたら呼ばれる
         *  @param {Event} ev
         */
        onClickDrawerOpener: function (ev) {
            app.ga.trackEvent("新聞アプリ/全ページ共通", "ヘッダ部メニュー","");
            ev.preventDefault();
            this.snapper.open('left');
        },

        /**
         *  今日の新聞に戻るボタンが押されたら呼ばれる
         */
        onClickBackHome: function (ev) {
            ev.preventDefault();
            app.router.back();
        },

        /**
         *  フォントサイズ変更ボタンが押されたら呼ばれる
         */
        onClickFontSize: function (ev) {
            $(document).trigger('willChangeFontSize');

            var $target = $(ev.currentTarget);
            var size = parseInt($target.attr('data-font-size'), 10);
            app.ga.trackEvent("新聞アプリ/全ページ共通", "文字サイズ選択", parseInt(size));
            $('html, body').css('font-size', size + 'px');

            $(document).trigger('didChangeFontSize');

            // 連続で押下された場合にリクエストが多数飛ばないようにするため
            // 数秒後に文字サイズ登録リクエストを飛ばす。
            // この間に再度文字サイズを変更されると前回の登録処理は無効とする
            if (this.fontTimer !== null) {
                clearTimeout(this.fontTimer);
            }
            this.fontTimer = setTimeout($.proxy(function() {
                this.saveFontSize($target.attr("value"));
            },this),1500);
        },

        /**
         *  文字サイズの保存処理
         *  @param {String} fontSize 文字サイズ
         */
        saveFontSize: function (fontSize) {
            var model = app.user;
            model.set("fontSize",fontSize);
            model.set("etag","*");
            // 文字サイズのみの保存処理のため、成功時や失敗時に
            // その都度ユーザに通知しない
            model.save(null,{});
        },

        /**
         * ルーティングによって呼ばれる
         *
         * @param {Event} ev
         */
        onRoute: function (ev) {
            console.log(ev);
        }
    });

    module.exports = GlobalNavView;
});
<|endoftext|>


<commit_before>// stupid download counters
// The download counts are like this:
//
// /download/_design/app/_view/pkg
// --> {key:["pkgname", "YYYY-MM-DD"],value:123}
// /download/_design/app/_view/day
// --> {key:["YYYY-MM-DD", "pkgname"],value:123}
//
// These only change daily.

module.exports = downloads

var AC = require('async-cache')
var cache = new AC({
  max: 1000,
  maxAge: 1000 * 60 * 60,
  load: load
})

var config = require('../config.js')
var qs = require('querystring')

function day (s) {
  if (!(s instanceof Date)) {
    if (!Date.parse(s))
      return null
    s = new Date(s)
  }
  return s.toISOString().substr(0, 10)
}

function downloads (start, end, pkg, detail, cb) {
  if (typeof cb !== 'function')
    cb = detail, detail = false
  if (typeof cb !== 'function')
    cb = pkg, pkg = null
  if (typeof cb !== 'function')
    cb = end, end = null
  if (typeof cb !== 'function')
    cb = start, start = null

  var k = JSON.stringify([start, end, pkg, detail])
  cache.get(k, cb)
}

function load (k, cb) {
  k = JSON.parse(k)
  var start = k[0]
  var end = k[1]
  var pkg = k[2]
  var detail = k[3]

  var view, startkey, endkey, grouplevel
  if (start) start = new Date(start).toISOString().split('T')[0]
  if (pkg) {
    view = 'pkg'
    startkey = [pkg, day(start)]
    endkey = [pkg, day(end) || {}]
  } else {
    view = 'day'
    startkey = [day(start)]
    endkey = [day(end) || {}, {}]
  }
  grouplevel = detail ? 2 : 1
  var u = '/downloads/_design/app/_view/' + view
  var q = qs.stringify({
    startkey: JSON.stringify(startkey),
    endkey: JSON.stringify(endkey),
    group_level: grouplevel
  })

  config.adminCouch.get(u + '?' + q, function (er, cr, data) {
    if (er)
      return cb(er)

    if (detail)
      data = data.rows.reduce(function (set, row) {
        var k = row.key
        var h = k[0]
        var t = k[1]
        set[h] = set[h] || {}
        set[h][t] = row.value
        return set
      }, {})
    else if (pkg)
      data = data.rows.reduce(function (set, row) {
        var h = row.key[0]
        set[h] = row.value
        return set
      }, {})
    else
      data = (data.rows || []).map(function (r) {
        return r.value
      }).reduce(function (a, b) {
        return a + b
      }, 0)

    if (pkg)
      data = data[pkg]

    if (data === undefined)
      data = 0

    return cb(er, data)
  })
}
<commit_msg>work without downloads<commit_after>// stupid download counters
// The download counts are like this:
//
// /download/_design/app/_view/pkg
// --> {key:["pkgname", "YYYY-MM-DD"],value:123}
// /download/_design/app/_view/day
// --> {key:["YYYY-MM-DD", "pkgname"],value:123}
//
// These only change daily.

module.exports = downloads

var AC = require('async-cache')
var cache = new AC({
  max: 1000,
  maxAge: 1000 * 60 * 60,
  load: load
})

var config = require('../config.js')
var qs = require('querystring')

function day (s) {
  if (!(s instanceof Date)) {
    if (!Date.parse(s))
      return null
    s = new Date(s)
  }
  return s.toISOString().substr(0, 10)
}

function downloads (start, end, pkg, detail, cb) {
  if (typeof cb !== 'function')
    cb = detail, detail = false
  if (typeof cb !== 'function')
    cb = pkg, pkg = null
  if (typeof cb !== 'function')
    cb = end, end = null
  if (typeof cb !== 'function')
    cb = start, start = null

  var k = JSON.stringify([start, end, pkg, detail])
  cache.get(k, cb)
}

function load (k, cb) {
  k = JSON.parse(k)
  var start = k[0]
  var end = k[1]
  var pkg = k[2]
  var detail = k[3]

  var view, startkey, endkey, grouplevel
  if (start) start = new Date(start).toISOString().split('T')[0]
  if (pkg) {
    view = 'pkg'
    startkey = [pkg, day(start)]
    endkey = [pkg, day(end) || {}]
  } else {
    view = 'day'
    startkey = [day(start)]
    endkey = [day(end) || {}, {}]
  }
  grouplevel = detail ? 2 : 1
  var u = '/downloads/_design/app/_view/' + view
  var q = qs.stringify({
    startkey: JSON.stringify(startkey),
    endkey: JSON.stringify(endkey),
    group_level: grouplevel
  })

  config.adminCouch.get(u + '?' + q, function (er, cr, data) {
    if (er)
      return cb(er)

    if (detail)
      data = data.rows.reduce(function (set, row) {
        var k = row.key
        var h = k[0]
        var t = k[1]
        set[h] = set[h] || {}
        set[h][t] = row.value
        return set
      }, {})
    else if (pkg)
      data = (data.rows || []).reduce(function (set, row) {
        var h = row.key[0]
        set[h] = row.value
        return set
      }, {})
    else
      data = (data.rows || []).map(function (r) {
        return r.value
      }).reduce(function (a, b) {
        return a + b
      }, 0)

    if (pkg)
      data = data[pkg]

    if (data === undefined)
      data = 0

    return cb(er, data)
  })
}
<|endoftext|>


<commit_before>'use strict';

const AWS = require('aws-sdk');

// local reference to this module
const utils = module.exports;

// cost of 6+N state transitions (AWS Step Functions)
module.exports.stepFunctionsCost = (nPower) => +(this.stepFunctionsBaseCost() * (6 + nPower)).toFixed(5);

module.exports.stepFunctionsBaseCost = () => {
    const prices = JSON.parse(process.env.sfCosts);
    // assume the AWS_REGION variable is set for this function
    return this.baseCostForRegion(prices, process.env.AWS_REGION);
};

module.exports.allPowerValues = () => {
    const increment = 64;
    const powerValues = [];
    for (let value = 128; value <= 3008; value += increment) {
        powerValues.push(value);
    }
    return powerValues;
};

/**
 * Check whether a Lambda Alias exists or not, and return its data.
 */
module.exports.getLambdaAlias = (lambdaARN, alias) => {
    console.log('Checking alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.getAlias(params).promise();
};

/**
 * Return true if alias exist, false if it doesn't.
 */
module.exports.verifyAliasExistance = async(lambdaARN, alias) => {
    try {
        await utils.getLambdaAlias(lambdaARN, alias);
        return true;
    } catch (error) {
        if (error.code === 'ResourceNotFoundException') {
            // OK, the alias isn't supposed to exist
            console.log('OK, even if missing alias ');
            return false;
        } else {
            console.log('error during alias check:');
            throw error; // a real error :)
        }
    }
};

/**
 * Update power, publish new version, and create/update alias.
 */
module.exports.createPowerConfiguration = async(lambdaARN, value, alias) => {
    try {
        await utils.setLambdaPower(lambdaARN, value);
        const {Version} = await utils.publishLambdaVersion(lambdaARN);
        const aliasExists = await utils.verifyAliasExistance(lambdaARN, alias);
        if (aliasExists) {
            await utils.updateLambdaAlias(lambdaARN, alias, Version);
        } else {
            await utils.createLambdaAlias(lambdaARN, alias, Version);
        }
    } catch (error) {
        if (error.message && error.message.includes('Alias already exists')) {
            // shouldn't happen, but nothing we can do in that case
            console.log('OK, even if: ', error);
        } else {
            console.log('error during config creation for value ' + value);
            throw error; // a real error :)
        }
    }
};

/**
 * Retrieve a given Lambda Function's memory size (always $LATEST version)
 */
module.exports.getLambdaPower = async(lambdaARN) => {
    console.log('Getting current power value');
    const params = {
        FunctionName: lambdaARN,
        Qualifier: '$LATEST',
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    const config = await lambda.getFunctionConfiguration(params).promise();
    return config.MemorySize;
};

/**
 * Update a given Lambda Function's memory size (always $LATEST version).
 */
module.exports.setLambdaPower = (lambdaARN, value) => {
    console.log('Setting power to ', value);
    const params = {
        FunctionName: lambdaARN,
        MemorySize: parseInt(value, 10),
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.updateFunctionConfiguration(params).promise();
};

/**
 * Publish a new Lambda Version (version number will be returned).
 */
module.exports.publishLambdaVersion = (lambdaARN /*, alias*/) => {
    console.log('Publishing new version');
    const params = {
        FunctionName: lambdaARN,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.publishVersion(params).promise();
};

/**
 * Delete a given Lambda Version.
 */
module.exports.deleteLambdaVersion = (lambdaARN, version) => {
    console.log('Deleting version ', version);
    const params = {
        FunctionName: lambdaARN,
        Qualifier: version,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.deleteFunction(params).promise();
};

/**
 * Create a new Lambda Alias and associate it with the given Lambda Version.
 */
module.exports.createLambdaAlias = (lambdaARN, alias, version) => {
    console.log('Creating Alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        FunctionVersion: version,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.createAlias(params).promise();
};

/**
 * Create a new Lambda Alias and associate it with the given Lambda Version.
 */
module.exports.updateLambdaAlias = (lambdaARN, alias, version) => {
    console.log('Updating Alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        FunctionVersion: version,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.updateAlias(params).promise();
};

/**
 * Delete a given Lambda Alias.
 */
module.exports.deleteLambdaAlias = (lambdaARN, alias) => {
    console.log('Deleting alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.deleteAlias(params).promise();
};

/**
 * Invoke a (pre/post-)processor Lambda function and return its output (data.Payload).
 */
module.exports.invokeLambdaProcessor = async(processorARN, payload) => {
    const processorData = await utils.invokeLambda(processorARN, null, payload);
    if (processorData.FunctionError) {
        throw new Error(`Processor ${processorARN} failed with error ${processorData.Payload} and payload ${JSON.stringify(payload)}`);
    }
    return processorData.Payload;
};

/**
 * Wrapper around Lambda function invocation with pre/post-processor functions.
 */
module.exports.invokeLambdaWithProcessors = async(lambdaARN, alias, payload, preARN, postARN) => {
    // first invoke pre-processor, if provided
    if (preARN) {
        console.log('Invoking pre-processor');
        // overwrite payload with pre-processor's output (only if not empty)
        const preProcessorOutput = await utils.invokeLambdaProcessor(preARN, payload);
        if (preProcessorOutput) {
            payload = preProcessorOutput;
        }
    }

    // invoke function to be power-tuned
    const data = await utils.invokeLambda(lambdaARN, alias, payload);

    // then invoke post-processor, if provided
    if (postARN) {
        console.log('Invoking post-processor');
        // note: invocation may have failed (data.FunctionError)
        await utils.invokeLambdaProcessor(postARN, data.Payload);
    }

    return data;
};

/**
 * Invoke a given Lambda Function:Alias with payload and return its logs.
 */
module.exports.invokeLambda = (lambdaARN, alias, payload) => {
    if (!alias) {
        alias = '$LATEST';
    }
    console.log(`Invoking function ${lambdaARN}:${alias} with payload ${JSON.stringify(payload)}`);
    const params = {
        FunctionName: lambdaARN,
        Qualifier: alias,
        Payload: payload,
        LogType: 'Tail', // will return logs
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.invoke(params).promise();
};

/**
 * Generate a list of `num` payloads (repeated or weighted)
 */
module.exports.generatePayloads = (num, payloadInput) => {
    if (Array.isArray(payloadInput)) {
        // if array, generate a list of payloads based on weights

        // fail if empty list or missing weight/payload
        if (payloadInput.length === 0 || payloadInput.some(p => !p.weight || !p.payload)) {
            throw new Error('Invalid weighted payload structure');
        }

        if (num < payloadInput.length) {
            throw new Error(`You have ${payloadInput.length} payloads and only "num"=${num}. Please increase "num".`);
        }

        // we use relative weights (not %), so here we compute the total weight
        const total = payloadInput.map(p => p.weight).reduce((a, b) => a + b, 0);

        // generate an array of num items (to be filled)
        const payloads = utils.range(num);

        // iterate over weighted payloads and fill the array based on relative weight
        let done = 0;
        for (let p of payloadInput) {
            var howMany = Math.floor(p.weight * num / total);
            if (howMany < 1) {
                throw new Error('Invalid payload weight (num is too small)');
            }
            // if it's an uneven division, make sure the last item fills the remaining gap
            const howManyWillBeLeft = num - done - howMany;
            if (howManyWillBeLeft > 0 && howManyWillBeLeft < howMany) {
                howMany += howManyWillBeLeft;
            }
            // finally fill the list with howMany items
            payloads.fill(utils.convertPayload(p.payload), done, done + howMany);
            done += howMany;
        }

        return payloads;
    } else {
        // if not an array, always use the same payload (still generate a list)
        const payloads = utils.range(num);
        payloads.fill(utils.convertPayload(payloadInput), 0, num);
        return payloads;
    }
};

/**
 * Convert payload to string, if it's not a string already
 */
module.exports.convertPayload = (payload) => {
    // optionally convert everything into string
    if (typeof payload !== 'string' && typeof payload !== 'undefined') {
        console.log('Converting payload to string from ', typeof payload);
        payload = JSON.stringify(payload);
    }
    return payload;
};

/**
 * Compute average price and returns with average duration.
 */
module.exports.computePrice = (minCost, minRAM, value, duration) => {
    // compute official price per 100ms
    const pricePer100ms = value * minCost / minRAM;
    // quantize price to upper 100ms (billed duration) and compute avg price
    return Math.ceil(duration / 100) * pricePer100ms;
};

module.exports.parseLogAndExtractDurations = (data) => {
    return data.map(log => {
        const logString = utils.base64decode(log.LogResult || '');
        return utils.extractDuration(logString);
    });
};

/**
 * Copute average duration
 */
module.exports.computeTotalCost = (minCost, minRAM, value, durations) => {
    if (!durations || !durations.length) {
        return 0;
    }

    // compute corresponding cost for each durationo
    const costs = durations.map(duration => utils.computePrice(minCost, minRAM, value, duration));

    // sum all together
    return costs.reduce((a, b) => a + b, 0);
};

/**
 * Copute average duration
 */
module.exports.computeAverageDuration = (durations) => {
    if (!durations || !durations.length) {
        return 0;
    }

    // 20% of durations will be discarted (trimmed mean)
    const toBeDiscarded = parseInt(durations.length * 20 / 100, 10);

    const newN = durations.length - 2 * toBeDiscarded;

    // compute trimmed mean (discard 20% of low/high values)
    const averageDuration = durations
        .sort() // sort numerically
        .slice(toBeDiscarded, -toBeDiscarded) // discard first/last values
        .reduce((a, b) => a + b, 0) // sum all together
        / newN
    ;

    return averageDuration;
};

/**
 * Extract duration (in ms) from a given Lambda's CloudWatch log.
 */
module.exports.extractDuration = (log) => {
    // extract duration from log (anyone can suggest a regex?)
    const durationSplit = log.split('\tDuration: ');
    if (durationSplit.length < 2) return 0;

    const durationStr = durationSplit[1].split(' ms')[0];
    return parseFloat(durationStr);
};

/**
 * Encode a given string to base64.
 */
module.exports.base64decode = (str) => {
    return Buffer.from(str, 'base64').toString();
};

/**
 * Generate a list of size n.
 */
module.exports.range = (n) => {
    if (n === null || typeof n === 'undefined') {
        n = -1;
    }
    return Array.from(Array(n).keys());
};

module.exports.regionFromARN = (arn) => {
    if (typeof arn !== 'string' || arn.split(':').length !== 7) {
        throw new Error('Invalid ARN: ' + arn);
    }
    return arn.split(':')[3];
};

module.exports.lambdaClientFromARN = (lambdaARN) => {
    const region = this.regionFromARN(lambdaARN);
    return new AWS.Lambda({region});
};

/**
 * Generate a URL with encoded stats.
 * Note: the URL hash is never sent to the server.
 */
module.exports.buildVisualizationURL = (stats, baseURL) => {

    function encode(inputList, EncodeType = null) {
        EncodeType = EncodeType || Float32Array;
        inputList = new EncodeType(inputList);
        inputList = new Uint8Array(inputList.buffer);
        return Buffer.from(inputList).toString('base64');
    }

    // sort by power
    stats.sort((p1, p2) => {
        return p1.power - p2.power;
    });

    const sizes = stats.map(p => p.power);
    const times = stats.map(p => p.duration);
    const costs = stats.map(p => p.cost);

    const hash = [
        encode(sizes, Int16Array),
        encode(times),
        encode(costs),
    ].join(';');

    return baseURL + '#' + hash;
};

/**
 * Using the prices supplied,
 * to figure what the base price is for the
 * supplied lambda's region
 */
module.exports.baseCostForRegion = (priceMap, region) => {
    if (priceMap[region]) {
        return priceMap[region];
    }
    console.log(region + ' not found in base price map, using default: ' + priceMap['default']);
    return priceMap['default'];
};
<commit_msg>Revert "improve coverage"<commit_after>'use strict';

const AWS = require('aws-sdk');

// local reference to this module
const utils = module.exports;

// cost of 6+N state transitions (AWS Step Functions)
module.exports.stepFunctionsCost = (nPower) => +(this.stepFunctionsBaseCost() * (6 + nPower)).toFixed(5);

module.exports.stepFunctionsBaseCost = () => {
    const prices = JSON.parse(process.env.sfCosts);
    // assume the AWS_REGION variable is set for this function
    return this.baseCostForRegion(prices, process.env.AWS_REGION);
};

module.exports.allPowerValues = () => {
    const increment = 64;
    const powerValues = [];
    for (let value = 128; value <= 3008; value += increment) {
        powerValues.push(value);
    }
    return powerValues;
};

/**
 * Check whether a Lambda Alias exists or not, and return its data.
 */
module.exports.getLambdaAlias = (lambdaARN, alias) => {
    console.log('Checking alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.getAlias(params).promise();
};

/**
 * Return true if alias exist, false if it doesn't.
 */
module.exports.verifyAliasExistance = async(lambdaARN, alias) => {
    try {
        await utils.getLambdaAlias(lambdaARN, alias);
        return true;
    } catch (error) {
        if (error.code === 'ResourceNotFoundException') {
            // OK, the alias isn't supposed to exist
            console.log('OK, even if missing alias ');
            return false;
        } else {
            console.log('error during alias check:');
            throw error; // a real error :)
        }
    }
};

/**
 * Update power, publish new version, and create/update alias.
 */
module.exports.createPowerConfiguration = async(lambdaARN, value, alias) => {
    try {
        await utils.setLambdaPower(lambdaARN, value);
        const {Version} = await utils.publishLambdaVersion(lambdaARN);
        const aliasExists = await utils.verifyAliasExistance(lambdaARN, alias);
        if (aliasExists) {
            await utils.updateLambdaAlias(lambdaARN, alias, Version);
        } else {
            await utils.createLambdaAlias(lambdaARN, alias, Version);
        }
    } catch (error) {
        if (error.message && error.message.includes('Alias already exists')) {
            // shouldn't happen, but nothing we can do in that case
            console.log('OK, even if: ', error);
        } else {
            console.log('error during config creation for value ' + value);
            throw error; // a real error :)
        }
    }
};

/**
 * Retrieve a given Lambda Function's memory size (always $LATEST version)
 */
module.exports.getLambdaPower = async(lambdaARN) => {
    console.log('Getting current power value');
    const params = {
        FunctionName: lambdaARN,
        Qualifier: '$LATEST',
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    const config = await lambda.getFunctionConfiguration(params).promise();
    return config.MemorySize;
};

/**
 * Update a given Lambda Function's memory size (always $LATEST version).
 */
module.exports.setLambdaPower = (lambdaARN, value) => {
    console.log('Setting power to ', value);
    const params = {
        FunctionName: lambdaARN,
        MemorySize: parseInt(value, 10),
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.updateFunctionConfiguration(params).promise();
};

/**
 * Publish a new Lambda Version (version number will be returned).
 */
module.exports.publishLambdaVersion = (lambdaARN /*, alias*/) => {
    console.log('Publishing new version');
    const params = {
        FunctionName: lambdaARN,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.publishVersion(params).promise();
};

/**
 * Delete a given Lambda Version.
 */
module.exports.deleteLambdaVersion = (lambdaARN, version) => {
    console.log('Deleting version ', version);
    const params = {
        FunctionName: lambdaARN,
        Qualifier: version,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.deleteFunction(params).promise();
};

/**
 * Create a new Lambda Alias and associate it with the given Lambda Version.
 */
module.exports.createLambdaAlias = (lambdaARN, alias, version) => {
    console.log('Creating Alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        FunctionVersion: version,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.createAlias(params).promise();
};

/**
 * Create a new Lambda Alias and associate it with the given Lambda Version.
 */
module.exports.updateLambdaAlias = (lambdaARN, alias, version) => {
    console.log('Updating Alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        FunctionVersion: version,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.updateAlias(params).promise();
};

/**
 * Delete a given Lambda Alias.
 */
module.exports.deleteLambdaAlias = (lambdaARN, alias) => {
    console.log('Deleting alias ', alias);
    const params = {
        FunctionName: lambdaARN,
        Name: alias,
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.deleteAlias(params).promise();
};

/**
 * Invoke a (pre/post-)processor Lambda function and return its output (data.Payload).
 */
module.exports.invokeLambdaProcessor = async(processorARN, payload) => {
    const processorData = await utils.invokeLambda(processorARN, null, payload);
    if (processorData.FunctionError) {
        throw new Error(`Processor ${processorARN} failed with error ${processorData.Payload} and payload ${JSON.stringify(payload)}`);
    }
    return processorData.Payload;
};

/**
 * Wrapper around Lambda function invocation with pre/post-processor functions.
 */
module.exports.invokeLambdaWithProcessors = async(lambdaARN, alias, payload, preARN, postARN) => {
    // first invoke pre-processor, if provided
    if (preARN) {
        console.log('Invoking pre-processor');
        // overwrite payload with pre-processor's output (only if not empty)
        const preProcessorOutput = await utils.invokeLambdaProcessor(preARN, payload);
        if (preProcessorOutput) {
            payload = preProcessorOutput;
        }
    }

    // invoke function to be power-tuned
    const data = await utils.invokeLambda(lambdaARN, alias, payload);

    // then invoke post-processor, if provided
    if (postARN) {
        console.log('Invoking post-processor');
        // note: invocation may have failed (data.FunctionError)
        await utils.invokeLambdaProcessor(postARN, data.Payload);
    }

    return data;
};

/**
 * Invoke a given Lambda Function:Alias with payload and return its logs.
 */
module.exports.invokeLambda = (lambdaARN, alias, payload) => {
    console.log(`Invoking function ${lambdaARN}:${alias || '$LATEST'} with payload ${JSON.stringify(payload)}`);
    const params = {
        FunctionName: lambdaARN,
        Qualifier: alias,
        Payload: payload,
        LogType: 'Tail', // will return logs
    };
    const lambda = utils.lambdaClientFromARN(lambdaARN);
    return lambda.invoke(params).promise();
};

/**
 * Generate a list of `num` payloads (repeated or weighted)
 */
module.exports.generatePayloads = (num, payloadInput) => {
    if (Array.isArray(payloadInput)) {
        // if array, generate a list of payloads based on weights

        // fail if empty list or missing weight/payload
        if (payloadInput.length === 0 || payloadInput.some(p => !p.weight || !p.payload)) {
            throw new Error('Invalid weighted payload structure');
        }

        if (num < payloadInput.length) {
            throw new Error(`You have ${payloadInput.length} payloads and only "num"=${num}. Please increase "num".`);
        }

        // we use relative weights (not %), so here we compute the total weight
        const total = payloadInput.map(p => p.weight).reduce((a, b) => a + b, 0);

        // generate an array of num items (to be filled)
        const payloads = utils.range(num);

        // iterate over weighted payloads and fill the array based on relative weight
        let done = 0;
        for (let p of payloadInput) {
            var howMany = Math.floor(p.weight * num / total);
            if (howMany < 1) {
                throw new Error('Invalid payload weight (num is too small)');
            }
            // if it's an uneven division, make sure the last item fills the remaining gap
            const howManyWillBeLeft = num - done - howMany;
            if (howManyWillBeLeft > 0 && howManyWillBeLeft < howMany) {
                howMany += howManyWillBeLeft;
            }
            // finally fill the list with howMany items
            payloads.fill(utils.convertPayload(p.payload), done, done + howMany);
            done += howMany;
        }

        return payloads;
    } else {
        // if not an array, always use the same payload (still generate a list)
        const payloads = utils.range(num);
        payloads.fill(utils.convertPayload(payloadInput), 0, num);
        return payloads;
    }
};

/**
 * Convert payload to string, if it's not a string already
 */
module.exports.convertPayload = (payload) => {
    // optionally convert everything into string
    if (typeof payload !== 'string' && typeof payload !== 'undefined') {
        console.log('Converting payload to string from ', typeof payload);
        payload = JSON.stringify(payload);
    }
    return payload;
};

/**
 * Compute average price and returns with average duration.
 */
module.exports.computePrice = (minCost, minRAM, value, duration) => {
    // compute official price per 100ms
    const pricePer100ms = value * minCost / minRAM;
    // quantize price to upper 100ms (billed duration) and compute avg price
    return Math.ceil(duration / 100) * pricePer100ms;
};

module.exports.parseLogAndExtractDurations = (data) => {
    return data.map(log => {
        const logString = utils.base64decode(log.LogResult || '');
        return utils.extractDuration(logString);
    });
};

/**
 * Copute average duration
 */
module.exports.computeTotalCost = (minCost, minRAM, value, durations) => {
    if (!durations || !durations.length) {
        return 0;
    }

    // compute corresponding cost for each durationo
    const costs = durations.map(duration => utils.computePrice(minCost, minRAM, value, duration));

    // sum all together
    return costs.reduce((a, b) => a + b, 0);
};

/**
 * Copute average duration
 */
module.exports.computeAverageDuration = (durations) => {
    if (!durations || !durations.length) {
        return 0;
    }

    // 20% of durations will be discarted (trimmed mean)
    const toBeDiscarded = parseInt(durations.length * 20 / 100, 10);

    const newN = durations.length - 2 * toBeDiscarded;

    // compute trimmed mean (discard 20% of low/high values)
    const averageDuration = durations
        .sort() // sort numerically
        .slice(toBeDiscarded, -toBeDiscarded) // discard first/last values
        .reduce((a, b) => a + b, 0) // sum all together
        / newN
    ;

    return averageDuration;
};

/**
 * Extract duration (in ms) from a given Lambda's CloudWatch log.
 */
module.exports.extractDuration = (log) => {
    // extract duration from log (anyone can suggest a regex?)
    const durationSplit = log.split('\tDuration: ');
    if (durationSplit.length < 2) return 0;

    const durationStr = durationSplit[1].split(' ms')[0];
    return parseFloat(durationStr);
};

/**
 * Encode a given string to base64.
 */
module.exports.base64decode = (str) => {
    return Buffer.from(str, 'base64').toString();
};

/**
 * Generate a list of size n.
 */
module.exports.range = (n) => {
    if (n === null || typeof n === 'undefined') {
        n = -1;
    }
    return Array.from(Array(n).keys());
};

module.exports.regionFromARN = (arn) => {
    if (typeof arn !== 'string' || arn.split(':').length !== 7) {
        throw new Error('Invalid ARN: ' + arn);
    }
    return arn.split(':')[3];
};

module.exports.lambdaClientFromARN = (lambdaARN) => {
    const region = this.regionFromARN(lambdaARN);
    return new AWS.Lambda({region});
};

/**
 * Generate a URL with encoded stats.
 * Note: the URL hash is never sent to the server.
 */
module.exports.buildVisualizationURL = (stats, baseURL) => {

    function encode(inputList, EncodeType = null) {
        EncodeType = EncodeType || Float32Array;
        inputList = new EncodeType(inputList);
        inputList = new Uint8Array(inputList.buffer);
        return Buffer.from(inputList).toString('base64');
    }

    // sort by power
    stats.sort((p1, p2) => {
        return p1.power - p2.power;
    });

    const sizes = stats.map(p => p.power);
    const times = stats.map(p => p.duration);
    const costs = stats.map(p => p.cost);

    const hash = [
        encode(sizes, Int16Array),
        encode(times),
        encode(costs),
    ].join(';');

    return baseURL + '#' + hash;
};

/**
 * Using the prices supplied,
 * to figure what the base price is for the
 * supplied lambda's region
 */
module.exports.baseCostForRegion = (priceMap, region) => {
    if (priceMap[region]) {
        return priceMap[region];
    }
    console.log(region + ' not found in base price map, using default: ' + priceMap['default']);
    return priceMap['default'];
};
<|endoftext|>


<commit_before>/*
 * @copyright Adam Benda, 2016
 *
 */

/* global Blob, File, SVGSVGElement, EventTarget */

/**
 * @classdesc SVG Rendering library
 * @class
 */
var SVGRender = function () {};

/**
 * Loads one SVG image from various source.
 * Will wipe out previously loaded image
 * @param {SVGSVGElement |  Blob | File | String} svg - svg element or its source
 * @returns {undefined}
 *
 * @public
 **/
SVGRender.prototype.load = function (svg) {
    /**
     * Loading from a file is asynchronous = do not call render() untill file is loaded.
     * @type {Boolean}
     * @private
     */
    this.loaded = false;

    if (Blob.prototype.isPrototypeOf(svg) || File.prototype.isPrototypeOf(svg)) {
//to be read with FileReader
        if (!svg.type || svg.type !== "image/svg+xml") {
            throw "Wrong file/blob type, should be image/svg+xml, is " + svg.type;
        }

        /**
         * File reader of input File / Blob
         * @type {FileReader}
         * @private
         */
        this.reader = new FileReader();
        this.reader.readAsDataURL(svg);
        this.reader.onload = function () {
            //File was loaded from input to dataURI

            //http://stackoverflow.com/questions/11335460/how-do-i-parse-a-data-url-in-node
            var svgCodeB64 = this.reader.result;
            var regex = /^data:.+\/(.+);base64,(.*)$/;
            var matches = svgCodeB64.match(regex);
            var data = matches[2];
            var svgCode = atob(data);
            this.load.bind(this)(svgCode);
            //Call load function again - but with svg source loaded from file
            return;
        }.bind(this);
        return;

    } else if (typeof svg === "string") {
        //svg xml code
        var svgCode = svg;
        //var svgCode = this.result.replace(/<?xml[^>]*/,"").replace(/<!DOCTYPE[^>]*/,"");

        //We are using document from global namespace
        //todo: cleanup afterwards
        this.svgDivElement = document.createElement('div');
        this.svgDivElement.innerHTML = svgCode;
        var svgElement = this.svgDivElement.children[0];
        document.body.appendChild(this.svgDivElement);
        this.svgDivElement.style.visibility = 'hidden';
        this.load(svgElement);
        return;
    } else if (SVGSVGElement.prototype.isPrototypeOf(svg)) {

        /**
         * SVG element present in document.
         * @type {SVGSVGElement}
         * @private
         */
        this.svgElement = svg;
        this.loaded = true;
        return;
    } else {
        throw "Unknown svg type in svg-render!";
    }
};

/**
 * Start rendering
 * @param {Object} options - contains numbers FPS, time, imagesCount and function progressSignal
 * @param {function} callback
 * @returns {undefined}
 *
 * @public
 */
SVGRender.prototype.render = function (options, callback) {
    if (!options) {
        options = {};
    }

    /**
     * Will be called after rendering is finished
     * @type {function}
     */
    this.callback = callback;
    if (!this.callback) {
        this.callback = function () {};
    }

    if (!this.loaded) {
        //todo: more elegant solution
        setTimeout(this.render.bind(this, options, callback), 100);
        return;
    }

    /**
     * Function to be called repeatedly when frame is rendered.
     * has two parameters; count(total #frames) and doneCount(#frames rendered)
     * @type {function}
     * @private
     */
    this.progressSignal = (options.processSignal || function () {});

    /**
     * begin time (seconds)
     * @type {number}
     * @private
     */
    this.beginMS = (options.begin * 1000 || 0); //default begin time

    /**
     * Frames per Second
     * @type {number}
     * @private
     */
    this.FPS = (options.FPS || 60); //default FPS

    /**
     * total time in miliseconds
     * @type {number}
     * @private
     */
    this.timeMS = (options.time * 1000 || 1000);

    /**
     * Number of frames to render
     * @type {number}
     * @private
     */
    this.imagesCount = Math.round(this.FPS * this.timeMS / 1000);

    if (options.imagesCount && options.imagesCount !== this.imagesCount) {
        //imagesCount was given
        if (options.time && options.FPS) {
            //FPS+time were also given and the tree given parameters are contradicting
            throw("Conflicting parameters FPS,time,imagesCount");
        } else if (options.time) {
            this.FPS = this.imagesCount * 1000 / this.timeMS;
        } else if (options.FPS) {
            this.timeMS = this.imagesCount * 1000 / this.FPS;
        }
    }

    /**
     * Time in miliseconds from the animation start time.
     * @type {int}
     * @private
     */
    this.SVGtime = 0; //in miliseconds

    /**
     * Number of already rendered images
     * @type {int}
     * @public
     */
    this.imagesDoneCount = 0;

    /**
     * Array of all rendered images in png format
     * @type {base64}
     * @public
     */
    this.images = [];

    /**
     * Array of all rendered images in png format
     * @type {number}
     * @private
     */
    this.nextFrame = setTimeout(this.renderNextFrame.bind(this), 0);

    /**
     * Canvas to draw on (optional - drawing is invisible if not provided)
     * @type {HTMLCanvasElement}
     * @private
     */
    this.canvas = (options.canvas || document.createElement('canvas')); //default begin time

};

/**
 * Goes through DOM tree of given HTMLElement and removes specific tags
 * @param {HTMLElement} htmlElement
 * @param {String[]} tags
 * @return {integer} - number of elements removed
 *
 * @private
 */
SVGRender.prototype.filterOut = function (htmlElement, tags) {
    var ret = 0;
    if (tags.indexOf(htmlElement.tagName) >= 0) {
        htmlElement.parentNode.removeChild(htmlElement);
        return 1;
    } else {
        //call filterOut recursively
        for (var i = 0; i < htmlElement.children.length; i++) {
            ret += this.filterOut(htmlElement.children[i], tags);
        }
    }
    return ret;
};

/**
 * Render next frame and schedule next run of render next frame
 * @returns {undefined}
 * @private
 */
SVGRender.prototype.renderNextFrame = function () {
    if (!this.svgElement) {
        throw "Cannot render - no svgElement loaded!";
    }

    this.SVGtime = this.beginMS + Math.round(1000 * this.imagesDoneCount) / (this.FPS);

    this.svgElement.pauseAnimations();
    this.svgElement.setCurrentTime(this.SVGtime / 1000);
    this.svgElement.forceRedraw();
    //Do deep copy of svgElement!
    var svgElementNew = this.svgElement.cloneNode(true);
    //maybe unnescessary
    svgElementNew.pauseAnimations();
    svgElementNew.setCurrentTime(this.SVGtime / 1000);
    svgElementNew.forceRedraw();

    this.filterOut(svgElementNew, "animate");
    this.filterOut(svgElementNew, "animateTransform");
    this.filterOut(svgElementNew, "animateMotion");
    this.filterOut(svgElementNew, "animateColor");

    var svgString = new XMLSerializer().serializeToString(svgElementNew);
    this.svgImage = new Image();
    this.svgImage.onload = function () {

        tmpCanvasx = this.canvas.getContext('2d');
        this.canvas.width = this.svgImage.width;
        this.canvas.height = this.svgImage.height;
        tmpCanvasx.drawImage(this.svgImage, 0, 0);
        //image now in tmpCanvas

        //signalize progress
        if (this.progressSignal && typeof this.progressSignal === "function") {
            this.progressSignal(this.imagesDoneCount, this.imagesCount);
        }

        // console.log("Out " + this.imagesDoneCount);
        this.images[this.imagesDoneCount++] = this.canvas.toDataURL("image/png").replace(/^data:.+\/(.+);base64,/, "");
        if (this.imagesDoneCount < this.imagesCount) {
            nextFrame = setTimeout(this.renderNextFrame.bind(this), 0);
        } else {
            this.callback();
        }
    }.bind(this);

    this.svgImage.src = "data:image/svg+xml;base64," + btoa("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n\
        <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">" + svgString);
};<commit_msg>Fixed progressSignal<commit_after>/*
 * @copyright Adam Benda, 2016
 *
 */

/* global Blob, File, SVGSVGElement, EventTarget */

/**
 * @classdesc SVG Rendering library
 * @class
 */
var SVGRender = function () {};

/**
 * Loads one SVG image from various source.
 * Will wipe out previously loaded image
 * @param {SVGSVGElement |  Blob | File | String} svg - svg element or its source
 * @returns {undefined}
 *
 * @public
 **/
SVGRender.prototype.load = function (svg) {
    /**
     * Loading from a file is asynchronous = do not call render() untill file is loaded.
     * @type {Boolean}
     * @private
     */
    this.loaded = false;

    if (Blob.prototype.isPrototypeOf(svg) || File.prototype.isPrototypeOf(svg)) {
//to be read with FileReader
        if (!svg.type || svg.type !== "image/svg+xml") {
            throw "Wrong file/blob type, should be image/svg+xml, is " + svg.type;
        }

        /**
         * File reader of input File / Blob
         * @type {FileReader}
         * @private
         */
        this.reader = new FileReader();
        this.reader.readAsDataURL(svg);
        this.reader.onload = function () {
            //File was loaded from input to dataURI

            //http://stackoverflow.com/questions/11335460/how-do-i-parse-a-data-url-in-node
            var svgCodeB64 = this.reader.result;
            var regex = /^data:.+\/(.+);base64,(.*)$/;
            var matches = svgCodeB64.match(regex);
            var data = matches[2];
            var svgCode = atob(data);
            this.load.bind(this)(svgCode);
            //Call load function again - but with svg source loaded from file
            return;
        }.bind(this);
        return;

    } else if (typeof svg === "string") {
        //svg xml code
        var svgCode = svg;
        //var svgCode = this.result.replace(/<?xml[^>]*/,"").replace(/<!DOCTYPE[^>]*/,"");

        //We are using document from global namespace
        //todo: cleanup afterwards
        this.svgDivElement = document.createElement('div');
        this.svgDivElement.innerHTML = svgCode;
        var svgElement = this.svgDivElement.children[0];
        document.body.appendChild(this.svgDivElement);
        this.svgDivElement.style.visibility = 'hidden';
        this.load(svgElement);
        return;
    } else if (SVGSVGElement.prototype.isPrototypeOf(svg)) {

        /**
         * SVG element present in document.
         * @type {SVGSVGElement}
         * @private
         */
        this.svgElement = svg;
        this.loaded = true;
        return;
    } else {
        throw "Unknown svg type in svg-render!";
    }
};

/**
 * Start rendering
 * @param {Object} options - contains numbers FPS, time, imagesCount and function progressSignal
 * @param {function} callback
 * @returns {undefined}
 *
 * @public
 */
SVGRender.prototype.render = function (options, callback) {
    if (!options) {
        options = {};
    }

    /**
     * Will be called after rendering is finished
     * @type {function}
     */
    this.callback = callback;
    if (!this.callback) {
        this.callback = function () {};
    }

    if (!this.loaded) {
        //todo: more elegant solution
        setTimeout(this.render.bind(this, options, callback), 100);
        return;
    }

    /**
     * Function to be called repeatedly when frame is rendered.
     * has two parameters; count(total #frames) and doneCount(#frames rendered)
     * @type {function}
     * @private
     */
    this.progressSignal = (options.progressSignal || function () {});

    /**
     * begin time (seconds)
     * @type {number}
     * @private
     */
    this.beginMS = (options.begin * 1000 || 0); //default begin time

    /**
     * Frames per Second
     * @type {number}
     * @private
     */
    this.FPS = (options.FPS || 60); //default FPS

    /**
     * total time in miliseconds
     * @type {number}
     * @private
     */
    this.timeMS = (options.time * 1000 || 1000);

    /**
     * Number of frames to render
     * @type {number}
     * @private
     */
    this.imagesCount = Math.round(this.FPS * this.timeMS / 1000);

    if (options.imagesCount && options.imagesCount !== this.imagesCount) {
        //imagesCount was given
        if (options.time && options.FPS) {
            //FPS+time were also given and the tree given parameters are contradicting
            throw("Conflicting parameters FPS,time,imagesCount");
        } else if (options.time) {
            this.FPS = this.imagesCount * 1000 / this.timeMS;
        } else if (options.FPS) {
            this.timeMS = this.imagesCount * 1000 / this.FPS;
        }
    }

    /**
     * Time in miliseconds from the animation start time.
     * @type {int}
     * @private
     */
    this.SVGtime = 0; //in miliseconds

    /**
     * Number of already rendered images
     * @type {int}
     * @public
     */
    this.imagesDoneCount = 0;

    /**
     * Array of all rendered images in png format
     * @type {base64}
     * @public
     */
    this.images = [];

    /**
     * Array of all rendered images in png format
     * @type {number}
     * @private
     */
    this.nextFrame = setTimeout(this.renderNextFrame.bind(this), 0);

    /**
     * Canvas to draw on (optional - drawing is invisible if not provided)
     * @type {HTMLCanvasElement}
     * @private
     */
    this.canvas = (options.canvas || document.createElement('canvas')); //default begin time

};

/**
 * Goes through DOM tree of given HTMLElement and removes specific tags
 * @param {HTMLElement} htmlElement
 * @param {String[]} tags
 * @return {integer} - number of elements removed
 *
 * @private
 */
SVGRender.prototype.filterOut = function (htmlElement, tags) {
    var ret = 0;
    if (tags.indexOf(htmlElement.tagName) >= 0) {
        htmlElement.parentNode.removeChild(htmlElement);
        return 1;
    } else {
        //call filterOut recursively
        for (var i = 0; i < htmlElement.children.length; i++) {
            ret += this.filterOut(htmlElement.children[i], tags);
        }
    }
    return ret;
};

/**
 * Render next frame and schedule next run of render next frame
 * @returns {undefined}
 * @private
 */
SVGRender.prototype.renderNextFrame = function () {
    if (!this.svgElement) {
        throw "Cannot render - no svgElement loaded!";
    }

    this.SVGtime = this.beginMS + Math.round(1000 * this.imagesDoneCount) / (this.FPS);

    this.svgElement.pauseAnimations();
    this.svgElement.setCurrentTime(this.SVGtime / 1000);
    this.svgElement.forceRedraw();
    //Do deep copy of svgElement!
    var svgElementNew = this.svgElement.cloneNode(true);
    //maybe unnescessary
    svgElementNew.pauseAnimations();
    svgElementNew.setCurrentTime(this.SVGtime / 1000);
    svgElementNew.forceRedraw();

    this.filterOut(svgElementNew, "animate");
    this.filterOut(svgElementNew, "animateTransform");
    this.filterOut(svgElementNew, "animateMotion");
    this.filterOut(svgElementNew, "animateColor");

    var svgString = new XMLSerializer().serializeToString(svgElementNew);
    this.svgImage = new Image();
    this.svgImage.onload = function () {

        tmpCanvasx = this.canvas.getContext('2d');
        this.canvas.width = this.svgImage.width;
        this.canvas.height = this.svgImage.height;
        tmpCanvasx.drawImage(this.svgImage, 0, 0);
        //image now in tmpCanvas

        //signalize progress
        if (this.progressSignal && typeof this.progressSignal === "function") {
            this.progressSignal(this.imagesDoneCount, this.imagesCount);
        }

        // console.log("Out " + this.imagesDoneCount);
        this.images[this.imagesDoneCount++] = this.canvas.toDataURL("image/png").replace(/^data:.+\/(.+);base64,/, "");
        if (this.imagesDoneCount < this.imagesCount) {
            nextFrame = setTimeout(this.renderNextFrame.bind(this), 0);
        } else {
            this.callback();
        }
    }.bind(this);

    this.svgImage.src = "data:image/svg+xml;base64," + btoa("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n\
        <!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">" + svgString);
};<|endoftext|>


<commit_before>/**
 * Initialize the test suite by first launching the mock api server, and when
 * it's up, fire off `testem ci`, which will run the test suite in up to five
 * browsers concurrently. cha-ching!
 */

'use strict';
var server;
var testem;

var cp = require('child_process');
var errorDie = function(err) {
    var code = err.code === undefined ? 1 : err.code;
    console.log('Failed to start child process', err);
    process.exit(code);
};
var closeChild = function(code) {
    if (code > 0) {
        errorDie({ message: 'mock-api-server exited', code: code });
    }
};
var testemExit = function(code) {
    console.log('test run complete, closing server (exit code ' + code + ')');
    server.kill();
    return closeChild(code);
};
var spawnTestem = function() {
    testem = cp.spawn('testem', ['ci', '-p', '7575', '-P', '5'], { stdio: 'inherit' });
    testem.on('error', errorDie);
    testem.on('exit', testemExit);
    console.log('Spawned testem, pid: ' + testem.pid);
};

var server = cp.fork('test/mock-api-server.js', { stdio: 'inherit' });
console.log('Spawned mock-api-server, pid: ' + server.pid);
server.on('error', errorDie);
server.on('exit', closeChild);
server.on('message', function handleMsg(msg) {
    if (msg && msg.status === 'READY') {
        console.log('server up! booting testem...');
        return spawnTestem();
    }
    throw new ReferenceError('unhandled msg: ' + JSON.stringify(msg));
});

// assert child processes exit when this process exits
process.on('exit', function() {
    server && server.kill();
    testem && testem.kill();
});
<commit_msg>specify local testem binary<commit_after>/**
 * Initialize the test suite by first launching the mock api server, and when
 * it's up, fire off `testem ci`, which will run the test suite in up to five
 * browsers concurrently. cha-ching!
 */

'use strict';
var server;
var testem;

var cp = require('child_process');
var errorDie = function(err) {
    var code = err.code === undefined ? 1 : err.code;
    console.log('Failed to start child process', err);
    process.exit(code);
};
var closeChild = function(code) {
    if (code > 0) {
        errorDie({ message: 'mock-api-server exited', code: code });
    }
};
var testemExit = function(code) {
    console.log('test run complete, closing server (exit code ' + code + ')');
    server.kill();
    return closeChild(code);
};
var spawnTestem = function() {
    testem = cp.spawn(
        'node_modules/.bin/testem', ['ci', '-p', '7575', '-P', '5'],
        { stdio: 'inherit' }
    );
    testem.on('error', errorDie);
    testem.on('exit', testemExit);
    console.log('Spawned testem, pid: ' + testem.pid);
};

var server = cp.fork('test/mock-api-server.js', { stdio: 'inherit' });
console.log('Spawned mock-api-server, pid: ' + server.pid);
server.on('error', errorDie);
server.on('exit', closeChild);
server.on('message', function handleMsg(msg) {
    if (msg && msg.status === 'READY') {
        console.log('server up! booting testem...');
        return spawnTestem();
    }
    throw new ReferenceError('unhandled msg: ' + JSON.stringify(msg));
});

// assert child processes exit when this process exits
process.on('exit', function() {
    server && server.kill();
    testem && testem.kill();
});
<|endoftext|>


<commit_before>import Frame from 'react-frame-component';
import React from 'react';

import './FormWrapper.css';
import styles from './styles';
import { themeContext } from './ThemeContext';

export default function FormWrapper({ children }) {
  const theme = React.useContext(themeContext);
  const content = (
    <React.Fragment>
      {children}
      {styles[theme]}
    </React.Fragment>
  );

  if (theme === 'material') {
    return
;
  }

  return <Frame className="FormWrapper" children={content} />;
}
<commit_msg>Applied antd rendering issue fix.<commit_after>import ConfigProvider from 'antd/lib/config-provider';
import Frame, { FrameContextConsumer } from 'react-frame-component';
import React from 'react';

import './FormWrapper.css';
import styles from './styles';
import { themeContext } from './ThemeContext';

export default function FormWrapper({ children }) {
  const theme = React.useContext(themeContext);
  const content = (
    <React.Fragment>
      {children}
      {styles[theme]}
    </React.Fragment>
  );

  if (theme === 'material') {
    return
;
  } else if (theme === 'antd') {
    return (
      <Frame className="FormWrapper">
        <FrameContextConsumer>
          {/*
            Provides iframe's `window` and `document` instance
            since Ant Design appends `<div>` to default (top most) `document.body`,
            we have to pass `getPopupContainer` `document.body` specific to
            rendered `iframe` in order to work as intended.
          */}
          {({ document }) => (
            <ConfigProvider getPopupContainer={() => document.body}>
              {content}
            </ConfigProvider>
          )}
        </FrameContextConsumer>
      </Frame>
    );
  }

  return <Frame className="FormWrapper" children={content} />;
}
<|endoftext|>