<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { find, findAll, render, settled } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { defer } from 'rsvp';
const { from } = Array;

module('select-box/option', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option />`);

    assert
      .dom('div.select-box__option')
      .exists({ count: 1 }, 'renders with correct class name and tag');
  });

  test('class prefix', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option @classNamePrefix="foo" />`);

    assert
      .dom('.foo__option')
      .exists({ count: 1 }, 'can override the class prefix');
  });

  test('aria role', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option />`);

    assert
      .dom('.select-box__option')
      .hasAttribute('role', 'option', 'defined as an option');
  });

  test('id', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option />`);

    assert.ok(
      find('.select-box__option')
        .getAttribute('id')
        .match(/select-box-el-\d+/),
      'gets a unique id'
    );
  });

  test('disabled', async function(assert) {
    assert.expect(2);

    await render(hbs`<SelectBox::Option @disabled={{true}} />`);

    assert
      .dom('.select-box__option')
      .hasClass(
        'select-box__option--disabled',
        'an option can be flagged as disabled'
      );

    assert.dom('.select-box__option').hasAttribute('aria-disabled', 'true');
  });

  test('aria selected', async function(assert) {
    assert.expect(4);

    this.set('value', 1);

    await render(hbs`
      <SelectBox @value={{this.value}} as |sb|>
        <sb.Option @value={{1}}>One</sb.Option>
        <sb.Option @value={{2}}>Two</sb.Option>
      </SelectBox>
    `);

    assert
      .dom('.select-box__option:nth-child(1)[aria-selected]')
      .hasText(
        'One',
        'the selected option receives an aria selected attribute'
      );

    assert
      .dom('.select-box__option:nth-child(1)[aria-selected]')
      .hasAttribute(
        'aria-selected',
        'true',
        'has correct string value when selected'
      );

    this.set('value', 2);

    assert
      .dom('.select-box__option:nth-child(2)[aria-selected]')
      .hasText(
        'Two',
        'the aria selected attribute is redetermined when the value changes'
      );

    assert
      .dom('.select-box__option:nth-child(2)[aria-selected]')
      .hasAttribute(
        'aria-selected',
        'true',
        'has correct string value when selected'
      );
  });

  test('yield index', async function(assert) {
    assert.expect(2);

    const foo = { myValue: 'foo', myLabel: 'Foo' };
    const bar = { myValue: 'bar', myLabel: 'Bar' };
    const baz = { myValue: 'baz', myLabel: 'Baz' };
    const qux = { myValue: 'qux', myLabel: 'Qux' };

    this.set('group1', [foo, bar]);
    this.set('group2', [baz, qux]);
    this.set('selectedValue', baz);

    await render(hbs`
      <SelectBox @value={{this.selectedValue}} as |sb|>
        <sb.Group @label="Group 1">
          {{#each this.group1 as |item i|}}
            <sb.Option @value={{item}} as |o|>
              {{o.value.myLabel}} {{i}} {{o.index}} {{o.isSelected}}
            </sb.Option>
          {{/each}}
        </sb.Group>
        <sb.Group @label="Group 2">
          {{#each this.group2 as |item i|}}
            <sb.Option @value={{item}} as |o|>
              {{o.value.myLabel}} {{i}} {{o.index}} {{o.isSelected}}
            </sb.Option>
          {{/each}}
        </sb.Group>
      </SelectBox>
    `);

    assert.ok(
      findAll('.select-box__option')[0].textContent.trim() ===
        'Foo 0 0 false' &&
        findAll('.select-box__option')[1].textContent.trim() ===
          'Bar 1 1 false' &&
        findAll('.select-box__option')[2].textContent.trim() ===
          'Baz 0 2 true' &&
        findAll('.select-box__option')[3].textContent.trim() ===
          'Qux 1 3 false',
      'select box options can yield their label, value, index and selected state'
    );

    this.set('group2', [qux, baz]);

    assert.ok(
      findAll('.select-box__option')[0].textContent.trim() ===
        'Foo 0 0 false' &&
        findAll('.select-box__option')[1].textContent.trim() ===
          'Bar 1 1 false' &&
        findAll('.select-box__option')[2].textContent.trim() ===
          'Qux 0 3 false' &&
        findAll('.select-box__option')[3].textContent.trim() === 'Baz 1 2 true',
      'index gets out of sync due to lack of key="@index"'
    );
  });

  test('yield index', async function(assert) {
    assert.expect(4);

    const labels = () => {
      return from(findAll('.select-box__option')).map(o =>
        o.textContent.trim()
      );
    };

    this.set('values', ['foo', 'bar', 'baz']);

    await render(hbs`
      <SelectBox @value="baz" as |sb|>
        {{#each this.values as |value|}}
          <sb.Option @value={{value}} as |o|>
            {{o.value}}: {{o.index}}
          </sb.Option>
        {{/each}}
      </SelectBox>
    `);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 1', 'baz: 2'],
      'precondition, indexes are correct'
    );

    this.set('values', ['foo', 'baz']);
    this.set('values', ['foo', 'bar', 'baz']);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 2', 'baz: 1'],
      'indexes are wrong due to component re-use'
    );

    await render(hbs`
      <SelectBox @value="baz" as |sb|>
        {{#each this.values key="@index" as |value|}}
          <sb.Option @value={{value}} as |o|>
            {{o.value}}: {{o.index}}
          </sb.Option>
        {{/each}}
      </SelectBox>
    `);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 1', 'baz: 2'],
      'precondition, indexes are correct'
    );

    this.set('values', ['foo', 'baz']);
    this.set('values', ['foo', 'bar', 'baz']);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 1', 'baz: 2'],
      'indexes are correct due to key on each'
    );
  });

  test('yield disabled', async function(assert) {
    assert.expect(2);

    this.set('fooDisabled', true);

    await render(hbs`
      <SelectBox::Option @disabled={{this.fooDisabled}} as |o|>
        foo {{if o.isDisabled "disabled"}}
      </SelectBox::Option>
    `);

    assert
      .dom('.select-box__option')
      .hasText('foo disabled', 'yields disabled state');

    this.set('fooDisabled', false);

    assert
      .dom('.select-box__option')
      .hasText('foo', 'disabled state is updated');
  });

  test('yielded promise state', async function(assert) {
    assert.expect(4);

    const deferred1 = defer();
    const deferred2 = defer();

    this.set('promise', deferred1.promise);

    await render(hbs`
      <SelectBox::Option @value={{this.promise}} as |o|>
        isPending: {{o.isPending}}

        isRejected: {{o.isRejected}}

        isFulfilled: {{o.isFulfilled}}

        isSettled: {{o.isSettled}}

      </SelectBox::Option>
    `);

    assert.dom(this.element).hasText(`
        isPending: true
        isRejected: false
        isFulfilled: false
        isSettled: false
    `);

    deferred1.resolve('foo');
    await settled();

    assert.dom(this.element).hasText(`
        isPending: false
        isRejected: false
        isFulfilled: true
        isSettled: true
    `);

    this.set('promise', deferred2.promise);

    assert.dom(this.element).hasText(`
        isPending: true
        isRejected: false
        isFulfilled: false
        isSettled: false
    `);

    deferred2.reject('bar');
    await settled();

    assert.dom(this.element).hasText(`
        isPending: false
        isRejected: true
        isFulfilled: false
        isSettled: true
    `);
  });

  test('promise value (aria busy)', async function(assert) {
    assert.expect(2);

    const deferred = defer();

    this.set('promise', deferred.promise);

    await render(hbs`<SelectBox::Option @value={{this.promise}} />`);

    assert
      .dom('.select-box__option')
      .hasAttribute(
        'aria-busy',
        'true',
        'select box option has busy attribute when resolving promise'
      );

    deferred.resolve();

    await settled();

    assert
      .dom('.select-box__option')
      .hasAttribute(
        'aria-busy',
        'false',
        'select box option no longer has busy attribute when promise has resolved'
      );
  });
});
<commit_msg>update test<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { find, findAll, render, settled } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { defer } from 'rsvp';
const { from } = Array;

module('select-box/option', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option />`);

    assert
      .dom('div.select-box__option')
      .exists({ count: 1 }, 'renders with correct class name and tag');
  });

  test('class prefix', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option @classNamePrefix="foo" />`);

    assert
      .dom('.foo__option')
      .exists({ count: 1 }, 'can override the class prefix');
  });

  test('aria role', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option />`);

    assert
      .dom('.select-box__option')
      .hasAttribute('role', 'option', 'defined as an option');
  });

  test('id', async function(assert) {
    assert.expect(1);

    await render(hbs`<SelectBox::Option />`);

    assert.ok(
      find('.select-box__option')
        .getAttribute('id')
        .match(/select-box-el-\d+/),
      'gets a unique id'
    );
  });

  test('disabled', async function(assert) {
    assert.expect(2);

    await render(hbs`<SelectBox::Option @disabled={{true}} />`);

    assert
      .dom('.select-box__option')
      .hasClass(
        'select-box__option--disabled',
        'an option can be flagged as disabled'
      );

    assert.dom('.select-box__option').hasAttribute('aria-disabled', 'true');
  });

  test('aria selected', async function(assert) {
    assert.expect(4);

    this.set('value', 1);

    await render(hbs`
      <SelectBox @value={{this.value}} as |sb|>
        <sb.Option @value={{1}}>One</sb.Option>
        <sb.Option @value={{2}}>Two</sb.Option>
      </SelectBox>
    `);

    assert
      .dom('.select-box__option:nth-child(1)[aria-selected]')
      .hasText(
        'One',
        'the selected option receives an aria selected attribute'
      );

    assert
      .dom('.select-box__option:nth-child(1)[aria-selected]')
      .hasAttribute(
        'aria-selected',
        'true',
        'has correct string value when selected'
      );

    this.set('value', 2);

    assert
      .dom('.select-box__option:nth-child(2)[aria-selected]')
      .hasText(
        'Two',
        'the aria selected attribute is redetermined when the value changes'
      );

    assert
      .dom('.select-box__option:nth-child(2)[aria-selected]')
      .hasAttribute(
        'aria-selected',
        'true',
        'has correct string value when selected'
      );
  });

  test('yield index failure', async function(assert) {
    // https://ember-twiddle.com/ddae8f58d5175e64577e79d720013cf2

    assert.expect(2);

    const foo = { myValue: 'foo', myLabel: 'Foo' };
    const bar = { myValue: 'bar', myLabel: 'Bar' };
    const baz = { myValue: 'baz', myLabel: 'Baz' };
    const qux = { myValue: 'qux', myLabel: 'Qux' };

    this.set('group1', [foo, bar]);
    this.set('group2', [baz, qux]);
    this.set('selectedValue', baz);

    await render(hbs`
      <SelectBox @value={{this.selectedValue}} as |sb|>
        <sb.Group @label="Group 1">
          {{#each this.group1 as |item i|}}
            <sb.Option @value={{item}} as |o|>
              {{o.value.myLabel}} {{i}} {{o.index}} {{o.isSelected}}
            </sb.Option>
          {{/each}}
        </sb.Group>
        <sb.Group @label="Group 2">
          {{#each this.group2 as |item i|}}
            <sb.Option @value={{item}} as |o|>
              {{o.value.myLabel}} {{i}} {{o.index}} {{o.isSelected}}
            </sb.Option>
          {{/each}}
        </sb.Group>
      </SelectBox>
    `);

    assert.ok(
      findAll('.select-box__option')[0].textContent.trim() ===
        'Foo 0 0 false' &&
        findAll('.select-box__option')[1].textContent.trim() ===
          'Bar 1 1 false' &&
        findAll('.select-box__option')[2].textContent.trim() ===
          'Baz 0 2 true' &&
        findAll('.select-box__option')[3].textContent.trim() ===
          'Qux 1 3 false',
      'select box options can yield their label, value, index and selected state'
    );

    this.set('group2', [qux, baz]);

    assert.ok(
      findAll('.select-box__option')[0].textContent.trim() ===
        'Foo 0 0 false' &&
        findAll('.select-box__option')[1].textContent.trim() ===
          'Bar 1 1 false' &&
        findAll('.select-box__option')[2].textContent.trim() ===
          'Qux 0 3 false' &&
        findAll('.select-box__option')[3].textContent.trim() === 'Baz 1 2 true',
      'index gets out of sync due to lack of key="@index"'
    );
  });

  test('yield index', async function(assert) {
    assert.expect(4);

    const labels = () => {
      return from(findAll('.select-box__option')).map(o =>
        o.textContent.trim()
      );
    };

    this.set('values', ['foo', 'bar', 'baz']);

    await render(hbs`
      <SelectBox @value="baz" as |sb|>
        {{#each this.values as |value|}}
          <sb.Option @value={{value}} as |o|>
            {{o.value}}: {{o.index}}
          </sb.Option>
        {{/each}}
      </SelectBox>
    `);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 1', 'baz: 2'],
      'precondition, indexes are correct'
    );

    this.set('values', ['foo', 'baz']);
    this.set('values', ['foo', 'bar', 'baz']);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 2', 'baz: 1'],
      'indexes are wrong due to component re-use, BUT they are still unique ' +
        'which is important'
    );

    await render(hbs`
      <SelectBox @value="baz" as |sb|>
        {{#each this.values key="@index" as |value|}}
          <sb.Option @value={{value}} as |o|>
            {{o.value}}: {{o.index}}
          </sb.Option>
        {{/each}}
      </SelectBox>
    `);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 1', 'baz: 2'],
      'precondition, indexes are correct'
    );

    this.set('values', ['foo', 'baz']);
    this.set('values', ['foo', 'bar', 'baz']);

    assert.deepEqual(
      labels(),
      ['foo: 0', 'bar: 1', 'baz: 2'],
      'indexes are correct due to key on each'
    );
  });

  test('yield disabled', async function(assert) {
    assert.expect(2);

    this.set('fooDisabled', true);

    await render(hbs`
      <SelectBox::Option @disabled={{this.fooDisabled}} as |o|>
        foo {{if o.isDisabled "disabled"}}
      </SelectBox::Option>
    `);

    assert
      .dom('.select-box__option')
      .hasText('foo disabled', 'yields disabled state');

    this.set('fooDisabled', false);

    assert
      .dom('.select-box__option')
      .hasText('foo', 'disabled state is updated');
  });

  test('yielded promise state', async function(assert) {
    assert.expect(4);

    const deferred1 = defer();
    const deferred2 = defer();

    this.set('promise', deferred1.promise);

    await render(hbs`
      <SelectBox::Option @value={{this.promise}} as |o|>
        isPending: {{o.isPending}}

        isRejected: {{o.isRejected}}

        isFulfilled: {{o.isFulfilled}}

        isSettled: {{o.isSettled}}

      </SelectBox::Option>
    `);

    assert.dom(this.element).hasText(`
        isPending: true
        isRejected: false
        isFulfilled: false
        isSettled: false
    `);

    deferred1.resolve('foo');
    await settled();

    assert.dom(this.element).hasText(`
        isPending: false
        isRejected: false
        isFulfilled: true
        isSettled: true
    `);

    this.set('promise', deferred2.promise);

    assert.dom(this.element).hasText(`
        isPending: true
        isRejected: false
        isFulfilled: false
        isSettled: false
    `);

    deferred2.reject('bar');
    await settled();

    assert.dom(this.element).hasText(`
        isPending: false
        isRejected: true
        isFulfilled: false
        isSettled: true
    `);
  });

  test('promise value (aria busy)', async function(assert) {
    assert.expect(2);

    const deferred = defer();

    this.set('promise', deferred.promise);

    await render(hbs`<SelectBox::Option @value={{this.promise}} />`);

    assert
      .dom('.select-box__option')
      .hasAttribute(
        'aria-busy',
        'true',
        'select box option has busy attribute when resolving promise'
      );

    deferred.resolve();

    await settled();

    assert
      .dom('.select-box__option')
      .hasAttribute(
        'aria-busy',
        'false',
        'select box option no longer has busy attribute when promise has resolved'
      );
  });
});
<|endoftext|>


<commit_before>
var index = require ('./views/index.hbs')
var Enemy = require ('./enemy.js')
var Player = require ('./player.js')
var survive = require ('./win.js')

document.addEventListener('DOMContentLoaded', start)

//proof of concept below

function start(){
  var target = document.getElementById('body')
  target.innerHTML = index({story: 'This is the story.', image: '../public/images/Village_test.jpg'})

  var introduction = function(){
    indexIntro ++
    var target = document.getElementById('story-text')
    target.innerHTML = introArray[indexIntro]

    if (indexIntro == introArray.length-1){
      introButton.removeEventListener('click', introduction)
      introButton.addEventListener('click', beginAdventure)
    }
   };

    var beginAdventure = function(){
      if(!battling){
      var target = document.getElementById('story-text')
      var battleText = "Scrundgy's health: " + testEnemy.health + " James's health: " + player.health
      target.innerHTML = battleText
      battling = true
      var image = document.getElementById('image')
      image.src = '../public/images/Village_test.jpg'
      }
    var image = document.getElementById('image')
    image.src = '../public/images/dragonimg_test2.jpg'
    }

    var reset = function() {
      console.log("help what's happening")
      introButton.removeEventListener('click', introduction)
      introButton.removeEventListener('click', beginAdventure)
      resetButton.removeEventListener('click', reset)
      attackButton.removeEventListener('click', ("click", function() {
        if(battling){
          player.attack(testEnemy)
          if(testEnemy.health <= 0){
            enemyDead = true
          }
          var target = document.getElementById('story-text')
          var battleText = "Scrundgy's health: " + testEnemy.health + " James's health: " + player.health
          if(enemyDead){
            battleText += "
  James, obviously too agile, cleaves SCRUNDGE's head in twain .Upon returning to the vilage, he is bestowed the title of AMAZING."
            battling = false
          }
          target.innerHTML = battleText
       }
      }))
      start()
    }

    var resetButton = document.getElementById('resetButton')
    resetButton.addEventListener('click', reset)

    var attackButton = document.getElementById("attackButton")
    attackButton.addEventListener("click", function() {
      if(battling){
        player.attack(testEnemy)
        if(testEnemy.health <= 0){
          enemyDead = true
          console.log(survive())
        }
        var target = document.getElementById('story-text')
        var battleText = "Scrundgy's health: " + testEnemy.health + " James's health: " + player.health
        if(enemyDead){
          battleText += "
  James, obviously too agile, cleaves SCRUNDGE's head in twain .Upon returning to the vilage, he is bestowed the title of AMAZING. GAME OVER"
          battling = false
        }
        target.innerHTML = battleText
     }
    })

  var enemyDead = false
  var battling = false
  var introButton = document.getElementById("introButton")
  introButton.addEventListener('click', introduction);
  var player = new Player({health: 1000,  minAttackPower: 50, maxAttackPower: 75})
  var testEnemy = new Enemy({health: 650,  minAttackPower: 15, maxAttackPower: 20})

  console.log("Scrundgy was here 2016");

  var introArray = ['James awakes up in his small village on the outskirts of westfall...', 'The news board has a display of a fearsome dragon by the name of SCRUNDGE has been terrorising nearby villagesand needs the be slain, JAMES feels like he is up to the challenge!', 'Upon equipping his sword and shield, James begins his adventure off to the dragons lair out in the burning steppes. A long and vicious journey he would surely encounter many foes.', 'Surprisingly, james makes it to the burning steppes uninterrupted, and un-scathed..', 'Upon seeing the SCRUNDGE himself James takes up arms and begins a GLORIOUS battle!']
  var indexIntro = -1
}
<commit_msg>fixed scrundgys name<commit_after>
var index = require ('./views/index.hbs')
var Enemy = require ('./enemy.js')
var Player = require ('./player.js')
var survive = require ('./win.js')

document.addEventListener('DOMContentLoaded', start)

//proof of concept below

function start(){
  var target = document.getElementById('body')
  target.innerHTML = index({story: 'This is the story.', image: '../public/images/Village_test.jpg'})

  var introduction = function(){
    indexIntro ++
    var target = document.getElementById('story-text')
    target.innerHTML = introArray[indexIntro]

    if (indexIntro == introArray.length-1){
      introButton.removeEventListener('click', introduction)
      introButton.addEventListener('click', beginAdventure)
    }
   };

    var beginAdventure = function(){
      if(!battling){
      var target = document.getElementById('story-text')
      var battleText = "Scrundgy's health: " + testEnemy.health + " James's health: " + player.health
      target.innerHTML = battleText
      battling = true
      var image = document.getElementById('image')
      image.src = '../public/images/Village_test.jpg'
      }
    var image = document.getElementById('image')
    image.src = '../public/images/dragonimg_test2.jpg'
    }

    var reset = function() {
      console.log("help what's happening")
      introButton.removeEventListener('click', introduction)
      introButton.removeEventListener('click', beginAdventure)
      resetButton.removeEventListener('click', reset)
      attackButton.removeEventListener('click', ("click", function() {
        if(battling){
          player.attack(testEnemy)
          if(testEnemy.health <= 0){
            enemyDead = true
          }
          var target = document.getElementById('story-text')
          var battleText = "Scrundgy's health: " + testEnemy.health + " James's health: " + player.health
          if(enemyDead){
            battleText += "
  James, obviously too agile, cleaves SCRUNDGE's head in twain .Upon returning to the vilage, he is bestowed the title of AMAZING."
            battling = false
          }
          target.innerHTML = battleText
       }
      }))
      start()
    }

    var resetButton = document.getElementById('resetButton')
    resetButton.addEventListener('click', reset)

    var attackButton = document.getElementById("attackButton")
    attackButton.addEventListener("click", function() {
      if(battling){
        player.attack(testEnemy)
        if(testEnemy.health <= 0){
          enemyDead = true
          console.log(survive())
        }
        var target = document.getElementById('story-text')
        var battleText = "Scrundgy's health: " + testEnemy.health + " James's health: " + player.health
        if(enemyDead){
          battleText += "
  James, obviously too agile, cleaves SCRUNDGEY's head in twain .Upon returning to the vilage, he is bestowed the title of AMAZING. GAME OVER"
          battling = false
        }
        target.innerHTML = battleText
     }
    })

  var enemyDead = false
  var battling = false
  var introButton = document.getElementById("introButton")
  introButton.addEventListener('click', introduction);
  var player = new Player({health: 1000,  minAttackPower: 50, maxAttackPower: 75})
  var testEnemy = new Enemy({health: 650,  minAttackPower: 15, maxAttackPower: 20})

  console.log("Scrundgy was here 2016");

  var introArray = ['James awakes up in his small village on the outskirts of westfall...', 'The news board has a display of a fearsome dragon by the name of SCRUNDGEY has been terrorising nearby villagesand needs the be slain, JAMES feels like he is up to the challenge!', 'Upon equipping his sword and shield, James begins his adventure off to the dragons lair out in the burning steppes. A long and vicious journey he would surely encounter many foes.', 'Surprisingly, james makes it to the burning steppes uninterrupted, and un-scathed..', 'Upon seeing the SCRUNDGEY himself James takes up arms and begins a GLORIOUS battle!']
  var indexIntro = -1
}
<|endoftext|>


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

  angular.module('app.resources')
    .factory('CollectionsApi', CollectionsApiFactory);

  /** @ngInject */
  function CollectionsApiFactory($http, API_BASE) {
    var service = {
      query: query,
      get: get,
      post: post,
    };

    return service;

    function query(collection, options) {
      var url = API_BASE + '/api/' + collection;

      // $log.debug("query = " + url + buildQuery(options));

      return $http.get(url + buildQuery(options), buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    function get(collection, id, options) {
      var url = API_BASE + '/api/' + collection + '/' + id;

      // $log.debug("get = " + url + buildQuery(options));

      return $http.get(url + buildQuery(options), buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    function post(collection, id, options, data) {
      var url = API_BASE + '/api/' + collection + '/' + (id || "") + buildQuery(options);

      // $log.debug("post = " + url + buildQuery(options));

      return $http.post(url, data, buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    // Private

    function buildQuery(options) {
      var params = [];
      options = options || {};

      if (options.expand) {
        if (angular.isArray(options.expand)) {
          options.expand = options.expand.join(',');
        }
        params.push('expand=' + encodeURIComponent(options.expand));
      }

      if (options.attributes) {
        if (angular.isArray(options.attributes)) {
          options.attributes = options.attributes.join(',');
        }
        params.push('attributes=' + encodeURIComponent(options.attributes));
      }

      if (options.decorators) {
        if (angular.isArray(options.decorators)) {
          options.decorators = options.decorators.join(',');
        }
        params.push('decorators=' + encodeURIComponent(options.decorators));
      }

      if (options.filter) {
        angular.forEach(options.filter, function(filter) {
          params.push('filter[]=' + encodeURIComponent(filter));
        });
      }

      if (options.sort_by) {
        params.push('sort_by=' + encodeURIComponent(options.sort_by));
      }

      if (options.sort_options) {
        params.push('sort_options=' + encodeURIComponent(options.sort_options));
      }

      if (params.length) {
        return '?' + params.join('&');
      }

      return '';
    }

    function buildConfig(options) {
      var config = {};
      options = options || {};

      if (options.auto_refresh) {
        config.headers = {
          'X-Auth-Skip-Token-Renewal': 'true',
        };
      }

      return config;
    }
  }
})();
<commit_msg>Implemented DELETE method in CollectionsApi for single deletions<commit_after>(function() {
  'use strict';

  angular.module('app.resources')
    .factory('CollectionsApi', CollectionsApiFactory);

  /** @ngInject */
  function CollectionsApiFactory($http, API_BASE) {
    var service = {
      query: query,
      get: get,
      post: post,
      delete: remove,
    };

    return service;

    function query(collection, options) {
      var url = API_BASE + '/api/' + collection;

      // $log.debug("query = " + url + buildQuery(options));

      return $http.get(url + buildQuery(options), buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    function get(collection, id, options) {
      var url = API_BASE + '/api/' + collection + '/' + id;

      // $log.debug("get = " + url + buildQuery(options));

      return $http.get(url + buildQuery(options), buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    function post(collection, id, options, data) {
      var url = API_BASE + '/api/' + collection + '/' + (id || "") + buildQuery(options);

      // $log.debug("post = " + url + buildQuery(options));

      return $http.post(url, data, buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    // delete is a reserved word in JS
    function remove(collection, id, options) {
      var url = API_BASE + '/api/' + collection + '/' + (id || "") + buildQuery(options);

      // $log.debug("post = " + url + buildQuery(options));

      return $http.delete(url, buildConfig(options))
        .then(handleSuccess);

      function handleSuccess(response) {
        return response.data;
      }
    }

    // Private

    function buildQuery(options) {
      var params = [];
      options = options || {};

      if (options.expand) {
        if (angular.isArray(options.expand)) {
          options.expand = options.expand.join(',');
        }
        params.push('expand=' + encodeURIComponent(options.expand));
      }

      if (options.attributes) {
        if (angular.isArray(options.attributes)) {
          options.attributes = options.attributes.join(',');
        }
        params.push('attributes=' + encodeURIComponent(options.attributes));
      }

      if (options.decorators) {
        if (angular.isArray(options.decorators)) {
          options.decorators = options.decorators.join(',');
        }
        params.push('decorators=' + encodeURIComponent(options.decorators));
      }

      if (options.filter) {
        angular.forEach(options.filter, function(filter) {
          params.push('filter[]=' + encodeURIComponent(filter));
        });
      }

      if (options.sort_by) {
        params.push('sort_by=' + encodeURIComponent(options.sort_by));
      }

      if (options.sort_options) {
        params.push('sort_options=' + encodeURIComponent(options.sort_options));
      }

      if (params.length) {
        return '?' + params.join('&');
      }

      return '';
    }

    function buildConfig(options) {
      var config = {};
      options = options || {};

      if (options.auto_refresh) {
        config.headers = {
          'X-Auth-Skip-Token-Renewal': 'true',
        };
      }

      return config;
    }
  }
})();
<|endoftext|>


<commit_before>DailymotionPlayer = (function() {

	var regex = /https?:\/\/(?:www\.)?dailymotion.com(?:\/embed)?\/video\/([\w-]+)/;

	var EVENT_MAP = {
		0: "onEnded",
		1: "onPlaying",
		2: "onPaused"
	};

	function DailymotionPlayer(eventHandlers, embedVars) {
		this.eventHandlers = eventHandlers || {};
		this.embedVars = embedVars || {};
		this.label = "Dailymotion";
		this.element = null;
		this.isReady = false;
		this.trackInfo = {};
		var that = this;

		window.onDailymotionStateChange = function(newState) {
			that.safeClientCall(EVENT_MAP[newState], that);
			/*if (newState == 1) {
				console.log("getduration", that.element.getDuration());
				that.trackInfo.duration = that.element.getDuration(); //that.safeCall("getDuration");
			}*/
		};

		window.onDailymotionError = function(error) {
			that.safeClientCall("onError", that, {source:"DailymotionPlayer", data: error});
		}

		/*window.onDailymotionVideoProgress = function(a) {
			console.log("progress", a)
		}*/

		window.onDailymotionPlayerReady = function(playerId) {
			that.element = /*that.element ||*/ document.getElementById(playerId); /* ytplayer*/
			that.element.addEventListener("onStateChange", "onDailymotionStateChange");
			that.element.addEventListener("onError", "onDailymotionError");
			//that.element.addEventListener("onVideoProgress", "onDailymotionVideoProgress");
			that.safeClientCall("onApiReady", that);
		}

		that.isReady = true;
		that.safeClientCall("onApiLoaded", that);
	}

	DailymotionPlayer.prototype.safeCall = function(fctName, p1, p2) {
		//return (this.element || {})[fctName] && this.element[fctName](p1, p2);
		var args = Array.apply(null, arguments).slice(1); // exclude first arg (fctName)
		var fct = (this.element || {})[fctName];
		fct && fct.apply(this.element, args);
	}

	DailymotionPlayer.prototype.safeClientCall = function(fctName, p1, p2) {
		try {
			return this.eventHandlers[fctName] && this.eventHandlers[fctName](p1, p2);
		}
		catch(e) {
			console.log("DM safeclientcall error", e, e.stack);
		}
	}

	DailymotionPlayer.prototype.embed = function (vars) {
		this.embedVars = vars = vars || {};
		this.embedVars.playerId = this.embedVars.playerId || 'dmplayer';
		this.trackInfo = {};
		this.element = document.createElement("object");
		this.element.id = this.embedVars.playerId;

		this.holder = document.createElement("div");
		this.holder.id = "genericholder";
		this.holder.appendChild(this.element);
		this.embedVars.playerContainer.appendChild(this.holder);

		var params = {
			allowScriptAccess: "always"
		};

		var atts = {
			id: this.embedVars.playerId
		};

		var swfParams = {
			//api: "postMessage",
			info: 0,
			logo: 0,
			related: 0,
			autoplay: 1,
			enableApi: 1,
			showinfo: 0,
			hideInfos: 1,
			chromeless: 1,
			withLoading: 0,
			playerapiid: this.embedVars.playerId
		};

		var paramsQS = Object.keys(swfParams).map(function(k){ // query string
			return k + "=" + encodeURIComponent(swfParams[k]);
		}).join("&");

		var paramsHTML = Object.keys(params).map(function(k){
			return '<param name="' + k +'" value="' + encodeURIComponent(params[k]) + '">';
		}).join();

		var embedAttrs = {
			id: this.embedVars.playerId,
			width: this.embedVars.height || '200',
			height: this.embedVars.width || '200',
			type: "application/x-shockwave-flash",
			data: window.location.protocol+'//www.dailymotion.com/swf/'+this.embedVars.videoId+'?'+paramsQS,
			innerHTML: paramsHTML
		};
		if (USE_SWFOBJECT) {
			swfobject.embedSWF(embedAttrs.data, this.embedVars.playerId, embedAttrs.width, embedAttrs.height, "9.0.0", "/js/swfobject_expressInstall.swf", null, params, atts);
		}
		else {
			$(this.element).attr(embedAttrs);
		}
		$(this.element).show();
		this.safeClientCall("onEmbedReady");
	}

	DailymotionPlayer.prototype.getEid = function(url, cb) {
		cb((url.match(regex) || []).pop(), this);
	}

	DailymotionPlayer.prototype.play = function(id) {
		if (!this.currentId || this.currentId != id) {
			this.embedVars.videoId = id;
			this.embed(this.embedVars);
		}
	}

	DailymotionPlayer.prototype.pause = function(vol) {
		//this.element.pauseVideo();
		this.safeCall("pauseVideo");
	};

	DailymotionPlayer.prototype.resume = function(vol) {
		//this.element.playVideo();
		this.safeCall("playVideo");
	};

	/*DailymotionPlayer.prototype.stop = function(vol) {
		//this.element.stopVideo();
		this.safeCall("clearVideo");
	};*/

	DailymotionPlayer.prototype.getTrackPosition = function(callback) {
		var time = this.element.getCurrentTime();
		/**/this.trackInfo.duration = this.element.getDuration();
		//console.log("DM time & duration", time, this.trackInfo.duration);
		callback && callback(this.element.getCurrentTime());
	};

	DailymotionPlayer.prototype.setTrackPosition = function(pos) {
		//this.element.seekTo(pos);
		this.safeCall("seekTo", pos);
	};

	DailymotionPlayer.prototype.setVolume = function(vol) {
		//(this.element||{}).setVolume && this.element.setVolume(vol * 100);
		this.safeCall("setVolume", vol * 100);
	};

	return DailymotionPlayer;
})();<commit_msg>fixed encoding problem<commit_after>DailymotionPlayer = (function() {

	var regex = /https?:\/\/(?:www\.)?dailymotion.com(?:\/embed)?\/video\/([\w-]+)/;

	var EVENT_MAP = {
		0: "onEnded",
		1: "onPlaying",
		2: "onPaused"
	};

	function DailymotionPlayer(eventHandlers, embedVars) {
		this.eventHandlers = eventHandlers || {};
		this.embedVars = embedVars || {};
		this.label = "Dailymotion";
		this.element = null;
		this.isReady = false;
		this.trackInfo = {};
		var that = this;

		window.onDailymotionStateChange = function(newState) {
			that.safeClientCall(EVENT_MAP[newState], that);
			/*if (newState == 1) {
				console.log("getduration", that.element.getDuration());
				that.trackInfo.duration = that.element.getDuration(); //that.safeCall("getDuration");
			}*/
		};

		window.onDailymotionError = function(error) {
			that.safeClientCall("onError", that, {source:"DailymotionPlayer", data: error});
		}

		/*window.onDailymotionVideoProgress = function(a) {
			console.log("progress", a)
		}*/

		window.onDailymotionPlayerReady = function(playerId) {
			that.element = /*that.element ||*/ document.getElementById(playerId); /* ytplayer*/
			that.element.addEventListener("onStateChange", "onDailymotionStateChange");
			that.element.addEventListener("onError", "onDailymotionError");
			//that.element.addEventListener("onVideoProgress", "onDailymotionVideoProgress");
			that.safeClientCall("onApiReady", that);
		}

		that.isReady = true;
		that.safeClientCall("onApiLoaded", that);
	}

	DailymotionPlayer.prototype.safeCall = function(fctName, p1, p2) {
		//return (this.element || {})[fctName] && this.element[fctName](p1, p2);
		var args = Array.apply(null, arguments).slice(1); // exclude first arg (fctName)
		var fct = (this.element || {})[fctName];
		fct && fct.apply(this.element, args);
	}

	DailymotionPlayer.prototype.safeClientCall = function(fctName, p1, p2) {
		try {
			return this.eventHandlers[fctName] && this.eventHandlers[fctName](p1, p2);
		}
		catch(e) {
			console.log("DM safeclientcall error", e, e.stack);
		}
	}

	DailymotionPlayer.prototype.embed = function (vars) {
		this.embedVars = vars = vars || {};
		this.embedVars.playerId = this.embedVars.playerId || 'dmplayer';
		this.trackInfo = {};
		this.element = document.createElement("object");
		this.element.id = this.embedVars.playerId;

		this.holder = document.createElement("div");
		this.holder.id = "genericholder";
		this.holder.appendChild(this.element);
		this.embedVars.playerContainer.appendChild(this.holder);

		var params = {
			allowScriptAccess: "always"
		};

		var atts = {
			id: this.embedVars.playerId
		};

		var swfParams = {
			//api: "postMessage",
			info: 0,
			logo: 0,
			related: 0,
			autoplay: 1,
			enableApi: 1,
			showinfo: 0,
			hideInfos: 1,
			chromeless: 1,
			withLoading: 0,
			playerapiid: this.embedVars.playerId
		};

		var paramsQS = Object.keys(swfParams).map(function(k){ // query string
			return k + "=" + encodeURIComponent(swfParams[k]);
		}).join("&");

		var paramsHTML = Object.keys(params).map(function(k){
			return '<param name="' + k +'" value="' + encodeURIComponent(params[k]) + '">';
		}).join();

		var embedAttrs = {
			id: this.embedVars.playerId,
			width: this.embedVars.height || '200',
			height: this.embedVars.width || '200',
			type: "application/x-shockwave-flash",
			data: window.location.protocol+'//www.dailymotion.com/swf/'+this.embedVars.videoId+'?'+paramsQS,
			innerHTML: paramsHTML
		};
		if (USE_SWFOBJECT) {
			swfobject.embedSWF(embedAttrs.data, this.embedVars.playerId, embedAttrs.width, embedAttrs.height, "9.0.0", "/js/swfobject_expressInstall.swf", null, params, atts);
		}
		else {
			$(this.element).attr(embedAttrs);
		}
		$(this.element).show();
		this.safeClientCall("onEmbedReady");
	}

	DailymotionPlayer.prototype.getEid = function(url, cb) {
		cb((url.match(regex) || []).pop(), this);
	}

	DailymotionPlayer.prototype.play = function(id) {
		if (!this.currentId || this.currentId != id) {
			this.embedVars.videoId = id;
			this.embed(this.embedVars);
		}
	}

	DailymotionPlayer.prototype.pause = function(vol) {
		//this.element.pauseVideo();
		this.safeCall("pauseVideo");
	};

	DailymotionPlayer.prototype.resume = function(vol) {
		//this.element.playVideo();
		this.safeCall("playVideo");
	};

	/*DailymotionPlayer.prototype.stop = function(vol) {
		//this.element.stopVideo();
		this.safeCall("clearVideo");
	};*/

	DailymotionPlayer.prototype.getTrackPosition = function(callback) {
		var time = this.element.getCurrentTime();
		/**/this.trackInfo.duration = this.element.getDuration();
		//console.log("DM time & duration", time, this.trackInfo.duration);
		callback && callback(this.element.getCurrentTime());
	};

	DailymotionPlayer.prototype.setTrackPosition = function(pos) {
		//this.element.seekTo(pos);
		this.safeCall("seekTo", pos);
	};

	DailymotionPlayer.prototype.setVolume = function(vol) {
		//(this.element||{}).setVolume && this.element.setVolume(vol * 100);
		this.safeCall("setVolume", vol * 100);
	};

	return DailymotionPlayer;
})();<|endoftext|>


<commit_before>function collatz(num) {
  while (num > 1) {
    if (num % 2 === 0) {
      console.log(num + ' is even, so we divide it by 2.');

      num /= 2;
    } else {
      console.log(num + ' is odd. Multiplyling by 3 and adding 1');

      num = num * 3 + 1;
    }
  }

  console.log(num);
}

collatz(15);<commit_msg>it turns out collatz music is... weird<commit_after>function collatz(num) {
  var sequence = [num];
  var counter = 0;

  var AudioContext = window.AudioContext || window.webkitAudioContext;
  var audioCtx = new AudioContext();
  var osc = audioCtx.createOscillator();

  while (num > 1) {
    if (num % 2 === 0) {
      console.log(num + ' is even, so we divide it by 2.');

      num /= 2;
      sequence.push(num);
    } else {
      console.log(num + ' is odd. Multiplyling by 3 and adding 1');

      num = num * 3 + 1;
      sequence.push(num);
    }
  }

  console.log('1');

  osc.type = 'square';
  osc.connect(audioCtx.destination);
  osc.start();
  osc.stop(sequence.length);

  var intervalID = setInterval(function() {
    if (counter === sequence.length) {
      clearInterval(intervalID);
      osc.disconnect(audioCtx.destination);
    } else {
      // multiplying frequency by ten so we can hear better
      osc.frequency.value = sequence[counter] * 10;
      counter++;
    }
  }, 300);
}

collatz(15);<|endoftext|>


<commit_before>import Popper from '../popper';
import utils from '../../utils/utils';

const DEFAULT_OPTIONS = {
  container: false,
  delay: 0,
  html: false,
  placement: 'top',
  triggerOnce: false,
  content: '',
  disabled: false,
  trigger: 'hover focus',
  offset: 0,
  equalWidth: false,
  type: 'dropdown',
  preventOverflow: false
};

/**
 * Create a new Pop.js instance
 * @class Pop
 * @param {HTMLElement} reference - The reference element used to position the pop
 * @param {Object} options
 * @param {String} options.placement=bottom
 *      Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -end),
 *      left(-start, -end)`
 *
 * @param {HTMLElement} reference - The DOM node used as reference of the pop (it can be a jQuery element).
 * @param {Object} options - Configuration of the pop
 * @param {HTMLElement|String|false} options.container=false - Append the pop to a specific element.
 * @param {Number|Object} options.delay=0
 * @param {Boolean} options.html=false - Insert HTML into the pop. If false, the content will inserted with `innerText`.
 * @param {String|PlacementFunction} options.placement='top' - One of the allowed placements, or a function returning one of them.
 * @param {String} options.template='

'
 *      Base HTML to used when creating the pop.
 *      The pop's `content` will be injected into the `.pop-inner` or `.pop__inner`.
 *      `.pop-arrow` or `.pop__arrow` will become the pop's arrow.
 *      The outermost wrapper element should have the `.pop` class.
 * @param {String|HTMLElement|ContentFunction} options.content='' - Default content value if `content` attribute isn't present.
 * @param {String} options.trigger='click hover focus manual contextMenu'
 *      How pop is triggered - click | hover | focus | manual | contextMenu.
 *      You may pass multiple triggers; separate them with a space. `manual` cannot be combined with any other trigger.
 * @param {HTMLElement} options.boundariesElement
 *      The element used as boundaries for the pop. For more information refer to Popper.js'
 *      [boundariesElement docs](https://popper.js.org/popper-documentation.html)
 * @param {Number|String} options.offset
 * @return {Object} instance - The generated pop instance
 */
class Pop {
  constructor(reference, options) {
    options = utils.extend({}, DEFAULT_OPTIONS, options);
    this.reference = reference;

    this.options = options;

    const triggerEvents = typeof options.trigger === 'string' ? options.trigger.split(' ').filter((trigger) => {
      return ['click', 'hover', 'focus', 'contextMenu'].indexOf(trigger) !== -1;
    }) : [];

    this.isOpen = false;

    this.arrowSelector = options.arrowSelector;
    this.innerSelector = options.innerSelector;
    this.triggerEvents = [];
    if (options.content.nodeType === 1) {
      options.content.style.display = 'none';
    }
    this.setEventListeners(triggerEvents, options);
  }

  create(reference, template, content) {
    const popGenerator = window.document.createElement('div');
    popGenerator.innerHTML = template;
    const popNode = popGenerator.childNodes[0];
    const allowHtml = this.options.html;

    popNode.id = `pop_${Math.random().toString(36).substr(2, 10)}`;
    const contentNode = popGenerator.querySelector(this.innerSelector);
    if (content.nodeType === 1) {
      if (allowHtml) contentNode.appendChild(content);
      content.style.display = 'block';
    } else if (utils.isFunction(content)) {
      const contentText = content.call(reference);
      if (allowHtml) {
        contentNode.innerHTML = contentText;
      } else {
        contentNode.innerText = contentText;
      }
    } else if (allowHtml) {
      contentNode.innerHTML = content;
    } else {
      contentNode.innerText = content;
    }

    return popNode;
  }

  updateContent(content) {
    this.options.content = content;
    if (!this.popNode || content == undefined) {
      return;
    }
    const contentNode = this.popNode.querySelector(this.innerSelector);
    const allowHtml = this.options.html;
    if (content.nodeType === 1) {
      if (allowHtml) contentNode.replaceChild(content, contentNode.firstChild);
      content.style.display = 'block';
    } else if (allowHtml) {
      contentNode.innerHTML = content;
    } else {
      contentNode.innerText = content;
    }
    this.update();
  }

  initPopNode() {
    let reference = this.reference;
    let options = this.options;
    const content = options.content || reference.getAttribute('content');

    if (!content) {
      return this;
    }

    const popNode = this.create(reference, options.template, content, options.html);

    popNode.setAttribute('aria-describedby', popNode.id);
    const container = this.findContainer(options.container, reference);

    container.appendChild(popNode);
    if (options.class) {
      utils.addClass(popNode, options.class);
    }
    if (options.className) {
      utils.addClass(popNode, options.className);
    }

    this.popNode = popNode;
    this.popNode.setAttribute('aria-hidden', 'true');

    if (this.options.trigger.indexOf('hover') > -1) {
      this.setPopNodeEvent();
    }
  }

  initPopper() {
    let reference = this.reference;
    let options = this.options;
    let popNode = this.popNode;
    const container = this.findContainer(options.container, reference);

    let modifiers = {
      computeStyle: {
        gpuAcceleration: false
      },
      arrow: {
        enabled: false
      },
      inner: {
        enabled: false
      },
      preventOverflow: {
        boundariesElement: 'window',
        enabled: true
      }
    };

    if (this.options.offset) {
      modifiers.offset = {
        enabled: true,
        offset: this.options.offset
      };
    }

    if (this.options.preventOverflow && container.tagName != 'BODY') {
      modifiers.hide = {
        enabled: false
      };
      modifiers.flip = {
        boundariesElement: container,
        enabled: true
      };

      modifiers.preventOverflow = {
        enabled: false
      };
    }
    if (this.options.trigger == 'contextMenu') {
      modifiers.flip = {
        enabled: false
      };
    }

    let popperOptions = {
      placement: options.placement,
      modifiers
    };
    this.popperOptions = popperOptions;
    this.popperInstance = new Popper(reference, popNode, popperOptions);
  }

  disabled() {
    this.options.disabled = true;
  }

  enabled() {
    this.options.disabled = false;
  }

  show(event) {
    if (this.hideTimeout) clearTimeout(this.hideTimeout);
    if (this.hideTimeout2) clearTimeout(this.hideTimeout2);
    if (this.isOpen || this.options.disabled) {
      return this;
    }
    this.isOpen = true;
    if (this.options.events && utils.isFunction(this.options.events.show)) {
      this.options.events.show(event);
    }
    if (!this.popNode) {
      this.initPopNode();
    }
    if (!this.popperInstance) {
      this.initPopper();
    }
    this.popperInstance.enableEventListeners();
    if (!this.popNode) {
      return;
    }
    if (this.options.equalWidth) {
      this.popNode.style.width = `${this.reference.clientWidth}px`;
    }

    this.popNode.style.display = '';
    utils.addClass(this.reference, 'h-pop-trigger');
    this.showTimeout = setTimeout(() => {
      this.popNode.setAttribute('aria-hidden', 'false');
      this.popperInstance.update();
    }, 0);
    return this;
  }

  update() {
    if (this.popperInstance) {
      this.popperInstance.update();
    }
  }

  hide() {
    if (this.showTimeout) clearTimeout(this.showTimeout);
    if (this.hideTimeout) clearTimeout(this.hideTimeout);
    if (this.isOpen === false) { return; }
    if (!document.body.contains(this.popNode)) {
      return;
    }
    if (!this.popNode) return this;
    if (!this.popperInstance) return this;
    this.hideTimeout = setTimeout(() => {
      utils.removeClass(this.reference, 'h-pop-trigger');
      if (this.options.events && utils.isFunction(this.options.events.hide)) {
        this.options.events.hide.call(null);
      }
      if (this.popNode) {
        this.popNode.setAttribute('aria-hidden', 'true');
      }
      this.isOpen = false;
      this.hideTimeout2 = setTimeout(() => {
        if (this.popNode) {
          this.popNode.style.display = 'none';
          if (this.popperInstance) {
            this.popperInstance.disableEventListeners();
          }
        }
      }, 300);
    }, this.options.delay);
    return this;
  }

  destory() {
    if (this.documentHandler) {
      document.removeEventListener('click', this.documentHandler);
      document.removeEventListener('contextmenu', this.documentHandler);
    }
    if (this.popperInstance) {
      this.popperInstance.destroy();
    }

    this.triggerEvents.forEach(({
      event,
      func
    }) => {
      this.reference.removeEventListener(event, func, event == 'focus' || event == 'blur');
    });
    this.triggerEvents = [];

    if (this.popNode) {
      this.hide();
      this.popNode.parentNode.removeChild(this.popNode);
      this.popNode = null;
    }
    return this;
  }

  findContainer(container, reference) {
    if (typeof container === 'string') {
      container = window.document.querySelector(container);
    } else if (container === false) {
      container = reference.parentNode;
    }
    return container;
  }

  setEventListeners(triggerEvents, options) {
    let reference = this.reference;
    const directtriggerEvents = [];
    const oppositetriggerEvents = [];

    triggerEvents.forEach((event) => {
      switch (event) {
        case 'hover':
          directtriggerEvents.push('mouseenter');
          oppositetriggerEvents.push('mouseleave');
          break;
        case 'focus':
          directtriggerEvents.push('focus');
          oppositetriggerEvents.push('blur');
          break;
        case 'click':
          directtriggerEvents.push('click');
          if (!this.options.triggerOnce) oppositetriggerEvents.push('click');
          break;
        case 'contextMenu':
          directtriggerEvents.push('contextmenu');
          oppositetriggerEvents.push('click');
          break;
        default:
          break;
      }
    });

    directtriggerEvents.forEach((event) => {
      const func = (evt) => {
        if (evt.type == 'contextmenu') {
          evt.preventDefault();
          // evt.stopPropagation();
          if (window.getSelection) {
            window.getSelection().removeAllRanges();
          } else {
            document.selection.empty();
          }
          let rect = reference.getBoundingClientRect();
          this.options.offset = `${evt.clientX - rect.x}, -${rect.bottom - evt.clientY - 10}`;
          if (this.popperInstance) {
            this.popperInstance.defaultOptions.modifiers.offset = {
              enabled: true,
              offset: this.options.offset
            };
            this.popperInstance.updateModifiers();
            this.popperInstance.update();
          }
        }
        if (this.isOpen === true) {
          return;
        }
        evt.usedByPop = true;
        this.show(evt);
      };
      this.triggerEvents.push({
        event,
        func
      });
      reference.addEventListener(event, func, event == 'focus');
    });

    oppositetriggerEvents.forEach((event) => {
      const func = (evt) => {
        if (evt.usedByPop === true) {
          return;
        }
        this.hide();
      };
      this.triggerEvents.push({
        event,
        func
      });
      reference.addEventListener(event, func, event == 'blur');
    });

    if (options.closeOnClickBody) {
      this.documentHandler = (e) => {
        if (!this.popNode || e.target.parentNode == null) return;
        if (!this.isOpen || reference.contains(e.target) || this.popNode.contains(e.target)) {
          return false;
        }
        let targetReference = e.reference;
        if (reference && this.popNode.contains(targetReference)) {
          return false;
        }
        this.hide();
      };
      document.addEventListener('click', this.documentHandler);
      document.addEventListener('contextmenu', this.documentHandler);
    }
  }

  setPopNodeEvent() {
    this.popNode.addEventListener('mouseenter', (event) => {
      this.show(event);
    });
    this.popNode.addEventListener('mouseout', (event) => {
      const relatedreference = event.relatedreference || event.toElement || event.relatedTarget;
      if (!this.popNode.contains(relatedreference) && relatedreference != this.reference && !this.reference.contains(relatedreference)) {
        this.hide();
      }
    });
  }
}

export default Pop;
<commit_msg>fix(Pop): change show event position<commit_after>import Popper from '../popper';
import utils from '../../utils/utils';

const DEFAULT_OPTIONS = {
  container: false,
  delay: 0,
  html: false,
  placement: 'top',
  triggerOnce: false,
  content: '',
  disabled: false,
  trigger: 'hover focus',
  offset: 0,
  equalWidth: false,
  type: 'dropdown',
  preventOverflow: false
};

/**
 * Create a new Pop.js instance
 * @class Pop
 * @param {HTMLElement} reference - The reference element used to position the pop
 * @param {Object} options
 * @param {String} options.placement=bottom
 *      Placement of the popper accepted values: `top(-start, -end), right(-start, -end), bottom(-start, -end),
 *      left(-start, -end)`
 *
 * @param {HTMLElement} reference - The DOM node used as reference of the pop (it can be a jQuery element).
 * @param {Object} options - Configuration of the pop
 * @param {HTMLElement|String|false} options.container=false - Append the pop to a specific element.
 * @param {Number|Object} options.delay=0
 * @param {Boolean} options.html=false - Insert HTML into the pop. If false, the content will inserted with `innerText`.
 * @param {String|PlacementFunction} options.placement='top' - One of the allowed placements, or a function returning one of them.
 * @param {String} options.template='

'
 *      Base HTML to used when creating the pop.
 *      The pop's `content` will be injected into the `.pop-inner` or `.pop__inner`.
 *      `.pop-arrow` or `.pop__arrow` will become the pop's arrow.
 *      The outermost wrapper element should have the `.pop` class.
 * @param {String|HTMLElement|ContentFunction} options.content='' - Default content value if `content` attribute isn't present.
 * @param {String} options.trigger='click hover focus manual contextMenu'
 *      How pop is triggered - click | hover | focus | manual | contextMenu.
 *      You may pass multiple triggers; separate them with a space. `manual` cannot be combined with any other trigger.
 * @param {HTMLElement} options.boundariesElement
 *      The element used as boundaries for the pop. For more information refer to Popper.js'
 *      [boundariesElement docs](https://popper.js.org/popper-documentation.html)
 * @param {Number|String} options.offset
 * @return {Object} instance - The generated pop instance
 */
class Pop {
  constructor(reference, options) {
    options = utils.extend({}, DEFAULT_OPTIONS, options);
    this.reference = reference;

    this.options = options;

    const triggerEvents = typeof options.trigger === 'string' ? options.trigger.split(' ').filter((trigger) => {
      return ['click', 'hover', 'focus', 'contextMenu'].indexOf(trigger) !== -1;
    }) : [];

    this.isOpen = false;

    this.arrowSelector = options.arrowSelector;
    this.innerSelector = options.innerSelector;
    this.triggerEvents = [];
    if (options.content.nodeType === 1) {
      options.content.style.display = 'none';
    }
    this.setEventListeners(triggerEvents, options);
  }

  create(reference, template, content) {
    const popGenerator = window.document.createElement('div');
    popGenerator.innerHTML = template;
    const popNode = popGenerator.childNodes[0];
    const allowHtml = this.options.html;

    popNode.id = `pop_${Math.random().toString(36).substr(2, 10)}`;
    const contentNode = popGenerator.querySelector(this.innerSelector);
    if (content.nodeType === 1) {
      if (allowHtml) contentNode.appendChild(content);
      content.style.display = 'block';
    } else if (utils.isFunction(content)) {
      const contentText = content.call(reference);
      if (allowHtml) {
        contentNode.innerHTML = contentText;
      } else {
        contentNode.innerText = contentText;
      }
    } else if (allowHtml) {
      contentNode.innerHTML = content;
    } else {
      contentNode.innerText = content;
    }

    return popNode;
  }

  updateContent(content) {
    this.options.content = content;
    if (!this.popNode || content == undefined) {
      return;
    }
    const contentNode = this.popNode.querySelector(this.innerSelector);
    const allowHtml = this.options.html;
    if (content.nodeType === 1) {
      if (allowHtml) contentNode.replaceChild(content, contentNode.firstChild);
      content.style.display = 'block';
    } else if (allowHtml) {
      contentNode.innerHTML = content;
    } else {
      contentNode.innerText = content;
    }
    this.update();
  }

  initPopNode() {
    let reference = this.reference;
    let options = this.options;
    const content = options.content || reference.getAttribute('content');

    if (!content) {
      return this;
    }

    const popNode = this.create(reference, options.template, content, options.html);

    popNode.setAttribute('aria-describedby', popNode.id);
    const container = this.findContainer(options.container, reference);

    container.appendChild(popNode);
    if (options.class) {
      utils.addClass(popNode, options.class);
    }
    if (options.className) {
      utils.addClass(popNode, options.className);
    }

    this.popNode = popNode;
    this.popNode.setAttribute('aria-hidden', 'true');

    if (this.options.trigger.indexOf('hover') > -1) {
      this.setPopNodeEvent();
    }
  }

  initPopper() {
    let reference = this.reference;
    let options = this.options;
    let popNode = this.popNode;
    const container = this.findContainer(options.container, reference);

    let modifiers = {
      computeStyle: {
        gpuAcceleration: false
      },
      arrow: {
        enabled: false
      },
      inner: {
        enabled: false
      },
      preventOverflow: {
        boundariesElement: 'window',
        enabled: true
      }
    };

    if (this.options.offset) {
      modifiers.offset = {
        enabled: true,
        offset: this.options.offset
      };
    }

    if (this.options.preventOverflow && container.tagName != 'BODY') {
      modifiers.hide = {
        enabled: false
      };
      modifiers.flip = {
        boundariesElement: container,
        enabled: true
      };

      modifiers.preventOverflow = {
        enabled: false
      };
    }
    if (this.options.trigger == 'contextMenu') {
      modifiers.flip = {
        enabled: false
      };
    }

    let popperOptions = {
      placement: options.placement,
      modifiers
    };
    this.popperOptions = popperOptions;
    this.popperInstance = new Popper(reference, popNode, popperOptions);
  }

  disabled() {
    this.options.disabled = true;
  }

  enabled() {
    this.options.disabled = false;
  }

  show(event) {
    if (this.hideTimeout) clearTimeout(this.hideTimeout);
    if (this.hideTimeout2) clearTimeout(this.hideTimeout2);
    if (this.options.events && utils.isFunction(this.options.events.show)) {
      this.options.events.show(event);
    }
    if (this.isOpen || this.options.disabled) {
      return this;
    }
    this.isOpen = true;
    if (!this.popNode) {
      this.initPopNode();
    }
    if (!this.popperInstance) {
      this.initPopper();
    }
    this.popperInstance.enableEventListeners();
    if (!this.popNode) {
      return;
    }
    if (this.options.equalWidth) {
      this.popNode.style.width = `${this.reference.clientWidth}px`;
    }

    this.popNode.style.display = '';
    utils.addClass(this.reference, 'h-pop-trigger');
    this.showTimeout = setTimeout(() => {
      this.popNode.setAttribute('aria-hidden', 'false');
      this.popperInstance.update();
    }, 0);
    return this;
  }

  update() {
    if (this.popperInstance) {
      this.popperInstance.update();
    }
  }

  hide() {
    if (this.showTimeout) clearTimeout(this.showTimeout);
    if (this.hideTimeout) clearTimeout(this.hideTimeout);
    if (this.isOpen === false) { return; }
    if (!document.body.contains(this.popNode)) {
      return;
    }
    if (!this.popNode) return this;
    if (!this.popperInstance) return this;
    this.hideTimeout = setTimeout(() => {
      utils.removeClass(this.reference, 'h-pop-trigger');
      if (this.options.events && utils.isFunction(this.options.events.hide)) {
        this.options.events.hide.call(null);
      }
      if (this.popNode) {
        this.popNode.setAttribute('aria-hidden', 'true');
      }
      this.isOpen = false;
      this.hideTimeout2 = setTimeout(() => {
        if (this.popNode) {
          this.popNode.style.display = 'none';
          if (this.popperInstance) {
            this.popperInstance.disableEventListeners();
          }
        }
      }, 300);
    }, this.options.delay);
    return this;
  }

  destory() {
    if (this.documentHandler) {
      document.removeEventListener('click', this.documentHandler);
      document.removeEventListener('contextmenu', this.documentHandler);
    }
    if (this.popperInstance) {
      this.popperInstance.destroy();
    }

    this.triggerEvents.forEach(({
      event,
      func
    }) => {
      this.reference.removeEventListener(event, func, event == 'focus' || event == 'blur');
    });
    this.triggerEvents = [];

    if (this.popNode) {
      this.hide();
      this.popNode.parentNode.removeChild(this.popNode);
      this.popNode = null;
    }
    return this;
  }

  findContainer(container, reference) {
    if (typeof container === 'string') {
      container = window.document.querySelector(container);
    } else if (container === false) {
      container = reference.parentNode;
    }
    return container;
  }

  setEventListeners(triggerEvents, options) {
    let reference = this.reference;
    const directtriggerEvents = [];
    const oppositetriggerEvents = [];

    triggerEvents.forEach((event) => {
      switch (event) {
        case 'hover':
          directtriggerEvents.push('mouseenter');
          oppositetriggerEvents.push('mouseleave');
          break;
        case 'focus':
          directtriggerEvents.push('focus');
          oppositetriggerEvents.push('blur');
          break;
        case 'click':
          directtriggerEvents.push('click');
          if (!this.options.triggerOnce) oppositetriggerEvents.push('click');
          break;
        case 'contextMenu':
          directtriggerEvents.push('contextmenu');
          oppositetriggerEvents.push('click');
          break;
        default:
          break;
      }
    });

    directtriggerEvents.forEach((event) => {
      const func = (evt) => {
        if (evt.type == 'contextmenu') {
          evt.preventDefault();
          // evt.stopPropagation();
          if (window.getSelection) {
            window.getSelection().removeAllRanges();
          } else {
            document.selection.empty();
          }
          let rect = reference.getBoundingClientRect();
          this.options.offset = `${evt.clientX - rect.x}, -${rect.bottom - evt.clientY - 10}`;
          if (this.popperInstance) {
            this.popperInstance.defaultOptions.modifiers.offset = {
              enabled: true,
              offset: this.options.offset
            };
            this.popperInstance.updateModifiers();
            this.popperInstance.update();
          }
        }
        if (this.isOpen === true) {
          return;
        }
        evt.usedByPop = true;
        this.show(evt);
      };
      this.triggerEvents.push({
        event,
        func
      });
      reference.addEventListener(event, func, event == 'focus');
    });

    oppositetriggerEvents.forEach((event) => {
      const func = (evt) => {
        if (evt.usedByPop === true) {
          return;
        }
        this.hide();
      };
      this.triggerEvents.push({
        event,
        func
      });
      reference.addEventListener(event, func, event == 'blur');
    });

    if (options.closeOnClickBody) {
      this.documentHandler = (e) => {
        if (!this.popNode || e.target.parentNode == null) return;
        if (!this.isOpen || reference.contains(e.target) || this.popNode.contains(e.target)) {
          return false;
        }
        let targetReference = e.reference;
        if (reference && this.popNode.contains(targetReference)) {
          return false;
        }
        this.hide();
      };
      document.addEventListener('click', this.documentHandler);
      document.addEventListener('contextmenu', this.documentHandler);
    }
  }

  setPopNodeEvent() {
    this.popNode.addEventListener('mouseenter', (event) => {
      this.show(event);
    });
    this.popNode.addEventListener('mouseout', (event) => {
      const relatedreference = event.relatedreference || event.toElement || event.relatedTarget;
      if (!this.popNode.contains(relatedreference) && relatedreference != this.reference && !this.reference.contains(relatedreference)) {
        this.hide();
      }
    });
  }
}

export default Pop;
<|endoftext|>


<commit_before>'use strict'

const _ = require('lodash')
const _h = require('../_helpers')

exports.handler = async (event, context) => {
  if (event.source === 'aws.events') { return _h.json({ message: 'OK' }) }

  if (!event.Records && event.Records.length === 0) { return }

  for (var i = 0; i < event.Records.length; i++) {
    const data = JSON.parse(event.Records[i].body)
    const entityId = _h.strToId(data.entity)
    const db = await _h.db(data.account)

    const entity = await db.collection('entity').findOne({ _id: entityId }, { projection: { _id: false, aggregated: true, 'private.name': true } })

    if (entity && entity.aggregated && data.dt && entity.aggregated >= new Date(data.dt)) {
      console.log('Entity', entityId, '@', data.account, 'already aggregated at', entity.aggregated)
      continue
    }

    const properties = await db.collection('property').find({ entity: entityId, deleted: { $exists: false } }).toArray()

    if (properties.find(x => x.type === '_deleted')) {
      const deleteResponse = await db.collection('entity').deleteOne({ _id: entityId })
      console.log('Entity', entityId, '@', data.account, 'deleted')
      continue
    }

    let newEntity = {
      aggregated: new Date()
    }

    for (var n = 0; n < properties.length; n++) {
      const prop = properties[n]
      let cleanProp = _.omit(prop, ['entity', 'type', 'created', 'search', 'public'])

      if (prop.reference && ['_viewer', '_expander', '_editor', '_owner'].includes(prop.type)) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push(prop.reference)
      }

      if (prop.type === '_public' && prop.boolean === true) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push('public')
      }

      if (!_.has(newEntity, ['private', prop.type])) {
        _.set(newEntity, ['private', prop.type], [])
      }

      if (prop.date) {
        const d = new Date(prop.date)
        cleanProp = { ...cleanProp, string: d.toISOString().substring(0, 9) }
      }

      if (prop.reference) {
        const referenceEntities = await db.collection('entity').findOne({ _id: prop.reference }, { projection: { 'private.name': true }})

        if (_.has(referenceEntities, 'private.name')) {
          cleanProp = referenceEntities.private.name.map(x => {
            return { ...cleanProp, ...x }
          })
        } else {
          cleanProp = { ...cleanProp, string: prop.reference.toString() }
        }
      }

      if (prop.formula) {
        const formulaResult = await formula(prop.formula, entityId, db)
        cleanProp = {...cleanProp, ...formulaResult}
      }

      if (!Array.isArray(cleanProp)) {
        cleanProp = [cleanProp]
      }
      newEntity.private[prop.type] = [...newEntity.private[prop.type], ...cleanProp]

      if (prop.public) {
        if (!_.has(newEntity, ['public', prop.type])) {
          _.set(newEntity, ['public', prop.type], [])
        }

        newEntity.public[prop.type] = [...newEntity.public[prop.type], ...cleanProp]
      }
    }

    const replaceResponse = await db.collection('entity').replaceOne({ _id: entityId }, newEntity)

    const name = _.get(entity, 'private.name', []).map(x => x.string || '')
    const newName = _.get(newEntity, 'private.name', []).map(x => x.string || '')

    if (!_.isEqual(_.sortBy(name), _.sortBy(newName))) {
      const referrers = await db.collection('property').aggregate([
        { $match: { reference: entityId, deleted: { $exists: false } } },
        { $group: { _id: '$entity' } }
      ]).toArray()

      const dt = data.dt ? new Date(data.dt) : newEntity.aggregated

      for (var i = 0; i < referrers.length; i++) {
        await _h.addEntityAggregateSqs(context, data.account, referrers[i]._id.toString(), dt)
      }

      console.log('Entity', entityId, '@', data.account, 'updated and added', referrers.length, 'entities to SQS')
    } else {
      console.log('Entity', entityId, '@', data.account, 'updated')
    }
  }
}

const formula = async (str, entityId, db) => {
  let func = formulaFunction(str)
  let data = formulaContent(str)

  if (!['CONCAT', 'COUNT', 'SUM', 'SUBTRACT', 'AVERAGE', 'MIN', 'MAX'].includes(func)) {
    return { string: str }
  }

  const dataArray = data.split(',')
  let valueArray = []

  for (let i = 0; i < dataArray.length; i++) {
    const value = await formulaField(dataArray[i], entityId, db)

    if (value) {
      valueArray = [...valueArray, ...value]
    }
  }

  valueArray = formulaValue(valueArray)

  switch (func) {
    case 'CONCAT':
      return { string: valueArray.join('') }
      break
    case 'COUNT':
      return { integer: valueArray.length }
      break
    case 'SUM':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) }
      break
    case 'SUBTRACT':
      return { decimal: valueArray.reduce((a, b) => a - b, 0) + (valueArray[0] * 2) }
      break
    case 'AVERAGE':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) / valueArray.length }
      break
    case 'MIN':
      return { decimal: Math.min(...valueArray) }
      break
    case 'MAX':
      return { decimal: Math.max(...valueArray) }
      break
  }
}

const formulaFunction = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return null
  } else {
    return str.substring(0, str.indexOf('(')).toUpperCase()
  }
}

const formulaContent = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return str
  } else {
    return str.substring(str.indexOf('(') + 1, str.lastIndexOf(')'))
  }
}

const formulaField = async (str, entityId, db) => {
  str = str.trim()

  if ((str.startsWith("'") || str.startsWith('"')) && (str.endsWith("'") || str.endsWith('"'))) {
    return [{
      string: str.substring(1, str.length - 1)
    }]
  }

  if (parseFloat(str).toString() === str) {
    return [{
      decimal: parseFloat(str)
    }]
  }

  const strParts = str.split('.')

  const [ref, type, property] = str.split('.')

  let result

  // same entity _id
  if (strParts.length === 1 && str === '_id') {
    result = [{ _id: entityId }]

  // same entity property
  } else if (strParts.length === 1 && str !== '_id') {
    result = await db.collection('property').find({
      entity: entityId,
      type: str,
      string: { $exists: true },
      deleted: { $exists: false }
    }, {
      sort: { _id: 1 },
      projection: { _id: false, entity: false, type: false }
    }).toArray()

  // childs _id
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] === '_id') {
    const config = {
      'private._parent.reference': entityId,
    }

    if (strParts[1] !== '*') {
      _.set(config, ['private._type.string'], strParts[1])
    }

    result = await db.collection('entity').find(config, {
      projection: { _id: true }
    }).toArray()

  // childs property
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] !== '_id') {
    const config = [
      {
        $match : { 'private._parent.reference': entityId }
      }, {
        $project: { _id: true }
      }, {
        $lookup: {
          from: 'property',
          let: { entityId: '$_id' },
          pipeline: [
            {
              $match: {
                type: strParts[2],
                deleted: { $exists: false },
                $expr: { $eq: [ '$entity',  '$$entityId' ] }
              }
            }, {
              $project: { _id: false, entity: false, type: false, created: false }
            }
          ],
          as: 'properties'
        }
      }, {
        $project: { _id: false }
      }
    ]

    if (strParts[1] !== '*') {
      _.set(config, [0, '$match', 'private._type.string'], strParts[1])
    }

    const p = await db.collection('entity').aggregate(config).toArray()

    result = p.map(x => x.properties).flat()

  }

  return result
}

const formulaValue = (values) => {
  if (!values) { return [] }

  return values.map(x => x.decimal || x.integer || x.datetime || x.date || x.string || x._id)
}
<commit_msg>Use parent._id in formulas<commit_after>'use strict'

const _ = require('lodash')
const _h = require('../_helpers')

exports.handler = async (event, context) => {
  if (event.source === 'aws.events') { return _h.json({ message: 'OK' }) }

  if (!event.Records && event.Records.length === 0) { return }

  for (var i = 0; i < event.Records.length; i++) {
    const data = JSON.parse(event.Records[i].body)
    const entityId = _h.strToId(data.entity)
    const db = await _h.db(data.account)

    const entity = await db.collection('entity').findOne({ _id: entityId }, { projection: { _id: false, aggregated: true, 'private.name': true } })

    if (entity && entity.aggregated && data.dt && entity.aggregated >= new Date(data.dt)) {
      console.log('Entity', entityId, '@', data.account, 'already aggregated at', entity.aggregated)
      continue
    }

    const properties = await db.collection('property').find({ entity: entityId, deleted: { $exists: false } }).toArray()

    if (properties.find(x => x.type === '_deleted')) {
      const deleteResponse = await db.collection('entity').deleteOne({ _id: entityId })
      console.log('Entity', entityId, '@', data.account, 'deleted')
      continue
    }

    let newEntity = {
      aggregated: new Date()
    }

    for (var n = 0; n < properties.length; n++) {
      const prop = properties[n]
      let cleanProp = _.omit(prop, ['entity', 'type', 'created', 'search', 'public'])

      if (prop.reference && ['_viewer', '_expander', '_editor', '_owner'].includes(prop.type)) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push(prop.reference)
      }

      if (prop.type === '_public' && prop.boolean === true) {
        if (!_.has(newEntity, 'access')) {
          _.set(newEntity, 'access', [])
        }
        newEntity.access.push('public')
      }

      if (!_.has(newEntity, ['private', prop.type])) {
        _.set(newEntity, ['private', prop.type], [])
      }

      if (prop.date) {
        const d = new Date(prop.date)
        cleanProp = { ...cleanProp, string: d.toISOString().substring(0, 9) }
      }

      if (prop.reference) {
        const referenceEntities = await db.collection('entity').findOne({ _id: prop.reference }, { projection: { 'private.name': true }})

        if (_.has(referenceEntities, 'private.name')) {
          cleanProp = referenceEntities.private.name.map(x => {
            return { ...cleanProp, ...x }
          })
        } else {
          cleanProp = { ...cleanProp, string: prop.reference.toString() }
        }
      }

      if (prop.formula) {
        const formulaResult = await formula(prop.formula, entityId, db)
        cleanProp = {...cleanProp, ...formulaResult}
      }

      if (!Array.isArray(cleanProp)) {
        cleanProp = [cleanProp]
      }
      newEntity.private[prop.type] = [...newEntity.private[prop.type], ...cleanProp]

      if (prop.public) {
        if (!_.has(newEntity, ['public', prop.type])) {
          _.set(newEntity, ['public', prop.type], [])
        }

        newEntity.public[prop.type] = [...newEntity.public[prop.type], ...cleanProp]
      }
    }

    const replaceResponse = await db.collection('entity').replaceOne({ _id: entityId }, newEntity)

    const name = _.get(entity, 'private.name', []).map(x => x.string || '')
    const newName = _.get(newEntity, 'private.name', []).map(x => x.string || '')

    if (!_.isEqual(_.sortBy(name), _.sortBy(newName))) {
      const referrers = await db.collection('property').aggregate([
        { $match: { reference: entityId, deleted: { $exists: false } } },
        { $group: { _id: '$entity' } }
      ]).toArray()

      const dt = data.dt ? new Date(data.dt) : newEntity.aggregated

      for (var i = 0; i < referrers.length; i++) {
        await _h.addEntityAggregateSqs(context, data.account, referrers[i]._id.toString(), dt)
      }

      console.log('Entity', entityId, '@', data.account, 'updated and added', referrers.length, 'entities to SQS')
    } else {
      console.log('Entity', entityId, '@', data.account, 'updated')
    }
  }
}

const formula = async (str, entityId, db) => {
  let func = formulaFunction(str)
  let data = formulaContent(str)

  if (!['CONCAT', 'COUNT', 'SUM', 'SUBTRACT', 'AVERAGE', 'MIN', 'MAX'].includes(func)) {
    return { string: str }
  }

  const dataArray = data.split(',')
  let valueArray = []

  for (let i = 0; i < dataArray.length; i++) {
    const value = await formulaField(dataArray[i], entityId, db)

    if (value) {
      valueArray = [...valueArray, ...value]
    }
  }

  valueArray = formulaValue(valueArray)

  switch (func) {
    case 'CONCAT':
      return { string: valueArray.join('') }
      break
    case 'COUNT':
      return { integer: valueArray.length }
      break
    case 'SUM':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) }
      break
    case 'SUBTRACT':
      return { decimal: valueArray.reduce((a, b) => a - b, 0) + (valueArray[0] * 2) }
      break
    case 'AVERAGE':
      return { decimal: valueArray.reduce((a, b) => a + b, 0) / valueArray.length }
      break
    case 'MIN':
      return { decimal: Math.min(...valueArray) }
      break
    case 'MAX':
      return { decimal: Math.max(...valueArray) }
      break
  }
}

const formulaFunction = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return null
  } else {
    return str.substring(0, str.indexOf('(')).toUpperCase()
  }
}

const formulaContent = (str) => {
  str = str.trim()

  if (!str.includes('(') || !str.includes(')')) {
    return str
  } else {
    return str.substring(str.indexOf('(') + 1, str.lastIndexOf(')'))
  }
}

const formulaField = async (str, entityId, db) => {
  str = str.trim()

  if ((str.startsWith("'") || str.startsWith('"')) && (str.endsWith("'") || str.endsWith('"'))) {
    return [{
      string: str.substring(1, str.length - 1)
    }]
  }

  if (parseFloat(str).toString() === str) {
    return [{
      decimal: parseFloat(str)
    }]
  }

  const strParts = str.split('.')

  const [ref, type, property] = str.split('.')

  let result

  // same entity _id
  if (strParts.length === 1 && str === '_id') {
    result = [{ _id: entityId }]

  // same entity property
  } else if (strParts.length === 1 && str !== '_id') {
    result = await db.collection('property').find({
      entity: entityId,
      type: str,
      string: { $exists: true },
      deleted: { $exists: false }
    }, {
      sort: { _id: 1 },
      projection: { _id: false, entity: false, type: false }
    }).toArray()

  // childs _id
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] === '_id') {
    const config = {
      'private._parent.reference': entityId,
    }

    if (strParts[1] !== '*') {
      _.set(config, ['private._type.string'], strParts[1])
    }

    result = await db.collection('entity').find(config, {
      projection: { _id: true }
    }).toArray()

  // childs property
  } else if (strParts.length === 3 && strParts[0] === 'child' && strParts[2] !== '_id') {
    const config = [
      {
        $match : { 'private._parent.reference': entityId }
      }, {
        $project: { _id: true }
      }, {
        $lookup: {
          from: 'property',
          let: { entityId: '$_id' },
          pipeline: [
            {
              $match: {
                type: strParts[2],
                deleted: { $exists: false },
                $expr: { $eq: [ '$entity',  '$$entityId' ] }
              }
            }, {
              $project: { _id: false, entity: false, type: false, created: false }
            }
          ],
          as: 'properties'
        }
      }, {
        $project: { _id: false }
      }
    ]

    if (strParts[1] !== '*') {
      _.set(config, [0, '$match', 'private._type.string'], strParts[1])
    }

    const p = await db.collection('entity').aggregate(config).toArray()

    result = p.map(x => x.properties).flat()

  // parents _id
  } else if (strParts.length === 3 && strParts[0] === 'parent' && strParts[2] === '_id') {
    const config = [
      {
        $match : {
          entity: entityId,
          type: '_parent',
          reference: { $exists: true },
          deleted: { $exists: false }
        }
      }, {
        $project: { _id: false, _id: '$reference' }
      }
    ]

    if (strParts[1] !== '*') {
      config.push({
        $lookup: {
          from: 'entity',
          let: { entityId: '$_id' },
          pipeline: [
            {
              $match: {
                'private._type.string': strParts[1],
                $expr: { $eq: [ '$_id',  '$$entityId' ] }
              }
            }, {
              $project: { _id: true }
            }
          ],
          as: 'parents'
        }
      })
      config.push({
        $project: { _id: false }
      })
    }

    const p = await db.collection('property').aggregate(config).toArray()

    result = p.map(x => x.parents).flat()

  }

  return result
}

const formulaValue = (values) => {
  if (!values) { return [] }

  return values.map(x => x.decimal || x.integer || x.datetime || x.date || x.string || x._id)
}
<|endoftext|>


<commit_before>const LifeforcePlugin = require("../utils/LifeforcePlugin.js");
const fs = require('fs');

let errormode = false;
const threshold = 45;
const timertime = 2700000;
let settings = null;
let transporter = null;

let tempCheckinLists = {};
let tempCheckinTimers = {};

class RaspiTempMonitor extends LifeforcePlugin {
    constructor(restifyserver, logger, name) {
        super(restifyserver, logger, name);
        this.apiMap = [
            {
                path: "/api/tempmon/:temp",
                type: "get",
                handler: handleTempCheckin
            },
            {
                path: "/api/temp/checkin",
                type: "post",
                handler: handleTempCheckinNew
            }
        ];

        // Grab the subset of settings we actually want
        settings = this.config.tempmonitor;
        this.timerfunc = null;

        // create reusable transporter object using SMTP transport
        const nodemailer = require("nodemailer");
        transporter = nodemailer.createTransport({
            service: 'Gmail',
            auth: {
                user: settings.emailuser,
                pass: settings.emailpass
            }
        });
    }
}

function handleTempCheckin(req, res, next) {
    this.log.info("Got a temp checkin from raspberry pi");

    // Clear the current timer because we got a check in time
    clearTimeout(this.timerfunc);

    // If we get this call, but we're in errormode, the system got a message
    // after a previous failure.
    if (errormode) {
        errorResolved();
    }

    var response = {};
    response.date = Date.now();
    response.temp = req.params.temp;

    // Check if the temp is under our threshold and warn us!
    if (response.temp < threshold) {
        handleColdTemp(response.temp);
    }

    // Write the results to a text file
    var fileString = response.date + " | " + response.temp;
    logfileout(fileString, "templogfile.txt");

    // Send the response to the client
    res.send(200, response);

    // restart the timer to wait until the next checkin
    this.log.info("Starting timer to wait for client checkin...");
    this.timerfunc = setTimeout(function () {
        serverTempTimeout();
    }, timertime);

    return next();
}

function handleTempCheckinNew(req, res, next) {
    if (req.body) {
        let body = req.body;
        let clientid = null;
        let temp = null;

        if (req.body.clientid && req.body.temp) {

            clientid = req.body.clientid;
            temp = req.body.temp;

            this.log.info("Got a client checkin from " + clientid + " with temp " + temp);
            clearTimeout(tempCheckinTimers[clientid]);

            tempCheckinLists[clientid] = temp;
            tempCheckinTimers[clientid] = setTimeout(serverTempTimeoutNew, timertime / 100, clientid);

            res.send(200, { checkin: "OK!", clientid: clientid, temp: temp });
        } else {
            res.send(400, "Bad Request");

        }
    } else {
        res.send(400, "Bad Request");

    }

    return next();
}

// Helper function to write to a file
function logfileout(message, filename) {
    console.log("logfileout: " + filename + ":" + message);
    try {
        fs.appendFile('/home/mark/website/tools/raspi-temp-monitor/' + filename, message + '\r\n', function (err) {
            if (err) {
                console.log(err);
            }
        });
    } catch (error) {
        console.error("Unable to write to output file... " + error.message);
        debugger;
    }
}

function serverTempTimeout() {
    console.log("Error: client timeout passed");
    errormode = true;
    var currentTime = new Date();

    var powerInternetMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>', // sender address
        to: settings.emailstring, // list of receivers
        subject: 'Possible Power or Internet Failure - pitempmon - ' + currentTime, // Subject line
        text: 'Hello! \nThis is an alert that the tempreature monitoring system has missed a status report.\nThis might mean that the system cannot access the internet or has powered off unexpectedly'
    };

    sendMailMessage(powerInternetMail);
}

function serverTempTimeoutNew(clientid) {
    console.log("Error: client " + clientid + " timeout passed");
    debugger;
    let singleClientError = true;
    var currentTime = new Date();

    var powerInternetMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>', // sender address
        to: settings.emailstring, // list of receivers
        subject: 'Possible Power or Internet Failure - pitempmon - ' + currentTime, // Subject line
        text: 'Hello! \nThis is an alert that the tempreature monitoring system has missed a status report.\nThis might mean that the system cannot access the internet or has powered off unexpectedly'
    };

    debugger;
    //sendMailMessage(powerInternetMail);
}

function handleColdTemp(temp) {
    console.log("Error: Cold Temp - " + temp);
    errormode = true;
    var currentTime = new Date();

    var coldMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>', // sender address
        to: emailstring, // list of receivers
        subject: 'Cold Temp Alert - pitempmon - ' + currentTime, // Subject line
        text: 'Hello! \nThis is an alert that the current temp recorded by the tempreature monitoring system was ' + temp + '.\nThis is below the accepted threshold of ' + threshold + '\n\nPlease verify that this reading is correct!'
    };

    sendMailMessage(coldMail);
}

function errorResolved() {
    console.log("Error Resolved - Client Checkin");
    errormode = false;
    var currentTime = new Date();

    var resolvedMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>',
        to: emailstring,
        subject: "Temp Monitor Okay - pitempmon - " + currentTime,
        text: "Hello! \nThis is a notification that the house temp monitor service is back up and running after an error. There is nothing you need to do at this time."
    };

    sendMailMessage(resolvedMail);
}

function sendMailMessage(options) {
    transporter.sendMail(options, function (error, info) {
        if (error) {
            console.log(error);
        } else {
            console.log('Message sent: ' + info.response);
        }
    });
}

module.exports = RaspiTempMonitor;<commit_msg>Add additional logging to temp monitor<commit_after>const LifeforcePlugin = require("../utils/LifeforcePlugin.js");
const fs = require('fs');

let errormode = false;
const threshold = 45;
const timertime = 2700000;
let settings = null;
let transporter = null;

let tempCheckinLists = {};
let tempCheckinTimers = {};

class RaspiTempMonitor extends LifeforcePlugin {
    constructor(restifyserver, logger, name) {
        super(restifyserver, logger, name);
        this.apiMap = [
            {
                path: "/api/tempmon/:temp",
                type: "get",
                handler: handleTempCheckin
            },
            {
                path: "/api/temp/checkin",
                type: "post",
                handler: handleTempCheckinNew
            }
        ];

        // Grab the subset of settings we actually want
        settings = this.config.tempmonitor;
        this.timerfunc = null;

        // create reusable transporter object using SMTP transport
        const nodemailer = require("nodemailer");
        transporter = nodemailer.createTransport({
            service: 'Gmail',
            auth: {
                user: settings.emailuser,
                pass: settings.emailpass
            }
        });
    }
}

function handleTempCheckin(req, res, next) {
    this.log.info("Got a temp checkin from raspberry pi");

    // Clear the current timer because we got a check in time
    clearTimeout(this.timerfunc);

    // If we get this call, but we're in errormode, the system got a message
    // after a previous failure.
    if (errormode) {
        errorResolved();
    }

    var response = {};
    response.date = Date.now();
    response.temp = req.params.temp;

    // Check if the temp is under our threshold and warn us!
    if (response.temp < threshold) {
        this.log.info("This is a cold temp! Handle this error!");
        handleColdTemp(response.temp);
    } else {
        this.log.info("Normal checkin, nothing is wrong.");
    }

    // Write the results to a text file
    var fileString = response.date + " | " + response.temp;
    logfileout(fileString, "templogfile.txt");

    // Send the response to the client
    res.send(200, response);

    // restart the timer to wait until the next checkin
    this.log.info("Starting timer to wait for client checkin...");
    this.timerfunc = setTimeout(function () {
        serverTempTimeout();
    }, timertime);

    return next();
}

function handleTempCheckinNew(req, res, next) {
    if (req.body) {
        let body = req.body;
        let clientid = null;
        let temp = null;

        if (req.body.clientid && req.body.temp) {

            clientid = req.body.clientid;
            temp = req.body.temp;

            this.log.info("Got a client checkin from " + clientid + " with temp " + temp);
            clearTimeout(tempCheckinTimers[clientid]);

            tempCheckinLists[clientid] = temp;
            tempCheckinTimers[clientid] = setTimeout(serverTempTimeoutNew, timertime / 100, clientid);

            res.send(200, { checkin: "OK!", clientid: clientid, temp: temp });
        } else {
            res.send(400, "Bad Request");

        }
    } else {
        res.send(400, "Bad Request");

    }

    return next();
}

// Helper function to write to a file
function logfileout(message, filename) {
    this.log.info("logfileout: " + filename + ":" + message);
    try {
        fs.appendFile('/home/mark/website/tools/raspi-temp-monitor/' + filename, message + '\r\n', function (err) {
            if (err) {
                this.log.info("Warning! Unable to write to log file!");
                console.log(err);
            }
        });
    } catch (error) {
        console.error("Unable to write to output file... " + error.message);
        debugger;
    }
}

function serverTempTimeout() {
    this.log.info("Error: client timeout passed");
    errormode = true;
    var currentTime = new Date();

    var powerInternetMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>', // sender address
        to: settings.emailstring, // list of receivers
        subject: 'Possible Power or Internet Failure - pitempmon - ' + currentTime, // Subject line
        text: 'Hello! \nThis is an alert that the tempreature monitoring system has missed a status report.\nThis might mean that the system cannot access the internet or has powered off unexpectedly'
    };

    sendMailMessage(powerInternetMail);
}

function serverTempTimeoutNew(clientid) {
    this.log.info("Error: client " + clientid + " timeout passed");
    debugger;
    let singleClientError = true;
    var currentTime = new Date();

    var powerInternetMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>', // sender address
        to: settings.emailstring, // list of receivers
        subject: 'Possible Power or Internet Failure - pitempmon - ' + currentTime, // Subject line
        text: 'Hello! \nThis is an alert that the tempreature monitoring system has missed a status report.\nThis might mean that the system cannot access the internet or has powered off unexpectedly'
    };

    debugger;
    //sendMailMessage(powerInternetMail);
}

function handleColdTemp(temp) {
    this.log.info("Error: Cold Temp - " + temp);
    errormode = true;
    var currentTime = new Date();

    var coldMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>', // sender address
        to: emailstring, // list of receivers
        subject: 'Cold Temp Alert - pitempmon - ' + currentTime, // Subject line
        text: 'Hello! \nThis is an alert that the current temp recorded by the tempreature monitoring system was ' + temp + '.\nThis is below the accepted threshold of ' + threshold + '\n\nPlease verify that this reading is correct!'
    };

    sendMailMessage(coldMail);
}

function errorResolved() {
    console.log("Error Resolved - Client Checkin");
    errormode = false;
    var currentTime = new Date();

    var resolvedMail = {
        from: 'Temp Monitor <raspitempmon@gmail.com>',
        to: emailstring,
        subject: "Temp Monitor Okay - pitempmon - " + currentTime,
        text: "Hello! \nThis is a notification that the house temp monitor service is back up and running after an error. There is nothing you need to do at this time."
    };

    sendMailMessage(resolvedMail);
}

function sendMailMessage(options) {

    this.log.info("Sending email message to " + emailstring + " with " + JSON.stringify(options));

    transporter.sendMail(options, function (error, info) {
        if (error) {
            this.log.info("Unable to send email:" + JSON.stringify(error));
        } else {
            this.log.info('Message sent: ' + info.response);
        }
    });
}

module.exports = RaspiTempMonitor;<|endoftext|>