<commit_before>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

export class CarouselCard {
  constructor(elem, tags) {
    this.elem = elem;
    this.elem.classList.add('carousel-card');
    // Move elem into a wrapper div
    this.wrapper = document.createElement('div');
    this.wrapper.className = 'carousel-card-wrapper';
    this.elem.parentNode.insertBefore(this.wrapper, this.elem);
    this.wrapper.appendChild(this.elem);

    this.tags = tags || elem.dataset.tags.split(',').map(t => t.trim());
    this.titleElem = elem.getElementsByTagName('h1')[0];

    this.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    // Title manipulation interface

    const _this2 = this;
    /**
     * Interface for updating styles on the title.
     * Examples:
     *   this.title.translate = '-10px'
     *   this.title.opacity = 0.5
     */
    this.title = {
      _apply() {
        _this2.titleElem.style.transform = `translateZ(25px) translateX(${this._translate})`;
        _this2.titleElem.style.opacity = this._opacity;
      },
      get translate() { return this._translate; },
      set translate(t) { this._translate = t; this._apply(); },
      get opacity() { return this._opacity; },
      set opacity(o) { this._opacity = o; this._apply(); },
    };
  }

  /**
   * Update the card wrapper's CSS in order to reflect changes that were made to its JS
   * representation.
   */
  _applyTransform() {
    this.wrapper.style.transform = [
      'translate(-50%, -50%)',
      `translateX(${this._translate})`,
    ].join(' ');
  }

  // Translation

  get translate() { return this._translate; }
  set translate(val) { this._translate = val; this._applyTransform(); }

  // Transition time

  get transitionTime() { return this._transitionTime; }
  set transitionTime(secs) {
    this._transitionTime = secs;
    this.wrapper.style.transition = `transform ${secs}s`;
    this.titleElem.style.transition = `transform ${secs}s, opacity ${secs}s`;
  }

  setEnabled(enable = true) {
    const totalTime = this.transitionTime * 1000;
    const startTime = new Date();

    // Recursively adjust using setAnimationFrame until transitionTime is reached
    function step(parallax) {
      const timeElapsed = new Date() - startTime;
      // Still going
      if (timeElapsed < totalTime) {
        const progress = (timeElapsed / totalTime);
        if (enable) parallax.rotmax = progress * 10;
        else parallax.rotmax = (1 - progress) * 10;
        parallax.rotate();
        requestAnimationFrame(() => step(parallax));
      } else {
        parallax.rotmax = enable ? 10 : 0;
      }
    }
    step(this.parallax);
  }

  disable() { this.setEnabled(false); }
  enable() { this.setEnabled(true); }
}

/** Collects and orchestrates multiple CarouselCards */
export class Carousel {
  constructor(cards) {
    this.cards = cards;
    this._position = 0;
    // Trigger immediate layout of cards
    this.position = this._position;
  }

  // Transition time logic

  get baseTransitionTime() {
    const answer = this.cards[0].baseTransitionTime;
    // Warn if not every card has the same baseTransitionTime
    if (!this.cards.every(card => card.baseTransitionTime === answer)) console.warn('Carousel.baseTransitionTime called but baseTransitionTime of cards is inconsistent');
    return answer;
  }
  get transitionTime() {
    const answer = this.cards[0].transitionTime;
    // Warn if not every card has the same transitionTime
    if (!this.cards.every(card => card.transitionTime === answer)) console.warn('Carousel.transitionTime called but transitionTime of cards is inconsistent');
    return answer;
  }
  set transitionTime(secs) {
    this.cards.forEach((c) => { c.transitionTime = secs; });
  }

  // Rearrange

  get position() { return this._position; }
  set position(pos) {
    const delta = Math.abs(this._position - pos);
    // mx + b
    this.transitionTime = ((0.5 * this.baseTransitionTime) * (delta - 1)) + this.baseTransitionTime;

    this.cards.forEach((card, index) => {
      const cardPosition = index - pos;
      const cardTranslation = `${48 * cardPosition}vw`;
      card.translate = cardTranslation;
      // Center card
      if (cardPosition === 0) {
        card.title.opacity = 1;
        card.title.translate = 0;
        card.enable();
      // Not center card
      } else {
        card.title.opacity = 0;
        card.title.translate = (pos > this._position) ? '5vw' : '-5vw';
        card.disable();
      }
    });

    this._position = pos;
  }

  get minIndex() { return 0; } // eslint-disable-line class-methods-use-this
  get maxIndex() { return this.cards.length - 1; }

  left() {
    if (this.position > this.minIndex) this.position -= 1;
  }
  right() {
    if (this.position < this.maxIndex) this.position += 1;
  }
}
<commit_msg>Add centerSelf methods to cards<commit_after>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

export class CarouselCard {
  constructor(elem, tags) {
    this.elem = elem;
    this.elem.classList.add('carousel-card');
    // Move elem into a wrapper div
    this.wrapper = document.createElement('div');
    this.wrapper.className = 'carousel-card-wrapper';
    this.elem.parentNode.insertBefore(this.wrapper, this.elem);
    this.wrapper.appendChild(this.elem);

    this.tags = tags || elem.dataset.tags.split(',').map(t => t.trim());
    this.titleElem = elem.getElementsByTagName('h1')[0];

    this.carousel = undefined; // Will be set when added to a carousel
    this.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    // Title manipulation interface

    const _this2 = this;
    /**
     * Interface for updating styles on the title.
     * Examples:
     *   this.title.translate = '-10px'
     *   this.title.opacity = 0.5
     */
    this.title = {
      _apply() {
        _this2.titleElem.style.transform = `translateZ(25px) translateX(${this._translate})`;
        _this2.titleElem.style.opacity = this._opacity;
      },
      get translate() { return this._translate; },
      set translate(t) { this._translate = t; this._apply(); },
      get opacity() { return this._opacity; },
      set opacity(o) { this._opacity = o; this._apply(); },
    };
  }

  /**
   * Update the card wrapper's CSS in order to reflect changes that were made to its JS
   * representation.
   */
  _applyTransform() {
    this.wrapper.style.transform = [
      'translate(-50%, -50%)',
      `translateX(${this._translate})`,
    ].join(' ');
  }

  // Translation

  get translate() { return this._translate; }
  set translate(val) { this._translate = val; this._applyTransform(); }

  // Transition time

  get transitionTime() { return this._transitionTime; }
  set transitionTime(secs) {
    this._transitionTime = secs;
    this.wrapper.style.transition = `transform ${secs}s`;
    this.titleElem.style.transition = `transform ${secs}s, opacity ${secs}s`;
  }

  setEnabled(enable = true) {
    const totalTime = this.transitionTime * 1000;
    const startTime = new Date();

    // Recursively adjust using setAnimationFrame until transitionTime is reached
    function step(parallax) {
      const timeElapsed = new Date() - startTime;
      // Still going
      if (timeElapsed < totalTime) {
        const progress = (timeElapsed / totalTime);
        if (enable) parallax.rotmax = progress * 10;
        else parallax.rotmax = (1 - progress) * 10;
        parallax.rotate();
        requestAnimationFrame(() => step(parallax));
      } else {
        parallax.rotmax = enable ? 10 : 0;
      }
    }
    step(this.parallax);
  }
  disable() { this.setEnabled(false); }
  enable() { this.setEnabled(true); }

  centerSelf() {
    this.carousel.position = this.carousel.cards.indexOf(this);
  }
}

/** Collects and orchestrates multiple CarouselCards */
export class Carousel {
  constructor(cards) {
    this.cards = cards;
    this.cards.forEach((c) => { c.carousel = this; });
    this._position = 0;
    // Trigger immediate layout of cards
    this.position = this._position;
  }

  // Transition time logic

  get baseTransitionTime() {
    const answer = this.cards[0].baseTransitionTime;
    // Warn if not every card has the same baseTransitionTime
    if (!this.cards.every(card => card.baseTransitionTime === answer)) console.warn('Carousel.baseTransitionTime called but baseTransitionTime of cards is inconsistent');
    return answer;
  }
  get transitionTime() {
    const answer = this.cards[0].transitionTime;
    // Warn if not every card has the same transitionTime
    if (!this.cards.every(card => card.transitionTime === answer)) console.warn('Carousel.transitionTime called but transitionTime of cards is inconsistent');
    return answer;
  }
  set transitionTime(secs) {
    this.cards.forEach((c) => { c.transitionTime = secs; });
  }

  // Rearrange

  get position() { return this._position; }
  set position(pos) {
    const delta = Math.abs(this._position - pos);
    // mx + b
    this.transitionTime = ((0.5 * this.baseTransitionTime) * (delta - 1)) + this.baseTransitionTime;

    this.cards.forEach((card, index) => {
      const cardPosition = index - pos;
      const cardTranslation = `${48 * cardPosition}vw`;
      card.translate = cardTranslation;
      // Center card
      if (cardPosition === 0) {
        card.title.opacity = 1;
        card.title.translate = 0;
        card.enable();
      // Not center card
      } else {
        card.title.opacity = 0;
        card.title.translate = (pos > this._position) ? '5vw' : '-5vw';
        card.disable();
      }
    });

    this._position = pos;
  }

  get minIndex() { return 0; } // eslint-disable-line class-methods-use-this
  get maxIndex() { return this.cards.length - 1; }

  left() {
    if (this.position > this.minIndex) this.position -= 1;
  }
  right() {
    if (this.position < this.maxIndex) this.position += 1;
  }
}
<|endoftext|>


<commit_before>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

export class CarouselTag {
  constructor(elem) {
    this.elem = elem;
    this.name = elem.textContent;
    this.carousel = undefined; // Will be set when added to a carousel

    this.elem.addEventListener('click', () => this.select());
  }

  select() { if (this.carousel) this.carousel.filter(this); }
}

export class CarouselCard {
  constructor(elem, tagNames) {
    this.elem = elem;
    this.elem.classList.add('carousel-card');
    // Move elem into a wrapper div
    this.wrapper = document.createElement('div');
    this.wrapper.className = 'carousel-card-wrapper';
    this.elem.parentNode.insertBefore(this.wrapper, this.elem);
    this.wrapper.appendChild(this.elem);

    this.tagNames = tagNames || elem.dataset.tags.split(',').map(t => t.trim());
    this.titleElem = elem.getElementsByTagName('h1')[0];

    this.carousel = undefined; // Will be set when added to a carousel
    this.hidden = false;
    this.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    this.elem.addEventListener('click', () => this.clickHandler());
    this.wrapper.addEventListener('scroll', () => this.scrollHandler());

    // Title manipulation interface

    const _this2 = this;
    /**
     * Interface for updating styles on the title.
     * Examples:
     *   this.title.translate = '-10px'
     *   this.title.opacity = 0.5
     */
    this.title = {
      _elem: _this2.titleElem,
      display() { this._elem.className = 'in-initial'; },
      fadeIn() { this._elem.className = 'fade-in'; },
      fadeOut() { this._elem.className = 'fade-out'; },
      fadeInLeft() { this._elem.className = 'in-from-left'; },
      fadeInRight() { this._elem.className = 'in-from-right'; },
      fadeOutLeft() { this._elem.className = 'out-to-left'; },
      fadeOutRight() { this._elem.className = 'out-to-right'; },
      fadeOutTop() { this._elem.className = 'out-to-top'; },
      moveDown() { this._elem.className = 'down'; },
      moveUp() { this._elem.className = 'up'; },
    };
  }

  /**
   * Update the card wrapper's CSS in order to reflect changes that were made to its JS
   * representation.
   */
  _applyTransform() {
    this.wrapper.style.transform = [
      'translateX(-50%)',
      `translateX(${this._translate})`,
    ].join(' ');
  }

  // Translation

  get translate() { return this._translate; }
  set translate(val) { this._translate = val; this._applyTransform(); }

  // Transition time

  get transitionTime() { return this._transitionTime; }
  set transitionTime(secs) {
    this._transitionTime = secs;
    this.wrapper.style.transition = `transform ${secs}s`;
  }

  setEnabled(enable = true) {
    const totalTime = this.transitionTime * 1000;
    const startTime = new Date();

    // Recursively adjust using setAnimationFrame until transitionTime is reached
    function step(parallax) {
      const timeElapsed = new Date() - startTime;
      // Still going
      if (timeElapsed < totalTime) {
        const progress = (timeElapsed / totalTime);
        if (enable) parallax.rotmax = progress * 10;
        else parallax.rotmax = (1 - progress) * 10;
        parallax.rotate();
        requestAnimationFrame(() => step(parallax));
      } else {
        parallax.rotmax = enable ? 10 : 0;
      }
    }
    step(this.parallax);
  }
  disable() { this.setEnabled(false); }
  enable() { this.setEnabled(true); }

  hide() {
    this.hidden = true;
    this.elem.classList.add('hidden');
  }
  show() {
    this.hidden = false;
    this.elem.classList.remove('hidden');
  }

  // Move the carousel so that this card is in the center
  centerSelf() {
    const thisIndex = this.carousel.cards.indexOf(this);
    this.carousel.position = this.carousel.adjustIndex(thisIndex);
  }

  // Bigger!
  expand() {
    // Prevents incorrect result when expanding and collapsing rapidly. Ensures that a pre-existing
    // timeout won't remove the expanded-x class after it's been added by this method.
    clearInterval(this._expansionTimeout);

    this.elem.classList.add('expanded-x');
    this.title.moveDown();
    this.disable();
    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.add('expanded-y');
      this._expansionTimeout = setTimeout(() => { this.elem.style.maxHeight = 'none'; this.elem.classList.remove('expanded-y'); }, 500);
      document.getElementById('email').classList.add('hidden');
      window.nav.disableScrollBinding = true;
    }, 500);

    this.expanded = true;
    if (this.carousel) this.carousel.expanded(this);
  }
  collapse() {
    clearInterval(this._expansionTimeout);

    this.elem.style.maxHeight = '';
    this.elem.classList.add('expanded-y');
    setTimeout(() => this.elem.classList.remove('expanded-y'), 10);
    document.getElementById('email').classList.remove('hidden');
    window.nav.disableScrollBinding = false;

    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.remove('expanded-x');
      this.title.moveUp();
      this.enable();
    }, 500);

    this.expanded = false;
    if (this.carousel) this.carousel.collapsed(this);
  }
  toggleExpand() {
    this[this.expanded ? 'collapse' : 'expand']();
  }

  fade() { this.elem.style.opacity = 0.5; }
  opacify() { this.elem.style.opacity = 1; }

  clickHandler() {
    if (this.carousel && !this.hidden) {
      const i1 = this.carousel.cards.indexOf(this);
      const i2 = this.carousel.adjustIndex(i1);
      if (this.carousel.position !== i2) this.centerSelf();
      else this.toggleExpand();
    } else if (!this.hidden) {
      this.toggleExpand();
    }
  }

  scrollHandler() {
    const transitionDistance = window.innerHeight / 10;
    const progress = Math.min(this.wrapper.scrollTop / transitionDistance, 1);

    const nav = document.getElementById('navigation');
    const tags = document.querySelector('.tags');
    nav.style.opacity = 0.5 * (1 - progress);
    tags.style.opacity = 1 - progress;

    // Turn off pointer events when halfway faded
    nav.style.pointerEvents = tags.style.pointerEvents = progress < 0.5 ? '' : 'none';
  }
}

/** Collects and orchestrates multiple CarouselCards */
export class Carousel {
  constructor(cards, tags) {
    this.tags = tags;
    this.tags.forEach((t) => { t.carousel = this; });
    this.cards = cards;
    this.cards.forEach((c) => { c.carousel = this; });
    this.hiddenIndices = [];

    this._position = 0;
    this.cards[this._position].title.display();
    // Trigger immediate layout of cards
    this.position = this._position;

    window.addEventListener('keydown', (e) => {
      // Carousel left/right with arrow keys, but only if carousel is expanded.
      // Carousel is expanded when the parent is #work-wrapper, the parent is .screen if it's not
      const projectsContainer = document.getElementsByClassName('projects-container')[0];
      if (projectsContainer.parentNode.id === 'work-wrapper') {
        if (e.key === 'ArrowRight') this.right();
        if (e.key === 'ArrowLeft') this.left();
      }
    });
  }

  // Transition time logic

  get baseTransitionTime() {
    const answer = this.cards[0].baseTransitionTime;
    // Warn if not every card has the same baseTransitionTime
    if (!this.cards.every(card => card.baseTransitionTime === answer)) console.warn('Carousel.baseTransitionTime called but baseTransitionTime of cards is inconsistent');
    return answer;
  }
  get transitionTime() {
    const answer = this.cards[0].transitionTime;
    // Warn if not every card has the same transitionTime
    if (!this.cards.every(card => card.transitionTime === answer)) console.warn('Carousel.transitionTime called but transitionTime of cards is inconsistent');
    return answer;
  }
  set transitionTime(secs) {
    this.cards.forEach((c) => { c.transitionTime = secs; });
  }

  // Rearrange

  // Adjust an index based on the number of hidden cards that come before it
  adjustIndex(index) {
    return index - this.hiddenIndices.filter(i => i < index).length;
  }

  get position() { return this._position; }
  set position(pos) {
    (async () => {
      const delta = Math.abs(this._position - pos);

      // Adjust transition time based on how many cards we're jumping
      const btt = this.baseTransitionTime;
      this.transitionTime = ((0.5 * btt) * ((delta || 1) - 1)) + btt;

      // If any card is open, collapse it and wait
      if (delta > 0 && typeof this.expandedIndex !== 'undefined') {
        this.cards[this.expandedIndex].collapse();
        await delay(1000);
      }

      // Apply certain changes to each card
      this.cards.forEach((card, index) => {
        const adj = i => this.adjustIndex(i);
        const cardPosition = adj(index) - pos;
        const oldPosition = cardPosition - (this._position - pos);
        // Calculate an offset based on position relative to any expanded card
        let cardOffset = 0;
        if (adj(index) > adj(this.expandedIndex)) cardOffset = 12.5;
        if (adj(index) < adj(this.expandedIndex)) cardOffset = -12.5;
        // Set translation
        const cardTranslation = `${(48 * cardPosition) + cardOffset}vw`;
        card.translate = cardTranslation;

        // Expand / disable cards based on whether they're centered
        if (cardPosition === 0 && !card.expanded) card.enable();
        else card.disable(); // Not center card

        // Apply animations to titles
        if (cardPosition === 0) { // Center card
          if (oldPosition < 0) card.title.fadeInLeft(); // coming from left
          if (oldPosition > 0) card.title.fadeInRight(); // coming from right
        } else if (oldPosition === 0) {
          if (cardPosition < 0) card.title.fadeOutLeft(); // going left
          if (cardPosition > 0) card.title.fadeOutRight(); // going right
        }
      });
      // Update internally-stored position tracker
      this._position = pos;
    })();
  }

  expanded(card) {
    this.expandedIndex = this.cards.indexOf(card);
    this.position = this.position; // Re-layout
    this.cards.forEach((c) => {
      if (c !== card) c.fade();
    });
  }
  collapsed() {
    this.expandedIndex = undefined;
    setTimeout(() => {
      this.position = this.position; // Re-layout
      this.cards.forEach(c => c.opacify());
    }, 500);
  }

  get minIndex() { return 0; } // eslint-disable-line class-methods-use-this
  get maxIndex() { return this.cards.length - this.hiddenIndices.length - 1; }

  left() {
    if (this.position > this.minIndex) this.position -= 1;
  }
  right() {
    if (this.position < this.maxIndex) this.position += 1;
  }

  async filter(tag) {
    const cardsToRemove = this.cards.filter(c => !c.tagNames.includes(tag.name));
    this.cards.forEach(c => c.show()); // Clean slate
    cardsToRemove.forEach(c => c.hide()); // Play removed animation on affected cards

    if (cardsToRemove.length) await delay(500);

    this.hiddenIndices = cardsToRemove.map(c => this.cards.indexOf(c));
    this.position = this.position; // Re-layout
  }

  async clearFilter() {
    if (this.hiddenIndices.length) {
      this.hiddenIndices = [];
      this.position = this.position; // Re-layout
      await delay(500);
      this.cards.forEach(c => c.show());
    }
  }
}
<commit_msg>Correct cases in which filtering results in a position past the last card<commit_after>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

export class CarouselTag {
  constructor(elem) {
    this.elem = elem;
    this.name = elem.textContent;
    this.carousel = undefined; // Will be set when added to a carousel

    this.elem.addEventListener('click', () => this.select());
  }

  select() { if (this.carousel) this.carousel.filter(this); }
}

export class CarouselCard {
  constructor(elem, tagNames) {
    this.elem = elem;
    this.elem.classList.add('carousel-card');
    // Move elem into a wrapper div
    this.wrapper = document.createElement('div');
    this.wrapper.className = 'carousel-card-wrapper';
    this.elem.parentNode.insertBefore(this.wrapper, this.elem);
    this.wrapper.appendChild(this.elem);

    this.tagNames = tagNames || elem.dataset.tags.split(',').map(t => t.trim());
    this.titleElem = elem.getElementsByTagName('h1')[0];

    this.carousel = undefined; // Will be set when added to a carousel
    this.hidden = false;
    this.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    this.elem.addEventListener('click', () => this.clickHandler());
    this.wrapper.addEventListener('scroll', () => this.scrollHandler());

    // Title manipulation interface

    const _this2 = this;
    /**
     * Interface for updating styles on the title.
     * Examples:
     *   this.title.translate = '-10px'
     *   this.title.opacity = 0.5
     */
    this.title = {
      _elem: _this2.titleElem,
      display() { this._elem.className = 'in-initial'; },
      fadeIn() { this._elem.className = 'fade-in'; },
      fadeOut() { this._elem.className = 'fade-out'; },
      fadeInLeft() { this._elem.className = 'in-from-left'; },
      fadeInRight() { this._elem.className = 'in-from-right'; },
      fadeOutLeft() { this._elem.className = 'out-to-left'; },
      fadeOutRight() { this._elem.className = 'out-to-right'; },
      fadeOutTop() { this._elem.className = 'out-to-top'; },
      moveDown() { this._elem.className = 'down'; },
      moveUp() { this._elem.className = 'up'; },
    };
  }

  /**
   * Update the card wrapper's CSS in order to reflect changes that were made to its JS
   * representation.
   */
  _applyTransform() {
    this.wrapper.style.transform = [
      'translateX(-50%)',
      `translateX(${this._translate})`,
    ].join(' ');
  }

  // Translation

  get translate() { return this._translate; }
  set translate(val) { this._translate = val; this._applyTransform(); }

  // Transition time

  get transitionTime() { return this._transitionTime; }
  set transitionTime(secs) {
    this._transitionTime = secs;
    this.wrapper.style.transition = `transform ${secs}s`;
  }

  setEnabled(enable = true) {
    const totalTime = this.transitionTime * 1000;
    const startTime = new Date();

    // Recursively adjust using setAnimationFrame until transitionTime is reached
    function step(parallax) {
      const timeElapsed = new Date() - startTime;
      // Still going
      if (timeElapsed < totalTime) {
        const progress = (timeElapsed / totalTime);
        if (enable) parallax.rotmax = progress * 10;
        else parallax.rotmax = (1 - progress) * 10;
        parallax.rotate();
        requestAnimationFrame(() => step(parallax));
      } else {
        parallax.rotmax = enable ? 10 : 0;
      }
    }
    step(this.parallax);
  }
  disable() { this.setEnabled(false); }
  enable() { this.setEnabled(true); }

  hide() {
    this.hidden = true;
    this.elem.classList.add('hidden');
  }
  show() {
    this.hidden = false;
    this.elem.classList.remove('hidden');
  }

  // Move the carousel so that this card is in the center
  centerSelf() {
    const thisIndex = this.carousel.cards.indexOf(this);
    this.carousel.position = this.carousel.adjustIndex(thisIndex);
  }

  // Bigger!
  expand() {
    // Prevents incorrect result when expanding and collapsing rapidly. Ensures that a pre-existing
    // timeout won't remove the expanded-x class after it's been added by this method.
    clearInterval(this._expansionTimeout);

    this.elem.classList.add('expanded-x');
    this.title.moveDown();
    this.disable();
    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.add('expanded-y');
      this._expansionTimeout = setTimeout(() => { this.elem.style.maxHeight = 'none'; this.elem.classList.remove('expanded-y'); }, 500);
      document.getElementById('email').classList.add('hidden');
      window.nav.disableScrollBinding = true;
    }, 500);

    this.expanded = true;
    if (this.carousel) this.carousel.expanded(this);
  }
  collapse() {
    clearInterval(this._expansionTimeout);

    this.elem.style.maxHeight = '';
    this.elem.classList.add('expanded-y');
    setTimeout(() => this.elem.classList.remove('expanded-y'), 10);
    document.getElementById('email').classList.remove('hidden');
    window.nav.disableScrollBinding = false;

    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.remove('expanded-x');
      this.title.moveUp();
      this.enable();
    }, 500);

    this.expanded = false;
    if (this.carousel) this.carousel.collapsed(this);
  }
  toggleExpand() {
    this[this.expanded ? 'collapse' : 'expand']();
  }

  fade() { this.elem.style.opacity = 0.5; }
  opacify() { this.elem.style.opacity = 1; }

  clickHandler() {
    if (this.carousel && !this.hidden) {
      const i1 = this.carousel.cards.indexOf(this);
      const i2 = this.carousel.adjustIndex(i1);
      if (this.carousel.position !== i2) this.centerSelf();
      else this.toggleExpand();
    } else if (!this.hidden) {
      this.toggleExpand();
    }
  }

  scrollHandler() {
    const transitionDistance = window.innerHeight / 10;
    const progress = Math.min(this.wrapper.scrollTop / transitionDistance, 1);

    const nav = document.getElementById('navigation');
    const tags = document.querySelector('.tags');
    nav.style.opacity = 0.5 * (1 - progress);
    tags.style.opacity = 1 - progress;

    // Turn off pointer events when halfway faded
    nav.style.pointerEvents = tags.style.pointerEvents = progress < 0.5 ? '' : 'none';
  }
}

/** Collects and orchestrates multiple CarouselCards */
export class Carousel {
  constructor(cards, tags) {
    this.tags = tags;
    this.tags.forEach((t) => { t.carousel = this; });
    this.cards = cards;
    this.cards.forEach((c) => { c.carousel = this; });
    this.hiddenIndices = [];

    this._position = 0;
    this.cards[this._position].title.display();
    // Trigger immediate layout of cards
    this.position = this._position;

    window.addEventListener('keydown', (e) => {
      // Carousel left/right with arrow keys, but only if carousel is expanded.
      // Carousel is expanded when the parent is #work-wrapper, the parent is .screen if it's not
      const projectsContainer = document.getElementsByClassName('projects-container')[0];
      if (projectsContainer.parentNode.id === 'work-wrapper') {
        if (e.key === 'ArrowRight') this.right();
        if (e.key === 'ArrowLeft') this.left();
      }
    });
  }

  // Transition time logic

  get baseTransitionTime() {
    const answer = this.cards[0].baseTransitionTime;
    // Warn if not every card has the same baseTransitionTime
    if (!this.cards.every(card => card.baseTransitionTime === answer)) console.warn('Carousel.baseTransitionTime called but baseTransitionTime of cards is inconsistent');
    return answer;
  }
  get transitionTime() {
    const answer = this.cards[0].transitionTime;
    // Warn if not every card has the same transitionTime
    if (!this.cards.every(card => card.transitionTime === answer)) console.warn('Carousel.transitionTime called but transitionTime of cards is inconsistent');
    return answer;
  }
  set transitionTime(secs) {
    this.cards.forEach((c) => { c.transitionTime = secs; });
  }

  // Rearrange

  // Adjust an index based on the number of hidden cards that come before it
  adjustIndex(index) {
    return index - this.hiddenIndices.filter(i => i < index).length;
  }

  get position() { return this._position; }
  set position(pos) {
    (async () => {
      const delta = Math.abs(this._position - pos);

      // Adjust transition time based on how many cards we're jumping
      const btt = this.baseTransitionTime;
      this.transitionTime = ((0.5 * btt) * ((delta || 1) - 1)) + btt;

      // If any card is open, collapse it and wait
      if (delta > 0 && typeof this.expandedIndex !== 'undefined') {
        this.cards[this.expandedIndex].collapse();
        await delay(1000);
      }

      // Apply certain changes to each card
      this.cards.forEach((card, index) => {
        const adj = i => this.adjustIndex(i);
        const cardPosition = adj(index) - pos;
        const oldPosition = cardPosition - (this._position - pos);
        // Calculate an offset based on position relative to any expanded card
        let cardOffset = 0;
        if (adj(index) > adj(this.expandedIndex)) cardOffset = 12.5;
        if (adj(index) < adj(this.expandedIndex)) cardOffset = -12.5;
        // Set translation
        const cardTranslation = `${(48 * cardPosition) + cardOffset}vw`;
        card.translate = cardTranslation;

        // Expand / disable cards based on whether they're centered
        if (cardPosition === 0 && !card.expanded) card.enable();
        else card.disable(); // Not center card

        // Apply animations to titles
        if (cardPosition === 0) { // Center card
          if (oldPosition < 0) card.title.fadeInLeft(); // coming from left
          if (oldPosition > 0) card.title.fadeInRight(); // coming from right
        } else if (oldPosition === 0) {
          if (cardPosition < 0) card.title.fadeOutLeft(); // going left
          if (cardPosition > 0) card.title.fadeOutRight(); // going right
        }
      });
      // Update internally-stored position tracker
      this._position = pos;
    })();
  }

  expanded(card) {
    this.expandedIndex = this.cards.indexOf(card);
    this.position = this.position; // Re-layout
    this.cards.forEach((c) => {
      if (c !== card) c.fade();
    });
  }
  collapsed() {
    this.expandedIndex = undefined;
    setTimeout(() => {
      this.position = this.position; // Re-layout
      this.cards.forEach(c => c.opacify());
    }, 500);
  }

  get minIndex() { return 0; } // eslint-disable-line class-methods-use-this
  get maxIndex() { return this.cards.length - this.hiddenIndices.length - 1; }

  left() {
    if (this.position > this.minIndex) this.position -= 1;
  }
  right() {
    if (this.position < this.maxIndex) this.position += 1;
  }

  async filter(tag) {
    const cardsToRemove = this.cards.filter(c => !c.tagNames.includes(tag.name));
    this.cards.forEach(c => c.show()); // Clean slate
    cardsToRemove.forEach(c => c.hide()); // Play removed animation on affected cards

    if (cardsToRemove.length) await delay(500);

    this.hiddenIndices = cardsToRemove.map(c => this.cards.indexOf(c));
    this.position = this.position; // Re-layout

    if (this.position > this.maxIndex) {
      await delay(750); // 250ms of space after first adjustment is completed
      this.position = this.maxIndex;
    }
  }

  async clearFilter() {
    if (this.hiddenIndices.length) {
      this.hiddenIndices = [];
      this.position = this.position; // Re-layout
      await delay(500);
      this.cards.forEach(c => c.show());
    }
  }
}
<|endoftext|>


<commit_before>/*
	ejies-jsextensions.js by Emmanuel Jourdan, Ircam - 02 2005
	"shared library" used for the ejies JavaScripts
	Pour l'instant, a ne sert  rien... mais  l'avenir... j'en ferai sans doute un usage intensif :-)
*/

var ejies = new EjiesUtils();
post("ejies", ejies.VersNum, ejies.VersDate);
post("\n     by Emmanuel Jourdan\, Ircam\n");

function EjiesUtils()
{
	this.VersNum = "1.56b5";		// Version Number
	this.VersDate = "(04/2006)";	// Version release date

	// clip method
	this.clip = function(x, min, max)
	{
		return Math.min(Math.max(x,min),max);
	}

/* 	perror: il faut mettre une fonction perror  l'intrieur de chaque */
	this.perror = function(a)
	{
		this.scriptname;

		var string = " error: " + this.scriptname + ":";
		var i;

		for (i = 0; i < a.length; i++) {
			string += " " + a[i];
		}
		string += "\n";

		post(string);
		return;
	}

	return this;
}
<commit_msg>ready to b6<commit_after>/*
	ejies-jsextensions.js by Emmanuel Jourdan, Ircam - 02 2005
	"shared library" used for the ejies JavaScripts
	Pour l'instant, a ne sert  rien... mais  l'avenir... j'en ferai sans doute un usage intensif :-)
*/

var ejies = new EjiesUtils();
post("ejies", ejies.VersNum, ejies.VersDate);
post("\n     by Emmanuel Jourdan\, Ircam\n");

function EjiesUtils()
{
	this.VersNum = "1.56b6";		// Version Number
	this.VersDate = "(04/2006)";	// Version release date

	// clip method
	this.clip = function(x, min, max)
	{
		return Math.min(Math.max(x,min),max);
	}

/* 	perror: il faut mettre une fonction perror  l'intrieur de chaque */
	this.perror = function(a)
	{
		this.scriptname;

		var string = " error: " + this.scriptname + ":";
		var i;

		for (i = 0; i < a.length; i++) {
			string += " " + a[i];
		}
		string += "\n";

		post(string);
		return;
	}

	return this;
}
<|endoftext|>


<commit_before>/*
	ejies-jsextensions.js by Emmanuel Jourdan, Ircam - 02 2005
	"shared library" used for the ejies JavaScripts
	Pour l'instant, a ne sert  rien... mais  l'avenir... j'en ferai sans doute un usage intensif :-)
*/

var ejies = new EjiesUtils();
post("ejies", ejies.VersNum, ejies.VersDate);
post("\n     by Emmanuel Jourdan\, Ircam\n");

function EjiesUtils()
{
	this.VersNum = "1.56b7";		// Version Number
	this.VersDate = "(05/2006)";	// Version release date

	// clip method
	this.clip = function(x, min, max)
	{
		return Math.min(Math.max(x,min),max);
	}

/* 	perror: il faut mettre une fonction perror  l'intrieur de chaque */
	this.perror = function(a)
	{
		this.scriptname;

		var string = " error: " + this.scriptname + ":";
		var i;

		for (i = 0; i < a.length; i++) {
			string += " " + a[i];
		}
		string += "\n";

		post(string);
		return;
	}

	return this;
}
<commit_msg>Compatibilité MaxMSP 4.6<commit_after>/*
	ejies-jsextensions.js by Emmanuel Jourdan, Ircam - 02 2005
	"shared library" used for the ejies JavaScripts
	Pour l'instant, a ne sert  rien... mais  l'avenir... j'en ferai sans doute un usage intensif :-)
*/

var ejies = new EjiesUtils();
post("ejies", ejies.VersNum, ejies.VersDate);
post("\n     by Emmanuel Jourdan\, Ircam\n");

function EjiesUtils()
{
	this.VersNum = "1.56b8";		// Version Number
	this.VersDate = "(05/2006)";	// Version release date

	// clip method
	this.clip = function(x, min, max)
	{
		return Math.min(Math.max(x,min),max);
	}

/* 	perror: il faut mettre une fonction perror  l'intrieur de chaque */
	this.perror = function(a)
	{
		this.scriptname;

		var string = " error: " + this.scriptname + ":";
		var i;

		for (i = 0; i < a.length; i++) {
			string += " " + a[i];
		}
		string += "\n";

		post(string);
		return;
	}

	return this;
}
<|endoftext|>


<commit_before>(function() {

	var _loadQueue = [],
	_loadingScripts = false;

	/**
	 * loads a script asynchronously.
	 */

	function loadScriptAsync(path, onload) {
		var script = document.createElement("script");
	    script.type = "text/javascript";
	    script.src = path;
	    script.onload = onload;
	    document.getElementsByTagName('head')[0].appendChild(script);
	}

	/**
	 * loads an entire application
	 */

	function loadAsync(fullPath, alias) {

		var cwd = _sardines.dirname(alias);

		_loadQueue.push(function() {

			//make sure the module isn't already loaded
			if(_sardines.modules[alias]) {
				return loadNextScript();
			}

			console.log(">> async load %s", fullPath);

			_loadingScripts = true;
			window.module = _sardines.modules[alias] = {
				exports: { }
			};

			var __dirname  = window.__dirname  = cwd;
			var __filename = window.__filename = alias;

			window.require = function(path) {
				_sardines.require(path, cwd);
			}

			loadScriptAsync(fullPath, loadNextScript);
		});

		if(!_loadingScripts) {
			_loadQueue.shift()();
		}
	}

	function loadNextScript() {
		var next = _loadQueue.shift();
		if(next) {
			next();
		} else {
			_loadingScripts = false;
		}
	}

	function onLoad(next) {

		_loadQueue.push(function(nx) {
			next();
			nx();
		})
	}

	_sardines.requireAsync = loadAsync;
	_sardines.onAsyncLoad = onLoad;

})();<commit_msg>fixed issue when assigning to module<commit_after>(function() {

	var _loadQueue = [],
	_loadingScripts = false;

	/**
	 * loads a script asynchronously.
	 */

	function loadScriptAsync(path, onload) {
		var script = document.createElement("script");
	    script.type = "text/javascript";
	    script.src = path;
	    script.onload = onload;
	    document.getElementsByTagName('head')[0].appendChild(script);
	}

	/**
	 * loads an entire application
	 */

	function loadAsync(fullPath, alias) {

		var cwd = _sardines.dirname(alias);

		_loadQueue.push(function() {

			//make sure the module isn't already loaded
			if(_sardines.modules[alias]) {
				return loadNextScript();
			}

			console.log(">> async load %s", fullPath);

			_loadingScripts = true;
			window.module = {
				exports: { }
			};

			window.exports = window.module.exports;

			var __dirname  = window.__dirname  = cwd;
			var __filename = window.__filename = alias;

			window.require = function(path) {
				return _sardines.require(path, cwd);
			}

			loadScriptAsync(fullPath, function() {
				_sardines.modules[alias] = window.module.exports;
				loadNextScript();
			});
		});

		if(!_loadingScripts) {
			_loadQueue.shift()();
		}
	}

	function loadNextScript() {
		var next = _loadQueue.shift();
		if(next) {
			next();
		} else {
			_loadingScripts = false;
		}
	}

	function onLoad(next) {

		_loadQueue.push(function(nx) {
			next();
			nx();
		})
	}

	_sardines.requireAsync = loadAsync;
	_sardines.onAsyncLoad = onLoad;

})();<|endoftext|>


<commit_before>var BufferedStream = require('bufferedstream'),
    URL = require('url'),
    http = require('http'),
    _ = require('underscore');

// Convert a header name to its canonical form, eg. "content-length" => "Content-Length".
var headerNameSpecialCases = {
    'content-md5': 'Content-MD5',
    dnt: 'DNT',
    etag: 'ETag',
    p3p: 'P3P',
    te: 'TE',
    'www-authenticate': 'WWW-Authenticate',
    'x-att-deviceid': 'X-ATT-DeviceId',
    'x-cdn': 'X-CDN',
    'x-ua-compatible': 'X-UA-Compatible',
    'x-xss-protection': 'X-XSS-Protection'
};

function formatHeaderName(headerName) {
    var lowerCasedHeaderName = headerName.toLowerCase();
    if (headerNameSpecialCases.hasOwnProperty(lowerCasedHeaderName)) {
        return headerNameSpecialCases[lowerCasedHeaderName];
    } else {
        // Make sure that the first char and all chars following a dash are upper-case:
        return lowerCasedHeaderName.replace(/(^|-)([a-z])/g, function ($0, optionalLeadingDash, ch) {
            return optionalLeadingDash + ch.toUpperCase();
        });
    }
}

function formatHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[formatHeaderName(headerName)] = obj[headerName];
    });
    return resultObj;
}

function lowerCaseHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[headerName.toLowerCase()] = obj[headerName];
    });
    return resultObj;
}

module.exports = function (expect) {
    return expect.addAssertion('to be middleware that processes', function (expect, subject, value, done) {
        this.errorMode = 'nested';
        expect(done, 'to be a function');
        var requestProperties = _.extend({method: 'GET'}, typeof value.request === 'string' ? {url: value.request} : (value.request || {}));

        requestProperties.headers = _.extend({}, requestProperties.headers); // Make a shallow copy so it's OK to add to it

        var expectedResponseProperties;

        if (typeof value.response === 'number') {
            expectedResponseProperties = {statusCode: value.response};
        } else if (typeof value.response === 'string' || Buffer.isBuffer(value.response)) {
            expectedResponseProperties = {body: value.response};
        } else {
            expectedResponseProperties = _.extend({}, value.response);
        }
        requestProperties.headers = lowerCaseHeaderNames(requestProperties.headers);

        if (/^https?:\/\//.test(requestProperties.url)) {
            var urlObj = URL.parse(requestProperties.url);
            if (typeof requestProperties.headers.host === 'undefined') {
                requestProperties.headers.host = urlObj.host;
            }
            if (urlObj.protocol === 'https:' && typeof requestProperties.https === 'undefined') {
                requestProperties.https = true;
            }
            requestProperties.url = urlObj.path;
        }

        var responseProperties = {},
            requestStream,
            body = requestProperties.body,
            req;

        if (typeof body !== 'undefined') {
            delete requestProperties.body;
            if (body.pipe) {
                requestStream = body;
                if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'multipart/form-data; boundary=' + requestStream.getBoundary();
                }
                requestProperties.headers['transfer-encoding'] = requestProperties.headers['transfer-encoding'] || 'chunked';
                requestStream.on('data', function (chunk) {
                    req.emit('data', new Buffer(chunk, 'utf-8'));
                }).on('end', function () {
                    req.emit('end');
                }).on('error', function (err) {
                    req.emit('error', err);
                });
            } else {
                if (typeof body === 'object' && !Buffer.isBuffer(body)) {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'application/json';
                    body = JSON.stringify(body);
                }

                if (!Buffer.isBuffer(body)) {
                    body = new Buffer(String(body), 'utf-8');
                }
                if (!('content-length' in requestProperties.headers) && !('transfer-encoding' in requestProperties.headers)) {
                    requestProperties.headers['content-length'] = String(body.length);
                }
                requestStream = new BufferedStream();
                setImmediate(function () {
                    req.emit('data', body);
                    req.emit('end');
                });
            }
        } else {
            // FIXME: Empty BufferedStream?
            requestStream = {
                destroy: function () {
                    responseProperties.requestDestroyed = true;
                }
            };
        }

        req = new http.IncomingMessage(requestStream);
        req.connection = req.connection || {};
        req.connection.encrypted = !!requestProperties.https;
        delete requestProperties.https;
        req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1';
        delete requestProperties.ip;
        delete requestProperties.remoteAddress;
        _.extend(req, requestProperties);

        var res = new http.ServerResponse(req);
        _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
        res.connection = new BufferedStream();
        res.connection._httpMessage = res;

        var nextCallCount = 0,
            next = function (err, _req, _res, _next) {
                nextCallCount += 1;
                if (typeof err === 'number') {
                    var statusCode = err;
                    err = new Error('' + statusCode);
                    err.statusCode = statusCode;
                }
                res.statusCode = err && (err.statusCode || err.status) || 404;
                doTheAssertions(err);
            };

        var responseBodyChunks = [],
            isDestroyed = false,
            isAsync = false;

        setImmediate(function () {
            isAsync = true;
        });

        function doTheAssertions(errorPassedToNext) {
            // Apparently setImmediate (or throwing an exception) is not an option here. If we don't call done, errors won't make it into mocha.
            try {
                var expectedResponseHeaders,
                    responseProperties = {
                        url: req.url,
                        strictAsync: isAsync,
                        errorPassedToNext: false,
                        statusCode: res.statusCode,
                        isDestroyed: isDestroyed,
                        nextCalled: nextCallCount > 0
                    };

                if (errorPassedToNext) {
                    responseProperties.errorPassedToNext = errorPassedToNext;
                    if (typeof expectedResponseProperties.errorPassedToNext !== 'undefined') {
                        if (expectedResponseProperties.errorPassedToNext === true) {
                            responseProperties.errorPassedToNext = true;
                        } else if (typeof expectedResponseProperties.errorPassedToNext === 'string') {
                            responseProperties.errorPassedToNext = errorPassedToNext.message;
                        } else {
                            responseProperties.errorPassedToNext = errorPassedToNext;
                        }
                    } else {
                        // Unexpected error, fail
                        return done(errorPassedToNext);
                    }
                }

                if (typeof expectedResponseProperties.headers === 'object') {
                    expectedResponseHeaders = expectedResponseProperties.headers;
                    delete expectedResponseProperties.headers;
                }

                if (responseBodyChunks.length > 0) {
                    responseProperties.body = Buffer.concat(responseBodyChunks);
                    if (typeof expectedResponseProperties.body === 'string') {
                        responseProperties.body = responseProperties.body.toString('utf-8');
                    } else if (Buffer.isBuffer(expectedResponseProperties.body) && typeof responseProperties.body === 'string') {
                        responseProperties.body = new Buffer(responseProperties.body, 'utf-8');
                    } else if (expectedResponseProperties.body && !Buffer.isBuffer(expectedResponseProperties.body) && typeof expectedResponseProperties.body === 'object' && /^application\/json\b/.test(res._headers['content-type'])) {
                        responseProperties.body = JSON.parse(responseProperties.body.toString('utf-8'));
                    }
                } else if (Buffer.isBuffer(expectedResponseProperties.body) && expectedResponseProperties.body.length === 0) {
                    responseProperties.body = new Buffer([]);
                } else if (expectedResponseProperties.body === '') {
                    responseProperties.body = '';
                }
                expect(responseProperties, 'to have properties', expectedResponseProperties);
                if (expectedResponseHeaders) {
                    expect(formatHeaderNames(res._headers), 'to have properties', formatHeaderNames(expectedResponseHeaders));
                }
            } catch (err) {
                return done(err);
            }
            done();
        }
        ['write', 'end', 'destroy'].forEach(function (methodName) {
            var orig = res[methodName];
            res[methodName] = function (chunk, encoding) {
                var returnValue = orig.apply(this, arguments);
                if (methodName !== 'destroy') {
                    if (encoding) {
                        throw new Error('encoding parameter not supported');
                    }
                    if (chunk) {
                        responseBodyChunks.push(chunk);
                    }
                }
                if (methodName === 'end' || methodName === 'destroy') {
                    isDestroyed = methodName === 'destroy';
                    doTheAssertions();
                }
                return returnValue;
            };
        });
        subject(req, res, next);
    });
};
<commit_msg>Don't throw when res.write/end gets an encoding of utf-8.<commit_after>var BufferedStream = require('bufferedstream'),
    URL = require('url'),
    http = require('http'),
    _ = require('underscore');

// Convert a header name to its canonical form, eg. "content-length" => "Content-Length".
var headerNameSpecialCases = {
    'content-md5': 'Content-MD5',
    dnt: 'DNT',
    etag: 'ETag',
    p3p: 'P3P',
    te: 'TE',
    'www-authenticate': 'WWW-Authenticate',
    'x-att-deviceid': 'X-ATT-DeviceId',
    'x-cdn': 'X-CDN',
    'x-ua-compatible': 'X-UA-Compatible',
    'x-xss-protection': 'X-XSS-Protection'
};

function formatHeaderName(headerName) {
    var lowerCasedHeaderName = headerName.toLowerCase();
    if (headerNameSpecialCases.hasOwnProperty(lowerCasedHeaderName)) {
        return headerNameSpecialCases[lowerCasedHeaderName];
    } else {
        // Make sure that the first char and all chars following a dash are upper-case:
        return lowerCasedHeaderName.replace(/(^|-)([a-z])/g, function ($0, optionalLeadingDash, ch) {
            return optionalLeadingDash + ch.toUpperCase();
        });
    }
}

function formatHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[formatHeaderName(headerName)] = obj[headerName];
    });
    return resultObj;
}

function lowerCaseHeaderNames(obj) {
    var resultObj = {};
    Object.keys(obj || {}).forEach(function (headerName) {
        resultObj[headerName.toLowerCase()] = obj[headerName];
    });
    return resultObj;
}

module.exports = function (expect) {
    return expect.addAssertion('to be middleware that processes', function (expect, subject, value, done) {
        this.errorMode = 'nested';
        expect(done, 'to be a function');
        var requestProperties = _.extend({method: 'GET'}, typeof value.request === 'string' ? {url: value.request} : (value.request || {}));

        requestProperties.headers = _.extend({}, requestProperties.headers); // Make a shallow copy so it's OK to add to it

        var expectedResponseProperties;

        if (typeof value.response === 'number') {
            expectedResponseProperties = {statusCode: value.response};
        } else if (typeof value.response === 'string' || Buffer.isBuffer(value.response)) {
            expectedResponseProperties = {body: value.response};
        } else {
            expectedResponseProperties = _.extend({}, value.response);
        }
        requestProperties.headers = lowerCaseHeaderNames(requestProperties.headers);

        if (/^https?:\/\//.test(requestProperties.url)) {
            var urlObj = URL.parse(requestProperties.url);
            if (typeof requestProperties.headers.host === 'undefined') {
                requestProperties.headers.host = urlObj.host;
            }
            if (urlObj.protocol === 'https:' && typeof requestProperties.https === 'undefined') {
                requestProperties.https = true;
            }
            requestProperties.url = urlObj.path;
        }

        var responseProperties = {},
            requestStream,
            body = requestProperties.body,
            req;

        if (typeof body !== 'undefined') {
            delete requestProperties.body;
            if (body.pipe) {
                requestStream = body;
                if (requestStream.constructor && requestStream.constructor.name === 'FormData') {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'multipart/form-data; boundary=' + requestStream.getBoundary();
                }
                requestProperties.headers['transfer-encoding'] = requestProperties.headers['transfer-encoding'] || 'chunked';
                requestStream.on('data', function (chunk) {
                    req.emit('data', new Buffer(chunk, 'utf-8'));
                }).on('end', function () {
                    req.emit('end');
                }).on('error', function (err) {
                    req.emit('error', err);
                });
            } else {
                if (typeof body === 'object' && !Buffer.isBuffer(body)) {
                    requestProperties.headers['content-type'] = requestProperties.headers['content-type'] || 'application/json';
                    body = JSON.stringify(body);
                }

                if (!Buffer.isBuffer(body)) {
                    body = new Buffer(String(body), 'utf-8');
                }
                if (!('content-length' in requestProperties.headers) && !('transfer-encoding' in requestProperties.headers)) {
                    requestProperties.headers['content-length'] = String(body.length);
                }
                requestStream = new BufferedStream();
                setImmediate(function () {
                    req.emit('data', body);
                    req.emit('end');
                });
            }
        } else {
            // FIXME: Empty BufferedStream?
            requestStream = {
                destroy: function () {
                    responseProperties.requestDestroyed = true;
                }
            };
        }

        req = new http.IncomingMessage(requestStream);
        req.connection = req.connection || {};
        req.connection.encrypted = !!requestProperties.https;
        delete requestProperties.https;
        req.connection.remoteAddress = requestProperties.remoteAddress || requestProperties.ip || '127.0.0.1';
        delete requestProperties.ip;
        delete requestProperties.remoteAddress;
        _.extend(req, requestProperties);

        var res = new http.ServerResponse(req);
        _.extend(res, requestProperties.res); // Allows for specifying eg. res.locals
        res.connection = new BufferedStream();
        res.connection._httpMessage = res;

        var nextCallCount = 0,
            next = function (err, _req, _res, _next) {
                nextCallCount += 1;
                if (typeof err === 'number') {
                    var statusCode = err;
                    err = new Error('' + statusCode);
                    err.statusCode = statusCode;
                }
                res.statusCode = err && (err.statusCode || err.status) || 404;
                doTheAssertions(err);
            };

        var responseBodyChunks = [],
            isDestroyed = false,
            isAsync = false;

        setImmediate(function () {
            isAsync = true;
        });

        function doTheAssertions(errorPassedToNext) {
            // Apparently setImmediate (or throwing an exception) is not an option here. If we don't call done, errors won't make it into mocha.
            try {
                var expectedResponseHeaders,
                    responseProperties = {
                        url: req.url,
                        strictAsync: isAsync,
                        errorPassedToNext: false,
                        statusCode: res.statusCode,
                        isDestroyed: isDestroyed,
                        nextCalled: nextCallCount > 0
                    };

                if (errorPassedToNext) {
                    responseProperties.errorPassedToNext = errorPassedToNext;
                    if (typeof expectedResponseProperties.errorPassedToNext !== 'undefined') {
                        if (expectedResponseProperties.errorPassedToNext === true) {
                            responseProperties.errorPassedToNext = true;
                        } else if (typeof expectedResponseProperties.errorPassedToNext === 'string') {
                            responseProperties.errorPassedToNext = errorPassedToNext.message;
                        } else {
                            responseProperties.errorPassedToNext = errorPassedToNext;
                        }
                    } else {
                        // Unexpected error, fail
                        return done(errorPassedToNext);
                    }
                }

                if (typeof expectedResponseProperties.headers === 'object') {
                    expectedResponseHeaders = expectedResponseProperties.headers;
                    delete expectedResponseProperties.headers;
                }

                if (responseBodyChunks.length > 0) {
                    responseProperties.body = Buffer.concat(responseBodyChunks);
                    if (typeof expectedResponseProperties.body === 'string') {
                        responseProperties.body = responseProperties.body.toString('utf-8');
                    } else if (Buffer.isBuffer(expectedResponseProperties.body) && typeof responseProperties.body === 'string') {
                        responseProperties.body = new Buffer(responseProperties.body, 'utf-8');
                    } else if (expectedResponseProperties.body && !Buffer.isBuffer(expectedResponseProperties.body) && typeof expectedResponseProperties.body === 'object' && /^application\/json\b/.test(res._headers['content-type'])) {
                        responseProperties.body = JSON.parse(responseProperties.body.toString('utf-8'));
                    }
                } else if (Buffer.isBuffer(expectedResponseProperties.body) && expectedResponseProperties.body.length === 0) {
                    responseProperties.body = new Buffer([]);
                } else if (expectedResponseProperties.body === '') {
                    responseProperties.body = '';
                }
                expect(responseProperties, 'to have properties', expectedResponseProperties);
                if (expectedResponseHeaders) {
                    expect(formatHeaderNames(res._headers), 'to have properties', formatHeaderNames(expectedResponseHeaders));
                }
            } catch (err) {
                return done(err);
            }
            done();
        }
        ['write', 'end', 'destroy'].forEach(function (methodName) {
            var orig = res[methodName];
            res[methodName] = function (chunk, encoding) {
                var returnValue = orig.apply(this, arguments);
                if (methodName !== 'destroy') {
                    if (encoding && !/^utf-?8$/i.test(encoding)) {
                        throw new Error('encoding parameter not supported except for "utf-8": ' + encoding);
                    }
                    if (chunk) {
                        responseBodyChunks.push(chunk);
                    }
                }
                if (methodName === 'end' || methodName === 'destroy') {
                    isDestroyed = methodName === 'destroy';
                    doTheAssertions();
                }
                return returnValue;
            };
        });
        subject(req, res, next);
    });
};
<|endoftext|>


<commit_before>/*! Pulse v1.1.0
 // alpha version 1.0
 // MIT Licensed http://opensource.org/licenses/MIT
 */

!function(){var n=function(n){this.bag=[],this.delay=n||0};return n.prototype.push=function(n){function e(){return function(e){n(),e()}}return"function"==typeof n?void this.bag.push(e):void console.error("Error: Only functions can be pushed into queues. '"+n+"' was ignored.")},n.prototype.dispatch=function(n,e){if("undefined"==typeof n?n=null:0,"undefined"==typeof e||null===e?e=!1:0,e?this.bag.reverse():0,this.bag.length<=0)return console.warn("Warning: There are no elements in the queue to dispatch."),!1;var t=this,i=function(){setTimeout(function(){t.bag[0]()(function(){return t.bag.shift(),t.bag.length<=0?(console.warn("Finished executing dispatch."),"function"==typeof n?(n(),!0):!0):void i()})},t.delay)};this.bag.length>0?i():0},window.Pulse=n,n}();
<commit_msg>Production version - minified<commit_after>/*! Pulse v1.1.0
 // alpha version 1.0
 // MIT Licensed http://opensource.org/licenses/MIT
 */

!function(){var n=function(n){this.bag=[],this.delay=n||0};return n.prototype.push=function(n){function e(){return function(e){n(),e()}}return"function"==typeof n?void this.bag.push(e):void console.error("Error: Only functions can be pushed into queues. '"+n+"' was ignored.")},n.prototype.dispatch=function(n,e){if("undefined"==typeof n?n=null:0,"undefined"==typeof e||null===e?e=!1:0,e?this.bag.reverse():0,this.bag.length<=0)return console.warn("Warning: There are no elements in the queue to dispatch."),!1;var t=this,i=function(){setTimeout(function(){t.bag[0]()(function(){return t.bag.shift(),t.bag.length<=0?(console.warn("Finished executing dispatch."),"function"==typeof n?(n(),!0):!0):void i()})},t.delay)};this.bag.length>0?i():0},window.Pulse=n,n}();
<|endoftext|>


<commit_before>var Bluebird = require('bluebird');
var Errors = require('./errors');
var Sandbox = require('sandboxjs');
var Superagent = require('superagent');
var Http = require('http');
var Url = require('url');
var Open = require('opn');
var Chalk = require('chalk');
var Decode = require('jwt-decode');
var Assert = require('assert');
var Crypto = require('crypto');

module.exports = UserAuthenticator;

function UserAuthenticator (config) {
    if (!config) config = {};

    this.sandboxUrl = config.sandboxUrl || 'https://sandbox.auth0-extend.com';
    this.authorizationServer = config.authorizationServer;
    this.audience = config.audience;
    this.clientId = config.clientId;
    this.refreshToken = config.refreshToken;
}

// Discover whether WT deployment supports auth v2 and if so create
// UserAuthenticator instance for it
UserAuthenticator.create = function (sandboxUrl, auth0) {
    // TODO, tjanczuk, remove the following line when v2 auth exits beta
    // Until then, it is an experimental feature enabled with AUTH_MODE=v2 env var
    // or use of --auth0 switch.
    if (process.env.AUTH_MODE !== 'v2' && !auth0) return Promise.resolve(null);

    var descriptionUrl = Url.parse(sandboxUrl);
    descriptionUrl.pathname = '/api/description';
    return Superagent
        .get(Url.format(descriptionUrl))
        .ok(res => res.status < 500)
        .then(res => {
            if (res.status === 200 && res.body && res.body.authorization_server) {
                return new UserAuthenticator({
                    sandboxUrl,
                    authorizationServer: res.body.authorization_server,
                    audience: res.body.audience || sandboxUrl,
                    clientId: res.body.client_id,
                });
            }
            else {
                return Promise.resolve(null);
            }
        });
};

UserAuthenticator.prototype.login = function (options) {
    options = options || {};

    if (this.refreshToken) {
        return this._refreshFlow(options);
    }
    else {
        return this._authorizationFlow(options);
    }
};

// Refresh token flow
UserAuthenticator.prototype._refreshFlow = function (options) {
    var refreshUrl = Url.parse(this.authorizationServer);
    refreshUrl.pathname = '/oauth/token';
    var self = this;
    return Superagent
        .post(Url.format(refreshUrl))
        .send({
            grant_type: 'refresh_token',
            client_id: this.clientId,
            refresh_token: this.refreshToken,
        })
        .then(res => {
            console.log('Your access token was successfuly refreshed.');
            return self._processAccessTokenResponse(options, res.body, options.requestedScopes);
        })
        .catch(e => {
            // In case of any error during refresh token flow, fall back on
            // regular authorization flow
            console.log(`Failure trying to refresh the access token: ${e.message}`);
            return self._authorizationFlow(options);
        });
};

// Browser based authorization flow
UserAuthenticator.prototype._authorizationFlow =  function (options) {
    // Initialize PKCE authorization flow through a browser

    var self = this;
    var codeVerifier = base64URLEncode(Crypto.randomBytes(16));
    var codeChallange = base64URLEncode(Crypto.createHash('sha256').update(codeVerifier).digest());
    var port = 8722 + Math.floor(6 * Math.random());
    var redirectUri = `http://127.0.0.1:${port}`;
    var requestedScopes = [ 'openid', 'offline_access' ];
    var audience;
    if (options.auth0) {
        audience = Url.parse(self.audience || self.sandboxUrl);
        audience.pathname += '/containers/' + options.container;
        audience = Url.format(audience);
        requestedScopes.push('wt:owner');
    }
    else {
        audience = self.audience || self.sandboxUrl;
        if (options.container) {
            requestedScopes.push(`wt:owner:${options.container}`);
        }
        if (options.admin) {
            requestedScopes.push(`wt:admin`);
        }
    }
    requestedScopes = requestedScopes.join(' ');

    var onceServer$ = createOnceServer();
    var loginUrl = createLoginUrl();

    console.log('Attempting to open the following login url in your browser: ');
    console.log();
    console.log(Chalk.underline(loginUrl));
    console.log();
    console.log('If the browser does not automatically open, please copy this address and paste it into your browser.');

    Open(loginUrl, { wait: false });

    return onceServer$;

    // Create PKCE login URL
    function createLoginUrl() {
        var loginUrl = Url.parse(self.authorizationServer, true);
        loginUrl.pathname = '/authorize';
        loginUrl.query = {
            redirect_uri: redirectUri,
            audience: audience,
            response_type: 'code',
            client_id: self.clientId,
            scope: requestedScopes,
            code_challenge: codeChallange,
            code_challenge_method: 'S256',
        };

        return Url.format(loginUrl);
    }

    // Returns a promise that resolves when a transient, localhost HTTP server
    // receives the first request. This request is a redirect from the authorization server.
    function createOnceServer() {
        return new Promise((resolve, reject) => {
            var server = Http.createServer((req, res) => {

                return processRedirectCallback(req, done);

                var _done;
                function done(error, data) {
                    if (_done) return;
                    _done = true;
                    res.writeHead(200, { 'Content-Type': 'text/plain' });
                    if (error) {
                        res.end(`Authentication failed:\n\n${error.message}`);
                    }
                    else {
                        res.end('Authentication successful.');
                    }
                    server.close();
                    return error ? reject(error) : resolve(data);
                }
            }).listen(port, (e) => {
                if (e) reject(e);
            });
        });
    }

    // Process redirect from authorization server to get authorization code
    function processRedirectCallback(req, done) {
        var url = Url.parse(req.url, true);
        if (req.method !== 'GET' || url.pathname !== '/') {
            return done(new Error(`Authentication failed. Invalid redirect from authorization server: ${req.method} ${req.url}`));
        }

        if (url.query.error) {
            return done(new Error(`Authentication failed: ${url.query.error}.`));
        }
        if (!url.query.code) {
            return done(new Error(`Authentication failed. Authorization server response does not specify authorization code: ${req.url}.`));
        }

        return exchangeAuthorizationCode(url.query.code, done);
    }

    // Exchange authorization code for access token using PKCE
    function exchangeAuthorizationCode(code, done) {
        var tokenUrl = Url.parse(self.authorizationServer);
        tokenUrl.pathname = '/oauth/token';

        return Superagent
            .post(Url.format(tokenUrl))
            .send({
                grant_type: 'authorization_code',
                client_id: self.clientId,
                code,
                code_verifier: codeVerifier,
                redirect_uri: redirectUri
            })
            .end((e,r) => {
                if (e) return done(new Error(`Authentication failed. Unable to obtian access token: ${e.message}.`));
                return self._processAccessTokenResponse(options, r.body, requestedScopes, done);
            });
    }

};

// Prepare wt-cli profile from rewfresh token or authorization code exchange response
UserAuthenticator.prototype._processAccessTokenResponse = function(options, body, requestedScopes, done) {
    var scopes = (body.scope || requestedScopes).split(' ');
    body.scopes = scopes;
    var isAdmin = scopes.indexOf('wt:admin') > -1;
    var container;
    if (options.container) {
        if (isAdmin || scopes.indexOf(`wt:owner:${options.container}`) > -1 || scopes.indexOf('wt:owner') > -1) {
            container = options.container;
        }
        else {
            return done(new Error(`Authentication failed: user does not have permissions to container '${options.container}'.`));
        }
    }
    else {
        if (isAdmin) {
            container = 'master';
        }
        else {
            scopes.forEach(s => {
                var match = s.match(/^wt\:owner\:(.+)$/);
                if (match && !container) {
                    container = match[1];
                }
            });
            if (!container) {
                return done(new Error(`Authentication failed: user has no permissions in the system.`));
            }
        }
    }

    var profile = Sandbox.init({
        url: this.sandboxUrl,
        token: body.access_token,
        container: container,
    });
    profile.name = options.profileName;
    profile.openid = body;
    if (profile.openid.expires_in) {
        profile.openid.valid_until = new Date(Date.now() + +profile.openid.expires_in * 1000).toString();
    }
    profile.openid.authorization_server = this.authorizationServer;
    profile.openid.audience = this.audience;
    profile.openid.client_id = this.clientId;
    profile.openid.refresh_token = profile.openid.refresh_token || this.refreshToken;
    profile.openid.auth0 = options.auth0;

    return done ? done(null, profile) : profile;
};

function base64URLEncode(str) {
    return str.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}
<commit_msg>modifed auth mode switch to default to v2<commit_after>var Bluebird = require('bluebird');
var Errors = require('./errors');
var Sandbox = require('sandboxjs');
var Superagent = require('superagent');
var Http = require('http');
var Url = require('url');
var Open = require('opn');
var Chalk = require('chalk');
var Decode = require('jwt-decode');
var Assert = require('assert');
var Crypto = require('crypto');

module.exports = UserAuthenticator;

function UserAuthenticator (config) {
    if (!config) config = {};

    this.sandboxUrl = config.sandboxUrl || 'https://sandbox.auth0-extend.com';
    this.authorizationServer = config.authorizationServer;
    this.audience = config.audience;
    this.clientId = config.clientId;
    this.refreshToken = config.refreshToken;
}

// Discover whether WT deployment supports auth v2 and if so create
// UserAuthenticator instance for it
UserAuthenticator.create = function (sandboxUrl, auth0) {
    // TODO, tjanczuk, remove the following line when v2 auth exits beta
    // we are defaulting to AUTHv2 here, we can revert to AUTHv1 using
    // AUTH_MODE=v1 env var
    // the use of --auth0 switch forces AUTHv2 as well.

    if (process.env.AUTH_MODE === 'v1' && !auth0) return Promise.resolve(null);

    var descriptionUrl = Url.parse(sandboxUrl);
    descriptionUrl.pathname = '/api/description';
    return Superagent
        .get(Url.format(descriptionUrl))
        .ok(res => res.status < 500)
        .then(res => {
            if (res.status === 200 && res.body && res.body.authorization_server) {
                return new UserAuthenticator({
                    sandboxUrl,
                    authorizationServer: res.body.authorization_server,
                    audience: res.body.audience || sandboxUrl,
                    clientId: res.body.client_id,
                });
            }
            else {
                return Promise.resolve(null);
            }
        });
};

UserAuthenticator.prototype.login = function (options) {
    options = options || {};

    if (this.refreshToken) {
        return this._refreshFlow(options);
    }
    else {
        return this._authorizationFlow(options);
    }
};

// Refresh token flow
UserAuthenticator.prototype._refreshFlow = function (options) {
    var refreshUrl = Url.parse(this.authorizationServer);
    refreshUrl.pathname = '/oauth/token';
    var self = this;
    return Superagent
        .post(Url.format(refreshUrl))
        .send({
            grant_type: 'refresh_token',
            client_id: this.clientId,
            refresh_token: this.refreshToken,
        })
        .then(res => {
            console.log('Your access token was successfuly refreshed.');
            return self._processAccessTokenResponse(options, res.body, options.requestedScopes);
        })
        .catch(e => {
            // In case of any error during refresh token flow, fall back on
            // regular authorization flow
            console.log(`Failure trying to refresh the access token: ${e.message}`);
            return self._authorizationFlow(options);
        });
};

// Browser based authorization flow
UserAuthenticator.prototype._authorizationFlow =  function (options) {
    // Initialize PKCE authorization flow through a browser

    var self = this;
    var codeVerifier = base64URLEncode(Crypto.randomBytes(16));
    var codeChallange = base64URLEncode(Crypto.createHash('sha256').update(codeVerifier).digest());
    var port = 8722 + Math.floor(6 * Math.random());
    var redirectUri = `http://127.0.0.1:${port}`;
    var requestedScopes = [ 'openid', 'offline_access' ];
    var audience;
    if (options.auth0) {
        audience = Url.parse(self.audience || self.sandboxUrl);
        audience.pathname += '/containers/' + options.container;
        audience = Url.format(audience);
        requestedScopes.push('wt:owner');
    }
    else {
        audience = self.audience || self.sandboxUrl;
        if (options.container) {
            requestedScopes.push(`wt:owner:${options.container}`);
        }
        if (options.admin) {
            requestedScopes.push(`wt:admin`);
        }
    }
    requestedScopes = requestedScopes.join(' ');

    var onceServer$ = createOnceServer();
    var loginUrl = createLoginUrl();

    console.log('Attempting to open the following login url in your browser: ');
    console.log();
    console.log(Chalk.underline(loginUrl));
    console.log();
    console.log('If the browser does not automatically open, please copy this address and paste it into your browser.');

    Open(loginUrl, { wait: false });

    return onceServer$;

    // Create PKCE login URL
    function createLoginUrl() {
        var loginUrl = Url.parse(self.authorizationServer, true);
        loginUrl.pathname = '/authorize';
        loginUrl.query = {
            redirect_uri: redirectUri,
            audience: audience,
            response_type: 'code',
            client_id: self.clientId,
            scope: requestedScopes,
            code_challenge: codeChallange,
            code_challenge_method: 'S256',
        };

        return Url.format(loginUrl);
    }

    // Returns a promise that resolves when a transient, localhost HTTP server
    // receives the first request. This request is a redirect from the authorization server.
    function createOnceServer() {
        return new Promise((resolve, reject) => {
            var server = Http.createServer((req, res) => {

                return processRedirectCallback(req, done);

                var _done;
                function done(error, data) {
                    if (_done) return;
                    _done = true;
                    res.writeHead(200, { 'Content-Type': 'text/plain' });
                    if (error) {
                        res.end(`Authentication failed:\n\n${error.message}`);
                    }
                    else {
                        res.end('Authentication successful.');
                    }
                    server.close();
                    return error ? reject(error) : resolve(data);
                }
            }).listen(port, (e) => {
                if (e) reject(e);
            });
        });
    }

    // Process redirect from authorization server to get authorization code
    function processRedirectCallback(req, done) {
        var url = Url.parse(req.url, true);
        if (req.method !== 'GET' || url.pathname !== '/') {
            return done(new Error(`Authentication failed. Invalid redirect from authorization server: ${req.method} ${req.url}`));
        }

        if (url.query.error) {
            return done(new Error(`Authentication failed: ${url.query.error}.`));
        }
        if (!url.query.code) {
            return done(new Error(`Authentication failed. Authorization server response does not specify authorization code: ${req.url}.`));
        }

        return exchangeAuthorizationCode(url.query.code, done);
    }

    // Exchange authorization code for access token using PKCE
    function exchangeAuthorizationCode(code, done) {
        var tokenUrl = Url.parse(self.authorizationServer);
        tokenUrl.pathname = '/oauth/token';

        return Superagent
            .post(Url.format(tokenUrl))
            .send({
                grant_type: 'authorization_code',
                client_id: self.clientId,
                code,
                code_verifier: codeVerifier,
                redirect_uri: redirectUri
            })
            .end((e,r) => {
                if (e) return done(new Error(`Authentication failed. Unable to obtian access token: ${e.message}.`));
                return self._processAccessTokenResponse(options, r.body, requestedScopes, done);
            });
    }

};

// Prepare wt-cli profile from rewfresh token or authorization code exchange response
UserAuthenticator.prototype._processAccessTokenResponse = function(options, body, requestedScopes, done) {
    var scopes = (body.scope || requestedScopes).split(' ');
    body.scopes = scopes;
    var isAdmin = scopes.indexOf('wt:admin') > -1;
    var container;
    if (options.container) {
        if (isAdmin || scopes.indexOf(`wt:owner:${options.container}`) > -1 || scopes.indexOf('wt:owner') > -1) {
            container = options.container;
        }
        else {
            return done(new Error(`Authentication failed: user does not have permissions to container '${options.container}'.`));
        }
    }
    else {
        if (isAdmin) {
            container = 'master';
        }
        else {
            scopes.forEach(s => {
                var match = s.match(/^wt\:owner\:(.+)$/);
                if (match && !container) {
                    container = match[1];
                }
            });
            if (!container) {
                return done(new Error(`Authentication failed: user has no permissions in the system.`));
            }
        }
    }

    var profile = Sandbox.init({
        url: this.sandboxUrl,
        token: body.access_token,
        container: container,
    });
    profile.name = options.profileName;
    profile.openid = body;
    if (profile.openid.expires_in) {
        profile.openid.valid_until = new Date(Date.now() + +profile.openid.expires_in * 1000).toString();
    }
    profile.openid.authorization_server = this.authorizationServer;
    profile.openid.audience = this.audience;
    profile.openid.client_id = this.clientId;
    profile.openid.refresh_token = profile.openid.refresh_token || this.refreshToken;
    profile.openid.auth0 = options.auth0;

    return done ? done(null, profile) : profile;
};

function base64URLEncode(str) {
    return str.toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=/g, '');
}
<|endoftext|>


<commit_before>const PropTypes = require('prop-types')
const UppyCore = require('@uppy/core').Uppy

// The `uppy` prop receives the Uppy core instance.
const uppy = PropTypes.instanceOf(UppyCore).isRequired

// A list of plugins to mount inside this component.
const plugins = PropTypes.arrayOf(PropTypes.string)

// Language strings for this component.
const locale = PropTypes.shape({
  strings: PropTypes.object,
  pluralize: PropTypes.func
})

// List of meta fields for the editor in the Dashboard.
const metaField = PropTypes.shape({
  id: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  placeholder: PropTypes.string
})
const metaFields = PropTypes.arrayOf(metaField)

// Common props for dashboardy components (Dashboard and DashboardModal).
const dashboard = {
  uppy,
  inline: PropTypes.bool,
  plugins,
  width: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number
  ]),
  height: PropTypes.number,
  showProgressDetails: PropTypes.bool,
  hideUploadButton: PropTypes.bool,
  hideProgressAfterFinish: PropTypes.bool,
  note: PropTypes.string,
  metaFields,
  proudlyDisplayPoweredByUppy: PropTypes.bool,
  disableStatusBar: PropTypes.bool,
  disableInformer: PropTypes.bool,
  disableThumbnailGenerator: PropTypes.bool,
  locale
}

module.exports = {
  uppy,
  locale,
  dashboard
}
<commit_msg>react: Allow Dashboard `height` to accept a string with unit<commit_after>const PropTypes = require('prop-types')
const UppyCore = require('@uppy/core').Uppy

// The `uppy` prop receives the Uppy core instance.
const uppy = PropTypes.instanceOf(UppyCore).isRequired

// A list of plugins to mount inside this component.
const plugins = PropTypes.arrayOf(PropTypes.string)

// Language strings for this component.
const locale = PropTypes.shape({
  strings: PropTypes.object,
  pluralize: PropTypes.func
})

// List of meta fields for the editor in the Dashboard.
const metaField = PropTypes.shape({
  id: PropTypes.string.isRequired,
  name: PropTypes.string.isRequired,
  placeholder: PropTypes.string
})
const metaFields = PropTypes.arrayOf(metaField)

// A size in pixels (number) or with some other unit (string).
const cssSize = PropTypes.oneOfType([
  PropTypes.string,
  PropTypes.number
])

// Common props for dashboardy components (Dashboard and DashboardModal).
const dashboard = {
  uppy,
  inline: PropTypes.bool,
  plugins,
  width: cssSize,
  height: cssSize,
  showProgressDetails: PropTypes.bool,
  hideUploadButton: PropTypes.bool,
  hideProgressAfterFinish: PropTypes.bool,
  note: PropTypes.string,
  metaFields,
  proudlyDisplayPoweredByUppy: PropTypes.bool,
  disableStatusBar: PropTypes.bool,
  disableInformer: PropTypes.bool,
  disableThumbnailGenerator: PropTypes.bool,
  locale
}

module.exports = {
  uppy,
  locale,
  dashboard
}
<|endoftext|>