<commit_before>var util = require('util'),
    Tab = require('../client/tab').Tab,
    rewriter = require('../util/jsonrewriter');

var BalanceTab = function ()
{
  Tab.call(this);
};

util.inherits(BalanceTab, Tab);

BalanceTab.prototype.mainMenu = 'wallet';

BalanceTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/balance.jade')();
};

BalanceTab.prototype.angular = function (module)
{
  var self = this;
  var app = this.app;

  module.controller('BalanceCtrl', ['$scope', 'rpId', 'rpNetwork',
                                     function ($scope, $id, $network)
  {
    if (!$id.loginStatus) return $id.goId();

    $scope.transactions = [];
    $scope.current_page = 1;

    // filter effect types
    // Show only offer_funded, offer_partially_funded, offer_cancelled, offer_bought side effects
    var filterEffects = function (events) {
      var transactions = [];

      $.each(events,function(){
        var event = this;
        var effects = [];

        if (event.effects) {
          $.each(event.effects, function(){
            var effect = this;
            if (effect.type == 'offer_funded'
                || effect.type == 'offer_partially_funded'
                || effect.type == 'offer_cancelled'
                || effect.type == 'offer_bought') {
              effects.push(effect);
            }
          });

          event.effects = effects;
        }

        if (effects.length || event.transaction) {
          transactions.push(event);
        }
      });

      return transactions;
    };

    // First page transactions
    $scope.$watch('events', function(){
      if ($scope.transactions.length === 0) {
        $scope.transactions = filterEffects($scope.events);
      }
    }, true);

    $scope.$watch('history_count', function(){
      // Pages count
      $scope.pages_count = Math.ceil($scope.history_count / Options.transactions_per_page);

      // Next page number
      if (!$scope.next_page && $scope.pages_count > 1) {
        $scope.next_page = 2;
      }
    }, true);

    $scope.goToPage = function(page) {
      // Click on disabled links
      if (!page) return;

      var account = $id.account;
      var offset = (page - 1) * Options.transactions_per_page;

      // Next, prev page numbers
      $scope.prev_page = page > 1 ? page-1 : 0;
      $scope.next_page = page < $scope.pages_count ? page+1 : 0;

      $scope.current_page = page;

      // Loading mode
      $scope.loading = true;

      $network.remote.request_account_tx({
        'account': account,
        'ledger_index_min': 0,
        'ledger_index_max': 9999999,
        'descending': true,
        'offset': offset,
        'limit': Options.transactions_per_page
      })
        .on('success', function(data) {
            $scope.transactions = [];
            $scope.$apply(function () {
              if (data.transactions) {
                var transactions = [];

                data.transactions.forEach(function (e) {
                  var tx = rewriter.processTxn(e.tx, e.meta, account);
                  if (tx) {
                    transactions.push(tx);
                  }
                });

                $scope.transactions = filterEffects(transactions);

                // Loading mode
                $scope.loading = false;
              }
            });
        })
        .on('error', function(err){console.log(err);}).request();
    }
  }]);
};

module.exports = BalanceTab;
<commit_msg>Balance tab: Activity update bug fix. #431<commit_after>var util = require('util'),
    Tab = require('../client/tab').Tab,
    rewriter = require('../util/jsonrewriter');

var BalanceTab = function ()
{
  Tab.call(this);
};

util.inherits(BalanceTab, Tab);

BalanceTab.prototype.mainMenu = 'wallet';

BalanceTab.prototype.generateHtml = function ()
{
  return require('../../jade/tabs/balance.jade')();
};

BalanceTab.prototype.angular = function (module)
{
  var self = this;
  var app = this.app;

  module.controller('BalanceCtrl', ['$scope', 'rpId', 'rpNetwork',
                                     function ($scope, $id, $network)
  {
    if (!$id.loginStatus) return $id.goId();

    $scope.transactions = [];
    $scope.current_page = 1;

    // filter effect types
    // Show only offer_funded, offer_partially_funded, offer_cancelled, offer_bought side effects
    var filterEffects = function (events) {
      var transactions = [];

      $.each(events,function(){
        var event = this;
        var effects = [];

        if (event.effects) {
          $.each(event.effects, function(){
            var effect = this;
            if (effect.type == 'offer_funded'
                || effect.type == 'offer_partially_funded'
                || effect.type == 'offer_cancelled'
                || effect.type == 'offer_bought') {
              effects.push(effect);
            }
          });

          event.effects = effects;
        }

        if (effects.length || event.transaction) {
          transactions.push(event);
        }
      });

      return transactions;
    };

    // First page transactions
    $scope.$watch('events', function(){
      if (1 === $scope.current_page) {
        $scope.transactions = filterEffects($scope.events);
      }
    }, true);

    $scope.$watch('history_count', function(){
      // Pages count
      $scope.pages_count = Math.ceil($scope.history_count / Options.transactions_per_page);

      // Next page number
      if (!$scope.next_page && $scope.pages_count > 1) {
        $scope.next_page = 2;
      }
    }, true);

    $scope.goToPage = function(page) {
      // Click on disabled links
      if (!page) return;

      var account = $id.account;
      var offset = (page - 1) * Options.transactions_per_page;

      // Next, prev page numbers
      $scope.prev_page = page > 1 ? page-1 : 0;
      $scope.next_page = page < $scope.pages_count ? page+1 : 0;

      $scope.current_page = page;

      // Loading mode
      $scope.loading = true;

      $network.remote.request_account_tx({
        'account': account,
        'ledger_index_min': 0,
        'ledger_index_max': 9999999,
        'descending': true,
        'offset': offset,
        'limit': Options.transactions_per_page
      })
        .on('success', function(data) {
          $scope.transactions = [];
          $scope.$apply(function () {
            if (data.transactions) {
              var transactions = [];

              data.transactions.forEach(function (e) {
                var tx = rewriter.processTxn(e.tx, e.meta, account);
                if (tx) {
                  transactions.push(tx);
                }
              });

              $scope.transactions = filterEffects(transactions);

              // Loading mode
              $scope.loading = false;
            }
          });
        })
        .on('error', function(err){console.log(err);}).request();
    }
  }]);
};

module.exports = BalanceTab;
<|endoftext|>


<commit_before>const grunt = require("grunt");

function uniq(names) {
    return [...new Set(names)];
}

function sync(patterns) {
    return uniq(grunt.file.expand({ filter: path => !new RegExp(/__test__/g).test(path) }, patterns)).map(name => `./${name}`);
}

const fixJs = "./dist/fix/fix.js";
const fixProxyJs = './dist/fix/fix.proxy.js';
const fixIEJs = "./dist/fix/fix.ie.js";
const fixCompact = "./dist/fix/fix.compact.js";
const fixIECompact = './dist/fix/fix.compact.ie.js';
const workerCompact = './dist/fix/worker.compact.js';
const lodashJs = "src/core/1.lodash.js";

const basicAttachmentMap = {
    polyfill: sync(["src/core/0.foundation.js", "src/polyfill/**/*.js"]).concat(["@babel/polyfill", "es6-promise/auto"]),
    polyfillIE: sync(["src/core/0.foundation.js", "src/polyfill/**/*.js"]).concat([
        "core-js/features/object/define-property",
        "core-js/features/object/create",
        "core-js/features/object/assign",
        "core-js/features/object/get-own-property-symbols",
        "core-js/features/object/get-prototype-of",
        "core-js/features/array/for-each",
        "core-js/features/array/index-of",
        "core-js/features/function/bind",
        "core-js/features/promise",
        "core-js/features/string/replace",
        "core-js/es/map",
        // "core-js",
    ]),
    core: sync([
        "src/less/core/**/*.less",
        "src/less/theme/**/*.less",
        lodashJs,
        "src/core/**/*.js",
        "src/data/**/*.js",
    ]),
    // 最基础的控件
    base: sync([
        "src/less/base/**/*.less",
        "src/third/**/*.js",
        "src/base/**/*.js",
    ]),
    // 实现好的一些基础实例
    case: sync([
        "src/case/**/*.js",
    ]),
    widget: sync([
        "src/less/widget/**/*.less",
        "src/widget/**/*.js",
        "src/component/**/*.js",
    ]),
    router: sync([
        "src/router/**/*.js",
    ]),
    'core_without_normalize': sync(
        ["src/less/core/**/*.less", "src/less/theme/**/*.less", "!src/less/core/normalize.less", "!src/less/core/normalize2.less"],
    ),
    resource: sync(["src/less/resource/**/*.less"]),
    font: sync(["public/less/font.less"]),
    ts: ['./typescript/bundle.ts'],
    ui: sync([
        'ui/less/app.less',
        'ui/less/**/*.less',
        'ui/js/**/*.js',
    ]),
    config: sync(["demo/version.js", "i18n/i18n.cn.js"]),
    utils: sync([
        "src/core/0.foundation.js",
        lodashJs,
        "src/core/constant/**/*.js",
        "src/core/func/**/*.js",
        "src/core/2.base.js",
        "src/core/3.ob.js",
        "src/core/6.inject.js",
        "src/core/utils/*.js",
        "i18n/i18n.cn.js",
        "_mobile/date.i18n.cn.js",
        "src/data/**/*.js",
    ]),
    fix: [fixJs],
    fixProxy: [fixProxyJs],
    fixIE: [fixIEJs],
};

const bundle = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    sync(["public/less/app.less", "public/less/**/*.less"]),
    [fixCompact, workerCompact],
    basicAttachmentMap.router,
    sync(["public/js/**/*.js", "public/js/index.js", "i18n/i18n.cn.js"]),
    basicAttachmentMap.ts,
);

const coreJs = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    ['./dist/fix/fix.compact.js'],
    basicAttachmentMap.router,
    basicAttachmentMap.ts,
);

const resource = sync(["private/less/app.less", "private/less/**/*.less"]);

const config = sync(["public/js/**/*.js", "public/js/index.js", "i18n/i18n.cn.js"]);

const bundleIE = [].concat(
    basicAttachmentMap.polyfillIE,
    basicAttachmentMap.core,
    basicAttachmentMap.fixIE,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    sync(["public/less/app.less", "public/less/**/*.less"]),
    [fixIECompact, workerCompact],
    basicAttachmentMap.router,
    sync(["public/js/**/*.js", "public/js/index.js", "i18n/i18n.cn.js"]),
    basicAttachmentMap.ts,
);

const bundleWithoutNormalize = [].concat(
    basicAttachmentMap.core_without_normalize,
    sync([
        "src/less/base/**/*.less",
        "src/less/widget/**/*.less",
        "public/less/**/*.less",
        "!public/less/app.less",
        // ts的less
    ]),
);

const fineuiWithoutNormalize = [].concat(
    basicAttachmentMap.core_without_normalize,
    sync([
        "src/less/base/**/*.less",
        "src/less/widget/**/*.less",
    ]),
);

const fineui = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    basicAttachmentMap.router,
    [fixCompact, workerCompact],
    basicAttachmentMap.ui,
    basicAttachmentMap.ts,
);

const fineuiProxy = [].concat(
    basicAttachmentMap.core,
    basicAttachmentMap.fixProxy,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    basicAttachmentMap.router,
    [fixCompact, workerCompact],
    basicAttachmentMap.ui,
    basicAttachmentMap.ts,
);

const fineuiIE = [].concat(
    basicAttachmentMap.polyfillIE,
    basicAttachmentMap.core,
    basicAttachmentMap.fixIE,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    basicAttachmentMap.router,
    [fixIECompact, workerCompact],
    basicAttachmentMap.ui,
    basicAttachmentMap.ts,
);

const fineuiWithoutJqueryAndPolyfillJs = [].concat(
    sync([
        "src/core/foundation.js",
        lodashJs,
        "src/core/**/*.js",
        "src/data/**/*.js",
        "dist/fix/fix.js",
        "src/base/**/*.js",
        "src/case/**/*.js",

        "!src/core/platform/web/**/*.js",
        "!src/base/single/input/file.js",
        "!src/case/ztree/**/*.js",
    ]),
    basicAttachmentMap.widget,
    sync([fixCompact, workerCompact, "ui/js/**/*.js"]),
    basicAttachmentMap.ts,
);

const demo = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.router,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    sync(["public/less/app.less", "public/less/**/*.less"]),
    [fixCompact, workerCompact],
    basicAttachmentMap.config,
    basicAttachmentMap.ts,
    sync(["demo/less/*.less", "demo/less/**/*.less", "demo/app.js", "demo/js/**/*.js", "demo/config.js"]),
);

module.exports = {
    fix: fixJs,
    fixProxy: fixProxyJs,
    fixIE: fixIEJs,
    lodash: lodashJs,
    font: basicAttachmentMap.font,
    bundle: uniq(bundle),
    bundleIE: uniq(bundleIE),
    fineuiWithoutNormalize: uniq(fineuiWithoutNormalize),
    bundleWithoutNormalize: uniq(bundleWithoutNormalize),
    fineui: uniq(fineui),
    fineuiProxy: uniq(fineuiProxy),
    fineuiIE: uniq(fineuiIE),
    fineuiWithoutJqueryAndPolyfillJs: uniq(fineuiWithoutJqueryAndPolyfillJs),
    utils: uniq(basicAttachmentMap.utils),
    demo: uniq(demo),
    coreJs: uniq(coreJs),
    resource: uniq((resource)),
    config: uniq(config),
};
<commit_msg>chore: 少了点css<commit_after>const grunt = require("grunt");

function uniq(names) {
    return [...new Set(names)];
}

function sync(patterns) {
    return uniq(grunt.file.expand({ filter: path => !new RegExp(/__test__/g).test(path) }, patterns)).map(name => `./${name}`);
}

const fixJs = "./dist/fix/fix.js";
const fixProxyJs = './dist/fix/fix.proxy.js';
const fixIEJs = "./dist/fix/fix.ie.js";
const fixCompact = "./dist/fix/fix.compact.js";
const fixIECompact = './dist/fix/fix.compact.ie.js';
const workerCompact = './dist/fix/worker.compact.js';
const lodashJs = "src/core/1.lodash.js";

const basicAttachmentMap = {
    polyfill: sync(["src/core/0.foundation.js", "src/polyfill/**/*.js"]).concat(["@babel/polyfill", "es6-promise/auto"]),
    polyfillIE: sync(["src/core/0.foundation.js", "src/polyfill/**/*.js"]).concat([
        "core-js/features/object/define-property",
        "core-js/features/object/create",
        "core-js/features/object/assign",
        "core-js/features/object/get-own-property-symbols",
        "core-js/features/object/get-prototype-of",
        "core-js/features/array/for-each",
        "core-js/features/array/index-of",
        "core-js/features/function/bind",
        "core-js/features/promise",
        "core-js/features/string/replace",
        "core-js/es/map",
        // "core-js",
    ]),
    core: sync([
        "src/less/core/**/*.less",
        "src/less/theme/**/*.less",
        lodashJs,
        "src/core/**/*.js",
        "src/data/**/*.js",
    ]),
    // 最基础的控件
    base: sync([
        "src/less/base/**/*.less",
        "src/third/**/*.js",
        "src/base/**/*.js",
    ]),
    // 实现好的一些基础实例
    case: sync([
        "src/case/**/*.js",
    ]),
    widget: sync([
        "src/less/widget/**/*.less",
        "src/widget/**/*.js",
        "src/component/**/*.js",
    ]),
    router: sync([
        "src/router/**/*.js",
    ]),
    'core_without_normalize': sync(
        ["src/less/core/**/*.less", "src/less/theme/**/*.less", "!src/less/core/normalize.less", "!src/less/core/normalize2.less"],
    ),
    resource: sync(["src/less/resource/**/*.less"]),
    font: sync(["public/less/font.less"]),
    ts: ['./typescript/bundle.ts'],
    ui: sync([
        'ui/less/app.less',
        'ui/less/**/*.less',
        'ui/js/**/*.js',
    ]),
    config: sync(["demo/version.js", "i18n/i18n.cn.js"]),
    utils: sync([
        "src/core/0.foundation.js",
        lodashJs,
        "src/core/constant/**/*.js",
        "src/core/func/**/*.js",
        "src/core/2.base.js",
        "src/core/3.ob.js",
        "src/core/6.inject.js",
        "src/core/utils/*.js",
        "i18n/i18n.cn.js",
        "_mobile/date.i18n.cn.js",
        "src/data/**/*.js",
    ]),
    fix: [fixJs],
    fixProxy: [fixProxyJs],
    fixIE: [fixIEJs],
};

const bundle = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    sync(["public/less/app.less", "public/less/**/*.less"]),
    [fixCompact, workerCompact],
    basicAttachmentMap.router,
    sync(["public/js/**/*.js", "public/js/index.js", "i18n/i18n.cn.js"]),
    basicAttachmentMap.ts,
);

const coreJs = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    ['./dist/fix/fix.compact.js'],
    basicAttachmentMap.router,
    basicAttachmentMap.ts,
);

const resource = sync(["private/less/app.less", "private/less/**/*.less"]);

const config = sync(["public/js/**/*.js", "public/js/index.js", "i18n/i18n.cn.js"]);

const bundleIE = [].concat(
    basicAttachmentMap.polyfillIE,
    basicAttachmentMap.core,
    basicAttachmentMap.fixIE,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    sync(["public/less/app.less", "public/less/**/*.less"]),
    [fixIECompact, workerCompact],
    basicAttachmentMap.router,
    sync(["public/js/**/*.js", "public/js/index.js", "i18n/i18n.cn.js"]),
    basicAttachmentMap.ts,
);

const bundleWithoutNormalize = [].concat(
    basicAttachmentMap.core_without_normalize,
    sync([
        "src/less/base/**/*.less",
        "src/less/widget/**/*.less",
        "public/less/**/*.less",
        "!public/less/app.less",
        // ts的less
    ]),
);

const fineuiWithoutNormalize = [].concat(
    basicAttachmentMap.core_without_normalize,
    sync([
        "src/less/base/**/*.less",
        "src/less/widget/**/*.less",
        'ui/less/app.less',
        'ui/less/**/*.less',
    ]),
);

const fineui = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    basicAttachmentMap.router,
    [fixCompact, workerCompact],
    basicAttachmentMap.ui,
    basicAttachmentMap.ts,
);

const fineuiProxy = [].concat(
    basicAttachmentMap.core,
    basicAttachmentMap.fixProxy,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    basicAttachmentMap.router,
    [fixCompact, workerCompact],
    basicAttachmentMap.ui,
    basicAttachmentMap.ts,
);

const fineuiIE = [].concat(
    basicAttachmentMap.polyfillIE,
    basicAttachmentMap.core,
    basicAttachmentMap.fixIE,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    basicAttachmentMap.router,
    [fixIECompact, workerCompact],
    basicAttachmentMap.ui,
    basicAttachmentMap.ts,
);

const fineuiWithoutJqueryAndPolyfillJs = [].concat(
    sync([
        "src/core/foundation.js",
        lodashJs,
        "src/core/**/*.js",
        "src/data/**/*.js",
        "dist/fix/fix.js",
        "src/base/**/*.js",
        "src/case/**/*.js",

        "!src/core/platform/web/**/*.js",
        "!src/base/single/input/file.js",
        "!src/case/ztree/**/*.js",
    ]),
    basicAttachmentMap.widget,
    sync([fixCompact, workerCompact, "ui/js/**/*.js"]),
    basicAttachmentMap.ts,
);

const demo = [].concat(
    basicAttachmentMap.polyfill,
    basicAttachmentMap.core,
    basicAttachmentMap.router,
    basicAttachmentMap.fix,
    basicAttachmentMap.base,
    basicAttachmentMap.case,
    basicAttachmentMap.widget,
    sync(["public/less/app.less", "public/less/**/*.less"]),
    [fixCompact, workerCompact],
    basicAttachmentMap.config,
    basicAttachmentMap.ts,
    sync(["demo/less/*.less", "demo/less/**/*.less", "demo/app.js", "demo/js/**/*.js", "demo/config.js"]),
);

module.exports = {
    fix: fixJs,
    fixProxy: fixProxyJs,
    fixIE: fixIEJs,
    lodash: lodashJs,
    font: basicAttachmentMap.font,
    bundle: uniq(bundle),
    bundleIE: uniq(bundleIE),
    fineuiWithoutNormalize: uniq(fineuiWithoutNormalize),
    bundleWithoutNormalize: uniq(bundleWithoutNormalize),
    fineui: uniq(fineui),
    fineuiProxy: uniq(fineuiProxy),
    fineuiIE: uniq(fineuiIE),
    fineuiWithoutJqueryAndPolyfillJs: uniq(fineuiWithoutJqueryAndPolyfillJs),
    utils: uniq(basicAttachmentMap.utils),
    demo: uniq(demo),
    coreJs: uniq(coreJs),
    resource: uniq((resource)),
    config: uniq(config),
};
<|endoftext|>


<commit_before>'use strict';

/** Directives */
angular.module('birdwatch.directives', ['charts.barchart', 'charts.wordcloud'])
    /** Tweet Card Layout (with external template)*/
    .directive('tweetCard', function () {
        return {
            restrict: 'C',
            scope: { tweet: "=tweet" },
            templateUrl: "/assets/templates/tweetCard.tpl.html",
            link: function (scope, elem, attrs) { }
        }
    })
    .directive('barchart', function (barchart) {
        return {
            restrict: 'C',
            scope: { words: "=words", live: "=live", callback: "=callback" },
            link: function ($scope, element, attrs) {
                $scope.barchartDefined = false;
                $scope.barchart = barchart.BarChart($scope.callback, element.width() - 180, element, 25);

                $scope.$watch("words", function() {
                    if ($scope.live) {
                        if ($scope.barchartDefined === false) {
                            $scope.barchart.init($scope.words.slice(0, 26));
                            $scope.barchartDefined = true;
                        } else {
                            $scope.barchart.redraw($scope.words.slice(0, 26))
                        }
                    }
                });
            }
        }
    })
    .directive('cloud', function (wordcloud) {
        return {
            restrict: 'C',
            scope: { words: "=words", live: "=live", interval: "=interval", callback: "=callback" },
            link: function ($scope, elem, attrs) {
                function init() {
                    elem.empty();
                    $scope.wordCloud = wordcloud.WordCloud(elem.width(), elem.width() * 0.75, 250, $scope.callback, elem);
                }
                init();

                var lastCloudUpdate = new Date().getTime() - $scope.interval;
                $scope.$watch("words", function() {
                    if ((new Date().getTime() - lastCloudUpdate) > $scope.interval &&  $scope.live) {
                        $scope.wordCloud.redraw($scope.words);
                    }
                });

                /** re-initialize wordCloud on window resize */
                $(window).resize(function () { _.throttle(init, 1000)(); });
            }
        }
    })
    .directive('timeseries', function () {
        return {
            restrict: 'C',
            scope: { tsdata: "=tsdata", live: "=live", interval: "=interval", height: "=height" },
            link: function ($scope, elem, attrs) {
                var graph = new Rickshaw.Graph( {
                    element: elem[0],
                    width: elem.width(),
                    height: $scope.height,
                    renderer: 'bar',
                    series: [ {
                        color: 'steelblue',
                        name: 'Tweets',
                        data: [{ x: 0, y: 0 }]
                    } ]
                } );
                new Rickshaw.Graph.Axis.Time({ graph: graph });
                graph.render();

                new Rickshaw.Graph.HoverDetail({
                    graph: graph,
                    yFormatter: function(y) { return y === null ? y : y.toFixed(0); }
                });

                $scope.$watch("tsdata", function() {
                    if ($scope.tsdata.length > 0) {
                        graph.series[0].data = $scope.tsdata.map(function(el) { return { x: el.key, y: el.value } });
                        graph.update();
                    }
                });
            }
        }
    });
<commit_msg>use ng-bind-html-unsafe from custom directive (was removed from AngularJS 1.2)<commit_after>'use strict';

/** Directives */
angular.module('birdwatch.directives', ['charts.barchart', 'charts.wordcloud'])
    /** Tweet Card Layout (with external template)*/
    .directive('tweetCard', function () {
        return {
            restrict: 'C',
            scope: { tweet: "=tweet" },
            templateUrl: "/assets/templates/tweetCard.tpl.html",
            link: function (scope, elem, attrs) { console.log(scope) }
        }
    })
    .directive('barchart', function (barchart) {
        return {
            restrict: 'C',
            scope: { words: "=words", live: "=live", callback: "=callback" },
            link: function ($scope, element, attrs) {
                $scope.barchartDefined = false;
                $scope.barchart = barchart.BarChart($scope.callback, element.width() - 180, element, 25);

                $scope.$watch("words", function() {
                    if ($scope.live) {
                        if ($scope.barchartDefined === false) {
                            $scope.barchart.init($scope.words.slice(0, 26));
                            $scope.barchartDefined = true;
                        } else {
                            $scope.barchart.redraw($scope.words.slice(0, 26))
                        }
                    }
                });
            }
        }
    })
    .directive('cloud', function (wordcloud) {
        return {
            restrict: 'C',
            scope: { words: "=words", live: "=live", interval: "=interval", callback: "=callback" },
            link: function ($scope, elem, attrs) {
                function init() {
                    elem.empty();
                    $scope.wordCloud = wordcloud.WordCloud(elem.width(), elem.width() * 0.75, 250, $scope.callback, elem);
                }
                init();

                var lastCloudUpdate = new Date().getTime() - $scope.interval;
                $scope.$watch("words", function() {
                    if ((new Date().getTime() - lastCloudUpdate) > $scope.interval &&  $scope.live) {
                        $scope.wordCloud.redraw($scope.words);
                    }
                });

                /** re-initialize wordCloud on window resize */
                $(window).resize(function () { _.throttle(init, 1000)(); });
            }
        }
    })
    .directive('timeseries', function () {
        return {
            restrict: 'C',
            scope: { tsdata: "=tsdata", live: "=live", interval: "=interval", height: "=height" },
            link: function ($scope, elem, attrs) {
                var graph = new Rickshaw.Graph( {
                    element: elem[0],
                    width: elem.width(),
                    height: $scope.height,
                    renderer: 'bar',
                    series: [ {
                        color: 'steelblue',
                        name: 'Tweets',
                        data: [{ x: 0, y: 0 }]
                    } ]
                } );
                new Rickshaw.Graph.Axis.Time({ graph: graph });
                graph.render();

                new Rickshaw.Graph.HoverDetail({
                    graph: graph,
                    yFormatter: function(y) { return y === null ? y : y.toFixed(0); }
                });

                $scope.$watch("tsdata", function() {
                    if ($scope.tsdata.length > 0) {
                        graph.series[0].data = $scope.tsdata.map(function(el) { return { x: el.key, y: el.value } });
                        graph.update();
                    }
                });
            }
        }
    })
    //from http://stackoverflow.com/questions/18340872/how-do-you-use-sce-trustashtmlstring-to-replicate-ng-bind-html-unsafe-in-angu
    .directive('ngBindHtmlUnsafe', ['$sce', function($sce){
    return {
        scope: {
            ngBindHtmlUnsafe: '='
        },
        template: "

",
        link: function($scope, iElm, iAttrs, controller) {
            $scope.updateView = function() {
                $scope.trustedHtml = $sce.trustAsHtml($scope.ngBindHtmlUnsafe);
            }

            $scope.$watch('ngBindHtmlUnsafe', function(newVal, oldVal) {
                $scope.updateView(newVal);
            });
        }
    };
}]);
<|endoftext|>


<commit_before>import React from 'react';
import ReactDOM from 'react-dom';

// Core elements
import { tangramLayer, loadScene } from './map/map';
import { editor, getEditorContent, setEditorContent } from './editor/editor';

// Addons
import { showSceneLoadingIndicator, hideSceneLoadingIndicator } from './map/MapLoading';
import localforage from 'localforage';
import { initWidgetMarks } from './widgets/widgets-manager';
import { initErrorsManager } from './editor/errors';
import { initSuggestions } from './editor/suggest';
import { initGlslWidgetsLink } from './components/widgets-link/glsl-widgets-link';
import ErrorModal from './modals/ErrorModal';

// Import Utils
import { debounce } from 'lodash';
import { prependProtocolToUrl } from './tools/helpers';
import { getQueryStringObject, pushHistoryState, replaceHistoryState } from './tools/url-state';
import { isGistURL, getSceneURLFromGistAPI } from './tools/gist-url';
import { createObjectURL } from './tools/common';
import { initHighlight, highlightRanges } from './editor/highlight';
import { EventEmitter } from './components/event-emitter';

const DEFAULT_SCENE = 'data/scenes/default.yaml';
const STORAGE_LAST_EDITOR_CONTENT = 'last-content';

let initialLoad = true;

let initialScene = ''; // This is where we'll store an initial scene file for purposes of embedded play.

export function initTangramPlay () {
    // TODO: Manage history / routing in its own module
    window.onpopstate = (e) => {
        if (e.state && e.state.scene) {
            load({ url: e.state.scene });
        }
    };

    // LOAD SCENE FILE
    determineScene()
        .then(load)
        .then(() => {
            // Highlight lines if requested by the query string.
            const query = getQueryStringObject();
            if (query.lines) {
                highlightRanges(query.lines);
            }

            // Turn on highlighting module
            initHighlight();

            if (window.isEmbedded === undefined) {
                // Add widgets marks and errors manager.
                initWidgetMarks();
                initErrorsManager();
            }

            // Things we do after Tangram is finished initializing
            tangramLayer.scene.initializing.then(() => {
                // Need to send a signal to the dropdown widgets of type source to populate
                EventEmitter.dispatch('tangram:sceneinit', {});

                if (window.isEmbedded === undefined) {
                    // Initialize addons after Tangram is done, because
                    // some addons depend on Tangram scene config being present
                    // TODO: Verify if this is still true?
                    initSuggestions();
                    initGlslWidgetsLink();
                }
            });
        });

    // If the user bails for whatever reason, hastily shove the contents of
    // the editor into some kind of storage. This overwrites whatever was
    // there before. Note that there is not really a way of handling unload
    // with our own UI and logic, since this allows for widespread abuse
    // of normal browser functionality.
    window.addEventListener('beforeunload', (event) => {
        // TODO:
        // Don't take original url or original base path from
        // Tangram (it may be wrong). Instead, remember this
        // in a "session" variable
        /* eslint-disable camelcase */
        const doc = editor.getDoc();
        const sceneData = {
            original_url: tangramLayer.scene.config_source,
            original_base_path: tangramLayer.scene.config_path,
            contents: getEditorContent(),
            is_clean: doc.isClean(),
            scrollInfo: editor.getScrollInfo(),
            cursor: doc.getCursor()
        };
        /* eslint-enable camelcase */

        // Expects an object of format:
        // {
        //     original_url: 'http://valid.url/path/scene.yaml',
        //     original_base_path: 'http://valid.url/path/',
        //     contents: 'Contents of scene.yaml',
        //     is_clean: boolean value; false indicates original contents were modified without saving
        //     scrollInfo: editor's scroll position
        //     cursor: where the cursor was positioned in the document.
        // }

        if (window.isEmbedded === undefined) {
            localforage.setItem(STORAGE_LAST_EDITOR_CONTENT, sceneData);
        }
    });
}

// Update widgets & content after a batch of changes
// Wrap updateContent() in a debounce function
const _watchEditorForChanges = debounce(updateContent, 500);

/**
 * This function is the canonical way to load a scene in Tangram Play.
 * We want to avoid loading scene files directly into either Tangram
 * or in CodeMirror and then having to update other parts of Tangram Play.
 * Instead, we load new scenes here so that all the different parts
 * of the application can be updated predictably. The load function takes
 * either a URL path (for remote / external scenes), or the contents
 * of a Tangram YAML file itself.
 *
 * @param {Object} scene - an object containing one of two properties:
 *      scene.url - a URL path to load a scene from
 *      scene.contents - Tangram YAML as a text blob
 *      Do not pass in both! Currently `url` takes priority, but
 *      this is not guaranteed behaviour.
 * @returns {Promise} A promise which is resolved when a scene's
 *      contents has been fetched.
 */
export function load (scene) {
    EventEmitter.dispatch('tangram:clear-palette', {});

    // Turn on loading indicator. This is turned off later
    // when Tangram reports that it's done.
    showSceneLoadingIndicator();

    // Turn off watching for changes in editor.
    editor.off('changes', _watchEditorForChanges);

    // Either we are passed a url path, or scene file contents
    if (scene.url) {
        let fetchPromise;

        // Provide protocol if it appears to be protocol-less URL
        scene.url = prependProtocolToUrl(scene.url);

        // If it appears to be a Gist URL:
        if (isGistURL(scene.url) === true) {
            fetchPromise = getSceneURLFromGistAPI(scene.url)
                .then(url => {
                    // Update the scene URL property with the correct URL
                    // to the raw YAML to ensure safe loading
                    scene.url = url;
                    return window.fetch(url);
                });
        }
        // Fetch the contents of a YAML file directly. This step
        // allows us to verify contents (TODO) or error status.
        else {
            fetchPromise = window.fetch(scene.url);
        }

        return fetchPromise.then(response => {
            if (!response.ok) {
                if (response.status === 404) {
                    throw new Error('The scene you requested could not be found.');
                }
                else {
                    throw new Error('Something went wrong loading the scene!');
                }
            }

            return response.text();
        })
        .then(contents => {
            _doLoadProcess({ url: scene.url, contents });
        })
        .catch(error => {
            _onLoadError(error);
        });
    }
    else if (scene.contents) {
        // If scene contents are provided, no asynchronous work is
        // performed here, but wrap this response in a Promise anyway
        // so that the return object is always a thenable.
        return new Promise((resolve, reject) => {
            _doLoadProcess(scene);
            resolve();
        });
    }
}

function _onLoadError (error) {
    ReactDOM.render(<ErrorModal error={error.message} />, document.getElementById('modal-container'));
    hideSceneLoadingIndicator();

    // TODO: editor should not be attached to this
    if (initialLoad === true) {
        showUnloadedState(editor);
        editor.doc.markClean();
    }
}

function _doLoadProcess (scene) {
    initialScene = scene; // Store our intial scene for use within embedded Tangram Play

    let url = scene.url || createObjectURL(scene.contents);

    // Send url to map and contents to editor
    // TODO: get contents from Tangram instead of another xhr request.
    loadScene(url, {
        reset: true,
        basePath: scene['original_base_path']
    });
    _setSceneContentsInEditor(scene);

    hideUnloadedState();

    // Update history
    // Don't push a new history state if we are loading a scene from the
    // initial load of Tangram Play.
    if (initialLoad === false) {
        pushHistoryState({
            scene: (scene.url) ? scene.url : null
        });
    }

    // This should only be true once
    initialLoad = false;

    // Trigger Events
    // Event object is empty right now.
    EventEmitter.dispatch('tangram:sceneload', {});
}

function _setSceneContentsInEditor (sceneData) {
    // Mark as "clean" if the contents are freshly loaded
    // (there is no is_clean property defined) or if contents
    // have been restored with the is_clean property set to "true"
    // This is converted from JSON so the value is a string, not
    // a Boolean. Otherwise, the document has not been previously
    // saved and it is left in the "dirty" state.
    const shouldMarkClean = (typeof sceneData['is_clean'] === 'undefined' || sceneData['is_clean'] === 'true');

    setEditorContent(sceneData.contents, shouldMarkClean);

    if (window.isEmbedded === undefined) {
        // Restore cursor position, if provided.
        if (sceneData.cursor) {
            editor.doc.setCursor(sceneData.cursor, {
                scroll: false
            });
        }
    }

    // Restores the part of the document that was scrolled to, if provided.
    if (sceneData.scrollInfo) {
        let left = sceneData.scrollInfo.left || 0;
        let top = sceneData.scrollInfo.top || 0;
        editor.scrollTo(left, top);
    }

    // Turn change watching back on.
    editor.on('changes', _watchEditorForChanges);
}

// If editor is updated, send it to the map.
function updateContent () {
    const content = getEditorContent();
    const url = createObjectURL(content);
    const isClean = editor.getDoc().isClean();

    // Send scene data to Tangram
    loadScene(url);

    // Update the page URL. When editor contents changes by user input
    // and the the editor state is not clean), we erase the ?scene= state
    // from the URL string. This prevents a situation where reloading (or
    // copy-pasting the URL) loads the scene file from an earlier state.
    if (!isClean) {
        replaceHistoryState({
            scene: null
        });
    }
}

function showUnloadedState (editor) {
    document.querySelector('.map-view').classList.add('map-view-not-loaded');
}

function hideUnloadedState () {
    document.querySelector('.map-view').classList.remove('map-view-not-loaded');
}

// This function is only used by the embedded version of Tangram Play.
// We need it in order to refresh the original scene file if user makes any changes in the editor
export function reloadOriginalScene () {
    _setSceneContentsInEditor(initialScene);
}

/**
 * Determine what is the scene url and content to load during start-up
 * Reading local memory is asynchronous, so this returns a Promise
 *
 * @returns {Promise} - resolves to an object of scene data.
 */
function determineScene () {
    // If there is a query, return it
    let query = getQueryStringObject();
    if (query.scene) {
        return new Promise((resolve, reject) => {
            resolve({ url: query.scene });
        });
    }

    // Else if there is something saved in memory (localforage), return that
    // Check that contents exist and that it is not empty.
    return localforage.getItem(STORAGE_LAST_EDITOR_CONTENT)
        .then((sceneData) => {
            if (sceneData && sceneData.contents && sceneData.contents.trim().length > 0) {
                return sceneData;
            }
            // Else load the default scene file.
            else {
                return { url: DEFAULT_SCENE };
            }
        });
}
<commit_msg>Tangram initialization should be part of the Promise chain, resolves #545<commit_after>import React from 'react';
import ReactDOM from 'react-dom';

// Core elements
import { tangramLayer, loadScene } from './map/map';
import { editor, getEditorContent, setEditorContent } from './editor/editor';

// Addons
import { showSceneLoadingIndicator, hideSceneLoadingIndicator } from './map/MapLoading';
import localforage from 'localforage';
import { initWidgetMarks } from './widgets/widgets-manager';
import { initErrorsManager } from './editor/errors';
import { initSuggestions } from './editor/suggest';
import { initGlslWidgetsLink } from './components/widgets-link/glsl-widgets-link';
import ErrorModal from './modals/ErrorModal';

// Import Utils
import { debounce } from 'lodash';
import { prependProtocolToUrl } from './tools/helpers';
import { getQueryStringObject, pushHistoryState, replaceHistoryState } from './tools/url-state';
import { isGistURL, getSceneURLFromGistAPI } from './tools/gist-url';
import { createObjectURL } from './tools/common';
import { initHighlight, highlightRanges } from './editor/highlight';
import { EventEmitter } from './components/event-emitter';

const DEFAULT_SCENE = 'data/scenes/default.yaml';
const STORAGE_LAST_EDITOR_CONTENT = 'last-content';

let initialLoad = true;

let initialScene = ''; // This is where we'll store an initial scene file for purposes of embedded play.

export function initTangramPlay () {
    // TODO: Manage history / routing in its own module
    window.onpopstate = (e) => {
        if (e.state && e.state.scene) {
            load({ url: e.state.scene });
        }
    };

    // LOAD SCENE FILE
    determineScene()
        .then(load)
        // Things we do after Tangram is finished initializing
        .then(() => {
            // Highlight lines if requested by the query string.
            const query = getQueryStringObject();
            if (query.lines) {
                highlightRanges(query.lines);
            }

            // Turn on highlighting module
            initHighlight();

            // Initialize addons after Tangram is done, because
            // some addons depend on Tangram scene config being present
            // TODO: Verify if this is still true?
            if (window.isEmbedded === undefined) {
                // Add widgets marks and errors manager.
                initWidgetMarks();
                initErrorsManager();

                initSuggestions();
                initGlslWidgetsLink();
            }

            // Need to send a signal to the dropdown widgets of type source to populate
            EventEmitter.dispatch('tangram:sceneinit', {});
        });

    // If the user bails for whatever reason, hastily shove the contents of
    // the editor into some kind of storage. This overwrites whatever was
    // there before. Note that there is not really a way of handling unload
    // with our own UI and logic, since this allows for widespread abuse
    // of normal browser functionality.
    window.addEventListener('beforeunload', (event) => {
        // TODO:
        // Don't take original url or original base path from
        // Tangram (it may be wrong). Instead, remember this
        // in a "session" variable
        /* eslint-disable camelcase */
        const doc = editor.getDoc();
        const sceneData = {
            original_url: tangramLayer.scene.config_source,
            original_base_path: tangramLayer.scene.config_path,
            contents: getEditorContent(),
            is_clean: doc.isClean(),
            scrollInfo: editor.getScrollInfo(),
            cursor: doc.getCursor()
        };
        /* eslint-enable camelcase */

        // Expects an object of format:
        // {
        //     original_url: 'http://valid.url/path/scene.yaml',
        //     original_base_path: 'http://valid.url/path/',
        //     contents: 'Contents of scene.yaml',
        //     is_clean: boolean value; false indicates original contents were modified without saving
        //     scrollInfo: editor's scroll position
        //     cursor: where the cursor was positioned in the document.
        // }

        if (window.isEmbedded === undefined) {
            localforage.setItem(STORAGE_LAST_EDITOR_CONTENT, sceneData);
        }
    });
}

// Update widgets & content after a batch of changes
// Wrap updateContent() in a debounce function
const _watchEditorForChanges = debounce(updateContent, 500);

/**
 * This function is the canonical way to load a scene in Tangram Play.
 * We want to avoid loading scene files directly into either Tangram
 * or in CodeMirror and then having to update other parts of Tangram Play.
 * Instead, we load new scenes here so that all the different parts
 * of the application can be updated predictably. The load function takes
 * either a URL path (for remote / external scenes), or the contents
 * of a Tangram YAML file itself.
 *
 * @param {Object} scene - an object containing one of two properties:
 *      scene.url - a URL path to load a scene from
 *      scene.contents - Tangram YAML as a text blob
 *      Do not pass in both! Currently `url` takes priority, but
 *      this is not guaranteed behaviour.
 * @returns {Promise} A promise which is resolved when a scene's
 *      contents has been fetched.
 */
export function load (scene) {
    EventEmitter.dispatch('tangram:clear-palette', {});

    // Turn on loading indicator. This is turned off later
    // when Tangram reports that it's done.
    showSceneLoadingIndicator();

    // Turn off watching for changes in editor.
    editor.off('changes', _watchEditorForChanges);

    // Either we are passed a url path, or scene file contents
    if (scene.url) {
        let fetchPromise;

        // Provide protocol if it appears to be protocol-less URL
        scene.url = prependProtocolToUrl(scene.url);

        // If it appears to be a Gist URL:
        if (isGistURL(scene.url) === true) {
            fetchPromise = getSceneURLFromGistAPI(scene.url)
                .then(url => {
                    // Update the scene URL property with the correct URL
                    // to the raw YAML to ensure safe loading
                    scene.url = url;
                    return window.fetch(url);
                });
        }
        // Fetch the contents of a YAML file directly. This step
        // allows us to verify contents (TODO) or error status.
        else {
            fetchPromise = window.fetch(scene.url);
        }

        return fetchPromise.then(response => {
            if (!response.ok) {
                if (response.status === 404) {
                    throw new Error('The scene you requested could not be found.');
                }
                else {
                    throw new Error('Something went wrong loading the scene!');
                }
            }

            return response.text();
        })
        .then(contents => {
            return _doLoadProcess({ url: scene.url, contents });
        })
        .catch(error => {
            _onLoadError(error);
        });
    }
    else if (scene.contents) {
        // If scene contents are provided, no asynchronous work is
        // performed here, but wrap this response in a Promise anyway
        // so that the return object is always a thenable.
        return new Promise((resolve, reject) => {
            _doLoadProcess(scene);
            resolve();
        });
    }
}

function _onLoadError (error) {
    ReactDOM.render(<ErrorModal error={error.message} />, document.getElementById('modal-container'));
    hideSceneLoadingIndicator();

    // TODO: editor should not be attached to this
    if (initialLoad === true) {
        showUnloadedState(editor);
        editor.doc.markClean();
    }
}

function _doLoadProcess (scene) {
    initialScene = scene; // Store our intial scene for use within embedded Tangram Play

    let url = scene.url || createObjectURL(scene.contents);

    // Send url to map and contents to editor
    // TODO: get contents from Tangram instead of another xhr request.
    loadScene(url, {
        reset: true,
        basePath: scene['original_base_path']
    });
    _setSceneContentsInEditor(scene);

    hideUnloadedState();

    // Update history
    // Don't push a new history state if we are loading a scene from the
    // initial load of Tangram Play.
    if (initialLoad === false) {
        pushHistoryState({
            scene: (scene.url) ? scene.url : null
        });
    }

    // This should only be true once
    initialLoad = false;

    // Trigger Events
    // Event object is empty right now.
    EventEmitter.dispatch('tangram:sceneload', {});

    // Return the Promise from Tangram initializing
    return tangramLayer.scene.initializing;
}

function _setSceneContentsInEditor (sceneData) {
    // Mark as "clean" if the contents are freshly loaded
    // (there is no is_clean property defined) or if contents
    // have been restored with the is_clean property set to "true"
    // This is converted from JSON so the value is a string, not
    // a Boolean. Otherwise, the document has not been previously
    // saved and it is left in the "dirty" state.
    const shouldMarkClean = (typeof sceneData['is_clean'] === 'undefined' || sceneData['is_clean'] === 'true');

    setEditorContent(sceneData.contents, shouldMarkClean);

    if (window.isEmbedded === undefined) {
        // Restore cursor position, if provided.
        if (sceneData.cursor) {
            editor.doc.setCursor(sceneData.cursor, {
                scroll: false
            });
        }
    }

    // Restores the part of the document that was scrolled to, if provided.
    if (sceneData.scrollInfo) {
        let left = sceneData.scrollInfo.left || 0;
        let top = sceneData.scrollInfo.top || 0;
        editor.scrollTo(left, top);
    }

    // Turn change watching back on.
    editor.on('changes', _watchEditorForChanges);
}

// If editor is updated, send it to the map.
function updateContent () {
    const content = getEditorContent();
    const url = createObjectURL(content);
    const isClean = editor.getDoc().isClean();

    // Send scene data to Tangram
    loadScene(url);

    // Update the page URL. When editor contents changes by user input
    // and the the editor state is not clean), we erase the ?scene= state
    // from the URL string. This prevents a situation where reloading (or
    // copy-pasting the URL) loads the scene file from an earlier state.
    if (!isClean) {
        replaceHistoryState({
            scene: null
        });
    }
}

function showUnloadedState (editor) {
    document.querySelector('.map-view').classList.add('map-view-not-loaded');
}

function hideUnloadedState () {
    document.querySelector('.map-view').classList.remove('map-view-not-loaded');
}

// This function is only used by the embedded version of Tangram Play.
// We need it in order to refresh the original scene file if user makes any changes in the editor
export function reloadOriginalScene () {
    _setSceneContentsInEditor(initialScene);
}

/**
 * Determine what is the scene url and content to load during start-up
 * Reading local memory is asynchronous, so this returns a Promise
 *
 * @returns {Promise} - resolves to an object of scene data.
 */
function determineScene () {
    // If there is a query, return it
    let query = getQueryStringObject();
    if (query.scene) {
        return new Promise((resolve, reject) => {
            resolve({ url: query.scene });
        });
    }

    // Else if there is something saved in memory (localforage), return that
    // Check that contents exist and that it is not empty.
    return localforage.getItem(STORAGE_LAST_EDITOR_CONTENT)
        .then((sceneData) => {
            if (sceneData && sceneData.contents && sceneData.contents.trim().length > 0) {
                return sceneData;
            }
            // Else load the default scene file.
            else {
                return { url: DEFAULT_SCENE };
            }
        });
}
<|endoftext|>


<commit_before>'use strict';

var app = angular.module('LDT.directives');

app.directive('appVersion', ['version', function(version) {
    return function(scope, elm, attrs) {
      elm.text(version);
    };
  }]);

app.directive('catchInput',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      function stopit(e) {
        e.stopPropagation();
      };

      //Don't allow clicks to change mode
      element.click(stopit);

      //Don't allow renaming keypresses to change mode
      element.keypress(stopit);
    }
  };
});

//Don't allow dragging that begins on this element to cause anything else to move
app.directive('noDrag',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      function stopit(e) {
        e.stopPropagation();
      };
      element.mousemove(stopit);

      element.mousedown(stopit);
    }
  };
});

app.directive('autoFocus',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var regex = /([-.#\w\d]+) on ([\w]+)/;
      var matches = regex.exec(iAttrs.autoFocus);
      var params = _.object(['targetSelector', 'eventName'], matches.slice(1,3));

      element.bind(params.eventName, function(e) {
        // Select the whole entity title for fast rename
        $(element.find(params.targetSelector)[0]).select();
      });
    }
  };
});

// Would be nice to automatically highlight the attribute that was double-clicked.
/*
app.directive('forwardEvent',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var regex = /([\w]+) to ([.#-\w\d]+)/
      var matches = regex.exec(iAttrs.forwardEvent);
      var params = _.object(['eventName', 'targetSelector'], matches.slice(1,3));

      element.bind(params.eventName, function(e) {
        // Trigger another event on child when parent event occurs (danger... recursive event firing)
        console.log(e)
        var child = element.find(params.targetSelector)
        child.bind(params.eventName, function(e) { e.stopPropagation(); });
        child.trigger(e);
      })
    }
  }
});
*/

// Setup a rectangular element to be movable and resizable, and bind its position to the scope
app.directive('moveAndResize',function() {
  return {
    link: function (scope, element, iAttrs, ctrl) {
      var subject = scope[iAttrs.moveAndResize];
      var dragging = false;
      var dragType,
          dragStartX, dragStartY,
          dragStartMouseX, dragStartMouseY,
          dragStartWidth, dragStartHeight;

      var GRIDSIZE  = 10;
      var BORDER_HANDLE_SIZE = 10;

      function grid(n) {
        return Math.floor((n + GRIDSIZE/2.0)/GRIDSIZE)*GRIDSIZE
      }

      // 0: move, 1: resize-left, 2: resize-right, 3: resize-top: 4: resize-bottom
      function boxBorderArea(x,y) {
               if (x < parseInt(element.offset().left) + BORDER_HANDLE_SIZE) {
          return 1;
        } else if (x > parseInt(element.offset().left) + parseInt(element.width()) - BORDER_HANDLE_SIZE) {
          return 2;
        } else if (y < parseInt(element.offset().top) + BORDER_HANDLE_SIZE) {
          return 3;
        } else if (y > parseInt(element.offset().top) + parseInt(element.height()) - BORDER_HANDLE_SIZE) {
          return 4;
        } else {
          return 0;
        }
      }

      function actionByDragType(ev) {
        switch(dragType) {
          case 0:
            subject.x      = grid(dragStartX      + ev.pageX - dragStartMouseX);
            subject.y      = grid(dragStartY      + ev.pageY - dragStartMouseY);
            break;
          case 1:
            subject.x      = grid(dragStartX      + ev.pageX - dragStartMouseX);
            subject.width  = grid(dragStartWidth  - ev.pageX + dragStartMouseX);
            break;
          case 2:
            subject.width  = grid(dragStartWidth  + ev.pageX - dragStartMouseX);
            break;
          case 3:
            subject.y      = grid(dragStartY      + ev.pageY - dragStartMouseY);
            subject.height = grid(dragStartHeight - ev.pageY + dragStartMouseY);
            break;
          case 4:
            subject.height = grid(dragStartHeight + ev.pageY - dragStartMouseY);
            break;
        }
      }

      function setCursor(ev) {
        switch(boxBorderArea(ev.pageX, ev.pageY)) {
          case 0: element.css('cursor', 'default' ); break;
          case 1: element.css('cursor', 'w-resize'); break;
          case 2: element.css('cursor', 'e-resize'); break;
          case 3: element.css('cursor', 'n-resize'); break;
          case 4: element.css('cursor', 's-resize'); break;
        }
      }

      function startDrag(ev) {
        dragging = true;
        dragStartX = subject.x;
        dragStartY = subject.y;
        dragStartWidth = subject.width;
        dragStartHeight = subject.height;
        dragStartMouseX = ev.pageX;
        dragStartMouseY = ev.pageY;
        dragType = boxBorderArea(ev.pageX, ev.pageY);
        ev.stopPropagation();
      };

      function stopDrag(ev) {
        dragging = false;
      };

      function moveDrag(ev) {
        if (dragging) {
          scope.$apply(actionByDragType(ev));
        }
        // prevent highlighting action (annoying)
        ev.preventDefault();
        ev.stopPropagation();
      };

      $(element).mousedown(startDrag);
      $(document).mouseup(stopDrag);
      $(document).mouseleave(stopDrag);
      $(document).mousemove(moveDrag);
      $(element).mousemove(setCursor);
    }
  };
});

// Setup entities to be draggable and resizable, and bind to the scope
app.directive('selectWith',function() {
  return {
    link: function (scope, element, iAttrs, ctrl) {
      var regex = /([\w\d]+) as ([\w\d]+)/;
      var matches = regex.exec(iAttrs.selectWith);
      var params = _.object(['eventName', 'varName'], matches.slice(1,3));

      // Don't allow parent to get the click again
      element.click(function(e) { e.stopPropagation(); });

      //Define variable if not externally defined
      if (typeof(scope[params.varName]) == 'undefined')
        scope[params.varName] = {};

      var scopeVar = scope[params.varName];
      scopeVar.selected = false;

      //Setup class to watch the scope
      scope.$watch(params.varName + '.selected', function(selected) {
        var stopListening;

        if (scopeVar.selected) {
          element.addClass('selected');

          stopListening = scope.$on('deselectAll', function() {
            scope.$apply( function() { scopeVar.selected = false; });
          });

        } else {
          element.removeClass('selected');
          if (stopListening !== undefined) stopListening();
        }
      });

      //Set true when activated by specified event (and start listening for 'deselectAll');
      //set false when 'deselectAll' is broadcast (and stop listening for 'deselectAll').
      element.bind(params.eventName, function(e) {
        scope.$apply( function() { scopeVar.selected = true; });

        e.stopPropagation();
      });
    }
  };
});

app.directive('deselector',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      element.click(function(e) {
        scope.$broadcast('deselectAll');
      });
    }
  };
});

app.directive('action',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      element.click(function(e) {
        var actionName = element[0].textContent.replace(' ', '').replace(/^(.)/, function(c) { return c.toLowerCase(); });
        scope[actionName + 'Command']();
      });
    }
  };
});

app.directive('hotkey',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var pattern = /(?:(.)-)?(.)/;
      var matches = pattern.exec(iAttrs.hotkey);
      var hotkey = _.object(['chord', 'letter'], matches.slice(1,3));

      $(window).keypress(function(e) {
        var pressedChord =
          (e.ctrlKey  ? 'c' : '') +
          (e.shiftKey ? 's' : '') +
          (e.altKey   ? 'a' : '') +
          (e.metaKey  ? 'm' : '');

        if ((pressedChord === ''  && hotkey.chord == undefined && e.charCode == hotkey.letter.charCodeAt(0)     ) ||
            (pressedChord == 'c'  && hotkey.chord == 'c'       && e.charCode == hotkey.letter.charCodeAt(0) - 96) )
           element.trigger('click');
        });
      element.attr('title', 'hotkey: ' + iAttrs.hotkey);
    }
  };
});

app.directive('stickToMouse',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      function follow(e) {
        element.css('left', e.pageX - element[0].parentElement.offsetLeft);
        element.css('top',  e.pageY - element[0].parentElement.offsetTop);
      };
      $(document).mousemove(follow);
    }
  };
});

//Calls scope with coordinates clicked in the current element,
//even if a child element with different coordinates caught the event.
app.directive('relativeClick',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var scopeFunctionName = iAttrs.relativeClick;

      $(element).click(function(ev) {
        var relativeX = ev.pageX - $(element)[0].offsetLeft;
        var relativeY = ev.pageY - $(element)[0].offsetTop;
        scope.$apply(function() {
          scope[scopeFunctionName](relativeX, relativeY);
        });
      });
    }
  };
});

app.directive('drag',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var scopeFunctionName = iAttrs.drag;

      $(element).mousedown(function(ev) { scope.dragging = true;  });
      $(element).mouseup  (function(ev) { scope.dragging = false; });

      $(element).mousemove(function(ev) {
        if (scope.dragging) {
          scope.$apply(function() {
            scope[scopeFunctionName](ev);
          });
        }
      });
    }
  };
});
<commit_msg>Allow drag only with primary (left) click<commit_after>'use strict';

var app = angular.module('LDT.directives');

app.directive('appVersion', ['version', function(version) {
    return function(scope, elm, attrs) {
      elm.text(version);
    };
  }]);

app.directive('catchInput',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      function stopit(e) {
        e.stopPropagation();
      };

      //Don't allow clicks to change mode
      element.click(stopit);

      //Don't allow renaming keypresses to change mode
      element.keypress(stopit);
    }
  };
});

//Don't allow dragging that begins on this element to cause anything else to move
app.directive('noDrag',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      function stopit(e) {
        e.stopPropagation();
      };
      element.mousemove(stopit);

      element.mousedown(stopit);
    }
  };
});

app.directive('autoFocus',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var regex = /([-.#\w\d]+) on ([\w]+)/;
      var matches = regex.exec(iAttrs.autoFocus);
      var params = _.object(['targetSelector', 'eventName'], matches.slice(1,3));

      element.bind(params.eventName, function(e) {
        // Select the whole entity title for fast rename
        $(element.find(params.targetSelector)[0]).select();
      });
    }
  };
});

// Would be nice to automatically highlight the attribute that was double-clicked.
/*
app.directive('forwardEvent',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var regex = /([\w]+) to ([.#-\w\d]+)/
      var matches = regex.exec(iAttrs.forwardEvent);
      var params = _.object(['eventName', 'targetSelector'], matches.slice(1,3));

      element.bind(params.eventName, function(e) {
        // Trigger another event on child when parent event occurs (danger... recursive event firing)
        console.log(e)
        var child = element.find(params.targetSelector)
        child.bind(params.eventName, function(e) { e.stopPropagation(); });
        child.trigger(e);
      })
    }
  }
});
*/

// Setup a rectangular element to be movable and resizable, and bind its position to the scope
app.directive('moveAndResize',function() {
  return {
    link: function (scope, element, iAttrs, ctrl) {
      var subject = scope[iAttrs.moveAndResize];
      var dragging = false;
      var dragType,
          dragStartX, dragStartY,
          dragStartMouseX, dragStartMouseY,
          dragStartWidth, dragStartHeight;

      var GRIDSIZE  = 10;
      var BORDER_HANDLE_SIZE = 10;

      function grid(n) {
        return Math.floor((n + GRIDSIZE/2.0)/GRIDSIZE)*GRIDSIZE
      }

      // 0: move, 1: resize-left, 2: resize-right, 3: resize-top: 4: resize-bottom
      function boxBorderArea(x,y) {
               if (x < parseInt(element.offset().left) + BORDER_HANDLE_SIZE) {
          return 1;
        } else if (x > parseInt(element.offset().left) + parseInt(element.width()) - BORDER_HANDLE_SIZE) {
          return 2;
        } else if (y < parseInt(element.offset().top) + BORDER_HANDLE_SIZE) {
          return 3;
        } else if (y > parseInt(element.offset().top) + parseInt(element.height()) - BORDER_HANDLE_SIZE) {
          return 4;
        } else {
          return 0;
        }
      }

      function actionByDragType(ev) {
        switch(dragType) {
          case 0:
            subject.x      = grid(dragStartX      + ev.pageX - dragStartMouseX);
            subject.y      = grid(dragStartY      + ev.pageY - dragStartMouseY);
            break;
          case 1:
            subject.x      = grid(dragStartX      + ev.pageX - dragStartMouseX);
            subject.width  = grid(dragStartWidth  - ev.pageX + dragStartMouseX);
            break;
          case 2:
            subject.width  = grid(dragStartWidth  + ev.pageX - dragStartMouseX);
            break;
          case 3:
            subject.y      = grid(dragStartY      + ev.pageY - dragStartMouseY);
            subject.height = grid(dragStartHeight - ev.pageY + dragStartMouseY);
            break;
          case 4:
            subject.height = grid(dragStartHeight + ev.pageY - dragStartMouseY);
            break;
        }
      }

      function setCursor(ev) {
        switch(boxBorderArea(ev.pageX, ev.pageY)) {
          case 0: element.css('cursor', 'default' ); break;
          case 1: element.css('cursor', 'w-resize'); break;
          case 2: element.css('cursor', 'e-resize'); break;
          case 3: element.css('cursor', 'n-resize'); break;
          case 4: element.css('cursor', 's-resize'); break;
        }
      }

      function startDrag(ev) {
				// left click only (otherwise right click has issues)
				if (ev.button == 0) {
					dragging = true;
					dragStartX = subject.x;
					dragStartY = subject.y;
					dragStartWidth = subject.width;
					dragStartHeight = subject.height;
					dragStartMouseX = ev.pageX;
					dragStartMouseY = ev.pageY;
					dragType = boxBorderArea(ev.pageX, ev.pageY);
					ev.stopPropagation();
				}
      };

      function stopDrag(ev) {
        dragging = false;
      };

      function moveDrag(ev) {
        if (dragging) {
          scope.$apply(actionByDragType(ev));
        }
        // prevent highlighting action (annoying)
        ev.preventDefault();
        ev.stopPropagation();
      };

      $(element).mousedown(startDrag);
      $(document).mouseup(stopDrag);
      $(document).mouseleave(stopDrag);
      $(document).mousemove(moveDrag);
      $(element).mousemove(setCursor);
    }
  };
});

// Setup entities to be draggable and resizable, and bind to the scope
app.directive('selectWith',function() {
  return {
    link: function (scope, element, iAttrs, ctrl) {
      var regex = /([\w\d]+) as ([\w\d]+)/;
      var matches = regex.exec(iAttrs.selectWith);
      var params = _.object(['eventName', 'varName'], matches.slice(1,3));

      // Don't allow parent to get the click again
      element.click(function(e) { e.stopPropagation(); });

      //Define variable if not externally defined
      if (typeof(scope[params.varName]) == 'undefined')
        scope[params.varName] = {};

      var scopeVar = scope[params.varName];
      scopeVar.selected = false;

      //Setup class to watch the scope
      scope.$watch(params.varName + '.selected', function(selected) {
        var stopListening;

        if (scopeVar.selected) {
          element.addClass('selected');

          stopListening = scope.$on('deselectAll', function() {
            scope.$apply( function() { scopeVar.selected = false; });
          });

        } else {
          element.removeClass('selected');
          if (stopListening !== undefined) stopListening();
        }
      });

      //Set true when activated by specified event (and start listening for 'deselectAll');
      //set false when 'deselectAll' is broadcast (and stop listening for 'deselectAll').
      element.bind(params.eventName, function(e) {
        scope.$apply( function() { scopeVar.selected = true; });

        e.stopPropagation();
      });
    }
  };
});

app.directive('deselector',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      element.click(function(e) {
        scope.$broadcast('deselectAll');
      });
    }
  };
});

app.directive('action',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      element.click(function(e) {
        var actionName = element[0].textContent.replace(' ', '').replace(/^(.)/, function(c) { return c.toLowerCase(); });
        scope[actionName + 'Command']();
      });
    }
  };
});

app.directive('hotkey',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var pattern = /(?:(.)-)?(.)/;
      var matches = pattern.exec(iAttrs.hotkey);
      var hotkey = _.object(['chord', 'letter'], matches.slice(1,3));

      $(window).keypress(function(e) {
        var pressedChord =
          (e.ctrlKey  ? 'c' : '') +
          (e.shiftKey ? 's' : '') +
          (e.altKey   ? 'a' : '') +
          (e.metaKey  ? 'm' : '');

        if ((pressedChord === ''  && hotkey.chord == undefined && e.charCode == hotkey.letter.charCodeAt(0)     ) ||
            (pressedChord == 'c'  && hotkey.chord == 'c'       && e.charCode == hotkey.letter.charCodeAt(0) - 96) )
           element.trigger('click');
        });
      element.attr('title', 'hotkey: ' + iAttrs.hotkey);
    }
  };
});

app.directive('stickToMouse',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      function follow(e) {
        element.css('left', e.pageX - element[0].parentElement.offsetLeft);
        element.css('top',  e.pageY - element[0].parentElement.offsetTop);
      };
      $(document).mousemove(follow);
    }
  };
});

//Calls scope with coordinates clicked in the current element,
//even if a child element with different coordinates caught the event.
app.directive('relativeClick',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var scopeFunctionName = iAttrs.relativeClick;

      $(element).click(function(ev) {
        var relativeX = ev.pageX - $(element)[0].offsetLeft;
        var relativeY = ev.pageY - $(element)[0].offsetTop;
        scope.$apply(function() {
          scope[scopeFunctionName](relativeX, relativeY);
        });
      });
    }
  };
});

app.directive('drag',function() {
  return {
    link: function(scope, element, iAttrs, ctrl) {
      var scopeFunctionName = iAttrs.drag;

      $(element).mousedown(function(ev) { scope.dragging = true;  });
      $(element).mouseup  (function(ev) { scope.dragging = false; });

      $(element).mousemove(function(ev) {
        if (scope.dragging) {
          scope.$apply(function() {
            scope[scopeFunctionName](ev);
          });
        }
      });
    }
  };
});
<|endoftext|>