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

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.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    this.elem.addEventListener('click', () => {
      if (this.carousel) {
        const thisIndex = this.carousel.cards.indexOf(this);
        if (this.carousel.position !== thisIndex) this.centerSelf();
        else this.toggleExpand();
      } else {
        this.toggleExpand();
      }
    });
    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); }

  // 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; }

  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 new Promise(resolve => setTimeout(resolve, 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 (index > this.expandedIndex) cardOffset = 12.5;
        if (index < 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.elem.classList.remove('removed')); // Clean slate
    cardsToRemove.forEach(c => c.elem.classList.add('removed')); // Play removed animation on affected cards

    if (cardsToRemove.length) await new Promise(resolve => setTimeout(resolve, 500));

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

  clearFilter() {
    this.cards.forEach(c => c.elem.classList.remove('removed'));
    this.hiddenIndices = [];
    this.position = this.position; // Re-layout
  }
}
<commit_msg>Make expanding compatible with hiding<commit_after>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

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.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    this.elem.addEventListener('click', () => {
      if (this.carousel) {
        const i1 = this.carousel.cards.indexOf(this);
        const i2 = this.carousel.adjustIndex(i1);
        if (this.carousel.position !== i2) this.centerSelf();
        else this.toggleExpand();
      } else {
        this.toggleExpand();
      }
    });
    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); }

  // 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; }

  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 new Promise(resolve => setTimeout(resolve, 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.elem.classList.remove('removed')); // Clean slate
    cardsToRemove.forEach(c => c.elem.classList.add('removed')); // Play removed animation on affected cards

    if (cardsToRemove.length) await new Promise(resolve => setTimeout(resolve, 500));

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

  clearFilter() {
    this.cards.forEach(c => c.elem.classList.remove('removed'));
    this.hiddenIndices = [];
    this.position = this.position; // Re-layout
  }
}
<|endoftext|>


<commit_before>export default {
  "type": "Range",
  "metadata": {"generates": true, "source": true},
  "params": [
    { "name": "start", "type": "number", "required": true },
    { "name": "stop", "type": "number", "required": true },
    { "name": "step", "type": "number", "default": 1 }
  ],
  "output": ["value"]
};
<commit_msg>Fix sequence transform definition.<commit_after>export default {
  "type": "Sequence",
  "metadata": {"generates": true, "source": true},
  "params": [
    { "name": "start", "type": "number", "required": true },
    { "name": "stop", "type": "number", "required": true },
    { "name": "step", "type": "number", "default": 1 }
  ],
  "output": ["value"]
};
<|endoftext|>


<commit_before>// A channel is a queue with a read-end and a write-end.
// Values are written and read asynchronously via callbacks.
// The basic channel is such that the callback associated
// with a value put into it will be called when the value
// is consumed from the read end.

function Channel() {
    this._queue = [];
    this._pending = [];
    return this;
}

function sendValue(value, callback) {
    callback && process.nextTick(function () { callback(null, value); });
}

function sendError(err, callback) {
    callback && process.nextTick(function () { callback(err, null); });
}

// Read a value from the channel, passing the value to the given callback.
Channel.prototype.take = function (callback) {
    if (this._queue.length > 0) {
        var q = this._queue.shift();
        sendValue(q.value, q.callback);
        sendValue(q.value, callback);
    } else {
        callback && this._pending.push(callback);
    }
};

// Places a value into the channel. The callback will be called when the value is
// consumed from the read-end.
Channel.prototype.put = function (value, callback) {
    if (this._pending.length > 0) {
        var p = this._pending.shift();
        sendValue(value, callback);
        sendValue(value, p);
    } else {
        this._queue.push({callback: callback, value: value});
    }
};

function ReceivedChannelValue(id, err, value) {
    this.id = id;
    this.err = err;
    this.val = value;
    return this;
}

// Makes a callback that will receive the value produced by
// some process and place the result into the channel. The
// "id" exists to identify the one producing the value.
// The "id", "err" and "val" are all available on the
// channel.
Channel.prototype.receive = function (id) {
    var self = this;
    return function (err, value) {
        self.put(new ReceivedChannelValue(id, err, value));
    };
};

// Answers "will read succeed immediately?"
Object.defineProperty(Channel.prototype, 'canRead', {
    get: function () {
        return this._queue.length > 0 && this._pending.length === 0;
    }
});

// Answers "will write succeed immediately?"
Object.defineProperty(Channel.prototype, 'canWrite', {
    get: function () {
        return this._pending.length > 0 || this._queue.length === 0;
    }
});

// Answers "how many values have been placed into the channel?"
// Positive values give the number of values available right away.
// Negative values give the number of pending take operations.
Object.defineProperty(Channel.prototype, 'backlog', {
    get: function () {
        return this._queue.length - this._pending.length;
    }
});

// Makes a new channel whose values are transformed by the given
// function "f". `cond(value)` is a function that specifies a
// condition until which the mapping will continue.
Channel.prototype.map = function (cond, f) {
    var ch2 = new Channel();
    var self = this;
    function receive(err, value) {
        if (cond === true || cond(value)) {
            ch2.put(f(value), loop);
        } else {
            ch2.put(null);
        }
    }
    function loop() {
        self.take(receive);
    }
    loop();
    return ch2;
};

// Makes a new channel and pipes the values in this
// channel to it. Only the values that satisfy the
// predicate function 'f' are piped and others
// are dropped. 'cond(value)' gives the condition
// until which the piping will continue to run.
Channel.prototype.filter = function (cond, f) {
    var ch2 = new Channel();
    var self = this;
    function receive(err, value) {
        if (cond === true || cond(value)) {
            if (f(value)) {
                ch2.put(value, loop);
            } else {
                loop();
            }
        } else {
            ch2.put(null);
        }
    }
    function loop() {
        self.take(receive);
    }
    loop();
    return ch2;
};

// Makes a new channel, reduces the values produced
// by this channel using the function "f" as long
// as "cond()" is satisfied and once all folding is done,
// sends the result to the new channel.
Channel.prototype.reduce = function (initial, cond, f) {
    var ch2 = new Channel();
    var self = this;
    var result = initial;
    function receive(err, value) {
        result = f(result, value);
        process.nextTick(loop);
    }
    function loop() {
        if (cond()) {
            self.take(receive);
        } else {
            ch2.put(result);
        }
    }
    loop();
    return ch2;
};

// Makes a new channel and pipes the values put into this
// channel in groups of N.
Channel.prototype.group = function (N) {
    var gch = new Channel();
    var group = [];
    var self = this;

    function receive(err, value) {
        group.push(value);
        if (group.length < N) {
            process.nextTick(loop);
        } else {
            var g = group;
            group = [];
            gch.put(g, receive);
        }
    }

    function loop() {
        self.take(receive);
    }

    loop();
    return gch;
};

// Temporarily switches the channel to a mode where it will
// collect the next N items into a group and pass it on to
// the callback.
//
// Use within task like this -
//      var ch = new Channel();
//      ...
//      x <- ch.takeN(10);
Channel.prototype.takeN = function (N, callback) {
    var group = [];
    var self = this;
    function receive(err, value) {
        if (err) {
            return sendError(err, callback);
        }
        group.push(value);
        if (group.length < N) {
            self.take(receive);
        } else {
            sendValue(group, callback);
        }
    }
    self.take(receive);
};

// Switches the channel to a state where every time some
// reader takes a value from the channel, they'll get
// `value` delivered immediately. This makes a channel
// behave somewhat like a promise, where until `fill`
// is called, asking for a value will cause a wait, but
// once `fill` is called somewhere, `take` will always
// succeed with a single value.
Channel.prototype.fill = function (value) {
    this.take = function (callback) {
        sendValue(value, callback);
    };
    this.put = function (ignoredValue, callback) {
        // no-op
        sendValue(value, callback);
    };
    this.fill = function (ignoredValue) {
        // no-op.
    };
};

// Sends the elements of the given array one by one
// to the channel as readers request values. The
// callback will be called when the last value is
// accepted.
Channel.prototype.stream = function (array, callback) {
    var i = 0, self = this;
    function next() {
        if (i < array.length) {
            self.put(array[i++], next);
        } else {
            sendValue(array, callback);
        }
    }
    next();
};

// Sets up the channel to receive events of the given type
// from the given domElement. (Works only in the browser.)
// `domElement` can either be a string which is taken to be
// a querySelector specifier, an array of DOM nodes, or
// a single DOM node. `eventName` is a string like 'click'
// which gives an event category to bind to.
//
// Note: If you want a channel to not receive events
// too frequently, you can first debounce the channel
// before listening for events, like this -
//
//      ch = new Channel();
//      ch.debounce(100).listen('.textarea', 'change');
//
// The above code will make sure that consecutive change
// events are separated by at least 100ms. The debounce()
// method call produces a wrapper channel object that
// acts as a gatekeeper to the original channel object
// 'ch'. So, while the above way will result in debounced
// actions, you can subsequently call `ch.listen()` to
// bypass debouncing on the same channel. Readers reading
// `ch` will receive events from the debounced elements
// as well from the elements bound directly.
Channel.prototype.listen = function (domElement, eventName) {
    var self = this;
    var elements = null;
    if (typeof domElement === 'string') {
        elements = document.querySelectorAll(domElement);
    } else if (domElement.length) {
        elements = domElement;
    } else {
        elements = [domElement];
    }

    function listener(event) {
        self.put(event);
        event.stopPropagation();
    }

    for (var i = 0, N = elements.length; i < N; ++i) {
        elements[i].addEventListener(eventName, listener);
    }
};

function MergedChannelValue(i, ch, err, value) {
    this.ix = i;
    this.chan = ch;
    this.err = err;
    this.val = value;
    return this;
}

// Makes a new channel that receives the values put into
// all the given channels (which is an array of channels).
// The value produced by a merged channel is a wrapper object
// that has three fields - "chan" giving the channel that
// produced the value, "val" giving the value and "ix"
// giving the index of the channel in the array that produced
// the value. If any of the source channels callback with
// an err (which is never supposed to happen) or "null" value
// (which can happen), the channel will cease to send its output
// to the merged channel.
Channel.merge = function (channels) {
    var channel = new Channel();

    function piper(ch, i) {
        function writer(err, value) {
            channel.put(new MergedChannelValue(i, ch, err, value), reader);
        }
        function reader(err, value) {
            ch.take(writer);
        }
        reader(null, null);
    }

    channels.forEach(piper);

    return channel;
};

// Makes a "timeout" channel, where every time someone pulls
// a value from it, it is delivered after "ms" milliseconds.
// This channel is not writeable.
Channel.timeout = function (ms) {
    var channel = new Channel();
    channel._timeInterval_ms = ms;
    channel.take = timeoutTake;
    return channel;
};

function timeoutTick(channel) {
    channel.put(true);
}

function timeoutTake(callback) {
    setTimeout(timeoutTick, this._timeInterval_ms, this);
    Channel.prototype.take.call(this, callback);
}

// Makes a "clock" channel which, once started, will produce
// values counting upwards from `startCounter`, until the
// `stop()` method is called on the channel. Calling `start()`
// will have an effect only when the clock is stopped.
Channel.clock = function (ms) {
    var channel = new Channel();
    channel._timer = null;
    channel._timeInterval_ms = ms;
    channel._counter = 0;
    channel.start = clockStart;
    channel.stop = clockStop;
    return channel;
};

function clockTick(clock) {
    clock.put(clock._counter++);
}

function clockStart(startCounter) {
    if (!this._timer) {
        startCounter = arguments.length < 1 ? 1 : startCounter;
        this._counter = startCounter;
        this._timer = setInterval(clockTick, this._timeInterval_ms, this);
    }
}

function clockStop() {
    if (this._timer) {
        clearInterval(this._timer);
        this._timer = null;
    }
}

// Returns a wrapped interface to channel which will
// debounce the values placed on it - i.e. it will
// reject put() operations that occur within a time
// of "ms" milliseconds between each other.
Channel.debounce = function (ms, channel) {
    var ch = Object.create(channel);
    ch._channel = channel;
    ch._debounceInterval_ms = ms;
    ch._lastPutTime = 0;
    ch.put = debouncingPut;
    return ch;
};

function debouncingPut(value, callback) {
    var now = Date.now();
    if (now - this._lastPutTime >= this._debounceInterval_ms) {
        this._lastPutTime = now;
        this._channel.put(value, callback);
    } else {
        sendValue(null, callback);
    }
}

// Wraps the given channel with an interface such
// that put() operations will immediately succeed
// as long as fewer than N values have been placed
// on the channel.
Channel.buffered = function (N, channel) {
    var ch = Object.create(channel);
    ch._channel = channel;
    ch._bufferLength = N;
    ch.put = bufferedPut;
    ch.take = bufferedTake;
    return ch;
};

function bufferedPut(value, callback) {
    if (this.backlog < this._bufferLength) {
        this._channel.put(value);
        sendValue(value, callback);
    } else {
        this._channel.put(value, callback);
    }
}

function bufferedTake(callback) {
    this._channel.take(callback);
    if (this._queue.length >= this._bufferLength - 1) {
        var q = this._queue[N-2];
        sendValue(q.value, q.callback);
        q.callback = null;
    }
}

// If more than N values have been placed into a channel
// and a writer tries to place one more value, sometimes
// we want the new value to be dropped in order that
// processing requirements don't accumulate. This is
// the purpose of `droppingAfter` which wraps the
// parent channel's `put` to do this dropping.

Channel.prototype.droppingAfter = function (N) {
    var ch = Object.create(this);
    ch._channel = this;
    ch._bufferLength = N;
    ch.put = droppingPut;
    return ch;
};

function droppingPut(value, callback) {
    if (this.backlog < this._bufferLength) {
        this._channel.put(value, callback);
    } else {
        sendValue(null, callback);
    }
}

// In the same situation as with `droppingAfter`,
// at other times, we want the more recent values
// to take precedence over the values already in
// the queue. In this case, we want to expire the
// old values and replace them with new values.
// That is what `expiringAfter` does.

Channel.prototype.expiringAfter = function (N) {
    var ch = Object.create(this);
    ch._channel = this;
    ch._bufferLength = N;
    ch.put = expiringPut;
    return ch;
};

function expiringPut(value, callback) {
    while (this.backlog > this._bufferLength) {
        this.take();
    }
    this._channel.put(value, callback);
    return this;
}

module.exports = Channel;
<commit_msg>Changed timeout to a one shot. Also added a method called 'timeout' which can be used to send a value to the receiving channel with a delay.<commit_after>// A channel is a queue with a read-end and a write-end.
// Values are written and read asynchronously via callbacks.
// The basic channel is such that the callback associated
// with a value put into it will be called when the value
// is consumed from the read end.

function Channel() {
    this._queue = [];
    this._pending = [];
    return this;
}

function sendValue(value, callback) {
    callback && process.nextTick(function () { callback(null, value); });
}

function sendError(err, callback) {
    callback && process.nextTick(function () { callback(err, null); });
}

// Read a value from the channel, passing the value to the given callback.
Channel.prototype.take = function (callback) {
    if (this._queue.length > 0) {
        var q = this._queue.shift();
        sendValue(q.value, q.callback);
        sendValue(q.value, callback);
    } else {
        callback && this._pending.push(callback);
    }
};

// Places a value into the channel. The callback will be called when the value is
// consumed from the read-end.
Channel.prototype.put = function (value, callback) {
    if (this._pending.length > 0) {
        var p = this._pending.shift();
        sendValue(value, callback);
        sendValue(value, p);
    } else {
        this._queue.push({callback: callback, value: value});
    }
};

function ReceivedChannelValue(id, err, value) {
    this.id = id;
    this.err = err;
    this.val = value;
    return this;
}

// Makes a callback that will receive the value produced by
// some process and place the result into the channel. The
// "id" exists to identify the one producing the value.
// The "id", "err" and "val" are all available on the
// channel.
Channel.prototype.receive = function (id) {
    var self = this;
    return function (err, value) {
        self.put(new ReceivedChannelValue(id, err, value));
    };
};

// Answers "will read succeed immediately?"
Object.defineProperty(Channel.prototype, 'canRead', {
    get: function () {
        return this._queue.length > 0 && this._pending.length === 0;
    }
});

// Answers "will write succeed immediately?"
Object.defineProperty(Channel.prototype, 'canWrite', {
    get: function () {
        return this._pending.length > 0 || this._queue.length === 0;
    }
});

// Answers "how many values have been placed into the channel?"
// Positive values give the number of values available right away.
// Negative values give the number of pending take operations.
Object.defineProperty(Channel.prototype, 'backlog', {
    get: function () {
        return this._queue.length - this._pending.length;
    }
});

// Makes a new channel whose values are transformed by the given
// function "f". `cond(value)` is a function that specifies a
// condition until which the mapping will continue.
Channel.prototype.map = function (cond, f) {
    var ch2 = new Channel();
    var self = this;
    function receive(err, value) {
        if (cond === true || cond(value)) {
            ch2.put(f(value), loop);
        } else {
            ch2.put(null);
        }
    }
    function loop() {
        self.take(receive);
    }
    loop();
    return ch2;
};

// Makes a new channel and pipes the values in this
// channel to it. Only the values that satisfy the
// predicate function 'f' are piped and others
// are dropped. 'cond(value)' gives the condition
// until which the piping will continue to run.
Channel.prototype.filter = function (cond, f) {
    var ch2 = new Channel();
    var self = this;
    function receive(err, value) {
        if (cond === true || cond(value)) {
            if (f(value)) {
                ch2.put(value, loop);
            } else {
                loop();
            }
        } else {
            ch2.put(null);
        }
    }
    function loop() {
        self.take(receive);
    }
    loop();
    return ch2;
};

// Makes a new channel, reduces the values produced
// by this channel using the function "f" as long
// as "cond()" is satisfied and once all folding is done,
// sends the result to the new channel.
Channel.prototype.reduce = function (initial, cond, f) {
    var ch2 = new Channel();
    var self = this;
    var result = initial;
    function receive(err, value) {
        result = f(result, value);
        process.nextTick(loop);
    }
    function loop() {
        if (cond()) {
            self.take(receive);
        } else {
            ch2.put(result);
        }
    }
    loop();
    return ch2;
};

// Makes a new channel and pipes the values put into this
// channel in groups of N.
Channel.prototype.group = function (N) {
    var gch = new Channel();
    var group = [];
    var self = this;

    function receive(err, value) {
        group.push(value);
        if (group.length < N) {
            process.nextTick(loop);
        } else {
            var g = group;
            group = [];
            gch.put(g, receive);
        }
    }

    function loop() {
        self.take(receive);
    }

    loop();
    return gch;
};

// Temporarily switches the channel to a mode where it will
// collect the next N items into a group and pass it on to
// the callback.
//
// Use within task like this -
//      var ch = new Channel();
//      ...
//      x <- ch.takeN(10);
Channel.prototype.takeN = function (N, callback) {
    var group = [];
    var self = this;
    function receive(err, value) {
        if (err) {
            return sendError(err, callback);
        }
        group.push(value);
        if (group.length < N) {
            self.take(receive);
        } else {
            sendValue(group, callback);
        }
    }
    self.take(receive);
};

// Switches the channel to a state where every time some
// reader takes a value from the channel, they'll get
// `value` delivered immediately. This makes a channel
// behave somewhat like a promise, where until `fill`
// is called, asking for a value will cause a wait, but
// once `fill` is called somewhere, `take` will always
// succeed with a single value.
Channel.prototype.fill = function (value) {
    this.take = function (callback) {
        sendValue(value, callback);
    };
    this.put = function (ignoredValue, callback) {
        // no-op
        sendValue(value, callback);
    };
    this.fill = function (ignoredValue) {
        // no-op.
    };
};

// Sends the elements of the given array one by one
// to the channel as readers request values. The
// callback will be called when the last value is
// accepted.
Channel.prototype.stream = function (array, callback) {
    var i = 0, self = this;
    function next() {
        if (i < array.length) {
            self.put(array[i++], next);
        } else {
            sendValue(array, callback);
        }
    }
    next();
};

// Sets up the channel to receive events of the given type
// from the given domElement. (Works only in the browser.)
// `domElement` can either be a string which is taken to be
// a querySelector specifier, an array of DOM nodes, or
// a single DOM node. `eventName` is a string like 'click'
// which gives an event category to bind to.
//
// Note: If you want a channel to not receive events
// too frequently, you can first debounce the channel
// before listening for events, like this -
//
//      ch = new Channel();
//      ch.debounce(100).listen('.textarea', 'change');
//
// The above code will make sure that consecutive change
// events are separated by at least 100ms. The debounce()
// method call produces a wrapper channel object that
// acts as a gatekeeper to the original channel object
// 'ch'. So, while the above way will result in debounced
// actions, you can subsequently call `ch.listen()` to
// bypass debouncing on the same channel. Readers reading
// `ch` will receive events from the debounced elements
// as well from the elements bound directly.
Channel.prototype.listen = function (domElement, eventName) {
    var self = this;
    var elements = null;
    if (typeof domElement === 'string') {
        elements = document.querySelectorAll(domElement);
    } else if (domElement.length) {
        elements = domElement;
    } else {
        elements = [domElement];
    }

    function listener(event) {
        self.put(event);
        event.stopPropagation();
    }

    for (var i = 0, N = elements.length; i < N; ++i) {
        elements[i].addEventListener(eventName, listener);
    }
};

function MergedChannelValue(i, ch, err, value) {
    this.ix = i;
    this.chan = ch;
    this.err = err;
    this.val = value;
    return this;
}

// Makes a new channel that receives the values put into
// all the given channels (which is an array of channels).
// The value produced by a merged channel is a wrapper object
// that has three fields - "chan" giving the channel that
// produced the value, "val" giving the value and "ix"
// giving the index of the channel in the array that produced
// the value. If any of the source channels callback with
// an err (which is never supposed to happen) or "null" value
// (which can happen), the channel will cease to send its output
// to the merged channel.
Channel.merge = function (channels) {
    var channel = new Channel();

    function piper(ch, i) {
        function writer(err, value) {
            channel.put(new MergedChannelValue(i, ch, err, value), reader);
        }
        function reader(err, value) {
            ch.take(writer);
        }
        reader(null, null);
    }

    channels.forEach(piper);

    return channel;
};

// It is sometimes useful to also have a value sent to
// an existing channel after a timeout expires. If some
// other process is supposed to write a value to the
// channel and it is taking too long, the value passed
// to the .timeout() call can be tested against to decide
// whether a timeout occurred before the process could
// do its thing.
Channel.prototype.timeout = function (ms, value) {
    setTimeout(timeoutTick, ms, this, value);
    return this;
};

// Makes a "timeout" channel, which'll deliver a value
// a given interval after the channel is created.
Channel.timeout = function (ms, value) {
    return (new Channel()).timeout(ms, value);
};

function timeoutTick(channel, value) {
    channel.put(value);
}

// Makes a "clock" channel which, once started, will produce
// values counting upwards from `startCounter`, until the
// `stop()` method is called on the channel. Calling `start()`
// will have an effect only when the clock is stopped.
Channel.clock = function (ms) {
    var channel = new Channel();
    channel._timer = null;
    channel._timeInterval_ms = ms;
    channel._counter = 0;
    channel.start = clockStart;
    channel.stop = clockStop;
    return channel;
};

function clockTick(clock) {
    clock.put(clock._counter++);
}

function clockStart(startCounter) {
    if (!this._timer) {
        startCounter = arguments.length < 1 ? 1 : startCounter;
        this._counter = startCounter;
        this._timer = setInterval(clockTick, this._timeInterval_ms, this);
    }
}

function clockStop() {
    if (this._timer) {
        clearInterval(this._timer);
        this._timer = null;
    }
}

// Returns a wrapped interface to channel which will
// debounce the values placed on it - i.e. it will
// reject put() operations that occur within a time
// of "ms" milliseconds between each other.
Channel.debounce = function (ms, channel) {
    var ch = Object.create(channel);
    ch._channel = channel;
    ch._debounceInterval_ms = ms;
    ch._lastPutTime = 0;
    ch.put = debouncingPut;
    return ch;
};

function debouncingPut(value, callback) {
    var now = Date.now();
    if (now - this._lastPutTime >= this._debounceInterval_ms) {
        this._lastPutTime = now;
        this._channel.put(value, callback);
    } else {
        sendValue(null, callback);
    }
}

// Wraps the given channel with an interface such
// that put() operations will immediately succeed
// as long as fewer than N values have been placed
// on the channel.
Channel.buffered = function (N, channel) {
    var ch = Object.create(channel);
    ch._channel = channel;
    ch._bufferLength = N;
    ch.put = bufferedPut;
    ch.take = bufferedTake;
    return ch;
};

function bufferedPut(value, callback) {
    if (this.backlog < this._bufferLength) {
        this._channel.put(value);
        sendValue(value, callback);
    } else {
        this._channel.put(value, callback);
    }
}

function bufferedTake(callback) {
    this._channel.take(callback);
    if (this._queue.length >= this._bufferLength - 1) {
        var q = this._queue[N-2];
        sendValue(q.value, q.callback);
        q.callback = null;
    }
}

// If more than N values have been placed into a channel
// and a writer tries to place one more value, sometimes
// we want the new value to be dropped in order that
// processing requirements don't accumulate. This is
// the purpose of `droppingAfter` which wraps the
// parent channel's `put` to do this dropping.

Channel.prototype.droppingAfter = function (N) {
    var ch = Object.create(this);
    ch._channel = this;
    ch._bufferLength = N;
    ch.put = droppingPut;
    return ch;
};

function droppingPut(value, callback) {
    if (this.backlog < this._bufferLength) {
        this._channel.put(value, callback);
    } else {
        sendValue(null, callback);
    }
}

// In the same situation as with `droppingAfter`,
// at other times, we want the more recent values
// to take precedence over the values already in
// the queue. In this case, we want to expire the
// old values and replace them with new values.
// That is what `expiringAfter` does.

Channel.prototype.expiringAfter = function (N) {
    var ch = Object.create(this);
    ch._channel = this;
    ch._bufferLength = N;
    ch.put = expiringPut;
    return ch;
};

function expiringPut(value, callback) {
    while (this.backlog > this._bufferLength) {
        this.take();
    }
    this._channel.put(value, callback);
    return this;
}

module.exports = Channel;
<|endoftext|>


<commit_before>import Transform from '../Transform';
import {inherits} from 'vega-util';

/**
 * Propagates a new pulse without any tuples so long as the input
 * pulse contains some added, removed or modified tuples.
 * @constructor
 */
export default function Sieve(params) {
  Transform.call(this, null, params);
  this.modified(true); // always treat as modified
}

var prototype = inherits(Sieve, Transform);

prototype.transform = function(_, pulse) {
  this.value = pulse.source;
  return pulse.changed()
    ? pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS)
    : pulse.StopPropagation;
};
<commit_msg>Update Sieve jsdoc.<commit_after>import Transform from '../Transform';
import {inherits} from 'vega-util';

/**
 * Propagates a new pulse without any tuples so long as the input
 * pulse contains some added, removed or modified tuples.
 * @param {object} params - The parameters for this operator.
 * @constructor
 */
export default function Sieve(params) {
  Transform.call(this, null, params);
  this.modified(true); // always treat as modified
}

var prototype = inherits(Sieve, Transform);

prototype.transform = function(_, pulse) {
  this.value = pulse.source;
  return pulse.changed()
    ? pulse.fork(pulse.NO_SOURCE | pulse.NO_FIELDS)
    : pulse.StopPropagation;
};
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var DuplexStream = require('stream').Duplex;

module.exports = class IrcChannel {
    constructor(irc_client, channel_name, key) {
        this.irc_client = irc_client;
        this.name = channel_name;

        // TODO: Proxy channel related events from irc_bot to this instance

        this.say = _.partial(irc_client.say.bind(irc_client), channel_name);
        this.notice = _.partial(irc_client.notice.bind(irc_client), channel_name);
        // this.action = _.partial(irc_client.action.bind(irc_client), channel_name);
        this.part = _.partial(irc_client.part.bind(irc_client), channel_name);
        this.join = _.partial(irc_client.join.bind(irc_client), channel_name);
        this.mode = _.partial(irc_client.mode.bind(irc_client), channel_name);
        this.banlist = _.partial(irc_client.banlist.bind(irc_client), channel_name);
        this.ban = _.partial(irc_client.ban.bind(irc_client), channel_name);
        this.unban = _.partial(irc_client.unban.bind(irc_client), channel_name);

        this.users = [];
        irc_client.on('userlist', (event) => {
            if (event.channel.toLowerCase() === this.name.toLowerCase()) {
                this.users = event.users;
            }
        });
        irc_client.on('join', (event) => {
            if (event.channel === this.name) {
                this.users.push(event);
            }
        });
        irc_client.on('part', (event) => {
            if (event.channel === this.name) {
                this.users = _.filter(this.users, function(o) {
                    return o.nick.toLowerCase() !== event.nick.toLowerCase();
                });
            }
        });
        irc_client.on('kick', (event) => {
            if (event.channel === this.name) {
                this.users = _.filter(this.users, function(o) {
                    return o.nick.toLowerCase() !== event.kicked.toLowerCase();
                });
            }
        });
        irc_client.on('quit', (event) => {
            this.users = _.filter(this.users, function(o) {
                return o.nick.toLowerCase() !== event.nick.toLowerCase();
            });
        });
        irc_client.on('nick', (event) => {
            _.find(this.users, function(o) {
                if(o.nick.toLowerCase() === event.nick.toLowerCase()) {
                    o.nick = event.new_nick;
                    return true;
                }
            });
        });
        irc_client.on('mode', (event) => {
            /* event will be something like:
            {
                target: '#prawnsalad',
                nick: 'ChanServ',
                modes: [ { mode: '+o', param: 'prawnsalad' } ],
                time: undefined
            }
            */

            if (event.target.toLowerCase() !== this.name.toLowerCase()) {
                return;
            }

            // There can be multiple modes set at once, loop through
            _.each(event.modes, function(mode) {
                // If this mode has a user prefix then we need to update the user object
                // eg. +o +h +v
                let user_prefix = _.find(irc_client.network.options.PREFIX, {
                    mode: mode.mode[1],
                });

                if (!user_prefix) {
                    // TODO : manage channel mode changes
                } else { // It's a user mode
                    // Find the user affected
                    let user = _.find(this.users, function(user) {
                        return user.nick.toLowerCase() === mode.param.toLowerCase();
                    });

                    if (!user) {
                        return;
                    }

                    if (mode.mode[0] === '+') {
                        user.modes = user.modes || [];
                        user.modes.push(mode.mode[1]);
                    } else {
                        _.pull(user.modes, mode.mode[1]);
                    }
                }
            });
        });

        this.join(key);
    }

    /**
     * Relay messages between this channel to another
     * @param  {IrcChannel|String} target_chan Target channel
     * @param  {Object} opts        Extra options
     *
     * opts may contain the following properties:
     * one_way (false) Only relay messages to target_chan, not the reverse
     * replay_nicks (true) Include the sending nick as part of the relayed message
     */
    relay(target_chan, opts) {
        opts = _.extend({
            one_way: false,
            replay_nicks: true
        }, opts);

        if (typeof target_chan === 'string') {
            target_chan = this.irc_client.channel(target_chan);
        }
        var this_stream = this.stream(opts);
        var other_stream = target_chan.stream(opts);

        this_stream.pipe(other_stream);
        if (!opts.one_way) {
            other_stream.pipe(this_stream);
        }
    }

    stream(stream_opts) {
        var read_queue = [];
        var is_reading = false;

        var stream = new DuplexStream({
            objectMode: true,

            write: (chunk, encoding, next) => {
                // Support piping from one irc buffer to another
                if (typeof chunk === 'object' && typeof chunk.message === 'string') {
                    if (stream_opts.replay_nicks) {
                        chunk = '<' + chunk.nick + '> ' + chunk.message;
                    } else {
                        chunk = chunk.message;
                    }
                }

                this.say(chunk.toString());
                next();
            },

            read: () => {
                is_reading = true;

                while (read_queue.length > 0) {
                    let message = read_queue.shift();
                    if (stream.push(message) === false) {
                        is_reading = false;
                        break;
                    }
                }
            }
        });

        this.irc_client.on('privmsg', (event) => {
            if (event.target.toLowerCase() === this.name.toLowerCase()) {
                read_queue.push(event);

                if (is_reading) {
                    stream._read();
                }
            }
        });

        return stream;
    }

    updateUsers(cb) {
        let updateUserList = (event) => {
            if (event.channel.toLowerCase() === this.name.toLowerCase()) {
                this.irc_client.removeListener('userlist', updateUserList);
                if (typeof cb === 'function') { cb(this); }
            }
        };

        this.irc_client.on('userlist', updateUserList);
        this.irc_client.raw('NAMES', this.name);
    }
};
<commit_msg>fix 'this' shadowing<commit_after>'use strict';

var _ = require('lodash');
var DuplexStream = require('stream').Duplex;

module.exports = class IrcChannel {
    constructor(irc_client, channel_name, key) {
        this.irc_client = irc_client;
        this.name = channel_name;

        // TODO: Proxy channel related events from irc_bot to this instance

        this.say = _.partial(irc_client.say.bind(irc_client), channel_name);
        this.notice = _.partial(irc_client.notice.bind(irc_client), channel_name);
        // this.action = _.partial(irc_client.action.bind(irc_client), channel_name);
        this.part = _.partial(irc_client.part.bind(irc_client), channel_name);
        this.join = _.partial(irc_client.join.bind(irc_client), channel_name);
        this.mode = _.partial(irc_client.mode.bind(irc_client), channel_name);
        this.banlist = _.partial(irc_client.banlist.bind(irc_client), channel_name);
        this.ban = _.partial(irc_client.ban.bind(irc_client), channel_name);
        this.unban = _.partial(irc_client.unban.bind(irc_client), channel_name);

        this.users = [];
        irc_client.on('userlist', (event) => {
            if (event.channel.toLowerCase() === this.name.toLowerCase()) {
                this.users = event.users;
            }
        });
        irc_client.on('join', (event) => {
            if (event.channel === this.name) {
                this.users.push(event);
            }
        });
        irc_client.on('part', (event) => {
            if (event.channel === this.name) {
                this.users = _.filter(this.users, function(o) {
                    return o.nick.toLowerCase() !== event.nick.toLowerCase();
                });
            }
        });
        irc_client.on('kick', (event) => {
            if (event.channel === this.name) {
                this.users = _.filter(this.users, function(o) {
                    return o.nick.toLowerCase() !== event.kicked.toLowerCase();
                });
            }
        });
        irc_client.on('quit', (event) => {
            this.users = _.filter(this.users, function(o) {
                return o.nick.toLowerCase() !== event.nick.toLowerCase();
            });
        });
        irc_client.on('nick', (event) => {
            _.find(this.users, function(o) {
                if(o.nick.toLowerCase() === event.nick.toLowerCase()) {
                    o.nick = event.new_nick;
                    return true;
                }
            });
        });
        irc_client.on('mode', (event) => {
            /* event will be something like:
            {
                target: '#prawnsalad',
                nick: 'ChanServ',
                modes: [ { mode: '+o', param: 'prawnsalad' } ],
                time: undefined
            }
            */

            if (event.target.toLowerCase() !== this.name.toLowerCase()) {
                return;
            }

            // There can be multiple modes set at once, loop through
            _.each(event.modes, mode => {
                // If this mode has a user prefix then we need to update the user object
                // eg. +o +h +v
                let user_prefix = _.find(irc_client.network.options.PREFIX, {
                    mode: mode.mode[1],
                });

                if (!user_prefix) {
                    // TODO : manage channel mode changes
                } else { // It's a user mode
                    // Find the user affected
                    let user = _.find(this.users, user =>
                        user.nick.toLowerCase() === mode.param.toLowerCase()
                    );

                    if (!user) {
                        return;
                    }

                    if (mode.mode[0] === '+') {
                        user.modes = user.modes || [];
                        user.modes.push(mode.mode[1]);
                    } else {
                        _.pull(user.modes, mode.mode[1]);
                    }
                }
            });
        });

        this.join(key);
    }

    /**
     * Relay messages between this channel to another
     * @param  {IrcChannel|String} target_chan Target channel
     * @param  {Object} opts        Extra options
     *
     * opts may contain the following properties:
     * one_way (false) Only relay messages to target_chan, not the reverse
     * replay_nicks (true) Include the sending nick as part of the relayed message
     */
    relay(target_chan, opts) {
        opts = _.extend({
            one_way: false,
            replay_nicks: true
        }, opts);

        if (typeof target_chan === 'string') {
            target_chan = this.irc_client.channel(target_chan);
        }
        var this_stream = this.stream(opts);
        var other_stream = target_chan.stream(opts);

        this_stream.pipe(other_stream);
        if (!opts.one_way) {
            other_stream.pipe(this_stream);
        }
    }

    stream(stream_opts) {
        var read_queue = [];
        var is_reading = false;

        var stream = new DuplexStream({
            objectMode: true,

            write: (chunk, encoding, next) => {
                // Support piping from one irc buffer to another
                if (typeof chunk === 'object' && typeof chunk.message === 'string') {
                    if (stream_opts.replay_nicks) {
                        chunk = '<' + chunk.nick + '> ' + chunk.message;
                    } else {
                        chunk = chunk.message;
                    }
                }

                this.say(chunk.toString());
                next();
            },

            read: () => {
                is_reading = true;

                while (read_queue.length > 0) {
                    let message = read_queue.shift();
                    if (stream.push(message) === false) {
                        is_reading = false;
                        break;
                    }
                }
            }
        });

        this.irc_client.on('privmsg', (event) => {
            if (event.target.toLowerCase() === this.name.toLowerCase()) {
                read_queue.push(event);

                if (is_reading) {
                    stream._read();
                }
            }
        });

        return stream;
    }

    updateUsers(cb) {
        let updateUserList = (event) => {
            if (event.channel.toLowerCase() === this.name.toLowerCase()) {
                this.irc_client.removeListener('userlist', updateUserList);
                if (typeof cb === 'function') { cb(this); }
            }
        };

        this.irc_client.on('userlist', updateUserList);
        this.irc_client.raw('NAMES', this.name);
    }
};
<|endoftext|>


<commit_before>angular.module('cleaverApp', [])

// this factory handles requests between the client and server

.factory('Rec', ['$http', function($http) {
  return {
    getRestaurants: function(uniqueID) {
      var promise = $http.get('/api/' + uniqueID)
      .success(function(data, status, config, headers){
        restaurantData = data;
      })
      .error(function(){
        console.error('error getting restaurants');
      });
    },

    postLocation: function(data) {
      $http.post('/location', data)
    },
    restaurants: function() { return restaurantData; };
  }
}]);
<commit_msg>refactored Rec factory<commit_after>angular.module('cleaverApp', [])

// this factory handles requests between the client and server

.factory('Rec', ['$http', function($http) {
  var restaurants;

  var getRestaurants = function (uniqueID) {
    return $http({
      method: 'GET',
      url: '/api/' + uniqueID
    })
    .then(function (resp) {
      console.log("get restaurants", resp.data);
      restaurants = resp.data;
    });
  };

  var addLocation = function() {
    return $http({
      method: 'POST',
      url: '/api/location',
      // data: { url:link }
    }).then(function(resp) {
      console.log("added location", resp);
    });
  };

  return {
    getRestaurants: getRestaurants,
    addLocation: addLocation,
    restaurants: restaurants
  };

}]);
<|endoftext|>


<commit_before>// Copyright 2019, University of Colorado Boulder
/* eslint-disable bad-sim-text */

/**
 * Lint detector for invalid text.
 * Lint is disabled for this file so the bad texts aren't themselves flagged.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

module.exports = function( context ) {

  const getBadTextTester = require( './getBadTextTester' );

  // see getBadTextTester for schema.
  const forbiddenTextObjects = [

    // should be using dot.Utils.roundSymmetric, Math.round does not treat positive and negative numbers
    // symmetrically see https://github.com/phetsims/dot/issues/35#issuecomment-113587879
    { id: 'Math.round(', codeTokens: [ 'Math', '.', 'round', '(' ] },

    // should be using `DOT/dotRandom`
    { id: 'Math.random()', codeTokens: [ 'Math', '.', 'random', '(', ')' ] },
    { id: '_.shuffle(', codeTokens: [ '_', '.', 'shuffle', '(' ] },
    { id: '_.sample(', codeTokens: [ '_', '.', 'sample', '(' ] },
    { id: '_.random(', codeTokens: [ '_', '.', 'random', '(' ] },
    { id: 'new Random()', codeTokens: [ 'new', 'Random', '(', ')' ] },

    // IE doesn't support:
    { id: 'Number.parseInt(', codeTokens: [ 'Number', '.', 'parseInt', '(' ] },
    { id: 'Array.prototype.find', codeTokens: [ 'Array', '.', 'prototype', '.', 'find' ] },

    // Use merge instead of _.extend for combining options/config. Leave out first letter to allow for `options = `
    // and `sliderOptions = _.extend` to both be caught.
    'ptions = _.extend(',
    'onfig = _.extend(',

    // ParallelDOM.pdomOrder should not be mutated, instead only set with `setPDOMOrder`
    '.pdomOrder.push(',

    // Should import dotRandom instead of using the namespace
    'phet.dot.dotRandom',

    // Prefer using Pointer.isTouchLike() to help support Pen. This is not set in stone, please see
    // https://github.com/phetsims/scenery/issues/1156 and feel free to discuss if there are usages you want to support.
    ' instanceof Touch ',

    // Prevent accidental importing of files from the TypeScript build output directory
    'chipper/dist',

    // Relying on these in sim code can break PhET-iO playback, instead use Sim.dimensionProperty, see https://github.com/phetsims/joist/issues/768
    'window.innerWidth',
    'window.innerHeight',

    // In sims, don't allow setTimout and setInterval calls coming from window, see https://github.com/phetsims/phet-info/issues/59
    {
      id: 'setTimeout(',
      regex: /(window\.| )setTimeout\(/
    },
    {
      id: 'setInterval(',
      regex: /(window\.| )setInterval\(/
    },

    // DOT/Utils.toFixed or DOT/Utils.toFixedNumber should be used instead of toFixed.
    // JavaScript's toFixed is notoriously buggy. Behavior differs depending on browser,
    // because the spec doesn't specify whether to round or floor.
    {
      id: '.toFixed(', // support regex with english names this way
      regex: new RegExp( '(?<!Utils)\\.toFixed\\(' ) // NOTE: eslint parsing breaks when using regex syntax like `/regex/`
    }
  ];

  return {
    Program: getBadTextTester( forbiddenTextObjects, context )
  };
};

module.exports.schema = [
  // JSON Schema for rule options goes here
];<commit_msg>use scenery imports through imports.js, plus lint rule, https://github.com/phetsims/scenery/issues/1324<commit_after>// Copyright 2019, University of Colorado Boulder
/* eslint-disable bad-sim-text */

/**
 * Lint detector for invalid text.
 * Lint is disabled for this file so the bad texts aren't themselves flagged.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Michael Kauzmann (PhET Interactive Simulations)
 */

module.exports = function( context ) {

  const getBadTextTester = require( './getBadTextTester' );

  // see getBadTextTester for schema.
  const forbiddenTextObjects = [

    // should be using dot.Utils.roundSymmetric, Math.round does not treat positive and negative numbers
    // symmetrically see https://github.com/phetsims/dot/issues/35#issuecomment-113587879
    { id: 'Math.round(', codeTokens: [ 'Math', '.', 'round', '(' ] },

    // should be using `DOT/dotRandom`
    { id: 'Math.random()', codeTokens: [ 'Math', '.', 'random', '(', ')' ] },
    { id: '_.shuffle(', codeTokens: [ '_', '.', 'shuffle', '(' ] },
    { id: '_.sample(', codeTokens: [ '_', '.', 'sample', '(' ] },
    { id: '_.random(', codeTokens: [ '_', '.', 'random', '(' ] },
    { id: 'new Random()', codeTokens: [ 'new', 'Random', '(', ')' ] },

    // IE doesn't support:
    { id: 'Number.parseInt(', codeTokens: [ 'Number', '.', 'parseInt', '(' ] },
    { id: 'Array.prototype.find', codeTokens: [ 'Array', '.', 'prototype', '.', 'find' ] },

    // Use merge instead of _.extend for combining options/config. Leave out first letter to allow for `options = `
    // and `sliderOptions = _.extend` to both be caught.
    'ptions = _.extend(',
    'onfig = _.extend(',

    // ParallelDOM.pdomOrder should not be mutated, instead only set with `setPDOMOrder`
    '.pdomOrder.push(',

    // Should import dotRandom instead of using the namespace
    'phet.dot.dotRandom',

    // Prefer using Pointer.isTouchLike() to help support Pen. This is not set in stone, please see
    // https://github.com/phetsims/scenery/issues/1156 and feel free to discuss if there are usages you want to support.
    ' instanceof Touch ',

    // Prevent accidental importing of files from the TypeScript build output directory
    'chipper/dist',

    // Relying on these in sim code can break PhET-iO playback, instead use Sim.dimensionProperty, see https://github.com/phetsims/joist/issues/768
    'window.innerWidth',
    'window.innerHeight',

    // In sims, don't allow setTimout and setInterval calls coming from window, see https://github.com/phetsims/phet-info/issues/59
    {
      id: 'setTimeout(',
      regex: /(window\.| )setTimeout\(/
    },
    {
      id: 'setInterval(',
      regex: /(window\.| )setInterval\(/
    },

    // Decided on during developer meeting, in regards to https://github.com/phetsims/scenery/issues/1324, use `{ Type }`
    // import syntax from SCENERY/imports.js
    {
      id: 'import from scenery not from imports.js',
      regex: /import.*from.*\/scenery\/(?!js\/imports.js)/
    },

    // DOT/Utils.toFixed or DOT/Utils.toFixedNumber should be used instead of toFixed.
    // JavaScript's toFixed is notoriously buggy. Behavior differs depending on browser,
    // because the spec doesn't specify whether to round or floor.
    {
      id: '.toFixed(', // support regex with english names this way
      regex: new RegExp( '(?<!Utils)\\.toFixed\\(' ) // NOTE: eslint parsing breaks when using regex syntax like `/regex/`
    }
  ];

  return {
    Program: getBadTextTester( forbiddenTextObjects, context )
  };
};

module.exports.schema = [
  // JSON Schema for rule options goes here
];<|endoftext|>