<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowserView',
  extends: 'foam.u2.View',
  requires: [
    'foam.comics.SearchMode',
    'foam.comics.v2.DAOControllerConfig',
    'foam.u2.ActionView',
    'foam.u2.dialog.Popup',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.filter.FilterSearch',
    'foam.u2.view.ScrollTableView',
    'foam.u2.view.SimpleSearch',
    'foam.u2.view.TabChoiceView',
  ],

  implements: [
    'foam.mlang.Expressions'
  ],

  documentation: `
    A scrolling table view customized for the inline DAOController
    with canned queries and a searchbar
  `,

  css: `
    ^export {
      margin-left: 16px;
    }

    ^export img {
      margin-right: 0;
    }

    .foam-u2-ActionView-refreshTable > img {
      margin-right: 0;
    }

    ^top-bar {
      border-bottom: solid 1px #e7eaec;
      align-items: center;
    }

    ^query-bar {
      padding: 24px 16px;
      align-items: center;
      justify-content: flex-end;
    }

    ^toolbar {
      flex-grow: 1;
    }

    ^browse-view-container {
      margin: auto;
      border-bottom: solid 1px #e7eaec;
      margin: 0px 0px 72px 0px;
      box-sizing: border-box;
      padding: 0 16px;
    }

    ^canned-queries {
      padding: 0 16px;
    }

    ^ .foam-u2-view-TableView th {
      background: #ffffff
    }

    ^ .foam-u2-view-TableView td {
      padding-left: 16px;
    }

    ^ .foam-u2-view-SimpleSearch {
      flex-grow: 1;
    }

    ^ .foam-u2-view-SimpleSearch .foam-u2-search-TextSearchView .foam-u2-tag-Input {
      width: 100%;
    }
  `,

  messages: [
    { name: 'REFRESH_MSG', message: 'Table Refreshed' }
  ],

  imports: [
    'stack?'
  ],

  exports: [
    'dblclick',
    'filteredTableColumns'
  ],

  properties: [
    {
      class: 'StringArray',
      name: 'filteredTableColumns'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config',
      factory: function() {
        return this.DAOControllerConfig.create({ dao: this.data });
      }
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'summaryView',
      factory: function() {
        return {
          class: 'foam.u2.view.ScrollTableView',
          enableDynamicTableHeight: false,
          css: {
            width: '100%',
            height: '424px'
          }
        };
      }
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'cannedQueriesView',
      factory: function() {
        return {
          class: 'foam.u2.view.TabChoiceView'
        };
      }
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'cannedPredicate',
      expression: function(config$cannedQueries) {
        return config$cannedQueries && config$cannedQueries.length
          ? config$cannedQueries[0].predicate
          : foam.mlang.predicate.True.create();
      }
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'searchPredicate',
      expression: function() {
        return foam.mlang.predicate.True.create();
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'predicatedDAO',
      expression: function(config, cannedPredicate, searchPredicate) {
        return config.dao$proxy.where(this.AND(cannedPredicate, searchPredicate));
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'searchFilterDAO',
      expression: function(config, cannedPredicate) {
        return config.dao$proxy.where(cannedPredicate);
      }
    }
  ],
  actions: [
    {
      name: 'export',
      label: '',
      icon: 'images/export-arrow-icon.svg',
      code: function() {
        this.add(this.Popup.create().tag({
          class: 'foam.u2.ExportModal',
          exportData: this.predicatedDAO$proxy,
          predicate: this.config.filterExportPredicate
        }));
      }
    },
    {
      name: 'refreshTable',
      label: '',
      icon: 'images/refresh-icon-black.svg',
      code: function(X) {
        this.data.cmd_(X, foam.dao.CachingDAO.PURGE);
        this.add(foam.u2.dialog.NotificationMessage.create({
          message: this.REFRESH_MSG
        }));
      }
    }
  ],
  methods: [
    function init() {
      // Reset the search filters when a different canned query is selected
      this.onDetach(this.cannedPredicate$.sub(() => {
        this.searchPredicate = foam.mlang.predicate.True.create();
      }));
    },
    function dblclick(obj) {
      if ( ! this.stack ) return;
      this.stack.push({
        class: 'foam.comics.v2.DAOSummaryView',
        data: obj,
        config: this.config,
        of: this.config.of
      });
    },
    function initE() {
      var self = this;
      this.addClass(this.myClass());
      this.SUPER();
      this
        .add(this.slot(function(data, config$cannedQueries, config$defaultColumns, searchFilterDAO) {
          return self.E()
            .start(self.Rows)
              .callIf(config$cannedQueries.length >= 1, function() {
                this
                  .start(self.Cols)
                    .addClass(self.myClass('top-bar'))
                    .start(self.Cols)
                      .callIf(config$cannedQueries.length > 1, function() {
                        this
                          .start(self.cannedQueriesView, {
                            choices: config$cannedQueries.map((o) => [o.predicate, o.label]),
                            data$: self.cannedPredicate$
                          })
                            .addClass(self.myClass('canned-queries'))
                          .end();
                      })
                    .end()
                  .end();
              })
              .start(self.Cols).addClass(self.myClass('query-bar'))
                .startContext({
                  dao: searchFilterDAO,
                  controllerMode: foam.u2.ControllerMode.EDIT
                })
                  .callIf(self.config.searchMode === self.SearchMode.SIMPLE, function() {
                    this.tag(self.SimpleSearch, {
                      showCount: false,
                      data$: self.searchPredicate$
                    });
                  })
                  .callIf(self.config.searchMode === self.SearchMode.FULL, function() {
                    this.tag(self.FilterSearch, {
                      data$: self.searchPredicate$
                    });
                  })
                .endContext()
                .startContext({ data: self })
                  .start(self.EXPORT, { buttonStyle: 'SECONDARY' })
                    .addClass(self.myClass('export'))
                  .end()
                  .tag(this.REFRESH_TABLE, { buttonStyle: 'SECONDARY' })
                .endContext()
              .end()
              .start(self.summaryView, {
                data: self.predicatedDAO$proxy,
                enableDynamicTableHeight: false,
                columns: config$defaultColumns
              })
                .addClass(self.myClass('browse-view-container'))
              .end()
            .end();
        }));
    }
  ]
});
<commit_msg>txt change more user friendly<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOBrowserView',
  extends: 'foam.u2.View',
  requires: [
    'foam.comics.SearchMode',
    'foam.comics.v2.DAOControllerConfig',
    'foam.u2.ActionView',
    'foam.u2.dialog.Popup',
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.filter.FilterSearch',
    'foam.u2.view.ScrollTableView',
    'foam.u2.view.SimpleSearch',
    'foam.u2.view.TabChoiceView',
  ],

  implements: [
    'foam.mlang.Expressions'
  ],

  documentation: `
    A scrolling table view customized for the inline DAOController
    with canned queries and a searchbar
  `,

  css: `
    ^export {
      margin-left: 16px;
    }

    ^export img {
      margin-right: 0;
    }

    .foam-u2-ActionView-refreshTable > img {
      margin-right: 0;
    }

    ^top-bar {
      border-bottom: solid 1px #e7eaec;
      align-items: center;
    }

    ^query-bar {
      padding: 24px 16px;
      align-items: center;
      justify-content: flex-end;
    }

    ^toolbar {
      flex-grow: 1;
    }

    ^browse-view-container {
      margin: auto;
      border-bottom: solid 1px #e7eaec;
      margin: 0px 0px 72px 0px;
      box-sizing: border-box;
      padding: 0 16px;
    }

    ^canned-queries {
      padding: 0 16px;
    }

    ^ .foam-u2-view-TableView th {
      background: #ffffff
    }

    ^ .foam-u2-view-TableView td {
      padding-left: 16px;
    }

    ^ .foam-u2-view-SimpleSearch {
      flex-grow: 1;
    }

    ^ .foam-u2-view-SimpleSearch .foam-u2-search-TextSearchView .foam-u2-tag-Input {
      width: 100%;
    }
  `,

  messages: [
    { name: 'REFRESH_MSG', message: 'Refresh Requested. Please be patient, refresh may take up to a minute.' }
  ],

  imports: [
    'stack?'
  ],

  exports: [
    'dblclick',
    'filteredTableColumns'
  ],

  properties: [
    {
      class: 'StringArray',
      name: 'filteredTableColumns'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config',
      factory: function() {
        return this.DAOControllerConfig.create({ dao: this.data });
      }
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'summaryView',
      factory: function() {
        return {
          class: 'foam.u2.view.ScrollTableView',
          enableDynamicTableHeight: false,
          css: {
            width: '100%',
            height: '424px'
          }
        };
      }
    },
    {
      class: 'foam.u2.ViewSpec',
      name: 'cannedQueriesView',
      factory: function() {
        return {
          class: 'foam.u2.view.TabChoiceView'
        };
      }
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'cannedPredicate',
      expression: function(config$cannedQueries) {
        return config$cannedQueries && config$cannedQueries.length
          ? config$cannedQueries[0].predicate
          : foam.mlang.predicate.True.create();
      }
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'searchPredicate',
      expression: function() {
        return foam.mlang.predicate.True.create();
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'predicatedDAO',
      expression: function(config, cannedPredicate, searchPredicate) {
        return config.dao$proxy.where(this.AND(cannedPredicate, searchPredicate));
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'searchFilterDAO',
      expression: function(config, cannedPredicate) {
        return config.dao$proxy.where(cannedPredicate);
      }
    }
  ],
  actions: [
    {
      name: 'export',
      label: '',
      icon: 'images/export-arrow-icon.svg',
      code: function() {
        this.add(this.Popup.create().tag({
          class: 'foam.u2.ExportModal',
          exportData: this.predicatedDAO$proxy,
          predicate: this.config.filterExportPredicate
        }));
      }
    },
    {
      name: 'refreshTable',
      label: '',
      icon: 'images/refresh-icon-black.svg',
      code: function(X) {
        this.data.cmd_(X, foam.dao.CachingDAO.PURGE);
        this.add(foam.u2.dialog.NotificationMessage.create({
          message: this.REFRESH_MSG
        }));
      }
    }
  ],
  methods: [
    function init() {
      // Reset the search filters when a different canned query is selected
      this.onDetach(this.cannedPredicate$.sub(() => {
        this.searchPredicate = foam.mlang.predicate.True.create();
      }));
    },
    function dblclick(obj) {
      if ( ! this.stack ) return;
      this.stack.push({
        class: 'foam.comics.v2.DAOSummaryView',
        data: obj,
        config: this.config,
        of: this.config.of
      });
    },
    function initE() {
      var self = this;
      this.addClass(this.myClass());
      this.SUPER();
      this
        .add(this.slot(function(data, config$cannedQueries, config$defaultColumns, searchFilterDAO) {
          return self.E()
            .start(self.Rows)
              .callIf(config$cannedQueries.length >= 1, function() {
                this
                  .start(self.Cols)
                    .addClass(self.myClass('top-bar'))
                    .start(self.Cols)
                      .callIf(config$cannedQueries.length > 1, function() {
                        this
                          .start(self.cannedQueriesView, {
                            choices: config$cannedQueries.map((o) => [o.predicate, o.label]),
                            data$: self.cannedPredicate$
                          })
                            .addClass(self.myClass('canned-queries'))
                          .end();
                      })
                    .end()
                  .end();
              })
              .start(self.Cols).addClass(self.myClass('query-bar'))
                .startContext({
                  dao: searchFilterDAO,
                  controllerMode: foam.u2.ControllerMode.EDIT
                })
                  .callIf(self.config.searchMode === self.SearchMode.SIMPLE, function() {
                    this.tag(self.SimpleSearch, {
                      showCount: false,
                      data$: self.searchPredicate$
                    });
                  })
                  .callIf(self.config.searchMode === self.SearchMode.FULL, function() {
                    this.tag(self.FilterSearch, {
                      data$: self.searchPredicate$
                    });
                  })
                .endContext()
                .startContext({ data: self })
                  .start(self.EXPORT, { buttonStyle: 'SECONDARY' })
                    .addClass(self.myClass('export'))
                  .end()
                  .tag(this.REFRESH_TABLE, { buttonStyle: 'SECONDARY' })
                .endContext()
              .end()
              .start(self.summaryView, {
                data: self.predicatedDAO$proxy,
                enableDynamicTableHeight: false,
                columns: config$defaultColumns
              })
                .addClass(self.myClass('browse-view-container'))
              .end()
            .end();
        }));
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
   // package: 'foam.graphics',
   name: 'TreeGraphDemo',
   extends: 'foam.graphics.CView',

   properties: [
     [ 'x', 0 ],
     [ 'y', 0 ],
     [ 'height', 2000 ],
     [ 'width', 2000 ]
   ],

   methods: [
     function init() {
       var g = TreeGraph.create({x:800, y:50});
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       g.childNodes[0].addChildNode()/*.addChildNode()*/;
       g.childNodes[0].addChildNode();
       g.childNodes[0].childNodes[0].addChildNode().addChildNode()/*.addChildNode()*/;
       g.childNodes[0].childNodes[0].childNodes[0].addChildNode().addChildNode().addChildNode();
       g.childNodes[0].childNodes[1].addChildNode();
       g.childNodes[1].addChildNode();
       // g.childNodes[1].childNodes[0].addChildNode(); // TODO: not supported
       g.childNodes[2].addChildNode();
       g.childNodes[2].childNodes[0].addChildNode();
       g.childNodes[2].childNodes[0].addChildNode();
       g.childNodes[2].childNodes[0].addChildNode();
       g.childNodes[2].childNodes[0].childNodes[1].addChildNode();
       g.childNodes[2].childNodes[0].childNodes[2].addChildNode().addChildNode();
       g.layout();
       g.x+=100;

/*
       var g = TreeGraph.create({x:100, y:500});
       this.add(g.addChildNode());

       var g = TreeGraph.create({x:350, y:500});
       this.add(g.addChildNode());
       this.add(g.addChildNode());

       var g = TreeGraph.create({x:850, y:500});
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       */
     }
   ]
 });

foam.CLASS({
  // package: 'foam.graphics',
  name: 'TreeGraph',
  extends: 'foam.graphics.Box',

  requires: [
    'foam.graphics.Box',
    'foam.graphics.CView',
    'foam.graphics.Label'
  ],

  properties: [
    [ 'height', 60 ],
    [ 'width', 140 ],
    [ 'border', 'gray' ],
    [ 'slide', 0 ],
    {
      name: 'childNodes',
      factory: function() { return []; }
    },
    [ 'left',  0 ],
    [ 'right', 0 ],
    {
      class: 'Boolean',
      name: 'expanded',
      value: true
    },
    [ 'color', 'white' ],
    [ 'padding', 30 ],
    'parentNode'
  ],

  methods: [
    function initCView() {
      this.SUPER();

      this.add(this.Label.create({x: -this.width/2+10, y: 7, text: 'ABC Corp.', font: 'bold 12px sans-serif'}));
      this.add(this.Label.create({x: this.width/2-10,  y: this.height-22, align: 'end', text: '$100,000'}));
      this.add(this.Label.create({x: -this.width/2+10, y: this.height-22, text: this.childNodes.length ? 'Aggregate' : ''}));

      // If top-level node
      if ( ! this.parentNode ) {
        var self = this;
        this.parent.canvas.on('click', function(e) {
          var c = self.findFirstChildAt(e.clientX+50, e.clientY);
          if ( ! c ) return;
          c.expanded = ! c.expanded;
          if ( ! c.expanded ) {
            for ( var i = 0 ; i < c.childNodes.length ; i++ ) {
              c.childNodes[i].y = self.height;
              c.childNodes[i].x = 0;
            }
          }
          self.doLayout();
        });
      }
    },

    function paint(x) {
      if ( this.parentNode && ! this.parentNode.expanded ) return;
      this.SUPER(x);
    },

    function paintSelf(x) {
      x.translate(-this.width/2, 0);
      this.SUPER(x);
      x.translate(this.width/2, 0);

      function line(x1, y1, x2, y2) {
        x.beginPath();
        x.moveTo(x1, y1);
        x.lineTo(x2, y2);
        x.stroke();
      }

      x.lineWidth   = 0.5; //this.borderWidth;
      x.strokeStyle = this.border;

      // Paint lines to childNodes
      if ( this.expanded && this.childNodes.length ) {
        var h = this.height+25;
        line(0, this.height, 0, h);
        var l = this.childNodes.length;
        for ( var i = 0 ; i < l ; i++ ) {
          var c = this.childNodes[i];
          line(0, h, c.x, h);
          line(c.x, h, c.x, c.y);
        }
      }

      x.lineWidth = 1.5; //this.borderWidth;
      // Paint expand/collapse arrow
      if ( this.childNodes.length ) {
        var d = this.expanded ? 5 : -5;
        var y = this.height - 8;
        line(-5, y, 0, y+d);
        line(0, y+d, 5, y);
      }
    },

    function addChildNode(args) {
      var node = this.cls_.create({y: 0, parentNode: this});
      node.copyFrom(args);
      this.add(node);
      this.childNodes.push(node);
      this.doLayout();
      return this;
    },

    function layout() {
      this.left = this.right = 0;

      if ( ! this.expanded ) return false;

      var moved      = false;
      var childNodes = this.childNodes;
      var l          = childNodes.length;

      for ( var i = 0 ; i < l ; i++ ) {
        var c = childNodes[i];
        if ( c.y < this.height*2 ) { moved = true; c.y += 5; }

        moved      = moved || c.layout();
        this.left  = Math.min(this.left, c.x);
        this.right = Math.max(this.right, c.x);
      }

      var m = l/2;
      for ( var i = 0 ; i < l-1 ; i++ ) {
        var n1 = childNodes[i];
        var n2 = childNodes[i+1];
        var d  = n2.x-n1.x+n2.left-n1.right;
        if ( d != this.width + this.padding ) {
          moved = true;
          var w = Math.min(Math.abs(this.width+this.padding-d), 10);
          if ( d > this.width + this.padding ) w = -w;
          if ( i+1 == m ) {
            // console.log('move away', w/2);
            n1.x -= w/2;
            n2.x += w/2;
          } else if ( i < Math.floor(m) ) {
            // console.log('move left', i, m, l, w);
            n1.x -= w;
          } else {
            // console.log('move right', i, m, l, w);
            n2.x += w;
          }
        }
      }

      return moved;
    }
  ],

  listeners: [
    {
      name: 'doLayout',
      isFramed: true,
      code: function() {
        if ( this.layout() ) this.doLayout();
      }
    }
  ]
} );
<commit_msg>TreeGraph improvements.<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
   // package: 'foam.graphics',
   name: 'TreeGraphDemo',
   extends: 'foam.graphics.CView',

   properties: [
     [ 'x', 0 ],
     [ 'y', 0 ],
     [ 'height', 2000 ],
     [ 'width', 2000 ]
   ],

   methods: [
     function init() {
       var g = TreeGraph.create({x:850, y:50});
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       g.childNodes[0].addChildNode()/*.addChildNode()*/;
       g.childNodes[0].addChildNode();
       g.childNodes[0].childNodes[0].addChildNode().addChildNode()/*.addChildNode()*/;
       g.childNodes[0].childNodes[0].childNodes[0].addChildNode().addChildNode().addChildNode();
       g.childNodes[0].childNodes[1].addChildNode();
       g.childNodes[1].addChildNode();
       // g.childNodes[1].childNodes[0].addChildNode(); // TODO: not supported
       g.childNodes[2].addChildNode();
       g.childNodes[2].childNodes[0].addChildNode();
       g.childNodes[2].childNodes[0].addChildNode();
       g.childNodes[2].childNodes[0].addChildNode();
       g.childNodes[2].childNodes[0].childNodes[1].addChildNode();
       g.childNodes[2].childNodes[0].childNodes[2].addChildNode().addChildNode();
       g.layout();
       g.x+=100;

/*
       var g = TreeGraph.create({x:100, y:500});
       this.add(g.addChildNode());

       var g = TreeGraph.create({x:350, y:500});
       this.add(g.addChildNode());
       this.add(g.addChildNode());

       var g = TreeGraph.create({x:850, y:500});
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       this.add(g.addChildNode());
       */
     }
   ]
 });

foam.CLASS({
  // package: 'foam.graphics',
  name: 'TreeGraph',
  extends: 'foam.graphics.Box',

  requires: [
    'foam.graphics.Box',
    'foam.graphics.CView',
    'foam.graphics.Label',
    'foam.graphics.Line'
  ],

  properties: [
    [ 'height', 60 ],
    [ 'width', 135 ],
    [ 'border', 'gray' ],
    [ 'slide', 0 ],
    {
      name: 'childNodes',
      factory: function() { return []; }
    },
    [ 'left',  0 ],
    [ 'right', 0 ],
    {
      class: 'Boolean',
      name: 'expanded',
      value: true
    },
    [ 'color', 'white' ],
    [ 'padding', 30 ],
    'parentNode'
  ],

  methods: [
    function initCView() {
      this.SUPER();

      var c = this.hsl(Math.random()*360, 90, 45);

      this.add(this.Label.create({color: 'black', x: -this.width/2+14, y: 7, text: 'ABC Corp.', font: 'bold 12px sans-serif'}));
      this.add(this.Label.create({color: 'gray', x: -this.width/2+14, y: this.height-22, text: this.childNodes.length ? 'Aggregate' : ''}));
      this.add(this.Label.create({color: 'gray', x: this.width/2-10,  y: this.height-22, align: 'end', text: '$100,000'}));
      this.add(this.Line.create({
        startX: -this.width/2+7,
        startY: 5,
        endX: -this.width/2+7,
        endY: this.height-5,
        color: c,
        lineWidth: 4
      }));

      // If top-level node
      if ( ! this.parentNode ) {
        var self = this;
        this.parent.canvas.on('click', function(e) {
          var c = self.findFirstChildAt(e.clientX+self.width/2, e.clientY);
          if ( ! c ) return;
          c.expanded = ! c.expanded;
          if ( ! c.expanded ) {
            for ( var i = 0 ; i < c.childNodes.length ; i++ ) {
              c.childNodes[i].y = self.height;
              c.childNodes[i].x = 0;
            }
          }
          self.doLayout();
        });
      }
    },

    function paint(x) {
      if ( this.parentNode && ! this.parentNode.expanded ) return;
      this.SUPER(x);
    },

    function paintSelf(x) {
      x.translate(-this.width/2, 0);
      this.SUPER(x);
      x.translate(this.width/2, 0);

      function line(x1, y1, x2, y2) {
        x.beginPath();
        x.moveTo(x1, y1);
        x.lineTo(x2, y2);
        x.stroke();
      }

      x.lineWidth   = 0.5; //this.borderWidth;
      x.strokeStyle = this.border;

      // Paint lines to childNodes
      if ( this.expanded && this.childNodes.length ) {
        var h = this.height+25;
        line(0, this.height, 0, h);
        var l = this.childNodes.length;
        for ( var i = 0 ; i < l ; i++ ) {
          var c = this.childNodes[i];
          line(0, h, c.x, h);
          line(c.x, h, c.x, c.y);
        }
      }

      x.lineWidth = 1.5; //this.borderWidth;
      // Paint expand/collapse arrow
      if ( this.childNodes.length ) {
        var d = this.expanded ? 5 : -5;
        var y = this.height - 8;
        line(-5, y, 0, y+d);
        line(0, y+d, 5, y);
      }
    },

    function addChildNode(args) {
      var node = this.cls_.create({y: 0, parentNode: this});
      node.copyFrom(args);
      this.add(node);
      this.childNodes.push(node);
      this.doLayout();
      return this;
    },

    function layout() {
      this.left = this.right = 0;

      if ( ! this.expanded ) return false;

      var moved      = false;
      var childNodes = this.childNodes;
      var l          = childNodes.length;

      for ( var i = 0 ; i < l ; i++ ) {
        var c = childNodes[i];
        if ( c.y < this.height*2 ) { moved = true; c.y += 5; }

        moved      = moved || c.layout();
        this.left  = Math.min(this.left, c.x);
        this.right = Math.max(this.right, c.x);
      }

      var m = l/2;
      for ( var i = 0 ; i < l-1 ; i++ ) {
        var n1 = childNodes[i];
        var n2 = childNodes[i+1];
        var d  = n2.x-n1.x+n2.left-n1.right;
        if ( d != this.width + this.padding ) {
          moved = true;
          var w = Math.min(Math.abs(this.width+this.padding-d), 10);
          if ( d > this.width + this.padding ) w = -w;
          if ( i+1 == m ) {
            // console.log('move away', w/2);
            n1.x -= w/2;
            n2.x += w/2;
          } else if ( i < Math.floor(m) ) {
            // console.log('move left', i, m, l, w);
            n1.x -= w;
          } else {
            // console.log('move right', i, m, l, w);
            n2.x += w;
          }
        }
      }

      return moved;
    }
  ],

  listeners: [
    {
      name: 'doLayout',
      isFramed: true,
      code: function() {
        if ( this.layout() ) this.doLayout();
      }
    }
  ]
} );
<|endoftext|>


<commit_before>define([
    'ash',
    'game/GameGlobals',
	'game/constants/GameConstants',
	'game/constants/CampConstants',
	'game/constants/LogConstants',
	'game/constants/OccurrenceConstants',
	'game/nodes/sector/CampNode',
    'game/nodes/PlayerPositionNode',
	'game/nodes/tribe/TribeUpgradesNode',
    'game/components/sector/improvements/SectorImprovementsComponent',
    'game/components/common/LogMessagesComponent',
    'game/components/type/LevelComponent',
], function (Ash, GameGlobals, GameConstants, CampConstants, LogConstants, OccurrenceConstants, CampNode, PlayerPositionNode, TribeUpgradesNode,
    SectorImprovementsComponent, LogMessagesComponent, LevelComponent) {
    var ReputationSystem = Ash.System.extend({

        playerNodes: null,
		campNodes: null,
        tribeUpgradeNodes: null,

        lastUpdatePenalties: {},

        constructor: function () {
        },

        addToEngine: function (engine) {
            this.engine = engine;
            this.playerNodes = engine.getNodeList(PlayerPositionNode);
            this.campNodes = engine.getNodeList(CampNode);
            this.tribeUpgradeNodes = engine.getNodeList(TribeUpgradesNode);
        },

        removeFromEngine: function (engine) {
            this.playerNodes = null;
            this.campNodes = null;
            this.tribeUpgradeNodes = null;
            this.engine = null;
        },

        update: function (time) {
            if (GameGlobals.gameState.isPaused) return;

			if (this.campNodes.head) {
				for (var campNode = this.campNodes.head; campNode; campNode = campNode.next) {
                    var reputationComponent = campNode.reputation;
                    var sectorImprovements = campNode.entity.get(SectorImprovementsComponent);

                    reputationComponent.accSources = [];
                    reputationComponent.targetValueSources = [];
                    reputationComponent.accumulation = 0;

                    reputationComponent.targetValue = this.getTargetReputation(campNode);

					this.applyReputationAccumulation(campNode, time);

                    reputationComponent.value = Math.max(0, reputationComponent.value);
                    reputationComponent.isAccumulating = campNode.camp.population > 0 || sectorImprovements.getTotal(improvementTypes.camp) > 0;
				}
			}
        },

        getTargetReputation: function (campNode) {
            var sectorImprovements = campNode.entity.get(SectorImprovementsComponent);
            var targetReputation = 0;

            var addValue = function (value, name) {
                targetReputation += value;
                campNode.reputation.addTargetValueSource(name, value);
            };

            // base: building happiness values
            var allImprovements = sectorImprovements.getAll(improvementTypes.camp);
            for (var i in allImprovements) {
                var improvementVO = allImprovements[i];
                var defaultBonus = improvementVO.getReputationBonus();
                switch (improvementVO.name) {
                    case improvementNames.generator:
                        var numHouses = sectorImprovements.getCount(improvementNames.house) + sectorImprovements.getCount(improvementNames.house2);
                        var generatorBonus = numHouses * CampConstants.REPUTATION_PER_HOUSE_FROM_GENERATOR * (1 + improvementVO.level * 0.02);
                        generatorBonus = Math.round(generatorBonus * 100) / 100;
                        addValue(generatorBonus, "Generator");
                        break;
                    case improvementNames.radio:
                        addValue(improvementVO.count * defaultBonus, "Radio");
                        break;
                    default:
                        addValue(improvementVO.count * defaultBonus, "Buildings");
                        break;
                }
            }

            var targetReputationWithoutPenalties = targetReputation;

            // penalties: food and water
            var storage = GameGlobals.resourcesHelper.getCurrentStorage(true);
			var resources = storage ? storage.resources : null;
            var noFood = resources && resources.getResource(resourceNames.food) <= 0;
            var noWater = resources && resources.getResource(resourceNames.water) <= 0;
            var penalty = Math.max(5, Math.ceil(targetReputationWithoutPenalties));
            if (noFood) {
                addValue(-penalty, "No food");
            }
            if (noWater) {
                addValue(-penalty, "No water");
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_FOOD, noFood);
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_WATER, noWater);

            // penalties: defences
            var defenceLimit = CampConstants.REPUTATION_PENALTY_DEFENCES_THRESHOLD;
            var soldiers = campNode.camp.assignedWorkers.soldier;
            var soldierLevel = GameGlobals.upgradeEffectsHelper.getWorkerLevel("soldier", this.tribeUpgradeNodes.head.upgrades);
            var danger = OccurrenceConstants.getRaidDanger(sectorImprovements, soldiers, soldierLevel);
            var noDefences = danger > defenceLimit;
            if (noDefences) {
                var steppedDanger = Math.ceil(danger * 100 / 5) * 5;
                var penaltyRatio = (steppedDanger - defenceLimit * 100) / 200;
                var defencePenalty = Math.ceil(targetReputationWithoutPenalties * penaltyRatio);
                if (steppedDanger >= 75) {
                    addValue(-defencePenalty, "Terrible defences");
                } else if (steppedDanger >= 0) {
                    addValue(-defencePenalty, "Poor defences");
                } else {
                    addValue(-defencePenalty, "Inadequate defences");
                }
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_DEFENCES, noDefences);

            // penalties: over-crowding
            var housingCap = CampConstants.getHousingCap(sectorImprovements);
            var population = Math.floor(campNode.camp.population);
            var noHousing = population > housingCap;
            if (noHousing) {
                var housingPenaltyRatio = Math.ceil((population - housingCap) / population * 20) / 20;
                var housingPenalty = Math.ceil(targetReputationWithoutPenalties * housingPenaltyRatio);
                addValue(-housingPenalty, "Overcrowding");
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_HOUSING, noHousing);

            // penalties: level population
            var levelVO = GameGlobals.levelHelper.getLevelEntityForSector(campNode.entity).get(LevelComponent).levelVO;
            if (levelVO.populationGrowthFactor < 1) {
                var levelPopPenalty = targetReputationWithoutPenalties * (1 - levelVO.populationGrowthFactor);
                addValue(-levelPopPenalty, "Level population");
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_LEVEL_POP, levelVO.populationGrowthFactor < 1);

            return Math.max(0, targetReputation);
        },

        applyReputationAccumulation: function (campNode, time) {
            var reputationComponent = campNode.reputation;
            var sectorImprovements = campNode.entity.get(SectorImprovementsComponent);
            var levelVO = GameGlobals.levelHelper.getLevelEntityForSector(campNode.entity).get(LevelComponent).levelVO;

            // improvements
            var accRadio = sectorImprovements.getCount(improvementNames.radio) * CampConstants.REPUTATION_PER_RADIO_PER_SEC * GameConstants.gameSpeedCamp;
            var accTargetDiff = reputationComponent.targetValue - reputationComponent.value;
            if (Math.abs(accTargetDiff) < 0.01) accTargetDiff = 0;
            if (accTargetDiff > 0) accTargetDiff = Math.min(10, Math.max(1, accTargetDiff));
            if (accTargetDiff < 0) accTargetDiff = Math.max(-10, Math.min(-1, accTargetDiff));
            var accTarget = (accTargetDiff < 0 ? accTargetDiff * 0.05 : accTargetDiff * 0.01) * GameConstants.gameSpeedCamp;

            // level population factor
            var accLevelPop = 0;
            if (accTarget > 0) {
                accLevelPop += accTarget * levelVO.populationGrowthFactor - accTarget;
                accTarget *= levelVO.populationGrowthFactor;
            }
            if (accRadio > 0) {
                accLevelPop += accRadio * levelVO.populationGrowthFactor - accRadio;
                accRadio *= levelVO.populationGrowthFactor;
            }

            // limits
            var accSpeed = accTarget + accRadio;
            accSpeed = Math.max(-0.05, accSpeed);
            accSpeed = Math.min(0.05, accSpeed);

            // set accumulation
            reputationComponent.addChange("Base", accTarget);
            reputationComponent.addChange("Radio", accRadio);
            if (accLevelPop) {
                reputationComponent.addChange("Level population", accLevelPop)
            }
            reputationComponent.accumulation += accSpeed;

            // apply accumulation
            reputationComponent.value += time * accSpeed;
            if (accTargetDiff === 0) {
                reputationComponent.value = reputationComponent.targetValue;
            } else if (reputationComponent.value > reputationComponent.targetValue && accTargetDiff > 0) {
                reputationComponent.value = reputationComponent.targetValue;
            }
            else if (reputationComponent.value < reputationComponent.targetValue && accTargetDiff < 0) {
                reputationComponent.value = reputationComponent.targetValue;
            }
        },

        logReputationPenalty: function (campNode, penaltyType, hasPenalty) {
            if (GameGlobals.gameState.uiStatus.isHidden) return;
            var campID = campNode.position.getPosition().toString();
            if (!(this.lastUpdatePenalties[campID])) {
                this.lastUpdatePenalties[campID] = {};
            }
            var hadPenalty = this.lastUpdatePenalties[campID][penaltyType];
            if (hasPenalty === hadPenalty) return;

            if (hasPenalty && !hadPenalty) {
                var playerPosition = this.playerNodes.head.position;
                var campPosition = campNode.position;
                if (playerPosition.level === campNode.position.level && playerPosition.sectorId() === campPosition.sectorId()) {
                    var logComponent = this.playerNodes.head.entity.get(LogMessagesComponent);
                    switch (penaltyType) {
                        case CampConstants.REPUTATION_PENALTY_TYPE_DEFENCES:
                            logComponent.addMessage(LogConstants.MSG_ID_REPUTATION_PENALTY_DEFENCES, "People are anxious. They say the camp needs better defences.");
                            break;
                        case CampConstants.REPUTATION_PENALTY_TYPE_HOUSING:
                            logComponent.addMessage(LogConstants.MSG_ID_REPUTATION_PENALTY_HOUSING, "People are unhappy because the camp is over-crowded.");
                            break;
                    }
                }
            }
            this.lastUpdatePenalties[campID][penaltyType] = hasPenalty;
        },
    });

    return ReputationSystem;
});
<commit_msg>[qa] fix reputation 0 if ANOTHER camp has no water/food<commit_after>define([
    'ash',
    'game/GameGlobals',
	'game/constants/GameConstants',
	'game/constants/CampConstants',
	'game/constants/LogConstants',
	'game/constants/OccurrenceConstants',
	'game/nodes/sector/CampNode',
    'game/nodes/PlayerPositionNode',
	'game/nodes/tribe/TribeUpgradesNode',
    'game/components/sector/improvements/SectorImprovementsComponent',
    'game/components/common/LogMessagesComponent',
    'game/components/type/LevelComponent',
], function (Ash, GameGlobals, GameConstants, CampConstants, LogConstants, OccurrenceConstants, CampNode, PlayerPositionNode, TribeUpgradesNode,
    SectorImprovementsComponent, LogMessagesComponent, LevelComponent) {
    var ReputationSystem = Ash.System.extend({

        playerNodes: null,
		campNodes: null,
        tribeUpgradeNodes: null,

        lastUpdatePenalties: {},

        constructor: function () {
        },

        addToEngine: function (engine) {
            this.engine = engine;
            this.playerNodes = engine.getNodeList(PlayerPositionNode);
            this.campNodes = engine.getNodeList(CampNode);
            this.tribeUpgradeNodes = engine.getNodeList(TribeUpgradesNode);
        },

        removeFromEngine: function (engine) {
            this.playerNodes = null;
            this.campNodes = null;
            this.tribeUpgradeNodes = null;
            this.engine = null;
        },

        update: function (time) {
            if (GameGlobals.gameState.isPaused) return;

			if (this.campNodes.head) {
				for (var campNode = this.campNodes.head; campNode; campNode = campNode.next) {
                    var reputationComponent = campNode.reputation;
                    var sectorImprovements = campNode.entity.get(SectorImprovementsComponent);

                    reputationComponent.accSources = [];
                    reputationComponent.targetValueSources = [];
                    reputationComponent.accumulation = 0;

                    reputationComponent.targetValue = this.getTargetReputation(campNode);

					this.applyReputationAccumulation(campNode, time);

                    reputationComponent.value = Math.max(0, reputationComponent.value);
                    reputationComponent.isAccumulating = campNode.camp.population > 0 || sectorImprovements.getTotal(improvementTypes.camp) > 0;
				}
			}
        },

        getTargetReputation: function (campNode) {
            var sectorImprovements = campNode.entity.get(SectorImprovementsComponent);
            var targetReputation = 0;

            var addValue = function (value, name) {
                targetReputation += value;
                campNode.reputation.addTargetValueSource(name, value);
            };

            // base: building happiness values
            var allImprovements = sectorImprovements.getAll(improvementTypes.camp);
            for (var i in allImprovements) {
                var improvementVO = allImprovements[i];
                var defaultBonus = improvementVO.getReputationBonus();
                switch (improvementVO.name) {
                    case improvementNames.generator:
                        var numHouses = sectorImprovements.getCount(improvementNames.house) + sectorImprovements.getCount(improvementNames.house2);
                        var generatorBonus = numHouses * CampConstants.REPUTATION_PER_HOUSE_FROM_GENERATOR * (1 + improvementVO.level * 0.02);
                        generatorBonus = Math.round(generatorBonus * 100) / 100;
                        addValue(generatorBonus, "Generator");
                        break;
                    case improvementNames.radio:
                        addValue(improvementVO.count * defaultBonus, "Radio");
                        break;
                    default:
                        addValue(improvementVO.count * defaultBonus, "Buildings");
                        break;
                }
            }

            var targetReputationWithoutPenalties = targetReputation;

            // penalties: food and water
            var storage = GameGlobals.resourcesHelper.getCurrentCampStorage(campNode.entity);
			var resources = storage ? storage.resources : null;
            var noFood = resources && resources.getResource(resourceNames.food) <= 0;
            var noWater = resources && resources.getResource(resourceNames.water) <= 0;
            var penalty = Math.max(5, Math.ceil(targetReputationWithoutPenalties));
            if (noFood) {
                addValue(-penalty, "No food");
            }
            if (noWater) {
                addValue(-penalty, "No water");
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_FOOD, noFood);
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_WATER, noWater);

            // penalties: defences
            var defenceLimit = CampConstants.REPUTATION_PENALTY_DEFENCES_THRESHOLD;
            var soldiers = campNode.camp.assignedWorkers.soldier;
            var soldierLevel = GameGlobals.upgradeEffectsHelper.getWorkerLevel("soldier", this.tribeUpgradeNodes.head.upgrades);
            var danger = OccurrenceConstants.getRaidDanger(sectorImprovements, soldiers, soldierLevel);
            var noDefences = danger > defenceLimit;
            if (noDefences) {
                var steppedDanger = Math.ceil(danger * 100 / 5) * 5;
                var penaltyRatio = (steppedDanger - defenceLimit * 100) / 200;
                var defencePenalty = Math.ceil(targetReputationWithoutPenalties * penaltyRatio);
                if (steppedDanger >= 75) {
                    addValue(-defencePenalty, "Terrible defences");
                } else if (steppedDanger >= 0) {
                    addValue(-defencePenalty, "Poor defences");
                } else {
                    addValue(-defencePenalty, "Inadequate defences");
                }
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_DEFENCES, noDefences);

            // penalties: over-crowding
            var housingCap = CampConstants.getHousingCap(sectorImprovements);
            var population = Math.floor(campNode.camp.population);
            var noHousing = population > housingCap;
            if (noHousing) {
                var housingPenaltyRatio = Math.ceil((population - housingCap) / population * 20) / 20;
                var housingPenalty = Math.ceil(targetReputationWithoutPenalties * housingPenaltyRatio);
                addValue(-housingPenalty, "Overcrowding");
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_HOUSING, noHousing);

            // penalties: level population
            var levelVO = GameGlobals.levelHelper.getLevelEntityForSector(campNode.entity).get(LevelComponent).levelVO;
            if (levelVO.populationGrowthFactor < 1) {
                var levelPopPenalty = targetReputationWithoutPenalties * (1 - levelVO.populationGrowthFactor);
                addValue(-levelPopPenalty, "Level population");
            }
            this.logReputationPenalty(campNode, CampConstants.REPUTATION_PENALTY_TYPE_LEVEL_POP, levelVO.populationGrowthFactor < 1);

            return Math.max(0, targetReputation);
        },

        applyReputationAccumulation: function (campNode, time) {
            var reputationComponent = campNode.reputation;
            var sectorImprovements = campNode.entity.get(SectorImprovementsComponent);
            var levelVO = GameGlobals.levelHelper.getLevelEntityForSector(campNode.entity).get(LevelComponent).levelVO;

            // improvements
            var accRadio = sectorImprovements.getCount(improvementNames.radio) * CampConstants.REPUTATION_PER_RADIO_PER_SEC * GameConstants.gameSpeedCamp;
            var accTargetDiff = reputationComponent.targetValue - reputationComponent.value;
            if (Math.abs(accTargetDiff) < 0.01) accTargetDiff = 0;
            if (accTargetDiff > 0) accTargetDiff = Math.min(10, Math.max(1, accTargetDiff));
            if (accTargetDiff < 0) accTargetDiff = Math.max(-10, Math.min(-1, accTargetDiff));
            var accTarget = (accTargetDiff < 0 ? accTargetDiff * 0.05 : accTargetDiff * 0.01) * GameConstants.gameSpeedCamp;

            // level population factor
            var accLevelPop = 0;
            if (accTarget > 0) {
                accLevelPop += accTarget * levelVO.populationGrowthFactor - accTarget;
                accTarget *= levelVO.populationGrowthFactor;
            }
            if (accRadio > 0) {
                accLevelPop += accRadio * levelVO.populationGrowthFactor - accRadio;
                accRadio *= levelVO.populationGrowthFactor;
            }

            // limits
            var accSpeed = accTarget + accRadio;
            accSpeed = Math.max(-0.05, accSpeed);
            accSpeed = Math.min(0.05, accSpeed);

            // set accumulation
            reputationComponent.addChange("Base", accTarget);
            reputationComponent.addChange("Radio", accRadio);
            if (accLevelPop) {
                reputationComponent.addChange("Level population", accLevelPop)
            }
            reputationComponent.accumulation += accSpeed;

            // apply accumulation
            reputationComponent.value += time * accSpeed;
            if (accTargetDiff === 0) {
                reputationComponent.value = reputationComponent.targetValue;
            } else if (reputationComponent.value > reputationComponent.targetValue && accTargetDiff > 0) {
                reputationComponent.value = reputationComponent.targetValue;
            }
            else if (reputationComponent.value < reputationComponent.targetValue && accTargetDiff < 0) {
                reputationComponent.value = reputationComponent.targetValue;
            }
        },

        logReputationPenalty: function (campNode, penaltyType, hasPenalty) {
            if (GameGlobals.gameState.uiStatus.isHidden) return;
            var campID = campNode.position.getPosition().toString();
            if (!(this.lastUpdatePenalties[campID])) {
                this.lastUpdatePenalties[campID] = {};
            }
            var hadPenalty = this.lastUpdatePenalties[campID][penaltyType];
            if (hasPenalty === hadPenalty) return;

            if (hasPenalty && !hadPenalty) {
                var playerPosition = this.playerNodes.head.position;
                var campPosition = campNode.position;
                if (playerPosition.level === campNode.position.level && playerPosition.sectorId() === campPosition.sectorId()) {
                    var logComponent = this.playerNodes.head.entity.get(LogMessagesComponent);
                    switch (penaltyType) {
                        case CampConstants.REPUTATION_PENALTY_TYPE_DEFENCES:
                            logComponent.addMessage(LogConstants.MSG_ID_REPUTATION_PENALTY_DEFENCES, "People are anxious. They say the camp needs better defences.");
                            break;
                        case CampConstants.REPUTATION_PENALTY_TYPE_HOUSING:
                            logComponent.addMessage(LogConstants.MSG_ID_REPUTATION_PENALTY_HOUSING, "People are unhappy because the camp is over-crowded.");
                            break;
                    }
                }
            }
            this.lastUpdatePenalties[campID][penaltyType] = hasPenalty;
        },
    });

    return ReputationSystem;
});
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file was auto-generated by the vanadium vdl tool.
var vdl = require('../../../../vdl');
var canonicalize = require('../../../../vdl/canonicalize');

var time = require('./../vdlroot/time');
var uniqueid = require('./../uniqueid');

module.exports = {};

// Types:
var _type1 = new vdl.Type();
var _type2 = new vdl.Type();
var _typeAnnotation = new vdl.Type();
var _typeRequest = new vdl.Type();
var _typeResponse = new vdl.Type();
var _typeSpanRecord = new vdl.Type();
var _typeTraceFlags = new vdl.Type();
var _typeTraceRecord = new vdl.Type();
_type1.kind = vdl.kind.LIST;
_type1.name = "";
_type1.elem = _typeSpanRecord;
_type2.kind = vdl.kind.LIST;
_type2.name = "";
_type2.elem = _typeAnnotation;
_typeAnnotation.kind = vdl.kind.STRUCT;
_typeAnnotation.name = "v.io/v23/vtrace.Annotation";
_typeAnnotation.fields = [{name: "When", type: new time.Time()._type}, {name: "Message", type: vdl.types.STRING}];
_typeRequest.kind = vdl.kind.STRUCT;
_typeRequest.name = "v.io/v23/vtrace.Request";
_typeRequest.fields = [{name: "SpanId", type: new uniqueid.Id()._type}, {name: "TraceId", type: new uniqueid.Id()._type}, {name: "Flags", type: _typeTraceFlags}];
_typeResponse.kind = vdl.kind.STRUCT;
_typeResponse.name = "v.io/v23/vtrace.Response";
_typeResponse.fields = [{name: "Flags", type: _typeTraceFlags}, {name: "Trace", type: _typeTraceRecord}];
_typeSpanRecord.kind = vdl.kind.STRUCT;
_typeSpanRecord.name = "v.io/v23/vtrace.SpanRecord";
_typeSpanRecord.fields = [{name: "Id", type: new uniqueid.Id()._type}, {name: "Parent", type: new uniqueid.Id()._type}, {name: "Name", type: vdl.types.STRING}, {name: "Start", type: new time.Time()._type}, {name: "End", type: new time.Time()._type}, {name: "Annotations", type: _type2}];
_typeTraceFlags.kind = vdl.kind.INT32;
_typeTraceFlags.name = "v.io/v23/vtrace.TraceFlags";
_typeTraceRecord.kind = vdl.kind.STRUCT;
_typeTraceRecord.name = "v.io/v23/vtrace.TraceRecord";
_typeTraceRecord.fields = [{name: "Id", type: new uniqueid.Id()._type}, {name: "Spans", type: _type1}];
_type1.freeze();
_type2.freeze();
_typeAnnotation.freeze();
_typeRequest.freeze();
_typeResponse.freeze();
_typeSpanRecord.freeze();
_typeTraceFlags.freeze();
_typeTraceRecord.freeze();
module.exports.Annotation = (vdl.registry.lookupOrCreateConstructor(_typeAnnotation));
module.exports.Request = (vdl.registry.lookupOrCreateConstructor(_typeRequest));
module.exports.Response = (vdl.registry.lookupOrCreateConstructor(_typeResponse));
module.exports.SpanRecord = (vdl.registry.lookupOrCreateConstructor(_typeSpanRecord));
module.exports.TraceFlags = (vdl.registry.lookupOrCreateConstructor(_typeTraceFlags));
module.exports.TraceRecord = (vdl.registry.lookupOrCreateConstructor(_typeTraceRecord));

// Consts:

  module.exports.Empty = canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeTraceFlags))(0, true), _typeTraceFlags);

  module.exports.CollectInMemory = canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeTraceFlags))(1, true), _typeTraceFlags);

// Errors:

// Services:

<commit_msg>js: Regenerate vdl files<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file was auto-generated by the vanadium vdl tool.
var vdl = require('../../../../vdl');
var canonicalize = require('../../../../vdl/canonicalize');

var time = require('./../vdlroot/time');
var uniqueid = require('./../uniqueid');

module.exports = {};

// Types:
var _type1 = new vdl.Type();
var _type2 = new vdl.Type();
var _typeAnnotation = new vdl.Type();
var _typeRequest = new vdl.Type();
var _typeResponse = new vdl.Type();
var _typeSpanRecord = new vdl.Type();
var _typeTraceFlags = new vdl.Type();
var _typeTraceRecord = new vdl.Type();
_type1.kind = vdl.kind.LIST;
_type1.name = "";
_type1.elem = _typeSpanRecord;
_type2.kind = vdl.kind.LIST;
_type2.name = "";
_type2.elem = _typeAnnotation;
_typeAnnotation.kind = vdl.kind.STRUCT;
_typeAnnotation.name = "v.io/v23/vtrace.Annotation";
_typeAnnotation.fields = [{name: "When", type: new time.Time()._type}, {name: "Message", type: vdl.types.STRING}];
_typeRequest.kind = vdl.kind.STRUCT;
_typeRequest.name = "v.io/v23/vtrace.Request";
_typeRequest.fields = [{name: "SpanId", type: new uniqueid.Id()._type}, {name: "TraceId", type: new uniqueid.Id()._type}, {name: "Flags", type: _typeTraceFlags}, {name: "LogLevel", type: vdl.types.INT32}];
_typeResponse.kind = vdl.kind.STRUCT;
_typeResponse.name = "v.io/v23/vtrace.Response";
_typeResponse.fields = [{name: "Flags", type: _typeTraceFlags}, {name: "Trace", type: _typeTraceRecord}];
_typeSpanRecord.kind = vdl.kind.STRUCT;
_typeSpanRecord.name = "v.io/v23/vtrace.SpanRecord";
_typeSpanRecord.fields = [{name: "Id", type: new uniqueid.Id()._type}, {name: "Parent", type: new uniqueid.Id()._type}, {name: "Name", type: vdl.types.STRING}, {name: "Start", type: new time.Time()._type}, {name: "End", type: new time.Time()._type}, {name: "Annotations", type: _type2}];
_typeTraceFlags.kind = vdl.kind.INT32;
_typeTraceFlags.name = "v.io/v23/vtrace.TraceFlags";
_typeTraceRecord.kind = vdl.kind.STRUCT;
_typeTraceRecord.name = "v.io/v23/vtrace.TraceRecord";
_typeTraceRecord.fields = [{name: "Id", type: new uniqueid.Id()._type}, {name: "Spans", type: _type1}];
_type1.freeze();
_type2.freeze();
_typeAnnotation.freeze();
_typeRequest.freeze();
_typeResponse.freeze();
_typeSpanRecord.freeze();
_typeTraceFlags.freeze();
_typeTraceRecord.freeze();
module.exports.Annotation = (vdl.registry.lookupOrCreateConstructor(_typeAnnotation));
module.exports.Request = (vdl.registry.lookupOrCreateConstructor(_typeRequest));
module.exports.Response = (vdl.registry.lookupOrCreateConstructor(_typeResponse));
module.exports.SpanRecord = (vdl.registry.lookupOrCreateConstructor(_typeSpanRecord));
module.exports.TraceFlags = (vdl.registry.lookupOrCreateConstructor(_typeTraceFlags));
module.exports.TraceRecord = (vdl.registry.lookupOrCreateConstructor(_typeTraceRecord));

// Consts:

  module.exports.Empty = canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeTraceFlags))(0, true), _typeTraceFlags);

  module.exports.CollectInMemory = canonicalize.reduce(new (vdl.registry.lookupOrCreateConstructor(_typeTraceFlags))(1, true), _typeTraceFlags);

// Errors:

// Services:

<|endoftext|>


<commit_before>// ==UserScript==
// @name        QSTT (Qlik Sense Tool Tip)
// @namespace   https://github.com/XavierWiltbank/QlikSenseToolTipUserScript/
// @description Hide the QSTT from the data load editor in qlik sense

//Set this as the url of your qlik sense site
// @include     https://qs*
// @version     1.0
// @grant       none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
// @updateURL   https://github.com/XavierWiltbank/QlikSenseToolTipUserScript/qstt.user.js
// ==/UserScript==

// Code sourced from stack overflow answer: http://stackoverflow.com/questions/2844565/is-there-a-javascript-jquery-dom-change-listener/11546242#11546242

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function (mutations, observer) {
    // fired when a mutation occurs
    $(".qv-function-tip-container").css("top", "20px");
    $(".qv-function-tip-container").css("left", "20px");
    console.log(mutations, observer);
    // ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
    subtree: true,
    attributes: true
});
<commit_msg>Update qstt.user.js<commit_after>// ==UserScript==
// @name        QSTT (Qlik Sense Tool Tip)
// @namespace   https://github.com/XavierWiltbank/QlikSenseToolTipUserScript/
// @description Hide the QSTT from the data load editor in qlik sense

//Set this as the url of your qlik sense site
// @include     https://qs*
// @version     1.0
// @grant       none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
// @updateURL   https://github.com/XavierWiltbank/QlikSenseToolTipUserScript/raw/master/qstt.user.js
// ==/UserScript==

// Code sourced from stack overflow answer: http://stackoverflow.com/questions/2844565/is-there-a-javascript-jquery-dom-change-listener/11546242#11546242

MutationObserver = window.MutationObserver || window.WebKitMutationObserver;

var observer = new MutationObserver(function (mutations, observer) {
    // fired when a mutation occurs
    $(".qv-function-tip-container").css("top", "20px");
    $(".qv-function-tip-container").css("left", "20px");
    console.log(mutations, observer);
    // ...
});

// define what element should be observed by the observer
// and what types of mutations trigger the callback
observer.observe(document, {
    subtree: true,
    attributes: true
});
<|endoftext|>


<commit_before>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2018 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * @callback HitAreaCallback
 *
 * @param {any} hitArea - The hit area object.
 * @param {number} x - The translated x coordinate of the hit test event.
 * @param {number} y - The translated y coordinate of the hit test event.
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that invoked the hit test.
 *
 * @return {boolean} `true` if the coordinates fall within the space of the hitArea, otherwise `false`.
 */

/**
 * @typedef {object} Phaser.Input.InteractiveObject
 *
 * @property {Phaser.GameObjects.GameObject} gameObject - The Game Object to which this Interactive Object is bound.
 * @property {boolean} enabled - Is this Interactive Object currently enabled for input events?
 * @property {boolean} draggable - Is this Interactive Object draggable? Enable with `InputPlugin.setDraggable`.
 * @property {boolean} dropZone - Is this Interactive Object a drag-targets drop zone? Set when the object is created.
 * @property {?Phaser.GameObjects.GameObject} target - [description]
 * @property {Phaser.Cameras.Scene2D.Camera} camera - The most recent Camera to be tested against this Interactive Object.
 * @property {any} hitArea - The hit area for this Interactive Object. Typically a geometry shape, like a Rectangle or Circle.
 * @property {HitAreaCallback} hitAreaCallback - The 'contains' check callback that the hit area shape will use for all hit tests.
 * @property {number} localX - The x coordinate that the Pointer interacted with this object on, relative to the Game Object's top-left position.
 * @property {number} localY - The y coordinate that the Pointer interacted with this object on, relative to the Game Object's top-left position.
 * @property {(0|1|2)} dragState - The current drag state of this Interactive Object. 0 = Not being dragged, 1 = being checked for drag, or 2 = being actively dragged.
 * @property {number} dragStartX - The x coordinate that the Pointer started dragging this Interactive Object from.
 * @property {number} dragStartY - The y coordinate that the Pointer started dragging this Interactive Object from.
 * @property {number} dragX - The x coordinate that this Interactive Object is currently being dragged to.
 * @property {number} dragY - The y coordinate that this Interactive Object is currently being dragged to.
 */

/**
 * Creates a new Interactive Object.
 *
 * This is called automatically by the Input Manager when you enable a Game Object for input.
 *
 * The resulting Interactive Object is mapped to the Game Object's `input` property.
 *
 * @function Phaser.Input.CreateInteractiveObject
 * @since 3.0.0
 *
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to which this Interactive Object is bound.
 * @param {any} hitArea - The hit area for this Interactive Object. Typically a geometry shape, like a Rectangle or Circle.
 * @param {HitAreaCallback} hitAreaCallback - The 'contains' check callback that the hit area shape will use for all hit tests.
 *
 * @return {Phaser.Input.InteractiveObject} The new Interactive Object.
 */
var CreateInteractiveObject = function (gameObject, hitArea, hitAreaCallback)
{
    return {

        gameObject: gameObject,

        enabled: true,
        draggable: false,
        dropZone: false,

        target: null,

        camera: null,

        hitArea: hitArea,
        hitAreaCallback: hitAreaCallback,

        localX: 0,
        localY: 0,

        //  0 = Not being dragged
        //  1 = Being checked for dragging
        //  2 = Being dragged
        dragState: 0,

        dragStartX: 0,
        dragStartY: 0,

        dragX: 0,
        dragY: 0

    };
};

module.exports = CreateInteractiveObject;
<commit_msg>Added docs<commit_after>/**
 * @author       Richard Davey <rich@photonstorm.com>
 * @copyright    2018 Photon Storm Ltd.
 * @license      {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
 */

/**
 * @callback HitAreaCallback
 *
 * @param {any} hitArea - The hit area object.
 * @param {number} x - The translated x coordinate of the hit test event.
 * @param {number} y - The translated y coordinate of the hit test event.
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object that invoked the hit test.
 *
 * @return {boolean} `true` if the coordinates fall within the space of the hitArea, otherwise `false`.
 */

/**
 * @typedef {object} Phaser.Input.InteractiveObject
 *
 * @property {Phaser.GameObjects.GameObject} gameObject - The Game Object to which this Interactive Object is bound.
 * @property {boolean} enabled - Is this Interactive Object currently enabled for input events?
 * @property {boolean} draggable - Is this Interactive Object draggable? Enable with `InputPlugin.setDraggable`.
 * @property {boolean} dropZone - Is this Interactive Object a drag-targets drop zone? Set when the object is created.
 * @property {?Phaser.GameObjects.GameObject} target - An optional drop target for a draggable Interactive Object.
 * @property {Phaser.Cameras.Scene2D.Camera} camera - The most recent Camera to be tested against this Interactive Object.
 * @property {any} hitArea - The hit area for this Interactive Object. Typically a geometry shape, like a Rectangle or Circle.
 * @property {HitAreaCallback} hitAreaCallback - The 'contains' check callback that the hit area shape will use for all hit tests.
 * @property {number} localX - The x coordinate that the Pointer interacted with this object on, relative to the Game Object's top-left position.
 * @property {number} localY - The y coordinate that the Pointer interacted with this object on, relative to the Game Object's top-left position.
 * @property {(0|1|2)} dragState - The current drag state of this Interactive Object. 0 = Not being dragged, 1 = being checked for drag, or 2 = being actively dragged.
 * @property {number} dragStartX - The x coordinate that the Pointer started dragging this Interactive Object from.
 * @property {number} dragStartY - The y coordinate that the Pointer started dragging this Interactive Object from.
 * @property {number} dragX - The x coordinate that this Interactive Object is currently being dragged to.
 * @property {number} dragY - The y coordinate that this Interactive Object is currently being dragged to.
 */

/**
 * Creates a new Interactive Object.
 *
 * This is called automatically by the Input Manager when you enable a Game Object for input.
 *
 * The resulting Interactive Object is mapped to the Game Object's `input` property.
 *
 * @function Phaser.Input.CreateInteractiveObject
 * @since 3.0.0
 *
 * @param {Phaser.GameObjects.GameObject} gameObject - The Game Object to which this Interactive Object is bound.
 * @param {any} hitArea - The hit area for this Interactive Object. Typically a geometry shape, like a Rectangle or Circle.
 * @param {HitAreaCallback} hitAreaCallback - The 'contains' check callback that the hit area shape will use for all hit tests.
 *
 * @return {Phaser.Input.InteractiveObject} The new Interactive Object.
 */
var CreateInteractiveObject = function (gameObject, hitArea, hitAreaCallback)
{
    return {

        gameObject: gameObject,

        enabled: true,
        draggable: false,
        dropZone: false,

        target: null,

        camera: null,

        hitArea: hitArea,
        hitAreaCallback: hitAreaCallback,

        localX: 0,
        localY: 0,

        //  0 = Not being dragged
        //  1 = Being checked for dragging
        //  2 = Being dragged
        dragState: 0,

        dragStartX: 0,
        dragStartY: 0,

        dragX: 0,
        dragY: 0

    };
};

module.exports = CreateInteractiveObject;
<|endoftext|>


<commit_before>/**
 * Sets up the required events and listeners for a complete drag interaction.
 *
 * @memberof interactivity
 */
export class DragInteraction {
  /** @type {number} */ startPositionX;
  /** @type {number} */ startPositionY;
  /** @type {number} */ currentPositionX;
  /** @type {number} */ currentPositionY;
  /** @type {number} */ relativePositionX;
  /** @type {number} */ relativePositionY;
  /** @type {number} */ transitionX;
  /** @type {number} */ transitionY;
  /** @type {number} */ endPositionX;
  /** @type {number} */ endPositionY;

  /**
   * @param {HTMLElement} element
   * @param {callbacks} callbacks
   */
  constructor(element, callbacks) {
    const { onStart, onMove, onEnd } = callbacks;

    /** @param {MouseEvent} event */
    const onMouseStart = event => {
      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
      this.startPositionX = event.clientX;
      this.startPositionY = event.clientY;
      if (typeof onStart === "function") {
        onStart(this);
      }
    };

    /** @param {TouchEvent} event */
    const onTouchStart = event => {
      document.addEventListener("touchmove", onTouchMove);
      document.addEventListener("touchend", onTouchEnd);
      this.startPositionX = event.touches[0].clientX;
      this.startPositionY = event.touches[0].clientY;
      if (typeof onStart === "function") {
        onStart(this);
      }
    };

    /** @param {MouseEvent} event */
    const onMouseMove = event => {
      this.currentPositionX = event.clientX;
      this.currentPositionY = event.clientY;
      this.setRelativePosition();
      this.setTransition();
      if (typeof onMove === "function") {
        onMove(this);
      }
    };

    /** @param {TouchEvent} event */
    const onTouchMove = event => {
      this.currentPositionX = event.touches[0].clientX;
      this.currentPositionY = event.touches[0].clientY;
      this.setRelativePosition();
      this.setTransition();
      if (typeof onMove === "function") {
        onMove(this);
      }
    };

    /** @param {MouseEvent} event */
    const onMouseUp = event => {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
      this.endPositionX = event.clientX;
      this.endPositionY = event.clientY;
      if (typeof onEnd === "function") {
        onEnd(this);
      }
    };

    /** @param {TouchEvent} event */
    const onTouchEnd = event => {
      document.removeEventListener("touchmove", onTouchMove);
      document.removeEventListener("touchend", onTouchEnd);
      this.endPositionX = event.touches[0].clientX;
      this.endPositionY = event.touches[0].clientY;
      if (typeof onEnd === "function") {
        onEnd(this);
      }
    };

    element.addEventListener("mousedown", onMouseStart);
    element.addEventListener("touchstart", onTouchStart);
  }

  setRelativePosition() {
    this.relativePositionX = this.startPositionX - this.currentPositionX;
    this.relativePositionY = this.startPositionY - this.currentPositionY;
  }

  setTransition() {
    this.transitionX = -(this.relativePositionX / element.offsetWidth);
    this.transitionY = -(this.relativePositionY / element.offsetHeight);
  }
}

/**
 * @typedef {object} callbacks
 * @property {dragInteractionCallback} onStart
 * @property {dragInteractionCallback} onMove
 * @property {dragInteractionCallback} onEnd
 */

/**
 * @callback dragInteractionCallback
 * @param {DragInteraction} data
 */
<commit_msg>Refactors DragInteraction.<commit_after>/**
 * Sets up the required events and listeners for a complete drag interaction.
 *
 * @memberof interactivity
 */
export class DragInteraction {
  /** @type {number} */ startX;
  /** @type {number} */ startY;
  /** @type {number} */ currentX;
  /** @type {number} */ currentY;
  /** @type {number} */ relativeX;
  /** @type {number} */ relativeY;
  /** @type {number} */ transitionX;
  /** @type {number} */ transitionY;
  /** @type {number} */ endX;
  /** @type {number} */ endY;

  /**
   * @param {HTMLElement} element
   * @param {callbacks} callbacks
   */
  constructor(element, callbacks) {
    const { onStart, onMove, onEnd } = callbacks;

    /** @param {MouseEvent} event */
    const onMouseStart = event => {
      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
      this.startX = event.clientX;
      this.startY = event.clientY;
      if (typeof onStart === "function") {
        onStart(this);
      }
    };

    /** @param {TouchEvent} event */
    const onTouchStart = event => {
      document.addEventListener("touchmove", onTouchMove);
      document.addEventListener("touchend", onTouchEnd);
      this.startX = event.touches[0].clientX;
      this.startY = event.touches[0].clientY;
      if (typeof onStart === "function") {
        onStart(this);
      }
    };

    /** @param {MouseEvent} event */
    const onMouseMove = event => {
      this.currentX = event.clientX;
      this.currentY = event.clientY;
      this.setValues();
      if (typeof onMove === "function") {
        onMove(this);
      }
    };

    /** @param {TouchEvent} event */
    const onTouchMove = event => {
      this.currentX = event.touches[0].clientX;
      this.currentY = event.touches[0].clientY;
      this.setValues();
      if (typeof onMove === "function") {
        onMove(this);
      }
    };

    /** @param {MouseEvent} event */
    const onMouseUp = event => {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
      this.endX = event.clientX;
      this.endY = event.clientY;
      if (typeof onEnd === "function") {
        onEnd(this);
      }
    };

    /** @param {TouchEvent} event */
    const onTouchEnd = event => {
      document.removeEventListener("touchmove", onTouchMove);
      document.removeEventListener("touchend", onTouchEnd);
      this.endX = event.touches[0].clientX;
      this.endY = event.touches[0].clientY;
      if (typeof onEnd === "function") {
        onEnd(this);
      }
    };

    element.addEventListener("mousedown", onMouseStart);
    element.addEventListener("touchstart", onTouchStart);
  }

  setValues() {
    this.setRelativePosition();
    this.setTransition();
  }

  setRelativePosition() {
    this.relativeX = this.startX - this.currentX;
    this.relativeY = this.startY - this.currentY;
  }

  setTransition() {
    this.transitionX = -(this.relativeX / element.offsetWidth);
    this.transitionY = -(this.relativeY / element.offsetHeight);
  }
}

/**
 * @typedef {object} callbacks
 * @property {dragInteractionCallback} onStart
 * @property {dragInteractionCallback} onMove
 * @property {dragInteractionCallback} onEnd
 */

/**
 * @callback dragInteractionCallback
 * @param {DragInteraction} data
 */
<|endoftext|>


<commit_before>/**
 * captures user actions (provenance) in the Chrome browser.
 * part of the 'browser controller'.
 */
sm.provenance.browser = function() {

	var count = 0;
    const module = {};
	var recordIDs = {};
	var recordNodeID = {};
	var recordNodeCounter = {};
	var recordNodeTime = {};
	var recordNodeLock = {};
	var recordNodeHasChild = {};

	// how to get an action back from the array?

	// can't use tab.id as node id because new url can be opened in the existing tab

    // this is the not needed initially
	const ignoredUrls = [
        'chrome://',
        'chrome-extension://',
        'chrome-devtools://',
        'view-source:',
        'google.co.uk/url',
        'google.com/url',
        'localhost://'
    ],
    bookmarkTypes = [ 'auto_bookmark' ],
    typedTypes = [ 'typed', 'generated', 'keyword', 'keyword_generated' ];

    const dispatch = d3.dispatch('dataChanged');
    onTabUpdate();
	onTabCreation();

	function onTabCreation() {
		chrome.tabs.onCreated.addListener( function(tab) {

			console.log('new - ', 'tabId: '+tab.id, ', index: '+tab.index, ', parent: '+tab.openerTabId, ', title: '+tab.title, 'tab ', tab); // for testing

			tab.title = 'id ' + tab.id + ' - ' + tab.title;

			addNode(tab);

			// if (tab.openerTabId && (tab.url.indexOf("chrome://newtab/") == -1)){
			// var pid = tab.openerTabId;
			// }
			// if(pid) {
			// recordIDs[tab.id] = pid; // an edge
			// } else {
			// recordIDs[tab.id] = tab.id;
			// addAction(tab,tab.id,0); // blank node gets created  - 0 is just written as null value
			// }
		});
	}

    function onTabUpdate() {
        chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {

			console.log('update - ','tabid: '+tabId, ', index: '+tab.index, ', parent: '+tab.openerTabId, ', title: '+tab.title, ', changeInfo ', changeInfo, 'tab ', tab); // for testing

			tab.title = 'id: ' + tab.id + ' - ' + tab.title;

			// 'changeInfo' information:
			// - status: 'loading', if (completed before) {create a new node} else {do nothing}
			// - favIconUrl: url, {udpate node favIcon} or {do nothing}
			// - title: 'page title', {update node title} or {do nothing}
			// - status: 'complete', {set node status to complete}

			/* Check for Finish Loading */
			if(!isFinishLoading(tab,changeInfo)) return;

			if(recordNodeLock[tabId] == 1) {  /* node is created and is waiting for information load to complete */
					if(recordNodeHasChild[tabId] == 1) {
						addAction(tab,tabId,1);
					} else {
						updateAction(tab,tabId); //update the node/edge
					}
			} else {
				addAction(tab,tabId,1); // when a link is added from an old tab this code runs
			}
        });
    }

	function updateAction(tab,tabId) {
		action = {
			id: recordNodeID[tabId],
			time: recordNodeTime[tabId],
			url: tab.url,
			text: tab.title || tab.url || '',
			type: "link",
			favIconUrl: tab.favIconUrl,
			counter: recordNodeCounter[tabId],
			from: recordNodeID[recordIDs[tabId]]
		};
		dispatch.dataChanged(action);
		recordNodeLock[tabId] = 0;
		return ;
	}

	function addAction(tab,tabId,hasChild) {

			const time = new Date(),
					action = {
						id: +time,
						time: time,
						url: tab.url,
						text: tab.title || tab.url || '',
						type: "link",
						favIconUrl: tab.favIconUrl,
						counter: count,
						from: recordNodeID[recordIDs[tabId]]
					};
			recordNodeID[tabId] = +time;
			recordNodeTime[tabId] = time;
			recordNodeCounter[tabId] = count;
			recordNodeLock[tabId] = 1;

			if(hasChild == 1) {
				recordNodeHasChild[recordIDs[tabId]] = 1;
			}

			dispatch.dataChanged(action);
			count++;

	}

	function addNode(tab) {
		const time = new Date();
		const node = {
			tabId: tab.id,
			time: time,
			url: tab.url,
			text: tab.title || tab.url || '',
			type: "link", // what is this?
			favIconUrl: tab.favIconUrl,
			from: tab.openerTabId || ''
		};
		dispatch.dataChanged(node);
	}

	/* Additional Functions for Checking */

	function isFinishLoading(tab,changeInfo) {
        if (isTabIgnored(tab) || isTabInComplete(tab)) return false;
		if(changeInfo.status === undefined || changeInfo.status === null) return false;
		return true;
	}

    function isTabInComplete(tab) {
        return tab.status !== 'complete';
    }

    function isTabIgnored(tab) {
        return ignoredUrls.some(url => tab.url.includes(url));
    }

    d3.rebind(module, dispatch, 'on'); // what's this?
    return module;
};<commit_msg>changed condition for 'loading'<commit_after>/**
 * captures user actions (provenance) in the Chrome browser.
 * part of the 'browser controller'.
 */
sm.provenance.browser = function() {

	var count = 0;
    const module = {};
	var recordIDs = {};
	var recordNodeID = {};
	var recordNodeCounter = {};
	var recordNodeTime = {};
	var recordNodeLock = {};
	var recordNodeHasChild = {};

	// how to get an action back from the array?

	// can't use tab.id as node id because new url can be opened in the existing tab

    // this is the not needed initially
	const ignoredUrls = [
        'chrome://',
        'chrome-extension://',
        'chrome-devtools://',
        'view-source:',
        'google.co.uk/url',
        'google.com/url',
        'localhost://'
    ],
    bookmarkTypes = [ 'auto_bookmark' ],
    typedTypes = [ 'typed', 'generated', 'keyword', 'keyword_generated' ];

    const dispatch = d3.dispatch('dataChanged');
    onTabUpdate();
	onTabCreation();

	function onTabCreation() {
		chrome.tabs.onCreated.addListener( function(tab) {

			console.log('new - ', 'tabId: '+tab.id, ', index: '+tab.index, ', parent: '+tab.openerTabId, ', title: '+tab.title, 'tab ', tab); // for testing

			tab.title = 'id ' + tab.id + ' - ' + tab.title;

			addNode(tab);

			// if (tab.openerTabId && (tab.url.indexOf("chrome://newtab/") == -1)){
			// var pid = tab.openerTabId;
			// }
			// if(pid) {
			// recordIDs[tab.id] = pid; // an edge
			// } else {
			// recordIDs[tab.id] = tab.id;
			// addAction(tab,tab.id,0); // blank node gets created  - 0 is just written as null value
			// }
		});
	}

    function onTabUpdate() {
        chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {

			console.log('update - ','tabid: '+tabId, ', index: '+tab.index, ', parent: '+tab.openerTabId, ', title: '+tab.title, ', changeInfo ', changeInfo, 'tab ', tab); // for testing

			tab.title = 'id: ' + tab.id + ' - ' + tab.title;

			// 'changeInfo' information:
			// - status: 'loading', if (completed before || url changed) {create a new node} else {do nothing}
			// - favIconUrl: url, {udpate node favIcon} or {do nothing}
			// - title: 'page title', {update node title} or {do nothing}
			// - status: 'complete', {set node status to complete}

			/* Check for Finish Loading */
			if(!isFinishLoading(tab,changeInfo)) return;

			if(recordNodeLock[tabId] == 1) {  /* node is created and is waiting for information load to complete */
					if(recordNodeHasChild[tabId] == 1) {
						addAction(tab,tabId,1);
					} else {
						updateAction(tab,tabId); //update the node/edge
					}
			} else {
				addAction(tab,tabId,1); // when a link is added from an old tab this code runs
			}
        });
    }

	function updateAction(tab,tabId) {
		action = {
			id: recordNodeID[tabId],
			time: recordNodeTime[tabId],
			url: tab.url,
			text: tab.title || tab.url || '',
			type: "link",
			favIconUrl: tab.favIconUrl,
			counter: recordNodeCounter[tabId],
			from: recordNodeID[recordIDs[tabId]]
		};
		dispatch.dataChanged(action);
		recordNodeLock[tabId] = 0;
		return ;
	}

	function addAction(tab,tabId,hasChild) {

			const time = new Date(),
					action = {
						id: +time,
						time: time,
						url: tab.url,
						text: tab.title || tab.url || '',
						type: "link",
						favIconUrl: tab.favIconUrl,
						counter: count,
						from: recordNodeID[recordIDs[tabId]]
					};
			recordNodeID[tabId] = +time;
			recordNodeTime[tabId] = time;
			recordNodeCounter[tabId] = count;
			recordNodeLock[tabId] = 1;

			if(hasChild == 1) {
				recordNodeHasChild[recordIDs[tabId]] = 1;
			}

			dispatch.dataChanged(action);
			count++;

	}

	function addNode(tab) {
		const time = new Date();
		const node = {
			tabId: tab.id,
			time: time,
			url: tab.url,
			text: tab.title || tab.url || '',
			type: "link", // what is this?
			favIconUrl: tab.favIconUrl,
			from: tab.openerTabId || ''
		};
		dispatch.dataChanged(node);
	}

	/* Additional Functions for Checking */

	function isFinishLoading(tab,changeInfo) {
        if (isTabIgnored(tab) || isTabInComplete(tab)) return false;
		if(changeInfo.status === undefined || changeInfo.status === null) return false;
		return true;
	}

    function isTabInComplete(tab) {
        return tab.status !== 'complete';
    }

    function isTabIgnored(tab) {
        return ignoredUrls.some(url => tab.url.includes(url));
    }

    d3.rebind(module, dispatch, 'on'); // what's this?
    return module;
};<|endoftext|>