<commit_before>import Promise from 'bluebird';
import ms from 'ms';
import { Types as MongoTypes } from 'mongoose';
import RedLock from 'redlock';
import createDebug from 'debug';

class PlaylistIsEmptyError extends Error {
  code = 'PLAYLIST_IS_EMPTY';
}

const debug = createDebug('uwave:advance');

const { ObjectId } = MongoTypes;

function cyclePlaylist(playlist) {
  const item = playlist.media.shift();
  playlist.media.push(item);
  return playlist.save();
}

export class Booth {
  timeout: ?number = null;

  constructor(uw) {
    this.uw = uw;

    uw.on('started', this.onStart.bind(this));
    uw.on('stop', this.onStop.bind(this));
  }

  async onStart() {
    this.locker = new RedLock([this.uw.redis]);

    const current = await this.getCurrentEntry();
    if (current && this.timeout === null) {
      // Restart the advance timer after a server restart, if a track was
      // playing before the server restarted.
      const duration = (current.media.end - current.media.start) * ms('1 second');
      const endTime = Number(current.playedAt) + duration;
      if (endTime > Date.now()) {
        this.timeout = setTimeout(
          () => this.uw.advance(),
          endTime - Date.now(),
        );
      } else {
        this.uw.advance();
      }
    }
  }

  onStop() {
    this.maybeStop();
  }

  async getCurrentEntry() {
    const History = this.uw.model('History');
    const historyID = await this.uw.redis.get('booth:historyID');
    if (!historyID) {
      return null;
    }

    return History.findOne(new ObjectId(historyID));
  }

  async saveStats(entry) {
    const stats = await Promise.props({
      upvotes: this.uw.redis.lrange('booth:upvotes', 0, -1),
      downvotes: this.uw.redis.lrange('booth:downvotes', 0, -1),
      favorites: this.uw.redis.lrange('booth:favorites', 0, -1),
    });

    Object.assign(entry, stats);
    return entry.save();
  }

  async getNextDJ(opts) {
    const User = this.uw.model('User');
    let userID = await this.uw.redis.lindex('waitlist', 0);
    if (!userID && !opts.remove) {
      // If the waitlist is empty, the current DJ will play again immediately.
      userID = await this.uw.redis.get('booth:currentDJ');
    }
    if (!userID) {
      return null;
    }

    return User.findOne(new ObjectId(userID));
  }

  async getNextEntry(opts) {
    const HistoryEntry = this.uw.model('History');

    const user = await this.getNextDJ(opts);
    if (!user) {
      return null;
    }
    const playlist = await user.getActivePlaylist();
    if (playlist.size === 0) {
      throw new PlaylistIsEmptyError();
    }
    const playlistItem = await playlist.getItemAt(0);

    await playlistItem.populate('media').execPopulate();

    return new HistoryEntry({
      user,
      playlist,
      item: playlistItem,
      media: {
        media: playlistItem.media,
        artist: playlistItem.artist,
        title: playlistItem.title,
        start: playlistItem.start,
        end: playlistItem.end,
      },
    });
  }

  async cycleWaitlist(previous, opts) {
    const waitlistLen = await this.uw.redis.llen('waitlist');
    if (waitlistLen > 0) {
      await this.uw.redis.lpop('waitlist');
      if (previous && !opts.remove) {
        // The previous DJ should only be added to the waitlist again if it was
        // not empty. If it was empty, the previous DJ is already in the booth.
        await this.uw.redis.rpush('waitlist', previous.user);
      }
    }
  }

  clear() {
    return this.uw.redis.del([
      'booth:historyID',
      'booth:currentDJ',
      'booth:upvotes',
      'booth:downvotes',
      'booth:favorites',
    ]);
  }

  update(next) {
    return this.uw.redis.multi()
      .del(['booth:upvotes', 'booth:downvotes', 'booth:favorites'])
      .set('booth:historyID', next.id)
      .set('booth:currentDJ', next.user.id)
      .exec();
  }

  maybeStop() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
  }

  play(entry) {
    this.maybeStop();
    this.timeout = setTimeout(
      () => this.uw.advance(),
      (entry.media.end - entry.media.start) * ms('1 second'),
    );
    return entry;
  }

  getWaitlist() {
    return this.uw.redis.lrange('waitlist', 0, -1);
  }

  async publish(next) {
    if (next) {
      this.uw.publish('advance:complete', next);
      this.uw.publish('playlist:cycle', {
        userID: next.user.id,
        playlistID: next.playlist.id,
      });
    } else {
      this.uw.publish('advance:complete', null);
    }
    this.uw.publish('waitlist:update', await this.getWaitlist());
  }

  async advance(opts = {}, reuseLock = null) {
    let lock;
    try {
      if (reuseLock) {
        lock = await reuseLock.extend(ms('2 seconds'));
      } else {
        lock = await this.locker.lock('booth:advancing', ms('2 seconds'));
      }
    } catch (err) {
      throw new Error('Another advance is still in progress.');
    }

    const previous = await this.getCurrentEntry();
    let next;
    try {
      next = await this.getNextEntry(opts);
    } catch (err) {
      // If the next user's playlist was empty, remove them from the waitlist
      // and try advancing again.
      if (err.code === 'PLAYLIST_IS_EMPTY') {
        debug('user has empty playlist, skipping on to the next');
        await this.cycleWaitlist(previous, opts);
        return this.advance({ ...opts, remove: true }, lock);
      }
      throw err;
    }

    if (previous) {
      await this.saveStats(previous);

      debug(
        'previous track:', previous.media.artist, '—', previous.media.title,
        `👍 ${previous.upvotes.length} ` +
        `★ ${previous.favorites.length} ` +
        `👎 ${previous.downvotes.length}`,
      );
    }

    if (next) {
      await next.save();
    } else {
      this.maybeStop();
    }

    await this.cycleWaitlist(previous, opts);

    if (next) {
      await this.update(next);
      await cyclePlaylist(next.playlist);
      await this.play(next);
    } else {
      await this.clear();
    }

    if (opts.publish !== false) {
      await this.publish(next);
    }

    lock.unlock().catch(() => {
      // Don't really care if this fails, it'll expire in some seconds anyway.
    });

    return next;
  }
}

export default function booth() {
  return (uw) => {
    uw.booth = new Booth(uw);
  };
}
<commit_msg>Remove `new ObjectId`, use `findById` instead<commit_after>import Promise from 'bluebird';
import ms from 'ms';
import RedLock from 'redlock';
import createDebug from 'debug';

class PlaylistIsEmptyError extends Error {
  code = 'PLAYLIST_IS_EMPTY';
}

const debug = createDebug('uwave:advance');

function cyclePlaylist(playlist) {
  const item = playlist.media.shift();
  playlist.media.push(item);
  return playlist.save();
}

export class Booth {
  timeout: ?number = null;

  constructor(uw) {
    this.uw = uw;

    uw.on('started', this.onStart.bind(this));
    uw.on('stop', this.onStop.bind(this));
  }

  async onStart() {
    this.locker = new RedLock([this.uw.redis]);

    const current = await this.getCurrentEntry();
    if (current && this.timeout === null) {
      // Restart the advance timer after a server restart, if a track was
      // playing before the server restarted.
      const duration = (current.media.end - current.media.start) * ms('1 second');
      const endTime = Number(current.playedAt) + duration;
      if (endTime > Date.now()) {
        this.timeout = setTimeout(
          () => this.uw.advance(),
          endTime - Date.now(),
        );
      } else {
        this.uw.advance();
      }
    }
  }

  onStop() {
    this.maybeStop();
  }

  async getCurrentEntry() {
    const History = this.uw.model('History');
    const historyID = await this.uw.redis.get('booth:historyID');
    if (!historyID) {
      return null;
    }

    return History.findById(historyID);
  }

  async saveStats(entry) {
    const stats = await Promise.props({
      upvotes: this.uw.redis.lrange('booth:upvotes', 0, -1),
      downvotes: this.uw.redis.lrange('booth:downvotes', 0, -1),
      favorites: this.uw.redis.lrange('booth:favorites', 0, -1),
    });

    Object.assign(entry, stats);
    return entry.save();
  }

  async getNextDJ(opts) {
    const User = this.uw.model('User');
    let userID = await this.uw.redis.lindex('waitlist', 0);
    if (!userID && !opts.remove) {
      // If the waitlist is empty, the current DJ will play again immediately.
      userID = await this.uw.redis.get('booth:currentDJ');
    }
    if (!userID) {
      return null;
    }

    return User.findById(userID);
  }

  async getNextEntry(opts) {
    const HistoryEntry = this.uw.model('History');

    const user = await this.getNextDJ(opts);
    if (!user) {
      return null;
    }
    const playlist = await user.getActivePlaylist();
    if (playlist.size === 0) {
      throw new PlaylistIsEmptyError();
    }
    const playlistItem = await playlist.getItemAt(0);

    await playlistItem.populate('media').execPopulate();

    return new HistoryEntry({
      user,
      playlist,
      item: playlistItem,
      media: {
        media: playlistItem.media,
        artist: playlistItem.artist,
        title: playlistItem.title,
        start: playlistItem.start,
        end: playlistItem.end,
      },
    });
  }

  async cycleWaitlist(previous, opts) {
    const waitlistLen = await this.uw.redis.llen('waitlist');
    if (waitlistLen > 0) {
      await this.uw.redis.lpop('waitlist');
      if (previous && !opts.remove) {
        // The previous DJ should only be added to the waitlist again if it was
        // not empty. If it was empty, the previous DJ is already in the booth.
        await this.uw.redis.rpush('waitlist', previous.user);
      }
    }
  }

  clear() {
    return this.uw.redis.del([
      'booth:historyID',
      'booth:currentDJ',
      'booth:upvotes',
      'booth:downvotes',
      'booth:favorites',
    ]);
  }

  update(next) {
    return this.uw.redis.multi()
      .del(['booth:upvotes', 'booth:downvotes', 'booth:favorites'])
      .set('booth:historyID', next.id)
      .set('booth:currentDJ', next.user.id)
      .exec();
  }

  maybeStop() {
    if (this.timeout) {
      clearTimeout(this.timeout);
      this.timeout = null;
    }
  }

  play(entry) {
    this.maybeStop();
    this.timeout = setTimeout(
      () => this.uw.advance(),
      (entry.media.end - entry.media.start) * ms('1 second'),
    );
    return entry;
  }

  getWaitlist() {
    return this.uw.redis.lrange('waitlist', 0, -1);
  }

  async publish(next) {
    if (next) {
      this.uw.publish('advance:complete', next);
      this.uw.publish('playlist:cycle', {
        userID: next.user.id,
        playlistID: next.playlist.id,
      });
    } else {
      this.uw.publish('advance:complete', null);
    }
    this.uw.publish('waitlist:update', await this.getWaitlist());
  }

  async advance(opts = {}, reuseLock = null) {
    let lock;
    try {
      if (reuseLock) {
        lock = await reuseLock.extend(ms('2 seconds'));
      } else {
        lock = await this.locker.lock('booth:advancing', ms('2 seconds'));
      }
    } catch (err) {
      throw new Error('Another advance is still in progress.');
    }

    const previous = await this.getCurrentEntry();
    let next;
    try {
      next = await this.getNextEntry(opts);
    } catch (err) {
      // If the next user's playlist was empty, remove them from the waitlist
      // and try advancing again.
      if (err.code === 'PLAYLIST_IS_EMPTY') {
        debug('user has empty playlist, skipping on to the next');
        await this.cycleWaitlist(previous, opts);
        return this.advance({ ...opts, remove: true }, lock);
      }
      throw err;
    }

    if (previous) {
      await this.saveStats(previous);

      debug(
        'previous track:', previous.media.artist, '—', previous.media.title,
        `👍 ${previous.upvotes.length} ` +
        `★ ${previous.favorites.length} ` +
        `👎 ${previous.downvotes.length}`,
      );
    }

    if (next) {
      await next.save();
    } else {
      this.maybeStop();
    }

    await this.cycleWaitlist(previous, opts);

    if (next) {
      await this.update(next);
      await cyclePlaylist(next.playlist);
      await this.play(next);
    } else {
      await this.clear();
    }

    if (opts.publish !== false) {
      await this.publish(next);
    }

    lock.unlock().catch(() => {
      // Don't really care if this fails, it'll expire in some seconds anyway.
    });

    return next;
  }
}

export default function booth() {
  return (uw) => {
    uw.booth = new Booth(uw);
  };
}
<|endoftext|>


<commit_before>import React from 'react';

export default class PopupContent extends React.Component {

  render() {
    return (

            <input id="popup-input" type="text" name="name" />
            <button onClick={() => this.props.onClose()}>:-)</button>

    );
  }
}
<commit_msg>fix linter warnings<commit_after>import React from 'react';

export default class PopupContent extends React.Component {

  static propTypes = {
    onClose: React.PropTypes.func.isRequired,
  };

  render() {
    return (

            <input id="popup-input" type="text" name="name" />
            <button onClick={() => this.props.onClose()}>:-)</button>

    );
  }
}
<|endoftext|>


<commit_before>import { h, render } from 'preact-cycle'
/** @jsx h */

const Toggle = (state, input) => {
  todo.done = !todo.done
  return state
}

const SetLayerHeight = (state, input) => {
  todo.done = !todo.done
  return state
}

const actions = {Toggle}

const PrintSettings = ({t}) => {

  const layerHeights = [0.2, 0.15, 0.1, 0.06]
  const layerHeightNames = {
    '0.2': t('activity_print_settings_text_draft'),
    '0.15': t('activity_print_settings_text_fast'),
    '0.1': t('activity_print_settings_text_normal'),
    '0.06': t('activity_print_settings_text_high')
  }
  const layerHeightUnit = 'mm'

  const layerHeightButtons = layerHeights.map(function (height) {
    const buttonText = `${layerHeightNames[height]}\n${height} ${layerHeightUnit}`
    return

      <button>{buttonText}</button>

  })

  return

             {t('activity_print_settings_text_info')}

             {t('activity_print_settings_text_the_app_only_supports')}

               {t('activity_print_settings_layer_height_description')}

              {layerHeightButtons}

}

export default PrintSettings
<commit_msg>feat(printSettings): fleshed out printSettings: now overall ui structure matched the app<commit_after>import { h, render } from 'preact-cycle'
/** @jsx h */

const Toggle = (state, input) => {
  todo.done = !todo.done
  return state
}

const SetLayerHeight = (state, input) => {
  todo.done = !todo.done
  return state
}

const actions = {Toggle}

const PrintSettings = ({t}) => {

  const selectedSupportMaterial = 'pva'

  const state = {
    support: {toggled: true},
    brim: {toggled: true}
  }
  const {support, brim} = state

  const layerHeights = [0.2, 0.15, 0.1, 0.06]
  const layerHeightNames = {
    '0.2': t('activity_print_settings_text_draft'),
    '0.15': t('activity_print_settings_text_fast'),
    '0.1': t('activity_print_settings_text_normal'),
    '0.06': t('activity_print_settings_text_high')
  }
  const layerHeightUnit = 'mm'

  const layerHeightButtons = layerHeights.map(function (height) {
    const buttonText = `${layerHeightNames[height]}
${height} ${layerHeightUnit}`
    return

             <button>
               {buttonText}
             </button>

  })

  const supportMaterials = ['pva', 'pla']

  const supportMaterialsUi = supportMaterials.map(function (supportMaterial) {
    return

             <input
               type="radio"
               name='supportMaterials'
               value={supportMaterial}
               checked={supportMaterial === selectedSupportMaterial}>
             {supportMaterial}
             </input>
             Extruder 1:
             {supportMaterial}

  })

  return

             {t('activity_print_settings_text_info')}

             {t('activity_print_settings_text_the_app_only_supports')}

{t('activity_print_settings_text_print_profile')}

               {t('activity_print_settings_layer_height_description')}

                 {layerHeightButtons}

{t('activity_print_settings_text_support_structure')} <input type='checkbox' checked={support.toggled} />

               {t('activity_print_settings_text_select_support_structure_description')}
               <form>
                 {supportMaterialsUi}
               </form>

{t('activity_print_settings_text_adhesion_brim')} <input type='checkbox' checked={brim.toggled} />

               {t('activity_print_settings_text_brim_description')}

}

export default PrintSettings
<|endoftext|>


<commit_before>import parent from '../render/dom/parent'
import { property } from '../render/static'

const injectable = {}

export default injectable

if (typeof window === 'undefined') {
  Object.defineProperty(global.Element.prototype, 'value', {
    get () { return this.getAttribute('value') },
    set (val) { this.setAttribute('value', val) }
  })
}

injectable.props = {
  attr: {
    type: 'property',
    render: {
      static: property,
      state (t, state, type, subs, tree, id, pid) {
        const pnode = parent(tree, pid)
        if (pnode && !pnode._propsStaticParsed) {
          property(t, pnode)
          pnode._propsStaticParsed = true
        }
      }
    },
    props: {
      type: null, // default to wrong one --- it defaults to element....
      default: {
        props: { name: true },
        render: {
          static (t, pnode) {
            const val = t.compute()
            if (val === t || val === void 0) {
              pnode.removeAttribute(t.name || t.key)
            } else {
              pnode.setAttribute(t.name || t.key, val)
            }
          },
          state (t, state, type, subs, tree, id, pid) {
            const pnode = parent(tree, pid)
            const key = t.name || t.key
            if (type === 'remove') {
              if (pnode) {
                pnode.removeAttribute(key)
              }
            } else {
              let val = t.compute(state, state)
              const type = typeof val
              if (type === 'boolean') { val = val + '' }
              if ((type === 'object' && val.inherits) || val === void 0) {
                if (pnode.getAttribute(key)) {
                  pnode.removeAttribute(key) // missing
                }
              } else {
                if (pnode.getAttribute(key) != val) { // eslint-disable-line
                  pnode.setAttribute(key, val)
                }
              }
            }
          }
        }
      },
      value: {
        render: {
          static (t, pnode) {
            const val = t.compute() // missing
            pnode.value = val // missing (needs a way on server this does not work)
          },
          state (t, state, type, subs, tree, id, pid) {
            const pnode = parent(tree, pid)
            if (type === 'remove') {
              if (pnode) { pnode.value = '' } // missing
            } else {
              const val = t.compute(state, state)
              if (val != pnode.value) { // eslint-disable-line
                pnode.value = val === t ? '' : val
              }
            }
          }
        }
      }
    }
  }
}
<commit_msg>added configurable flag to value<commit_after>import parent from '../render/dom/parent'
import { property } from '../render/static'

const injectable = {}

export default injectable

if (typeof window === 'undefined') {
  Object.defineProperty(global.Element.prototype, 'value', {
    configurable: true,
    get () { return this.getAttribute('value') },
    set (val) { this.setAttribute('value', val) }
  })
}

injectable.props = {
  attr: {
    type: 'property',
    render: {
      static: property,
      state (t, state, type, subs, tree, id, pid) {
        const pnode = parent(tree, pid)
        if (pnode && !pnode._propsStaticParsed) {
          property(t, pnode)
          pnode._propsStaticParsed = true
        }
      }
    },
    props: {
      type: null, // default to wrong one --- it defaults to element....
      default: {
        props: { name: true },
        render: {
          static (t, pnode) {
            const val = t.compute()
            if (val === t || val === void 0) {
              pnode.removeAttribute(t.name || t.key)
            } else {
              pnode.setAttribute(t.name || t.key, val)
            }
          },
          state (t, state, type, subs, tree, id, pid) {
            const pnode = parent(tree, pid)
            const key = t.name || t.key
            if (type === 'remove') {
              if (pnode) {
                pnode.removeAttribute(key)
              }
            } else {
              let val = t.compute(state, state)
              const type = typeof val
              if (type === 'boolean') { val = val + '' }
              if ((type === 'object' && val.inherits) || val === void 0) {
                if (pnode.getAttribute(key)) {
                  pnode.removeAttribute(key) // missing
                }
              } else {
                if (pnode.getAttribute(key) != val) { // eslint-disable-line
                  pnode.setAttribute(key, val)
                }
              }
            }
          }
        }
      },
      value: {
        render: {
          static (t, pnode) {
            const val = t.compute() // missing
            pnode.value = val // missing (needs a way on server this does not work)
          },
          state (t, state, type, subs, tree, id, pid) {
            const pnode = parent(tree, pid)
            if (type === 'remove') {
              if (pnode) { pnode.value = '' } // missing
            } else {
              const val = t.compute(state, state)
              if (val != pnode.value) { // eslint-disable-line
                pnode.value = val === t ? '' : val
              }
            }
          }
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>// A prototypical set object cunningly written to enable easy mixins.
//
var LIB_set = {

    // This set implementation does not add a "LIB_id" property with
    // a unique value to each element and so the "has" method is O(n).
    //
    has: function(element) {
        if (this._elements) {
            for (var i = 0, ilen = this._elements.length; i < ilen; i++) {
                if (element === this._elements[i]) {
                    return true;
                }
            }
        }
        return false;
    },

    add: function(element) {
        if (!this.has(element)) {
            this._elements || (this._elements = []);
            this._elements.push(element);
        }
    },

    // "delete" is a reserved word and older implementations
    // did not allow bare reserved words in property name
    // position so quote "delete".
    //
    "delete": function(element) {
        if (this._elements) {
            for (var i=0, ilen=this._elements.length; i<ilen; i++) {
                if (element === this._elements[i]) {
                    this._elements.splice(i, 1);
                    return;
                }
            }
        }
    }

};

var LIB_Set = function() {
    for (var i=0, ilen=arguments.length; i<ilen; i++) {
        this.add(arguments[i]);
    }
};
LIB_Set.prototype = LIB_set;
LIB_Set.prototype.constructor = LIB_Set;

var LIB_mixinSet = function(obj) {
    for (var p in LIB_set) {
        if (Object.prototype.hasOwnProperty.call(LIB_set, p)) {
            obj[p] = LIB_set[p];
        }
    }
};

// function APP_Alphabet(name) {
//     this.name = name;
// };
// LIB_mixinSet(APP_Alphabet.prototype);
//
// var alphabet = new APP_Alphabet('Greek');
// alphabet.add('alpha');
// alphabet.add('zed');         // oops, that's not Greek
// alphabet['delete']('zed');
// alphabet.add('omega');
// alphabet.has('zed');        // false
// alphabet.has('omega');      // true
<commit_msg>whitespace<commit_after>// A prototypical set object cunningly written to enable easy mixins.
//
var LIB_set = {

    // This set implementation does not add a "LIB_id" property with
    // a unique value to each element and so the "has" method is O(n).
    //
    has: function(element) {
        if (this._elements) {
            for (var i = 0, ilen = this._elements.length; i < ilen; i++) {
                if (element === this._elements[i]) {
                    return true;
                }
            }
        }
        return false;
    },

    add: function(element) {
        if (!this.has(element)) {
            this._elements || (this._elements = []);
            this._elements.push(element);
        }
    },

    // "delete" is a reserved word and older implementations
    // did not allow bare reserved words in property name
    // position so quote "delete".
    //
    "delete": function(element) {
        if (this._elements) {
            for (var i = 0, ilen = this._elements.length; i < ilen; i++) {
                if (element === this._elements[i]) {
                    this._elements.splice(i, 1);
                    return;
                }
            }
        }
    }

};

var LIB_Set = function() {
    for (var i = 0, ilen = arguments.length; i < ilen; i++) {
        this.add(arguments[i]);
    }
};
LIB_Set.prototype = LIB_set;
LIB_Set.prototype.constructor = LIB_Set;

var LIB_mixinSet = function(obj) {
    for (var p in LIB_set) {
        if (Object.prototype.hasOwnProperty.call(LIB_set, p)) {
            obj[p] = LIB_set[p];
        }
    }
};

// function APP_Alphabet(name) {
//     this.name = name;
// };
// LIB_mixinSet(APP_Alphabet.prototype);
//
// var alphabet = new APP_Alphabet('Greek');
// alphabet.add('alpha');
// alphabet.add('zed');         // oops, that's not Greek
// alphabet['delete']('zed');
// alphabet.add('omega');
// alphabet.has('zed');        // false
// alphabet.has('omega');      // true
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Cropper from 'cropperjs';

const optionProps = [
  'dragMode',
  'aspectRatio',
  'data',
  'crop',
  // unchangeable props start from here
  'viewMode',
  'preview',
  'responsive',
  'restore',
  'checkCrossOrigin',
  'checkOrientation',
  'modal',
  'guides',
  'center',
  'highlight',
  'background',
  'autoCrop',
  'autoCropArea',
  'movable',
  'rotatable',
  'scalable',
  'zoomable',
  'zoomOnTouch',
  'zoomOnWheel',
  'wheelZoomRation',
  'cropBoxMovable',
  'cropBoxResizable',
  'toggleDragModeOnDblclick',
  'minContainerWidth',
  'minContainerHeight',
  'minCanvasWidth',
  'minCanvasHeight',
  'minCropBoxWidth',
  'minCropBoxHeight',
  'ready',
  'cropstart',
  'cropmove',
  'cropend',
  'zoom',
];

const unchangeableProps = optionProps.slice(4);

class ReactCropper extends Component {

  componentDidMount() {
    const options = Object.keys(this.props)
    .filter(propKey => optionProps.indexOf(propKey) !== -1)
    .reduce((prevOptions, propKey) =>
      Object.assign({}, prevOptions, { [propKey]: this.props[propKey] })
    , {});
    this.cropper = new Cropper(this.img, options);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.src !== this.props.src) {
      this.cropper.reset().clear().replace(nextProps.src);
    }
    if (nextProps.aspectRatio !== this.props.aspectRatio) {
      this.setAspectRatio(nextProps.aspectRatio);
    }
    if (nextProps.data !== this.props.data) {
      this.setData(nextProps.data);
    }
    if (nextProps.dragMode !== this.props.dragMode) {
      this.setDragMode(nextProps.dragMode);
    }
    if (nextProps.cropBoxData !== this.props.cropBoxData) {
      this.setCropBoxData(nextProps.cropBoxData);
    }
    if (nextProps.canvasData !== this.props.canvasData) {
      this.setCanvasData(nextProps.canvasData);
    }
    if (nextProps.moveTo !== this.props.moveTo) {
      if (nextProps.moveTo.length > 1) {
        this.moveTo(nextProps.moveTo[0], nextProps.moveTo[1]);
      } else {
        this.moveTo(nextProps.moveTo[0]);
      }
    }
    if (nextProps.zoomTo !== this.props.zoomTo) {
      this.zoomTo(nextProps.zoomTo);
    }
    if (nextProps.rotateTo !== this.props.rotateTo) {
      this.rotateTo(nextProps.rotateTo);
    }
    if (nextProps.scaleX !== this.props.scaleX) {
      this.scaleX(nextProps.scaleX);
    }
    if (nextProps.scaleY !== this.props.scaleY) {
      this.scaleY(nextProps.scaleY);
    }
    if (nextProps.enable !== this.props.enable) {
      if (nextProps.enable) {
        this.enable();
      } else {
        this.disable();
      }
    }

    Object.keys(nextProps).forEach((propKey) => {
      if (nextProps[propKey] !== this.props[propKey]
        && unchangeableProps.indexOf(propKey) !== -1) {
        throw new Error(`prop: ${propKey} can't be change after componentDidMount`);
      }
    });
  }

  componentWillUnmount() {
    if (this.img) {
      // Destroy the cropper, this makes sure events such as resize are cleaned up and do not leak
      this.cropper.destroy();
      delete this.img;
      delete this.cropper;
    }
  }

  setDragMode(mode) {
    return this.cropper.setDragMode(mode);
  }

  setAspectRatio(aspectRatio) {
    return this.cropper.setAspectRatio(aspectRatio);
  }

  getCroppedCanvas(options) {
    return this.cropper.getCroppedCanvas(options);
  }

  setCropBoxData(data) {
    return this.cropper.setCropBoxData(data);
  }

  getCropBoxData() {
    return this.cropper.getCropBoxData();
  }

  setCanvasData(data) {
    return this.cropper.setCanvasData(data);
  }

  getCanvasData() {
    return this.cropper.getCanvasData();
  }

  getImageData() {
    return this.cropper.getImageData();
  }

  getContainerData() {
    return this.cropper.getContainerData();
  }

  setData(data) {
    return this.cropper.setData(data);
  }

  getData(rounded) {
    return this.cropper.getData(rounded);
  }

  crop() {
    return this.cropper.crop();
  }

  move(offsetX, offsetY) {
    return this.cropper.move(offsetX, offsetY);
  }

  moveTo(x, y) {
    return this.cropper.moveTo(x, y);
  }

  zoom(ratio) {
    return this.cropper.zoom(ratio);
  }

  zoomTo(ratio) {
    return this.cropper.zoomTo(ratio);
  }

  rotate(degree) {
    return this.cropper.rotate(degree);
  }

  rotateTo(degree) {
    return this.cropper.rotateTo(degree);
  }

  enable() {
    return this.cropper.enable();
  }

  disable() {
    return this.cropper.disable();
  }

  reset() {
    return this.cropper.reset();
  }

  clear() {
    return this.cropper.clear();
  }

  replace(url, onlyColorChanged) {
    return this.cropper.replace(url, onlyColorChanged);
  }

  scale(scaleX, scaleY) {
    return this.cropper.scale(scaleX, scaleY);
  }

  scaleX(scaleX) {
    return this.cropper.scaleX(scaleX);
  }

  scaleY(scaleY) {
    return this.cropper.scaleY(scaleY);
  }

  render() {
    const {
      src,
      alt,
      crossOrigin,
    } = this.props;

    return (
      <div
        src={null}
        crossOrigin={null}
        alt={null}
        style={this.props.style}
        className={this.props.className}
      >
        <img
          crossOrigin={crossOrigin}
          ref={(img) => { this.img = img; }}
          src={src}
          alt={alt === undefined ? 'picture' : alt}
          style={{ opacity: 0 }}
        />
      </div>
    );
  }
}

ReactCropper.propTypes = {
  style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  className: PropTypes.string,

  // react cropper options
  crossOrigin: PropTypes.string,
  src: PropTypes.string,
  alt: PropTypes.string,

  // props of option can be changed after componentDidmount
  aspectRatio: PropTypes.number,
  dragMode: PropTypes.oneOf(['crop', 'move', 'none']),
  data: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
    rotate: PropTypes.number,
    scaleX: PropTypes.number,
    scaleY: PropTypes.number,
  }),
  scaleX: PropTypes.number,
  scaleY: PropTypes.number,
  enable: PropTypes.bool,
  cropBoxData: PropTypes.shape({
    left: PropTypes.number,
    top: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
  }),
  canvasData: PropTypes.shape({
    left: PropTypes.number,
    top: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
  }),
  zoomTo: PropTypes.number,
  moveTo: PropTypes.arrayOf(PropTypes.number),
  rotateTo: PropTypes.number,

  // cropperjs options
  // https://github.com/fengyuanchen/cropperjs#options
  // aspectRatio, dragMode, data
  viewMode: PropTypes.oneOf([0, 1, 2, 3]),
  preview: PropTypes.string,
  responsive: PropTypes.bool,
  restore: PropTypes.bool,
  checkCrossOrigin: PropTypes.bool,
  checkOrientation: PropTypes.bool,
  modal: PropTypes.bool,
  guides: PropTypes.bool,
  center: PropTypes.bool,
  highlight: PropTypes.bool,
  background: PropTypes.bool,
  autoCrop: PropTypes.bool,
  autoCropArea: PropTypes.number,
  movable: PropTypes.bool,
  rotatable: PropTypes.bool,
  scalable: PropTypes.bool,
  zoomable: PropTypes.bool,
  zoomOnTouch: PropTypes.bool,
  zoomOnWheel: PropTypes.bool,
  wheelZoomRation: PropTypes.number,
  cropBoxMovable: PropTypes.bool,
  cropBoxResizable: PropTypes.bool,
  toggleDragModeOnDblclick: PropTypes.bool,
  minContainerWidth: PropTypes.number,
  minContainerHeight: PropTypes.number,
  minCanvasWidth: PropTypes.number,
  minCanvasHeight: PropTypes.number,
  minCropBoxWidth: PropTypes.number,
  minCropBoxHeight: PropTypes.number,
  ready: PropTypes.func,
  cropstart: PropTypes.func,
  cropmove: PropTypes.func,
  cropend: PropTypes.func,
  crop: PropTypes.func,
  zoom: PropTypes.func,
};

ReactCropper.defaultProps = {
  src: null,
  dragMode: 'crop',
  data: null,
  scaleX: 1,
  scaleY: 1,
  enable: true,
  zoomTo: 1,
  rotateTo: 0,
};

export default ReactCropper;
<commit_msg>change checked value toString if unchangeable props is a function<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Cropper from 'cropperjs';

const optionProps = [
  'dragMode',
  'aspectRatio',
  'data',
  'crop',
  // unchangeable props start from here
  'viewMode',
  'preview',
  'responsive',
  'restore',
  'checkCrossOrigin',
  'checkOrientation',
  'modal',
  'guides',
  'center',
  'highlight',
  'background',
  'autoCrop',
  'autoCropArea',
  'movable',
  'rotatable',
  'scalable',
  'zoomable',
  'zoomOnTouch',
  'zoomOnWheel',
  'wheelZoomRation',
  'cropBoxMovable',
  'cropBoxResizable',
  'toggleDragModeOnDblclick',
  'minContainerWidth',
  'minContainerHeight',
  'minCanvasWidth',
  'minCanvasHeight',
  'minCropBoxWidth',
  'minCropBoxHeight',
  'ready',
  'cropstart',
  'cropmove',
  'cropend',
  'zoom',
];

const unchangeableProps = optionProps.slice(4);

class ReactCropper extends Component {

  componentDidMount() {
    const options = Object.keys(this.props)
    .filter(propKey => optionProps.indexOf(propKey) !== -1)
    .reduce((prevOptions, propKey) =>
      Object.assign({}, prevOptions, { [propKey]: this.props[propKey] })
    , {});
    this.cropper = new Cropper(this.img, options);
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.src !== this.props.src) {
      this.cropper.reset().clear().replace(nextProps.src);
    }
    if (nextProps.aspectRatio !== this.props.aspectRatio) {
      this.setAspectRatio(nextProps.aspectRatio);
    }
    if (nextProps.data !== this.props.data) {
      this.setData(nextProps.data);
    }
    if (nextProps.dragMode !== this.props.dragMode) {
      this.setDragMode(nextProps.dragMode);
    }
    if (nextProps.cropBoxData !== this.props.cropBoxData) {
      this.setCropBoxData(nextProps.cropBoxData);
    }
    if (nextProps.canvasData !== this.props.canvasData) {
      this.setCanvasData(nextProps.canvasData);
    }
    if (nextProps.moveTo !== this.props.moveTo) {
      if (nextProps.moveTo.length > 1) {
        this.moveTo(nextProps.moveTo[0], nextProps.moveTo[1]);
      } else {
        this.moveTo(nextProps.moveTo[0]);
      }
    }
    if (nextProps.zoomTo !== this.props.zoomTo) {
      this.zoomTo(nextProps.zoomTo);
    }
    if (nextProps.rotateTo !== this.props.rotateTo) {
      this.rotateTo(nextProps.rotateTo);
    }
    if (nextProps.scaleX !== this.props.scaleX) {
      this.scaleX(nextProps.scaleX);
    }
    if (nextProps.scaleY !== this.props.scaleY) {
      this.scaleY(nextProps.scaleY);
    }
    if (nextProps.enable !== this.props.enable) {
      if (nextProps.enable) {
        this.enable();
      } else {
        this.disable();
      }
    }

    Object.keys(nextProps).forEach((propKey) => {
      let isDifferentVal = nextProps[propKey] !== this.props[propKey];
      const isUnchangeableProps = unchangeableProps.indexOf(propKey) !== -1;

      if (typeof nextProps[propKey] === 'function' && typeof this.props[propKey] === 'function') {
        isDifferentVal = nextProps[propKey].toString() !== this.props[propKey].toString();
      }

      if (isDifferentVal && isUnchangeableProps) {
        throw new Error(`prop: ${propKey} can't be change after componentDidMount`);
      }
    });
  }

  componentWillUnmount() {
    if (this.img) {
      // Destroy the cropper, this makes sure events such as resize are cleaned up and do not leak
      this.cropper.destroy();
      delete this.img;
      delete this.cropper;
    }
  }

  setDragMode(mode) {
    return this.cropper.setDragMode(mode);
  }

  setAspectRatio(aspectRatio) {
    return this.cropper.setAspectRatio(aspectRatio);
  }

  getCroppedCanvas(options) {
    return this.cropper.getCroppedCanvas(options);
  }

  setCropBoxData(data) {
    return this.cropper.setCropBoxData(data);
  }

  getCropBoxData() {
    return this.cropper.getCropBoxData();
  }

  setCanvasData(data) {
    return this.cropper.setCanvasData(data);
  }

  getCanvasData() {
    return this.cropper.getCanvasData();
  }

  getImageData() {
    return this.cropper.getImageData();
  }

  getContainerData() {
    return this.cropper.getContainerData();
  }

  setData(data) {
    return this.cropper.setData(data);
  }

  getData(rounded) {
    return this.cropper.getData(rounded);
  }

  crop() {
    return this.cropper.crop();
  }

  move(offsetX, offsetY) {
    return this.cropper.move(offsetX, offsetY);
  }

  moveTo(x, y) {
    return this.cropper.moveTo(x, y);
  }

  zoom(ratio) {
    return this.cropper.zoom(ratio);
  }

  zoomTo(ratio) {
    return this.cropper.zoomTo(ratio);
  }

  rotate(degree) {
    return this.cropper.rotate(degree);
  }

  rotateTo(degree) {
    return this.cropper.rotateTo(degree);
  }

  enable() {
    return this.cropper.enable();
  }

  disable() {
    return this.cropper.disable();
  }

  reset() {
    return this.cropper.reset();
  }

  clear() {
    return this.cropper.clear();
  }

  replace(url, onlyColorChanged) {
    return this.cropper.replace(url, onlyColorChanged);
  }

  scale(scaleX, scaleY) {
    return this.cropper.scale(scaleX, scaleY);
  }

  scaleX(scaleX) {
    return this.cropper.scaleX(scaleX);
  }

  scaleY(scaleY) {
    return this.cropper.scaleY(scaleY);
  }

  render() {
    const {
      src,
      alt,
      crossOrigin,
    } = this.props;

    return (
      <div
        src={null}
        crossOrigin={null}
        alt={null}
        style={this.props.style}
        className={this.props.className}
      >
        <img
          crossOrigin={crossOrigin}
          ref={(img) => { this.img = img; }}
          src={src}
          alt={alt === undefined ? 'picture' : alt}
          style={{ opacity: 0 }}
        />
      </div>
    );
  }
}

ReactCropper.propTypes = {
  style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
  className: PropTypes.string,

  // react cropper options
  crossOrigin: PropTypes.string,
  src: PropTypes.string,
  alt: PropTypes.string,

  // props of option can be changed after componentDidmount
  aspectRatio: PropTypes.number,
  dragMode: PropTypes.oneOf(['crop', 'move', 'none']),
  data: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
    rotate: PropTypes.number,
    scaleX: PropTypes.number,
    scaleY: PropTypes.number,
  }),
  scaleX: PropTypes.number,
  scaleY: PropTypes.number,
  enable: PropTypes.bool,
  cropBoxData: PropTypes.shape({
    left: PropTypes.number,
    top: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
  }),
  canvasData: PropTypes.shape({
    left: PropTypes.number,
    top: PropTypes.number,
    width: PropTypes.number,
    height: PropTypes.number,
  }),
  zoomTo: PropTypes.number,
  moveTo: PropTypes.arrayOf(PropTypes.number),
  rotateTo: PropTypes.number,

  // cropperjs options
  // https://github.com/fengyuanchen/cropperjs#options
  // aspectRatio, dragMode, data
  viewMode: PropTypes.oneOf([0, 1, 2, 3]),
  preview: PropTypes.string,
  responsive: PropTypes.bool,
  restore: PropTypes.bool,
  checkCrossOrigin: PropTypes.bool,
  checkOrientation: PropTypes.bool,
  modal: PropTypes.bool,
  guides: PropTypes.bool,
  center: PropTypes.bool,
  highlight: PropTypes.bool,
  background: PropTypes.bool,
  autoCrop: PropTypes.bool,
  autoCropArea: PropTypes.number,
  movable: PropTypes.bool,
  rotatable: PropTypes.bool,
  scalable: PropTypes.bool,
  zoomable: PropTypes.bool,
  zoomOnTouch: PropTypes.bool,
  zoomOnWheel: PropTypes.bool,
  wheelZoomRation: PropTypes.number,
  cropBoxMovable: PropTypes.bool,
  cropBoxResizable: PropTypes.bool,
  toggleDragModeOnDblclick: PropTypes.bool,
  minContainerWidth: PropTypes.number,
  minContainerHeight: PropTypes.number,
  minCanvasWidth: PropTypes.number,
  minCanvasHeight: PropTypes.number,
  minCropBoxWidth: PropTypes.number,
  minCropBoxHeight: PropTypes.number,
  ready: PropTypes.func,
  cropstart: PropTypes.func,
  cropmove: PropTypes.func,
  cropend: PropTypes.func,
  crop: PropTypes.func,
  zoom: PropTypes.func,
};

ReactCropper.defaultProps = {
  src: null,
  dragMode: 'crop',
  data: null,
  scaleX: 1,
  scaleY: 1,
  enable: true,
  zoomTo: 1,
  rotateTo: 0,
};

export default ReactCropper;
<|endoftext|>


<commit_before>import * as actionTypes from '../constants/actionTypes'

export default function grid(state = { rows: 4, cols: 6, isFetchingGrid: [], singerLists: {} }, action) {
  switch (action.type) {
    case actionTypes.GRID_LOADED:
      return { ...state, rows: action.payload.grid.rows, cols: action.payload.grid.cols };
    case actionTypes.GRID_UPDATED:
      return { ...action.payload.grid };
    case actionTypes.GRID_REQUESTED:
      return {
        ...state,
        isFetchingGrid: state.isFetchingGrid.concat(action.payload.performanceId)
      };
    case actionTypes.GRID_RECEIVED:
      let updatedIsFetchingGrid = JSON.parse(JSON.stringify(state.isFetchingGrid));
      updatedIsFetchingGrid.splice(state.isFetchingGrid.findIndex(performanceId => performanceId === action.payload.performanceId), 1);
      return {
        ...state,
        isFetchingGrid: updatedIsFetchingGrid,
        singerLists: {
          ...state.singerLists,
          [action.payload.performanceId]: action.payload.singerList
        }
      };
    case actionTypes.SINGER_MOVED:
      let singerLists = JSON.parse(JSON.stringify(state.singerLists));
      let singerList = singerLists[action.payload.performanceId];
      let getSingerToReplaceIndex = () => {
        return singerList.findIndex(gridSinger => gridSinger.x === action.payload.targetX && gridSinger.y === action.payload.targetY);
      };
      let singerToMoveIndex = singerList.findIndex(gridSinger => gridSinger.singerId === action.payload.singerId);
      if(singerToMoveIndex !== -1) {
        singerList.splice(singerToMoveIndex, 1);
        let singerToReplaceIndex = getSingerToReplaceIndex();
        if(singerToReplaceIndex !== -1) {
          let singerToReplaceId = singerList[singerToReplaceIndex].singerId;
          let singerToReplace = singerList.splice(singerToReplaceIndex, 1);
          singerList.push({ singerId: singerToReplaceId, x: action.payload.sourceX, y: action.payload.sourceY });
        }
      } else {
        let singerToReplaceIndex = getSingerToReplaceIndex();
        if(singerToReplaceIndex !== -1) {
          if (singerList[singerToReplaceIndex].x === action.payload.targetX && singerList[singerToReplaceIndex].y === action.payload.targetY) {
            return state;
          }
        }
      }
      singerList.push({ singerId: action.payload.singerId, x: action.payload.targetX, y: action.payload.targetY });
      singerLists[action.payload.performanceId] = singerList;
      return { ...state, singerLists };
      break;
    default:
      return state;
  }
}
<commit_msg>Fix grid resizing bug<commit_after>import * as actionTypes from '../constants/actionTypes'

export default function grid(state = { rows: 4, cols: 6, isFetchingGrid: [], singerLists: {} }, action) {
  switch (action.type) {
    case actionTypes.GRID_LOADED:
      return { ...state, rows: action.payload.grid.rows, cols: action.payload.grid.cols };
    case actionTypes.GRID_UPDATED:
      return { ...state, ...action.payload.grid };
    case actionTypes.GRID_REQUESTED:
      return {
        ...state,
        isFetchingGrid: state.isFetchingGrid.concat(action.payload.performanceId)
      };
    case actionTypes.GRID_RECEIVED:
      let updatedIsFetchingGrid = JSON.parse(JSON.stringify(state.isFetchingGrid));
      updatedIsFetchingGrid.splice(state.isFetchingGrid.findIndex(performanceId => performanceId === action.payload.performanceId), 1);
      return {
        ...state,
        isFetchingGrid: updatedIsFetchingGrid,
        singerLists: {
          ...state.singerLists,
          [action.payload.performanceId]: action.payload.singerList
        }
      };
    case actionTypes.SINGER_MOVED:
      let singerLists = JSON.parse(JSON.stringify(state.singerLists));
      let singerList = singerLists[action.payload.performanceId];
      let getSingerToReplaceIndex = () => {
        return singerList.findIndex(gridSinger => gridSinger.x === action.payload.targetX && gridSinger.y === action.payload.targetY);
      };
      let singerToMoveIndex = singerList.findIndex(gridSinger => gridSinger.singerId === action.payload.singerId);
      if(singerToMoveIndex !== -1) {
        singerList.splice(singerToMoveIndex, 1);
        let singerToReplaceIndex = getSingerToReplaceIndex();
        if(singerToReplaceIndex !== -1) {
          let singerToReplaceId = singerList[singerToReplaceIndex].singerId;
          let singerToReplace = singerList.splice(singerToReplaceIndex, 1);
          singerList.push({ singerId: singerToReplaceId, x: action.payload.sourceX, y: action.payload.sourceY });
        }
      } else {
        let singerToReplaceIndex = getSingerToReplaceIndex();
        if(singerToReplaceIndex !== -1) {
          if (singerList[singerToReplaceIndex].x === action.payload.targetX && singerList[singerToReplaceIndex].y === action.payload.targetY) {
            return state;
          }
        }
      }
      singerList.push({ singerId: action.payload.singerId, x: action.payload.targetX, y: action.payload.targetY });
      singerLists[action.payload.performanceId] = singerList;
      return { ...state, singerLists };
      break;
    default:
      return state;
  }
}
<|endoftext|>


<commit_before>(function(global) {

  var SyncedGetPutDelete = {
    get: function(path) {
      if(this.caching.cachePath(path)) {
        return this.local.get(path);
      } else {
        return this.remote.get(path);
      }
    },

    put: function(path, body, contentType) {
      if(this.caching.cachePath(path)) {
        return this.local.put(path, body, contentType);
      } else {
        return SyncedGetPutDelete._wrapBusyDone.call(this, this.remote.put(path, body, contentType));
      }
    },

    'delete': function(path) {
      if(this.caching.cachePath(path)) {
        return this.local.delete(path);
      } else {
        return SyncedGetPutDelete._wrapBusyDone.call(this, this.remote.delete(path));
      }
    },

    _wrapBusyDone: function(result) {
      this._emit('sync-busy');
      return result.then(function() {
        var promise = promising();
        this._emit('sync-done');
        return promise.fulfill.apply(promise, arguments);
      }.bind(this), function(err) {
        throw err;
      });
    }
  }

  var haveLocalStorage = 'localStorage' in global;

  /**
   * Class: RemoteStorage
   *
   * Constructor for global remoteStorage object.
   *
   * This class primarily contains feature detection code and a global convenience API.
   *
   * Depending on which features are built in, it contains different attributes and
   * functions. See the individual features for more information.
   *
   */
  var RemoteStorage = function() {
    RemoteStorage.eventHandling(
      this, 'ready', 'disconnected', 'disconnect', 'conflict', 'error',
      'features-loaded', 'connecting', 'authing', 'sync-busy', 'sync-done'
    );
    // pending get/put/delete calls.
    this._pending = [];
    this._setGPD({
      get: this._pendingGPD('get'),
      put: this._pendingGPD('put'),
      delete: this._pendingGPD('delete')
    });
    this._cleanups = [];
    this._pathHandlers = { change: {}, conflict: {} };
    this.apiKeys = {};
    if(haveLocalStorage) {
      try {
        this.apiKeys = JSON.parse(localStorage['remotestorage:api-keys']);
      } catch(exc) { /* ignored. */ };
    }

    var origOn = this.on;
    this.on = function(eventName, handler) {
      if(eventName == 'ready' && this.remote.connected && this._allLoaded) {
        setTimeout(handler, 0);
      } else if(eventName == 'features-loaded' && this._allLoaded) {
        setTimeout(handler, 0);
      }
      return origOn.call(this, eventName, handler);
    }

    this._init();

    this.on('ready', function() {
      if(this.local) {
        setTimeout(this.local.fireInitial.bind(this.local), 0);
      }
    }.bind(this));
  };

  RemoteStorage.DiscoveryError = function(message) {
    Error.apply(this, arguments);
    this.message = message;
  };
  RemoteStorage.DiscoveryError.prototype = Object.create(Error.prototype);

  RemoteStorage.Unauthorized = function() { Error.apply(this, arguments); };
  RemoteStorage.Unauthorized.prototype = Object.create(Error.prototype);

  /**
   * Method: RemoteStorage.log
   *
   * Logging using console.log, when logging is enabled.
   */
  RemoteStorage.log = function() {
    if(RemoteStorage._log) {
      console.log.apply(console, arguments);
    }
  };

  RemoteStorage._log = true;

  RemoteStorage.prototype = {

    /**
     ** PUBLIC INTERFACE
     **/

    /**
     * Method: connect
     *
     * Connect to a remotestorage server.
     *
     * Parameters:
     *   userAddress - The user address (user@host) to connect to.
     *
     * Discovers the webfinger profile of the given user address and
     * initiates the OAuth dance.
     *
     * This method must be called *after* all required access has been claimed.
     *
     */
    connect: function(userAddress) {
      if( userAddress.indexOf('@') < 0) {
        this._emit('error', new RemoteStorage.DiscoveryError("user adress doesn't contain an @"));
        return;
      }
      this._emit('connecting');
      this.remote.configure(userAddress);
      RemoteStorage.Discover(userAddress,function(href, storageApi, authURL){
        if(!href){
          this._emit('error', new RemoteStorage.DiscoveryError('failed to contact storage server'));
          return;
        }
        this._emit('authing');
        this.remote.configure(userAddress, href, storageApi);
        if(! this.remote.connected) {
          this.authorize(authURL);
        }
      }.bind(this));
    },

    /**
     * Method: disconnect
     *
     * "Disconnect" from remotestorage server to terminate current session.
     * This method clears all stored settings and deletes the entire local cache.
     *
     * Once the disconnect is complete, the "disconnected" event will be fired.
     * From that point on you can connect again (using <connect>).
     */
    disconnect: function() {
      if(this.remote) {
        this.remote.configure(null, null, null, null);
      }
      this._setGPD({
        get: this._pendingGPD('get'),
        put: this._pendingGPD('put'),
        delete: this._pendingGPD('delete')
      });
      var n = this._cleanups.length, i = 0;
      var oneDone = function() {
        i++;
        if(i == n) {
          this._init();
          this._emit('disconnected');
          this._emit('disconnect');// DEPRECATED?
        }
      }.bind(this);
      this._cleanups.forEach(function(cleanup) {
        var cleanupResult = cleanup(this);
        if(typeof(cleanup) == 'object' && typeof(cleanup.then) == 'function') {
          cleanupResult.then(oneDone);
        } else {
          oneDone();
        }
      }.bind(this));
    },

    /**
     * Method: onChange
     *
     * Adds a 'change' event handler to the given path.
     * Whenever a 'change' happens (as determined by the backend, such
     * as <RemoteStorage.IndexedDB>) and the affected path is equal to
     * or below the given 'path', the given handler is called.
     *
     * You shouldn't need to use this method directly, but instead use
     * the "change" events provided by <RemoteStorage.BaseClient>.
     *
     * Parameters:
     *   path    - Absolute path to attach handler to.
     *   handler - Handler function.
     */
    onChange: function(path, handler) {
      if(! this._pathHandlers.change[path]) {
        this._pathHandlers.change[path] = [];
      }
      this._pathHandlers.change[path].push(handler);
    },

    onConflict: function(path, handler) {
      if(! this._conflictBound) {
        this.on('features-loaded', function() {
          if(this.local) {
            this.local.on('conflict', this._dispatchEvent.bind(this, 'conflict'));
          }
        }.bind(this));
        this._conflictBound = true;
      }
      if(! this._pathHandlers.conflict[path]) {
        this._pathHandlers.conflict[path] = [];
      }
      this._pathHandlers.conflict[path].push(handler);
    },

    /**
     * Method: enableLog
     *
     * enable logging
     */
    enableLog: function() {
      RemoteStorage._log = true;
    },

    /**
     * Method: disableLog
     *
     * disable logging
     */
    disableLog: function() {
      RemoteStorage._log = false;
    },

    /**
     * Method: log
     *
     * The same as <RemoteStorage.log>.
     */
    log: function() {
      RemoteStorage.log.apply(RemoteStorage, arguments);
    },

    setApiKeys: function(type, keys) {
      if(keys) {
        this.apiKeys[type] = keys;
      } else {
        delete this.apiKeys[type];
      }
      if(haveLocalStorage) {
        localStorage['remotestorage:api-keys'] = JSON.stringify(this.apiKeys);
      }
    },

    /**
     ** INITIALIZATION
     **/

    _init: function() {
      this._loadFeatures(function(features) {
        this.log('all features loaded');
        this.local = features.local && new features.local();
        // (this.remote set by WireClient._rs_init
        //  as lazy property on RS.prototype)

        if(this.local && this.remote) {
          this._setGPD(SyncedGetPutDelete, this);
          this._bindChange(this.local);
        } else if(this.remote) {
          this._setGPD(this.remote, this.remote);
        }

        if(this.remote) {
          this.remote.on('connected', function() {
            try {
              this._emit('ready');
            } catch(e) {
              console.error("'ready' failed: ", e, e.stack);
              this._emit('error', e);
            };
          }.bind(this));
          if(this.remote.connected) {
            try {
              this._emit('ready');
            } catch(e) {
              console.error("'ready' failed: ", e, e.stack);
              this._emit('error', e);
            };
          }
        }

        var fl = features.length;
        for(var i=0;i<fl;i++) {
          var cleanup = features[i].cleanup;
          if(cleanup) {
            this._cleanups.push(cleanup);
          }
        }

        try {
          this._allLoaded = true;
          this._emit('features-loaded');
        } catch(exc) {
          console.error("remoteStorage#ready block failed: ");
          if(typeof(exc) == 'string') {
            console.error(exc);
          } else {
            console.error(exc.message, exc.stack);
          }
          this._emit('error', exc);
        }
        this._processPending();
      });
    },

    /**
     ** FEATURE DETECTION
     **/

    _detectFeatures: function() {
      // determine availability
      var features = [
        'WireClient',
        'GoogleDrive',
        'Access',
        'Caching',
        'Discover',
        'Authorize',
	      'Widget',
        'IndexedDB',
        'LocalStorage',
        'Sync',
        'BaseClient'
      ].map(function(featureName) {
        var impl = RemoteStorage[featureName];
        return {
          name: featureName,
          init: (impl && impl._rs_init),
          supported: impl && (impl._rs_supported ? impl._rs_supported() : true),
          cleanup: ( impl && impl._rs_cleanup )
        };
      }).filter(function(feature) {
        var supported = !! (feature.init && feature.supported);
        this.log("[FEATURE " + feature.name + "] " + (supported ? '' : 'not ') + 'supported.');
        return supported;
      }.bind(this));

      features.forEach(function(feature) {
        if(feature.name == 'IndexedDB') {
          features.local = RemoteStorage.IndexedDB;
        } else if(feature.name == 'LocalStorage' && ! features.local) {
          features.local = RemoteStorage.LocalStorage;
        }
      });
      features.caching = !!RemoteStorage.Caching;
      features.sync = !!RemoteStorage.Sync;

      this.features = features;

      return features;
    },

    _loadFeatures: function(callback) {
      var features = this._detectFeatures();
      var n = features.length, i = 0;
      var self = this;
      function featureDoneCb(name) {
        return function() {
          i++;
          self.log("[FEATURE " + name + "] initialized. (" + i + "/" + n + ")");
          if(i == n)
            setTimeout(function() {
              callback.apply(self, [features]);
            }, 0);
        }
      }
      features.forEach(function(feature) {
        self.log("[FEATURE " + feature.name + "] initializing...");
        var initResult = feature.init(self);
        var cb = featureDoneCb(feature.name);
        if(typeof(initResult) == 'object' && typeof(initResult.then) == 'function') {
          initResult.then(cb);
        } else {
          cb();
        }
      });
    },

    /**
     ** GET/PUT/DELETE INTERFACE HELPERS
     **/

    _setGPD: function(impl, context) {
      this.get = impl.get.bind(context);
      this.put = impl.put.bind(context);
      this.delete = impl.delete.bind(context);
    },

    _pendingGPD: function(methodName) {
      return function() {
        var promise = promising();
        this._pending.push({
          method: methodName,
          args: Array.prototype.slice.call(arguments),
          promise: promise
        });
        return promise;
      }.bind(this);
    },

    _processPending: function() {
      this._pending.forEach(function(pending) {
        this[pending.method].apply(this, pending.args).then(pending.promise.fulfill, pending.promise.reject);
      }.bind(this));
      this._pending = [];
    },

    /**
     ** CHANGE EVENT HANDLING
     **/

    _bindChange: function(object) {
      object.on('change', this._dispatchEvent.bind(this, 'change'));
    },

    _dispatchEvent: function(eventName, event) {
      for(var path in this._pathHandlers[eventName]) {
        var pl = path.length;
        this._pathHandlers[eventName][path].forEach(function(handler) {
          if(event.path.substr(0, pl) == path) {
            var ev = {};
            for(var key in event) { ev[key] = event[key]; }
            ev.relativePath = event.path.replace(new RegExp('^' + path), '');
            try {
              handler(ev);
            } catch(e) {
              console.error("'change' handler failed: ", e, e.stack);
              this._emit('error', e);
            }
          }
        }.bind(this));
      }
    }
  };

  /**
   * Method: claimAccess
   *
   * High-level method to claim access on one or multiple scopes and enable
   * caching for them. WARNING: when using Caching control, use remoteStorage.access.claim instead,
   * see https://github.com/remotestorage/remotestorage.js/issues/380
   *
   * Examples:
   *   (start code)
   *     remoteStorage.claimAccess('foo', 'rw');
   *     // is equivalent to:
   *     remoteStorage.claimAccess({ foo: 'rw' });
   *
   *     // is equivalent to:
   *     remoteStorage.access.claim('foo', 'rw');
   *     remoteStorage.caching.enable('/foo/');
   *     remoteStorage.caching.enable('/public/foo/');
   *   (end code)
   */

  /**
   * Property: connected
   *
   * Boolean property indicating if remoteStorage is currently connected.
   */
  Object.defineProperty(RemoteStorage.prototype, 'connected', {
    get: function() {
      return this.remote.connected;
    }
  });

  /**
   * Property: access
   *
   * Tracking claimed access scopes. A <RemoteStorage.Access> instance.
   *
   *
   * Property: caching
   *
   * Caching settings. A <RemoteStorage.Caching> instance.
   *
   * (only available when caching is built in)
   *
   *
   * Property: remote
   *
   * Access to the remote backend used. Usually a <RemoteStorage.WireClient>.
   *
   *
   * Property: local
   *
   * Access to the local caching backend used.
   * Only available when caching is built in.
   * Usually either a <RemoteStorage.IndexedDB> or <RemoteStorage.LocalStorage>
   * instance.
   */

  global.RemoteStorage = RemoteStorage;

})(this);
<commit_msg>disabled logging by default<commit_after>(function(global) {

  var SyncedGetPutDelete = {
    get: function(path) {
      if(this.caching.cachePath(path)) {
        return this.local.get(path);
      } else {
        return this.remote.get(path);
      }
    },

    put: function(path, body, contentType) {
      if(this.caching.cachePath(path)) {
        return this.local.put(path, body, contentType);
      } else {
        return SyncedGetPutDelete._wrapBusyDone.call(this, this.remote.put(path, body, contentType));
      }
    },

    'delete': function(path) {
      if(this.caching.cachePath(path)) {
        return this.local.delete(path);
      } else {
        return SyncedGetPutDelete._wrapBusyDone.call(this, this.remote.delete(path));
      }
    },

    _wrapBusyDone: function(result) {
      this._emit('sync-busy');
      return result.then(function() {
        var promise = promising();
        this._emit('sync-done');
        return promise.fulfill.apply(promise, arguments);
      }.bind(this), function(err) {
        throw err;
      });
    }
  }

  var haveLocalStorage = 'localStorage' in global;

  /**
   * Class: RemoteStorage
   *
   * Constructor for global remoteStorage object.
   *
   * This class primarily contains feature detection code and a global convenience API.
   *
   * Depending on which features are built in, it contains different attributes and
   * functions. See the individual features for more information.
   *
   */
  var RemoteStorage = function() {
    RemoteStorage.eventHandling(
      this, 'ready', 'disconnected', 'disconnect', 'conflict', 'error',
      'features-loaded', 'connecting', 'authing', 'sync-busy', 'sync-done'
    );
    // pending get/put/delete calls.
    this._pending = [];
    this._setGPD({
      get: this._pendingGPD('get'),
      put: this._pendingGPD('put'),
      delete: this._pendingGPD('delete')
    });
    this._cleanups = [];
    this._pathHandlers = { change: {}, conflict: {} };
    this.apiKeys = {};
    if(haveLocalStorage) {
      try {
        this.apiKeys = JSON.parse(localStorage['remotestorage:api-keys']);
      } catch(exc) { /* ignored. */ };
    }

    var origOn = this.on;
    this.on = function(eventName, handler) {
      if(eventName == 'ready' && this.remote.connected && this._allLoaded) {
        setTimeout(handler, 0);
      } else if(eventName == 'features-loaded' && this._allLoaded) {
        setTimeout(handler, 0);
      }
      return origOn.call(this, eventName, handler);
    }

    this._init();

    this.on('ready', function() {
      if(this.local) {
        setTimeout(this.local.fireInitial.bind(this.local), 0);
      }
    }.bind(this));
  };

  RemoteStorage.DiscoveryError = function(message) {
    Error.apply(this, arguments);
    this.message = message;
  };
  RemoteStorage.DiscoveryError.prototype = Object.create(Error.prototype);

  RemoteStorage.Unauthorized = function() { Error.apply(this, arguments); };
  RemoteStorage.Unauthorized.prototype = Object.create(Error.prototype);

  /**
   * Method: RemoteStorage.log
   *
   * Logging using console.log, when logging is enabled.
   */
  RemoteStorage.log = function() {
    if(RemoteStorage._log) {
      console.log.apply(console, arguments);
    }
  };

  RemoteStorage.prototype = {

    /**
     ** PUBLIC INTERFACE
     **/

    /**
     * Method: connect
     *
     * Connect to a remotestorage server.
     *
     * Parameters:
     *   userAddress - The user address (user@host) to connect to.
     *
     * Discovers the webfinger profile of the given user address and
     * initiates the OAuth dance.
     *
     * This method must be called *after* all required access has been claimed.
     *
     */
    connect: function(userAddress) {
      if( userAddress.indexOf('@') < 0) {
        this._emit('error', new RemoteStorage.DiscoveryError("user adress doesn't contain an @"));
        return;
      }
      this._emit('connecting');
      this.remote.configure(userAddress);
      RemoteStorage.Discover(userAddress,function(href, storageApi, authURL){
        if(!href){
          this._emit('error', new RemoteStorage.DiscoveryError('failed to contact storage server'));
          return;
        }
        this._emit('authing');
        this.remote.configure(userAddress, href, storageApi);
        if(! this.remote.connected) {
          this.authorize(authURL);
        }
      }.bind(this));
    },

    /**
     * Method: disconnect
     *
     * "Disconnect" from remotestorage server to terminate current session.
     * This method clears all stored settings and deletes the entire local cache.
     *
     * Once the disconnect is complete, the "disconnected" event will be fired.
     * From that point on you can connect again (using <connect>).
     */
    disconnect: function() {
      if(this.remote) {
        this.remote.configure(null, null, null, null);
      }
      this._setGPD({
        get: this._pendingGPD('get'),
        put: this._pendingGPD('put'),
        delete: this._pendingGPD('delete')
      });
      var n = this._cleanups.length, i = 0;
      var oneDone = function() {
        i++;
        if(i == n) {
          this._init();
          this._emit('disconnected');
          this._emit('disconnect');// DEPRECATED?
        }
      }.bind(this);
      this._cleanups.forEach(function(cleanup) {
        var cleanupResult = cleanup(this);
        if(typeof(cleanup) == 'object' && typeof(cleanup.then) == 'function') {
          cleanupResult.then(oneDone);
        } else {
          oneDone();
        }
      }.bind(this));
    },

    /**
     * Method: onChange
     *
     * Adds a 'change' event handler to the given path.
     * Whenever a 'change' happens (as determined by the backend, such
     * as <RemoteStorage.IndexedDB>) and the affected path is equal to
     * or below the given 'path', the given handler is called.
     *
     * You shouldn't need to use this method directly, but instead use
     * the "change" events provided by <RemoteStorage.BaseClient>.
     *
     * Parameters:
     *   path    - Absolute path to attach handler to.
     *   handler - Handler function.
     */
    onChange: function(path, handler) {
      if(! this._pathHandlers.change[path]) {
        this._pathHandlers.change[path] = [];
      }
      this._pathHandlers.change[path].push(handler);
    },

    onConflict: function(path, handler) {
      if(! this._conflictBound) {
        this.on('features-loaded', function() {
          if(this.local) {
            this.local.on('conflict', this._dispatchEvent.bind(this, 'conflict'));
          }
        }.bind(this));
        this._conflictBound = true;
      }
      if(! this._pathHandlers.conflict[path]) {
        this._pathHandlers.conflict[path] = [];
      }
      this._pathHandlers.conflict[path].push(handler);
    },

    /**
     * Method: enableLog
     *
     * enable logging
     */
    enableLog: function() {
      RemoteStorage._log = true;
    },

    /**
     * Method: disableLog
     *
     * disable logging
     */
    disableLog: function() {
      RemoteStorage._log = false;
    },

    /**
     * Method: log
     *
     * The same as <RemoteStorage.log>.
     */
    log: function() {
      RemoteStorage.log.apply(RemoteStorage, arguments);
    },

    setApiKeys: function(type, keys) {
      if(keys) {
        this.apiKeys[type] = keys;
      } else {
        delete this.apiKeys[type];
      }
      if(haveLocalStorage) {
        localStorage['remotestorage:api-keys'] = JSON.stringify(this.apiKeys);
      }
    },

    /**
     ** INITIALIZATION
     **/

    _init: function() {
      this._loadFeatures(function(features) {
        this.log('all features loaded');
        this.local = features.local && new features.local();
        // (this.remote set by WireClient._rs_init
        //  as lazy property on RS.prototype)

        if(this.local && this.remote) {
          this._setGPD(SyncedGetPutDelete, this);
          this._bindChange(this.local);
        } else if(this.remote) {
          this._setGPD(this.remote, this.remote);
        }

        if(this.remote) {
          this.remote.on('connected', function() {
            try {
              this._emit('ready');
            } catch(e) {
              console.error("'ready' failed: ", e, e.stack);
              this._emit('error', e);
            };
          }.bind(this));
          if(this.remote.connected) {
            try {
              this._emit('ready');
            } catch(e) {
              console.error("'ready' failed: ", e, e.stack);
              this._emit('error', e);
            };
          }
        }

        var fl = features.length;
        for(var i=0;i<fl;i++) {
          var cleanup = features[i].cleanup;
          if(cleanup) {
            this._cleanups.push(cleanup);
          }
        }

        try {
          this._allLoaded = true;
          this._emit('features-loaded');
        } catch(exc) {
          console.error("remoteStorage#ready block failed: ");
          if(typeof(exc) == 'string') {
            console.error(exc);
          } else {
            console.error(exc.message, exc.stack);
          }
          this._emit('error', exc);
        }
        this._processPending();
      });
    },

    /**
     ** FEATURE DETECTION
     **/

    _detectFeatures: function() {
      // determine availability
      var features = [
        'WireClient',
        'GoogleDrive',
        'Access',
        'Caching',
        'Discover',
        'Authorize',
	      'Widget',
        'IndexedDB',
        'LocalStorage',
        'Sync',
        'BaseClient'
      ].map(function(featureName) {
        var impl = RemoteStorage[featureName];
        return {
          name: featureName,
          init: (impl && impl._rs_init),
          supported: impl && (impl._rs_supported ? impl._rs_supported() : true),
          cleanup: ( impl && impl._rs_cleanup )
        };
      }).filter(function(feature) {
        var supported = !! (feature.init && feature.supported);
        this.log("[FEATURE " + feature.name + "] " + (supported ? '' : 'not ') + 'supported.');
        return supported;
      }.bind(this));

      features.forEach(function(feature) {
        if(feature.name == 'IndexedDB') {
          features.local = RemoteStorage.IndexedDB;
        } else if(feature.name == 'LocalStorage' && ! features.local) {
          features.local = RemoteStorage.LocalStorage;
        }
      });
      features.caching = !!RemoteStorage.Caching;
      features.sync = !!RemoteStorage.Sync;

      this.features = features;

      return features;
    },

    _loadFeatures: function(callback) {
      var features = this._detectFeatures();
      var n = features.length, i = 0;
      var self = this;
      function featureDoneCb(name) {
        return function() {
          i++;
          self.log("[FEATURE " + name + "] initialized. (" + i + "/" + n + ")");
          if(i == n)
            setTimeout(function() {
              callback.apply(self, [features]);
            }, 0);
        }
      }
      features.forEach(function(feature) {
        self.log("[FEATURE " + feature.name + "] initializing...");
        var initResult = feature.init(self);
        var cb = featureDoneCb(feature.name);
        if(typeof(initResult) == 'object' && typeof(initResult.then) == 'function') {
          initResult.then(cb);
        } else {
          cb();
        }
      });
    },

    /**
     ** GET/PUT/DELETE INTERFACE HELPERS
     **/

    _setGPD: function(impl, context) {
      this.get = impl.get.bind(context);
      this.put = impl.put.bind(context);
      this.delete = impl.delete.bind(context);
    },

    _pendingGPD: function(methodName) {
      return function() {
        var promise = promising();
        this._pending.push({
          method: methodName,
          args: Array.prototype.slice.call(arguments),
          promise: promise
        });
        return promise;
      }.bind(this);
    },

    _processPending: function() {
      this._pending.forEach(function(pending) {
        this[pending.method].apply(this, pending.args).then(pending.promise.fulfill, pending.promise.reject);
      }.bind(this));
      this._pending = [];
    },

    /**
     ** CHANGE EVENT HANDLING
     **/

    _bindChange: function(object) {
      object.on('change', this._dispatchEvent.bind(this, 'change'));
    },

    _dispatchEvent: function(eventName, event) {
      for(var path in this._pathHandlers[eventName]) {
        var pl = path.length;
        this._pathHandlers[eventName][path].forEach(function(handler) {
          if(event.path.substr(0, pl) == path) {
            var ev = {};
            for(var key in event) { ev[key] = event[key]; }
            ev.relativePath = event.path.replace(new RegExp('^' + path), '');
            try {
              handler(ev);
            } catch(e) {
              console.error("'change' handler failed: ", e, e.stack);
              this._emit('error', e);
            }
          }
        }.bind(this));
      }
    }
  };

  /**
   * Method: claimAccess
   *
   * High-level method to claim access on one or multiple scopes and enable
   * caching for them. WARNING: when using Caching control, use remoteStorage.access.claim instead,
   * see https://github.com/remotestorage/remotestorage.js/issues/380
   *
   * Examples:
   *   (start code)
   *     remoteStorage.claimAccess('foo', 'rw');
   *     // is equivalent to:
   *     remoteStorage.claimAccess({ foo: 'rw' });
   *
   *     // is equivalent to:
   *     remoteStorage.access.claim('foo', 'rw');
   *     remoteStorage.caching.enable('/foo/');
   *     remoteStorage.caching.enable('/public/foo/');
   *   (end code)
   */

  /**
   * Property: connected
   *
   * Boolean property indicating if remoteStorage is currently connected.
   */
  Object.defineProperty(RemoteStorage.prototype, 'connected', {
    get: function() {
      return this.remote.connected;
    }
  });

  /**
   * Property: access
   *
   * Tracking claimed access scopes. A <RemoteStorage.Access> instance.
   *
   *
   * Property: caching
   *
   * Caching settings. A <RemoteStorage.Caching> instance.
   *
   * (only available when caching is built in)
   *
   *
   * Property: remote
   *
   * Access to the remote backend used. Usually a <RemoteStorage.WireClient>.
   *
   *
   * Property: local
   *
   * Access to the local caching backend used.
   * Only available when caching is built in.
   * Usually either a <RemoteStorage.IndexedDB> or <RemoteStorage.LocalStorage>
   * instance.
   */

  global.RemoteStorage = RemoteStorage;

})(this);
<|endoftext|>


<commit_before>roleAllocator = {

    spawnNecessary: function() {
	    var store = {
	        'generalist': 4,
	    };

	    for(role in store) {
	        var count = store[role];
	        var creeps = _.filter(Game.creeps, (creep) => creep.memory.role == role);
	        if(creeps.length < count) {
		        var newName = Game.spawns['Spawn1'].createCreep([WORK,WORK,CARRY,CARRY,MOVE,MOVE], undefined, {role: role});
		        console.log('spawning new ' + role + ': ' + newName);
	        }
	    }
    }
}

module.exports = roleAllocator;
<commit_msg>moar workers!<commit_after>roleAllocator = {

    spawnNecessary: function() {
	    var store = {
	        'generalist': 6,
	    };

	    for(role in store) {
	        var count = store[role];
	        var creeps = _.filter(Game.creeps, (creep) => creep.memory.role == role);
	        if(creeps.length < count) {
		        var newName = Game.spawns['Spawn1'].createCreep([WORK,WORK,CARRY,CARRY,MOVE,MOVE], undefined, {role: role});
		        console.log('spawning new ' + role + ': ' + newName);
	        }
	    }
    }
}

module.exports = roleAllocator;
<|endoftext|>


<commit_before>import Home from '@/components/Home';
import Profile from '@/components/Profile';
import Startup from '@/components/Startup';
import Authentication from '@/components/Authentication';

const routes = [
  {
    path: '/',
    component: Startup,
    meta: { noAuth: true },
  },
  {
    path: '/entrar',
    component: Authentication,
    meta: { noAuth: true },
  },
  {
    path: '/home',
    titulo: 'Home',
    component: Home,
  },
  {
    path: '/ao',
    component: () => import('../components/AwaitingOrder'),
  },
  {
    path: '/perfil',
    name: 'usuario',
    titulo: 'Profile',
    component: Profile,
  },
];

export default routes;
<commit_msg>fix route to awaiting order<commit_after>import Home from '@/components/Home';
import Profile from '@/components/Profile';
import Startup from '@/components/Startup';
import Authentication from '@/components/Authentication';

const routes = [
  {
    path: '/',
    component: Startup,
    meta: { noAuth: true },
  },
  {
    path: '/entrar',
    component: Authentication,
    meta: { noAuth: true },
  },
  {
    path: '/home',
    titulo: 'Home',
    component: Home,
  },
  {
    path: '/pedidos/acompanhar',
    component: () => import('../components/AwaitingOrder'),
  },
  {
    path: '/perfil',
    name: 'usuario',
    titulo: 'Profile',
    component: Profile,
  },
];

export default routes;
<|endoftext|>


<commit_before>// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.9.0',
  require: function(libraryName) {
    try{
      // inserting via DOM fails in Safari 2.0, so brute force approach
      document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
    } catch(e) {
      // for xhtml+xml served content, fall back to DOM methods
      var script = document.createElement('script');
      script.type = 'text/javascript';
      script.src = libraryName;
      document.getElementsByTagName('head')[0].appendChild(script);
    }
  },
  REQUIRED_PROTOTYPE: '1.6.0.3',
  load: function() {
    function convertVersionString(versionString) {
      var v = versionString.replace(/_.*|\./g, '');
      v = parseInt(v + '0'.times(4-v.length));
      return versionString.indexOf('_') > -1 ? v-1 : v;
    }

    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);

    var js = /scriptaculous\.js(\?.*)?$/;
    $$('script[src]').findAll(function(s) {
      return s.src.match(js);
    }).each(function(s) {
      var path = s.src.replace(js, ''),
      includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
};

Scriptaculous.load();<commit_msg>removed document.write part of scriptaculous#require<commit_after>// Copyright (c) 2005-2010 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// For details, see the script.aculo.us web site: http://script.aculo.us/

var Scriptaculous = {
  Version: '1.9.0',
  require: function(libraryName) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = libraryName;
    document.getElementsByTagName('head')[0].appendChild(script);
  },
  REQUIRED_PROTOTYPE: '1.6.0.3',
  load: function() {
    function convertVersionString(versionString) {
      var v = versionString.replace(/_.*|\./g, '');
      v = parseInt(v + '0'.times(4-v.length));
      return versionString.indexOf('_') > -1 ? v-1 : v;
    }

    if((typeof Prototype=='undefined') ||
       (typeof Element == 'undefined') ||
       (typeof Element.Methods=='undefined') ||
       (convertVersionString(Prototype.Version) <
        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
        Scriptaculous.REQUIRED_PROTOTYPE);

    var js = /scriptaculous\.js(\?.*)?$/;
    $$('script[src]').findAll(function(s) {
      return s.src.match(js);
    }).each(function(s) {
      var path = s.src.replace(js, ''),
      includes = s.src.match(/\?.*load=([a-z,]*)/);
      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
       function(include) { Scriptaculous.require(path+include+'.js') });
    });
  }
};

Scriptaculous.load();<|endoftext|>


<commit_before>const json = require('../demos.json')

function getItems (query) {
  return query
    ? json.filter(item => {
      return (item.packages.map(hash).indexOf(hash(query)) > -1) || (item['dcos_version'].indexOf(query) > -1)
    })
    : json
}

function hash (name) {
  return name.replace(/\s+/g, '-').toLowerCase()
}

function getQuery () {
  const query = window.location.hash.split('#')
  const q = query[1]

  return q // string or undefined
}

function clean () {
  $(".demos-cards").empty()
}

function getDemoItem(demo) {

  const contentClass = demo.featured ? 'px2 lg-col-6 col-6 xs-col-12' : 'pt2';
  const imageClass = demo.featured ? 'lg-col-6 col-6 xs-col-12 pr2' : '';
  const image = demo.image ? `<div class="${imageClass}"><img src="${ demo.image }" alt=""></div>` : ``;
  const classNames = demo.featured ? 'lg-col-12 col-12 xs-col-12 flex flex-wrap' : 'lg-col-6 col-6 xs-col-12 bg-white'

  return `

      ${image}

${demo.demo_name}

${demo.name}

${demo.description}

Packages

${demo.packages.join(', ').toString()}

Version

${$.map(demo.dcos_version, (version) => `<a href="#${version}">${version}</a>`).join(', ').toString()}

Language

${demo.language}

          ${$.map(demo.callouts, (calloutUrl, name) => `<a class="cta cta--button" href="${calloutUrl}">View demo</a>`).join(' • ').toString()}

    `;
}

function render (items) {

  const featured_items = items.filter(function(value) {
    return value.featured == true
  })

  const other_items = items.filter(function(value) {
    return value.featured == false
  })

  other_items.forEach(demo => {
    const item = getDemoItem(demo);
    $(".demos-cards").append(item);
  });

  if (featured_items.length) {
    const featured_item = getDemoItem(featured_items[0]);
    $(".demos-cards-featured").prepend(featured_item);
  }
}

function main () {
  const query = getQuery()
  const items = getItems(query)
  render(items)

  // if(query) {
  //   $(".demos-cards").prepend(
  //     `
  //

  //
Showing demos matching: ${query}

  //
  //         Reset filter
  //
  //

  //     `
  //   )
  // }
}

/*****************
  This is used to listen to when people filter tags
*****************/
// window.onhashchange = function () {
//   const query = getQuery()

//   if (query) {
//     clean()
//     main()
//   }
// }

$(document).ready(main)
<commit_msg>Feedback fixes<commit_after>const json = require('../demos.json')

function getItems (query) {
  return query
    ? json.filter(item => {
      return (item.packages.map(hash).indexOf(hash(query)) > -1) || (item['dcos_version'].indexOf(query) > -1)
    })
    : json
}

function hash (name) {
  return name.replace(/\s+/g, '-').toLowerCase()
}

function getQuery () {
  const query = window.location.hash.split('#')
  const q = query[1]

  return q // string or undefined
}

function clean () {
  $(".demos-cards").empty()
}

function getDemoItem(demo) {

  const contentClass = demo.featured ? 'px2 lg-col-6 col-6 xs-col-12' : 'pt2';
  const imageClass = demo.featured ? 'lg-col-6 col-6 xs-col-12 pr2' : '';
  const image = demo.image ? `<div class="${imageClass}"><img src="${ demo.image }" alt=""></div>` : ``;
  const classNames = demo.featured ? 'lg-col-12 col-12 xs-col-12 flex flex-wrap' : 'lg-col-6 col-6 xs-col-12 bg-white'

  return `

      ${image}

${demo.demo_name}

${demo.name}

${demo.description}

Packages

${demo.packages.join(', ').toString()}

Version

${demo.dcos_version.join(', ').toString()}

Language

${demo.language}

          ${$.map(demo.callouts, (calloutUrl, name) => `<a class="cta cta--button" href="${calloutUrl}">View demo</a>`).join(' • ').toString()}

    `;
}

function render (items) {

  const featured_items = items.filter(function(value) {
    return value.featured == true
  })

  const other_items = items.filter(function(value) {
    return value.featured == false
  })

  other_items.forEach(demo => {
    const item = getDemoItem(demo);
    $(".demos-cards").append(item);
  });

  if (featured_items.length) {
    const featured_item = getDemoItem(featured_items[0]);
    $(".demos-cards-featured").prepend(featured_item);
  }
}

function main () {
  const query = getQuery()
  const items = getItems(query)
  render(items)

  // if(query) {
  //   $(".demos-cards").prepend(
  //     `
  //

  //
Showing demos matching: ${query}

  //
  //         Reset filter
  //
  //

  //     `
  //   )
  // }
}

/*****************
  This is used to listen to when people filter tags
*****************/
// window.onhashchange = function () {
//   const query = getQuery()

//   if (query) {
//     clean()
//     main()
//   }
// }

$(document).ready(main)
<|endoftext|>


<commit_before>import * as THREE from "three";
import WindowResize from "three-window-resize";
import random from "lodash.random";
import FontCache from "./font-cache";
import ModelCache from "./model-cache";
import SoundCache from "./sound-cache";

const loadingEl = document.getElementById("loading");
const color = new THREE.Color(getComputedStyle(document.body).getPropertyValue("color"));
const material = new THREE.MeshToonMaterial({ color, transparent: true, opacity: 0.85 });
const fieldOfView = 45;
const drawDistance = 1000;
const gridSize = 10000;
const gridDivisions = 150;

let renderer, scene, camera, t;

init().then(render);

function init() {
  return Promise
  .resolve()
  .then(() => {
    loadingEl.innerHTML = "Loading fonts...";
    return FontCache.init(["Righteous_Regular"]);
  }).then(() => {
    loadingEl.innerHTML = "Loading models...";
    return ModelCache.init(["note1", "note2", "note3", "note4"]);
  }).then(() => {
    loadingEl.innerHTML = "Loading sounds...";
    return SoundCache.init(["1", "2", "3", "4", "5"]);
  }).then(() => {
    loadingEl.remove();

    renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    scene = new THREE.Scene();

    camera = new THREE.PerspectiveCamera(fieldOfView, window.innerWidth / window.innerHeight, 1, drawDistance);
    camera.position.x = 0;
    camera.position.y = 0;
    camera.position.z = drawDistance;

    let ambientLight = new THREE.AmbientLight();
    scene.add(ambientLight);

    let font = FontCache.get("Righteous_Regular");
    let geometry = new THREE.TextGeometry("SYNTH MOOD", { font, size: 85, height: 1 });
    let text = new THREE.Mesh(geometry, material);
    geometry.computeBoundingBox(); // Compute bounding box so that text can be centered.
    text.position.x = geometry.boundingBox.min.x - geometry.boundingBox.max.x / 2;
    text.position.y = geometry.boundingBox.min.y - geometry.boundingBox.max.y / 2;
    text.position.z = 0;
    scene.add(text);

    let gridBottom = new THREE.GridHelper(gridSize, gridDivisions);
    gridBottom.position.x = 0;
    gridBottom.position.y = -100;
    gridBottom.position.z = 0;
    gridBottom.material = material;
    gridBottom.userData = { type: "grid" };
    scene.add(gridBottom);

    let gridTop = new THREE.GridHelper(gridSize, gridDivisions);
    gridTop.position.x = 0;
    gridTop.position.y = 100;
    gridTop.position.z = 0;
    gridTop.material = material;
    gridTop.userData = { type: "grid" };
    scene.add(gridTop);

    WindowResize(renderer, camera); // Automatically handle window resize events.

    t = 0;
  });
}

function render() {
  requestAnimationFrame(render);

  // Update all objects in the scene.
  let childrenToRemove = new Set();
  scene.traverse(child => {
    switch (child.userData.type) {
      case "grid":
        if (child.position.z < gridSize / gridDivisions)
          // Move grid closer to the camera.
          child.position.z += 1;
        else
          // To make grid appear "infinite", reset its position once it has travelled one grid row of distance.
          child.position.z = 0;
        break;
      case "note":
        if (child.position.z < camera.position.z)
          // Move note closer to the camera.
          child.position.z += 3;
        else
          // Destroy note once it has travelled past the camera.
          childrenToRemove.add(child);
        // child.rotation.y += 0.02;
        break;
    }
  });

  // Destroy scene objects.
  childrenToRemove.forEach(::scene.remove);

  // Spawn a new note.
  if (t % 15 === 0) {
    let note = ModelCache.get(`note${random(1, 4)}`);
    note.position.x = random(-380, 380);
    note.position.y = random(-100, 100);
    note.position.z = 0;
    note.scale.x = note.scale.y = note.scale.z = 10;
    note.userData = { type: "note" };
    note.traverse(child => {
      if (child instanceof THREE.Mesh) {
        child.material = material;
      }
    });
    scene.add(note);
  }

  // Spawn a new sound.
  if (t % 1000 === 0) {
    let sound = SoundCache.get(`${random(1, 5)}`);
    // sound.play();
  }

  // Render the scene!
  renderer.render(scene, camera);

  t++;
}
<commit_msg>Retain references to significant objects<commit_after>import * as THREE from "three";
import WindowResize from "three-window-resize";
import random from "lodash.random";
import FontCache from "./font-cache";
import ModelCache from "./model-cache";
import SoundCache from "./sound-cache";

const loadingEl = document.getElementById("loading");
const color = new THREE.Color(getComputedStyle(document.body).getPropertyValue("color"));
const material = new THREE.MeshToonMaterial({ color, transparent: true, opacity: 0.85 });
const fieldOfView = 45;
const drawDistance = 1000;
const gridSize = 10000;
const gridDivisions = 150;

let renderer, camera, scene, text, gridTop, gridBottom, notes, frame;

init().then(render);

function init() {
  return Promise
  .resolve()
  .then(() => {
    loadingEl.innerHTML = "Loading fonts...";
    return FontCache.init(["Righteous_Regular"]);
  }).then(() => {
    loadingEl.innerHTML = "Loading models...";
    return ModelCache.init(["note1", "note2", "note3", "note4"]);
  }).then(() => {
    loadingEl.innerHTML = "Loading sounds...";
    return SoundCache.init(["1", "2", "3", "4", "5"]);
  }).then(() => {
    loadingEl.remove();

    renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    camera = new THREE.PerspectiveCamera(fieldOfView, window.innerWidth / window.innerHeight, 1, drawDistance);
    camera.position.x = 0;
    camera.position.y = 0;
    camera.position.z = drawDistance;

    WindowResize(renderer, camera); // Automatically handle window resize events.

    scene = new THREE.Scene();

    let ambientLight = new THREE.AmbientLight();
    scene.add(ambientLight);

    let font = FontCache.get("Righteous_Regular");
    let geometry = new THREE.TextGeometry("SYNTH MOOD", { font, size: 85, height: 1 });
    geometry.computeBoundingBox(); // Compute bounding box so that text can be centered.
    text = new THREE.Mesh(geometry, material);
    text.position.x = geometry.boundingBox.min.x - geometry.boundingBox.max.x / 2;
    text.position.y = geometry.boundingBox.min.y - geometry.boundingBox.max.y / 2;
    text.position.z = 0;
    scene.add(text);

    gridTop = new THREE.GridHelper(gridSize, gridDivisions);
    gridTop.position.x = 0;
    gridTop.position.y = 100;
    gridTop.position.z = 0;
    gridTop.material = material;
    scene.add(gridTop);

    gridBottom = new THREE.GridHelper(gridSize, gridDivisions);
    gridBottom.position.x = 0;
    gridBottom.position.y = -100;
    gridBottom.position.z = 0;
    gridBottom.material = material;
    scene.add(gridBottom);

    notes = new Set();

    frame = 0;
  });
}

function render() {
  requestAnimationFrame(render);

  // Move grids closer to the camera.
  // To make grids appear "infinite", reset their position once they have travelled one grid row of distance.
  gridTop.position.z += gridTop.position.z < gridSize / gridDivisions ? 1 : -gridTop.position.z;
  gridBottom.position.z += gridBottom.position.z < gridSize / gridDivisions ? 1 : -gridBottom.position.z;

  // Move notes closer to the camera.
  // Destroy notes once they have travelled past the camera.
  notes.forEach(note => {
    if (note.position.z < camera.position.z) {
      note.position.z += 3;
    } else {
      notes.delete(note);
      scene.remove(note);
    }
  });

  // Spawn a new note?
  if (frame % 15 === 0) {
    let note = ModelCache.get(`note${random(1, 4)}`);
    note.position.x = random(text.position.x, text.position.x + text.geometry.boundingBox.max.x - text.geometry.boundingBox.min.x);
    note.position.y = random(gridBottom.position.y, gridTop.position.y);
    note.position.z = text.position.z;
    note.scale.x = note.scale.y = note.scale.z = 10;
    note.traverse(child => Object.assign(child, { material }));
    notes.add(note);
    scene.add(note);
  }

  // Spawn a new sound?
  if (frame % 1000 === 0) {
    let sound = SoundCache.get(`${random(1, 5)}`);
    // sound.play();
  }

  // Render the scene!
  renderer.render(scene, camera);

  frame++;
}
<|endoftext|>