<commit_before>/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * 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
 *
 *      http://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.
 */
'use strict';

FriendlyEats.prototype.initTemplates = function() {
  this.templates = {};
  document.querySelectorAll('.template').forEach(el => {
    this.templates[el.getAttribute('id')] = el;
  });
};

FriendlyEats.prototype.viewHome = function() {
  this.getAllRestaurants();
};

FriendlyEats.prototype.viewList = function(filters, filter_description) {
  if (!filter_description) {
    filter_description = 'any type of food with any price in any city.';
  }

  const mainEl = this.renderTemplate('main-adjusted');
  const headerEl = this.renderTemplate('header-base', {
    hasSectionHeader: true
  });

  this.replaceElement(
      headerEl.querySelector('#section-header'),
      this.renderTemplate('filter-display', { filter_description })
  );

  this.replaceElement(document.querySelector('.header'), headerEl);
  this.replaceElement(document.querySelector('main'), mainEl);

  headerEl.querySelector('#show-filters').addEventListener('click', () => {
    this.dialogs.filter.show();
  });

  const renderResults = doc => {
    if (!doc) {
      const headerEl = this.renderTemplate('header-base', {
        hasSectionHeader: true
      });

      const noResultsEl = this.renderTemplate('no-results');

      this.replaceElement(
          headerEl.querySelector('#section-header'),
          this.renderTemplate('filter-display', { filter_description })
      );

      headerEl.querySelector('#show-filters').addEventListener('click', () => {
        this.dialogs.filter.show();
      });

      this.replaceElement(document.querySelector('.header'), headerEl);
      this.replaceElement(document.querySelector('main'), noResultsEl);
      return;
    }
    const data = doc.data();
    data['.id'] = doc.id;
    data['go_to_restaurant'] = () => {
      this.router.navigate(`/restaurants/${doc.id}`);
    };

    const el = this.renderTemplate('restaurant-card', data);
    el.querySelector('.rating').append(this.renderRating(data.avgRating));
    el.querySelector('.price').append(this.renderPrice(data.price));

    mainEl.querySelector('#cards').append(el);
  };

  if (filters.city || filters.category || filters.price || filters.sort !== 'Rating' ) {
    this.getFilteredRestaurants({
     city: filters.city || 'Any',
     category: filters.category || 'Any',
     price: filters.price || 'Any',
     sort: filters.sort
    }, renderResults);
  } else {
    this.getAllRestaurants(renderResults);
  }

  const toolbar = mdc.toolbar.MDCToolbar.attachTo(document.querySelector('.mdc-toolbar'));
  toolbar.fixedAdjustElement = document.querySelector('.mdc-toolbar-fixed-adjust');

  mdc.autoInit();
};

FriendlyEats.prototype.viewSetup = function() {
  const headerEl = this.renderTemplate('header-base', {
    hasSectionHeader: false
  });

  const config = this.getFirebaseConfig();
  const noRestaurantsEl = this.renderTemplate('no-restaurants', config);

  const button = noRestaurantsEl.querySelector('#add_mock_data');
  let addingMockData = false;

  button.addEventListener('click', () => {
    if (addingMockData) return;
    addingMockData = true;

    this.style.opacity = '0.4';
    this.innerText = 'Please wait...';

    this.addMockRestaurants().then(() => {
      this.rerender();
    });
  });

  this.replaceElement(document.querySelector('.header'), headerEl);
  this.replaceElement(document.querySelector('main'), noRestaurantsEl);

  firebase
  .firestore()
  .collection('restaurants')
  .limit(1)
  .onSnapshot(snapshot => {
    if (snapshot.size && !addingMockData) {
      this.router.navigate('/');
    }
  });
};

FriendlyEats.prototype.initReviewDialog = function() {
  const dialog = document.querySelector('#dialog-add-review');
  this.dialogs.add_review = new mdc.dialog.MDCDialog(dialog);

  this.dialogs.add_review.listen('MDCDialog:accept', () => {
    let pathname = this.getCleanPath(document.location.pathname);
    let id = pathname.split('/')[2];

    this.addRating(id, {
      rating,
      text: dialog.querySelector('#text').value,
      userName: 'Anonymous (Web)',
      timestamp: new Date(),
      userId: firebase.auth().currentUser.uid
    }).then(() => {
      this.rerender();
    });
  });

  let rating = 0;

  dialog.querySelectorAll('.star-input i').forEach(el => {
    const rate = () => {
      let after = false;
      rating = 0;
      [].slice.call(el.parentNode.children).forEach(child => {
        if (!after) {
          rating++;
          child.innerText = 'star';
        } else {
          child.innerText = 'star_border';
        }
        after = after || child.isSameNode(el);
      });
    };
    el.addEventListener('mouseover', rate);
  });
};

FriendlyEats.prototype.initFilterDialog = function() {
  // TODO: Reset filter dialog to init state on close.
  this.dialogs.filter = new mdc.dialog.MDCDialog(document.querySelector('#dialog-filter-all'));

  this.dialogs.filter.listen('MDCDialog:accept', () => {
    this.updateQuery(this.filters);
  });

  const dialog = document.querySelector('aside');
  const pages = dialog.querySelectorAll('.page');

  this.replaceElement(
    dialog.querySelector('#category-list'),
      this.renderTemplate('item-list', { items: ['Any'].concat(this.data.categories) })
  );

  this.replaceElement(
      dialog.querySelector('#city-list'),
      this.renderTemplate('item-list', { items: ['Any'].concat(this.data.cities) })
  );

  const renderAllList = () => {
    this.replaceElement(
        dialog.querySelector('#all-filters-list'),
        this.renderTemplate('all-filters-list', this.filters)
    );

    dialog.querySelectorAll('#page-all .mdc-list-item').forEach(el => {
      el.addEventListener('click', () => {
        const id = el.id.split('-').slice(1).join('-');
        displaySection(id);
      });
    });
  };

  const displaySection = id => {
    if (id === 'page-all') {
      renderAllList();
    }

    pages.forEach(sel => {
      if (sel.id === id) {
        sel.style.display = 'block';
      } else {
        sel.style.display = 'none';
      }
    });
  };

  pages.forEach(sel => {
    const type = sel.id.split('-')[1];
    if (type === 'all') return;

    sel.querySelectorAll('.mdc-list-item').forEach(el => {
      el.addEventListener('click', () => {
        this.filters[type] = el.innerText.trim() === 'Any'? '' : el.innerText.trim();
        displaySection('page-all');
      });
    });
  });

  displaySection('page-all');
  dialog.querySelectorAll('.back').forEach(el => {
    el.addEventListener('click', () => {
      displaySection('page-all');
    });
  });
};

FriendlyEats.prototype.updateQuery = function(filters) {
  let query_description = '';

  if (filters.category !== '') {
    query_description += `${filters.category} places`;
  } else {
    query_description += 'any restaurant';
  }

  if (filters.city !== '') {
    query_description += ` in ${filters.city}`;
  } else {
    query_description += ' located anywhere';
  }

  if (filters.price !== '') {
    query_description += ` with a price of ${filters.price}`;
  } else {
    query_description += ' with any price';
  }

  if (filters.sort === 'Rating') {
    query_description += ' sorted by rating';
  } else if (filters.sort === 'Reviews') {
    query_description += ' sorted by # of reviews';
  }

  this.viewList(filters, query_description);
};

FriendlyEats.prototype.viewRestaurant = function(id) {
  let sectionHeaderEl;
  return this.getRestaurant(id)
    .then(doc => {
      const data = doc.data();
      const dialog =  this.dialogs.add_review;

      data.show_add_review = () => {
        dialog.show();
      };

      sectionHeaderEl = this.renderTemplate('restaurant-header', data);
      sectionHeaderEl
        .querySelector('.rating')
        .append(this.renderRating(data.avgRating));

      sectionHeaderEl
        .querySelector('.price')
        .append(this.renderPrice(data.price));
      return doc.ref.collection('ratings').orderBy('timestamp', 'desc').get();
    })
    .then(ratings => {
      let mainEl;

      if (ratings.size) {
        mainEl = this.renderTemplate('main');

        ratings.forEach(rating => {
          const data = rating.data();
          const el = this.renderTemplate('review-card', data);
          el.querySelector('.rating').append(this.renderRating(data.rating));
          mainEl.querySelector('#cards').append(el);
        });
      } else {
        mainEl = this.renderTemplate('no-ratings', {
          add_mock_data: () => {
            this.addMockRatings(id).then(() => {
              this.rerender();
            });
          }
        });
      }

      const headerEl = this.renderTemplate('header-base', {
        hasSectionHeader: true
      });

      this.replaceElement(document.querySelector('.header'), sectionHeaderEl);
      this.replaceElement(document.querySelector('main'), mainEl);
    })
    .then(() => {
      this.router.updatePageLinks();
    })
    .catch(err => {
      console.warn('Error rendering page', err);
    });
};

FriendlyEats.prototype.renderTemplate = function(id, data) {
  const template = this.templates[id];
  const el = template.cloneNode(true);
  el.removeAttribute('hidden');
  this.render(el, data);
  return el;
};

FriendlyEats.prototype.render = function(el, data) {
  if (!data) return;

  const modifiers = {
    'data-fir-foreach': tel => {
      const field = tel.getAttribute('data-fir-foreach');
      const values = this.getDeepItem(data, field);

      values.forEach((value, index) => {
        const cloneTel = tel.cloneNode(true);
        tel.parentNode.append(cloneTel);

        Object.keys(modifiers).forEach(selector => {
          const children = Array.prototype.slice.call(
            cloneTel.querySelectorAll(`[${selector}]`)
          );
          children.push(cloneTel);
          children.forEach(childEl => {
            const currentVal = childEl.getAttribute(selector);

            if (!currentVal) return;
            childEl.setAttribute(
              selector,
              currentVal.replace('~', `${field}/${index}`)
            );
          });
        });
      });

      tel.parentNode.removeChild(tel);
    },
    'data-fir-content': tel => {
      const field = tel.getAttribute('data-fir-content');
      tel.innerText = this.getDeepItem(data, field);
    },
    'data-fir-click': tel => {
      tel.addEventListener('click', () => {
        const field = tel.getAttribute('data-fir-click');
        this.getDeepItem(data, field)();
      });
    },
    'data-fir-if': tel => {
      const field = tel.getAttribute('data-fir-if');
      if (!this.getDeepItem(data, field)) {
        tel.style.display = 'none';
      }
    },
    'data-fir-if-not': tel => {
      const field = tel.getAttribute('data-fir-if-not');
      if (this.getDeepItem(data, field)) {
        tel.style.display = 'none';
      }
    },
    'data-fir-attr': tel => {
      const chunks = tel.getAttribute('data-fir-attr').split(':');
      const attr = chunks[0];
      const field = chunks[1];
      tel.setAttribute(attr, this.getDeepItem(data, field));
    },
    'data-fir-style': tel => {
      const chunks = tel.getAttribute('data-fir-style').split(':');
      const attr = chunks[0];
      const field = chunks[1];
      let value = this.getDeepItem(data, field);

      if (attr.toLowerCase() === 'backgroundimage') {
        value = `url(${value})`;
      }
      tel.style[attr] = value;
    }
  };

  const preModifiers = ['data-fir-foreach'];

  preModifiers.forEach(selector => {
    const modifier = modifiers[selector];
    this.useModifier(el, selector, modifier);
  });

  Object.keys(modifiers).forEach(selector => {
    if (preModifiers.indexOf(selector) !== -1) return;

    const modifier = modifiers[selector];
    this.useModifier(el, selector, modifier);
  });
};

FriendlyEats.prototype.useModifier = function(el, selector, modifier) {
  el.querySelectorAll(`[${selector}]`).forEach(modifier);
};

FriendlyEats.prototype.getDeepItem = function(obj, path) {
  path.split('/').forEach(chunk => {
    obj = obj[chunk];
  });
  return obj;
};

FriendlyEats.prototype.renderRating = function(rating) {
  const el = this.renderTemplate('rating', {});
  for (let r = 0; r < 5; r += 1) {
    let star;
    if (r < Math.floor(rating)) {
      star = this.renderTemplate('star-icon', {});
    } else {
      star = this.renderTemplate('star-border-icon', {});
    }
    el.append(star);
  }
  return el;
};

FriendlyEats.prototype.renderPrice = function(price) {
  const el = this.renderTemplate('price', {});
  for (let r = 0; r < price; r += 1) {
    el.append('$');
  }
  return el;
};

FriendlyEats.prototype.replaceElement = function(parent, content) {
  parent.innerHTML = '';
  parent.append(content);
};

FriendlyEats.prototype.rerender = function() {
  this.router.navigate(document.location.pathname + '?' + new Date().getTime());
};
<commit_msg>Bugfix in Firestore<commit_after>/**
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * 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
 *
 *      http://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.
 */
'use strict';

FriendlyEats.prototype.initTemplates = function() {
  this.templates = {};
  document.querySelectorAll('.template').forEach(el => {
    this.templates[el.getAttribute('id')] = el;
  });
};

FriendlyEats.prototype.viewHome = function() {
  this.getAllRestaurants();
};

FriendlyEats.prototype.viewList = function(filters, filter_description) {
  if (!filter_description) {
    filter_description = 'any type of food with any price in any city.';
  }

  const mainEl = this.renderTemplate('main-adjusted');
  const headerEl = this.renderTemplate('header-base', {
    hasSectionHeader: true
  });

  this.replaceElement(
      headerEl.querySelector('#section-header'),
      this.renderTemplate('filter-display', { filter_description })
  );

  this.replaceElement(document.querySelector('.header'), headerEl);
  this.replaceElement(document.querySelector('main'), mainEl);

  headerEl.querySelector('#show-filters').addEventListener('click', () => {
    this.dialogs.filter.show();
  });

  const renderResults = doc => {
    if (!doc) {
      const headerEl = this.renderTemplate('header-base', {
        hasSectionHeader: true
      });

      const noResultsEl = this.renderTemplate('no-results');

      this.replaceElement(
          headerEl.querySelector('#section-header'),
          this.renderTemplate('filter-display', { filter_description })
      );

      headerEl.querySelector('#show-filters').addEventListener('click', () => {
        this.dialogs.filter.show();
      });

      this.replaceElement(document.querySelector('.header'), headerEl);
      this.replaceElement(document.querySelector('main'), noResultsEl);
      return;
    }
    const data = doc.data();
    data['.id'] = doc.id;
    data['go_to_restaurant'] = () => {
      this.router.navigate(`/restaurants/${doc.id}`);
    };

    const el = this.renderTemplate('restaurant-card', data);
    el.querySelector('.rating').append(this.renderRating(data.avgRating));
    el.querySelector('.price').append(this.renderPrice(data.price));

    mainEl.querySelector('#cards').append(el);
  };

  if (filters.city || filters.category || filters.price || filters.sort !== 'Rating' ) {
    this.getFilteredRestaurants({
     city: filters.city || 'Any',
     category: filters.category || 'Any',
     price: filters.price || 'Any',
     sort: filters.sort
    }, renderResults);
  } else {
    this.getAllRestaurants(renderResults);
  }

  const toolbar = mdc.toolbar.MDCToolbar.attachTo(document.querySelector('.mdc-toolbar'));
  toolbar.fixedAdjustElement = document.querySelector('.mdc-toolbar-fixed-adjust');

  mdc.autoInit();
};

FriendlyEats.prototype.viewSetup = function() {
  const headerEl = this.renderTemplate('header-base', {
    hasSectionHeader: false
  });

  const config = this.getFirebaseConfig();
  const noRestaurantsEl = this.renderTemplate('no-restaurants', config);

  const button = noRestaurantsEl.querySelector('#add_mock_data');
  let addingMockData = false;

  button.addEventListener('click', event => {
    if (addingMockData) return;
    addingMockData = true;

    event.target.style.opacity = '0.4';
    event.target.innerText = 'Please wait...';

    this.addMockRestaurants().then(() => {
      this.rerender();
    });
  });

  this.replaceElement(document.querySelector('.header'), headerEl);
  this.replaceElement(document.querySelector('main'), noRestaurantsEl);

  firebase
  .firestore()
  .collection('restaurants')
  .limit(1)
  .onSnapshot(snapshot => {
    if (snapshot.size && !addingMockData) {
      this.router.navigate('/');
    }
  });
};

FriendlyEats.prototype.initReviewDialog = function() {
  const dialog = document.querySelector('#dialog-add-review');
  this.dialogs.add_review = new mdc.dialog.MDCDialog(dialog);

  this.dialogs.add_review.listen('MDCDialog:accept', () => {
    let pathname = this.getCleanPath(document.location.pathname);
    let id = pathname.split('/')[2];

    this.addRating(id, {
      rating,
      text: dialog.querySelector('#text').value,
      userName: 'Anonymous (Web)',
      timestamp: new Date(),
      userId: firebase.auth().currentUser.uid
    }).then(() => {
      this.rerender();
    });
  });

  let rating = 0;

  dialog.querySelectorAll('.star-input i').forEach(el => {
    const rate = () => {
      let after = false;
      rating = 0;
      [].slice.call(el.parentNode.children).forEach(child => {
        if (!after) {
          rating++;
          child.innerText = 'star';
        } else {
          child.innerText = 'star_border';
        }
        after = after || child.isSameNode(el);
      });
    };
    el.addEventListener('mouseover', rate);
  });
};

FriendlyEats.prototype.initFilterDialog = function() {
  // TODO: Reset filter dialog to init state on close.
  this.dialogs.filter = new mdc.dialog.MDCDialog(document.querySelector('#dialog-filter-all'));

  this.dialogs.filter.listen('MDCDialog:accept', () => {
    this.updateQuery(this.filters);
  });

  const dialog = document.querySelector('aside');
  const pages = dialog.querySelectorAll('.page');

  this.replaceElement(
    dialog.querySelector('#category-list'),
      this.renderTemplate('item-list', { items: ['Any'].concat(this.data.categories) })
  );

  this.replaceElement(
      dialog.querySelector('#city-list'),
      this.renderTemplate('item-list', { items: ['Any'].concat(this.data.cities) })
  );

  const renderAllList = () => {
    this.replaceElement(
        dialog.querySelector('#all-filters-list'),
        this.renderTemplate('all-filters-list', this.filters)
    );

    dialog.querySelectorAll('#page-all .mdc-list-item').forEach(el => {
      el.addEventListener('click', () => {
        const id = el.id.split('-').slice(1).join('-');
        displaySection(id);
      });
    });
  };

  const displaySection = id => {
    if (id === 'page-all') {
      renderAllList();
    }

    pages.forEach(sel => {
      if (sel.id === id) {
        sel.style.display = 'block';
      } else {
        sel.style.display = 'none';
      }
    });
  };

  pages.forEach(sel => {
    const type = sel.id.split('-')[1];
    if (type === 'all') return;

    sel.querySelectorAll('.mdc-list-item').forEach(el => {
      el.addEventListener('click', () => {
        this.filters[type] = el.innerText.trim() === 'Any'? '' : el.innerText.trim();
        displaySection('page-all');
      });
    });
  });

  displaySection('page-all');
  dialog.querySelectorAll('.back').forEach(el => {
    el.addEventListener('click', () => {
      displaySection('page-all');
    });
  });
};

FriendlyEats.prototype.updateQuery = function(filters) {
  let query_description = '';

  if (filters.category !== '') {
    query_description += `${filters.category} places`;
  } else {
    query_description += 'any restaurant';
  }

  if (filters.city !== '') {
    query_description += ` in ${filters.city}`;
  } else {
    query_description += ' located anywhere';
  }

  if (filters.price !== '') {
    query_description += ` with a price of ${filters.price}`;
  } else {
    query_description += ' with any price';
  }

  if (filters.sort === 'Rating') {
    query_description += ' sorted by rating';
  } else if (filters.sort === 'Reviews') {
    query_description += ' sorted by # of reviews';
  }

  this.viewList(filters, query_description);
};

FriendlyEats.prototype.viewRestaurant = function(id) {
  let sectionHeaderEl;
  return this.getRestaurant(id)
    .then(doc => {
      const data = doc.data();
      const dialog =  this.dialogs.add_review;

      data.show_add_review = () => {
        dialog.show();
      };

      sectionHeaderEl = this.renderTemplate('restaurant-header', data);
      sectionHeaderEl
        .querySelector('.rating')
        .append(this.renderRating(data.avgRating));

      sectionHeaderEl
        .querySelector('.price')
        .append(this.renderPrice(data.price));
      return doc.ref.collection('ratings').orderBy('timestamp', 'desc').get();
    })
    .then(ratings => {
      let mainEl;

      if (ratings.size) {
        mainEl = this.renderTemplate('main');

        ratings.forEach(rating => {
          const data = rating.data();
          const el = this.renderTemplate('review-card', data);
          el.querySelector('.rating').append(this.renderRating(data.rating));
          mainEl.querySelector('#cards').append(el);
        });
      } else {
        mainEl = this.renderTemplate('no-ratings', {
          add_mock_data: () => {
            this.addMockRatings(id).then(() => {
              this.rerender();
            });
          }
        });
      }

      const headerEl = this.renderTemplate('header-base', {
        hasSectionHeader: true
      });

      this.replaceElement(document.querySelector('.header'), sectionHeaderEl);
      this.replaceElement(document.querySelector('main'), mainEl);
    })
    .then(() => {
      this.router.updatePageLinks();
    })
    .catch(err => {
      console.warn('Error rendering page', err);
    });
};

FriendlyEats.prototype.renderTemplate = function(id, data) {
  const template = this.templates[id];
  const el = template.cloneNode(true);
  el.removeAttribute('hidden');
  this.render(el, data);
  return el;
};

FriendlyEats.prototype.render = function(el, data) {
  if (!data) return;

  const modifiers = {
    'data-fir-foreach': tel => {
      const field = tel.getAttribute('data-fir-foreach');
      const values = this.getDeepItem(data, field);

      values.forEach((value, index) => {
        const cloneTel = tel.cloneNode(true);
        tel.parentNode.append(cloneTel);

        Object.keys(modifiers).forEach(selector => {
          const children = Array.prototype.slice.call(
            cloneTel.querySelectorAll(`[${selector}]`)
          );
          children.push(cloneTel);
          children.forEach(childEl => {
            const currentVal = childEl.getAttribute(selector);

            if (!currentVal) return;
            childEl.setAttribute(
              selector,
              currentVal.replace('~', `${field}/${index}`)
            );
          });
        });
      });

      tel.parentNode.removeChild(tel);
    },
    'data-fir-content': tel => {
      const field = tel.getAttribute('data-fir-content');
      tel.innerText = this.getDeepItem(data, field);
    },
    'data-fir-click': tel => {
      tel.addEventListener('click', () => {
        const field = tel.getAttribute('data-fir-click');
        this.getDeepItem(data, field)();
      });
    },
    'data-fir-if': tel => {
      const field = tel.getAttribute('data-fir-if');
      if (!this.getDeepItem(data, field)) {
        tel.style.display = 'none';
      }
    },
    'data-fir-if-not': tel => {
      const field = tel.getAttribute('data-fir-if-not');
      if (this.getDeepItem(data, field)) {
        tel.style.display = 'none';
      }
    },
    'data-fir-attr': tel => {
      const chunks = tel.getAttribute('data-fir-attr').split(':');
      const attr = chunks[0];
      const field = chunks[1];
      tel.setAttribute(attr, this.getDeepItem(data, field));
    },
    'data-fir-style': tel => {
      const chunks = tel.getAttribute('data-fir-style').split(':');
      const attr = chunks[0];
      const field = chunks[1];
      let value = this.getDeepItem(data, field);

      if (attr.toLowerCase() === 'backgroundimage') {
        value = `url(${value})`;
      }
      tel.style[attr] = value;
    }
  };

  const preModifiers = ['data-fir-foreach'];

  preModifiers.forEach(selector => {
    const modifier = modifiers[selector];
    this.useModifier(el, selector, modifier);
  });

  Object.keys(modifiers).forEach(selector => {
    if (preModifiers.indexOf(selector) !== -1) return;

    const modifier = modifiers[selector];
    this.useModifier(el, selector, modifier);
  });
};

FriendlyEats.prototype.useModifier = function(el, selector, modifier) {
  el.querySelectorAll(`[${selector}]`).forEach(modifier);
};

FriendlyEats.prototype.getDeepItem = function(obj, path) {
  path.split('/').forEach(chunk => {
    obj = obj[chunk];
  });
  return obj;
};

FriendlyEats.prototype.renderRating = function(rating) {
  const el = this.renderTemplate('rating', {});
  for (let r = 0; r < 5; r += 1) {
    let star;
    if (r < Math.floor(rating)) {
      star = this.renderTemplate('star-icon', {});
    } else {
      star = this.renderTemplate('star-border-icon', {});
    }
    el.append(star);
  }
  return el;
};

FriendlyEats.prototype.renderPrice = function(price) {
  const el = this.renderTemplate('price', {});
  for (let r = 0; r < price; r += 1) {
    el.append('$');
  }
  return el;
};

FriendlyEats.prototype.replaceElement = function(parent, content) {
  parent.innerHTML = '';
  parent.append(content);
};

FriendlyEats.prototype.rerender = function() {
  this.router.navigate(document.location.pathname + '?' + new Date().getTime());
};
<|endoftext|>


<commit_before>var ChartComponent =  UnmanagedComponent.extend({
    exportChart: function(outputType, overrides) {
        var me = this;

        var buildUrlParameters = function(overrides) {
            overrides = overrides || {};

            var urlParams = {};

            // Pass the parameters defined in this component to the used data source.
            var paramDefsArray = me.parameters;
            if(paramDefsArray && paramDefsArray.length) {
                var paramDefs = $.extend({}, Dashboards.propertiesArrayToObject(paramDefsArray), overrides);
                for(var name in paramDefs) {
                    if(paramDefs.hasOwnProperty(name)) {
                        // Works with eval ...
                        var value = Dashboards.getParameterValue(paramDefs[name]);
                        if($.isArray(value) && value.length == 1 && ('' + value[0]).indexOf(';') >= 0) {
                            // Special case where single element will wrongly be treated as a parseable array by cda
                            value = doCsvQuoting(value[0],';');
                        }
                        //else Will not be correctly handled for functions that return arrays

                        if(typeof value == 'function') { value = value(); }

                        urlParams['param' + name] = value;
                    }
                }
            }

            // Check debug level and pass as parameter
            var level = me.dashboard.debug;
            if(level > 1) {
                urlParams.paramdebug = true;
                urlParams.paramdebugLevel = level;
            }

            var scriptName =  me.name.replace(/render_/, '');

            urlParams.script = ("/"+
                Dashboards.context.solution + "/" +
                Dashboards.context.path     + "/" +

                /* Dashboards.context.file.split('.')[0] + "_" + */
                scriptName + ".js") // TODO: This prevents deprecating the generation of 2 file names in CDE/CGG

                .replace(/\/+/g, '/');

            urlParams.attachmentName = scriptName;

            return urlParams;
        };

        var urlParams = buildUrlParameters(overrides);
        urlParams.outputType = outputType || 'png';

        var $exportIFrame = $('#cccExportIFrame');
        if(!$exportIFrame.length) {
            $exportIFrame = $('');
            $exportIFrame[0].src = "../cgg/draw?" + $.param(urlParams);
            $exportIFrame.appendTo($('body'));
        } else {
            $exportIFrame[0].src = "../cgg/draw?" + $.param(urlParams);
        }
    }
});

var ProtovisComponent =  ChartComponent.extend({

  update : function() {
    if (this.parameters == undefined) {
      this.parameters = [];
    };
    // clear previous table

    this.triggerQuery(this.chartDefinition, _.bind(this.render,this));
  },

  render: function(values) {
    $("#" + this.htmlObject).html('

');

    var vis = new pv.Panel()
      .canvas(this.htmlObject + "protovis")
      .width (this.width)
      .height(this.height);
    this.vis = vis;
    this.customfunction(vis, values);
    vis.root.render();
  },

  processdata: function(values) {
    this.render(values);
  }
});

var BaseCccComponent = ChartComponent.extend({

    query: null,
    chart: null,

    _preProcessChartDefinition: function() {
        var chartDef = this.chartDefinition;
        if(chartDef){
            // Obtain effective compatVersion
            var compatVersion = chartDef.compatVersion;
            if(compatVersion == null){
                compatVersion = typeof pvc.defaultCompatVersion === 'function' ?
                                pvc.defaultCompatVersion() :
                                1;
            }

            if(compatVersion <= 1){
                // Properties that are no more registered in the component
                // and that had a name mapping.
                // The default mapping, for unknown properties, doesn't work.
                if('showLegend' in chartDef){
                    chartDef.legend = chartDef.showLegend;
                    delete chartDef.showLegend;
                }

                // Don't presume chartDef props must be own
                for(var p in chartDef){
                    var m = /^barLine(.*)$/.exec(p);
                    if(m){
                        p2 = 'secondAxis' + (m[1] || '');
                        chartDef[p2] = chartDef[p];
                        delete chartDef[p];
                    }
                }
            }
        }
    }
});

var CccComponent = BaseCccComponent.extend({

    update: function() {
        if(this.parameters == null) {
            this.parameters = [];
        }

        // clear placeholder
        var ph = $("#"+this.htmlObject).empty();
        var me = this;

        // Set up defaults for height and width
        if(typeof(this.chartDefinition.width) === "undefined")
            this.chartDefinition.width = ph.width();

        if(typeof(this.chartDefinition.height) === "undefined")
            this.chartDefinition.height = ph.height();

        if (typeof Modernizr !== 'undefined' && Modernizr.svg) {
            this.renderChart();
        } else {
            pv.listenForPageLoad(function() {
                me.renderChart();
            });
        }
    },

    renderChart: function() {
      var cd = this.chartDefinition;
      if(cd.dataAccessId || cd.query || cd.endpoint /*cpk*/) {
        this.triggerQuery(this.chartDefinition,_.bind(this.render, this));
      } else if(this.valuesArray != undefined) {
        this.synchronous(_.bind(function() { this.render(this.valuesArray); }, this));
      } else {
        // initialize the component only
        this.synchronous(_.bind(this.render, this));
      }
    },

    render: function(values) {

        $("#" + this.htmlObject).append('

');

        this._preProcessChartDefinition();

        var o = $.extend({},this.chartDefinition);
        o.canvas = this.htmlObject+'protovis';
        // Extension points
        if(typeof o.extensionPoints != "undefined"){
            var ep = {};
            o.extensionPoints.forEach(function(a){
                ep[a[0]]=a[1];
            });
            o.extensionPoints=ep;
        }

        this.chart =  new this.cccType(o);
        if(arguments.length > 0){
            this.chart.setData(values,{
                crosstabMode: this.crosstabMode,
                seriesInRows: this.seriesInRows
            });
        }
        this.chart.render();
    }

});

/*
 *   Modified version of CccComponent which loads 2 datasources.
 */
var CccComponent2 = BaseCccComponent.extend({

    query: null,
    sQuery: null,  // second datasource
    chart: null,

    update : function() {

        var dataQuery = null, sDataQuery = null;

        if (this.parameters == undefined) {
            this.parameters = [];
        };

        // clear previous table
        $("#"+this.htmlObject).empty();
        var me = this;

        this.query = Dashboards.getQuery(this.chartDefinition);

        var sQueryDef = $.extend({}, this.chartDefinition);
        sQueryDef.dataAccessId = sQueryDef.structDatasource;
        this.sQuery = Dashboards.getQuery(sQueryDef);

        var executed = false;
        var execComponent = function() {

            if (   ( dataQuery != null)
                && (sDataQuery != null)
                && !executed) {

                me.render(dataQuery, sDataQuery);
                executed = true;   // safety in case both queries return
            // simultaneously (is this possible in single-threaded Javascript?)
            }

        };

        pv.listenForPageLoad(function() {
            me.query.fetchData(me.parameters, function(values) {
                // why is changedValues a GLOBAL ??  potential conflicts!!
                var changedValues = undefined;
                if((typeof(me.postFetch)=='function')){
                    changedValues = me.postFetch(values);
                    $("#" + this.htmlObject).append('

');
                }
                if (changedValues != undefined) {
                    values = changedValues;
                }

                dataQuery = values;
                execComponent();
            });
        });

        // load the second query (in parallel)
        pv.listenForPageLoad(function() {
            me.sQuery.fetchData(me.parameters, function(values) {
                var changedValues = undefined;
                if((typeof(me.postFetch)=='function')){
                    changedValues = me.postFetch(values);
                    $("#" + this.htmlObject).append('

');
                }
                if (changedValues != undefined) {
                    values = changedValues;
                }

                sDataQuery = values;
                execComponent();
            });
        });
    },

    render: function(values, sValues) {

        $("#" + this.htmlObject).append('

');

        this._preProcessChartDefinition();

        var o = $.extend({},this.chartDefinition);
        o.canvas = this.htmlObject+'protovis';
        // Extension points
        if(typeof o.extensionPoints != "undefined"){
            var ep = {};
            o.extensionPoints.forEach(function(a){
                ep[a[0]]=a[1];
            });
            o.extensionPoints=ep;
        }
        this.chart =  new this.cccType(o);
        this.chart.setData(values,{
            crosstabMode: this.crosstabMode,
            seriesInRows: this.seriesInRows
        });

        this.chart.setStructData(sValues);
        this.chart.render();
    }

});

var CccAreaChartComponent = CccComponent.extend({

    cccType: pvc.AreaChart

});

var CccStackedDotChart = CccComponent.extend({

    cccType: pvc.StackedDotChart
});

var CccDotChartComponent = CccComponent.extend({

    cccType: pvc.DotChart

});

var CccLineChartComponent = CccComponent.extend({

    cccType: pvc.LineChart

});

var CccStackedLineChartComponent = CccComponent.extend({

    cccType: pvc.StackedLineChart

});

var CccStackedAreaChartComponent = CccComponent.extend({

    cccType: pvc.StackedAreaChart

});

var CccBarChartComponent = CccComponent.extend({

    cccType: pvc.BarChart

});

var CccPieChartComponent = CccComponent.extend({

    cccType: pvc.PieChart

});

var CccHeatGridChartComponent = CccComponent.extend({

    cccType: pvc.HeatGridChart

});

var CccBulletChartComponent = CccComponent.extend({

    cccType: pvc.BulletChart

});

var CccWaterfallChartComponent = CccComponent.extend({

    cccType: pvc.WaterfallChart

});

var CccMetricDotChartComponent = CccComponent.extend({

    cccType: pvc.MetricDotChart

});

var CccMetricLineChartComponent = CccComponent.extend({

    cccType: pvc.MetricLineChart

});

var CccParCoordComponent = CccComponent.extend({

    cccType: pvc.ParallelCoordinates

});

var CccDataTreeComponent = CccComponent2.extend({

    cccType: pvc.DataTree

});

var CccBoxplotChartComponent = CccComponent.extend({

    cccType: pvc.BoxplotChart

});

var CccTreemapChartComponent = CccComponent.extend({

    cccType: pvc.TreemapChart

});

var CccNormalizedBarChartComponent = CccComponent.extend({

    cccType: pvc.NormalizedBarChart

});<commit_msg>[FEAT] (Protovis Component No Data) Add suport for no data source, cpk and valuesArray for the protovis component.<commit_after>var ChartComponent =  UnmanagedComponent.extend({
    exportChart: function(outputType, overrides) {
        var me = this;

        var buildUrlParameters = function(overrides) {
            overrides = overrides || {};

            var urlParams = {};

            // Pass the parameters defined in this component to the used data source.
            var paramDefsArray = me.parameters;
            if(paramDefsArray && paramDefsArray.length) {
                var paramDefs = $.extend({}, Dashboards.propertiesArrayToObject(paramDefsArray), overrides);
                for(var name in paramDefs) {
                    if(paramDefs.hasOwnProperty(name)) {
                        // Works with eval ...
                        var value = Dashboards.getParameterValue(paramDefs[name]);
                        if($.isArray(value) && value.length == 1 && ('' + value[0]).indexOf(';') >= 0) {
                            // Special case where single element will wrongly be treated as a parseable array by cda
                            value = doCsvQuoting(value[0],';');
                        }
                        //else Will not be correctly handled for functions that return arrays

                        if(typeof value == 'function') { value = value(); }

                        urlParams['param' + name] = value;
                    }
                }
            }

            // Check debug level and pass as parameter
            var level = me.dashboard.debug;
            if(level > 1) {
                urlParams.paramdebug = true;
                urlParams.paramdebugLevel = level;
            }

            var scriptName =  me.name.replace(/render_/, '');

            urlParams.script = ("/"+
                Dashboards.context.solution + "/" +
                Dashboards.context.path     + "/" +

                /* Dashboards.context.file.split('.')[0] + "_" + */
                scriptName + ".js") // TODO: This prevents deprecating the generation of 2 file names in CDE/CGG

                .replace(/\/+/g, '/');

            urlParams.attachmentName = scriptName;

            return urlParams;
        };

        var urlParams = buildUrlParameters(overrides);
        urlParams.outputType = outputType || 'png';

        var $exportIFrame = $('#cccExportIFrame');
        if(!$exportIFrame.length) {
            $exportIFrame = $('');
            $exportIFrame[0].src = "../cgg/draw?" + $.param(urlParams);
            $exportIFrame.appendTo($('body'));
        } else {
            $exportIFrame[0].src = "../cgg/draw?" + $.param(urlParams);
        }
    },

    renderChart: function() {
      var cd = this.chartDefinition;
      if(cd.dataAccessId || cd.query || cd.endpoint /*cpk*/) {
        this.triggerQuery(this.chartDefinition,_.bind(this.render, this));
      } else if(this.valuesArray != undefined) {
        this.synchronous(_.bind(function() { this.render(this.valuesArray); }, this));
      } else {
        // initialize the component only
        this.synchronous(_.bind(this.render, this));
      }
    }
});

var ProtovisComponent =  ChartComponent.extend({

  update : function() {
    if (this.parameters == undefined) {
      this.parameters = [];
    };

    this.renderChart();
  },

  render: function(values) {
    $("#" + this.htmlObject).html('

');

    var vis = new pv.Panel()
      .canvas(this.htmlObject + "protovis")
      .width (this.width)
      .height(this.height);
    this.vis = vis;
    this.customfunction(vis, values);
    vis.root.render();
  },

  processdata: function(values) {
    this.render(values);
  }
});

var BaseCccComponent = ChartComponent.extend({

    query: null,
    chart: null,

    _preProcessChartDefinition: function() {
        var chartDef = this.chartDefinition;
        if(chartDef){
            // Obtain effective compatVersion
            var compatVersion = chartDef.compatVersion;
            if(compatVersion == null){
                compatVersion = typeof pvc.defaultCompatVersion === 'function' ?
                                pvc.defaultCompatVersion() :
                                1;
            }

            if(compatVersion <= 1){
                // Properties that are no more registered in the component
                // and that had a name mapping.
                // The default mapping, for unknown properties, doesn't work.
                if('showLegend' in chartDef){
                    chartDef.legend = chartDef.showLegend;
                    delete chartDef.showLegend;
                }

                // Don't presume chartDef props must be own
                for(var p in chartDef){
                    var m = /^barLine(.*)$/.exec(p);
                    if(m){
                        p2 = 'secondAxis' + (m[1] || '');
                        chartDef[p2] = chartDef[p];
                        delete chartDef[p];
                    }
                }
            }
        }
    }
});

var CccComponent = BaseCccComponent.extend({

    update: function() {
        if(this.parameters == null) {
            this.parameters = [];
        }

        // clear placeholder
        var ph = $("#"+this.htmlObject).empty();
        var me = this;

        // Set up defaults for height and width
        if(typeof(this.chartDefinition.width) === "undefined")
            this.chartDefinition.width = ph.width();

        if(typeof(this.chartDefinition.height) === "undefined")
            this.chartDefinition.height = ph.height();

        if (typeof Modernizr !== 'undefined' && Modernizr.svg) {
            this.renderChart();
        } else {
            pv.listenForPageLoad(function() {
                me.renderChart();
            });
        }
    },

    render: function(values) {

        $("#" + this.htmlObject).append('

');

        this._preProcessChartDefinition();

        var o = $.extend({},this.chartDefinition);
        o.canvas = this.htmlObject+'protovis';
        // Extension points
        if(typeof o.extensionPoints != "undefined"){
            var ep = {};
            o.extensionPoints.forEach(function(a){
                ep[a[0]]=a[1];
            });
            o.extensionPoints=ep;
        }

        this.chart =  new this.cccType(o);
        if(arguments.length > 0){
            this.chart.setData(values,{
                crosstabMode: this.crosstabMode,
                seriesInRows: this.seriesInRows
            });
        }
        this.chart.render();
    }
});

/*
 *   Modified version of CccComponent which loads 2 datasources.
 */
var CccComponent2 = BaseCccComponent.extend({

    query: null,
    sQuery: null,  // second datasource
    chart: null,

    update : function() {

        var dataQuery = null, sDataQuery = null;

        if (this.parameters == undefined) {
            this.parameters = [];
        };

        // clear previous table
        $("#"+this.htmlObject).empty();
        var me = this;

        this.query = Dashboards.getQuery(this.chartDefinition);

        var sQueryDef = $.extend({}, this.chartDefinition);
        sQueryDef.dataAccessId = sQueryDef.structDatasource;
        this.sQuery = Dashboards.getQuery(sQueryDef);

        var executed = false;
        var execComponent = function() {

            if (   ( dataQuery != null)
                && (sDataQuery != null)
                && !executed) {

                me.render(dataQuery, sDataQuery);
                executed = true;   // safety in case both queries return
            // simultaneously (is this possible in single-threaded Javascript?)
            }

        };

        pv.listenForPageLoad(function() {
            me.query.fetchData(me.parameters, function(values) {
                // why is changedValues a GLOBAL ??  potential conflicts!!
                var changedValues = undefined;
                if((typeof(me.postFetch)=='function')){
                    changedValues = me.postFetch(values);
                    $("#" + this.htmlObject).append('

');
                }
                if (changedValues != undefined) {
                    values = changedValues;
                }

                dataQuery = values;
                execComponent();
            });
        });

        // load the second query (in parallel)
        pv.listenForPageLoad(function() {
            me.sQuery.fetchData(me.parameters, function(values) {
                var changedValues = undefined;
                if((typeof(me.postFetch)=='function')){
                    changedValues = me.postFetch(values);
                    $("#" + this.htmlObject).append('

');
                }
                if (changedValues != undefined) {
                    values = changedValues;
                }

                sDataQuery = values;
                execComponent();
            });
        });
    },

    render: function(values, sValues) {

        $("#" + this.htmlObject).append('

');

        this._preProcessChartDefinition();

        var o = $.extend({},this.chartDefinition);
        o.canvas = this.htmlObject+'protovis';
        // Extension points
        if(typeof o.extensionPoints != "undefined"){
            var ep = {};
            o.extensionPoints.forEach(function(a){
                ep[a[0]]=a[1];
            });
            o.extensionPoints=ep;
        }
        this.chart =  new this.cccType(o);
        this.chart.setData(values,{
            crosstabMode: this.crosstabMode,
            seriesInRows: this.seriesInRows
        });

        this.chart.setStructData(sValues);
        this.chart.render();
    }

});

var CccAreaChartComponent = CccComponent.extend({

    cccType: pvc.AreaChart

});

var CccStackedDotChart = CccComponent.extend({

    cccType: pvc.StackedDotChart
});

var CccDotChartComponent = CccComponent.extend({

    cccType: pvc.DotChart

});

var CccLineChartComponent = CccComponent.extend({

    cccType: pvc.LineChart

});

var CccStackedLineChartComponent = CccComponent.extend({

    cccType: pvc.StackedLineChart

});

var CccStackedAreaChartComponent = CccComponent.extend({

    cccType: pvc.StackedAreaChart

});

var CccBarChartComponent = CccComponent.extend({

    cccType: pvc.BarChart

});

var CccPieChartComponent = CccComponent.extend({

    cccType: pvc.PieChart

});

var CccHeatGridChartComponent = CccComponent.extend({

    cccType: pvc.HeatGridChart

});

var CccBulletChartComponent = CccComponent.extend({

    cccType: pvc.BulletChart

});

var CccWaterfallChartComponent = CccComponent.extend({

    cccType: pvc.WaterfallChart

});

var CccMetricDotChartComponent = CccComponent.extend({

    cccType: pvc.MetricDotChart

});

var CccMetricLineChartComponent = CccComponent.extend({

    cccType: pvc.MetricLineChart

});

var CccParCoordComponent = CccComponent.extend({

    cccType: pvc.ParallelCoordinates

});

var CccDataTreeComponent = CccComponent2.extend({

    cccType: pvc.DataTree

});

var CccBoxplotChartComponent = CccComponent.extend({

    cccType: pvc.BoxplotChart

});

var CccTreemapChartComponent = CccComponent.extend({

    cccType: pvc.TreemapChart

});

var CccNormalizedBarChartComponent = CccComponent.extend({

    cccType: pvc.NormalizedBarChart

});<|endoftext|>


<commit_before>function test(){
    var A = {name: "A", adjacencies: [{weight: 2, targetNode: "B", index: 0}, {weight: 4, targetNode: "C", index: 1}, {weight: 1, targetNode: "D", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var B = {name: "B", adjacencies: [{weight: 2, targetNode: "A", index: 0}, {weight: 3, targetNode: "C", index: 1}, {weight: 1, targetNode: "E", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var C = {name: "C", adjacencies: [{weight: 4, targetNode: "A", index: 0}, {weight: 3, targetNode: "B", index: 1}, {weight: 2, targetNode: "E", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var D = {name: "D", adjacencies: [{weight: 1, targetNode: "A", index: 0}], weight: Number.MAX_VALUE, minPath: []};
    var E = {name: "E", adjacencies: [{weight: 1, targetNode: "B", index: 0}, {weight: 2, targetNode: "C", index: 1}], weight: Number.MAX_VALUE, minPath: []};

    /*var A = {name: "A", adjacencies: [{weight: 2, targetNode: "B", index: 0}, {weight: 4, targetNode: "C", index: 1}, {weight: 1, targetNode: "D", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var B = {name: "B", adjacencies: [{weight: 2, targetNode: "A", index: 0}, {weight: 3, targetNode: "C", index: 1}, {weight: 1, targetNode: "E", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var C = {name: "C", adjacencies: [{weight: 4, targetNode: "A", index: 0}, {weight: 3, targetNode: "B", index: 1}, {weight: 2, targetNode: "E", index: 2}, {weight: 2, targetNode: "F", index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var D = {name: "D", adjacencies: [{weight: 1, targetNode: "A", index: 0}, {weight: 5, targetNode: "F", index: 1}, {weight: 4, targetNode: "G", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var E = {name: "E", adjacencies: [{weight: 1, targetNode: "B", index: 0}, {weight: 2, targetNode: "C", index: 1}, {weight: 3, targetNode: "H", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var F = {name: "F", adjacencies: [{weight: 2, targetNode: C, index: 0}, {weight: 5, targetNode: D, index: 1}, {weight: 3, targetNode: G, index: 2}, {weight: 4, targetNode: J, index: 3}, {weight: 2, targetNode: I, index: 4}, {weight: 3, targetNode: H, index: 5}], weight: Number.MAX_VALUE, minPath: []};
    var G = {name: "G", adjacencies: [{weight: 4, targetNode: D, index: 0}, {weight: 3, targetNode: F, index: 1}, {weight: 2, targetNode: K, index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var H = {name: "H", adjacencies: [{weight: 3, targetNode: E, index: 0}, {weight: 3, targetNode: F, index: 1}, {weight: 1, targetNode: L, index: 2}, {weight: 8, targetNode: L, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var I = {name: "I", adjacencies: [{weight: 2, targetNode: F, index: 0}, {weight: 3, targetNode: J, index: 1}, {weight: 2, targetNode: M, index: 2}, {weight: 3, targetNode: L, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var J = {name: "J", adjacencies: [{weight: 4, targetNode: F, index: 0}, {weight: 3, targetNode: I, index: 1}, {weight: 6, targetNode: M, index: 2}, {weight: 3, targetNode: M, index: 3}, {weight: 6, targetNode: K, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var K = {name: "K", adjacencies: [{weight: 2, targetNode: G, index: 0}, {weight: 6, targetNode: J, index: 1}, {weight: 4, targetNode: N, index: 2}, {weight: 2, targetNode: R, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var L = {name: "L", adjacencies: [{weight: 1, targetNode: H, index: 0}, {weight: 6, targetNode: O, index: 1}, {weight: 3, targetNode: M, index: 2}, {weight: 3, targetNode: I, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var M = {name: "M", adjacencies: [{weight: 2, targetNode: I, index: 0}, {weight: 3, targetNode: L, index: 1}, {weight: 4, targetNode: O, index: 2}, {weight: 2, targetNode: L, index: 3}, {weight: 5, targetNode: N, index: 4}, {weight: 2, targetNode: P, index: 5}, {weight: 6, targetNode: J, index: 6}], weight: Number.MAX_VALUE, minPath: []};
    var N = {name: "N", adjacencies: [{weight: 4, targetNode: K, index: 0}, {weight: 3, targetNode: J, index: 1}, {weight: 5, targetNode: M, index: 2}, {weight: 2, targetNode: Q, index: 3}, {weight: 1, targetNode: R, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var O = {name: "O", adjacencies: [{weight: 8, targetNode: H, index: 0}, {weight: 6, targetNode: L, index: 1}, {weight: 4, targetNode: M, index: 2}, {weight: 2, targetNode: P, index: 3}, {weight: 6, targetNode: S, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var P = {name: "P", adjacencies: [{weight: 2, targetNode: M, index: 0}, {weight: 2, targetNode: O, index: 1}, {weight: 2, targetNode: S, index: 2}, {weight: 1, targetNode: T, index: 3}, {weight: 1, targetNode: Q, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var Q = {name: "Q", adjacencies: [{weight: 2, targetNode: N, index: 0}, {weight: 1, targetNode: P, index: 1}, {weight: 3, targetNode: T, index: 2}, {weight: 8, targetNode: R, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var R = {name: "R", adjacencies: [{weight: 2, targetNode: K, index: 0}, {weight: 1, targetNode: N, index: 1}, {weight: 8, targetNode: Q, index: 2}, {weight: 5, targetNode: T, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var S = {name: "S", adjacencies: [{weight: 6, targetNode: O, index: 0}, {weight: 2, targetNode: P, index: 1}, {weight: 2, targetNode: Z, index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var T = {name: "T", adjacencies: [{weight: 5, targetNode: R, index: 0}, {weight: 3, targetNode: Q, index: 1}, {weight: 1, targetNode: P, index: 2}, {weight: 8, targetNode: "Z", index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var Z = {name: "Z", adjacencies: [{weight: 2, targetNode: S, index: 0}, {weight: 8, targetNode: T, index: 1}], weight: Number.MAX_VALUE, minPath: []};*/
    console.log(runDijkstrasAlgorithm([A, B, C, D, E], A, E));
}

function runDijkstrasAlgorithm(listOfNodes, startingNode, endingNode)
{
    //creating the unvisted list
    var unvisited = listOfNodes;
    //var minNode = startingNode;
    //for(index = 0; index < listOfNodes.length; index++)
        //unvisited += listOfNodes[index];
    //JSON already has starting node, now set weight to 0
    startingNode.weight = 0;
    //by default, minPath is null. so the first one has to have a non null path so that it can copy to all the other nodes.
    startingNode.minPath = startingNode.name;

    //JSON already has ending node
    var chosenNode = startingNode;
    while(chosenNode.name !== endingNode.name)
    {
        for(index = 0; index < chosenNode.adjacencies.length; index++)
        {
            //panda is that particular targetnode from that particular edge
            var panda = chosenNode.adjacencies[index].targetNode;
            console.log(panda);
            //update only if panda has been unvisited. if it has already been visited, we don't look at it.
            //basiclaly don't look at the targetnode if, as a node, the targetnode has already been looked at
            //confusing a bit. becuase panda is a target node. but that node only goes to unvisited if it was ever the chosenNode
            //so if panda was already a chosenNode, then don't look at it, basically, if that makes more sense
            if(binarySearch(panda, unvisited))
            {
                var suggestedWeight = chosenNode.weight + chosenNode.adjacencies[index].weight;
                console.log(suggestedWeight);
                //only if my new weight < my old weight, do i update
                if(suggestedWeight < panda.weight)
                {
                    index2 = find(listOfNodes, chosenNode.name);
                    index3 = find(unvisited, chosenNode.name);
                    //update values
                    listOfNodes[index2].weight = suggestedWeight;
                    unvisited[index3].weight = suggestedWeight;

                    //so i can add myself, the panda, to the new improved chain. becuase the old chain came from the chosenNode, the startingNode of the edge (vs. targetNode).
                    //and THAT path/chain does not include the targetNode/panda. so you have to add itself to the new, min path. beecause the min path takes
                    //vestiges of the old chain from chosenNode + the panda node.

                    //var path = chosenNode.minPath;
                    listOfNodes[index2].minPath = chosenNode.minPath;
                    listOfNodes[index2].minPath += chosenNode.adjacencies[index];
                    unvisited[index3].minPath = chosenNode.minPath;
                    unvisited[index3].minPath += chosenNode.adjacencies[index];
                    //listOfNodes[index].minPath = path;
                }
            }
        }

        unvisited = binarySearchAndDestroyNode(chosenNode.name, unvisited);
        var minNode = {name: null, adjacencies: [], weight: Number.MAX_VALUE, minPath: []};
        for(index = 0; index < unvisited.length; index++)
        {
            if(unvisited[index].weight < minNode.weight)
            {
                minNode = unvisited[index];
            }
            if(minNode.name === endingNode.name){
                listOfNodes[find(listOfNodes, endingNode.name)].minPath += chosenNode.minPath;
            }
        }
        chosenNode = minNode;
        console.log(chosenNode);
    }

    //done!
    //this printout works becuase of polymorphism. eventually, chosenNode will = endingNode, and then when that happens, endingNode will have already been altered.
    //console.log("hi");
    console.log(listOfNodes[find(listOfNodes, endingNode.name)].minPath);
    return endingNode.minPath;

}

function find(array, name){
    for(index = 0; index < array.length; index++){
        if(array[index].name === name)
            return index;
    }
    return -1;
}

//precondition: checkThis is a Node and inThis is a list of Nodes
function binarySearch(checkThis, inThis)
{
    //returns a boolean. checks the path.
    //will return true if the path's last element is in the unselected.
    var min = 0;
    var max = inThis.length - 1;
    var index;
    //console.log(inThis);
    while(max - min > -1)
    {
        index = Math.floor((min + max)/2);
        if(inThis[index].name === checkThis)
        {
            //found it!
            return true;
        }
        else if(inThis[index].name.localeCompare(checkThis) < 0)
        {
            min = index + 1;
        }
        else
        {
            max = index - 1;
        }
    }

    return false;
}

//precondition: toBeDestroyed is a node name (string) and searchThis is a list of nodes
 function binarySearchAndDestroyNode(toBeDestroyed, searchThis) {
        //returns index of the toBeDestroyed within the array
        var min = 0;
        var max = searchThis.length - 1;
        var index;

        while (max - min > -1)
        {
            index = Math.floor((min + max) / 2);
            if (searchThis[index].name === toBeDestroyed)
            {
                searchThis.splice(index, 1);
                return searchThis;
            }
            else if (searchThis[index].name.toUpperCase().localeCompare(toBeDestroyed.toUpperCase()) < 0)
            {
                min = index + 1;
            }
            else
            {
                max = index - 1;
            }
        }
    }
<commit_msg>Add files via upload<commit_after>function test(){
    var A = {name: "A", adjacencies: [{weight: 2, targetNode: "B", index: 0}, {weight: 4, targetNode: "C", index: 1}, {weight: 1, targetNode: "D", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var B = {name: "B", adjacencies: [{weight: 2, targetNode: "A", index: 0}, {weight: 3, targetNode: "C", index: 1}, {weight: 1, targetNode: "E", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var C = {name: "C", adjacencies: [{weight: 4, targetNode: "A", index: 0}, {weight: 3, targetNode: "B", index: 1}, {weight: 2, targetNode: "E", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var D = {name: "D", adjacencies: [{weight: 1, targetNode: "A", index: 0}], weight: Number.MAX_VALUE, minPath: []};
    var E = {name: "E", adjacencies: [{weight: 1, targetNode: "B", index: 0}, {weight: 2, targetNode: "C", index: 1}], weight: Number.MAX_VALUE, minPath: []};

    /*var A = {name: "A", adjacencies: [{weight: 2, targetNode: "B", index: 0}, {weight: 4, targetNode: "C", index: 1}, {weight: 1, targetNode: "D", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var B = {name: "B", adjacencies: [{weight: 2, targetNode: "A", index: 0}, {weight: 3, targetNode: "C", index: 1}, {weight: 1, targetNode: "E", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var C = {name: "C", adjacencies: [{weight: 4, targetNode: "A", index: 0}, {weight: 3, targetNode: "B", index: 1}, {weight: 2, targetNode: "E", index: 2}, {weight: 2, targetNode: "F", index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var D = {name: "D", adjacencies: [{weight: 1, targetNode: "A", index: 0}, {weight: 5, targetNode: "F", index: 1}, {weight: 4, targetNode: "G", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var E = {name: "E", adjacencies: [{weight: 1, targetNode: "B", index: 0}, {weight: 2, targetNode: "C", index: 1}, {weight: 3, targetNode: "H", index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var F = {name: "F", adjacencies: [{weight: 2, targetNode: C, index: 0}, {weight: 5, targetNode: D, index: 1}, {weight: 3, targetNode: G, index: 2}, {weight: 4, targetNode: J, index: 3}, {weight: 2, targetNode: I, index: 4}, {weight: 3, targetNode: H, index: 5}], weight: Number.MAX_VALUE, minPath: []};
    var G = {name: "G", adjacencies: [{weight: 4, targetNode: D, index: 0}, {weight: 3, targetNode: F, index: 1}, {weight: 2, targetNode: K, index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var H = {name: "H", adjacencies: [{weight: 3, targetNode: E, index: 0}, {weight: 3, targetNode: F, index: 1}, {weight: 1, targetNode: L, index: 2}, {weight: 8, targetNode: L, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var I = {name: "I", adjacencies: [{weight: 2, targetNode: F, index: 0}, {weight: 3, targetNode: J, index: 1}, {weight: 2, targetNode: M, index: 2}, {weight: 3, targetNode: L, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var J = {name: "J", adjacencies: [{weight: 4, targetNode: F, index: 0}, {weight: 3, targetNode: I, index: 1}, {weight: 6, targetNode: M, index: 2}, {weight: 3, targetNode: M, index: 3}, {weight: 6, targetNode: K, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var K = {name: "K", adjacencies: [{weight: 2, targetNode: G, index: 0}, {weight: 6, targetNode: J, index: 1}, {weight: 4, targetNode: N, index: 2}, {weight: 2, targetNode: R, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var L = {name: "L", adjacencies: [{weight: 1, targetNode: H, index: 0}, {weight: 6, targetNode: O, index: 1}, {weight: 3, targetNode: M, index: 2}, {weight: 3, targetNode: I, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var M = {name: "M", adjacencies: [{weight: 2, targetNode: I, index: 0}, {weight: 3, targetNode: L, index: 1}, {weight: 4, targetNode: O, index: 2}, {weight: 2, targetNode: L, index: 3}, {weight: 5, targetNode: N, index: 4}, {weight: 2, targetNode: P, index: 5}, {weight: 6, targetNode: J, index: 6}], weight: Number.MAX_VALUE, minPath: []};
    var N = {name: "N", adjacencies: [{weight: 4, targetNode: K, index: 0}, {weight: 3, targetNode: J, index: 1}, {weight: 5, targetNode: M, index: 2}, {weight: 2, targetNode: Q, index: 3}, {weight: 1, targetNode: R, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var O = {name: "O", adjacencies: [{weight: 8, targetNode: H, index: 0}, {weight: 6, targetNode: L, index: 1}, {weight: 4, targetNode: M, index: 2}, {weight: 2, targetNode: P, index: 3}, {weight: 6, targetNode: S, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var P = {name: "P", adjacencies: [{weight: 2, targetNode: M, index: 0}, {weight: 2, targetNode: O, index: 1}, {weight: 2, targetNode: S, index: 2}, {weight: 1, targetNode: T, index: 3}, {weight: 1, targetNode: Q, index: 4}], weight: Number.MAX_VALUE, minPath: []};
    var Q = {name: "Q", adjacencies: [{weight: 2, targetNode: N, index: 0}, {weight: 1, targetNode: P, index: 1}, {weight: 3, targetNode: T, index: 2}, {weight: 8, targetNode: R, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var R = {name: "R", adjacencies: [{weight: 2, targetNode: K, index: 0}, {weight: 1, targetNode: N, index: 1}, {weight: 8, targetNode: Q, index: 2}, {weight: 5, targetNode: T, index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var S = {name: "S", adjacencies: [{weight: 6, targetNode: O, index: 0}, {weight: 2, targetNode: P, index: 1}, {weight: 2, targetNode: Z, index: 2}], weight: Number.MAX_VALUE, minPath: []};
    var T = {name: "T", adjacencies: [{weight: 5, targetNode: R, index: 0}, {weight: 3, targetNode: Q, index: 1}, {weight: 1, targetNode: P, index: 2}, {weight: 8, targetNode: "Z", index: 3}], weight: Number.MAX_VALUE, minPath: []};
    var Z = {name: "Z", adjacencies: [{weight: 2, targetNode: S, index: 0}, {weight: 8, targetNode: T, index: 1}], weight: Number.MAX_VALUE, minPath: []};*/
    console.log(runDijkstrasAlgorithm([A, B, C, D, E], "A", "E"));
}

function runDijkstrasAlgorithm(listOfNodes, startingNode, endingNode)
{
    //creating the unvisted list
    var unvisited = listOfNodes;
    var indexStart = find(unvisited, startingNode);
    var indexEnd = find(unvisited, endingNode);

    //var minNode = startingNode;
    //for(index = 0; index < listOfNodes.length; index++)
        //unvisited += listOfNodes[index];
    //JSON already has starting node, now set weight to 0
    unvisited[indexStart].weight = 0;
    //startingNode.weight = 0;
    //by default, minPath is null. so the first one has to have a non null path so that it can copy to all the other nodes.
    unvisited[indexStart].minPath = startingNode;

    //JSON already has ending node
    var chosenNode = unvisited[indexStart];
    while(chosenNode.name !== endingNode)
    {
        for(index = 0; index < chosenNode.adjacencies.length; index++)
        {
            //panda is that particular targetnode from that particular edge
            var panda = chosenNode.adjacencies[index].targetNode;
            console.log(panda);
            //update only if panda has been unvisited. if it has already been visited, we don't look at it.
            //basiclaly don't look at the targetnode if, as a node, the targetnode has already been looked at
            //confusing a bit. becuase panda is a target node. but that node only goes to unvisited if it was ever the chosenNode
            //so if panda was already a chosenNode, then don't look at it, basically, if that makes more sense
            if(find(panda, unvisited) !== -1)
            {
                var currentNode = unvisited[find(panda, unvisited)];
                var suggestedWeight = chosenNode.weight + chosenNode.adjacencies[index].weight;
                console.log(suggestedWeight);
                //only if my new weight < my old weight, do i update
                if(suggestedWeight < currentNode.weight)
                {
                    //index2 = find(listOfNodes, chosenNode.name);
                    index3 = find(unvisited, chosenNode.name);
                    //update values
                    //listOfNodes[index2].weight = suggestedWeight;
                    unvisited[index3].weight = suggestedWeight;

                    //so i can add myself, the panda, to the new improved chain. becuase the old chain came from the chosenNode, the startingNode of the edge (vs. targetNode).
                    //and THAT path/chain does not include the targetNode/panda. so you have to add itself to the new, min path. beecause the min path takes
                    //vestiges of the old chain from chosenNode + the panda node.

                    //var path = chosenNode.minPath;
                    //listOfNodes[index2].minPath = chosenNode.minPath;
                    //listOfNodes[index2].minPath += chosenNode.adjacencies[index];
                    unvisited[index3].minPath = chosenNode.minPath;
                    unvisited[index3].minPath += chosenNode.adjacencies[index];
                    //listOfNodes[index].minPath = path;
                }
            }
        }

        unvisited = binarySearchAndDestroyNode(chosenNode.name, unvisited);
        var minNode = {name: null, adjacencies: [], weight: Number.MAX_VALUE, minPath:[]};
        for(index = 0; index < unvisited.length; index++)
        {
            if(unvisited[index].weight < minNode.weight)
            {
                minNode = unvisited[index];
            }
            if(minNode.name === endingNode){
                unvisited[indexEnd].minPath += chosenNode.minPath;
            }
        }
        chosenNode = minNode;
        console.log(chosenNode);
    }

    //done!
    //this printout works becuase of polymorphism. eventually, chosenNode will = endingNode, and then when that happens, endingNode will have already been altered.
    //console.log("hi");
    console.log(unvisited[indexEnd].minPath);
    return unvisited[indexEnd].minPath;
}

//***need to change to binary search
//finds the index of a certain node in array
function find(array, name){
    for(index = 0; index < array.length; index++){
        if(array[index].name === name)
            return index;
    }
    return -1;
}

//precondition: checkThis is a Node and inThis is a list of Nodes
function binarySearch(checkThis, inThis)
{
    //returns a boolean. checks the path.
    //will return true if the path's last element is in the unselected.
    var min = 0;
    var max = inThis.length - 1;
    var index;
    //console.log(inThis);
    while(max - min > -1)
    {
        index = Math.floor((min + max)/2);
        if(inThis[index].name === checkThis)
        {
            //found it!
            return true;
        }
        else if(inThis[index].name.localeCompare(checkThis) < 0)
        {
            min = index + 1;
        }
        else
        {
            max = index - 1;
        }
    }

    return false;
}

//precondition: toBeDestroyed is a node name (string) and searchThis is a list of nodes
 function binarySearchAndDestroyNode(toBeDestroyed, searchThis) {
        //returns index of the toBeDestroyed within the array
        var min = 0;
        var max = searchThis.length - 1;
        var index;

        while (max - min > -1)
        {
            index = Math.floor((min + max) / 2);
            if (searchThis[index].name === toBeDestroyed)
            {
                searchThis.splice(index, 1);
                return searchThis;
            }
            else if (searchThis[index].name.toUpperCase().localeCompare(toBeDestroyed.toUpperCase()) < 0)
            {
                min = index + 1;
            }
            else
            {
                max = index - 1;
            }
        }
    }<|endoftext|>


<commit_before>import clone from 'lodash/clone'
import defaults from 'lodash/defaults'

export default {
  schema: {
    autoStart: {
      type: 'boolean',
      default: true
    },
    loop: {
      type: 'boolean',
      default: true
    },
    wait: {
      type: 'number',
      default: 2000
    },
    move: {
      type: 'number',
      default: 3000
    }
  },

  init: function () {
    this._currentWayPoint = -1
    this.el.setAttribute('animation__move', { startEvents: 'doNotFire', pauseEvents: 'pauseTour', resumeEvents:'resumeTour', property: 'position', easing: 'easeInOutSine', dur: 100 })
    this.el.setAttribute('animation__turn', { startEvents: 'doNotFire', pauseEvents: 'pauseTour', resumeEvents:'resumeTour', property: 'rotation', easing: 'easeInOutSine', dur: 100 })
    this._nextWaypointHandler = this._nextWaypoint.bind(this)
  },

  update: function () {
    this._waypoints = Array.from(this.el.querySelectorAll('[tour-waypoint]'))

    if(this.data.autoStart) {
      this.playTour()
    }
  },

  playTour: function () {
    if (!this._waypoints || !this._waypoints.length) {
      console.warn('camera tour has no waypoints')
      return
    }
    if (this._isPlaying) {
      if(this._isChangingAnimation) {
        clearTimeout(this._nextAnimationTimeout)
        this.goTo(this._waypoints[this._currentWayPoint].getAttribute('io3d-uuid'), this._isPlaying)
      } else {
        this.el.dispatchEvent(new CustomEvent('resumeTour'))
      }
      this._isPaused = false
    } else {
      this._isPlaying = true
      this._isPaused = false
      this.el.addEventListener('animation__move-complete', this._nextWaypointHandler)
      var next = this._waypoints[++this._currentWayPoint]
      if (next) this.goTo(next.getAttribute('io3d-uuid'), true)
      else if (this.data.loop) {
        this._currentWayPoint = 0
        this.goTo(this._waypoints[0].getAttribute('io3d-uuid'), true)
      }
    }
  },

  pauseTour: function () {
    this._isPaused = true
    this.el.dispatchEvent(new CustomEvent('pauseTour'))
  },

  stopTour: function () {
    this.pauseTour()
    this.el.removeEventListener('animation__move-complete', this._nextWaypointHandler)
    this._isPlaying = false
    this._isPaused = false
  },

  goTo: function (uuid, keepPlaying) {
    this._isPlaying = !!keepPlaying
    var target = this._waypoints.find(function (item) { return item.getAttribute('io3d-uuid') === uuid })
    if (!target) {
      console.error('The given waypoint ' + uuid + ' does not exist. Available waypoints:', this._waypoints.map(function (elem) { return elem.getAttribute('io3d-uuid') }))
      return
    }

    this.animate(target)
  },

  // set camera position and rotation by providing changes for certain axes
  // to reset camera to walking mode do:
  // updateViewPoint({position: {y:1.6}, rotation: {x:0})
  updateViewPoint: function (args) {
    args = args || {}
    if (typeof args !== 'object') {
      console.error('not supported camera view point: ' + args)
      return
    }
    var posChange = args.position || {}
    var rotChange = args.rotation || {}

    this._isPlaying = false
    // apply changes to current camera position
    var pos = defaults({}, posChange, clone(this.el.getAttribute('position')))
    var rot = defaults({}, rotChange, clone(this.el.getAttribute('rotation')))

    var target = {
      position: pos,
      rotation: rot
    }
    this.animate(target)
  },

  animate: function (bookmark) {
    var isDomElement = isElement(bookmark)
    var entity = this.el
    var newPosition = isDomElement ? bookmark.getAttribute('position') : bookmark.position
    var newRotation = isDomElement ? bookmark.getAttribute('rotation') : bookmark.rotation
    var startPosition = entity.getAttribute('position')
    var startRotation = entity.getAttribute('rotation')

    // compute shortest rotation
    newRotation = normalizeRotation(startRotation, newRotation)

    // compute distance to adapt speed
    var d = dist(startPosition, newPosition)
    // compute angle difference to adapt speed
    var angle = Math.abs(startRotation.y - newRotation.y)
    // compute animation time
    // add 1 to the this.data.move parameter to allow users to specify 0 without the animation cancelling out
    var t = Math.round((this.data.move === undefined ? 3000 : this.data.move + 1) / 6 * (d + angle / 30))
    if (t > Math.max(5000, this.data.move)) t = Math.max(5000, this.data.move)
    // prevent zero length animation
    if (!t) return this._nextWaypoint()

    entity.components.animation__move.pauseAnimation()
    entity.components.animation__turn.pauseAnimation()
    entity.components.animation__move.data.dur = t
    entity.components.animation__move.data.from = startPosition
    entity.components.animation__move.data.to = newPosition
    entity.components.animation__move.update()
    entity.components.animation__turn.data.dur = t
    entity.components.animation__turn.data.from = startRotation
    entity.components.animation__turn.data.to = newRotation
    entity.components.animation__turn.update()
    entity.components.animation__move.resumeAnimation()
    entity.components.animation__turn.resumeAnimation()
    this._isChangingAnimation = false
  },

  _nextWaypoint: function () {
    // FIXME: Find the root cause of the weird jumpy behaviour when using WASD controls
    this.setAttribute('position', AFRAME.utils.coordinates.stringify(this.getAttribute('position')))

    if (!this._isPlaying) return this.stopTour()
    if (this._currentWayPoint === this._waypoints.length - 1) {
      if (!this.data.loop) return
      this._currentWayPoint = -1
    }
    this._isChangingAnimation = true
    var next = this._waypoints[++this._currentWayPoint]
    this._nextAnimationTimeout = setTimeout(function () { this.goTo(next.getAttribute('io3d-uuid'), this._isPlaying) }.bind(this), this.data.wait === undefined ? 0 : this.data.wait)
  }
}

// we want to prevent excessive spinning in rotations
function normalizeRotation(start, end) {
  // prevent angles larger than 360
  var normRot = {
    x: end.x % 360,
    y: end.y % 360,
    z: end.z % 360,
  }
  // if delta is bigger than 180 degrees we spin to the other direction
  Object.keys(normRot).forEach(function(axis) {
    if (normRot[axis] - start[axis] > 180) normRot[axis] -= 360
  })
  return normRot
}

function dist(p, q) {
  var a = parseFloat(q.x) - parseFloat(p.x)
  var b = parseFloat(q.y) - parseFloat(p.y)
  var c = parseFloat(q.z) - parseFloat(p.z)
  return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2) + Math.pow(c, 2))
}

// Returns true if it is a DOM element
// https://stackoverflow.com/a/384380/2835973
function isElement(o){
  var DOM_ELEMENT = 1
  return (
    typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
      o && typeof o === "object" && o !== null && o.nodeType === DOM_ELEMENT && typeof o.nodeName==="string"
  );
}
<commit_msg>Fix cases where still heavy spinning was happening #49<commit_after>import clone from 'lodash/clone'
import defaults from 'lodash/defaults'

export default {
  schema: {
    autoStart: {
      type: 'boolean',
      default: true
    },
    loop: {
      type: 'boolean',
      default: true
    },
    wait: {
      type: 'number',
      default: 2000
    },
    move: {
      type: 'number',
      default: 3000
    }
  },

  init: function () {
    this._currentWayPoint = -1
    this.el.setAttribute('animation__move', { startEvents: 'doNotFire', pauseEvents: 'pauseTour', resumeEvents:'resumeTour', property: 'position', easing: 'easeInOutSine', dur: 100 })
    this.el.setAttribute('animation__turn', { startEvents: 'doNotFire', pauseEvents: 'pauseTour', resumeEvents:'resumeTour', property: 'rotation', easing: 'easeInOutSine', dur: 100 })
    this._nextWaypointHandler = this._nextWaypoint.bind(this)
  },

  update: function () {
    this._waypoints = Array.from(this.el.querySelectorAll('[tour-waypoint]'))

    if(this.data.autoStart) {
      this.playTour()
    }
  },

  playTour: function () {
    if (!this._waypoints || !this._waypoints.length) {
      console.warn('camera tour has no waypoints')
      return
    }
    if (this._isPlaying) {
      if(this._isChangingAnimation) {
        clearTimeout(this._nextAnimationTimeout)
        this.goTo(this._waypoints[this._currentWayPoint].getAttribute('io3d-uuid'), this._isPlaying)
      } else {
        this.el.dispatchEvent(new CustomEvent('resumeTour'))
      }
      this._isPaused = false
    } else {
      this._isPlaying = true
      this._isPaused = false
      this.el.addEventListener('animation__move-complete', this._nextWaypointHandler)
      var next = this._waypoints[++this._currentWayPoint]
      if (next) this.goTo(next.getAttribute('io3d-uuid'), true)
      else if (this.data.loop) {
        this._currentWayPoint = 0
        this.goTo(this._waypoints[0].getAttribute('io3d-uuid'), true)
      }
    }
  },

  pauseTour: function () {
    this._isPaused = true
    this.el.dispatchEvent(new CustomEvent('pauseTour'))
  },

  stopTour: function () {
    this.pauseTour()
    this.el.removeEventListener('animation__move-complete', this._nextWaypointHandler)
    this._isPlaying = false
    this._isPaused = false
  },

  goTo: function (uuid, keepPlaying) {
    this._isPlaying = !!keepPlaying
    var target = this._waypoints.find(function (item) { return item.getAttribute('io3d-uuid') === uuid })
    if (!target) {
      console.error('The given waypoint ' + uuid + ' does not exist. Available waypoints:', this._waypoints.map(function (elem) { return elem.getAttribute('io3d-uuid') }))
      return
    }

    this.animate(target)
  },

  // set camera position and rotation by providing changes for certain axes
  // to reset camera to walking mode do:
  // updateViewPoint({position: {y:1.6}, rotation: {x:0})
  updateViewPoint: function (args) {
    args = args || {}
    if (typeof args !== 'object') {
      console.error('not supported camera view point: ' + args)
      return
    }
    var posChange = args.position || {}
    var rotChange = args.rotation || {}

    this._isPlaying = false
    // apply changes to current camera position
    var pos = defaults({}, posChange, clone(this.el.getAttribute('position')))
    var rot = defaults({}, rotChange, clone(this.el.getAttribute('rotation')))

    var target = {
      position: pos,
      rotation: rot
    }
    this.animate(target)
  },

  animate: function (bookmark) {
    var isDomElement = isElement(bookmark)
    var entity = this.el
    var newPosition = isDomElement ? bookmark.getAttribute('position') : bookmark.position
    var newRotation = isDomElement ? bookmark.getAttribute('rotation') : bookmark.rotation
    var startPosition = entity.getAttribute('position')
    var startRotation = entity.getAttribute('rotation')

    // normalize start and end rotation and find shortest arc for each rotation
    var normalizedRotations = getNormalizeRotations(startRotation, newRotation)
    newRotation = normalizedRotations.end
    startRotation = normalizedRotations.start

    // compute distance to adapt speed
    var d = dist(startPosition, newPosition)
    // compute angle difference to adapt speed
    var angle = Math.abs(startRotation.y - newRotation.y)
    // compute animation time
    // add 1 to the this.data.move parameter to allow users to specify 0 without the animation cancelling out
    var t = Math.round((this.data.move === undefined ? 3000 : this.data.move + 1) / 6 * (d + angle / 30))
    if (t > Math.max(5000, this.data.move)) t = Math.max(5000, this.data.move)
    // prevent zero length animation
    if (!t) return this._nextWaypoint()

    entity.components.animation__move.pauseAnimation()
    entity.components.animation__turn.pauseAnimation()
    entity.components.animation__move.data.dur = t
    entity.components.animation__move.data.from = startPosition
    entity.components.animation__move.data.to = newPosition
    entity.components.animation__move.update()
    entity.components.animation__turn.data.dur = t
    entity.components.animation__turn.data.from = startRotation
    entity.components.animation__turn.data.to = newRotation
    entity.components.animation__turn.update()
    entity.components.animation__move.resumeAnimation()
    entity.components.animation__turn.resumeAnimation()
    this._isChangingAnimation = false
  },

  _nextWaypoint: function () {
    // FIXME: Find the root cause of the weird jumpy behaviour when using WASD controls
    this.setAttribute('position', AFRAME.utils.coordinates.stringify(this.getAttribute('position')))

    if (!this._isPlaying) return this.stopTour()
    if (this._currentWayPoint === this._waypoints.length - 1) {
      if (!this.data.loop) return
      this._currentWayPoint = -1
    }
    this._isChangingAnimation = true
    var next = this._waypoints[++this._currentWayPoint]
    this._nextAnimationTimeout = setTimeout(function () { this.goTo(next.getAttribute('io3d-uuid'), this._isPlaying) }.bind(this), this.data.wait === undefined ? 0 : this.data.wait)
  }
}

// we want to prevent excessive spinning in rotations
function getNormalizeRotations(start, end) {
  // normalize both rotations
  var normStart = normalizeRotation(start)
  var normEnd = normalizeRotation(end)
  // find the shortest arc for each rotation
  Object.keys(start).forEach(function(axis) {
    if (normEnd[axis] - normStart[axis] > 180) normEnd[axis] -= 360
  })
  return { start: normStart, end: normEnd }
}

function normalizeRotation(rot) {
  return {
    x: rot.x % 360,
    y: rot.y % 360,
    z: rot.z % 360,
  }
}

function dist(p, q) {
  var a = parseFloat(q.x) - parseFloat(p.x)
  var b = parseFloat(q.y) - parseFloat(p.y)
  var c = parseFloat(q.z) - parseFloat(p.z)
  return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2) + Math.pow(c, 2))
}

// Returns true if it is a DOM element
// https://stackoverflow.com/a/384380/2835973
function isElement(o){
  var DOM_ELEMENT = 1
  return (
    typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
      o && typeof o === "object" && o !== null && o.nodeType === DOM_ELEMENT && typeof o.nodeName==="string"
  );
}
<|endoftext|>


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

  angular
    .module('users')
    .controller('InviteController', InviteController);

  /* @ngInject */
  function InviteController(InvitationService, Authentication, $window, $location, $timeout, $state) {

    // ViewModel
    var vm = this;

    // Exposed to the view
    vm.invitation = InvitationService.get();

    configureAddThis();

    /**
     * Configure add this share buttons
     * https://www.addthis.com/academy/category/developer-documentation/
     */
    function configureAddThis() {

      // Ensure we have code and continue only then
      vm.invitation.$promise.then(function(invitation) {

        var code = invitation.code.toUpperCase();

        // Build full domain dynamically
        var domain =
          $location.protocol() +
          '://' +
          $location.host() +
          ($location.port() !== '80' || $location.port() !== '443' ? ':' + $location.port() : '');

        // Build full signup URL dynamically (including domain and path)
        var signUpUrl = domain + $state.href('signup', { code: invitation.code });

        var inviteTextPersonal =
          'Would you like to join me on Trustroots.org? ' +
          'It\'s an invitation only travellers community where we ' +
          'host and meet each other.';

        var inviteSingupUrlText =
          'You can sign up at ' + signUpUrl +
          ' (link is valid for 24 hours)';

        var inviteTextGeneric =
          'You have been invited to join Trustroots, an invite only ' +
          'travellers community where people host and meet each other.' +
          inviteSingupUrlText;

        // UI Configuration
        // @link https://www.addthis.com/academy/addthis-email-sharing-service/
        // @link https://www.addthis.com/academy/the-addthis_config-variable/
        $window.addthis_config = {
          ui_email_from: Authentication.user.email,
          ui_email_note: inviteTextPersonal,
          data_track_clickback: false,
          data_track_addressbar: false
        };

        // Configure AddThis to send GA data
        // @link https://www.addthis.com/academy/integrating-with-google-analytics/
        if ($window.gaId) {
          $window.addthis_config.data_ga_property = $window.gaId;
        }

        // Sharing Configuration
        // https://www.addthis.com/academy/the-addthis_share-variable/
        $window.addthis_share = {
          url: signUpUrl,
          title: Authentication.user.displayName + ' invites you to join Trustroots.org',
          description: inviteTextGeneric,
          // Image used for sharing
          // Image URL would be appended to "note" section in invite emails as well
          // media: domain + '/modules/core/img/og_image.jpg',
          email_template: 'invite',
          email_vars: {
            'code': code,
            'invitee': Authentication.user.displayName
          }
        };

      });
    }

  }

}());
<commit_msg>Fix invite url port appearing when not needed<commit_after>(function () {
  'use strict';

  angular
    .module('users')
    .controller('InviteController', InviteController);

  /* @ngInject */
  function InviteController(InvitationService, Authentication, $window, $location, $timeout, $state) {

    // ViewModel
    var vm = this;

    // Exposed to the view
    vm.invitation = InvitationService.get();

    configureAddThis();

    /**
     * Configure add this share buttons
     * https://www.addthis.com/academy/category/developer-documentation/
     */
    function configureAddThis() {

      // Ensure we have code and continue only then
      vm.invitation.$promise.then(function(invitation) {

        var code = invitation.code.toUpperCase();

        // Build full domain dynamically
        var domain =
          $location.protocol() +
          '://' +
          $location.host() +
          ($location.port() === '80' || $location.port() === '443' ? '' : ':' + $location.port());

        // Build full signup URL dynamically (including domain and path)
        var signUpUrl = domain + $state.href('signup', { code: invitation.code });

        var inviteTextPersonal =
          'Would you like to join me on Trustroots.org? ' +
          'It\'s an invitation only travellers community where we ' +
          'host and meet each other.';

        var inviteSingupUrlText =
          'You can sign up at ' + signUpUrl +
          ' (link is valid for 24 hours)';

        var inviteTextGeneric =
          'You have been invited to join Trustroots, an invite only ' +
          'travellers community where people host and meet each other.' +
          inviteSingupUrlText;

        // UI Configuration
        // @link https://www.addthis.com/academy/addthis-email-sharing-service/
        // @link https://www.addthis.com/academy/the-addthis_config-variable/
        $window.addthis_config = {
          ui_email_from: Authentication.user.email,
          ui_email_note: inviteTextPersonal,
          data_track_clickback: false,
          data_track_addressbar: false
        };

        // Configure AddThis to send GA data
        // @link https://www.addthis.com/academy/integrating-with-google-analytics/
        if ($window.gaId) {
          $window.addthis_config.data_ga_property = $window.gaId;
        }

        // Sharing Configuration
        // https://www.addthis.com/academy/the-addthis_share-variable/
        $window.addthis_share = {
          url: signUpUrl,
          title: Authentication.user.displayName + ' invites you to join Trustroots.org',
          description: inviteTextGeneric,
          // Image used for sharing
          // Image URL would be appended to "note" section in invite emails as well
          // media: domain + '/modules/core/img/og_image.jpg',
          email_template: 'invite',
          email_vars: {
            'code': code,
            'invitee': Authentication.user.displayName
          }
        };

      });
    }

  }

}());
<|endoftext|>