<commit_before>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var tagsAll = ['environment', 'blm', 'volunteer', 'education', 'LGBTQ+'];
var tagsSearch = [];
var tagsBox = [...tagsAll];

function addTagBoxToSearch(tag) {
  var boxIndex = tagsBox.indexOf(tag);
  if (boxIndex > -1) {
    tagsBox.splice(boxIndex, 1);
  }

  tagsSearch.push(tag);
  updateTagBox();
  updateSearchBar();
}

function addTagSearchToBox(tag) {
  var searchIndex = tagsSearch.indexOf(tag);
  if (searchIndex > -1) {
    tagsSearch.splice(searchIndex, 1);
  }

  tagsBox.splice(tagsAll.indexOf(tag), 0, tag);
  updateSearchBar();
  updateTagBox();
}

function updateSearchBar() {
  const elements = document.getElementsByClassName('search-bar');
  const searchBarElement = elements[0];
  searchBarElement.innerHTML = '';
  tagsSearch.forEach(function(tag) {
    const spanElement = document.createElement('span');
    spanElement.setAttribute('onclick', 'addTagSearchToBox(\"' + tag +
        '\")');
    // class name is now (for example) 'tag environment'
    if (tag == 'LGBTQ+') spanElement.className = 'tag rainbow';
    else spanElement.className = 'tag ' + tag;
    spanElement.innerText = tag;

    searchBarElement.appendChild(spanElement);
  });

  generateRainbowTags();
}

function updateTagBox() {
  const elements = document.getElementsByClassName('tag-box');
  const tagBoxElement = elements[0];
  tagBoxElement.innerHTML = '';
  tagsBox.forEach(function(tag) {
    const spanElement = document.createElement('span');
    spanElement.setAttribute('onclick', 'addTagBoxToSearch(\"' + tag +
        '\")');
    // class name is now (for example) 'tag environment'
    if (tag == 'LGBTQ+') spanElement.className = 'tag rainbow';
    else spanElement.className = 'tag ' + tag;
    spanElement.innerText = tag;

    tagBoxElement.appendChild(spanElement);
  });

  generateRainbowTags();
}

/**
 * Generates all the rainbow tags on a page, currently made to be used with LGBTQ+
 *
 * To have a rainbow tag generated, set its innerText to the tag name and give it
 * the class 'rainbow'
 */
function generateRainbowTags() {
  const elements = document.getElementsByClassName('rainbow');
  for (var e = 0; e < elements.length; e++) {
    var tag = elements[e].innerText;
    var tagHTML = '';
    var colors = ['#FF0900', '#FF7F00', '#ffe600','#00F11D', '#0079FF', '#A800FF'];
    for (var i = 0; i < tag.length; i++) {
      if (i >= colors.length) break;
      tagHTML = tagHTML + '' + tag.charAt(i) + '';
    }
    elements[e].innerHTML = tagHTML;
  }
}

/**
 * Placeholder function for search functionality
 */
function search() {
  var url = '/search.html?tags=';
  tagsSearch.forEach(function(tag) {
    url += tag + ',';
  });
  // trim the last comma off
  if (url.charAt(url.length - 1) == ',') {
    url = url.substring(0, url.length - 1);
  }

  window.location.href = url;
  //TODO fetch call to server with search parameters
}

/* Two test examples to use with getEvents() */
var test = {title:'Beach clean up',
            description:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
                'Nam efficitur enim quis est mollis blandit. Integer vitae augue risus. ' +
                'Nunc sit amet semper urna, ac mollis dui. Aenean vitae imperdiet nisi, ' +
                'sit amet mattis libero. Sed tincidunt arcu in justo...',
            date:'Saturday, June 20, 2020',
            time:'1:00 PM',
            distance:'5 miles away',
            address:'Main St, Venice, CA',
            attendeeCount: 12,
            tags:['environment']};
var test2 = {title:'Book Drive',
            description:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
                'Nam efficitur enim quis est mollis blandit. Integer vitae augue risus. ' +
                'Nunc sit amet semper urna, ac mollis dui. Aenean vitae imperdiet nisi, ' +
                'sit amet mattis libero. Sed tincidunt arcu in justo...',
            date:'Sunday, June 21, 2020',
            time:'1:00 PM',
            distance:'6 miles away',
            address:'Main St, Los Angeles, CA',
            attendeeCount: 12,
            tags:['education']};
var events = [test, test2];

const dummyText = "Suggested for you"; // TODO: come up with variety
/**
 * Fetches events from a specific url
 *
 * Currently uses the events variable defined above
 */
async function getEvents(url, index, option) {
  const eventListElements = document.getElementsByClassName('event-list-container');
  if(index === null || index >= eventListElements.length) {
    index = 0;
  }
  var eventListElement = eventListElements[index];
  eventListElement.innerHTML = '';
  console.log(eventListElement);
  events.forEach(function(event) {
    const eventItemElement = document.createElement('div');
    eventItemElement.className = 'event-item';
    eventListElement.appendChild(eventItemElement);

    const eventImageElement = document.createElement('div');
    eventImageElement.className = 'event-image ' + event.tags[0];
    eventItemElement.appendChild(eventImageElement);

    const eventItemInfoElement = document.createElement('div');
    eventItemInfoElement.className = 'event-item-info';
    eventItemElement.appendChild(eventItemInfoElement);

    const eventItemHeaderElement = document.createElement('div');
    eventItemHeaderElement.className = 'event-item-header';
    eventItemInfoElement.appendChild(eventItemHeaderElement);

    const eventItemTitleElement = document.createElement('div');
    eventItemTitleElement.className = 'event-item-title';
    if(option == 1 || option == 2) {
      const eventItemTitleName = document.createElement('div');
      eventItemTitleName.innerText = event.title;
      const eventItemTitleAddr = document.createElement('div');
      eventItemTitleAddr.innerText = event.address;
      eventItemTitleAddr.className = 'event-item-address';
      eventItemTitleElement.appendChild(eventItemTitleName);
      eventItemTitleElement.appendChild(eventItemTitleAddr);
    } else {
      eventItemTitleElement.innerText = event.title;
    }
    eventItemHeaderElement.appendChild(eventItemTitleElement);

    const eventItemDetailsElement = document.createElement('div');
    eventItemDetailsElement.className = 'event-item-details';
    eventItemHeaderElement.appendChild(eventItemDetailsElement);
    const eventItemDateElement = document.createElement('div');
    eventItemDateElement.className = 'event-item-date';
    eventItemDateElement.innerText = event.date;
    eventItemDetailsElement.appendChild(eventItemDateElement);
    const eventItemDistanceElement = document.createElement('div');
    eventItemDistanceElement.className = 'event-item-distance';
    if(option == 1 || option == 2) {
      eventItemDistanceElement.innerText = event.time;
    } else {
      eventItemDistanceElement.innerText = event.distance;
    }
    eventItemDetailsElement.appendChild(eventItemDistanceElement);

    const eventItemDescElement = document.createElement('div');
    eventItemDescElement.className = 'event-item-description';
    eventItemDescElement.innerText = event.description;
    eventItemInfoElement.appendChild(eventItemDescElement);

    const eventItemFooterElement = document.createElement('div');
    eventItemFooterElement.className = 'event-item-footer';
    eventItemInfoElement.appendChild(eventItemFooterElement);

    // decide which footer item to use
    const attendeeCountContainerElement = document.createElement('div');
    if(option == 0) {
      // "recommended for you"
      attendeeCountContainerElement.innerText = dummyText;

    } else if (option == 1) {
      // unsave an event
      attendeeCountContainerElement.className = 'edit-unsave-event';
      attendeeCountContainerElement.innerText = "Unsave this event";
      attendeeCountContainerElement.onclick = function() {
          // TODO: unsave from datastore or make popup that confirms choice first
      }
    } else if (option == 2) {
      // edit an event
      attendeeCountContainerElement.className = 'edit-unsave-event';
      const editEventLink = document.createElement("a");
      editEventLink.innerText = "Edit this event";

      // TODO: set this href to edit-event page
      editEventLink.href = "/create-event-form.html";
      attendeeCountContainerElement.appendChild(editEventLink);

    } else {
      // default: show attendee count
      attendeeCountContainerElement.className = 'attendee-count-container';

      const attendeeCountElement = document.createElement('span');
      attendeeCountElement.className = 'attendee-count ' + event.tags[0] + '-text';
      attendeeCountElement.innerText = event.attendeeCount;
      attendeeCountContainerElement.appendChild(attendeeCountElement);
      attendeeCountContainerElement.appendChild(
        document.createTextNode(' already attending'));

    }

    eventItemFooterElement.appendChild(attendeeCountContainerElement);

    const tagsContainerElement = document.createElement('div');
    tagsContainerElement.className = 'tags-container';
    eventItemFooterElement.appendChild(tagsContainerElement);
    event.tags.forEach(function(tag) {
      const tagElement = document.createElement('span');
      // class name is now (for example) 'tag environment'
      if (tag == 'LGBTQ+') spanElement.className = 'tag rainbow';
      else tagElement.className = 'tag ' + tag;
      tagElement.innerText = tag;

      tagsContainerElement.appendChild(tagElement);
    });
  });
}
<commit_msg>Adjust spacing<commit_after>// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var tagsAll = ['environment', 'blm', 'volunteer', 'education', 'LGBTQ+'];
var tagsSearch = [];
var tagsBox = [...tagsAll];

function addTagBoxToSearch(tag) {
  var boxIndex = tagsBox.indexOf(tag);
  if (boxIndex > -1) {
    tagsBox.splice(boxIndex, 1);
  }

  tagsSearch.push(tag);
  updateTagBox();
  updateSearchBar();
}

function addTagSearchToBox(tag) {
  var searchIndex = tagsSearch.indexOf(tag);
  if (searchIndex > -1) {
    tagsSearch.splice(searchIndex, 1);
  }

  tagsBox.splice(tagsAll.indexOf(tag), 0, tag);
  updateSearchBar();
  updateTagBox();
}

function updateSearchBar() {
  const elements = document.getElementsByClassName('search-bar');
  const searchBarElement = elements[0];
  searchBarElement.innerHTML = '';
  tagsSearch.forEach(function(tag) {
    const spanElement = document.createElement('span');
    spanElement.setAttribute('onclick', 'addTagSearchToBox(\"' + tag +
        '\")');
    // class name is now (for example) 'tag environment'
    if (tag == 'LGBTQ+') spanElement.className = 'tag rainbow';
    else spanElement.className = 'tag ' + tag;
    spanElement.innerText = tag;

    searchBarElement.appendChild(spanElement);
  });

  generateRainbowTags();
}

function updateTagBox() {
  const elements = document.getElementsByClassName('tag-box');
  const tagBoxElement = elements[0];
  tagBoxElement.innerHTML = '';
  tagsBox.forEach(function(tag) {
    const spanElement = document.createElement('span');
    spanElement.setAttribute('onclick', 'addTagBoxToSearch(\"' + tag +
        '\")');
    // class name is now (for example) 'tag environment'
    if (tag == 'LGBTQ+') spanElement.className = 'tag rainbow';
    else spanElement.className = 'tag ' + tag;
    spanElement.innerText = tag;

    tagBoxElement.appendChild(spanElement);
  });

  generateRainbowTags();
}

/**
 * Generates all the rainbow tags on a page, currently made to be used with LGBTQ+
 *
 * To have a rainbow tag generated, set its innerText to the tag name and give it
 * the class 'rainbow'
 */
function generateRainbowTags() {
  const elements = document.getElementsByClassName('rainbow');
  for (var e = 0; e < elements.length; e++) {
    var tag = elements[e].innerText;
    var tagHTML = '';
    var colors = ['#FF0900', '#FF7F00', '#ffe600','#00F11D', '#0079FF', '#A800FF'];
    for (var i = 0; i < tag.length; i++) {
      if (i >= colors.length) break;
      tagHTML = tagHTML + '' + tag.charAt(i) + '';
    }
    elements[e].innerHTML = tagHTML;
  }
}

/**
 * Placeholder function for search functionality
 */
function search() {
  var url = '/search.html?tags=';
  tagsSearch.forEach(function(tag) {
    url += tag + ',';
  });
  // trim the last comma off
  if (url.charAt(url.length - 1) == ',') {
    url = url.substring(0, url.length - 1);
  }

  window.location.href = url;
  //TODO fetch call to server with search parameters
}

/* Two test examples to use with getEvents() */
var test = {title:'Beach clean up',
            description:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
                'Nam efficitur enim quis est mollis blandit. Integer vitae augue risus. ' +
                'Nunc sit amet semper urna, ac mollis dui. Aenean vitae imperdiet nisi, ' +
                'sit amet mattis libero. Sed tincidunt arcu in justo...',
            date:'Saturday, June 20, 2020',
            time:'1:00 PM',
            distance:'5 miles away',
            address:'Main St, Venice, CA',
            attendeeCount: 12,
            tags:['environment']};
var test2 = {title:'Book Drive',
            description:'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' +
                'Nam efficitur enim quis est mollis blandit. Integer vitae augue risus. ' +
                'Nunc sit amet semper urna, ac mollis dui. Aenean vitae imperdiet nisi, ' +
                'sit amet mattis libero. Sed tincidunt arcu in justo...',
            date:'Sunday, June 21, 2020',
            time:'1:00 PM',
            distance:'6 miles away',
            address:'Main St, Los Angeles, CA',
            attendeeCount: 12,
            tags:['education']};
var events = [test, test2];

const dummyText = "Suggested for you"; // TODO: come up with variety
/**
 * Fetches events from a specific url
 *
 * Currently uses the events variable defined above
 */
async function getEvents(url, index, option) {
  const eventListElements = document.getElementsByClassName('event-list-container');
  if (index === null || index >= eventListElements.length) {
    index = 0;
  }
  var eventListElement = eventListElements[index];
  eventListElement.innerHTML = '';
  console.log(eventListElement);
  events.forEach(function(event) {
    const eventItemElement = document.createElement('div');
    eventItemElement.className = 'event-item';
    eventListElement.appendChild(eventItemElement);

    const eventImageElement = document.createElement('div');
    eventImageElement.className = 'event-image ' + event.tags[0];
    eventItemElement.appendChild(eventImageElement);

    const eventItemInfoElement = document.createElement('div');
    eventItemInfoElement.className = 'event-item-info';
    eventItemElement.appendChild(eventItemInfoElement);

    const eventItemHeaderElement = document.createElement('div');
    eventItemHeaderElement.className = 'event-item-header';
    eventItemInfoElement.appendChild(eventItemHeaderElement);

    const eventItemTitleElement = document.createElement('div');
    eventItemTitleElement.className = 'event-item-title';
    if (option == 1 || option == 2) {
      const eventItemTitleName = document.createElement('div');
      eventItemTitleName.innerText = event.title;
      const eventItemTitleAddr = document.createElement('div');
      eventItemTitleAddr.innerText = event.address;
      eventItemTitleAddr.className = 'event-item-address';
      eventItemTitleElement.appendChild(eventItemTitleName);
      eventItemTitleElement.appendChild(eventItemTitleAddr);
    } else {
      eventItemTitleElement.innerText = event.title;
    }
    eventItemHeaderElement.appendChild(eventItemTitleElement);

    const eventItemDetailsElement = document.createElement('div');
    eventItemDetailsElement.className = 'event-item-details';
    eventItemHeaderElement.appendChild(eventItemDetailsElement);
    const eventItemDateElement = document.createElement('div');
    eventItemDateElement.className = 'event-item-date';
    eventItemDateElement.innerText = event.date;
    eventItemDetailsElement.appendChild(eventItemDateElement);
    const eventItemDistanceElement = document.createElement('div');
    eventItemDistanceElement.className = 'event-item-distance';
    if (option == 1 || option == 2) {
      eventItemDistanceElement.innerText = event.time;
    } else {
      eventItemDistanceElement.innerText = event.distance;
    }
    eventItemDetailsElement.appendChild(eventItemDistanceElement);

    const eventItemDescElement = document.createElement('div');
    eventItemDescElement.className = 'event-item-description';
    eventItemDescElement.innerText = event.description;
    eventItemInfoElement.appendChild(eventItemDescElement);

    const eventItemFooterElement = document.createElement('div');
    eventItemFooterElement.className = 'event-item-footer';
    eventItemInfoElement.appendChild(eventItemFooterElement);

    // decide which footer item to use
    const attendeeCountContainerElement = document.createElement('div');
    if (option == 0) {
      // "recommended for you"
      attendeeCountContainerElement.innerText = dummyText;

    } else if (option == 1) {
      // unsave an event
      attendeeCountContainerElement.className = 'edit-unsave-event';
      attendeeCountContainerElement.innerText = "Unsave this event";
      attendeeCountContainerElement.onclick = function() {
          // TODO: unsave from datastore or make popup that confirms choice first
      }
    } else if (option == 2) {
      // edit an event
      attendeeCountContainerElement.className = 'edit-unsave-event';
      const editEventLink = document.createElement("a");
      editEventLink.innerText = "Edit this event";

      // TODO: set this href to edit-event page
      editEventLink.href = "/create-event-form.html";
      attendeeCountContainerElement.appendChild(editEventLink);
    } else {
      // default: show attendee count
      attendeeCountContainerElement.className = 'attendee-count-container';

      const attendeeCountElement = document.createElement('span');
      attendeeCountElement.className = 'attendee-count ' + event.tags[0] + '-text';
      attendeeCountElement.innerText = event.attendeeCount;
      attendeeCountContainerElement.appendChild(attendeeCountElement);
      attendeeCountContainerElement.appendChild(
        document.createTextNode(' already attending'));
    }

    eventItemFooterElement.appendChild(attendeeCountContainerElement);

    const tagsContainerElement = document.createElement('div');
    tagsContainerElement.className = 'tags-container';
    eventItemFooterElement.appendChild(tagsContainerElement);
    event.tags.forEach(function(tag) {
      const tagElement = document.createElement('span');
      // class name is now (for example) 'tag environment'
      if (tag == 'LGBTQ+') spanElement.className = 'tag rainbow';
      else tagElement.className = 'tag ' + tag;
      tagElement.innerText = tag;

      tagsContainerElement.appendChild(tagElement);
    });
  });
}
<|endoftext|>


<commit_before>var Guest = Entity.extend({
    isNpc: true,
    isGuest: true,

    restlessTimer: 0,

    firstName: 'John',
    lastName: 'Doe',
    gender: Gender.MALE,
    martialStatus: MaritalStatus.UNDISCLOSED,

    /**
     * The room we were in at the time of the murder.
     */
    room: null,

    isMurderer: false,
    isVictim: false,
    isWitness: false,

    init: function (firstName, lastName, gender, maritalStatus, head, body) {
        this._super();

        this.firstName = firstName;
        this.lastName = lastName;
        this.gender = gender;
        this.martialStatus = maritalStatus;

        this.width = 40;
        this.height = 26;

        this.spriteHead = Gfx.load('head_' + head);
        this.spriteBody = Gfx.load('body_' + body);
        this.spriteShadow = Gfx.load('shadow_body_generic');
    },

    findFreePosition: function () {
        do {
            this.posX = chance.integer({
                min: 30,
                max: 955
            });

            this.posY = chance.integer({
                min: 190,
                max: 330
            });

            this.direction = chance.integer({
                min: Direction.UP,
                max: Direction.LEFT
            })
        }
        while (!this.canMoveAnywhere());
    },

    getNamePrefix: function () {
        if (this.gender == Gender.MALE) {
            return 'Mr';
        }

        // Wow, okay, so there is a lot of discussions on these prefixes on the internet. Hope I used this right.
        switch (this.martialStatus) {
            case MaritalStatus.UNDISCLOSED:
                return 'Ms';
            case MaritalStatus.MARRIED:
                return 'Mrs';
            case MaritalStatus.UNMARRIED:
                return 'Miss';
        }
    },

    getDisplayName: function () {
        return this.getNamePrefix() + ' ' + this.firstName + ' ' + this.lastName;
    },

    update: function () {
        this._super();
    }
});<commit_msg>Guests: Randomly wander about the living area.<commit_after>var Guest = Entity.extend({
    isNpc: true,
    isGuest: true,

    firstName: 'John',
    lastName: 'Doe',
    gender: Gender.MALE,
    martialStatus: MaritalStatus.UNDISCLOSED,

    /**
     * The room we were in at the time of the murder.
     */
    room: null,

    isMurderer: false,
    isVictim: false,
    isWitness: false,

    init: function (firstName, lastName, gender, maritalStatus, head, body) {
        this._super();

        this.firstName = firstName;
        this.lastName = lastName;
        this.gender = gender;
        this.martialStatus = maritalStatus;

        this.width = 40;
        this.height = 26;

        this.movementSpeed = 1;

        this.spriteHead = Gfx.load('head_' + head);
        this.spriteBody = Gfx.load('body_' + body);
        this.spriteShadow = Gfx.load('shadow_body_generic');
    },

    findFreePosition: function () {
        do {
            this.posX = chance.integer({
                min: 30,
                max: 955
            });

            this.posY = chance.integer({
                min: 190,
                max: 330
            });

            this.direction = chance.integer({
                min: Direction.UP,
                max: Direction.LEFT
            })
        }
        while (!this.canMoveAnywhere());
    },

    getNamePrefix: function () {
        if (this.gender == Gender.MALE) {
            return 'Mr';
        }

        // Wow, okay, so there is a lot of discussions on these prefixes on the internet. Hope I used this right.
        switch (this.martialStatus) {
            case MaritalStatus.UNDISCLOSED:
                return 'Ms';
            case MaritalStatus.MARRIED:
                return 'Mrs';
            case MaritalStatus.UNMARRIED:
                return 'Miss';
        }
    },

    getDisplayName: function () {
        return this.getNamePrefix() + ' ' + this.firstName + ' ' + this.lastName;
    },

    restlessTimer: 0,
    tiredTimer: 0,
    currentAction: null,

    doSomething: function () {
        this.restlessTimer = chance.integer({
            min: 60,
            max: 600
        });

        this.tiredTimer = -chance.integer({
            min: 60,
            max: 600
        });

        var direction = chance.integer({
            min: 0,
            max: 4
        });

        switch (direction) {
            case Direction.UP:
                this.direction = Direction.UP;
                this.velocityX = 0;
                this.velocityY = -this.movementSpeed;
                break;

            case Direction.LEFT:
                this.direction = Direction.LEFT;
                this.velocityX = -this.movementSpeed;
                this.velocityY = 0;
                break;

            case Direction.RIGHT:
                this.direction = Direction.RIGHT;
                this.velocityX = +this.movementSpeed;
                this.velocityY = 0;
                break;

            case Direction.DOWN:
                this.direction = Direction.DOWN;
                this.velocityX = 0;
                this.velocityY = +this.movementSpeed;
                break;
        }
    },

    isWalking: function () {
        return this.velocityX != 0 || this.velocityY != 0;
    },

    isBored: function () {
        if (this.restlessTimer > 0) {
            this.restlessTimer--;
        }

        return this.restlessTimer == 0;
    },

    isTired: function () {
        if (this.tiredTimer < 0) {
            this.tiredTimer++;
        }

        return this.tiredTimer >= 0;
    },

    stopWalking: function () {
        this.velocityX = 0;
        this.velocityY = 0;
    },

    update: function () {
        this._super();

        if (this.isBored()) {
            this.doSomething();
        }

        if (this.isWalking()) {
            if (this.isTired()) {
                this.stopWalking();
            }
        }
    }
});<|endoftext|>


<commit_before>/**
 * @author mr.doob / http://mrdoob.com/
 * @author alteredq / http://alteredqualia.com/
 */

THREE.Material = function () {

	this.id = THREE.MaterialCount ++;

	this.name = '';

	this.side = THREE.FrontSide;

	this.opacity = 1;
	this.transparent = false;

	this.blending = THREE.NormalBlending;

	this.blendSrc = THREE.SrcAlphaFactor;
	this.blendDst = THREE.OneMinusSrcAlphaFactor;
	this.blendEquation = THREE.AddEquation;

	this.depthTest = true;
	this.depthWrite = true;

	this.polygonOffset = false;
	this.polygonOffsetFactor = 0;
	this.polygonOffsetUnits = 0;

	this.alphaTest = 0;

	this.overdraw = false; // Boolean for fixing antialiasing gaps in CanvasRenderer

	this.visible = true;

	this.needsUpdate = true;

};

THREE.Material.prototype.setValues = function ( values ) {

	if ( values === undefined ) return;

	for ( var key in values ) {

		var newValue = values[ key ];

		if ( newValue === undefined ) continue;

		if ( key in this ) {

			var currentValue = this[ key ];

			if ( currentValue instanceof THREE.Color && newValue instanceof THREE.Color ) {

				currentValue.copy( newValue );

			} else if ( currentValue instanceof THREE.Color && typeof( newValue ) === "number" ) {

				currentValue.setHex( newValue );

			} else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) {

				currentValue.copy( newValue );

			} else {

				this[ key ] = newValue;

			}

		}

	}

};

THREE.Material.prototype.clone = function ( material ) {

	if ( material === undefined ) material = new THREE.Material();

	material.name = this.name;

	material.side = this.side;

	material.opacity = this.opacity;
	material.transparent = this.transparent;

	material.blending = this.blending;

	material.blendSrc = this.blendSrc;
	material.blendDst = this.blendDst;
	material.blendEquation = this.blendEquation;

	material.depthTest = this.depthTest;
	material.depthWrite = this.depthWrite;

	material.polygonOffset = this.polygonOffset;
	material.polygonOffsetFactor = this.polygonOffsetFactor;
	material.polygonOffsetUnits = this.polygonOffsetUnits;

	material.alphaTest = this.alphaTest;

	material.overdraw = this.overdraw;

	material.visible = this.visible;

	return material;

};

THREE.MaterialCount = 0;
<commit_msg>I guess we can start adding some warning here and there.<commit_after>/**
 * @author mr.doob / http://mrdoob.com/
 * @author alteredq / http://alteredqualia.com/
 */

THREE.Material = function () {

	this.id = THREE.MaterialCount ++;

	this.name = '';

	this.side = THREE.FrontSide;

	this.opacity = 1;
	this.transparent = false;

	this.blending = THREE.NormalBlending;

	this.blendSrc = THREE.SrcAlphaFactor;
	this.blendDst = THREE.OneMinusSrcAlphaFactor;
	this.blendEquation = THREE.AddEquation;

	this.depthTest = true;
	this.depthWrite = true;

	this.polygonOffset = false;
	this.polygonOffsetFactor = 0;
	this.polygonOffsetUnits = 0;

	this.alphaTest = 0;

	this.overdraw = false; // Boolean for fixing antialiasing gaps in CanvasRenderer

	this.visible = true;

	this.needsUpdate = true;

};

THREE.Material.prototype.setValues = function ( values ) {

	if ( values === undefined ) return;

	for ( var key in values ) {

		var newValue = values[ key ];

		if ( newValue === undefined ) {

			console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' );
			continue;

		}

		if ( key in this ) {

			var currentValue = this[ key ];

			if ( currentValue instanceof THREE.Color && newValue instanceof THREE.Color ) {

				currentValue.copy( newValue );

			} else if ( currentValue instanceof THREE.Color && typeof( newValue ) === "number" ) {

				currentValue.setHex( newValue );

			} else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) {

				currentValue.copy( newValue );

			} else {

				this[ key ] = newValue;

			}

		}

	}

};

THREE.Material.prototype.clone = function ( material ) {

	if ( material === undefined ) material = new THREE.Material();

	material.name = this.name;

	material.side = this.side;

	material.opacity = this.opacity;
	material.transparent = this.transparent;

	material.blending = this.blending;

	material.blendSrc = this.blendSrc;
	material.blendDst = this.blendDst;
	material.blendEquation = this.blendEquation;

	material.depthTest = this.depthTest;
	material.depthWrite = this.depthWrite;

	material.polygonOffset = this.polygonOffset;
	material.polygonOffsetFactor = this.polygonOffsetFactor;
	material.polygonOffsetUnits = this.polygonOffsetUnits;

	material.alphaTest = this.alphaTest;

	material.overdraw = this.overdraw;

	material.visible = this.visible;

	return material;

};

THREE.MaterialCount = 0;
<|endoftext|>


<commit_before>module.exports.decodeValue = decodeValue;
module.exports.encodeValue = encodeValue;
module.exports.decode = decode;
module.exports.encode = encode;

const tokens_map = {
    '\\\\': '\\',
    '\\:':  ';',
    '\\s':  ' ',
    '\\n':  '\n',
    '\\r':  '\r'
};

const token_lookup = /\\\\|\\:|\\s|\\n|\\r/gi;

function decodeValue(value) {
    return value.replace(token_lookup, m => tokens_map[m] || '');
}

const vals_map = {
    '\\': '\\\\',
    ';':  '\\:',
    ' ':  '\\s',
    '\n': '\\n',
    '\r': '\\r',
};

const val_lookup = /\\|;| |\n|\r/gi;

function encodeValue(value) {
    return value.replace(val_lookup, m => vals_map[m] || '');
}

function decode(tag_str) {
    let tags = Object.create(null);

    tag_str.split(';').forEach(tag => {
        let parts = tag.split('=');
        let key = parts[0].toLowerCase();
        let value = parts[1];

        if (!key) {
            return;
        }

        // if no value is given for this tag, just set it to true
        if (typeof value === 'string') {
            value = decodeValue(value);
        } else {
            value = true;
        }
        tags[key] = value;
    });

    return tags;
}

function encode(tags) {
    let parts = Object.keys(tags).map(key => {
        let val = tags[key];

        if (typeof val === 'boolean') {
            return key;
        }

        return `${key}=${encodeValue(val.toString())}`;
    });

    return parts.join(';');
}
<commit_msg>Bring node v4 support back<commit_after>'use strict';

module.exports.decodeValue = decodeValue;
module.exports.encodeValue = encodeValue;
module.exports.decode = decode;
module.exports.encode = encode;

const tokens_map = {
    '\\\\': '\\',
    '\\:':  ';',
    '\\s':  ' ',
    '\\n':  '\n',
    '\\r':  '\r'
};

const token_lookup = /\\\\|\\:|\\s|\\n|\\r/gi;

function decodeValue(value) {
    return value.replace(token_lookup, m => tokens_map[m] || '');
}

const vals_map = {
    '\\': '\\\\',
    ';':  '\\:',
    ' ':  '\\s',
    '\n': '\\n',
    '\r': '\\r',
};

const val_lookup = /\\|;| |\n|\r/gi;

function encodeValue(value) {
    return value.replace(val_lookup, m => vals_map[m] || '');
}

function decode(tag_str) {
    let tags = Object.create(null);

    tag_str.split(';').forEach(tag => {
        let parts = tag.split('=');
        let key = parts[0].toLowerCase();
        let value = parts[1];

        if (!key) {
            return;
        }

        // if no value is given for this tag, just set it to true
        if (typeof value === 'string') {
            value = decodeValue(value);
        } else {
            value = true;
        }
        tags[key] = value;
    });

    return tags;
}

function encode(tags) {
    let parts = Object.keys(tags).map(key => {
        let val = tags[key];

        if (typeof val === 'boolean') {
            return key;
        }

        return key + '=' + encodeValue(val.toString());
    });

    return parts.join(';');
}
<|endoftext|>


<commit_before>define(['app', 'scripts/webitel/utils', 'modules/callflows/editor', 'modules/hooks/hookModel', 'ui-ace'],
    function (app, utils, aceEditor) {

    app.directive( 'wDictionary', function() {
        return {
            restrict: 'AE',
            scope: {
                "data": "=",
                "fields": "=",
                "select": "="
            },
            templateUrl: 'modules/hooks/arrayData.html',
            controller: function ( $scope ) {
                if (!$scope.data)
                    $scope.data = [];

                $scope.addData = function() {
                    $scope.inserted = {};
                    $scope.data.push($scope.inserted);
                };

                $scope.removeData = function (index) {
                    $scope.data.splice(index, 1);
                }
            }
        };
    });

    app.controller('HookCtrl', ['$scope', 'webitel', '$rootScope', 'notifi', 'HookModel', '$route', '$location', '$routeParams',
        '$confirm', '$timeout', 'TableSearch', 'cfpLoadingBar', 'FileUploader',
        function ($scope, webitel, $rootScope, notifi, HookModel, $route, $location, $routeParams, $confirm, $timeout,
                  TableSearch, cfpLoadingBar, FileUploader) {

        $scope.json = {body : aceEditor.getStrFromJson({})};

        $scope.aceLoaded = function(_editor) {
            // Options
            aceEditor.init(_editor, false);
        };

        // $scope.aceChanged = function (v) {
        //     debugger;
        // };

        $scope.domain = webitel.domain();
        $scope.hook = {};
        $scope.userVariables = utils.switchVar;
        $scope.canDelete = webitel.connection.session.checkResource('hook', 'd');
        $scope.canUpdate = webitel.connection.session.checkResource('hook', 'u');
        $scope.canCreate = webitel.connection.session.checkResource('hook', 'c');
        $scope.EVENTS = utils.events;

        $scope.viewMode = !$scope.canUpdate;
        $scope.view = function () {
            $scope.viewMode = true;
            edit();
        };

        $scope.remVar = [];
        $scope.roles = [];
        $scope.queues = {};
        $scope.isLoading = false;
        $scope.$watch('isLoading', function (val) {
            if (val) {
                cfpLoadingBar.start()
            } else {
                cfpLoadingBar.complete()
            }
        });

        $scope.query = TableSearch.get('hooks');

        $scope.$watch("query", function (newVal) {
            TableSearch.set(newVal, 'hooks')
        });

        $scope.displayedCollection = [];

        var changeDomainEvent = $rootScope.$on('webitel:changeDomain', function (e, domainName) {
            $scope.domain = domainName;
            closePage();
        });

        $scope.$on('$destroy', function () {
            changeDomainEvent();
        });

        function reloadData () {
            if ($location.$$path != '/hooks')
                return 0;

            if (!$scope.domain)
                return $scope.rowCollection = [];

            $scope.isLoading = true;
            HookModel.list({
                domain: $scope.domain,
                page: 1,
                limit: 5000,
                columns: '_id,name,enable,event,action.type,description'
            }, function (err, res) {
                $scope.isLoading = false;
                if (err)
                    return notifi.error(err, 5000);
                var arr = [];
                angular.forEach(res.data || res.info, function(item) {
                    arr.push(item);
                });
                $scope.rowCollection = arr;
            });
        };

        $scope.$watch('[hook,json]', function(newValue, oldValue) {
            if ($scope.hook._new)
                return $scope.isEdit = $scope.isNew = true;

            return $scope.isEdit = !!oldValue[0]._id;
        }, true);

        $scope.downloadScheme = function (row) {
            HookModel.item(row._id, $scope.domain, function(err, item) {
                if (err) {
                    return notifi.error(err, 5000);
                }
                utils.saveJsonToPc(item, item._id + '.json');
            });

        };

        function uploadJson (data, update) {
            function cb(err, res) {
                if (err)
                    return notifi.error(err, 5000);

                reloadData();
                var str;
                if (update) {
                    str = "Updated";
                } else {
                    str = "Created";
                }
                return notifi.success(str, 2000);
            };

            if (update) {
                HookModel.update(data, cb)
            } else {
                HookModel.add(data, cb);
            }
        };

        var uploader = $scope.uploader = new FileUploader();
        uploader.onWhenAddingFileFailed = function(item /*{File|FileLikeObject}*/, filter, options) {
            console.info('onWhenAddingFileFailed', item, filter, options);
        };
        uploader.onAfterAddingFile = function(item) {
            console.info('onAfterAddingFile', item);

            var reader = new FileReader();
            reader.onload = function(event) {
                try {
                    var data = JSON.parse(event.target.result);
                    if(data._id){
                        HookModel.item(data._id, $scope.domain, function (err, item) {
                            if (err && err.statusCode !== 404)
                                return notifi.error(err, 3000);
                            uploadJson(data, !!item);
                        });
                    }
                    else{
                        uploadJson(data, false);
                    }
                } catch (e) {
                    notifi.error(e, 10000);
                }
            };
            reader.readAsText(item._file);
        };

        $scope.cancel = function () {
            $scope.hook = angular.copy($scope.oldHook);
            disableEditMode();
        };

        function disableEditMode () {
            $timeout(function () {
                $scope.isEdit = false;
            }, 0);
        };

        $scope.$watch('domain', function(domainName) {
            $scope.domain = domainName;
            reloadData();
        });
        $scope.edit = edit;
        $scope.create = create;
        $scope.reloadData = reloadData;
        $scope.closePage = closePage;
        $scope.save = save;

        $scope.removeItem = function (row) {
            $confirm({text: 'Are you sure you want to delete ' + row._id+ ' ?'},  { templateUrl: 'views/confirm.html' })
                .then(function() {
                    HookModel.remove(row._id, $scope.domain, function (err) {
                        if (err)
                            return notifi.error(err, 5000);
                        reloadData()
                    })
                });
        };

        function closePage() {
            $location.path('/hooks');
        };

        function save() {
            var cb = function (err, res) {
                if (err)
                    return notifi.error(err, 5000);

                if ($scope.hook._new) {
                    return $location.path('/hooks/' + res.data.insertedIds[0] + '/edit');
                } else {
                    $scope.hook.__time = Date.now();
                    return edit();
                };
            };

            if ($scope.hook.customBody) {
                try {
                    JSON.parse($scope.json.body);
                    $scope.hook.rawBody = $scope.json.body;
                } catch (e) {
                    return notifi.error(e, 5000);
                }
            }

            if ($scope.hook._new) {
                HookModel.add($scope.hook, cb);
            } else {
                HookModel.update($scope.hook, cb);
            }
        };

        function edit () {
            var id = $routeParams.id;
            var domain = $routeParams.domain;

            HookModel.item(id, domain, function(err, item) {
                if (err) {
                    return notifi.error(err, 5000);
                };

                $scope.oldJsonBody = angular.copy(item.rawBody);
                $scope.json.body = $scope.oldJsonBody;

                $scope.oldHook = angular.copy(item);
                $scope.hook = item;
                disableEditMode();
            });
        }
        function create() {
            $scope.hook = HookModel.create();
            var domain = $routeParams.domain;
            $scope.hook.domain = domain;
            $scope.hook._new = true;
        };

        $scope.addHead = function () {
            if (!$scope.hook.headers) {
                $scope.hook.headers = [];
            }
            $scope.headInserted = {name: '', value: ''};
            $scope.hook.headers.push($scope.headInserted);
        };

        $scope.init = function init () {
            if (!!$route.current.method) {
                return $scope[$route.current.method]();
            };
        }();

    }]);
});<commit_msg>WTEL-688<commit_after>define(['app', 'scripts/webitel/utils', 'modules/callflows/editor', 'modules/hooks/hookModel', 'ui-ace'],
    function (app, utils, aceEditor) {

    app.directive( 'wDictionary', function() {
        return {
            restrict: 'AE',
            scope: {
                "data": "=",
                "fields": "=",
                "select": "="
            },
            templateUrl: 'modules/hooks/arrayData.html',
            controller: function ( $scope ) {
                if (!$scope.data)
                    $scope.data = [];

                $scope.addData = function() {
                    $scope.inserted = {};
                    $scope.data.push($scope.inserted);
                };

                $scope.removeData = function (index) {
                    $scope.data.splice(index, 1);
                }
            }
        };
    });

    app.controller('HookCtrl', ['$scope', 'webitel', '$rootScope', 'notifi', 'HookModel', '$route', '$location', '$routeParams',
        '$confirm', '$timeout', 'TableSearch', 'cfpLoadingBar', 'FileUploader',
        function ($scope, webitel, $rootScope, notifi, HookModel, $route, $location, $routeParams, $confirm, $timeout,
                  TableSearch, cfpLoadingBar, FileUploader) {

        $scope.json = {body : aceEditor.getStrFromJson({})};

        $scope.aceLoaded = function(_editor) {
            // Options
            aceEditor.init(_editor, false);
        };

        // $scope.aceChanged = function (v) {
        //     debugger;
        // };

        $scope.domain = webitel.domain();
        $scope.hook = {};
        $scope.userVariables = utils.switchVar;
        $scope.canDelete = webitel.connection.session.checkResource('hook', 'd');
        $scope.canUpdate = webitel.connection.session.checkResource('hook', 'u');
        $scope.canCreate = webitel.connection.session.checkResource('hook', 'c');
        $scope.EVENTS = utils.events;

        $scope.viewMode = !$scope.canUpdate;
        $scope.view = function () {
            $scope.viewMode = true;
            edit();
        };

        $scope.remVar = [];
        $scope.roles = [];
        $scope.queues = {};
        $scope.isLoading = false;
        $scope.$watch('isLoading', function (val) {
            if (val) {
                cfpLoadingBar.start()
            } else {
                cfpLoadingBar.complete()
            }
        });

        $scope.query = TableSearch.get('hooks');

        $scope.$watch("query", function (newVal) {
            TableSearch.set(newVal, 'hooks')
        });

        $scope.displayedCollection = [];

        var changeDomainEvent = $rootScope.$on('webitel:changeDomain', function (e, domainName) {
            $scope.domain = domainName;
            closePage();
        });

        $scope.$on('$destroy', function () {
            changeDomainEvent();
        });

        function reloadData () {
            if ($location.$$path != '/hooks')
                return 0;

            if (!$scope.domain)
                return $scope.rowCollection = [];

            $scope.isLoading = true;
            HookModel.list({
                domain: $scope.domain,
                page: 1,
                limit: 5000,
                columns: '_id,name,enable,event,action.type,description'
            }, function (err, res) {
                $scope.isLoading = false;
                if (err)
                    return notifi.error(err, 5000);
                var arr = [];
                angular.forEach(res.data || res.info, function(item) {
                    arr.push(item);
                });
                $scope.rowCollection = arr;
            });
        };

        $scope.$watch('[hook,json]', function(newValue, oldValue) {
            if ($scope.hook._new)
                return $scope.isEdit = $scope.isNew = true;

            return $scope.isEdit = !!oldValue[0]._id;
        }, true);

        $scope.downloadScheme = function (row) {
            HookModel.item(row._id, $scope.domain, function(err, item) {
                if (err) {
                    return notifi.error(err, 5000);
                }
                utils.saveJsonToPc(item, item._id + '.json');
            });

        };

        function uploadJson (data, update) {
            function cb(err, res) {
                if (err)
                    return notifi.error(err, 5000);

                reloadData();
                var str;
                if (update) {
                    str = "Updated";
                } else {
                    str = "Created";
                }
                return notifi.success(str, 2000);
            };

            if (update) {
                HookModel.update(data, cb)
            } else {
                HookModel.add(data, cb);
            }
        };

        var uploader = $scope.uploader = new FileUploader();
        uploader.onWhenAddingFileFailed = function(item /*{File|FileLikeObject}*/, filter, options) {
            console.info('onWhenAddingFileFailed', item, filter, options);
        };
        uploader.onAfterAddingFile = function(item) {
            console.info('onAfterAddingFile', item);

            var reader = new FileReader();
            reader.onload = function(event) {
                try {
                    var data = JSON.parse(event.target.result);
                    data.domain = $scope.domain;
                    if(data._id){
                        HookModel.item(data._id, $scope.domain, function (err, item) {
                            if (err && err.statusCode !== 404)
                                return notifi.error(err, 3000);
                            uploadJson(data, !!item);
                        });
                    }
                    else{
                        uploadJson(data, false);
                    }
                } catch (e) {
                    notifi.error(e, 10000);
                }
            };
            reader.readAsText(item._file);
        };

        $scope.cancel = function () {
            $scope.hook = angular.copy($scope.oldHook);
            disableEditMode();
        };

        function disableEditMode () {
            $timeout(function () {
                $scope.isEdit = false;
            }, 0);
        };

        $scope.$watch('domain', function(domainName) {
            $scope.domain = domainName;
            reloadData();
        });
        $scope.edit = edit;
        $scope.create = create;
        $scope.reloadData = reloadData;
        $scope.closePage = closePage;
        $scope.save = save;

        $scope.removeItem = function (row) {
            $confirm({text: 'Are you sure you want to delete ' + row._id+ ' ?'},  { templateUrl: 'views/confirm.html' })
                .then(function() {
                    HookModel.remove(row._id, $scope.domain, function (err) {
                        if (err)
                            return notifi.error(err, 5000);
                        reloadData()
                    })
                });
        };

        function closePage() {
            $location.path('/hooks');
        };

        function save() {
            var cb = function (err, res) {
                if (err)
                    return notifi.error(err, 5000);

                if ($scope.hook._new) {
                    return $location.path('/hooks/' + res.data.insertedIds[0] + '/edit');
                } else {
                    $scope.hook.__time = Date.now();
                    return edit();
                };
            };

            if ($scope.hook.customBody) {
                try {
                    JSON.parse($scope.json.body);
                    $scope.hook.rawBody = $scope.json.body;
                } catch (e) {
                    return notifi.error(e, 5000);
                }
            }

            if ($scope.hook._new) {
                HookModel.add($scope.hook, cb);
            } else {
                HookModel.update($scope.hook, cb);
            }
        };

        function edit () {
            var id = $routeParams.id;
            var domain = $routeParams.domain;

            HookModel.item(id, domain, function(err, item) {
                if (err) {
                    return notifi.error(err, 5000);
                };

                $scope.oldJsonBody = angular.copy(item.rawBody);
                $scope.json.body = $scope.oldJsonBody;

                $scope.oldHook = angular.copy(item);
                $scope.hook = item;
                disableEditMode();
            });
        }
        function create() {
            $scope.hook = HookModel.create();
            var domain = $routeParams.domain;
            $scope.hook.domain = domain;
            $scope.hook._new = true;
        };

        $scope.addHead = function () {
            if (!$scope.hook.headers) {
                $scope.hook.headers = [];
            }
            $scope.headInserted = {name: '', value: ''};
            $scope.hook.headers.push($scope.headInserted);
        };

        $scope.init = function init () {
            if (!!$route.current.method) {
                return $scope[$route.current.method]();
            };
        }();

    }]);
});<|endoftext|>


<commit_before>import Vue from 'vue'
import { merge as mergeObs } from 'rxjs/observable'
import { getLayerId, initializeLayer, setLayerId } from '../ol-ext'
import { obsFromOlEvent, obsFromOlChangeEvent } from '../rx-ext'
import { isEqual, pick } from '../util/minilo'
import mergeDescriptors from '../util/multi-merge-descriptors'
import { makeWatchers } from '../util/vue-helpers'
import cmp from './ol-cmp'
import sourceContainer from './source-container'
import useMapCmp from './use-map-cmp'
import stubVNode from './stub-vnode'

export default {
  mixins: [cmp, stubVNode, useMapCmp, sourceContainer],
  stubVNode: {
    attrs () {
      return {
        id: this.vmId,
        class: this.vmClass,
      }
    },
  },
  props: {
    className: String,
    opacity: {
      type: Number,
      default: 1,
    },
    visible: {
      type: Boolean,
      default: true,
    },
    /**
     * The bounding extent for layer rendering defined in the map view projection.
     * The layer will not be rendered outside of this extent.
     * @default undefined
     * @type {number[]|undefined}
     */
    extent: {
      type: Array,
      validator: value => value.length === 4,
    },
    zIndex: Number,
    minResolution: Number,
    maxResolution: Number,
    minZoom: Number,
    maxZoom: Number,
    overlay: {
      type: Boolean,
      default: false,
    },
    render: Function,
  },
  watch: {
    id (value) {
      this.setId(value)
    },
    opacity (value) {
      this.setOpacity(value)
    },
    visible (value) {
      this.setVisible(value)
    },
    extent (value) {
      this.setExtent(value)
    },
    zIndex (value) {
      this.setZIndex(value)
    },
    minResolution (value) {
      this.setMinResolution(value)
    },
    maxResolution (value) {
      this.setMaxResolution(value)
    },
    minZoom (value) {
      this.setMinZoom(value)
    },
    maxZoom (value) {
      this.setMaxZoom(value)
    },
    ...makeWatchers([
      'overlay',
      'render',
    ], () => function () {
      this.scheduleRecreate()
    }),
  },
  created () {
    this::defineServices()
  },
  methods: {
    /**
     * @return {Promise<module:ol/layer/Base~BaseLayer>}
     * @protected
     */
    async createOlObject () {
      const layer = await this.createLayer()

      initializeLayer(layer, this.id)

      return layer
    },
    /**
     * @return {module:ol/layer/Base~BaseLayer|Promise<module:ol/layer/Base~BaseLayer>}
     * @protected
     * @abstract
     */
    createLayer () {
      throw new Error('Not implemented method')
    },
    /**
     * @returns {Promise<string|number>}
     */
    async getId () {
      return getLayerId(await this.resolveLayer())
    },
    /**
     * @param {string|number} id
     * @returns {Promise<void>}
     */
    async setId (id) {
      const layer = await this.resolveLayer()

      if (id === getLayerId(layer)) return

      setLayerId(layer, id)
    },
    /**
     * @returns {Promise<number[]>}
     */
    async getExtent () {
      return this.extentToDataProj((await this.resolveLayer()).getExtent())
    },
    /**
     * @param {number[]} extent
     * @returns {Promise<void>}
     */
    async setExtent (extent) {
      extent = this.extentToViewProj(extent)
      const layer = await this.resolveLayer()

      if (isEqual(extent, layer.getExtent())) return

      layer.setExtent(extent)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMaxResolution () {
      return (await this.resolveLayer()).getMaxResolution()
    },
    /**
     * @param {number} resolution
     * @returns {Promise<void>}
     */
    async setMaxResolution (resolution) {
      const layer = await this.resolveLayer()

      if (resolution === layer.getMaxResolution()) return

      layer.setMaxResolution(resolution)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMinResolution () {
      return (await this.resolveLayer()).getMinResolution()
    },
    /**
     * @param {number} resolution
     * @returns {Promise<void>}
     */
    async setMinResolution (resolution) {
      const layer = await this.resolveLayer()

      if (resolution === layer.getMinResolution()) return

      layer.setMinResolution(resolution)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMaxZoom () {
      return (await this.resolveLayer()).getMaxZoom()
    },
    /**
     * @param {number} zoom
     * @returns {Promise<void>}
     */
    async setMaxZoom (zoom) {
      const layer = await this.resolveLayer()

      if (zoom === layer.getMaxZoom()) return

      layer.setMaxZoom(zoom)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMinZoom () {
      return (await this.resolveLayer()).getMinZoom()
    },
    /**
     * @param {number} zoom
     * @returns {Promise<void>}
     */
    async setMinZoom (zoom) {
      const layer = await this.resolveLayer()

      if (zoom === layer.getMinZoom()) return

      layer.setMinZoom(zoom)
    },
    /**
     * @returns {Promise<number>}
     */
    async getOpacity () {
      return (await this.resolveLayer()).getOpacity()
    },
    /**
     * @param {number} opacity
     * @returns {Promise<void>}
     */
    async setOpacity (opacity) {
      const layer = await this.resolveLayer()

      if (opacity === layer.getOpacity()) return

      layer.setOpacity(opacity)
    },
    /**
     * @returns {Promise<boolean>}
     */
    async getVisible () {
      return (await this.resolveLayer()).getVisible()
    },
    /**
     * @param {boolean} visible
     * @returns {Promise<void>}
     */
    async setVisible (visible) {
      const layer = await this.resolveLayer()

      if (visible === layer.getVisible()) return

      layer.setVisible(visible)
    },
    /**
     * @returns {Promise<number>}
     */
    async getZIndex () {
      return (await this.resolveLayer()).getZIndex()
    },
    /**
     * @param {number} zIndex
     * @returns {Promise<void>}
     */
    async setZIndex (zIndex) {
      const layer = await this.resolveLayer()

      if (zIndex === layer.getZIndex()) return

      layer.setZIndex(zIndex)
    },
    /**
     * @param {module:ol/Map~Map|Vue|undefined} map
     * @return {Promise<void>}
     */
    async setMap (map) {
      if (map instanceof Vue) {
        map = await map.resolveMap()
      }

      (await this.resolveLayer()).setMap(map)
    },
    /**
     * @param {number[]} pixel
     * @return {boolean}
     */
    async isAtPixel (pixel) {
      const layer = await this.resolveLayer()

      return this.$mapVm.forEachLayerAtPixel(pixel, mapLayer => mapLayer === layer)
    },
    /**
     * @returns {Object}
     * @protected
     */
    getServices () {
      const vm = this

      return mergeDescriptors(
        this::cmp.methods.getServices(),
        this::sourceContainer.methods.getServices(),
        {
          get layer () { return vm.$layer },
          get layerVm () { return vm },
        },
      )
    },
    /**
     * @return {Promise<void>}
     * @protected
     */
    async mount () {
      if (this.overlay && this.$map) {
        await this.setMap(this.$map)
      } else if (this.$layersContainer) {
        await this.$layersContainer.addLayer(this)
      }

      return this::cmp.methods.mount()
    },
    /**
     * @return {Promise<void>}
     * @protected
     */
    async unmount () {
      if (this.overlay) {
        await this.setMap(null)
      } else if (this.$layersContainer) {
        await this.$layersContainer.removeLayer(this)
      }

      return this::cmp.methods.unmount()
    },
    /**
     * @return {Promise<void>}
     * @protected
     */
    subscribeAll () {
      return Promise.all([
        this::cmp.methods.subscribeAll(),
        this::subscribeToLayerEvents(),
      ])
    },
    resolveLayer: cmp.methods.resolveOlObject,
    getSourceTarget: cmp.methods.resolveOlObject,
    ...pick(cmp.methods, [
      'init',
      'deinit',
      'refresh',
      'recreate',
      'remount',
      'recreate',
    ]),
  },
}

function defineServices () {
  Object.defineProperties(this, {
    $layer: {
      enumerable: true,
      get: () => this.$olObject,
    },
    $mapVm: {
      enumerable: true,
      get: () => this.$services?.mapVm,
    },
    $view: {
      enumerable: true,
      get: () => this.$mapVm?.$view,
    },
    $layersContainer: {
      enumerable: true,
      get: () => this.$services?.layersContainer,
    },
  })
}

async function subscribeToLayerEvents () {
  const layer = await this.resolveLayer()

  const t = 1000 / 60
  const changes = mergeObs(
    obsFromOlChangeEvent(layer, 'extent', true, t, () => this.extentToDataProj(layer.getExtent())),
    obsFromOlChangeEvent(layer, [
      'opacity',
      'visible',
      'zIndex',
      'minResolution',
      'maxResolution',
      'minZoom',
      'maxZoom',
    ], true, t)
  )

  this.subscribeTo(changes, ({ prop, value }) => {
    ++this.rev

    this.$nextTick(() => {
      this.$emit(`update:${prop}`, value)
    })
  })

  const events = obsFromOlEvent(layer, [
    // todo review which events are actually exists in the current ol version
    'postcompose',
    'precompose',
    'render',
    'postrender',
    'prerender',
  ])

  this.subscribeTo(events, evt => {
    ++this.rev

    this.$nextTick(() => {
      this.$emit(evt.type, evt)
    })
  })
}
<commit_msg>Fix layer events list<commit_after>import Vue from 'vue'
import { merge as mergeObs } from 'rxjs/observable'
import { getLayerId, initializeLayer, setLayerId } from '../ol-ext'
import { obsFromOlEvent, obsFromOlChangeEvent } from '../rx-ext'
import { isEqual, pick } from '../util/minilo'
import mergeDescriptors from '../util/multi-merge-descriptors'
import { makeWatchers } from '../util/vue-helpers'
import cmp from './ol-cmp'
import sourceContainer from './source-container'
import useMapCmp from './use-map-cmp'
import stubVNode from './stub-vnode'

export default {
  mixins: [cmp, stubVNode, useMapCmp, sourceContainer],
  stubVNode: {
    attrs () {
      return {
        id: this.vmId,
        class: this.vmClass,
      }
    },
  },
  props: {
    className: String,
    opacity: {
      type: Number,
      default: 1,
    },
    visible: {
      type: Boolean,
      default: true,
    },
    /**
     * The bounding extent for layer rendering defined in the map view projection.
     * The layer will not be rendered outside of this extent.
     * @default undefined
     * @type {number[]|undefined}
     */
    extent: {
      type: Array,
      validator: value => value.length === 4,
    },
    zIndex: Number,
    minResolution: Number,
    maxResolution: Number,
    minZoom: Number,
    maxZoom: Number,
    overlay: {
      type: Boolean,
      default: false,
    },
    render: Function,
  },
  watch: {
    id (value) {
      this.setId(value)
    },
    opacity (value) {
      this.setOpacity(value)
    },
    visible (value) {
      this.setVisible(value)
    },
    extent (value) {
      this.setExtent(value)
    },
    zIndex (value) {
      this.setZIndex(value)
    },
    minResolution (value) {
      this.setMinResolution(value)
    },
    maxResolution (value) {
      this.setMaxResolution(value)
    },
    minZoom (value) {
      this.setMinZoom(value)
    },
    maxZoom (value) {
      this.setMaxZoom(value)
    },
    ...makeWatchers([
      'overlay',
      'render',
    ], () => function () {
      this.scheduleRecreate()
    }),
  },
  created () {
    this::defineServices()
  },
  methods: {
    /**
     * @return {Promise<module:ol/layer/Base~BaseLayer>}
     * @protected
     */
    async createOlObject () {
      const layer = await this.createLayer()

      initializeLayer(layer, this.id)

      return layer
    },
    /**
     * @return {module:ol/layer/Base~BaseLayer|Promise<module:ol/layer/Base~BaseLayer>}
     * @protected
     * @abstract
     */
    createLayer () {
      throw new Error('Not implemented method')
    },
    /**
     * @returns {Promise<string|number>}
     */
    async getId () {
      return getLayerId(await this.resolveLayer())
    },
    /**
     * @param {string|number} id
     * @returns {Promise<void>}
     */
    async setId (id) {
      const layer = await this.resolveLayer()

      if (id === getLayerId(layer)) return

      setLayerId(layer, id)
    },
    /**
     * @returns {Promise<number[]>}
     */
    async getExtent () {
      return this.extentToDataProj((await this.resolveLayer()).getExtent())
    },
    /**
     * @param {number[]} extent
     * @returns {Promise<void>}
     */
    async setExtent (extent) {
      extent = this.extentToViewProj(extent)
      const layer = await this.resolveLayer()

      if (isEqual(extent, layer.getExtent())) return

      layer.setExtent(extent)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMaxResolution () {
      return (await this.resolveLayer()).getMaxResolution()
    },
    /**
     * @param {number} resolution
     * @returns {Promise<void>}
     */
    async setMaxResolution (resolution) {
      const layer = await this.resolveLayer()

      if (resolution === layer.getMaxResolution()) return

      layer.setMaxResolution(resolution)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMinResolution () {
      return (await this.resolveLayer()).getMinResolution()
    },
    /**
     * @param {number} resolution
     * @returns {Promise<void>}
     */
    async setMinResolution (resolution) {
      const layer = await this.resolveLayer()

      if (resolution === layer.getMinResolution()) return

      layer.setMinResolution(resolution)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMaxZoom () {
      return (await this.resolveLayer()).getMaxZoom()
    },
    /**
     * @param {number} zoom
     * @returns {Promise<void>}
     */
    async setMaxZoom (zoom) {
      const layer = await this.resolveLayer()

      if (zoom === layer.getMaxZoom()) return

      layer.setMaxZoom(zoom)
    },
    /**
     * @returns {Promise<number>}
     */
    async getMinZoom () {
      return (await this.resolveLayer()).getMinZoom()
    },
    /**
     * @param {number} zoom
     * @returns {Promise<void>}
     */
    async setMinZoom (zoom) {
      const layer = await this.resolveLayer()

      if (zoom === layer.getMinZoom()) return

      layer.setMinZoom(zoom)
    },
    /**
     * @returns {Promise<number>}
     */
    async getOpacity () {
      return (await this.resolveLayer()).getOpacity()
    },
    /**
     * @param {number} opacity
     * @returns {Promise<void>}
     */
    async setOpacity (opacity) {
      const layer = await this.resolveLayer()

      if (opacity === layer.getOpacity()) return

      layer.setOpacity(opacity)
    },
    /**
     * @returns {Promise<boolean>}
     */
    async getVisible () {
      return (await this.resolveLayer()).getVisible()
    },
    /**
     * @param {boolean} visible
     * @returns {Promise<void>}
     */
    async setVisible (visible) {
      const layer = await this.resolveLayer()

      if (visible === layer.getVisible()) return

      layer.setVisible(visible)
    },
    /**
     * @returns {Promise<number>}
     */
    async getZIndex () {
      return (await this.resolveLayer()).getZIndex()
    },
    /**
     * @param {number} zIndex
     * @returns {Promise<void>}
     */
    async setZIndex (zIndex) {
      const layer = await this.resolveLayer()

      if (zIndex === layer.getZIndex()) return

      layer.setZIndex(zIndex)
    },
    /**
     * @param {module:ol/Map~Map|Vue|undefined} map
     * @return {Promise<void>}
     */
    async setMap (map) {
      if (map instanceof Vue) {
        map = await map.resolveMap()
      }

      (await this.resolveLayer()).setMap(map)
    },
    /**
     * @param {number[]} pixel
     * @return {boolean}
     */
    async isAtPixel (pixel) {
      const layer = await this.resolveLayer()

      return this.$mapVm.forEachLayerAtPixel(pixel, mapLayer => mapLayer === layer)
    },
    /**
     * @returns {Object}
     * @protected
     */
    getServices () {
      const vm = this

      return mergeDescriptors(
        this::cmp.methods.getServices(),
        this::sourceContainer.methods.getServices(),
        {
          get layer () { return vm.$layer },
          get layerVm () { return vm },
        },
      )
    },
    /**
     * @return {Promise<void>}
     * @protected
     */
    async mount () {
      if (this.overlay && this.$map) {
        await this.setMap(this.$map)
      } else if (this.$layersContainer) {
        await this.$layersContainer.addLayer(this)
      }

      return this::cmp.methods.mount()
    },
    /**
     * @return {Promise<void>}
     * @protected
     */
    async unmount () {
      if (this.overlay) {
        await this.setMap(null)
      } else if (this.$layersContainer) {
        await this.$layersContainer.removeLayer(this)
      }

      return this::cmp.methods.unmount()
    },
    /**
     * @return {Promise<void>}
     * @protected
     */
    subscribeAll () {
      return Promise.all([
        this::cmp.methods.subscribeAll(),
        this::subscribeToLayerEvents(),
      ])
    },
    resolveLayer: cmp.methods.resolveOlObject,
    getSourceTarget: cmp.methods.resolveOlObject,
    ...pick(cmp.methods, [
      'init',
      'deinit',
      'refresh',
      'recreate',
      'remount',
      'recreate',
    ]),
  },
}

function defineServices () {
  Object.defineProperties(this, {
    $layer: {
      enumerable: true,
      get: () => this.$olObject,
    },
    $mapVm: {
      enumerable: true,
      get: () => this.$services?.mapVm,
    },
    $view: {
      enumerable: true,
      get: () => this.$mapVm?.$view,
    },
    $layersContainer: {
      enumerable: true,
      get: () => this.$services?.layersContainer,
    },
  })
}

async function subscribeToLayerEvents () {
  const layer = await this.resolveLayer()

  const t = 1000 / 60
  const changes = mergeObs(
    obsFromOlChangeEvent(layer, 'extent', true, t, () => this.extentToDataProj(layer.getExtent())),
    obsFromOlChangeEvent(layer, [
      'opacity',
      'visible',
      'zIndex',
      'minResolution',
      'maxResolution',
      'minZoom',
      'maxZoom',
    ], true, t)
  )

  this.subscribeTo(changes, ({ prop, value }) => {
    ++this.rev

    this.$nextTick(() => {
      this.$emit(`update:${prop}`, value)
    })
  })

  const events = obsFromOlEvent(layer, [
    'postrender',
    'prerender',
  ])

  this.subscribeTo(events, evt => {
    ++this.rev

    this.$nextTick(() => {
      this.$emit(evt.type, evt)
    })
  })
}
<|endoftext|>


<commit_before>/*

  OQ:
    * is timeline.kill() the best way to gracefully end heartbeat animation on escape?

    * is enter/exit for this mode intuitive? shouldnt it be three?
      1. enter   2. cancel  3.  complete?

    * flow of this logic is awkward. Need to break into peices and refactor.
      Current UX flow:
      - person hovers over a source bit (the beginning part of the connection/"parallel")
      - person presses Shift key to select hovered bit. Doing this enters Parallel Create mode
      - person hovers over destination bit
      - person presses Shift key, while hovering the destination bit, to choose it
      - form is presented, prompting person for a relationship name, like tagging
      - Enter key submits it, saves the connection, exiting Parallel Create mode, and returns person to the canvas "home"

 */

// TODO: to move these into mode, as private object properties?

// for Greensock heartbeat animation
var timeline, $originBit;

// two.js vars, for parallel line drawing
var line, updatedLine, lineContainer, params, two, mouse, circle;

 // for sparks animation, on bit corners
var spark;

var canvasElement;
var isSlicingDicing = false;
var rafHandle;

var $destBit;

Parallels.AppModes['create-parallel'] = {

  enter: function () {
    log.debug("mode:create-parallel:enter");

    Session.set('currentMode', 'create-parallel');

    var originBitId = Session.get('bitHoveringId');
    Session.set('originBitId', originBitId);
    $originBit = Utilities.getBitElement(originBitId);
    var originBitData = Blaze.getData($originBit[0]);
    var originBitCenterX = originBitData.position.x + ($originBit[0].clientWidth / 2)
    var originBitCenterY = originBitData.position.y + ($originBit[0].clientHeight / 2)

    Parallels.KeyCommands.disableAll();
    Parallels.KeyCommands.bindEsc();

    // re-binding Shift, so if person hits it again,
    // we know they are have chosen a destination bit
    // and ready to commit this to the db/UI.
    Mousetrap.bind('shift', function (){
      log.debug("pressed 'Shift' key: looking to close parallel.");

      // get latest value of bit person is hovering over as destination bit.
      // this is expected to happen now that the origin has already been chosen
      destBitId = Session.get('bitHoveringId');

      if (destBitId && (destBitId != originBitId)) {

        $destBit = Utilities.getBitElement(destBitId);

        // stop heartbeat animation
        // TODO: after one cycle is done. reset to original size
        timeline.kill();

        $(window).off('mousemove');
        // stop drawing parallel line
        two.unbind('update');

        Session.set('destBitId', destBitId);
        log.debug(
          'closing parallel: source:',
          originBitId,
          " -> dest:",
          destBitId
        );

        $('.create-parallel--line').remove();
        $originBit.removeClass('create-parallel--origin');

        /*  Play 4 corner spark animation, with sounds

            DOCS:
            /private/docs/parallel-create-spark-animation-v1.png
        */

        var destBitRect = $destBit[0].getClientRects()[0];
        var corners = [

          { x: destBitRect.top,     y: destBitRect.left,  freq: teoria.note('g2').fq() },
          { x: destBitRect.top,     y: destBitRect.right, freq: teoria.note('d2').fq() },
          { x: destBitRect.bottom,  y: destBitRect.left,  freq: teoria.note('c2').fq() },
          { x: destBitRect.bottom,  y: destBitRect.right, freq: teoria.note('a2').fq() }
        ];

        // TODO: set up Meteor.settings vars for map dimensions instead of hardcoding
        // TODO: convert to RAF
        var particles = document.createElement('div');
        $(particles)
          .attr( "id", "create-parallel--particles")
          .height( 5000 )
          .width( 5000)
          .prependTo(".map");

        var setupAndPlayCornerSpark = function(corner){
          log.debug("setupAndPlayCornerSpark: ", corner);

          var spark = new particleEmitter({
            onStartCallback: function(){
              log.debug("starting particleEmitter for corner: ", corner);
            },
            onStopCallback: function(){
              log.debug("ending particleEmitter for corner: ", corner);
            },
            container: '#create-parallel--particles',
            image: 'images/ui/particle.gif',
            center: [ corner.y, corner.x ],
            size: 4,
            velocity: 450,
            decay: 500,
            rate: 600
          });

          spark.start()
          var handle = Meteor.setTimeout(function(){
            spark.stop();
          },
          100);
        }

        var tl = new TimelineMax({ paused:true });

        _.each(corners, function(corner, i) {
          var offsetDelay = i * 0.10;
          // log.debug("count:", i, ":", corner, ", delayed: ", offsetDelay);
          tl.call(setupAndPlayCornerSpark, [ corner ], this, offsetDelay );
        });

        tl.play();

        // TODO: show form

        // TODO: on form submit, save connection to neo4j

        // TODO: call Exit mode

        // prep slicer
        // TODO: move this to on bitOnHover during selectDest mode
        if (!isSlicingDicing){

          isSlicingDicing = true;

          // set the canvas when pixi stage will sit in
          // the same size as the thumbnail image

          // we already calculated the bounding rectangle earlier,
          // when we ran the spark animation

          // lets reuse the coords to calc our bit dimensions
          var bitWidth = destBitRect.right - destBitRect.left;
          var bitHeight = destBitRect.bottom - destBitRect.top;

          // create the canvas element, position it, z-index
          canvasElement = document.createElement('canvas');
          canvasElement.id = "create-parallel--remix-slices";
          canvasElement.height = bitHeight;
          canvasElement.width = bitWidth;

          $(canvasElement)
            .prependTo(".map")
            .css({
              left: destBitRect.left, // TODO: replace with transform positioning
              top: destBitRect.top,
              position: 'absolute',
              zIndex: 10
            });

          var ctx = canvasElement.getContext('2d');
          ctx.width = bitWidth;
          ctx.height = bitHeight;

          var frame = document.createElement('canvas');
          frame.width = bitWidth;
          frame.height = bitHeight;

          var frameCtx = frame.getContext('2d');
          frameCtx.drawImage(
            $destBit.find('img')[0],
            0,
            0,
            bitHeight,
            bitWidth
          );
          // img.crossOrigin = '';

          // TODO: query Neo4j for image bits that are connected, by X number of hops
          // also, spatially: K-nearest algo?
          // http://burakkanber.com/blog/machine-learning-in-js-k-nearest-neighbor-part-1/

          var sw = 32;
          sliceAndDice();

          function sliceAndDice() {
            // adapted from https://stackoverflow.com/questions/27208715/webgl-animated-texture-performance-versus-canvas-drawimage-performance
            // cancel when the rollage is done
            // if(){
            //   isSlicingDicing = false;
            //   log.debug("canvas slice+dice: saving rafHandle:", rafHandle);
            // }

            // some misc slicing
            for(var x = 0; x < frame.width; x += sw) {

                var y = Math.sin(x*1.5) * sw + 20;
                ctx.drawImage(frame, x, 0, sw, frame.height,          // source slice
                               x * 1.1, y, sw, frame.height);   // dest. slice
            }

            // we only need to save the very first handle, that we'll use later to stop the RAF
            if (!rafHandle) {
              rafHandle = requestAnimationFrame(sliceAndDice);
            // TODO: save instance tilities.getMapTemplate()
              log.debug("canvas slice+dice: saving rafHandle:", rafHandle);
            }

            else{
              requestAnimationFrame(sliceAndDice);
            }
          }

          // TODO: replace with transform positioning

          // TODO: set z-index, to move the canvas over on top of the DOM version
          $destBit.hide();

        }

        else {
          log.debug("canvas slice+dice: exists");
        };

        // show form so person can define relationship
        // -- bind escape rids form, create-parallel mode
        // -- Later: autocomplete from collection of parallel types

        // play yay sound, connection stamp/re-enforcing/"hardening" animation
        // not sure what this means yet

        // Session.set('isDrawingParallel', null);
        // Session.set('originBitId', null);

      }

    });

    log.debug("ready for creating parallel. starting at bit: " + originBitId);

    // TODO: abstract out into reusable mode concept.
    // here, it might be : enterMode.createParallel()
    $(".map").addClass('mode--create-parallel'); // visually demonstrate we're in connecting mode

    // TODO: animate
    $originBit.addClass('create-parallel--origin');

    // TODO:
    // try bg overlay over other bits too?

    // ****************** RENDER LINE *************
    // set up an SVG container via two.js container to draw the line stroke
    lineContainer = document.createElement('div');
    $(lineContainer)
      .addClass("create-parallel--line")
      .height( 5000 )
      .width( 5000)
      .prependTo(".map");

    params = {
      fullscreen: true,
      autostart: true
    };

    two = new Two(params).appendTo(lineContainer);
    mouse = new Two.Vector();

    // OQ: should we use Meteor binding?
    // https://stackoverflow.com/questions/21486667/meteor-js-how-should-i-bind-events-to-the-window-in-meteor
    $(window).on('mousemove', function(event){
      mouse.x = verge.scrollX() + event.clientX;
      mouse.y = verge.scrollY() + event.clientY
    });

    circle = two.makeCircle(
      originBitCenterX,
      originBitCenterY,
      2 // width
    );

    circle.noStroke().fill = 'blue';

    // By passing the circle.translation into the origin
    // two.js automatically data binds the line (via Backbone
    // underneath it's hood). Whenever the data properties for
    // circle updates in .bind below, the line destination data
    // (as an anchor/vector), updates too
    // https://github.com/jonobr1/two.js/issues/133
    var line = new Two.Polygon([
            circle.translation, // origin
            new Two.Vector(originBitCenterX, originBitCenterY)
        ]);

    line.noFill().stroke = 'yellow';
    line.linewidth = 7;

    two
      .add(line)
      .bind('update', function(frameCount) {
        circle.translation.set(mouse.x, mouse.y);

        // TODO: the longer the distance, the thinner the linewidth

        // TODO: depending on direction on direction parallel is pointing,
        // addClass(left);

        // TODO: stretch sound, bind to flocking

      });
    // ****************** RENDER LINE *************

    // ****************** HEARTBEAT ANIMATION *************
    var timelineStart = function () {
      log.debug('bit:parallel:create. Origin bit' + originBitId + ': selected-loop animation starting ...');
      // TODO: play sound indicating origin start, jeopardy jingle??
    };

    var timelineDone = function( bitOriginId ){
      log.debug('bit:parallel:create. End mode, origin bit' + bitOriginId + ': selected-loop animation ending.');
    };

    timeline = new TimelineMax({
      onStart: timelineStart,
      onComplete: timelineDone,
      onCompleteParams:[ originBitId ],
      repeat: -1
    });

    timeline
      .to($originBit, 0.50, { scale: 1.05, ease:Expo.easeOut } )
      .to($originBit, 0.5, { scale: 1, ease:Expo.easeOut } );
    // ****************** HEARTBEAT ANIMATION *************

  },

  exit: function () {
    log.debug("mode:create-parallel:exit");

    if (Session.get('currentMode')) {

      Session.set('currentMode', null);
      Session.set('originBitId', null);
      Session.set('destBitId', null);

      $(".map").removeClass('mode--create-parallel');
      $originBit.removeClass('create-parallel--origin');

      $('.create-parallel--line').remove();
      $('#create-parallel--particles').remove();

      // stop heartbeat animation
      timeline.kill();

      // stop drawing parallel line
      two.unbind('update');
      $(window).off('mousemove');

      // TODO: properly garbage collect these objects
      // reset vars for next create-parallel use
      lineContainer, params, two, mouse, updatedLine, line, circle = null;

      // TODO: move handle to mapInstance
      // if (Utilities.getMapTemplate().pixiInstance.rafHandle){
      if (rafHandle){
        // log.debug("about to cancelAnimationFrame on Utilities.getMapTemplate().pixiInstance.rafHandle", rafHandle);
        log.debug("about to cancelAnimationFrame on rafHandle:", rafHandle);

        // cancelAnimationFrame(Utilities.getMapTemplate().pixiInstance.rafHandle);
        cancelAnimationFrame(rafHandle);
        isSlicingDicing = false;
      }

      // erase the canvas by setting re-setting it's width [to anything]
      $(canvasElement).width = 0;
      $(canvasElement).remove();
      if ($destBit) { $destBit.show() } ;

      // reenable scrolling
      $("body").css( "overflow", "visible");
      $("body").css( "position", "static");

      // put key commands back to normal
      Parallels.KeyCommands.bindAll();
    }
  }
};
<commit_msg>parallel:create- intitial animation of slices. removing spark debugging statements<commit_after>/*

  OQ:
    * is timeline.kill() the best way to gracefully end heartbeat animation on escape?

    * is enter/exit for this mode intuitive? shouldnt it be three?
      1. enter   2. cancel  3.  complete?

    * flow of this logic is awkward. Need to break into peices and refactor.
      Current UX flow:
      - person hovers over a source bit (the beginning part of the connection/"parallel")
      - person presses Shift key to select hovered bit. Doing this enters Parallel Create mode
      - person hovers over destination bit
      - person presses Shift key, while hovering the destination bit, to choose it
      - form is presented, prompting person for a relationship name, like tagging
      - Enter key submits it, saves the connection, exiting Parallel Create mode, and returns person to the canvas "home"

 */

// TODO: to move these into mode, as private object properties?

// for Greensock heartbeat animation
var timeline, $originBit;

// two.js vars, for parallel line drawing
var line, updatedLine, lineContainer, params, two, mouse, circle;

 // for sparks animation, on bit corners
var spark;

var canvasElement;
var isSlicingDicing = false;
var rafHandle;

var $destBit;

Parallels.AppModes['create-parallel'] = {

  enter: function () {
    log.debug("mode:create-parallel:enter");

    Session.set('currentMode', 'create-parallel');

    var originBitId = Session.get('bitHoveringId');
    Session.set('originBitId', originBitId);
    $originBit = Utilities.getBitElement(originBitId);
    var originBitData = Blaze.getData($originBit[0]);
    var originBitCenterX = originBitData.position.x + ($originBit[0].clientWidth / 2)
    var originBitCenterY = originBitData.position.y + ($originBit[0].clientHeight / 2)

    Parallels.KeyCommands.disableAll();
    Parallels.KeyCommands.bindEsc();

    // re-binding Shift, so if person hits it again,
    // we know they are have chosen a destination bit
    // and ready to commit this to the db/UI.
    Mousetrap.bind('shift', function (){
      log.debug("pressed 'Shift' key: looking to close parallel.");

      // get latest value of bit person is hovering over as destination bit.
      // this is expected to happen now that the origin has already been chosen
      destBitId = Session.get('bitHoveringId');

      if (destBitId && (destBitId != originBitId)) {

        $destBit = Utilities.getBitElement(destBitId);

        // stop heartbeat animation
        // TODO: after one cycle is done. reset to original size
        timeline.kill();

        $(window).off('mousemove');
        // stop drawing parallel line
        two.unbind('update');

        Session.set('destBitId', destBitId);
        log.debug(
          'closing parallel: source:',
          originBitId,
          " -> dest:",
          destBitId
        );

        $('.create-parallel--line').remove();
        $originBit.removeClass('create-parallel--origin');

        /*  Play 4 corner spark animation, with sounds

            DOCS:
            /private/docs/parallel-create-spark-animation-v1.png
        */

        var destBitRect = $destBit[0].getClientRects()[0];
        var corners = [

          { x: destBitRect.top,     y: destBitRect.left,  freq: teoria.note('g2').fq() },
          { x: destBitRect.top,     y: destBitRect.right, freq: teoria.note('d2').fq() },
          { x: destBitRect.bottom,  y: destBitRect.left,  freq: teoria.note('c2').fq() },
          { x: destBitRect.bottom,  y: destBitRect.right, freq: teoria.note('a2').fq() }
        ];

        // TODO: set up Meteor.settings vars for map dimensions instead of hardcoding
        // TODO: convert to RAF
        var particles = document.createElement('div');
        $(particles)
          .attr( "id", "create-parallel--particles")
          .height( 5000 )
          .width( 5000)
          .prependTo(".map");

        var setupAndPlayCornerSpark = function(corner){
          // log.debug("setupAndPlayCornerSpark: ", corner);

          var spark = new particleEmitter({
            onStartCallback: function(){
              // log.debug("starting particleEmitter for corner: ", corner);
            },
            onStopCallback: function(){
              // log.debug("ending particleEmitter for corner: ", corner);
            },
            container: '#create-parallel--particles',
            image: 'images/ui/particle.gif',
            center: [ corner.y, corner.x ],
            size: 4,
            velocity: 450,
            decay: 500,
            rate: 600
          });

          spark.start()
          var handle = Meteor.setTimeout(function(){
            spark.stop();
          },
          100);
        }

        var tl = new TimelineMax({ paused:true });

        _.each(corners, function(corner, i) {
          var offsetDelay = i * 0.10;
          // log.debug("count:", i, ":", corner, ", delayed: ", offsetDelay);
          tl.call(setupAndPlayCornerSpark, [ corner ], this, offsetDelay );
        });

        tl.play();

        // TODO: show form

        // TODO: on form submit, save connection to neo4j

        // TODO: call Exit mode

        // prep slicer
        // TODO: move this to on bitOnHover during selectDest mode
        if (!isSlicingDicing){

          isSlicingDicing = true;

          // set the canvas when pixi stage will sit in
          // the same size as the thumbnail image

          // we already calculated the bounding rectangle earlier,
          // when we ran the spark animation

          // lets reuse the coords to calc our bit dimensions
          var bitWidth = destBitRect.right - destBitRect.left;
          var bitHeight = destBitRect.bottom - destBitRect.top;

          // create the canvas element, position it, z-index
          canvasElement = document.createElement('canvas');
          canvasElement.id = "create-parallel--remix-slices";
          canvasElement.height = bitHeight;
          canvasElement.width = bitWidth;

          $(canvasElement)
            .prependTo(".map")
            .css({
              left: destBitRect.left, // TODO: replace with transform positioning
              top: destBitRect.top,
              position: 'absolute',
              zIndex: 10
            });

          var ctx = canvasElement.getContext('2d');
          ctx.width = bitWidth;
          ctx.height = bitHeight;

          var frame = document.createElement('canvas');
          frame.width = bitWidth;
          frame.height = bitHeight;

          var frameCtx = frame.getContext('2d');
          frameCtx.drawImage(
            $destBit.find('img')[0],
            0,
            0,
            bitHeight,
            bitWidth
          );
          // img.crossOrigin = '';

          // TODO: query Neo4j for image bits that are connected, by X number of hops
          // also, spatially: K-nearest algo?
          // http://burakkanber.com/blog/machine-learning-in-js-k-nearest-neighbor-part-1/

          var sw = 32;
          var dlt = 0;

          sliceAndDice();

          function sliceAndDice() {
            // adapted from https://stackoverflow.com/questions/27208715/webgl-animated-texture-performance-versus-canvas-drawimage-performance
            // cancel when the rollage is done
            // if(){
            //   isSlicingDicing = false;
            //   log.debug("canvas slice+dice: saving rafHandle:", rafHandle);
            // }

            // some misc slicing
            for(var x = 0; x < frame.width; x += sw) {

                var y = Math.sin(x*1.5) * sw + 20;
                 y = Math.sin(x*32+dlt) * 3 + 10;               // "random" y pos.
                ctx.drawImage(frame, x, 0, sw, frame.height,    // source slice
                               x * 1.1, y, sw, frame.height);   // dest. slice
            }
            dlt += 0.2;

            // we only need to save the very first handle, that we'll use later to stop the RAF
            if (!rafHandle) {
              rafHandle = requestAnimationFrame(sliceAndDice);
            // TODO: save instance tilities.getMapTemplate()
              log.debug("canvas slice+dice: saving rafHandle:", rafHandle);
            }

            else{
              requestAnimationFrame(sliceAndDice);
            }
          }

          // TODO: replace with transform positioning

          // TODO: set z-index, to move the canvas over on top of the DOM version
          $destBit.hide();

        }

        else {
          log.debug("canvas slice+dice: exists");
        };

        // show form so person can define relationship
        // -- bind escape rids form, create-parallel mode
        // -- Later: autocomplete from collection of parallel types

        // play yay sound, connection stamp/re-enforcing/"hardening" animation
        // not sure what this means yet

        // Session.set('isDrawingParallel', null);
        // Session.set('originBitId', null);

      }

    });

    log.debug("ready for creating parallel. starting at bit: " + originBitId);

    // TODO: abstract out into reusable mode concept.
    // here, it might be : enterMode.createParallel()
    $(".map").addClass('mode--create-parallel'); // visually demonstrate we're in connecting mode

    // TODO: animate
    $originBit.addClass('create-parallel--origin');

    // TODO:
    // try bg overlay over other bits too?

    // ****************** RENDER LINE *************
    // set up an SVG container via two.js container to draw the line stroke
    lineContainer = document.createElement('div');
    $(lineContainer)
      .addClass("create-parallel--line")
      .height( 5000 )
      .width( 5000)
      .prependTo(".map");

    params = {
      fullscreen: true,
      autostart: true
    };

    two = new Two(params).appendTo(lineContainer);
    mouse = new Two.Vector();

    // OQ: should we use Meteor binding?
    // https://stackoverflow.com/questions/21486667/meteor-js-how-should-i-bind-events-to-the-window-in-meteor
    $(window).on('mousemove', function(event){
      mouse.x = verge.scrollX() + event.clientX;
      mouse.y = verge.scrollY() + event.clientY
    });

    circle = two.makeCircle(
      originBitCenterX,
      originBitCenterY,
      2 // width
    );

    circle.noStroke().fill = 'blue';

    // By passing the circle.translation into the origin
    // two.js automatically data binds the line (via Backbone
    // underneath it's hood). Whenever the data properties for
    // circle updates in .bind below, the line destination data
    // (as an anchor/vector), updates too
    // https://github.com/jonobr1/two.js/issues/133
    var line = new Two.Polygon([
            circle.translation, // origin
            new Two.Vector(originBitCenterX, originBitCenterY)
        ]);

    line.noFill().stroke = 'yellow';
    line.linewidth = 7;

    two
      .add(line)
      .bind('update', function(frameCount) {
        circle.translation.set(mouse.x, mouse.y);

        // TODO: the longer the distance, the thinner the linewidth

        // TODO: depending on direction on direction parallel is pointing,
        // addClass(left);

        // TODO: stretch sound, bind to flocking

      });
    // ****************** RENDER LINE *************

    // ****************** HEARTBEAT ANIMATION *************
    var timelineStart = function () {
      log.debug('bit:parallel:create. Origin bit' + originBitId + ': selected-loop animation starting ...');
      // TODO: play sound indicating origin start, jeopardy jingle??
    };

    var timelineDone = function( bitOriginId ){
      log.debug('bit:parallel:create. End mode, origin bit' + bitOriginId + ': selected-loop animation ending.');
    };

    timeline = new TimelineMax({
      onStart: timelineStart,
      onComplete: timelineDone,
      onCompleteParams:[ originBitId ],
      repeat: -1
    });

    timeline
      .to($originBit, 0.50, { scale: 1.05, ease:Expo.easeOut } )
      .to($originBit, 0.5, { scale: 1, ease:Expo.easeOut } );
    // ****************** HEARTBEAT ANIMATION *************

  },

  exit: function () {
    log.debug("mode:create-parallel:exit");

    if (Session.get('currentMode')) {

      Session.set('currentMode', null);
      Session.set('originBitId', null);
      Session.set('destBitId', null);

      $(".map").removeClass('mode--create-parallel');
      $originBit.removeClass('create-parallel--origin');

      $('.create-parallel--line').remove();
      $('#create-parallel--particles').remove();

      // stop heartbeat animation
      timeline.kill();

      // stop drawing parallel line
      two.unbind('update');
      $(window).off('mousemove');

      // TODO: properly garbage collect these objects
      // reset vars for next create-parallel use
      lineContainer, params, two, mouse, updatedLine, line, circle = null;

      // TODO: move handle to mapInstance
      // if (Utilities.getMapTemplate().pixiInstance.rafHandle){
      if (rafHandle){
        // log.debug("about to cancelAnimationFrame on Utilities.getMapTemplate().pixiInstance.rafHandle", rafHandle);
        log.debug("about to cancelAnimationFrame on rafHandle:", rafHandle);

        // cancelAnimationFrame(Utilities.getMapTemplate().pixiInstance.rafHandle);
        cancelAnimationFrame(rafHandle);
        isSlicingDicing = false;
      }

      // erase the canvas by setting re-setting it's width [to anything]
      $(canvasElement).width = 0;
      $(canvasElement).remove();
      if ($destBit) { $destBit.show() } ;

      // reenable scrolling
      $("body").css( "overflow", "visible");
      $("body").css( "position", "static");

      // put key commands back to normal
      Parallels.KeyCommands.bindAll();
    }
  }
};
<|endoftext|>