<commit_before>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { isRtl } from 'flavours/glitch/util/rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
import Icon from 'flavours/glitch/components/icon';
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/util/idna';

const textMatchesTarget = (text, origin, host) => {
  return (text === origin || text === host
          || text.startsWith(origin + '/') || text.startsWith(host + '/')
          || 'www.' + text === host || ('www.' + text).startsWith(host + '/'));
}

const isLinkMisleading = (link) => {
  let linkTextParts = [];

  // Reconstruct visible text, as we do not have much control over how links
  // from remote software look, and we can't rely on `innerText` because the
  // `invisible` class does not set `display` to `none`.

  const walk = (node) => {
    switch (node.nodeType) {
    case Node.TEXT_NODE:
      linkTextParts.push(node.textContent);
      break;
    case Node.ELEMENT_NODE:
      if (node.classList.contains('invisible')) return;
      const children = node.childNodes;
      for (let i = 0; i < children.length; i++) {
        walk(children[i]);
      }
      break;
    }
  };

  walk(link);

  const linkText = linkTextParts.join('');
  const targetURL = new URL(link.href);

  if (targetURL.protocol === 'magnet:') {
    return !linkText.startsWith('magnet:');
  }

  if (targetURL.protocol === 'xmpp:') {
    return !(linkText === targetURL.href || 'xmpp:' + linkText === targetURL.href);
  }

  // The following may not work with international domain names
  if (textMatchesTarget(linkText, targetURL.origin, targetURL.host) || textMatchesTarget(linkText.toLowerCase(), targetURL.origin, targetURL.host)) {
    return false;
  }

  // The link hasn't been recognized, maybe it features an international domain name
  const hostname = decodeIDNA(targetURL.hostname).normalize('NFKC');
  const host = targetURL.host.replace(targetURL.hostname, hostname);
  const origin = targetURL.origin.replace(targetURL.host, host);
  const text = linkText.normalize('NFKC');
  return !(textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host));
};

export default class StatusContent extends React.PureComponent {

  static propTypes = {
    status: ImmutablePropTypes.map.isRequired,
    expanded: PropTypes.bool,
    collapsed: PropTypes.bool,
    onExpandedToggle: PropTypes.func,
    media: PropTypes.element,
    mediaIcon: PropTypes.string,
    parseClick: PropTypes.func,
    disabled: PropTypes.bool,
    onUpdate: PropTypes.func,
    tagLinks: PropTypes.bool,
    rewriteMentions: PropTypes.string,
  };

  static defaultProps = {
    tagLinks: true,
    rewriteMentions: 'no',
  };

  state = {
    hidden: true,
  };

  _updateStatusLinks () {
    const node = this.contentsNode;
    const { tagLinks, rewriteMentions } = this.props;

    if (!node) {
      return;
    }

    const links = node.querySelectorAll('a');

    for (var i = 0; i < links.length; ++i) {
      let link = links[i];
      if (link.classList.contains('status-link')) {
        continue;
      }
      link.classList.add('status-link');

      let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));

      if (mention) {
        link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
        link.setAttribute('title', mention.get('acct'));
        if (rewriteMentions !== 'no') {
          while (link.firstChild) link.removeChild(link.firstChild);
          link.appendChild(document.createTextNode('@'));
          const acctSpan = document.createElement('span');
          acctSpan.textContent = rewriteMentions === 'acct' ? mention.get('acct') : mention.get('username');
          link.appendChild(acctSpan);
        }
      } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
        link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
      } else {
        link.addEventListener('click', this.onLinkClick.bind(this), false);
        link.setAttribute('title', link.href);
        link.classList.add('unhandled-link');

        try {
          if (tagLinks && isLinkMisleading(link)) {
            // Add a tag besides the link to display its origin

            const url = new URL(link.href);
            const tag = document.createElement('span');
            tag.classList.add('link-origin-tag');
            switch (url.protocol) {
            case 'xmpp:':
              tag.textContent = `[${url.href}]`;
              break;
            case 'magnet:':
              tag.textContent = '(magnet)';
              break;
            default:
              tag.textContent = `[${url.host}]`;
            }
            link.insertAdjacentText('beforeend', ' ');
            link.insertAdjacentElement('beforeend', tag);
          }
        } catch (e) {
          // The URL is invalid, remove the href just to be safe
          if (tagLinks && e instanceof TypeError) link.removeAttribute('href');
        }
      }

      link.setAttribute('target', '_blank');
      link.setAttribute('rel', 'noopener noreferrer');
    }
  }

  _updateStatusEmojis () {
    const node = this.node;

    if (!node || autoPlayGif) {
      return;
    }

    const emojis = node.querySelectorAll('.custom-emoji');

    for (var i = 0; i < emojis.length; i++) {
      let emoji = emojis[i];
      if (emoji.classList.contains('status-emoji')) {
        continue;
      }
      emoji.classList.add('status-emoji');

      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
    }
  }

  componentDidMount () {
    this._updateStatusLinks();
    this._updateStatusEmojis();
  }

  componentDidUpdate () {
    this._updateStatusLinks();
    this._updateStatusEmojis();
    if (this.props.onUpdate) this.props.onUpdate();
  }

  onLinkClick = (e) => {
    if (this.props.collapsed) {
      if (this.props.parseClick) this.props.parseClick(e);
    }
  }

  onMentionClick = (mention, e) => {
    if (this.props.parseClick) {
      this.props.parseClick(e, `/accounts/${mention.get('id')}`);
    }
  }

  onHashtagClick = (hashtag, e) => {
    hashtag = hashtag.replace(/^#/, '');

    if (this.props.parseClick) {
      this.props.parseClick(e, `/timelines/tag/${hashtag}`);
    }
  }

  handleEmojiMouseEnter = ({ target }) => {
    target.src = target.getAttribute('data-original');
  }

  handleEmojiMouseLeave = ({ target }) => {
    target.src = target.getAttribute('data-static');
  }

  handleMouseDown = (e) => {
    this.startXY = [e.clientX, e.clientY];
  }

  handleMouseUp = (e) => {
    const { parseClick, disabled } = this.props;

    if (disabled || !this.startXY) {
      return;
    }

    const [ startX, startY ] = this.startXY;
    const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];

    let element = e.target;
    while (element) {
      if (['button', 'video', 'a', 'label', 'wave'].includes(element.localName)) {
        return;
      }
      element = element.parentNode;
    }

    if (deltaX + deltaY < 5 && e.button === 0 && parseClick) {
      parseClick(e);
    }

    this.startXY = null;
  }

  handleSpoilerClick = (e) => {
    e.preventDefault();

    if (this.props.onExpandedToggle) {
      this.props.onExpandedToggle();
    } else {
      this.setState({ hidden: !this.state.hidden });
    }
  }

  setRef = (c) => {
    this.node = c;
  }

  setContentsRef = (c) => {
    this.contentsNode = c;
  }

  render () {
    const {
      status,
      media,
      mediaIcon,
      parseClick,
      disabled,
      tagLinks,
      rewriteMentions,
    } = this.props;

    const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;

    const content = { __html: status.get('contentHtml') };
    const spoilerContent = { __html: status.get('spoilerHtml') };
    const directionStyle = { direction: 'ltr' };
    const classNames = classnames('status__content', {
      'status__content--with-action': parseClick && !disabled,
      'status__content--with-spoiler': status.get('spoiler_text').length > 0,
    });

    if (isRtl(status.get('search_index'))) {
      directionStyle.direction = 'rtl';
    }

    if (status.get('spoiler_text').length > 0) {
      let mentionsPlaceholder = '';

      const mentionLinks = status.get('mentions').map(item => (
        <Permalink
          to={`/accounts/${item.get('id')}`}
          href={item.get('url')}
          key={item.get('id')}
          className='mention'
        >
          @{item.get('username')}
        </Permalink>
      )).reduce((aggregate, item) => [...aggregate, item, ' '], []);

      const toggleText = hidden ? [
        <FormattedMessage
          id='status.show_more'
          defaultMessage='Show more'
          key='0'
        />,
        mediaIcon ? (
          <Icon
            fixedWidth
            className='status__content__spoiler-icon'
            id={mediaIcon}
            aria-hidden='true'
            key='1'
          />
        ) : null,
      ] : [
        <FormattedMessage
          id='status.show_less'
          defaultMessage='Show less'
          key='0'
        />,
      ];

      if (hidden) {
        mentionsPlaceholder =
{mentionLinks}
;
      }

      return (

            {' '}
            <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>
              {toggleText}
            </button>

          {mentionsPlaceholder}

          <div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
            <div
              ref={this.setContentsRef}
              key={`contents-${tagLinks}`}
              style={directionStyle}
              tabIndex={!hidden ? 0 : null}
              dangerouslySetInnerHTML={content}
              className='status__content__text'
            />
            {media}

      );
    } else if (parseClick) {
      return (

          <div
            ref={this.setContentsRef}
            key={`contents-${tagLinks}-${rewriteMentions}`}
            dangerouslySetInnerHTML={content}
            className='status__content__text'
            tabIndex='0'
          />
          {media}

      );
    } else {
      return (

          <div ref={this.setContentsRef} key={`contents-${tagLinks}`} className='status__content__text' dangerouslySetInnerHTML={content} tabIndex='0' />
          {media}

      );
    }
  }

}
<commit_msg>Fix clicking the audio player also opening toots in detailed view<commit_after>import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import { isRtl } from 'flavours/glitch/util/rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
import Icon from 'flavours/glitch/components/icon';
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/util/idna';

const textMatchesTarget = (text, origin, host) => {
  return (text === origin || text === host
          || text.startsWith(origin + '/') || text.startsWith(host + '/')
          || 'www.' + text === host || ('www.' + text).startsWith(host + '/'));
}

const isLinkMisleading = (link) => {
  let linkTextParts = [];

  // Reconstruct visible text, as we do not have much control over how links
  // from remote software look, and we can't rely on `innerText` because the
  // `invisible` class does not set `display` to `none`.

  const walk = (node) => {
    switch (node.nodeType) {
    case Node.TEXT_NODE:
      linkTextParts.push(node.textContent);
      break;
    case Node.ELEMENT_NODE:
      if (node.classList.contains('invisible')) return;
      const children = node.childNodes;
      for (let i = 0; i < children.length; i++) {
        walk(children[i]);
      }
      break;
    }
  };

  walk(link);

  const linkText = linkTextParts.join('');
  const targetURL = new URL(link.href);

  if (targetURL.protocol === 'magnet:') {
    return !linkText.startsWith('magnet:');
  }

  if (targetURL.protocol === 'xmpp:') {
    return !(linkText === targetURL.href || 'xmpp:' + linkText === targetURL.href);
  }

  // The following may not work with international domain names
  if (textMatchesTarget(linkText, targetURL.origin, targetURL.host) || textMatchesTarget(linkText.toLowerCase(), targetURL.origin, targetURL.host)) {
    return false;
  }

  // The link hasn't been recognized, maybe it features an international domain name
  const hostname = decodeIDNA(targetURL.hostname).normalize('NFKC');
  const host = targetURL.host.replace(targetURL.hostname, hostname);
  const origin = targetURL.origin.replace(targetURL.host, host);
  const text = linkText.normalize('NFKC');
  return !(textMatchesTarget(text, origin, host) || textMatchesTarget(text.toLowerCase(), origin, host));
};

export default class StatusContent extends React.PureComponent {

  static propTypes = {
    status: ImmutablePropTypes.map.isRequired,
    expanded: PropTypes.bool,
    collapsed: PropTypes.bool,
    onExpandedToggle: PropTypes.func,
    media: PropTypes.element,
    mediaIcon: PropTypes.string,
    parseClick: PropTypes.func,
    disabled: PropTypes.bool,
    onUpdate: PropTypes.func,
    tagLinks: PropTypes.bool,
    rewriteMentions: PropTypes.string,
  };

  static defaultProps = {
    tagLinks: true,
    rewriteMentions: 'no',
  };

  state = {
    hidden: true,
  };

  _updateStatusLinks () {
    const node = this.contentsNode;
    const { tagLinks, rewriteMentions } = this.props;

    if (!node) {
      return;
    }

    const links = node.querySelectorAll('a');

    for (var i = 0; i < links.length; ++i) {
      let link = links[i];
      if (link.classList.contains('status-link')) {
        continue;
      }
      link.classList.add('status-link');

      let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));

      if (mention) {
        link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
        link.setAttribute('title', mention.get('acct'));
        if (rewriteMentions !== 'no') {
          while (link.firstChild) link.removeChild(link.firstChild);
          link.appendChild(document.createTextNode('@'));
          const acctSpan = document.createElement('span');
          acctSpan.textContent = rewriteMentions === 'acct' ? mention.get('acct') : mention.get('username');
          link.appendChild(acctSpan);
        }
      } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
        link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
      } else {
        link.addEventListener('click', this.onLinkClick.bind(this), false);
        link.setAttribute('title', link.href);
        link.classList.add('unhandled-link');

        try {
          if (tagLinks && isLinkMisleading(link)) {
            // Add a tag besides the link to display its origin

            const url = new URL(link.href);
            const tag = document.createElement('span');
            tag.classList.add('link-origin-tag');
            switch (url.protocol) {
            case 'xmpp:':
              tag.textContent = `[${url.href}]`;
              break;
            case 'magnet:':
              tag.textContent = '(magnet)';
              break;
            default:
              tag.textContent = `[${url.host}]`;
            }
            link.insertAdjacentText('beforeend', ' ');
            link.insertAdjacentElement('beforeend', tag);
          }
        } catch (e) {
          // The URL is invalid, remove the href just to be safe
          if (tagLinks && e instanceof TypeError) link.removeAttribute('href');
        }
      }

      link.setAttribute('target', '_blank');
      link.setAttribute('rel', 'noopener noreferrer');
    }
  }

  _updateStatusEmojis () {
    const node = this.node;

    if (!node || autoPlayGif) {
      return;
    }

    const emojis = node.querySelectorAll('.custom-emoji');

    for (var i = 0; i < emojis.length; i++) {
      let emoji = emojis[i];
      if (emoji.classList.contains('status-emoji')) {
        continue;
      }
      emoji.classList.add('status-emoji');

      emoji.addEventListener('mouseenter', this.handleEmojiMouseEnter, false);
      emoji.addEventListener('mouseleave', this.handleEmojiMouseLeave, false);
    }
  }

  componentDidMount () {
    this._updateStatusLinks();
    this._updateStatusEmojis();
  }

  componentDidUpdate () {
    this._updateStatusLinks();
    this._updateStatusEmojis();
    if (this.props.onUpdate) this.props.onUpdate();
  }

  onLinkClick = (e) => {
    if (this.props.collapsed) {
      if (this.props.parseClick) this.props.parseClick(e);
    }
  }

  onMentionClick = (mention, e) => {
    if (this.props.parseClick) {
      this.props.parseClick(e, `/accounts/${mention.get('id')}`);
    }
  }

  onHashtagClick = (hashtag, e) => {
    hashtag = hashtag.replace(/^#/, '');

    if (this.props.parseClick) {
      this.props.parseClick(e, `/timelines/tag/${hashtag}`);
    }
  }

  handleEmojiMouseEnter = ({ target }) => {
    target.src = target.getAttribute('data-original');
  }

  handleEmojiMouseLeave = ({ target }) => {
    target.src = target.getAttribute('data-static');
  }

  handleMouseDown = (e) => {
    this.startXY = [e.clientX, e.clientY];
  }

  handleMouseUp = (e) => {
    const { parseClick, disabled } = this.props;

    if (disabled || !this.startXY) {
      return;
    }

    const [ startX, startY ] = this.startXY;
    const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)];

    let element = e.target;
    while (element) {
      if (['button', 'video', 'a', 'label', 'canvas'].includes(element.localName)) {
        return;
      }
      element = element.parentNode;
    }

    if (deltaX + deltaY < 5 && e.button === 0 && parseClick) {
      parseClick(e);
    }

    this.startXY = null;
  }

  handleSpoilerClick = (e) => {
    e.preventDefault();

    if (this.props.onExpandedToggle) {
      this.props.onExpandedToggle();
    } else {
      this.setState({ hidden: !this.state.hidden });
    }
  }

  setRef = (c) => {
    this.node = c;
  }

  setContentsRef = (c) => {
    this.contentsNode = c;
  }

  render () {
    const {
      status,
      media,
      mediaIcon,
      parseClick,
      disabled,
      tagLinks,
      rewriteMentions,
    } = this.props;

    const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;

    const content = { __html: status.get('contentHtml') };
    const spoilerContent = { __html: status.get('spoilerHtml') };
    const directionStyle = { direction: 'ltr' };
    const classNames = classnames('status__content', {
      'status__content--with-action': parseClick && !disabled,
      'status__content--with-spoiler': status.get('spoiler_text').length > 0,
    });

    if (isRtl(status.get('search_index'))) {
      directionStyle.direction = 'rtl';
    }

    if (status.get('spoiler_text').length > 0) {
      let mentionsPlaceholder = '';

      const mentionLinks = status.get('mentions').map(item => (
        <Permalink
          to={`/accounts/${item.get('id')}`}
          href={item.get('url')}
          key={item.get('id')}
          className='mention'
        >
          @{item.get('username')}
        </Permalink>
      )).reduce((aggregate, item) => [...aggregate, item, ' '], []);

      const toggleText = hidden ? [
        <FormattedMessage
          id='status.show_more'
          defaultMessage='Show more'
          key='0'
        />,
        mediaIcon ? (
          <Icon
            fixedWidth
            className='status__content__spoiler-icon'
            id={mediaIcon}
            aria-hidden='true'
            key='1'
          />
        ) : null,
      ] : [
        <FormattedMessage
          id='status.show_less'
          defaultMessage='Show less'
          key='0'
        />,
      ];

      if (hidden) {
        mentionsPlaceholder =
{mentionLinks}
;
      }

      return (

            {' '}
            <button tabIndex='0' className='status__content__spoiler-link' onClick={this.handleSpoilerClick}>
              {toggleText}
            </button>

          {mentionsPlaceholder}

          <div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
            <div
              ref={this.setContentsRef}
              key={`contents-${tagLinks}`}
              style={directionStyle}
              tabIndex={!hidden ? 0 : null}
              dangerouslySetInnerHTML={content}
              className='status__content__text'
            />
            {media}

      );
    } else if (parseClick) {
      return (

          <div
            ref={this.setContentsRef}
            key={`contents-${tagLinks}-${rewriteMentions}`}
            dangerouslySetInnerHTML={content}
            className='status__content__text'
            tabIndex='0'
          />
          {media}

      );
    } else {
      return (

          <div ref={this.setContentsRef} key={`contents-${tagLinks}`} className='status__content__text' dangerouslySetInnerHTML={content} tabIndex='0' />
          {media}

      );
    }
  }

}
<|endoftext|>


<commit_before>import transceiver from '../../src/transceiver';
import Channel from '../../src/channel';

const OriginalPromiseConstructor = transceiver.Promise;
const FakePromiseConstructor = () => {};
const channel = transceiver.channel('test');
const data = {
  hello: 'world'
};
const cb = sinon.spy(() => {
  return data;
});
const name = 'name';
const event = 'event';

describe('transceiver', () => {
  beforeEach(() => {
    cb.reset();
  });

  describe('.channel(String name)', () => {
    beforeEach(() => {
      spy(transceiver, 'channel');
    });

    it('should have been run once', () => {
      transceiver.channel('test');
      expect(transceiver.channel).to.have.been.calledOnce;
    });

    it('should have returned a channel instance', () => {
      transceiver.channel('test');
      expect(transceiver.channel).to.have.returned(sinon.match.instanceOf(Channel));
    });

    it('should have thrown an error if channel name is missing', () => {
      expect(() => {
        transceiver.channel();
      }).to.always.throw(Error);
    });

    it('should have thrown an error if channel name is not a string', () => {
      expect(() => {
        transceiver.channel(4);
      }).to.always.throw(Error);
    });
  });

  describe('.reply(String channel [, args])', () => {
    beforeEach(() => {
      spy(transceiver, 'reply');
    });

    it('should have been run once', () => {
      transceiver.reply('test', name, cb);
      expect(transceiver.reply).to.have.been.calledOnce;
    });
  });

  describe('.request(String channel [, args])', () => {
    beforeEach(() => {
      spy(transceiver, 'request');
      transceiver.request('test', name);
    });

    it('should have been run once', () => {
      expect(transceiver.request).to.have.been.calledOnce;
    });

    it('should have returned handler callback return value', (done) => {
      channel.request(name).then((result) => {
        expect(result).to.equal(data);
        done();
      });
    });
  });

  describe('.setPromise(Promise promise)', () => {
    beforeEach(() => {
      spy(transceiver, 'setPromise');
    });

    afterEach(() => {
      transceiver.setPromise(OriginalPromiseConstructor);
    });

    it('should have been run once', () => {
      transceiver.setPromise(null);
      expect(transceiver.setPromise).to.have.been.calledOnce;
    });

    it('should have changed promise constructor of each channel', () => {
      transceiver.channel(name);
      transceiver.setPromise(FakePromiseConstructor);
      expect(transceiver.channel(name).Promise).to.equals(FakePromiseConstructor);
    });
  });
});
<commit_msg>Add shorthands tests<commit_after>import transceiver from '../../src/transceiver';
import Channel from '../../src/channel';

const OriginalPromiseConstructor = transceiver.Promise;
const FakePromiseConstructor = () => {};
const channel = transceiver.channel('test');
const data = {
  hello: 'world'
};
const cb = sinon.spy(() => {
  return data;
});
const name = 'name';
const event = 'event';

describe('transceiver', () => {
  beforeEach(() => {
    cb.reset();
  });

  describe('.channel(String name)', () => {
    beforeEach(() => {
      spy(transceiver, 'channel');
    });

    it('should have been run once', () => {
      transceiver.channel('test');
      expect(transceiver.channel).to.have.been.calledOnce;
    });

    it('should have returned a channel instance', () => {
      transceiver.channel('test');
      expect(transceiver.channel).to.have.returned(sinon.match.instanceOf(Channel));
    });

    it('should have thrown an error if channel name is missing', () => {
      expect(() => {
        transceiver.channel();
      }).to.always.throw(Error);
    });

    it('should have thrown an error if channel name is not a string', () => {
      expect(() => {
        transceiver.channel(4);
      }).to.always.throw(Error);
    });
  });

  describe('.setPromise(Promise promise)', () => {
    beforeEach(() => {
      spy(transceiver, 'setPromise');
    });

    afterEach(() => {
      transceiver.setPromise(OriginalPromiseConstructor);
    });

    it('should have been run once', () => {
      transceiver.setPromise(null);
      expect(transceiver.setPromise).to.have.been.calledOnce;
    });

    it('should have changed promise constructor of each channel', () => {
      transceiver.channel(name);
      transceiver.setPromise(FakePromiseConstructor);
      expect(transceiver.channel(name).Promise).to.equals(FakePromiseConstructor);
    });
  });

  describe('Shorthands', () => {
    describe('.request(String channelName, ...args)', () => {
      it('should have called channel.request(...args) with given arguments', () => {
        spy(channel, 'request');
        transceiver.reply('test', name, cb);
        transceiver.request('test', name);
        expect(channel.request).to.have.been.calledWithExactly(name);
      });
    });

    describe('.reply(String channelName, ...args)', () => {
      it('should have called channel.reply(...args) with given arguments', () => {
        spy(channel, 'reply');
        transceiver.reply('test', name, cb);
        expect(channel.reply).to.have.been.calledWithExactly(name, cb);
      });
    });

    describe('.replyPromise(String channelName, ...args)', () => {
      it('should have called channel.replyPromise(...args) with given arguments', () => {
        spy(channel, 'replyPromise');
        transceiver.replyPromise('test', name, cb);
        expect(channel.replyPromise).to.have.been.calledWithExactly(name, cb);
      });
    });

    describe('.all(String channelName, ...args)', () => {
      it('should have called channel.all(...args) with given arguments', () => {
        spy(channel, 'all');
        transceiver.all('test', []);
        expect(channel.all).to.have.been.calledWithExactly([]);
      });
    });

    describe('.race(String channelName, ...args)', () => {
      it('should have called channel.race(...args) with given arguments', () => {
        spy(channel, 'race');
        transceiver.race('test', []);
        expect(channel.race).to.have.been.calledWithExactly([]);
      });
    });

    describe('.requestArray(String channelName, ...args)', () => {
      it('should have called channel.requestArray(...args) with given arguments', () => {
        spy(channel, 'requestArray');
        transceiver.requestArray('test', []);
        expect(channel.requestArray).to.have.been.calledWithExactly([]);
      });
    });

    describe('.requestProps(String channelName, ...args)', () => {
      it('should have called channel.requestProps(...args) with given arguments', () => {
        spy(channel, 'requestProps');
        transceiver.requestProps('test', []);
        expect(channel.requestProps).to.have.been.calledWithExactly([]);
      });
    });

    describe('.emit(String channelName, ...args)', () => {
      it('should have called channel.emit(...args) with given arguments', () => {
        spy(channel, 'emit');
        transceiver.emit('test', event);
        expect(channel.emit).to.have.been.calledWithExactly(event);
      });
    });

    describe('.on(String channelName, ...args)', () => {
      it('should have called channel.on(...args) with given arguments', () => {
        spy(channel, 'on');
        transceiver.on('test', event, cb);
        expect(channel.on).to.have.been.calledWithExactly(event, cb);
      });
    });

    describe('.once(String channelName, ...args)', () => {
      it('should have called channel.once(...args) with given arguments', () => {
        spy(channel, 'once');
        transceiver.once('test', event, cb);
        expect(channel.once).to.have.been.calledWithExactly(event, cb);
      });
    });

    describe('.off(String channelName, ...args)', () => {
      it('should have called channel.off(...args) with given arguments', () => {
        spy(channel, 'off');
        transceiver.off('test', event, cb);
        expect(channel.off).to.have.been.calledWithExactly(event, cb);
      });
    });

    describe('.reset(String channelName, ...args)', () => {
      it('should have called channel.reset(...args) with given arguments', () => {
        spy(channel, 'reset');
        transceiver.reset('test');
        expect(channel.reset).to.have.been.calledWithExactly();
      });
    });

  });
});
<|endoftext|>


<commit_before>var request = require('request');
var cheerio = require('cheerio');
var _ = require('underscore');

// TODO: allow this to be configurable by API
var FORECASTS_COUNT = 4;

// return the closest N forecasts in the future to current time with the smallest time range
var closestInstantForecasts = function($, times, N) {
  N = typeof N === 'undefined' ? 24 : N;

  return _.chain(times).map(function(time){
    // map to back to cheerio'fied object so we can read attr()
    return $(time);
  }).filter(function(time){
    // filter by instantaneous forecasts only
    return time.attr("from") == time.attr("to");
  }).sortBy(function(time){
    // sort by closest to most distant time
    var from = new Date(time.attr("from"));
    return Math.abs((new Date()).getTime() - from.getTime());
  }).first(N).value();
};

// return an object with temperature in C and F and a relative ETA in hours for that temperature.
var createForecastItem = function(time){
  var tempC = parseFloat(time.find('temperature').eq(0).attr('value'));
  return {
    hoursFromNow: Math.ceil(((new Date(time.attr("from"))).getTime() - (new Date()).getTime()) / 1000 / 60 / 60),

    // Celcius is kept decimal since met.no returns a pleasant formatted value.
    // Fahrenheit is rounded for reasons of laziness.
    // TODO: format Fahrenheit value.
    tempC: tempC,
    tempF: Math.round((9 / 5 * tempC) + 32)
  };
};

module.exports = {

  // call done with weather forecast results for GPS location lat, lon
  getForecast: function(lat, lon, done){

    var url = "http://api.met.no/weatherapi/locationforecast/1.8/?lat=" + lat + ";lon=" + lon;

    request(url, function(error, response, body) {

      if (!error && response.statusCode == 200) {

        var $ = cheerio.load(body);
        var nearbyForecasts = closestInstantForecasts($, $("product time"), FORECASTS_COUNT);

        done(nearbyForecasts.map(createForecastItem));

      } else {
        console.error("error requesting met.no forecast");
      }
    });
  }

};<commit_msg>Allow any 2xx or 3xx response code<commit_after>var request = require('request');
var cheerio = require('cheerio');
var _ = require('underscore');

// TODO: allow this to be configurable by API
var FORECASTS_COUNT = 4;

// return the closest N forecasts in the future to current time with the smallest time range
var closestInstantForecasts = function($, times, N) {
  N = typeof N === 'undefined' ? 24 : N;

  return _.chain(times).map(function(time){
    // map to back to cheerio'fied object so we can read attr()
    return $(time);
  }).filter(function(time){
    // filter by instantaneous forecasts only
    return time.attr("from") == time.attr("to");
  }).sortBy(function(time){
    // sort by closest to most distant time
    var from = new Date(time.attr("from"));
    return Math.abs((new Date()).getTime() - from.getTime());
  }).first(N).value();
};

// return an object with temperature in C and F and a relative ETA in hours for that temperature.
var createForecastItem = function(time){
  var tempC = parseFloat(time.find('temperature').eq(0).attr('value'));
  return {
    hoursFromNow: Math.ceil(((new Date(time.attr("from"))).getTime() - (new Date()).getTime()) / 1000 / 60 / 60),

    // Celcius is kept decimal since met.no returns a pleasant formatted value.
    // Fahrenheit is rounded for reasons of laziness.
    // TODO: format Fahrenheit value.
    tempC: tempC,
    tempF: Math.round((9 / 5 * tempC) + 32)
  };
};

module.exports = {

  // call done with weather forecast results for GPS location lat, lon
  getForecast: function(lat, lon, done){

    var url = "http://api.met.no/weatherapi/locationforecast/1.8/?lat=" + lat + ";lon=" + lon;

    request(url, function(error, response, body) {

      if (!error && response.statusCode < 400) {

        var $ = cheerio.load(body);
        var nearbyForecasts = closestInstantForecasts($, $("product time"), FORECASTS_COUNT);

        done(nearbyForecasts.map(createForecastItem));

      } else {
        console.error("error requesting met.no forecast");
      }
    });
  }

};
<|endoftext|>


<commit_before>const pool = require('../pool')
const {poolQuery} = require('../helpers')
const {requireAuth} = require('../auth')
const {processedString, processedTitleString} = require('../helpers/stringHelpers')
const {fetchPlaylists} = require('../helpers/playlistHelpers')
const async = require('async')
const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
  const playlistId = typeof req.query.playlistId !== 'undefined' ? req.query.playlistId : null
  const where = playlistId !== null ? `WHERE a.id < ${playlistId} ` : ''
  const query = `
    SELECT id AS playlistId FROM vq_playlists ${where}
    ORDER BY id DESC LIMIT 4
  `
  return fetchPlaylists(query).then(
    playlists => res.send({playlists})
  ).catch(
    err => {
      console.error(err)
      res.status(500).send(err)
    }
  )
})

router.post('/', requireAuth, (req, res) => {
  const user = req.user
  const title = processedTitleString(req.body.title)
  const description = processedString(req.body.description)
  const videos = req.body.selectedVideos
  const taskArray = []
  async.waterfall([
    (callback) => {
      const uploaderId = user.id
      const uploaderName = user.username
      const post = {title, description, creator: uploaderId}
      pool.query('INSERT INTO vq_playlists SET ?', post, (err, res) => {
        const playlistId = res.insertId
        callback(err, playlistId, uploaderName, uploaderId)
      })
    },
    (playlistId, uploaderName, uploaderId, callback) => {
      for (let i = 0; i < videos.length; i++) {
        taskArray.push(callback => {
          let playlistVideo = {playlistId: playlistId, videoId: videos[i]}
          pool.query('INSERT INTO vq_playlistvideos SET ?', playlistVideo, function(err) {
            callback(err)
          })
        })
      }
      async.series(taskArray, function(err) {
        if (err) return callback(err)
        const query = `
          SELECT a.id, a.videoId, b.title AS video_title, b.content, c.username AS video_uploader,
          COUNT(d.id) AS numLikes
          FROM vq_playlistvideos a JOIN vq_videos b ON a.videoId = b.id LEFT JOIN users c ON b.uploader = c.id
          LEFT JOIN content_likes d ON b.id = d.rootId AND d.rootType = 'video'
          WHERE a.playlistId = ? GROUP BY a.id ORDER BY a.id
        `
        pool.query(query, playlistId, (err, rows) => {
          callback(err, {
            playlist: rows,
            title: title,
            id: playlistId,
            uploader: uploaderName,
            uploaderId: uploaderId
          })
        })
      })
    }
  ], (err, result) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.send({result})
  })
})

router.post('/edit/title', requireAuth, (req, res) => {
  const title = req.body.title
  const playlistId = req.body.playlistId
  const newTitle = processedTitleString(title)
  const post = {
    title: newTitle
  }
  pool.query('UPDATE vq_playlists SET ? WHERE id = ?', [post, playlistId], err => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.json({result: newTitle})
  })
})

router.post('/edit/videos', requireAuth, (req, res) => {
  const playlistId = req.body.playlistId
  const selectedVideos = req.body.selectedVideos

  return poolQuery('DELETE FROM vq_playlistvideos WHERE playlistId = ?', playlistId).then(
    () => {
      let tasks = []
      for (let i = 0; i < selectedVideos.length; i++) {
        tasks.push(() => poolQuery('INSERT INTO vq_playlistvideos SET ?', {playlistId, videoId: selectedVideos[i]}))
      }
      return tasks.reduce((promise, task) => promise.then(task), Promise.resolve())
    }
  ).then(
    () => {
      let query = `
        SELECT a.id, a.videoId, b.title AS video_title, b.content, c.username AS video_uploader,
        COUNT(d.id) AS numLikes
        FROM vq_playlistvideos a
          JOIN vq_videos b ON a.videoId = b.id
          LEFT JOIN users c ON b.uploader = c.id
          LEFT JOIN content_likes d ON b.id = d.rootId AND d.rootType = 'video'
        WHERE a.playlistId = ? GROUP BY a.id ORDER BY a.id
      `
      return poolQuery(query, playlistId)
    }
  ).then(
    result => res.send({result})
  ).catch(
    err => {
      console.error(err)
      res.status(500).send({error: err})
    }
  )
})

router.delete('/', requireAuth, (req, res) => {
  const playlistId = typeof req.query.playlistId !== 'undefined' ? Number(req.query.playlistId) : 0
  async.waterfall([
    (callback) => {
      pool.query('SELECT * FROM vq_playlists WHERE id = ?', playlistId, (err, rows) => {
        if (!rows || rows.length === 0) return callback('User is not the owner of the playlist')
        callback(err)
      })
    },
    (callback) => {
      async.parallel([
        (callback) => {
          pool.query('DELETE FROM vq_playlists WHERE id = ?', playlistId, (err, result) => {
            callback(err, result)
          })
        },
        (callback) => {
          pool.query('DELETE FROM vq_pinned_playlists WHERE playlistId = ?', playlistId, (err, result) => {
            callback(err, result)
          })
        },
        (callback) => {
          pool.query('DELETE FROM vq_playlistvideos WHERE playlistId = ?', playlistId, (err, result) => {
            callback(err, result)
          })
        }
      ], (err, results) => {
        callback(err, results)
      })
    }
  ], (err, result) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.send({success: true})
  })
})

router.get('/pinned', (req, res) => {
  const query = `SELECT playlistId FROM vq_pinned_playlists ORDER BY id DESC`
  return fetchPlaylists(query).then(
    playlists => res.send({playlists})
  ).catch(
    err => {
      console.error(err)
      res.status(500).send(err)
    }
  )
})

router.post('/pinned', requireAuth, (req, res) => {
  const user = req.user
  const selectedPlaylists = req.body.selectedPlaylists

  if (selectedPlaylists.length > 5) {
    return res.status(500).send({error: 'Maximum playlist number exceeded'})
  }
  async.waterfall([
    (callback) => {
      const userType = user.userType
      if (userType !== 'master') {
        return callback('User is not authorized to perform this action')
      }
      pool.query('SELECT * FROM vq_pinned_playlists', (err, rows) => {
        if (rows) {
          pool.query('TRUNCATE vq_pinned_playlists', err => {
            if (err) {
              return callback(err)
            }
            callback(err)
          })
        } else {
          callback(err)
        }
      })
    },
    callback => {
      if (selectedPlaylists.length === 0) {
        callback(null)
      } else {
        let taskArray = []
        for (let i = selectedPlaylists.length - 1; i >= 0; i--) {
          taskArray.push(callback => {
            pool.query('INSERT INTO vq_pinned_playlists SET ?', {playlistId: selectedPlaylists[i]}, err => {
              callback(err)
            })
          })
        }
        async.series(taskArray, (err) => {
          callback(err)
        })
      }
    },
    callback => {
      const query = [
        'SELECT a.id, a.title, a.creator AS uploaderId, b.username AS uploader ',
        'FROM vq_playlists a JOIN vq_pinned_playlists c ON c.playlistId = a.id ',
        'LEFT JOIN users b ON a.creator = b.id ORDER BY c.id DESC'
      ].join('')
      fetchPlaylists(query, (err, playlists) => {
        callback(err, playlists)
      })
    }
  ], (err, playlists) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.json({playlists})
  })
})

router.get('/list', (req, res) => {
  const playlistId = req.query.playlistId ? Number(req.query.playlistId) : 0
  const where = playlistId !== 0 ? 'WHERE id < ' + playlistId + ' ' : ''
  const query = [
    'SELECT id, title FROM vq_playlists ',
    where,
    'ORDER BY id DESC LIMIT 11'
  ].join('')
  pool.query(query, (err, rows) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.send({result: rows})
  })
})

module.exports = router
<commit_msg>minor bug fix<commit_after>const pool = require('../pool')
const {poolQuery} = require('../helpers')
const {requireAuth} = require('../auth')
const {processedString, processedTitleString} = require('../helpers/stringHelpers')
const {fetchPlaylists} = require('../helpers/playlistHelpers')
const async = require('async')
const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
  const playlistId = typeof req.query.playlistId !== 'undefined' ? req.query.playlistId : null
  const where = playlistId !== null ? `WHERE id < ${playlistId} ` : ''
  const query = `
    SELECT id AS playlistId FROM vq_playlists ${where}
    ORDER BY id DESC LIMIT 4
  `
  return fetchPlaylists(query).then(
    playlists => res.send({playlists})
  ).catch(
    err => {
      console.error(err)
      res.status(500).send(err)
    }
  )
})

router.post('/', requireAuth, (req, res) => {
  const user = req.user
  const title = processedTitleString(req.body.title)
  const description = processedString(req.body.description)
  const videos = req.body.selectedVideos
  const taskArray = []
  async.waterfall([
    (callback) => {
      const uploaderId = user.id
      const uploaderName = user.username
      const post = {title, description, creator: uploaderId}
      pool.query('INSERT INTO vq_playlists SET ?', post, (err, res) => {
        const playlistId = res.insertId
        callback(err, playlistId, uploaderName, uploaderId)
      })
    },
    (playlistId, uploaderName, uploaderId, callback) => {
      for (let i = 0; i < videos.length; i++) {
        taskArray.push(callback => {
          let playlistVideo = {playlistId: playlistId, videoId: videos[i]}
          pool.query('INSERT INTO vq_playlistvideos SET ?', playlistVideo, function(err) {
            callback(err)
          })
        })
      }
      async.series(taskArray, function(err) {
        if (err) return callback(err)
        const query = `
          SELECT a.id, a.videoId, b.title AS video_title, b.content, c.username AS video_uploader,
          COUNT(d.id) AS numLikes
          FROM vq_playlistvideos a JOIN vq_videos b ON a.videoId = b.id LEFT JOIN users c ON b.uploader = c.id
          LEFT JOIN content_likes d ON b.id = d.rootId AND d.rootType = 'video'
          WHERE a.playlistId = ? GROUP BY a.id ORDER BY a.id
        `
        pool.query(query, playlistId, (err, rows) => {
          callback(err, {
            playlist: rows,
            title: title,
            id: playlistId,
            uploader: uploaderName,
            uploaderId: uploaderId
          })
        })
      })
    }
  ], (err, result) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.send({result})
  })
})

router.post('/edit/title', requireAuth, (req, res) => {
  const title = req.body.title
  const playlistId = req.body.playlistId
  const newTitle = processedTitleString(title)
  const post = {
    title: newTitle
  }
  pool.query('UPDATE vq_playlists SET ? WHERE id = ?', [post, playlistId], err => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.json({result: newTitle})
  })
})

router.post('/edit/videos', requireAuth, (req, res) => {
  const playlistId = req.body.playlistId
  const selectedVideos = req.body.selectedVideos

  return poolQuery('DELETE FROM vq_playlistvideos WHERE playlistId = ?', playlistId).then(
    () => {
      let tasks = []
      for (let i = 0; i < selectedVideos.length; i++) {
        tasks.push(() => poolQuery('INSERT INTO vq_playlistvideos SET ?', {playlistId, videoId: selectedVideos[i]}))
      }
      return tasks.reduce((promise, task) => promise.then(task), Promise.resolve())
    }
  ).then(
    () => {
      let query = `
        SELECT a.id, a.videoId, b.title AS video_title, b.content, c.username AS video_uploader,
        COUNT(d.id) AS numLikes
        FROM vq_playlistvideos a
          JOIN vq_videos b ON a.videoId = b.id
          LEFT JOIN users c ON b.uploader = c.id
          LEFT JOIN content_likes d ON b.id = d.rootId AND d.rootType = 'video'
        WHERE a.playlistId = ? GROUP BY a.id ORDER BY a.id
      `
      return poolQuery(query, playlistId)
    }
  ).then(
    result => res.send({result})
  ).catch(
    err => {
      console.error(err)
      res.status(500).send({error: err})
    }
  )
})

router.delete('/', requireAuth, (req, res) => {
  const playlistId = typeof req.query.playlistId !== 'undefined' ? Number(req.query.playlistId) : 0
  async.waterfall([
    (callback) => {
      pool.query('SELECT * FROM vq_playlists WHERE id = ?', playlistId, (err, rows) => {
        if (!rows || rows.length === 0) return callback('User is not the owner of the playlist')
        callback(err)
      })
    },
    (callback) => {
      async.parallel([
        (callback) => {
          pool.query('DELETE FROM vq_playlists WHERE id = ?', playlistId, (err, result) => {
            callback(err, result)
          })
        },
        (callback) => {
          pool.query('DELETE FROM vq_pinned_playlists WHERE playlistId = ?', playlistId, (err, result) => {
            callback(err, result)
          })
        },
        (callback) => {
          pool.query('DELETE FROM vq_playlistvideos WHERE playlistId = ?', playlistId, (err, result) => {
            callback(err, result)
          })
        }
      ], (err, results) => {
        callback(err, results)
      })
    }
  ], (err, result) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.send({success: true})
  })
})

router.get('/pinned', (req, res) => {
  const query = `SELECT playlistId FROM vq_pinned_playlists ORDER BY id DESC`
  return fetchPlaylists(query).then(
    playlists => res.send({playlists})
  ).catch(
    err => {
      console.error(err)
      res.status(500).send(err)
    }
  )
})

router.post('/pinned', requireAuth, (req, res) => {
  const user = req.user
  const selectedPlaylists = req.body.selectedPlaylists

  if (selectedPlaylists.length > 5) {
    return res.status(500).send({error: 'Maximum playlist number exceeded'})
  }
  async.waterfall([
    (callback) => {
      const userType = user.userType
      if (userType !== 'master') {
        return callback('User is not authorized to perform this action')
      }
      pool.query('SELECT * FROM vq_pinned_playlists', (err, rows) => {
        if (rows) {
          pool.query('TRUNCATE vq_pinned_playlists', err => {
            if (err) {
              return callback(err)
            }
            callback(err)
          })
        } else {
          callback(err)
        }
      })
    },
    callback => {
      if (selectedPlaylists.length === 0) {
        callback(null)
      } else {
        let taskArray = []
        for (let i = selectedPlaylists.length - 1; i >= 0; i--) {
          taskArray.push(callback => {
            pool.query('INSERT INTO vq_pinned_playlists SET ?', {playlistId: selectedPlaylists[i]}, err => {
              callback(err)
            })
          })
        }
        async.series(taskArray, (err) => {
          callback(err)
        })
      }
    },
    callback => {
      const query = [
        'SELECT a.id, a.title, a.creator AS uploaderId, b.username AS uploader ',
        'FROM vq_playlists a JOIN vq_pinned_playlists c ON c.playlistId = a.id ',
        'LEFT JOIN users b ON a.creator = b.id ORDER BY c.id DESC'
      ].join('')
      fetchPlaylists(query, (err, playlists) => {
        callback(err, playlists)
      })
    }
  ], (err, playlists) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.json({playlists})
  })
})

router.get('/list', (req, res) => {
  const playlistId = req.query.playlistId ? Number(req.query.playlistId) : 0
  const where = playlistId !== 0 ? 'WHERE id < ' + playlistId + ' ' : ''
  const query = [
    'SELECT id, title FROM vq_playlists ',
    where,
    'ORDER BY id DESC LIMIT 11'
  ].join('')
  pool.query(query, (err, rows) => {
    if (err) {
      console.error(err)
      return res.status(500).send({error: err})
    }
    res.send({result: rows})
  })
})

module.exports = router
<|endoftext|>


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

    /**
     * @ngdoc function
     * @name upBoardApp.controller:MainCtrl
     * @description
     * # MainCtrl
     * Controller of the upBoardApp
     */
    angular.module('upBoardApp')
      .controller('MainCtrl', mainCtrl);

    mainCtrl.$inject = ['$firebaseArray', 'Auth', 'Ref',
                        '$timeout', '$log', '$location',
                        'hotkeys', 'toaster', 'utility'];
    function mainCtrl($firebaseArray, Auth, Ref,
                      $timeout, $log, $location,
                      hotkeys, toaster, utility) {

        var vm = this;

        vm.currentIndex = 0;
        vm.loop = true;
        vm.activeDeck = null;
        vm.currentSlide = null;
        vm.logo = null;
        vm.timeoutPromises = [];

        (function init(){
            var auth = Auth.$getAuth();

            // go to login page
            if(auth == null) $location.path('/login');

            else{
                // retrieve active slide deck
                vm.data = $firebaseArray(Ref.child('users/' + auth.uid + '/decks').orderByChild('active').equalTo(true));
                vm.data.$loaded().then(startSlideShow);
            }

            // add key controls
            hotkeys.add({
              combo: 'ctrl+space',
              description: 'Pause/Stop slideshow',
              callback: function(){

                if(vm.timeoutPromises.length > 0){
                  cancelTimeouts();
                  toaster.pop('error', '', 'Slideshow stopped');
                }
                else toaster.pop('warn', '', 'Slideshow already stopped');
              }
            });

            hotkeys.add({
              combo: 'ctrl+enter',
              description: 'Resume slideshow',
              callback: function(){

                if(vm.timeoutPromises.length === 0){

                  if(!isEnd()) nextSlide();
                  else if(vm.loop) restart();
                  toaster.pop('info', '', 'Slideshow resumed');
                }
                else toaster.pop('warning', '', 'Slideshow already running');
              }
            });

            hotkeys.add({
              combo: 'ctrl+r',
              description: 'Restart slideshow',
              callback: function(){
                cancelTimeouts();
                restart();
                toaster.pop('info', '', 'Slideshow restarted');
              }
            });

            hotkeys.add({
              combo: 'ctrl+b',
              description: 'Back to previous slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(vm.currentSlide.slideId !== 0){
                  vm.currentIndex = vm.currentSlide.slideId != null ?
                                    vm.currentSlide.slideId - 1 : vm.currentIndex - 1;
                  setCurrentSlide();
                  toaster.pop('info', '', 'Previous slide');
                }
                else toaster.pop('warning', '', 'First slide');
              }
            });

            hotkeys.add({
              combo: 'ctrl+n',
              description: 'Skip to next slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(!isEnd()) {
                  vm.currentIndex = vm.currentSlide.slideId != null ?
                                    vm.currentSlide.slideId + 1 : vm.currentIndex + 1;
                  setCurrentSlide();
                  toaster.pop('info', '', 'Next slide');
                }
                else toaster.pop('warning', '', 'Last slide');
              }
            });

            hotkeys.add({
              combo: 'ctrl+l',
              description: 'Jump to last slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(!isEnd()){
                  vm.currentIndex = vm.activeDeck.slides.length - 1;
                  setCurrentSlide();
                  toaster.pop('info', '', 'Last slide');
                }
                else toaster.pop('warning', '', 'Last slide already');
              }
            });

            hotkeys.add({
              combo: 'ctrl+k',
              description: 'Jump to first slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(vm.currentSlide.slideId !== 0){
                  vm.currentIndex = 0;
                  setCurrentSlide();
                  toaster.pop('info', '', 'First slide');
                }
                else toaster.pop('warning', '', 'First slide already');
              }
            });

        })();

        function cancelTimeouts(){

          $log.debug('canceling ' + vm.timeoutPromises.length + ' promises');

          // loop through promises and cancel them all
          while(vm.timeoutPromises.length > 0){
            $timeout.cancel(vm.timeoutPromises.pop());
          }
        }

        function isEnd(){
            return vm.currentIndex >= vm.activeDeck.slides.length;
        }

        function restart(){
            vm.currentIndex = 0;
            nextSlide();
        }

        function nextSlide(){

//            $log.debug(vm.currentSlide);

            var slideTime = utility.calculateSlideTime(vm.currentSlide);

            var delay = slideTime + vm.currentSlide.timing.transitionTime + 2000;
//            $log.debug('delay: ' + delay);

            vm.timeoutPromises.push($timeout(function(){

                // get ready to go to next slide
                vm.timeoutPromises.push($timeout(function(){

                    setCurrentSlide();
                    if(!isEnd()) nextSlide();
                    else if(vm.loop) restart();

                }, Number(vm.currentSlide.timing.transitionTime) + 2000));  // compensate for transition slide time

                // go to transition slide
                vm.currentSlide = {type:'transition'};

            }, slideTime));
        }

        function setCurrentSlide(){

            // pass active deck id and slide id for firebase ref binding
            vm.currentSlide = vm.activeDeck.slides[vm.currentIndex];
            vm.currentSlide.activeDeckId = vm.activeDeck.$id;
            vm.currentSlide.slideId = vm.currentIndex++;
            vm.currentSlide.timeoutPromises = vm.timeoutPromises;
        }

        function startSlideShow(){
            if(vm.data != null && vm.data[0] != null){

              vm.activeDeck = vm.data[0];
              vm.logo = vm.activeDeck.logo;

              setCurrentSlide();
              if(!isEnd()) nextSlide();
            }
        }
    }

})();<commit_msg>minor toaster update<commit_after>(function(){
   'use strict';

    /**
     * @ngdoc function
     * @name upBoardApp.controller:MainCtrl
     * @description
     * # MainCtrl
     * Controller of the upBoardApp
     */
    angular.module('upBoardApp')
      .controller('MainCtrl', mainCtrl);

    mainCtrl.$inject = ['$firebaseArray', 'Auth', 'Ref',
                        '$timeout', '$log', '$location',
                        'hotkeys', 'toaster', 'utility'];
    function mainCtrl($firebaseArray, Auth, Ref,
                      $timeout, $log, $location,
                      hotkeys, toaster, utility) {

        var vm = this;

        vm.currentIndex = 0;
        vm.loop = true;
        vm.activeDeck = null;
        vm.currentSlide = null;
        vm.logo = null;
        vm.timeoutPromises = [];

        (function init(){
            var auth = Auth.$getAuth();

            // go to login page
            if(auth == null) $location.path('/login');

            else{
                // retrieve active slide deck
                vm.data = $firebaseArray(Ref.child('users/' + auth.uid + '/decks').orderByChild('active').equalTo(true));
                vm.data.$loaded().then(startSlideShow);
            }

            // add key controls
            hotkeys.add({
              combo: 'ctrl+space',
              description: 'Pause/Stop slideshow',
              callback: function(){

                if(vm.timeoutPromises.length > 0){
                  cancelTimeouts();
                  toaster.pop('error', '', 'Slideshow stopped');
                }
                else toaster.pop('warning', '', 'Slideshow already stopped');
              }
            });

            hotkeys.add({
              combo: 'ctrl+enter',
              description: 'Resume slideshow',
              callback: function(){

                if(vm.timeoutPromises.length === 0){

                  if(!isEnd()) nextSlide();
                  else if(vm.loop) restart();
                  toaster.pop('info', '', 'Slideshow resumed');
                }
                else toaster.pop('warning', '', 'Slideshow already running');
              }
            });

            hotkeys.add({
              combo: 'ctrl+r',
              description: 'Restart slideshow',
              callback: function(){
                cancelTimeouts();
                restart();
                toaster.pop('info', '', 'Slideshow restarted');
              }
            });

            hotkeys.add({
              combo: 'ctrl+b',
              description: 'Back to previous slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(vm.currentSlide.slideId !== 0){
                  vm.currentIndex = vm.currentSlide.slideId != null ?
                                    vm.currentSlide.slideId - 1 : vm.currentIndex - 1;
                  setCurrentSlide();
                  toaster.pop('info', '', 'Previous slide');
                }
                else toaster.pop('warning', '', 'First slide');
              }
            });

            hotkeys.add({
              combo: 'ctrl+n',
              description: 'Skip to next slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(!isEnd()) {
                  vm.currentIndex = vm.currentSlide.slideId != null ?
                                    vm.currentSlide.slideId + 1 : vm.currentIndex + 1;
                  setCurrentSlide();
                  toaster.pop('info', '', 'Next slide');
                }
                else toaster.pop('warning', '', 'Last slide');
              }
            });

            hotkeys.add({
              combo: 'ctrl+l',
              description: 'Jump to last slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(!isEnd()){
                  vm.currentIndex = vm.activeDeck.slides.length - 1;
                  setCurrentSlide();
                  toaster.pop('info', '', 'Last slide');
                }
                else toaster.pop('warning', '', 'Last slide already');
              }
            });

            hotkeys.add({
              combo: 'ctrl+k',
              description: 'Jump to first slide, slideshow stopped',
              callback: function(){

                cancelTimeouts();

                if(vm.currentSlide.slideId !== 0){
                  vm.currentIndex = 0;
                  setCurrentSlide();
                  toaster.pop('info', '', 'First slide');
                }
                else toaster.pop('warning', '', 'First slide already');
              }
            });

        })();

        function cancelTimeouts(){

          $log.debug('canceling ' + vm.timeoutPromises.length + ' promises');

          // loop through promises and cancel them all
          while(vm.timeoutPromises.length > 0){
            $timeout.cancel(vm.timeoutPromises.pop());
          }
        }

        function isEnd(){
            return vm.currentIndex >= vm.activeDeck.slides.length;
        }

        function restart(){
            vm.currentIndex = 0;
            nextSlide();
        }

        function nextSlide(){

//            $log.debug(vm.currentSlide);

            var slideTime = utility.calculateSlideTime(vm.currentSlide);

            var delay = slideTime + vm.currentSlide.timing.transitionTime + 2000;
//            $log.debug('delay: ' + delay);

            vm.timeoutPromises.push($timeout(function(){

                // get ready to go to next slide
                vm.timeoutPromises.push($timeout(function(){

                    setCurrentSlide();
                    if(!isEnd()) nextSlide();
                    else if(vm.loop) restart();

                }, Number(vm.currentSlide.timing.transitionTime) + 2000));  // compensate for transition slide time

                // go to transition slide
                vm.currentSlide = {type:'transition'};

            }, slideTime));
        }

        function setCurrentSlide(){

            // pass active deck id and slide id for firebase ref binding
            vm.currentSlide = vm.activeDeck.slides[vm.currentIndex];
            vm.currentSlide.activeDeckId = vm.activeDeck.$id;
            vm.currentSlide.slideId = vm.currentIndex++;
            vm.currentSlide.timeoutPromises = vm.timeoutPromises;
        }

        function startSlideShow(){
            if(vm.data != null && vm.data[0] != null){

              vm.activeDeck = vm.data[0];
              vm.logo = vm.activeDeck.logo;

              setCurrentSlide();
              if(!isEnd()) nextSlide();
            }
        }
    }

})();<|endoftext|>


<commit_before>/**
	A multi-line text input that supports rich formatting, such as bold, italics,
	and underlining.

	Use the _value_ property to get or set the displayed text.

	RichText is not supported on Android < 3.

	Selection operations and [insertAtCursor](#enyo.RichText::insertAtCursor)
	use the HTML Editing APIs.

	[HTML Editing APIs # Selection Reference](https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
*/
enyo.kind({
	name: "enyo.RichText",
	classes: "enyo-richtext enyo-selectable",
	published: {
		/**
			_allowHtml_ is enabled by default in RichText to take advantage of all the rich editing properties.
			However, this allows for **ANY** HTML to be inserted into the RichText, including _iframe_ and _script_ tags, which can be a secuity concern in some situations.
			If set to false, inserted HTML will be escaped.
		*/
		allowHtml: true,
		disabled: false,
		value: ""
	},
	//* Set to true to focus this control when it is rendered.
	defaultFocus: false,
	//* @protected
	osInfo: [
		{os: "android", version: 3},
		{os: "ios", version: 5}
	],
	kind: enyo.Input,
	attributes: {
		contenteditable: true
	},
	handlers: {
		onfocus: "focusHandler",
		onblur: "blurHandler"
	},
	// create RichText as a div if platform has contenteditable attribute, otherwise create it as a textarea
	create: function() {
		this.setTag(this.hasContentEditable()?"div":"textarea");
		this.inherited(arguments);
	},
	// simulate onchange event that inputs expose
	focusHandler: function() {
		this._value = this.getValue();
	},
	blurHandler: function() {
		if (this._value !== this.getValue()) {
			this.bubble("onchange");
		}
	},
	valueChanged: function() {
		if (this.hasFocus()) {
			this.selectAll();
			this.insertAtCursor(this.value);
		} else {
			this.setPropertyValue("content", this.value, "contentChanged");
		}
	},
	//* @public
		//* Returns true if the platform has contenteditable attribute.
	hasContentEditable: function() {
		for (var i=0, t, m; t=this.osInfo[i]; i++) {
			if (enyo.platform[t.os] < t.version) {
				return false;
			}
		}
		return true;
	},
	getValue: function() {
		if (this.hasNode()) {
			return this.node.innerHTML;
		}
	},
	//* Returns true if the RichText is focused.
	hasFocus: function() {
		if (this.hasNode()) {
			return document.activeElement === this.node;
		}
	},
	/**
		Returns the selection object.
	*/
	getSelection: function() {
		if (this.hasFocus()) {
			return window.getSelection();
		}
	},
	removeSelection: function(inStart) {
		var s = this.getSelection();
		if (s) {
			s[inStart ? "collapseToStart" : "collapseToEnd"]();
		}
	},
	modifySelection: function(inType, inDirection, inAmount) {
		var s = this.getSelection();
		if (s) {
			s.modify(inType || "move", inDirection, inAmount);
		}
	},
	//* Moves the cursor according to the Editing API.
	moveCursor: function(inDirection, inAmount) {
		this.modifySelection("move", inDirection, inAmount);
	},
	moveCursorToEnd: function() {
		this.moveCursor("forward", "documentboundary");
	},
	moveCursorToStart: function() {
		this.moveCursor("backward", "documentboundary");
	},
	selectAll: function() {
		if (this.hasFocus()) {
			document.execCommand("selectAll");
		}
	},
	//* Inserts HTML at the cursor position.  HTML is escaped unless the
	//* _allowHTML_ property is true.
	insertAtCursor: function(inValue) {
		if (this.hasFocus()) {
			var v = this.allowHtml ? inValue : enyo.Control.escapeHtml(inValue).replace(/\n/g, "
");
			document.execCommand("insertHTML", false, v);
		}
	}
});
<commit_msg>made osInfo and hasContentEditable() static per Ben's suggestion<commit_after>/**
	A multi-line text input that supports rich formatting, such as bold, italics,
	and underlining.

	Use the _value_ property to get or set the displayed text.

	RichText is not supported on Android < 3.

	Selection operations and [insertAtCursor](#enyo.RichText::insertAtCursor)
	use the HTML Editing APIs.

	[HTML Editing APIs # Selection Reference](https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
*/
enyo.kind({
	name: "enyo.RichText",
	classes: "enyo-richtext enyo-selectable",
	published: {
		/**
			_allowHtml_ is enabled by default in RichText to take advantage of all the rich editing properties.
			However, this allows for **ANY** HTML to be inserted into the RichText, including _iframe_ and _script_ tags, which can be a secuity concern in some situations.
			If set to false, inserted HTML will be escaped.
		*/
		allowHtml: true,
		disabled: false,
		value: ""
	},
	//* Set to true to focus this control when it is rendered.
	defaultFocus: false,
	//* @protected
	statics: {
		osInfo: [
			{os: "android", version: 3},
			{os: "ios", version: 5}
		],
		//* Returns true if the platform has contenteditable attribute.
		hasContentEditable: function() {
			for (var i=0, t, m; t=enyo.RichText.osInfo[i]; i++) {
				if (enyo.platform[t.os] < t.version) {
					return false;
				}
			}
			return true;
		}
	},
	kind: enyo.Input,
	attributes: {
		contenteditable: true
	},
	handlers: {
		onfocus: "focusHandler",
		onblur: "blurHandler"
	},
	// create RichText as a div if platform has contenteditable attribute, otherwise create it as a textarea
	create: function() {
		this.setTag(enyo.RichText.hasContentEditable()?"div":"textarea");
		this.inherited(arguments);
	},
	// simulate onchange event that inputs expose
	focusHandler: function() {
		this._value = this.getValue();
	},
	blurHandler: function() {
		if (this._value !== this.getValue()) {
			this.bubble("onchange");
		}
	},
	valueChanged: function() {
		if (this.hasFocus()) {
			this.selectAll();
			this.insertAtCursor(this.value);
		} else {
			this.setPropertyValue("content", this.value, "contentChanged");
		}
	},
	//* @public
	getValue: function() {
		if (this.hasNode()) {
			return this.node.innerHTML;
		}
	},
	//* Returns true if the RichText is focused.
	hasFocus: function() {
		if (this.hasNode()) {
			return document.activeElement === this.node;
		}
	},
	/**
		Returns the selection object.
	*/
	getSelection: function() {
		if (this.hasFocus()) {
			return window.getSelection();
		}
	},
	removeSelection: function(inStart) {
		var s = this.getSelection();
		if (s) {
			s[inStart ? "collapseToStart" : "collapseToEnd"]();
		}
	},
	modifySelection: function(inType, inDirection, inAmount) {
		var s = this.getSelection();
		if (s) {
			s.modify(inType || "move", inDirection, inAmount);
		}
	},
	//* Moves the cursor according to the Editing API.
	moveCursor: function(inDirection, inAmount) {
		this.modifySelection("move", inDirection, inAmount);
	},
	moveCursorToEnd: function() {
		this.moveCursor("forward", "documentboundary");
	},
	moveCursorToStart: function() {
		this.moveCursor("backward", "documentboundary");
	},
	selectAll: function() {
		if (this.hasFocus()) {
			document.execCommand("selectAll");
		}
	},
	//* Inserts HTML at the cursor position.  HTML is escaped unless the
	//* _allowHTML_ property is true.
	insertAtCursor: function(inValue) {
		if (this.hasFocus()) {
			var v = this.allowHtml ? inValue : enyo.Control.escapeHtml(inValue).replace(/\n/g, "
");
			document.execCommand("insertHTML", false, v);
		}
	}
});
<|endoftext|>


<commit_before>'use strict';

// test memcache:

var
    _ = require('lodash'),
    should = require('should'),
    thunkify = require('thunkify'),
    cache = require('../cache');

var keyPrefix = require('node-uuid').v4().replace(/\-/g, '');

function toKeys(keys) {
    if (typeof(keys)==='string') {
        return keyPrefix + keys;
    }
    return _.map(keys, function (key) {
        return keyPrefix + key;
    });
}

var $sleep = thunkify(function (ms, callback) {
    setTimeout(function () {
        callback(null, 'done');
    }, ms);
});

describe('#cache', function () {

    it('get cache but missing', function* () {
        var keys = toKeys(['aa', 'bb', 'cc']);
        var aa = yield cache.$get(keys[0]);
        var bb = yield cache.$get(keys[1]);
        var cc = yield cache.$get(keys[2]);
        should(aa===null).be.true;
        should(bb===null).be.true;
        should(cc===null).be.true;
    });

    it('get missing value with default value', function* () {
        var key1 = keyPrefix + 'k1';
        var data = yield cache.$get(key1, 'DEFAULT-1');
        should(data==='DEFAULT-1').be.true;
        // should not in cache:
        var data2 = yield cache.$get(key1);
        should(data2===null).be.true;
    });

    it('get by function value', function* () {
        var key1 = keyPrefix + 'f1';
        var data = yield cache.$get(key1, function () {
            return ['F1', null, { 't': 999 }];
        });
        data.should.be.instanceof(Array).and.have.lengthOf(3);
        data[0].should.equal('F1');
        should(data[1]===null).be.true;
        data[2].should.be.instanceof(Object);
        data[2].t.should.equal(999);
        // should in cache:
        var data2 = yield cache.$get(key1);
        data2.should.be.instanceof(Array).and.have.lengthOf(3);
        data2[0].should.equal('F1');
        should(data2[1]===null).be.true;
        data2[2].should.be.instanceof(Object);
        data2[2].t.should.equal(999);
    });

    it('get by generator function', function* () {
        var key1 = keyPrefix + 'g1';
        var data = yield cache.$get(key1, function* () {
            yield $sleep(500);
            return 'G1';
        });
        should(data!==null).be.true;
        data.should.be.equal('G1');
        // should in cache:
        var data2 = yield cache.$get(key1);
        should(data2!==null).be.true;
        data2.should.be.equal('G1');
    });

    it('set and get', function* () {
        var key1 = keyPrefix + 'set1';
        yield cache.$set(key1, 'Value1');
        // get:
        var data = yield cache.$get(key1, '@@@');
        should(data==='Value1').be.true;
    });

    it('gets multikeys', function* () {
        var key1 = keyPrefix + 'multi-1';
        var key2 = keyPrefix + 'multi-2';
        var key3 = keyPrefix + 'multi-3';
        var keys = [key1, key2, key3];
        var data = yield cache.$gets(keys);
        data.should.be.instanceof(Array).and.have.lengthOf(3);
        should(data[0]===null).be.true;
        should(data[1]===null).be.true;
        should(data[2]===null).be.true;
        yield cache.$set(key1, 'Multi');
        // gets again:
        var data2 = yield cache.$gets(keys);
        data2.should.be.instanceof(Array).and.have.lengthOf(3);
        should(data2[0]==='Multi').be.true;
        should(data2[1]===null).be.true;
        should(data2[2]===null).be.true;
    });

    it('set and get then expires', function* () {
        var key1 = keyPrefix + 'exp1';
        yield cache.$set(key1, { expires: 1234 }, 2);
        // get:
        var data = yield cache.$get(key1);
        data.should.be.ok;
        data.expires.should.equal(1234);
        // wait 3 seconds:
        yield $sleep(3000);
        // get again:
        var data2 = yield cache.$get(key1);
        should(data2===null).be.true;
    });

    it('incr counter', function* () {
        var key1 = keyPrefix + 'inc1';
        var num = yield cache.$incr(key1);
        should(num===1).be.true;
        // should get as 1:
        var data = yield cache.$get(key1);
        should(data===1).be.true;
    });

    it('set initial and incr', function* () {
        var key1 = keyPrefix + 'countFrom100';
        var num = yield cache.$incr(key1, 100);
        should(num===101).be.true;
    });

    it('count as 0', function* () {
        var key1 = keyPrefix + 'notFoundButCount0';
        var num = yield cache.$count(key1);
        should(num===0).be.true;
    });

    it('incr and counts', function* () {
        var key1 = keyPrefix + 'countMe';
        var num = yield cache.$incr(key1, 20);
        should(num===21).be.true;
        // should get as 21:
        var data = yield cache.$count(key1);
        should(data===21).be.true;
        // counts:
        var nums = yield cache.$counts([key1, 'count2', 'count3']);
        should(nums!==null).be.true;
        nums.should.be.instanceof(Array).and.have.lengthOf(3);
        should(nums[0]===21).be.true;
        should(nums[1]===0).be.true;
        should(nums[2]===0).be.true;
    });

    it('remove key', function* () {
        var key1 = keyPrefix + 'rm1';
        yield cache.$set(key1, 'To be removed');
        // get:
        var data = yield cache.$get(key1);
        data.should.equal('To be removed');
        // remove:
        yield cache.$remove(key1);
        // get again:
        var data2 = yield cache.$get(key1);
        should(data2===null).be.true;
    });

});
<commit_msg>refactor<commit_after>'use strict';

// test memcache:

var
    _ = require('lodash'),
    should = require('should'),
    remote = require('./_remote'),
    cache = require('../cache');

var keyPrefix = require('node-uuid').v4().replace(/\-/g, '');

function toKeys(keys) {
    if (typeof(keys)==='string') {
        return keyPrefix + keys;
    }
    return _.map(keys, function (key) {
        return keyPrefix + key;
    });
}

describe('#cache', function () {

    it('get cache but missing', function* () {
        var keys = toKeys(['aa', 'bb', 'cc']);
        var aa = yield cache.$get(keys[0]);
        var bb = yield cache.$get(keys[1]);
        var cc = yield cache.$get(keys[2]);
        should(aa===null).be.true;
        should(bb===null).be.true;
        should(cc===null).be.true;
    });

    it('get missing value with default value', function* () {
        var key1 = keyPrefix + 'k1';
        var data = yield cache.$get(key1, 'DEFAULT-1');
        should(data==='DEFAULT-1').be.true;
        // should not in cache:
        var data2 = yield cache.$get(key1);
        should(data2===null).be.true;
    });

    it('get by function value', function* () {
        var key1 = keyPrefix + 'f1';
        var data = yield cache.$get(key1, function () {
            return ['F1', null, { 't': 999 }];
        });
        data.should.be.instanceof(Array).and.have.lengthOf(3);
        data[0].should.equal('F1');
        should(data[1]===null).be.true;
        data[2].should.be.instanceof(Object);
        data[2].t.should.equal(999);
        // should in cache:
        var data2 = yield cache.$get(key1);
        data2.should.be.instanceof(Array).and.have.lengthOf(3);
        data2[0].should.equal('F1');
        should(data2[1]===null).be.true;
        data2[2].should.be.instanceof(Object);
        data2[2].t.should.equal(999);
    });

    it('get by generator function', function* () {
        var key1 = keyPrefix + 'g1';
        var data = yield cache.$get(key1, function* () {
            yield remote.$sleep(500);
            return 'G1';
        });
        should(data!==null).be.true;
        data.should.be.equal('G1');
        // should in cache:
        var data2 = yield cache.$get(key1);
        should(data2!==null).be.true;
        data2.should.be.equal('G1');
    });

    it('set and get', function* () {
        var key1 = keyPrefix + 'set1';
        yield cache.$set(key1, 'Value1');
        // get:
        var data = yield cache.$get(key1, '@@@');
        should(data==='Value1').be.true;
    });

    it('gets multikeys', function* () {
        var key1 = keyPrefix + 'multi-1';
        var key2 = keyPrefix + 'multi-2';
        var key3 = keyPrefix + 'multi-3';
        var keys = [key1, key2, key3];
        var data = yield cache.$gets(keys);
        data.should.be.instanceof(Array).and.have.lengthOf(3);
        should(data[0]===null).be.true;
        should(data[1]===null).be.true;
        should(data[2]===null).be.true;
        yield cache.$set(key1, 'Multi');
        // gets again:
        var data2 = yield cache.$gets(keys);
        data2.should.be.instanceof(Array).and.have.lengthOf(3);
        should(data2[0]==='Multi').be.true;
        should(data2[1]===null).be.true;
        should(data2[2]===null).be.true;
    });

    it('set and get then expires', function* () {
        var key1 = keyPrefix + 'exp1';
        yield cache.$set(key1, { expires: 1234 }, 2);
        // get:
        var data = yield cache.$get(key1);
        data.should.be.ok;
        data.expires.should.equal(1234);
        // wait 3 seconds:
        yield remote.$sleep(3000);
        // get again:
        var data2 = yield cache.$get(key1);
        should(data2===null).be.true;
    });

    it('incr counter', function* () {
        var key1 = keyPrefix + 'inc1';
        var num = yield cache.$incr(key1);
        should(num===1).be.true;
        // should get as 1:
        var data = yield cache.$get(key1);
        should(data===1).be.true;
    });

    it('set initial and incr', function* () {
        var key1 = keyPrefix + 'countFrom100';
        var num = yield cache.$incr(key1, 100);
        should(num===101).be.true;
    });

    it('count as 0', function* () {
        var key1 = keyPrefix + 'notFoundButCount0';
        var num = yield cache.$count(key1);
        should(num===0).be.true;
    });

    it('incr and counts', function* () {
        var key1 = keyPrefix + 'countMe';
        var num = yield cache.$incr(key1, 20);
        should(num===21).be.true;
        // should get as 21:
        var data = yield cache.$count(key1);
        should(data===21).be.true;
        // counts:
        var nums = yield cache.$counts([key1, 'count2', 'count3']);
        should(nums!==null).be.true;
        nums.should.be.instanceof(Array).and.have.lengthOf(3);
        should(nums[0]===21).be.true;
        should(nums[1]===0).be.true;
        should(nums[2]===0).be.true;
    });

    it('remove key', function* () {
        var key1 = keyPrefix + 'rm1';
        yield cache.$set(key1, 'To be removed');
        // get:
        var data = yield cache.$get(key1);
        data.should.equal('To be removed');
        // remove:
        yield cache.$remove(key1);
        // get again:
        var data2 = yield cache.$get(key1);
        should(data2===null).be.true;
    });

});
<|endoftext|>


<commit_before>var task_start = performance.now();

// for ticks per second
var tick = 0;
var lastTime = 0.0;

var alpha = 0.0;
var beta = 0.0;
var gamma = 0.0;

var correctionX = 0.0;
var correctionY = 0.0;
var correctionZ = 0.0;

var measurementActive = false;
var data = [];

var state = -1;

const INIT = 0;
const CALIBRATE = 1;
const READY = 2;
const MEASURE = 3;
const CALCULATE = 4;

var stop = false;

function main() {

	/*
	var vec = [];
	vec["x"] = -4.549;
	vec["y"] = -0.067;
	vec["z"] = -9.0884;
	vec = transformDeviceToWorld(vec, 328.57 , 360.3779,26.589);
	console.log("x: " + vec["x"] + "\n");
	console.log("y: " + vec["y"] + "\n");
	console.log("z: " + vec["z"] + "\n");
	*/
	init();
}

function init() {
	state = INIT;
	document.getElementById("actionBtn").value = "Please hold still and press button for calibration.";
}

function calibrate() {
	state = CALIBRATE;
	document.getElementById("actionBtn").value = "Now Calibrating";
	document.querySelector("#dist_acc").style.backgroundColor = 'orange';
	startMeasurement();
}

function ready() {
	state = READY;
	alert("X: " + correctionX + " Y: " + correctionY + " Z: " + correctionZ);
}

// ------------------------------------------------------------

function performAction() {
	/*
	stop = !stop;
	return;
	*/
	switch (state) {
		case INIT: calibrate();
			break;
		case CALIBRATE:
			break;
		case READY:
			break;
		case MEASURE:
			break;
		case CALCULATE:
			break;
		default: alert("Invalid state: " + state);
			break;
	}

	/*
	if (!measurementActive) {
		document.querySelector("#dist_acc").innerHTML = "Measuring";
		document.querySelector("#dist_acc").style.backgroundColor = 'green';
		data.length = 0;
		task_start = performance.now();
		measurementActive = true;
	} else {
		measurementActive = false;
		if (data.length > 0) {
			cX = data[0]["ax"];
			cY = data[0]["ay"];;
			cZ = data[0]["az"];;
		}

		document.querySelector("#dist_acc").innerHTML = "Number of Measurements: " + data.length;
		document.querySelector("#dist_acc").style.backgroundColor = 'orange';

		var zDistance = calculateDistance(data);
		document.querySelector("#zdist_acc").innerHTML = "Traveled Z-distance: " + zDistance;
	}
	*/
}

// ------------------------------------------------------------

// measurement routine
window.ondevicemotion = function(event) {
	//if(!stop) {

	var ax = -event.accelerationIncludingGravity.x;
	var ay = -event.accelerationIncludingGravity.y;
	var az = -event.accelerationIncludingGravity.z;

	if(measurementActive) { // record data
		var newItem = [];
		newItem["ax"] = ax;
		newItem["ay"] = ay;
		newItem["az"] = az;
		newItem["alpha"] = alpha;
		newItem["beta"] = beta;
		newItem["gamma"] = gamma;
		newItem["time"] = (performance.now() - task_start) / 1000.0;

		data.push(newItem);
	}

	if (state == CALIBRATE && data.length >= 100) {
		stopMeasurement();
		performCalibration();
	}

	var currentTime = performance.now() - task_start;
	var outTime = (Math.round(currentTime) / 1000.0);
	var outAx = (Math.round(ax * 10000) / 10000.0);
	var outAy = (Math.round(ay * 10000) / 10000.0);
	var outAz = (Math.round(az * 10000) / 10000.0);
	var outAlpha = (Math.round(alpha * 10000) / 10000.0);
	var outBeta = (Math.round(beta * 10000) / 10000.0);
	var outGamma = (Math.round(gamma * 10000) / 10000.0);

	document.querySelector("#x_acc").innerHTML = "X = " + outAx;
	document.querySelector("#y_acc").innerHTML = "Y = " + outAy;
	document.querySelector("#z_acc").innerHTML = "Z = " + outAz;
	document.querySelector("#time_acc").innerHTML = "Time = " + outTime;

	document.querySelector("#mag_alpha").innerHTML = "alpha = " + outAlpha;
	document.querySelector("#mag_beta").innerHTML = "beta = " + outBeta;
	document.querySelector("#mag_gamma").innerHTML = "gamma = " + outGamma;

	// measurements per second
	tick = tick + 1;
	if (currentTime - lastTime >= 1000.0) {
		lastTime = Math.floor(currentTime);
		document.querySelector("#tick_acc").innerHTML = "Ticks per Second = " + tick;
		tick = 0;
	}
	//}
}

window.addEventListener("deviceorientation", function(event) {
	// angles in system
	alpha = event.alpha;
	beta = (event.beta + 360.0);
	gamma = -event.gamma;
}, true);

// ------------------------------------------------------------

// trial for z distance
function calculateDistance() {
    var speed = [];

	speed.push(0.0);

	for (var i = 1; i < data.length; i++) { // simple trapez rule
		var interval = (data[i]["time"] - data[i - 1]["time"]);
		var avgAcceleration = (data[i]["az"] + data[i - 1]["az"]) / 2.0 - correctionZ;
		var newSpeed = speed[i - 1] + avgAcceleration * interval;
		speed.push(newSpeed);
	}

	var dist = 0.0;
	for (var i = 1; i < speed.length; i++) {
		var interval = (data[i]["time"] - data[i - 1]["time"]);
		var avgSpeed = (speed[i - 1] + speed[i]) / 2.0;
		dist = dist + avgSpeed * interval;
	}

	return dist;
}

// performs calibration based on measured data
function performCalibration() {
	var sum_x = 0.0;
	var sum_y = 0.0;
	var sum_z = 0.0;
	var sum_alpha = 0.0;
	var sum_beta = 0.0;
	var sum_gamma = 0.0;

	for (var i = 0; i < data.length; i++) {
		sum_x += data[i]["ax"];
		sum_y += data[i]["ay"];
		sum_z += data[i]["az"];
		sum_alpha += data[i]["alpha"];
		sum_beta += data[i]["beta"];
		sum_gamma += data[i]["gamma"];
	}

	var vec = [];
	vec["x"] = sum_x / data.length;
	vec["y"] = sum_y / data.length;
	vec["z"] = sum_z / data.length;

	var angleAlpha = (sum_alpha / data.length);
	var angleBeta = (sum_beta / data.length);
	var angleGamma = (sum_gamma / data.length);

	vec = transformDeviceToWorld(vec, angleAlpha, angleBeta, angleGamma);

	correctionX = vec["x"];
	correctionY = vec["y"];
	correctionZ = vec["z"];

	ready();
}

// ------------------------------------------------------------

// starts measurement and clears data
function startMeasurement() {
	resetMeasurement();
	task_start = performance.now();
	measurementActive = true;
}

// stops measurement and clears data
function resetMeasurement() {
	stopMeasurement();
	data.length = 0;
}

// stop measurements
function stopMeasurement() {
	measurementActive = false;
}

<commit_msg>Perhaps now it works<commit_after>var task_start = performance.now();

// for ticks per second
var tick = 0;
var lastTime = 0.0;

var alpha = 0.0;
var beta = 0.0;
var gamma = 0.0;

var correctionX = 0.0;
var correctionY = 0.0;
var correctionZ = 0.0;

var measurementActive = false;
var data = [];

var state = -1;

const INIT = 0;
const CALIBRATE = 1;
const READY = 2;
const MEASURE = 3;
const CALCULATE = 4;

var stop = false;

function main() {

	/*
	var vec = [];
	vec["x"] = -4.549;
	vec["y"] = -0.067;
	vec["z"] = -9.0884;
	vec = transformDeviceToWorld(vec, 328.57 , 360.3779,26.589);
	console.log("x: " + vec["x"] + "\n");
	console.log("y: " + vec["y"] + "\n");
	console.log("z: " + vec["z"] + "\n");
	*/
	init();
}

function init() {
	state = INIT;
	document.getElementById("actionBtn").value = "Please hold still and press button for calibration.";
}

function calibrate() {
	state = CALIBRATE;
	document.getElementById("actionBtn").value = "Now Calibrating";
	document.querySelector("#dist_acc").style.backgroundColor = 'orange';
	startNewMeasurement();
}

function ready() {
	state = READY;
	document.getElementById("actionBtn").value = "Press to start measurement.";
}

function measure() {
	state = MEASURE;
	startNewMeasurement();
	document.getElementById("actionBtn").value = "Now measuring. Press again to stop measurement.";
}

function calculate() {
	state = CALCULATE;
	startNewMeasurement();
	var zDistance = calculateDistance();
	document.querySelector("#zdist_acc").innerHTML = "Traveled Z-distance: " + zDistance;

	ready();
}

// ------------------------------------------------------------

function performAction() {
	/*
	stop = !stop;
	return;
	*/

	switch (state) {
		case INIT: calibrate();
			break;
		case CALIBRATE: document.getElementById("actionBtn").value = "Please wait";
			break;
		case READY:
			break;
		case MEASURE: calculate();
			break;
		case CALCULATE: document.getElementById("actionBtn").value = "Please wait";
			break;
		default: alert("Invalid state: " + state);
			break;
	}
}

// ------------------------------------------------------------

// measurement routine
window.ondevicemotion = function(event) {
	//if(!stop) {

	var ax = -event.accelerationIncludingGravity.x;
	var ay = -event.accelerationIncludingGravity.y;
	var az = -event.accelerationIncludingGravity.z;

	if(measurementActive) { // record data
		var newItem = [];
		newItem["ax"] = ax;
		newItem["ay"] = ay;
		newItem["az"] = az;
		newItem["alpha"] = alpha;
		newItem["beta"] = beta;
		newItem["gamma"] = gamma;
		newItem["time"] = (performance.now() - task_start) / 1000.0;

		data.push(newItem);
	}

	if (state == CALIBRATE && data.length >= 100) {
		stopMeasurement();
		performCalibration();
	}

	var currentTime = performance.now() - task_start;
	var outTime = (Math.round(currentTime) / 1000.0);
	var outAx = (Math.round(ax * 10000) / 10000.0);
	var outAy = (Math.round(ay * 10000) / 10000.0);
	var outAz = (Math.round(az * 10000) / 10000.0);
	var outAlpha = (Math.round(alpha * 10000) / 10000.0);
	var outBeta = (Math.round(beta * 10000) / 10000.0);
	var outGamma = (Math.round(gamma * 10000) / 10000.0);

	document.querySelector("#x_acc").innerHTML = "X = " + outAx;
	document.querySelector("#y_acc").innerHTML = "Y = " + outAy;
	document.querySelector("#z_acc").innerHTML = "Z = " + outAz;
	document.querySelector("#time_acc").innerHTML = "Time = " + outTime;

	document.querySelector("#mag_alpha").innerHTML = "alpha = " + outAlpha;
	document.querySelector("#mag_beta").innerHTML = "beta = " + outBeta;
	document.querySelector("#mag_gamma").innerHTML = "gamma = " + outGamma;

	// measurements per second
	tick = tick + 1;
	if (currentTime - lastTime >= 1000.0) {
		lastTime = Math.floor(currentTime);
		document.querySelector("#tick_acc").innerHTML = "Ticks per Second = " + tick;
		tick = 0;
	}

	//}
}

window.addEventListener("deviceorientation", function(event) {
	// angles in system
	alpha = event.alpha;
	beta = (event.beta + 360.0);
	gamma = -event.gamma;
}, true);

// ------------------------------------------------------------

// trial for z distance
function calculateDistance() {
    var speed = [];

	speed.push(0.0);

	for (var i = 1; i < data.length; i++) { // simple trapez rule
		var interval = (data[i]["time"] - data[i - 1]["time"]);
		var avgAcceleration = (data[i]["az"] + data[i - 1]["az"]) / 2.0 - correctionZ;
		var newSpeed = speed[i - 1] + avgAcceleration * interval;
		speed.push(newSpeed);
	}

	var dist = 0.0;
	for (var i = 1; i < speed.length; i++) {
		var interval = (data[i]["time"] - data[i - 1]["time"]);
		var avgSpeed = (speed[i - 1] + speed[i]) / 2.0;
		dist = dist + avgSpeed * interval;
	}

	return dist;
}

// performs calibration based on measured data
function performCalibration() {
	var sum_x = 0.0;
	var sum_y = 0.0;
	var sum_z = 0.0;
	var sum_alpha = 0.0;
	var sum_beta = 0.0;
	var sum_gamma = 0.0;

	for (var i = 0; i < data.length; i++) {
		sum_x += data[i]["ax"];
		sum_y += data[i]["ay"];
		sum_z += data[i]["az"];
		sum_alpha += data[i]["alpha"];
		sum_beta += data[i]["beta"];
		sum_gamma += data[i]["gamma"];
	}

	var vec = [];
	vec["x"] = sum_x / data.length;
	vec["y"] = sum_y / data.length;
	vec["z"] = sum_z / data.length;

	var angleAlpha = (sum_alpha / data.length);
	var angleBeta = (sum_beta / data.length);
	var angleGamma = (sum_gamma / data.length);

	vec = transformDeviceToWorld(vec, angleAlpha, angleBeta, angleGamma);

	correctionX = vec["x"];
	correctionY = vec["y"];
	correctionZ = vec["z"];

	ready();
}

// ------------------------------------------------------------

// starts measurement and clears data
function startNewMeasurement() {
	resetMeasurement();
	task_start = performance.now();
	measurementActive = true;
}

// stops measurement and clears data
function resetMeasurement() {
	stopMeasurement();
	data.length = 0;
}

// stop measurements
function stopMeasurement() {
	measurementActive = false;
}

<|endoftext|>


<commit_before>/* @flow */
'use strict';

import React from 'react';
import {View} from 'react-native';
import NativeBaseComponent from '../Base/NativeBaseComponent';
import computeProps from '../../Utils/computeProps';
import Text from './Text';

export default class BadgeNB extends NativeBaseComponent {

    propTypes: {
        style : React.PropTypes.object
    }

    prepareRootProps() {

        var type = {

            backgroundColor:this.props.primary ?
                            this.getTheme().brandPrimary :
                            this.props.success ?
                            this.getTheme().brandSuccess :
                            this.props.info ?
                            this.getTheme().brandInfo :
                            this.props.warning ?
                            this.getTheme().brandWarning :
                            this.props.danger ?
                            this.getTheme().brandDanger :
                            this.getTheme().badgeBg,
            padding: 3,
            paddingHorizontal: 10,
            alignSelf: 'flex-start',
            borderRadius: 13.5,
            height: 27

        }

        var defaultProps = {
            style: type
        }

        return computeProps(this.props, defaultProps);

    }
    render() {
        return(
            <View ref={c => this._root = c} {...this.prepareRootProps()}>
                <Text style={{ color: (this.props.textStyle && this.props.textStyle.color) ? this.props.textStyle.color : this.getTheme().badgeColor,
                                fontSize: (this.props.textStyle && this.props.textStyle.fontSize) ? this.props.textStyle.fontSize : this.getTheme().fontSizeBase,
                                lineHeight: (this.props.textStyle && this.props.textStyle.lineHeight) ? this.props.textStyle.lineHeight : this.getTheme().lineHeight-1,
                                textAlign: 'center'}}>{this.props.children}
                </Text>
            </View>
        );
    }

}
<commit_msg>Apply complete styling to the Badge text<commit_after>/* @flow */
'use strict';

import React from 'react';
import {View} from 'react-native';
import NativeBaseComponent from '../Base/NativeBaseComponent';
import computeProps from '../../Utils/computeProps';
import Text from './Text';

export default class BadgeNB extends NativeBaseComponent {

    propTypes: {
        style : React.PropTypes.object
    }

    prepareRootProps() {

        var type = {

            backgroundColor:this.props.primary ?
                            this.getTheme().brandPrimary :
                            this.props.success ?
                            this.getTheme().brandSuccess :
                            this.props.info ?
                            this.getTheme().brandInfo :
                            this.props.warning ?
                            this.getTheme().brandWarning :
                            this.props.danger ?
                            this.getTheme().brandDanger :
                            this.getTheme().badgeBg,
            padding: 3,
            paddingHorizontal: 10,
            alignSelf: 'flex-start',
            borderRadius: 13.5,
            height: 27

        }

        var defaultProps = {
            style: type
        }

        return computeProps(this.props, defaultProps);

    }
    render() {
        return(
<<<<<<< HEAD
            <View ref={c => this._root = c} {...this.prepareRootProps()}>
                <Text style={{ color: (this.props.textStyle && this.props.textStyle.color) ? this.props.textStyle.color : this.getTheme().badgeColor,
=======
            <View {...this.prepareRootProps()}>
                <Text style={[ this.props.textStyle, {
                                color: (this.props.textStyle && this.props.textStyle.color) ? this.props.textStyle.color : this.getTheme().badgeColor,
>>>>>>> 8629392d4ad27c911011618f496c993296db2f67
                                fontSize: (this.props.textStyle && this.props.textStyle.fontSize) ? this.props.textStyle.fontSize : this.getTheme().fontSizeBase,
                                lineHeight: (this.props.textStyle && this.props.textStyle.lineHeight) ? this.props.textStyle.lineHeight : this.getTheme().lineHeight-1,
                                textAlign: 'center'
                            } ]}>{this.props.children}
                </Text>
            </View>
        );
    }

}
<|endoftext|>