<commit_before>/* global URLSearchParams */

'use strict';

import { Service } from 'components/mvc';

import Settings from './settings';
import Db from './db';
import Network from './network';
import Recipes from './recipes';
import WebPush from './webpush';

import BaseService from './services/base';
import IpCameraService from './services/ip-camera';
import LightService from './services/light';
import DoorLockService from './services/door-lock';
import MotionSensorService from './services/motion-sensor';

// Private members.
const p = Object.freeze({
  // Private properties.
  settings: Symbol('settings'),
  db: Symbol('db'),
  net: Symbol('net'),
  boxes: Symbol('boxes'),
  isPollingEnabled: Symbol('isPollingEnabled'),
  nextPollTimeout: Symbol('nextPollTimeout'),
  webPush: Symbol('webPush'),

  // Private methods.
  fetchServices: Symbol('fetchServices'),
  getServiceInstance: Symbol('getServiceInstance'),
  hasDoorLockChannel: Symbol('hasDoorLockChannel'),
});

/**
 * Compare 2 objects. Returns true if all properties of object A have the same
 * value in object B. Extraneous properties in object B are ignored.
 * Properties order is not important.
 *
 * @param {Object} objectA
 * @param {Object} objectB
 * @return {boolean}
 */
const isSimilar = (objectA, objectB) => {
  for (let prop in objectA) {
    if (!(prop in objectB) || objectA[prop] !== objectB[prop]) {
      return false;
    }
  }

  return true;
};

export default class Foxbox extends Service {
  constructor() {
    super();

    // Private properties.
    this[p.settings] = new Settings();
    this[p.db] = new Db();
    this[p.net] = new Network(this[p.settings], (foxboxOnline) => {
      this._dispatchEvent('box-online', foxboxOnline);
    });
    this[p.boxes] = Object.freeze([]);
    this[p.isPollingEnabled] = false;
    this[p.nextPollTimeout] = null;
    this[p.webPush] = new WebPush(this[p.net], this[p.settings],
      (msg) => {
        this._dispatchEvent('push-action', msg);
      });

    // Public properties.
    this.recipes = null;

    Object.seal(this);
  }

  init() {
    window.foxbox = this;

    // No need to block the UI on the discovery process.
    // Once we discover a box we can connect to, we will start
    // polling and triggering box-online events with a boolean
    // indicating if we have access to box or not.
    this._initDiscovery()
      .then(() => this[p.net].init())
      .then(() => {
        // Start polling.
        this[p.settings].on('pollingEnabled', () => {
          this.togglePolling(this[p.settings].pollingEnabled);
        });
        this.togglePolling(this[p.settings].pollingEnabled);

        this.recipes = new Recipes({
          settings: this[p.settings],
          net: this[p.net],
        });
      });

    return this._initUserSession()
      // The DB is only initialised if there's no redirection to the box.
      .then(() => this[p.db].init());
  }

  /**
   * Clear all data/settings stored on the browser. Use with caution.
   *
   * @param {boolean} ignoreServiceWorker
   * @return {Promise}
   */
  clear(ignoreServiceWorker) {
    const promises = [this[p.settings].clear(), this[p.db].clear()];
    if (!navigator.serviceWorker) {
      return Promise.all(promises);
    }

    if (!ignoreServiceWorker) {
      promises.push(navigator.serviceWorker.ready
        .then((registration) => registration.unregister()));
    }

    return Promise.all(promises);
  }

  get localOrigin() {
    return this[p.settings].localOrigin;
  }

  get clientId() {
    return this[p.settings].clientId;
  }

  get boxes() {
    return this[p.boxes];
  }

  /**
   * Get the URL of the box using the registration server.
   * If it fails, we fallback to the previously set hostname.
   * It there isn't, we schedule a retry.
   *
   * @return {Promise}
   * @private
   */
  _initDiscovery() {
    // For development purposes if you want to skip the
    // discovery phase set the 'foxbox-skipDiscovery' variable to
    // 'true'.
    if (this[p.settings].skipDiscovery) {
      return Promise.resolve();
    }

    return this[p.net].fetchJSON(this[p.settings].registrationService)
      .then((boxes) => {
        if (!Array.isArray(boxes)) {
          console.warn('Got unexpected response from registry server', boxes);
          return;
        }

        // We filter out boxes registered more than 2 minutes ago.
        const now = Math.floor(Date.now() / 1000) - 60 * 2;
        this[p.boxes] = Object.freeze(
          boxes
            .filter((box) => box.timestamp - now >= 0)
            .map((box) => {
                // NOTE(sgiles): There is consideration to allow
                // only "local_origin" and "tunnel_origin", removing the
                // need to parse message - this merges the relevant message
                // fields into the main object
                const { local_origin, tunnel_origin } =
                  JSON.parse(box.message);
                box.local_origin = local_origin;
                box.tunnel_origin = tunnel_origin;
                return Object.freeze(box);
            })
        );

        // If the registration server didn't give us any info and
        // we have no record of previous registrations, we schedule
        // a retry.
        if (!this[p.boxes].length &&
            !this[p.settings]._localOrigin &&
            !this[p.settings]._tunnelOrigin) {
          throw new Error('Registration service did not return any boxes.');
        }

        if (!this[p.settings].configured) {
          this.selectBox();
        }

        this._dispatchEvent('box-online', true);
      })
      .catch((error) => {
        if (this[p.settings]._localOrigin ||
            this[p.settings]._tunnelOrigin) {
          // Default to a previously stored box registration.
          this._dispatchEvent('box-online', true);
        } else {
          // If there's no previously stored box registration, we schedule a
          // retry.
          console.warn('Retrying box discovery... Reason is %o', error);
          return new Promise((resolve) => {
            setTimeout(() => {
              this._initDiscovery().then(resolve, resolve);
            }, 1000);
          });
        }
      });
  }

  /**
   * Change the currently selected box.
   *
   * @param {number} index The index of the box in the boxes array.
   */
  selectBox(index = 0) {
    if (!this[p.boxes].length) {
      this[p.settings].configured = false;
      console.error('No boxes found. Is this app online? Is the box online?');

      return;
    }

    if (index >= this[p.boxes].length) {
      this[p.settings].configured = false;
      console.error('Index out of range.');

      return;
    }

    const box = this[p.boxes][index];

    this[p.settings].localOrigin = box.local_origin;
    if (box.tunnel_origin) {
      this[p.settings].tunnelOrigin = box.tunnel_origin;
    } else {
      this[p.settings].tunnelOrigin = '';
    }

    this[p.settings].clientId = box.client;
    this[p.settings].configured = true;
  }

  /**
   * Detect a session token in the URL and process it if present.
   *
   * @return {Promise}
   * @private
   */
  _initUserSession() {
    if (this.isLoggedIn) {
      return Promise.resolve();
    }

    const queryString = location.search.substring(1);
    const searchParams = new URLSearchParams(queryString);

    if (searchParams.has('session_token')) {
      // There is a session token in the URL, let's remember it.
      // @todo Find a better way to handle URL escape.
      this[p.settings].session = searchParams.get('session_token')
        .replace(/ /g, '+');

      // Remove the session param from the current location.
      searchParams.delete('session_token');
      location.search = searchParams.toString();

      // Throwing here to abort the promise chain.
      throw(new Error('Redirecting to a URL without session'));
    }

    return Promise.resolve();
  }

  get isLoggedIn() {
    return !!this[p.settings].session;
  }

  /**
   * Redirect the user to the box to get authenticated if she isn't already.
   */
  login() {
    if (this.isLoggedIn) {
      return;
    }

    const redirectUrl = encodeURIComponent(location);
    location.replace(`${this[p.net].origin}/?redirect_url=${redirectUrl}`);
  }

  /**
   * Log out the user.
   */
  logout() {
    this[p.settings].session = undefined;
  }

  /**
   * Start or stop polling.
   *
   * @param {boolean} pollingEnabled Flag that indicates whether polling should
   * be started or stopped.
   */
  togglePolling(pollingEnabled) {
    this[p.isPollingEnabled] = pollingEnabled;

    if (pollingEnabled) {
      this.schedulePoll();
    } else {
      // Cancel next poll attempt if it has been scheduled.
      clearTimeout(this[p.nextPollTimeout]);
      this[p.nextPollTimeout] = null;
    }
  }

  /**
   * Schedules an attempt to poll the server, does nothing if polling is not
   * enabled or it has already been scheduled. New poll is scheduled only once
   * previous one is completed or failed.
   */
  schedulePoll() {
    // Return early if polling is not enabled or it has already been scheduled.
    if (!this[p.isPollingEnabled]
      || this[p.nextPollTimeout]
      || !this.isLoggedIn) {
      return;
    }

    this[p.nextPollTimeout] = setTimeout(() => {
      this.refreshServicesByPolling()
        .catch((error) => {
          console.error('Polling has failed, scheduling one more attempt: ',
            error);
        })
        .then(() => {
          this[p.nextPollTimeout] = null;

          this.schedulePoll();
        });
    }, this[p.settings].pollingInterval);
  }

  /**
   * Detect changes in the services:
   * * Emits a `service-change` event if a service is connected/disconnected.
   * * Emits a `service-state-change` event if the state of a service changes.
   *
   * @return {Promise}
   */
  refreshServicesByPolling() {
    if (!this.isLoggedIn) {
      return Promise.resolve();
    }

    /*const fetchedServicesPromise = this[p.net]
      .fetchJSON(`${this[p.net].origin}/services/list`)
      .then((services) => {
        // @todo We should ask for state only for services that actually support
        // it.
        return Promise.all(
          services.map((service) => {
            // Use empty state if service failed to return actual state.
            return this.getServiceState(service.id)
              .catch(() => ({}))
              .then((state) => service.state = state);
          })
        ).then(() => services);
      });*/

    return Promise.all([this.getServices(), this[p.fetchServices]()])
      .then(([storedServices, fetchedServices]) => {
        const hasNewServices = fetchedServices.reduce(
          (hasNewServices, fetchedService) => {
            const storedService = storedServices.find(
              (service) => service.id === fetchedService.id
            );

            const isExistingService = !!storedService;

            if (isExistingService &&
              isSimilar(fetchedService.state, storedService.state)) {
              return hasNewServices;
            }

            fetchedService = isExistingService ?
              Object.assign(storedService, fetchedService) : fetchedService;

            this._dispatchEvent('service-state-change', fetchedService);

            // Populate the db with the latest service.
            this[p.db].setService(fetchedService);

            return hasNewServices || !isExistingService;
          },
          false /* hasNewServices */
        );

        if (hasNewServices ||
          fetchedServices.length !== storedServices.length) {
          // The services changed.
          fetchedServices = fetchedServices.map(
            this[p.getServiceInstance].bind(this)
          );
          this._dispatchEvent('service-change', fetchedServices);
        }

        return fetchedServices;
      });
  }

  /**
   * Retrieve the list of the services available.
   * Use the database as a source of truth.
   *
   * @return {Promise} A promise that resolves with an array of objects.
   */
  getServices() {
    return this[p.db].getServices()
      .then((services) => services.map(
        (service) => this[p.getServiceInstance](service.data)
      ));
  }

  getTags() {
    return this[p.db].getTags.apply(this[p.db], arguments);
  }

  // @todo If service doesn't exist in the DB, fetch it from the box.
  getService() {
    // Get data from the DB so we get the attributes, the state and the tags.
    return this[p.db].getService.apply(this[p.db], arguments)
      .then((service) => this[p.getServiceInstance](service.data));
  }

  setService() {
    return this[p.db].setService.apply(this[p.db], arguments);
  }

  setTag() {
    return this[p.db].setTag.apply(this[p.db], arguments);
  }

  /**
   * Ask the user for accepting push notifications from the box.
   * This method will be call each time that we log in, but will
   * stop the execution if we already have the push subscription
   * information.
   *
   * @param {boolean} resubscribe Parameter used for testing
   * purposes, and follow the whole subscription process even if
   * we have push subscription information.
   * @return {Promise}
   */
  subscribeToNotifications(resubscribe = false) {
    return this[p.webPush].subscribeToNotifications(resubscribe);
  }

  [p.fetchServices]() {
    return this[p.net].fetchJSON(
      `${this[p.net].origin}/api/v${this[p.settings].apiVersion}/services`
    );
  }

  [p.getServiceInstance](data) {
    const config = {
      net: this[p.net],
      settings: this[p.settings],
    };

    switch (data.adapter) {
      case 'ip-camera@link.mozilla.org':
        return new IpCameraService(data, config);

      case 'philips_hue@link.mozilla.org':
        return new LightService(data, config);

      case 'OpenZwave Adapter':
        if (this[p.hasDoorLockChannel](data.getters) ||
            this[p.hasDoorLockChannel](data.setters)) {
          return new DoorLockService(data, config);
        }

        return new MotionSensorService(data, config);

      default:
        return new BaseService(data, config);
    }
  }

  [p.hasDoorLockChannel](channels) {
    return Object.keys(channels).find(
      (key) => channels[key].kind === 'DoorLocked'
    );
  }
}
<commit_msg>Only force the configuration when there is one box discovered. (#125)<commit_after>/* global URLSearchParams */

'use strict';

import { Service } from 'components/mvc';

import Settings from './settings';
import Db from './db';
import Network from './network';
import Recipes from './recipes';
import WebPush from './webpush';

import BaseService from './services/base';
import IpCameraService from './services/ip-camera';
import LightService from './services/light';
import DoorLockService from './services/door-lock';
import MotionSensorService from './services/motion-sensor';

// Private members.
const p = Object.freeze({
  // Private properties.
  settings: Symbol('settings'),
  db: Symbol('db'),
  net: Symbol('net'),
  boxes: Symbol('boxes'),
  isPollingEnabled: Symbol('isPollingEnabled'),
  nextPollTimeout: Symbol('nextPollTimeout'),
  webPush: Symbol('webPush'),

  // Private methods.
  fetchServices: Symbol('fetchServices'),
  getServiceInstance: Symbol('getServiceInstance'),
  hasDoorLockChannel: Symbol('hasDoorLockChannel'),
});

/**
 * Compare 2 objects. Returns true if all properties of object A have the same
 * value in object B. Extraneous properties in object B are ignored.
 * Properties order is not important.
 *
 * @param {Object} objectA
 * @param {Object} objectB
 * @return {boolean}
 */
const isSimilar = (objectA, objectB) => {
  for (let prop in objectA) {
    if (!(prop in objectB) || objectA[prop] !== objectB[prop]) {
      return false;
    }
  }

  return true;
};

export default class Foxbox extends Service {
  constructor() {
    super();

    // Private properties.
    this[p.settings] = new Settings();
    this[p.db] = new Db();
    this[p.net] = new Network(this[p.settings], (foxboxOnline) => {
      this._dispatchEvent('box-online', foxboxOnline);
    });
    this[p.boxes] = Object.freeze([]);
    this[p.isPollingEnabled] = false;
    this[p.nextPollTimeout] = null;
    this[p.webPush] = new WebPush(this[p.net], this[p.settings],
      (msg) => {
        this._dispatchEvent('push-action', msg);
      });

    // Public properties.
    this.recipes = null;

    Object.seal(this);
  }

  init() {
    window.foxbox = this;

    // No need to block the UI on the discovery process.
    // Once we discover a box we can connect to, we will start
    // polling and triggering box-online events with a boolean
    // indicating if we have access to box or not.
    this._initDiscovery()
      .then(() => this[p.net].init())
      .then(() => {
        // Start polling.
        this[p.settings].on('pollingEnabled', () => {
          this.togglePolling(this[p.settings].pollingEnabled);
        });
        this.togglePolling(this[p.settings].pollingEnabled);

        this.recipes = new Recipes({
          settings: this[p.settings],
          net: this[p.net],
        });
      });

    return this._initUserSession()
      // The DB is only initialised if there's no redirection to the box.
      .then(() => this[p.db].init());
  }

  /**
   * Clear all data/settings stored on the browser. Use with caution.
   *
   * @param {boolean} ignoreServiceWorker
   * @return {Promise}
   */
  clear(ignoreServiceWorker) {
    const promises = [this[p.settings].clear(), this[p.db].clear()];
    if (!navigator.serviceWorker) {
      return Promise.all(promises);
    }

    if (!ignoreServiceWorker) {
      promises.push(navigator.serviceWorker.ready
        .then((registration) => registration.unregister()));
    }

    return Promise.all(promises);
  }

  get localOrigin() {
    return this[p.settings].localOrigin;
  }

  get clientId() {
    return this[p.settings].clientId;
  }

  get boxes() {
    return this[p.boxes];
  }

  /**
   * Get the URL of the box using the registration server.
   * If it fails, we fallback to the previously set hostname.
   * It there isn't, we schedule a retry.
   *
   * @return {Promise}
   * @private
   */
  _initDiscovery() {
    // For development purposes if you want to skip the
    // discovery phase set the 'foxbox-skipDiscovery' variable to
    // 'true'.
    if (this[p.settings].skipDiscovery) {
      return Promise.resolve();
    }

    return this[p.net].fetchJSON(this[p.settings].registrationService)
      .then((boxes) => {
        if (!Array.isArray(boxes)) {
          console.warn('Got unexpected response from registry server', boxes);
          return;
        }

        // We filter out boxes registered more than 2 minutes ago.
        const now = Math.floor(Date.now() / 1000) - 60 * 2;
        this[p.boxes] = Object.freeze(
          boxes
            .filter((box) => box.timestamp - now >= 0)
            .map((box) => {
                // NOTE(sgiles): There is consideration to allow
                // only "local_origin" and "tunnel_origin", removing the
                // need to parse message - this merges the relevant message
                // fields into the main object
                const { local_origin, tunnel_origin } =
                  JSON.parse(box.message);
                box.local_origin = local_origin;
                box.tunnel_origin = tunnel_origin;
                return Object.freeze(box);
            })
        );

        // If the registration server didn't give us any info and
        // we have no record of previous registrations, we schedule
        // a retry.
        if (!this[p.boxes].length &&
            !this[p.settings]._localOrigin &&
            !this[p.settings]._tunnelOrigin) {
          throw new Error('Registration service did not return any boxes.');
        }

        if (!this[p.settings].configured && this[p.boxes].length === 1) {
          this.selectBox();
        }

        this._dispatchEvent('box-online', true);
      })
      .catch((error) => {
        if (this[p.settings]._localOrigin ||
            this[p.settings]._tunnelOrigin) {
          // Default to a previously stored box registration.
          this._dispatchEvent('box-online', true);
        } else {
          // If there's no previously stored box registration, we schedule a
          // retry.
          console.warn('Retrying box discovery... Reason is %o', error);
          return new Promise((resolve) => {
            setTimeout(() => {
              this._initDiscovery().then(resolve, resolve);
            }, 1000);
          });
        }
      });
  }

  /**
   * Change the currently selected box.
   *
   * @param {number} index The index of the box in the boxes array.
   */
  selectBox(index = 0) {
    if (!this[p.boxes].length) {
      this[p.settings].configured = false;
      console.error('No boxes found. Is this app online? Is the box online?');

      return;
    }

    if (index >= this[p.boxes].length) {
      this[p.settings].configured = false;
      console.error('Index out of range.');

      return;
    }

    const box = this[p.boxes][index];

    this[p.settings].localOrigin = box.local_origin;
    if (box.tunnel_origin) {
      this[p.settings].tunnelOrigin = box.tunnel_origin;
    } else {
      this[p.settings].tunnelOrigin = '';
    }

    this[p.settings].clientId = box.client;
    this[p.settings].configured = true;
  }

  /**
   * Detect a session token in the URL and process it if present.
   *
   * @return {Promise}
   * @private
   */
  _initUserSession() {
    if (this.isLoggedIn) {
      return Promise.resolve();
    }

    const queryString = location.search.substring(1);
    const searchParams = new URLSearchParams(queryString);

    if (searchParams.has('session_token')) {
      // There is a session token in the URL, let's remember it.
      // @todo Find a better way to handle URL escape.
      this[p.settings].session = searchParams.get('session_token')
        .replace(/ /g, '+');

      // Remove the session param from the current location.
      searchParams.delete('session_token');
      location.search = searchParams.toString();

      // Throwing here to abort the promise chain.
      throw(new Error('Redirecting to a URL without session'));
    }

    return Promise.resolve();
  }

  get isLoggedIn() {
    return !!this[p.settings].session;
  }

  /**
   * Redirect the user to the box to get authenticated if she isn't already.
   */
  login() {
    if (this.isLoggedIn) {
      return;
    }

    const redirectUrl = encodeURIComponent(location);
    location.replace(`${this[p.net].origin}/?redirect_url=${redirectUrl}`);
  }

  /**
   * Log out the user.
   */
  logout() {
    this[p.settings].session = undefined;
  }

  /**
   * Start or stop polling.
   *
   * @param {boolean} pollingEnabled Flag that indicates whether polling should
   * be started or stopped.
   */
  togglePolling(pollingEnabled) {
    this[p.isPollingEnabled] = pollingEnabled;

    if (pollingEnabled) {
      this.schedulePoll();
    } else {
      // Cancel next poll attempt if it has been scheduled.
      clearTimeout(this[p.nextPollTimeout]);
      this[p.nextPollTimeout] = null;
    }
  }

  /**
   * Schedules an attempt to poll the server, does nothing if polling is not
   * enabled or it has already been scheduled. New poll is scheduled only once
   * previous one is completed or failed.
   */
  schedulePoll() {
    // Return early if polling is not enabled or it has already been scheduled.
    if (!this[p.isPollingEnabled]
      || this[p.nextPollTimeout]
      || !this.isLoggedIn) {
      return;
    }

    this[p.nextPollTimeout] = setTimeout(() => {
      this.refreshServicesByPolling()
        .catch((error) => {
          console.error('Polling has failed, scheduling one more attempt: ',
            error);
        })
        .then(() => {
          this[p.nextPollTimeout] = null;

          this.schedulePoll();
        });
    }, this[p.settings].pollingInterval);
  }

  /**
   * Detect changes in the services:
   * * Emits a `service-change` event if a service is connected/disconnected.
   * * Emits a `service-state-change` event if the state of a service changes.
   *
   * @return {Promise}
   */
  refreshServicesByPolling() {
    if (!this.isLoggedIn) {
      return Promise.resolve();
    }

    /*const fetchedServicesPromise = this[p.net]
      .fetchJSON(`${this[p.net].origin}/services/list`)
      .then((services) => {
        // @todo We should ask for state only for services that actually support
        // it.
        return Promise.all(
          services.map((service) => {
            // Use empty state if service failed to return actual state.
            return this.getServiceState(service.id)
              .catch(() => ({}))
              .then((state) => service.state = state);
          })
        ).then(() => services);
      });*/

    return Promise.all([this.getServices(), this[p.fetchServices]()])
      .then(([storedServices, fetchedServices]) => {
        const hasNewServices = fetchedServices.reduce(
          (hasNewServices, fetchedService) => {
            const storedService = storedServices.find(
              (service) => service.id === fetchedService.id
            );

            const isExistingService = !!storedService;

            if (isExistingService &&
              isSimilar(fetchedService.state, storedService.state)) {
              return hasNewServices;
            }

            fetchedService = isExistingService ?
              Object.assign(storedService, fetchedService) : fetchedService;

            this._dispatchEvent('service-state-change', fetchedService);

            // Populate the db with the latest service.
            this[p.db].setService(fetchedService);

            return hasNewServices || !isExistingService;
          },
          false /* hasNewServices */
        );

        if (hasNewServices ||
          fetchedServices.length !== storedServices.length) {
          // The services changed.
          fetchedServices = fetchedServices.map(
            this[p.getServiceInstance].bind(this)
          );
          this._dispatchEvent('service-change', fetchedServices);
        }

        return fetchedServices;
      });
  }

  /**
   * Retrieve the list of the services available.
   * Use the database as a source of truth.
   *
   * @return {Promise} A promise that resolves with an array of objects.
   */
  getServices() {
    return this[p.db].getServices()
      .then((services) => services.map(
        (service) => this[p.getServiceInstance](service.data)
      ));
  }

  getTags() {
    return this[p.db].getTags.apply(this[p.db], arguments);
  }

  // @todo If service doesn't exist in the DB, fetch it from the box.
  getService() {
    // Get data from the DB so we get the attributes, the state and the tags.
    return this[p.db].getService.apply(this[p.db], arguments)
      .then((service) => this[p.getServiceInstance](service.data));
  }

  setService() {
    return this[p.db].setService.apply(this[p.db], arguments);
  }

  setTag() {
    return this[p.db].setTag.apply(this[p.db], arguments);
  }

  /**
   * Ask the user for accepting push notifications from the box.
   * This method will be call each time that we log in, but will
   * stop the execution if we already have the push subscription
   * information.
   *
   * @param {boolean} resubscribe Parameter used for testing
   * purposes, and follow the whole subscription process even if
   * we have push subscription information.
   * @return {Promise}
   */
  subscribeToNotifications(resubscribe = false) {
    return this[p.webPush].subscribeToNotifications(resubscribe);
  }

  [p.fetchServices]() {
    return this[p.net].fetchJSON(
      `${this[p.net].origin}/api/v${this[p.settings].apiVersion}/services`
    );
  }

  [p.getServiceInstance](data) {
    const config = {
      net: this[p.net],
      settings: this[p.settings],
    };

    switch (data.adapter) {
      case 'ip-camera@link.mozilla.org':
        return new IpCameraService(data, config);

      case 'philips_hue@link.mozilla.org':
        return new LightService(data, config);

      case 'OpenZwave Adapter':
        if (this[p.hasDoorLockChannel](data.getters) ||
            this[p.hasDoorLockChannel](data.setters)) {
          return new DoorLockService(data, config);
        }

        return new MotionSensorService(data, config);

      default:
        return new BaseService(data, config);
    }
  }

  [p.hasDoorLockChannel](channels) {
    return Object.keys(channels).find(
      (key) => channels[key].kind === 'DoorLocked'
    );
  }
}
<|endoftext|>


<commit_before>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor()
    {
        console.log("Init EAL");
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    //Gets extension settings and then converts addresses to links
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1 ? true : false);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            if(true || this.intSettingsCount === this.intSettingsTotalCount) {
                if(this.blBlacklistDomains) {
                    this.blacklistedDomainCheck();
                }
                this.convertAddressToLink();
            }
        }.bind(this), 10)
    }

    //Finds Ethereum addresses and converts to a link to a block explorer
    convertAddressToLink()
    {
        //Put the blockchain explorer into a so we can parse it.
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;

        var arrWhitelistedTags = new Array("code", "span", "p", "td", "li", "em", "i", "b", "strong", "small");
        var strRegex = /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(?:\s|$)/gi;

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {
                var strContent = objNodes[x].innerHTML;
                if( /((?:0x)[0-9a-fA-F]{40})/gi.exec(strContent) !== null) {
                    //If we are on our favourite blockchain explorer, don't target blank.
                    objNodes[x].innerHTML = strContent.replace(
                        new RegExp(strRegex, "gi"),
                        '$1$2'
                    );
                }
            }
        }

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup();
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<commit_msg>Added ENS address highlighting and linking<commit_after>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor()
    {
        console.log("Init EAL");

        this.ethereumAddressRegex = new RegExp(/(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(?:\s|$)/gi, "gi");
        this.ENSAddressRegex = new RegExp(/(^|\s|:|-)(\S+(?:\.eth))(?:\s|$)/gi, "gi");

        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    //Gets extension settings and then converts addresses to links
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1 ? true : false);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            if(true || this.intSettingsCount === this.intSettingsTotalCount) {
                if(this.blBlacklistDomains) {
                    this.blacklistedDomainCheck();
                }
                this.convertAddressToLink();
            }
        }.bind(this), 10)
    }

    //Finds Ethereum addresses and converts to a link to a block explorer
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {

                this.convertEthereumAddress(objNodes, x);
                this.convertENSAddress(objNodes, x);

            }
        }

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    convertEthereumAddress(objNodes, index)
    {
        this.convertAddress(objNodes, index, /((?:0x)[0-9a-fA-F]{40})/gi, this.ethereumAddressRegex);
    }

    convertENSAddress(objNodes, index)
    {
        this.convertAddress(objNodes, index, this.ENSAddressRegex, this.ENSAddressRegex);
    }

    convertAddress(objNodes, index, checkRegex, replaceRegex)
    {
        //Put the blockchain explorer into a so we can parse it.
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;

        var strContent = objNodes[index].innerHTML;
        if( checkRegex.exec(strContent) !== null) {
            objNodes[index].innerHTML = strContent.replace(
                replaceRegex,
                '$1$2'
            );
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup();
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<|endoftext|>


<commit_before>$p.get = function ($control) {
    if (!$p.confirmReload()) return false;
    if ($p.outsideDialog($control)) return false;
    switch ($control.attr('id')) {
        case 'Reload': move(0); break;
        case 'Previous': move(-1); break;
        case 'Next': move(1); break;
    }

    function move(additional) {
        var array = $p.switchTargets();
        var index = $p.currentIndex(array);
        var target = index + additional;
        if (index != -1 && target >= 0 && target < array.length) {
            var url = $('#BaseUrl').val() + array[target];
            $p.ajax(url, 'post', null, null, false);
            if (additional !== 0) history.pushState(null, null, url);
        }
    }
}

$p.new = function ($control) {
    location.href = $('#BaseUrl').val() +
        $control.attr('data-to-site-id') + '/new' +
        '?FromSiteId=' + $control.attr('data-from-site-id') +
        '&LinkId=' + $control.attr('data-id');
}

$p.create = function ($control) {
    $p.syncSend($control);
    history.replaceState(null, null, $('#BaseUrl').val() + $('#Id').val());
}

$p.copy = function ($control) {
    var error = $p.syncSend($control);
    if (error === 0) {
        history.pushState(null, null, $('#BaseUrl').val() + $('#Id').val());
    }
}

$p.search = function (searchWord, redirect, offset) {
    offset = offset !== undefined ? offset : 0;
    if ($p.searchWord !== searchWord + offset) {
        var url = $('#ApplicationPath').val() +
            'items/search?text=' + escape(searchWord)
        if (offset > 0) url += '&offset=' + offset;
        if (redirect) {
            location.href = url;
        } else if (offset !== '-1') {
            $p.ajax(url, 'get', false);
            if (offset === 0) history.pushState(null, null, url);
        }
        $p.searchWord = searchWord + offset;
    }
}<commit_msg>アイテムの作成後にページのトップにスクロールする<commit_after>$p.get = function ($control) {
    if (!$p.confirmReload()) return false;
    if ($p.outsideDialog($control)) return false;
    switch ($control.attr('id')) {
        case 'Reload': move(0); break;
        case 'Previous': move(-1); break;
        case 'Next': move(1); break;
    }

    function move(additional) {
        var array = $p.switchTargets();
        var index = $p.currentIndex(array);
        var target = index + additional;
        if (index != -1 && target >= 0 && target < array.length) {
            var url = $('#BaseUrl').val() + array[target];
            $p.ajax(url, 'post', null, null, false);
            if (additional !== 0) history.pushState(null, null, url);
        }
    }
}

$p.new = function ($control) {
    location.href = $('#BaseUrl').val() +
        $control.attr('data-to-site-id') + '/new' +
        '?FromSiteId=' + $control.attr('data-from-site-id') +
        '&LinkId=' + $control.attr('data-id');
}

$p.create = function ($control) {
    $p.syncSend($control);
    history.replaceState(null, null, $('#BaseUrl').val() + $('#Id').val());
    $('body,html').animate({scrollTop: 0}, 500);
}

$p.copy = function ($control) {
    var error = $p.syncSend($control);
    if (error === 0) {
        history.pushState(null, null, $('#BaseUrl').val() + $('#Id').val());
    }
}

$p.search = function (searchWord, redirect, offset) {
    offset = offset !== undefined ? offset : 0;
    if ($p.searchWord !== searchWord + offset) {
        var url = $('#ApplicationPath').val() +
            'items/search?text=' + escape(searchWord)
        if (offset > 0) url += '&offset=' + offset;
        if (redirect) {
            location.href = url;
        } else if (offset !== '-1') {
            $p.ajax(url, 'get', false);
            if (offset === 0) history.pushState(null, null, url);
        }
        $p.searchWord = searchWord + offset;
    }
}<|endoftext|>


<commit_before>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `getInput()`.
    return var move;
    move = move || getInput();
}

function getComputerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `randomPlay()`.
    return var move;
    move = move || randomPlay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.
    /* YOUR CODE HERE */
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<commit_msg>added control flow to determine which value is returned<commit_after>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `getInput()`.
    return var move;
    move = move || getInput();
}

function getComputerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `randomPlay()`.
    return var move;
    move = move || randomPlay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.

    if (playerMove === computerMove) {
        winner = 'tie';
    } else if (playerMove === 'rock' && computerMove === 'scissors') {
        winner = 'player';
    } else if (playerMove === 'paper' && computerMove === 'rock') {
        winner = 'player';
    } else if (playerMove === 'scissors' && computerMove === 'paper') {
        winner = 'player';
    } else if (playerMove === 'rock' && computerMove === 'paper') {
        winner = 'computer';
    } else if (playerMove === 'paper' && computerMove === 'scissors') {
        winner = 'computer';
    } else if (playerMove === 'scissors' && computerMove === 'rock') {
        winner = 'computer';
    }
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<|endoftext|>


<commit_before>module.exports = function (grunt) {
    "use strict";

    grunt.initConfig({
        banner:
            '/*!\n' +
            ' * Lightbox for Bootstrap by @ashleydw\n' +
            ' * https://github.com/ashleydw/lightbox\n' +
            ' *\n' +
            ' * License: https://github.com/ashleydw/lightbox/blob/master/LICENSE\n' +
            ' */',

	    less: {
		    dist: {
			    files: {
				    'dist/ekko-lightbox.css': 'ekko-lightbox.less'
			    }
		    }
	    },
	    babel: {
		    options: {
			    sourceMap: true,
			    modules: 'ignore'
		    },
		    dist: {
			    files: {
				    'dist/ekko-lightbox.js': 'ekko-lightbox.js',
			    }
		    }
	    },
        uglify: {
	        options: {
		        sourceMap: true,
	        },
            js: {
                files: {
                    'dist/ekko-lightbox.min.js': 'dist/ekko-lightbox.js'
                }
            }
        },
	    postcss: {
		    options: {
			    map: true,
			    processors: [
				    require('autoprefixer')({
					    browsers: ['last 2 versions']
				    }),
					require('cssnano')()
			    ]
		    },
		    dist: {
			    src: 'dist/*.css'
		    }
	    },
	    stamp: {
		    options: {
			    banner: '<%= banner %>\n+function ($) {\n',
			    footer: '\n}(jQuery);'
		    },
		    lightbox: {
			    files: {
				    src: ['dist/ekko-lightbox.js', 'dist/ekko-lightbox.min.js']
			    }
		    }
	    },
        watch: {
            babel: {
                files: ['ekko-lightbox.js', 'ekko-lightbox.less'],
                tasks: ['dev']
            }
        }
    });

	grunt.loadNpmTasks('grunt-stamp');
    grunt.loadNpmTasks('grunt-babel');
    grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-contrib-less');
	grunt.loadNpmTasks('grunt-postcss');

    grunt.registerTask('dev', ['babel', 'less']);
    grunt.registerTask('dist', ['babel', 'less', 'stamp', 'postcss:dist', 'uglify']);
    grunt.registerTask('default', ['dist']);
};<commit_msg>Delete Gruntfile.js<commit_after><|endoftext|>


<commit_before>import Drawable from './Drawable';
import Deferred from '../Util/Deferred';

/**
 * Very basic wrapper for canvas drawing methods
 * Incomplete: missing translate, rotates, scale support
 *
 * @extends Drawable
 */
class Canvas extends Drawable {
    /**
     * Creates a new Canvas instance
     *
     * @param {String} name The name of the Canvas element.
     * @param {Object} options
     * @param {Number} [options.width] The width of the Canvas element.
     * @param {Number} [options.height] The height of the Canvas element.
     * @param {Number} [options.x] The horizontal position of the element.
     * @param {Number} [options.y] The vertical position of the element.
     * @param {String} [options.color] The color of the element. Can be changed by subsequent drawing method calls.
     *
     */
    constructor(name, options) {
        super(name, options);

        this.width = options.width || 0;
        this.height = options.height || 0;
        this.x = options.x || 0;
        this.y = options.y || 0;
        this.color = options.color || 'red';

    }

    /**
     * Canvas's draw method that's called on every frame
     *
     * @param {RenderingContext} ctx The rendering context to use for drawing.
     */
    draw(ctx/*, debug*/) {
        this.ctx = ctx;

        ctx.setTransform(1, 0, 0, 1, 0, 0);

        this._applyMask(ctx, this.x, this.y);

        if (!this.isFxQueueEmpty()) {
            this.executeFx(ctx);
        }

        this.render();

        this._undoMask();
    }

    /**
     * Renders the Canvas.
     *
     * User should redefine this and put there needed drawing calls
     */
    render() {

    }

    /**
     * Fills the Canvas with specified color
     *
     * @param {String} [color=this.color] The color to used for filling the canvas.
     */
    fill(color) {
        this.ctx.fillStyle = color || this.color;
        this.ctx.fillRect(this.x, this.y, this.width, this.height);
    }

    /**
     * Draws a rectangle
     *
     * @param {Number} x The rect's x related to the Canvas'x position.
     * @param {Number} y The rect's y related to the Canvas'x position.
     * @param {Number} width The width of the rectangle.
     * @param {Number} height The height of the rectangle.
     * @param {String} [color=this.color] The color of the rectangle.
     */
    rect(x, y, width, height, color) {
        const ctx = this.ctx;

        ctx.fillStyle = color || this.color;
        ctx.fillRect(this.x + x, this.y + y, width, height);
    }

    /**
     * Draws a circle
     *
     * @param {Number} x The circle's center x related to the Canvas'position.
     * @param {Number} y The circle's center y related to the Canvas'position.
     * @param {Number} radius The circle's radius in radian.
     * @param {String} color The circle's color.
     * @param {Number} strokeWidth The circle's line width.
     * @param {String} strokeStyle The circle's strokeStyle.
     */
    circle(x, y, radius, color, strokeWidth, strokeStyle) {
        const ctx = this.ctx;

        ctx.beginPath();
        ctx.arc(this.x + x + radius, this.y + y + radius, radius, 0, 2 * Math.PI, false);
        ctx.fillStyle = color || this.color;
        ctx.fill();

        if (arguments.length > 4) {
            ctx.strokeStyle = strokeStyle || this.color;
            ctx.lineWidth = strokeWidth;
            ctx.stroke();
        }
    }

    /**
     * Draws an arc
     *
     * @param {Number} x The arc's center x position, related to the canvas'position.
     * @param {Number} y The arc's center y position, related to the canvas'position.
     * @param {Number} radius The arc's radius, in radian.
     * @param {Number} startAngle The arc's start angle, in radian.
     * @param {Number} endAngle The arc's send angle, in radian.
     * @param {String} strokeStyle The arc's stroke style.
     * @param {Number} strokeWidth The arc's line width.
     */
    arc(x, y, radius, startAngle, endAngle, strokeStyle, strokeWidth) {
        const ctx = this.ctx;

        ctx.beginPath();
        ctx.arc(this.x + x, this.y + y, radius, startAngle, endAngle, false);
        ctx.lineWidth = strokeWidth;
        ctx.strokeStyle = strokeStyle || this.color;
        ctx.stroke();
    }

    /**
     * Since the Canvas Drawable only supports the Fade effect, we override
     * the Drawable's animate method and print a warning in case the user
     * attempts to run an unsupported animation.
     *
     * @param {String} name The name of the animation to run.
     * @param {Object} options
     */
    animate(name, options) {
        if (name !== 'Fade') {
            console.warn('animte() not supported on Canvas objects yet. Effect not applied.');
            return Deferred.resolve(true);
        } else {
            return super.animate(name, options);
        }
    }
}

window.Canvas = Canvas;

export default Canvas;<commit_msg>CLEANED-UP: Canvas -> Paint (jsDoc)<commit_after>import Drawable from './Drawable';
import Deferred from '../Util/Deferred';

/**
 * Very basic wrapper for canvas drawing methods
 * Incomplete: missing translate, rotates, scale support
 *
 * @extends Drawable
 */
class Paint extends Drawable {
    /**
     * Creates a new Paint instance
     *
     * @param {String} name The name of the Paint element.
     * @param {Object} options
     * @param {Number} [options.width] The width of the Paint element.
     * @param {Number} [options.height] The height of the Paint element.
     * @param {Number} [options.x] The horizontal position of the element.
     * @param {Number} [options.y] The vertical position of the element.
     * @param {String} [options.color] The color of the element. Can be changed by subsequent drawing method calls.
     *
     */
    constructor(name, options) {
        super(name, options);

        this.width = options.width || 0;
        this.height = options.height || 0;
        this.x = options.x || 0;
        this.y = options.y || 0;
        this.color = options.color || 'red';

    }

    /**
     * Paint's draw method that's called on every frame:
     * that's where the magic happens, it simply calls user-defined
     * render method and translate calls into native canvas calls
     *
     * @param {RenderingContext} ctx The rendering context to use for drawing.
     *
     * @private
     */
    draw(ctx/*, debug*/) {
        this.ctx = ctx;

        ctx.setTransform(1, 0, 0, 1, 0, 0);

        this._applyMask(ctx, this.x, this.y);

        if (!this.isFxQueueEmpty()) {
            this.executeFx(ctx);
        }

        this.render();

        this._undoMask();
    }

    /**
     * The render method is called at each frame.
     *
     * User should redefine this and put there needed drawing calls
     */
    render() {

    }

    /**
     * Fills the Paint with specified color
     *
     * @param {String} [color=this.color] The color to used for filling the Paint.
     */
    fill(color) {
        this.ctx.fillStyle = color || this.color;
        this.ctx.fillRect(this.x, this.y, this.width, this.height);
    }

    /**
     * Draws a rectangle
     *
     * @param {Number} x The rect's x related to the Paint'x position.
     * @param {Number} y The rect's y related to the Paint'x position.
     * @param {Number} width The width of the rectangle.
     * @param {Number} height The height of the rectangle.
     * @param {String} [color=this.color] The color of the rectangle.
     */
    rect(x, y, width, height, color) {
        const ctx = this.ctx;

        ctx.fillStyle = color || this.color;
        ctx.fillRect(this.x + x, this.y + y, width, height);
    }

    /**
     * Draws a circle
     *
     * @param {Number} x The circle's center x related to the Paint's position.
     * @param {Number} y The circle's center y related to the Paint's position.
     * @param {Number} radius The circle's radius in radian.
     * @param {String} color The circle's color.
     * @param {Number} strokeWidth The circle's line width.
     * @param {String} strokeStyle The circle's strokeStyle.
     */
    circle(x, y, radius, color, strokeWidth, strokeStyle) {
        const ctx = this.ctx;

        ctx.beginPath();
        ctx.arc(this.x + x + radius, this.y + y + radius, radius, 0, 2 * Math.PI, false);
        ctx.fillStyle = color || this.color;
        ctx.fill();

        if (arguments.length > 4) {
            ctx.strokeStyle = strokeStyle || this.color;
            ctx.lineWidth = strokeWidth;
            ctx.stroke();
        }
    }

    /**
     * Draws an arc
     *
     * @param {Number} x The arc's center x position, related to the Paint's position.
     * @param {Number} y The arc's center y position, related to the Paint's position.
     * @param {Number} radius The arc's radius, in radian.
     * @param {Number} startAngle The arc's start angle, in radian.
     * @param {Number} endAngle The arc's send angle, in radian.
     * @param {String} strokeStyle The arc's stroke style.
     * @param {Number} strokeWidth The arc's line width.
     */
    arc(x, y, radius, startAngle, endAngle, strokeStyle, strokeWidth) {
        const ctx = this.ctx;

        ctx.beginPath();
        ctx.arc(this.x + x, this.y + y, radius, startAngle, endAngle, false);
        ctx.lineWidth = strokeWidth;
        ctx.strokeStyle = strokeStyle || this.color;
        ctx.stroke();
    }

    /**
     * Since the Paint Drawable only supports the Fade effect, we override
     * the Drawable's animate method and print a warning in case the user
     * attempts to run an unsupported animation.
     *
     * @param {String} name The name of the animation to run.
     * @param {Object} options
     */
    animate(name, options) {
        if (name !== 'Fade') {
            console.warn('animte() not supported on Paint objects yet. Effect not applied.');
            return Deferred.resolve(true);
        } else {
            return super.animate(name, options);
        }
    }
}

export default Paint;<|endoftext|>


<commit_before>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit - cheeky checky changey  */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `getInput()`.
    return move || getInput();
}

function getComputerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `randomPlay()`.
    return move || randomPlay();

}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.
    /* YOUR CODE HERE */
    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<commit_msg>some more changes, exercise chapter 4<commit_after>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit - cheeky checky changey  */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.")
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `getInput()`.
    return move || getInput();
}

function getComputerMove(move) {
    // Write an expression that operates on a variable called `move`
    // If a `move` has a value, your expression should evaluate to that value.
    // However, if `move` is not specified / is null, your expression should equal `randomPlay()`.
    return move || randomPlay();

}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.
    /* YOUR CODE HERE */
    if ((playerMove == "rock" && computerMove == "scissors") || playerMove == "scissors" && computerMove == "paper") || playerMove == "paper" && computerMove == "rock")) {
    winner = "player";
    }
    else if ((playerMove == "rock" && computerMove == "rock") || playerMove == "scissors" && computerMove == "scissors") || playerMove == "paper" && computerMove == "paper")) {
    winner = "tie";
    }
    else {
        winner = "computer";
    }

    return winner;
}

function playToFive() {
    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    // Write code that plays 'Rock, Paper, Scissors' until either the player or the computer has won five times.
    /* YOUR CODE HERE */
    return [playerWins, computerWins];
}

<|endoftext|>


<commit_before>import React from 'react'

var SettingsGlobalHandler = React.createClass({
	render: function()
	{
		return (

Inställningar

Diverse inställningar

		);
	},
});

module.exports = { SettingsGlobalHandler }<commit_msg>Some information about loaded modules in settings<commit_after>import React from 'react'

var SettingsGlobalHandler = React.createClass({
	render: function()
	{
		return (

Inställningar

Diverse inställningar

Aktiverade moduler

 Medlemshantering

 Grupper

 Nycklar

 Försäljning

 Ekonomi

 Utskick

 Statistik

		);
	},
});

module.exports = SettingsGlobalHandler<|endoftext|>


<commit_before>"use strict";
define(function(require) {

	// Imports
  var AsyncCleaners = require('AsyncCleaners');

	// The static class
  var InfiniteScroll = {};

  // Class variables
  var results;
  var AsyncCleaners;
  var earliestShown;

  // Functions
  var showEarlierNode;
  var filterPanel;

  // DOM nodes
  var resultsContainer = document.getElementById("results_container");

  // Initialization. Should be repeatable.
  InfiniteScroll.initialize = function(results_arr, fpanel) {
  	results = results_arr;
  	filterPanel = fpanel;

  	earliestShown = new Date();
    earliestShown.setHours(0,0,0,0);
  	AsyncCleaners.initialize(results, InfiniteScroll.isHiddenByFilters);
  }

  InfiniteScroll.isHiddenByFilters = function(result) {
    var all_checkboxes = filterPanel.getAllCheckboxes();
    for (var checkbox of all_checkboxes) {
      if (!checkbox.checked && checkbox.testing_function(result)) {
        return true;
      }
    }
    return false;
  }

  //================================================================================================
  // Event handling functions

  function showEarlierEvent(event) {
    showEarlierNode(event.currentTarget);
  }

  function showLaterEvent(event) {
    showLaterNode(event.currentTarget);
  }

  function showEarlierNode(node) {
    node.spin();
    setTimeout(function() {
      showEarlierAsync();
      node.stop();
    }, 0);
  }

  function showLaterNode(node) {
    console.log("show later");
    node.spin();
    var numToReveal = 50;
    setTimeout(function() {
      InfiniteScroll.updateResults(numToReveal);
      node.stop();
    }, 0);
  }
  //================================================================================================
  // Show Earlier Async
  function showEarlierAsync() {
    var numToReveal = 10; // TODO: one month's worth?
    var newly_revealed = 0;
    var previousElement = false; // temporally before, but lower in display order

    for (var i=results.length-1; i>=0; i--) {
      var result = results[i];

      var earlySection = result.end_date < earliestShown;
      var showable = !InfiniteScroll.isHiddenByFilters(result);
      var show_more = newly_revealed < numToReveal;
      var hidden = !result.is_visible();

      if (earlySection && showable && hidden && show_more) {
        if (!result.html_element) {
          var resNode = result.getOrCreateElement();
          insertBeforeSibling(resultsContainer, previousElement, resNode);
        }
        result.set_visible(true);
        newly_revealed++;
      }

      // end-of-loop bookkeeping
      if (result.html_element) {
        previousElement = result.html_element;
      }
    }

    // post-loop bookkeeping
    AsyncCleaners.cleanUp();

    // TODO: move to HTML util class
    function insertBeforeSibling(parent, sibling, newNode) {
      if (sibling) {
        parent.insertBefore(newNode, sibling);
      } else {
        parent.insertBefore(newNode, parent.firstChild);
      }
    }
  }

  //================================================================================================
  // Show Earlier Async

  // After a filter has been toggled, update results (change visibility of Milestone elts + headers)
  InfiniteScroll.updateResults = function(numToReveal = 0) {
    var all_checkboxes = filterPanel.getAllCheckboxes();
    var visible_count = 0; // number of user-visible results
    var found_count = 0; // max the user could see with these filter options
    var newly_revealed = 0;

    var minResultsShown = 50;
    var earlierExist = false;
    var laterExist = false;
    var previousElement = false;

    for (var result of results) {
      var filterVisible = !InfiniteScroll.isHiddenByFilters(result);
      var afterStart = result.end_date >= earliestShown;
      var showMore = visible_count < minResultsShown;
      var revealMore = newly_revealed < numToReveal;

      if (filterVisible && afterStart && (showMore || revealMore)) {
        if (!result.is_visible()) {
          newly_revealed ++;
        }
        if (!result.html_element) {
          var resNode = result.getOrCreateElement();
          insertAfterChild(resultsContainer, previousElement, result.html_element);
        }
        visible_count++;
      }

      // end-of-loop bookkeeping
      result.set_visible(filterVisible);
      earlierExist = earlierExist || (filterVisible && !afterStart);
      laterExist = laterExist || (filterVisible && !showMore);
      if (filterVisible) found_count ++;
      if (result.html_element) {
        previousElement = result.html_element;
      }

      // Helper subfunctions
      // TODO: defined in 2 places. consolidate, move to a util class
      function insertAfterChild(parent, sibling, newNode) {
        if (sibling) {
          parent.insertBefore(newNode, sibling.nextSibling);
        } else {
          parent.appendChild(newNode);
        }
      }
    }

    // post-loop bookkeeping
    document.getElementById("earlier_results").style.display = earlierExist ? 'block' : 'none';
    document.getElementById("later_results").style.display = laterExist ? 'block' : 'none';
    AsyncCleaners.cleanUp();
  }

  //================================================================================================
  // Run before initialization
  InfiniteScroll.wireUpInfiniteScroll = function() {
    var showEarlier = document.createElement("div");
    showEarlier.id = 'earlier_results';
    showEarlier.style.display = 'none';
    showEarlier.onclick = showEarlierEvent;
    addLabelAndSpinner(showEarlier, "show earlier results");
    resultsContainer.parentNode.insertBefore(showEarlier, resultsContainer);

    var showLater = document.createElement("div");
    showLater.id = 'later_results';
    showLater.style.display = 'none';
    showLater.onclick = showLaterEvent;
    addLabelAndSpinner(showLater, "show more results");
    resultsContainer.parentNode.appendChild(showLater);

    function isVisible(elem) {
      var windowBottom = window.scrollY + window.innerHeight;
      var margin = 600;
      var eltPos = elem.offsetTop - margin;
      return windowBottom > eltPos;
    }

    window.onscroll = function() {
      if (isVisible(showLater)) {
        showLaterNode(showLater);
      }
    }
  }

  function addLabelAndSpinner(elt, labelText) {
    var label = document.createElement("p");
    label.textContent = labelText;
    elt.appendChild(label);

    var spinner = getSpinner();
    elt.appendChild(spinner);

    elt.spin = function() {
      label.style.display = "none";
      spinner.style.display = "block";
    }

    elt.stop = function() {
      label.style.display = "block";
      spinner.style.display = "none";
    }

    elt.stop();
  }

  function getSpinner() {
    var spin = document.createElement("div");
    spin.className = "sk-circle";
    for (var i=1; i<=12; i++) {
      var dot = document.createElement("div");
      dot.className = "sk-child sk-circle" + i;
      spin.appendChild(dot);
    }
    return spin;
  }

  return InfiniteScroll;
});<commit_msg>move insertAfterChild out of loop<commit_after>"use strict";
define(function(require) {

	// Imports
  var AsyncCleaners = require('AsyncCleaners');

	// The static class
  var InfiniteScroll = {};

  // Class variables
  var results;
  var AsyncCleaners;
  var earliestShown;

  // Functions
  var showEarlierNode;
  var filterPanel;

  // DOM nodes
  var resultsContainer = document.getElementById("results_container");

  // Initialization. Should be repeatable.
  InfiniteScroll.initialize = function(results_arr, fpanel) {
  	results = results_arr;
  	filterPanel = fpanel;

  	earliestShown = new Date();
    earliestShown.setHours(0,0,0,0);
  	AsyncCleaners.initialize(results, InfiniteScroll.isHiddenByFilters);
  }

  InfiniteScroll.isHiddenByFilters = function(result) {
    var all_checkboxes = filterPanel.getAllCheckboxes();
    for (var checkbox of all_checkboxes) {
      if (!checkbox.checked && checkbox.testing_function(result)) {
        return true;
      }
    }
    return false;
  }

  //================================================================================================
  // Event handling functions

  function showEarlierEvent(event) {
    showEarlierNode(event.currentTarget);
  }

  function showLaterEvent(event) {
    showLaterNode(event.currentTarget);
  }

  function showEarlierNode(node) {
    node.spin();
    setTimeout(function() {
      showEarlierAsync();
      node.stop();
    }, 0);
  }

  function showLaterNode(node) {
    console.log("show later");
    node.spin();
    var numToReveal = 50;
    setTimeout(function() {
      InfiniteScroll.updateResults(numToReveal);
      node.stop();
    }, 0);
  }
  //================================================================================================
  // Show Earlier Async
  function showEarlierAsync() {
    var numToReveal = 10; // TODO: one month's worth?
    var newly_revealed = 0;
    var previousElement = false; // temporally before, but lower in display order

    for (var i=results.length-1; i>=0; i--) {
      var result = results[i];

      var earlySection = result.end_date < earliestShown;
      var showable = !InfiniteScroll.isHiddenByFilters(result);
      var show_more = newly_revealed < numToReveal;
      var hidden = !result.is_visible();

      if (earlySection && showable && hidden && show_more) {
        if (!result.html_element) {
          var resNode = result.getOrCreateElement();
          insertBeforeSibling(resultsContainer, previousElement, resNode);
        }
        result.set_visible(true);
        newly_revealed++;
      }

      // end-of-loop bookkeeping
      if (result.html_element) {
        previousElement = result.html_element;
      }
    }

    // post-loop bookkeeping
    AsyncCleaners.cleanUp();

    // TODO: move to HTML util class
    function insertBeforeSibling(parent, sibling, newNode) {
      if (sibling) {
        parent.insertBefore(newNode, sibling);
      } else {
        parent.insertBefore(newNode, parent.firstChild);
      }
    }
  }

  //================================================================================================
  // Show Earlier Async

  // After a filter has been toggled, update results (change visibility of Milestone elts + headers)
  InfiniteScroll.updateResults = function(numToReveal = 0) {
    var all_checkboxes = filterPanel.getAllCheckboxes();
    var visible_count = 0; // number of user-visible results
    var found_count = 0; // max the user could see with these filter options
    var newly_revealed = 0;

    var minResultsShown = 50;
    var earlierExist = false;
    var laterExist = false;
    var previousElement = false;

    for (var result of results) {
      var filterVisible = !InfiniteScroll.isHiddenByFilters(result);
      var afterStart = result.end_date >= earliestShown;
      var showMore = visible_count < minResultsShown;
      var revealMore = newly_revealed < numToReveal;

      if (filterVisible && afterStart && (showMore || revealMore)) {
        if (!result.is_visible()) {
          newly_revealed ++;
        }
        if (!result.html_element) {
          var resNode = result.getOrCreateElement();
          insertAfterChild(resultsContainer, previousElement, result.html_element);
        }
        visible_count++;
      }

      // end-of-loop bookkeeping
      result.set_visible(filterVisible);
      earlierExist = earlierExist || (filterVisible && !afterStart);
      laterExist = laterExist || (filterVisible && !showMore);
      if (filterVisible) found_count ++;
      if (result.html_element) {
        previousElement = result.html_element;
      }
    }

    // TODO: defined in 2 places. consolidate, move to a util class
    function insertAfterChild(parent, sibling, newNode) {
      if (sibling) {
        parent.insertBefore(newNode, sibling.nextSibling);
      } else {
        parent.appendChild(newNode);
      }
    }

    // post-loop bookkeeping
    document.getElementById("earlier_results").style.display = earlierExist ? 'block' : 'none';
    document.getElementById("later_results").style.display = laterExist ? 'block' : 'none';
    AsyncCleaners.cleanUp();
  }

  //================================================================================================
  // Run before initialization
  InfiniteScroll.wireUpInfiniteScroll = function() {
    var showEarlier = document.createElement("div");
    showEarlier.id = 'earlier_results';
    showEarlier.style.display = 'none';
    showEarlier.onclick = showEarlierEvent;
    addLabelAndSpinner(showEarlier, "show earlier results");
    resultsContainer.parentNode.insertBefore(showEarlier, resultsContainer);

    var showLater = document.createElement("div");
    showLater.id = 'later_results';
    showLater.style.display = 'none';
    showLater.onclick = showLaterEvent;
    addLabelAndSpinner(showLater, "show more results");
    resultsContainer.parentNode.appendChild(showLater);

    function isVisible(elem) {
      var windowBottom = window.scrollY + window.innerHeight;
      var margin = 600;
      var eltPos = elem.offsetTop - margin;
      return windowBottom > eltPos;
    }

    window.onscroll = function() {
      if (isVisible(showLater)) {
        showLaterNode(showLater);
      }
    }
  }

  function addLabelAndSpinner(elt, labelText) {
    var label = document.createElement("p");
    label.textContent = labelText;
    elt.appendChild(label);

    var spinner = getSpinner();
    elt.appendChild(spinner);

    elt.spin = function() {
      label.style.display = "none";
      spinner.style.display = "block";
    }

    elt.stop = function() {
      label.style.display = "block";
      spinner.style.display = "none";
    }

    elt.stop();
  }

  function getSpinner() {
    var spin = document.createElement("div");
    spin.className = "sk-circle";
    for (var i=1; i<=12; i++) {
      var dot = document.createElement("div");
      dot.className = "sk-child sk-circle" + i;
      spin.appendChild(dot);
    }
    return spin;
  }

  return InfiniteScroll;
});<|endoftext|>


<commit_before>(function () {
    angular
            .module('references')
            .controller('ReferenceBrowsingController', ReferenceBrowsingController);

    ReferenceBrowsingController.$inject = [
        'ReferencesService',
        '$route',
        'researchEntity'
    ];

    function ReferenceBrowsingController(ReferencesService, $route, researchEntity) {
        var vm = this;

        vm.researchEntity = researchEntity;
        vm.deleteReference = deleteReference;
        vm.verifyReference = verifyReference;
        vm.createNewUrl = (vm.researchEntity.getType ==='user') ? "/users/" + researchEntity.id + "/references/new" : "/groups/" + researchEntity.id + "/references/new";
        vm.editUrl = (vm.researchEntity.getType ==='user') ? '#/users/' + researchEntity.id + '/edit' : '#/groups/' + researchEntity.id + '/edit';

        activate();

        function activate() {
            return getReferences().then(function () {

            });
        }

        function getReferences() {
            return researchEntity.getList('references', { populate: ['publicCoauthors', 'privateCoauthors']})
                    .then(function (references) {
                        vm.references = references;
                        return vm.references;
            });
        }

        function deleteReference(reference) {
            ReferencesService.delete(reference)
                    .then(function () {
                        _.remove(vm.references, reference);
                    });
        }

        function verifyReference(reference) {
            return ReferencesService.verify(reference);
        }
    }
})();
<commit_msg>Fixed bug on new reference creation.<commit_after>(function () {
    angular
            .module('references')
            .controller('ReferenceBrowsingController', ReferenceBrowsingController);

    ReferenceBrowsingController.$inject = [
        'ReferencesService',
        '$route',
        'researchEntity'
    ];

    function ReferenceBrowsingController(ReferencesService, $route, researchEntity) {
        var vm = this;

        vm.researchEntity = researchEntity;
        vm.deleteReference = deleteReference;
        vm.verifyReference = verifyReference;
        vm.createNewUrl = (vm.researchEntity.getType() === 'user') ? "/users/" + researchEntity.id + "/references/new" : "/groups/" + researchEntity.id + "/references/new";
        vm.editUrl = (vm.researchEntity.getType() === 'user') ? '#/users/' + researchEntity.id + '/edit' : '#/groups/' + researchEntity.id + '/edit';
        activate();

        function activate() {
            return getReferences().then(function () {

            });
        }

        function getReferences() {
            return researchEntity.getList('references', { populate: ['publicCoauthors', 'privateCoauthors']})
                    .then(function (references) {
                        vm.references = references;
                        return vm.references;
            });
        }

        function deleteReference(reference) {
            ReferencesService.delete(reference)
                    .then(function () {
                        _.remove(vm.references, reference);
                    });
        }

        function verifyReference(reference) {
            return ReferencesService.verify(reference);
        }
    }
})();
<|endoftext|>


<commit_before>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.");
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {

    return move || getInput();
}

function getComputerMove(move) {

	return move || randomPLay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.

    if (playerMove === computerMove)
	winner = "tie";

	else if(playerMove === "rock"){
		if (computerMove === "paper")
		   winner = "computer";
		   else
			winner = "player";
		}

	else if (playerMove === "paper"){
		if (computerMove === "scissors")
			winner = "computer";
			else
			     winner = "player";
		}
	else if (playerMove === "scissors"){
		if (computerMove === "rock")
			winner = "computer";
			else
			     winner = "player";
		}
    return winner;
}

function playToFive() {

    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    var playerMove;
    var computerMove;
    var winner;

	for (var i = 0; i < 20; i++){
		playerMove = getPlayerMove();
		computerMove = getComputerMove();
		winner = getWinner(playerMove, computerMove);

			console.log("Player chose: " + playerMove + ". Computer chose: " + computerMove + ".");

		if (winner === "player") {
			playerWins++;
            		console.log("Current Score: Player: " + playerWins + ". Computer: " + computerWins + ".");

				if (playerWins === 5 || computerWins === 5){
				console.log("FINAL SCORE! Player: " + playerWins + ". Computer: " + computerWins + ". The winner is: " + winner "!");
				}
        	}
        	else if (winner === "computer") {
            		computerWins++;
			console.log("Current Score: Player: " + playerWins + ". Computer: " + computerWins + ".");

				if (playerWins === 5 || computerWins === 5){
                		console.log("FINAL SCORE! Player: " + playerWins + ". Computer: " + computerWins + ". The winner is: " + winner "!");
                		}
        		}
		else if (winner === 'tie')
	   		 console.log("It's a tie!");

    	}

    return [playerWins, computerWins];
}

<commit_msg>Added RPS file<commit_after>////////////////////////////////////////////////
/*   Provided Code - Please Don't Edit   */
////////////////////////////////////////////////
'use strict';

function getInput() {
    console.log("Please choose either 'rock', 'paper', or 'scissors'.");
    return prompt();
}
function randomPlay() {
    var randomNumber = Math.random();
    if (randomNumber < 0.33) {
        return "rock";
    } else if (randomNumber < 0.66) {
        return "paper";
    } else {
        return "scissors";
    }
}
////////////////////////////////////////////////
/*           Write Your Code Below            */
////////////////////////////////////////////////

function getPlayerMove(move) {

    return move || getInput();
}

function getComputerMove(move) {

	return move || randomPLay();
}

function getWinner(playerMove,computerMove) {
    var winner;
    // Write code that will set winner to either 'player', 'computer', or 'tie' based on the values of playerMove and computerMove.
    // Assume that the only values playerMove and computerMove can have are 'rock', 'paper', and 'scissors'.
    // The rules of the game are that 'rock' beats 'scissors', 'scissors' beats 'paper', and 'paper' beats 'rock'.

    if (playerMove === computerMove)
	winner = "tie";

	else if(playerMove === "rock"){
		if (computerMove === "paper")
		   winner = "computer";
		   else
			winner = "player";
		}

	else if (playerMove === "paper"){
		if (computerMove === "scissors")
			winner = "computer";
			else
			     winner = "player";
		}
	else if (playerMove === "scissors"){
		if (computerMove === "rock")
			winner = "computer";
			else
			     winner = "player";
		}
    return winner;
}

function playToFive() {

    console.log("Let's play Rock, Paper, Scissors");
    var playerWins = 0;
    var computerWins = 0;
    var playerMove;
    var computerMove;
    var winner;

	for (var i = 0; i < 20; i++){
		playerMove = getPlayerMove();
		computerMove = getComputerMove();
		winner = getWinner(playerMove, computerMove);

			console.log("Player chose: " + playerMove + ". Computer chose: " + computerMove + ".");

		if (winner === "player") {
			playerWins++;
            		console.log("Current Score: Player: " + playerWins + ". Computer: " + computerWins + ".");

				if (playerWins === 5 || computerWins === 5){
				console.log("FINAL SCORE! Player: " + playerWins + ". Computer: " + computerWins + ". The winner is: " + winner "!");
				}
        	}
        	else if (winner === "computer") {
            		computerWins++;
			console.log("Current Score: Player: " + playerWins + ". Computer: " + computerWins + ".");

				if (playerWins === 5 || computerWins === 5){
                		console.log("FINAL SCORE! Player: " + playerWins + ". Computer: " + computerWins + ". The winner is: " + winner "!");
                		}
        		}
		else if (winner === 'tie')
	   		 console.log("It's a tie!");

    	}

    return [playerWins, computerWins];
}
playToFive();
<|endoftext|>