<commit_before>/* @flow */

import ConnectivityAwareHTTPNetworkInterface from './ConnectivityAwareHTTPNetworkInterface';

type AuthAwareNetworkInterfaceOptions = {
  uri: string,
  getIdToken: () => ?string,
  setIdToken: (idToken: string) => void,
  getRefreshToken: () => ?string,
  idTokenIsValid: () => boolean,
  refreshIdTokenAsync: () => Promise<string>,
  getSessionSecret: () => ?string,
  setSession: (session: { [string]: any }) => void,
  signOutAsync: () => Promise<void>,
  migrateAuth0ToSessionAsync: () => void,
};

class AuthAwareNetworkInterface {
  _requestQueue = [];
  _setIdToken: (token: string) => void;
  _getIdToken: () => ?string;
  _getRefreshToken: () => ?string;
  _refreshIdTokenAsync: () => Promise<string>;
  _idTokenIsValid: () => boolean;
  _networkInterface: ConnectivityAwareHTTPNetworkInterface;
  _getSessionSecret: () => ?string;
  _setSession: (session: { [string]: any }) => void;
  _signOutAsync: () => void;
  _migrateAuth0ToSessionAsync: () => void;

  __debug_hasCompletedRefresh: any;

  constructor(options: AuthAwareNetworkInterfaceOptions) {
    let {
      uri,
      getIdToken,
      setIdToken,
      getRefreshToken,
      idTokenIsValid,
      refreshIdTokenAsync,
      getSessionSecret,
      setSession,
      signOutAsync,
      migrateAuth0ToSessionAsync,
      ...connectivityInterfaceOptions
    } = options;

    this._networkInterface = new ConnectivityAwareHTTPNetworkInterface(
      uri,
      connectivityInterfaceOptions
    );
    this._getIdToken = getIdToken;
    this._setIdToken = setIdToken;
    this._getRefreshToken = getRefreshToken;
    this._idTokenIsValid = idTokenIsValid;
    this._refreshIdTokenAsync = refreshIdTokenAsync;
    this._getSessionSecret = getSessionSecret;
    this._setSession = setSession;
    this._signOutAsync = signOutAsync;
    this._migrateAuth0ToSessionAsync = migrateAuth0ToSessionAsync;

    this._applyAuthorizationHeaderMiddleware();
  }

  _applyAuthorizationHeaderMiddleware = () => {
    this._networkInterface.use([
      {
        applyMiddleware: (req, next) => {
          if (!req.options.headers) {
            req.options.headers = {};
          }

          const sessionSecret = this._getSessionSecret();
          if (sessionSecret) {
            req.options.headers['Expo-Session'] = sessionSecret;
          }

          const idToken = this._getIdToken();
          if (idToken) {
            req.options.headers['Authorization'] = `Bearer ${idToken}`;
          }

          next();
        },
      },
    ]);
  };

  query(request: any) {
    // If we don't have an auth0 token, we make a regular query
    // It either means we are logged out, or have only interacted with our Sessions system
    if (!this._getIdToken) {
      return this._networkInterface.query(request);
    }

    // If we have auth0 tokens, but have a session secret, we make a regular query
    if (this._getSessionSecret) {
      return this._networkInterface.query(request);
    }

    // We dont have a session, but we have a valid idToken
    if (this._idTokenIsValid()) {
      // try migrating, but dont wait for the call back
      // this should never throw an error
      this._migrateAuth0ToSessionAsync();

      return this._networkInterface.query(request);
    }

    // We dont have a session or a valid idToken
    // If Auth0 has already shut down, we must log out and start again
    const dateAuth0Gone = new Date(2018, 3, 2); // April 1, 2018 - the months are 0 indexed
    if (Date.now() > dateAuth0Gone) {
      return new Promise(async (resolve, reject) => {
        await this._signOutAsync({ shouldResetApolloStore: false });
        alert('Your session has expired, logging out. Please sign in again.');
        resolve({
          data: { error: 'Your session has expired, logging out. Please sign in again.' },
        });
      });
    }

    // We dont have a session or a valid idToken
    // Auth0 hasnt shut down yet, so we ask them for a valid token
    return new Promise(async (resolve, reject) => {
      // Throw it into the queue
      this._requestQueue.push(() => {
        this._networkInterface
          .query(request)
          .then(resolve)
          .catch(reject);
      });

      if (this._requestQueue.length === 1) {
        let newIdToken = await this._refreshIdTokenAsync();
        this._setIdToken(newIdToken);
        this._flushRequestQueue();
      }
    });
  }

  _flushRequestQueue() {
    this._requestQueue.forEach(queuedRequest => queuedRequest());
    this._requestQueue = [];
  }

  use(middleware: any) {
    return this._networkInterface.use(middleware);
  }

  useAfter(afterware: any) {
    return this._networkInterface.useAfter(afterware);
  }
}

export default function createAuthAwareNetworkInterface(options: AuthAwareNetworkInterfaceOptions) {
  return new AuthAwareNetworkInterface(options);
}
<commit_msg>interface function bug<commit_after>/* @flow */

import ConnectivityAwareHTTPNetworkInterface from './ConnectivityAwareHTTPNetworkInterface';

type AuthAwareNetworkInterfaceOptions = {
  uri: string,
  getIdToken: () => ?string,
  setIdToken: (idToken: string) => void,
  getRefreshToken: () => ?string,
  idTokenIsValid: () => boolean,
  refreshIdTokenAsync: () => Promise<string>,
  getSessionSecret: () => ?string,
  setSession: (session: { [string]: any }) => void,
  signOutAsync: () => Promise<void>,
  migrateAuth0ToSessionAsync: () => void,
};

class AuthAwareNetworkInterface {
  _requestQueue = [];
  _setIdToken: (token: string) => void;
  _getIdToken: () => ?string;
  _getRefreshToken: () => ?string;
  _refreshIdTokenAsync: () => Promise<string>;
  _idTokenIsValid: () => boolean;
  _networkInterface: ConnectivityAwareHTTPNetworkInterface;
  _getSessionSecret: () => ?string;
  _setSession: (session: { [string]: any }) => void;
  _signOutAsync: () => void;
  _migrateAuth0ToSessionAsync: () => void;

  __debug_hasCompletedRefresh: any;

  constructor(options: AuthAwareNetworkInterfaceOptions) {
    let {
      uri,
      getIdToken,
      setIdToken,
      getRefreshToken,
      idTokenIsValid,
      refreshIdTokenAsync,
      getSessionSecret,
      setSession,
      signOutAsync,
      migrateAuth0ToSessionAsync,
      ...connectivityInterfaceOptions
    } = options;

    this._networkInterface = new ConnectivityAwareHTTPNetworkInterface(
      uri,
      connectivityInterfaceOptions
    );
    this._getIdToken = getIdToken;
    this._setIdToken = setIdToken;
    this._getRefreshToken = getRefreshToken;
    this._idTokenIsValid = idTokenIsValid;
    this._refreshIdTokenAsync = refreshIdTokenAsync;
    this._getSessionSecret = getSessionSecret;
    this._setSession = setSession;
    this._signOutAsync = signOutAsync;
    this._migrateAuth0ToSessionAsync = migrateAuth0ToSessionAsync;

    this._applyAuthorizationHeaderMiddleware();
  }

  _applyAuthorizationHeaderMiddleware = () => {
    this._networkInterface.use([
      {
        applyMiddleware: (req, next) => {
          if (!req.options.headers) {
            req.options.headers = {};
          }

          const sessionSecret = this._getSessionSecret();
          if (sessionSecret) {
            req.options.headers['Expo-Session'] = sessionSecret;
          }

          const idToken = this._getIdToken();
          if (idToken) {
            req.options.headers['Authorization'] = `Bearer ${idToken}`;
          }

          next();
        },
      },
    ]);
  };

  query(request: any) {
    // If we don't have an auth0 token, we make a regular query
    // It either means we are logged out, or have only interacted with our Sessions system
    if (!this._getIdToken()) {
      console.log('no id token');
      return this._networkInterface.query(request);
    }

    // If we have auth0 tokens, but have a session secret, we make a regular query
    if (this._getSessionSecret()) {
      return this._networkInterface.query(request);
    }

    // We dont have a session, but we have a valid idToken
    if (this._idTokenIsValid()) {
      // try migrating, but dont wait for the call back
      // this should never throw an error
      this._migrateAuth0ToSessionAsync();

      return this._networkInterface.query(request);
    }

    // We dont have a session or a valid idToken
    // If Auth0 has already shut down, we must log out and start again
    const dateAuth0Gone = new Date(2018, 3, 2); // April 1, 2018 - the months are 0 indexed
    if (Date.now() > dateAuth0Gone) {
      return new Promise(async (resolve, reject) => {
        await this._signOutAsync({ shouldResetApolloStore: false });
        alert('Your session has expired, logging out. Please sign in again.');
        resolve({
          data: { error: 'Your session has expired, logging out. Please sign in again.' },
        });
      });
    }

    // We dont have a session or a valid idToken
    // Auth0 hasnt shut down yet, so we ask them for a valid token
    return new Promise(async (resolve, reject) => {
      // Throw it into the queue
      this._requestQueue.push(() => {
        this._networkInterface
          .query(request)
          .then(resolve)
          .catch(reject);
      });

      if (this._requestQueue.length === 1) {
        let newIdToken = await this._refreshIdTokenAsync();
        this._setIdToken(newIdToken);
        this._flushRequestQueue();
      }
    });
  }

  _flushRequestQueue() {
    this._requestQueue.forEach(queuedRequest => queuedRequest());
    this._requestQueue = [];
  }

  use(middleware: any) {
    return this._networkInterface.use(middleware);
  }

  useAfter(afterware: any) {
    return this._networkInterface.useAfter(afterware);
  }
}

export default function createAuthAwareNetworkInterface(options: AuthAwareNetworkInterfaceOptions) {
  return new AuthAwareNetworkInterface(options);
}
<|endoftext|>


<commit_before>var Blog = require("blog");
var User = require("user");
var config = require("config");
var async = require("async");
var stripe = require("stripe")(config.stripe.secret);
var email = "dmerfield" + Date.now() + "@gmail.com";
var uuid = require("uuid/v4");
var debug = require("debug")("scripts:user:overdue-subscription");
var access = require("../access");
var fetchSubscription = require("./fetch-subscription-from-stripe");

var customer = {
  card: "tok_mastercard",
  email: email,
  plan: config.stripe.plan,
  trial_end: Math.round(Date.now() / 1000) + 1000,
  description: "Blot subscription"
};

var blog = {
  handle:
    "overdue" +
    uuid()
      .split("-")
      .join("")
};

if (require.main === module)
  main(function(err, email, invoice) {
    if (err) {
      console.log(err);
      throw err;
    }

    if (!invoice) throw new Error("No invoice returned");

    access(email, function(err, url) {
      if (err) throw err;
      console.log(
        "Unpaid invoice found for " + email + "! Please log in and pay it:"
      );
      console.log(url);
      process.exit();
    });
  });

function main(callback) {
  stripe.customers.create(customer, function(err, customer) {
    if (err) return callback(err);

    debug("Created stripe customer");

    User.create(email, "XXXX", customer.subscription, function(err, user) {
      if (err) return callback(err);

      debug("Created user on Blot", blog, user.uid);

      Blog.create(user.uid, blog, function(err, blog) {
        if (err) return callback(err);

        debug("Created blog", blog);

        stripe.customers.update(
          customer.id,
          {
            card: "tok_chargeCustomerFail"
          },
          function(err) {
            if (err) return callback(err);

            debug("Updated stripe subscription to bad card");

            stripe.customers.updateSubscription(
              customer.id,
              customer.subscription.id,
              {
                trial_end: Math.round(Date.now() / 1000) + 3,
                prorate: false
              },
              function(err, subscription) {
                if (err) return callback(err);

                debug("Ended stripe subscription trial");

                stripe.invoices.list({ customer: customer.id }, function onList(
                  err,
                  res
                ) {
                  if (err) return callback(err);

                  var invoices = res.data.filter(function(invoice) {
                    return !invoice.paid;
                  });

                  if (!invoices.length) {
                    console.log(
                      "No unpaid invoices yet, waiting 5s and trying again..."
                    );
                    return setTimeout(function() {
                      stripe.invoices.list({ customer: customer.id }, onList);
                    }, 5000);
                  }

                  async.each(
                    invoices,
                    function(invoice, nextInvoice) {
                      if (invoice.paid) return nextInvoice();
                      stripe.invoices.pay(invoice.id, nextInvoice);
                    },
                    function() {
                      fetchSubscription(email, function(err) {
                        if (err) return callback(err);
                        callback(null, email, invoices);
                      });
                    }
                  );
                });
              }
            );
          }
        );
      });
    });
  });
}
<commit_msg>Improved script to generate user with overdue subscription<commit_after>var Blog = require("blog");
var User = require("user");
var config = require("config");
var async = require("async");
var stripe = require("stripe")(config.stripe.secret);
var email = "dmerfield" + Date.now() + "@gmail.com";
var uuid = require("uuid/v4");
var debug = require("debug")("scripts:user:overdue-subscription");
var access = require("../access");
var fetchSubscription = require("./fetch-subscription-from-stripe");

var customer = {
  card: "tok_mastercard",
  email: email,
  plan: config.stripe.plan,
  trial_end: Math.round(Date.now() / 1000) + 1000,
  description: "Blot subscription"
};

var password = "x";

var blog = {
  handle:
    "overdue" +
    uuid()
      .split("-")
      .join("")
};

if (require.main === module)
  main(function(err, email, invoice) {
    if (err) {
      console.log(err);
      throw err;
    }

    if (!invoice) throw new Error("No invoice returned");

    access(email, function(err, url) {
      if (err) throw err;
      console.log(
        "Unpaid invoice found for " + email + "! Please log in and pay it:"
      );
      console.log(url);
      console.log("Use password:", password);
      process.exit();
    });
  });

function main(callback) {
  stripe.customers.create(customer, function(err, customer) {
    if (err) return callback(err);

    debug("Created stripe customer");

    User.hashPassword(password, function(err, passwordHash) {
      if (err) return callback(err);
      User.create(email, passwordHash, customer.subscription, function(
        err,
        user
      ) {
        if (err) return callback(err);

        debug("Created user on Blot", blog, user.uid);

        Blog.create(user.uid, blog, function(err, blog) {
          if (err) return callback(err);

          debug("Created blog", blog);

          stripe.customers.update(
            customer.id,
            {
              card: "tok_chargeCustomerFail"
            },
            function(err) {
              if (err) return callback(err);

              debug("Updated stripe subscription to bad card");

              stripe.customers.updateSubscription(
                customer.id,
                customer.subscription.id,
                {
                  trial_end: Math.round(Date.now() / 1000) + 3,
                  prorate: false
                },
                function(err, subscription) {
                  if (err) return callback(err);

                  debug("Ended stripe subscription trial");

                  stripe.invoices.list(
                    { customer: customer.id },
                    function onList(err, res) {
                      if (err) return callback(err);

                      var invoices = res.data.filter(function(invoice) {
                        return !invoice.paid;
                      });

                      if (!invoices.length) {
                        console.log(
                          "No unpaid invoices yet, waiting 5s and trying again..."
                        );
                        return setTimeout(function() {
                          stripe.invoices.list(
                            { customer: customer.id },
                            onList
                          );
                        }, 5000);
                      }

                      async.each(
                        invoices,
                        function(invoice, nextInvoice) {
                          if (invoice.paid) return nextInvoice();
                          stripe.invoices.pay(invoice.id, nextInvoice);
                        },
                        function() {
                          fetchSubscription(email, function(err) {
                            if (err) return callback(err);
                            callback(null, email, invoices);
                          });
                        }
                      );
                    }
                  );
                }
              );
            }
          );
        });
      });
    });
  });
}
<|endoftext|>


<commit_before>/*
*  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
*  Use of this source code is governed by a BSD-style license
*  that can be found in the LICENSE file in the root of the source
*  tree.
*/

'use strict';

/* globals MediaRecorder */

// This code is adapted from
// https://rawgit.com/Miguelao/demos/master/mediarecorder.html

'use strict';

/* globals MediaRecorder */

var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);
var mediaRecorder;
var recordedBlobs;
var sourceBuffer;

var gumVideo = document.querySelector('video#gum');
var recordedVideo = document.querySelector('video#recorded');

var recordButton = document.querySelector('button#record');
var playButton = document.querySelector('button#play');
var downloadButton = document.querySelector('button#download');
recordButton.onclick = toggleRecording;
playButton.onclick = play;
downloadButton.onclick = download;

// window.isSecureContext could be used for Chrome
var isSecureOrigin = location.protocol === 'https:' ||
location.host === 'localhost';
if (!isSecureOrigin) {
  alert('getUserMedia() must be run from a secure origin: HTTPS or localhost.' +
    '\n\nChanging protocol to HTTPS');
  location.protocol = 'HTTPS';
}

// Use old-style gUM to avoid requirement to enable the
// Enable experimental Web Platform features flag in Chrome 49

var constraints = {
  audio: true,
  video: true
};

function handleSuccess(stream) {
  console.log('getUserMedia() got stream: ', stream);
  window.stream = stream;
  if (window.URL) {
    gumVideo.src = window.URL.createObjectURL(stream);
  } else {
    gumVideo.src = stream;
  }
}

function handleError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

navigator.mediaDevices.getUserMedia(constraints).
    then(handleSuccess).catch(handleError);

function handleSourceOpen(event) {
  console.log('MediaSource opened');
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
  console.log('Source buffer: ', sourceBuffer);
}

function handleDataAvailable(event) {
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
  }
}

function handleStop(event) {
  console.log('Recorder stopped: ', event);
}

function toggleRecording() {
  if (recordButton.textContent === 'Start Recording') {
    startRecording();
  } else {
    stopRecording();
    recordButton.textContent = 'Start Recording';
    playButton.disabled = false;
    downloadButton.disabled = false;
  }
}

// The nested try blocks will be simplified when Chrome 47 moves to Stable
function startRecording() {
  var options = {mimeType: 'video/webm'};
  recordedBlobs = [];
  try {
    mediaRecorder = new MediaRecorder(window.stream, options);
  } catch (e0) {
    console.log('Unable to create MediaRecorder with options Object: ', e0);
    try {
      options = {mimeType: 'video/webm,codecs=vp9'};
      mediaRecorder = new MediaRecorder(window.stream, options);
    } catch (e1) {
      console.log('Unable to create MediaRecorder with options Object: ', e1);
      try {
        options = 'video/vp8'; // Chrome 47
        mediaRecorder = new MediaRecorder(window.stream, options);
      } catch (e2) {
        alert('MediaRecorder is not supported by this browser.\n\n' +
            'Try Firefox 29 or later, or Chrome 47 or later, with Enable experimental Web Platform features enabled from chrome://flags.');
        console.error('Exception while creating MediaRecorder:', e2);
        return;
      }
    }
  }
  console.log('Created MediaRecorder', mediaRecorder, 'with options', options);
  recordButton.textContent = 'Stop Recording';
  playButton.disabled = true;
  downloadButton.disabled = true;
  mediaRecorder.onstop = handleStop;
  mediaRecorder.ondataavailable = handleDataAvailable;
  mediaRecorder.start(10); // collect 10ms of data
  console.log('MediaRecorder started', mediaRecorder);
}

function stopRecording() {
  mediaRecorder.stop();
  console.log('Recorded Blobs: ', recordedBlobs);
  recordedVideo.controls = true;
}

function play() {
  var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
  recordedVideo.src = window.URL.createObjectURL(superBuffer);
}

function download() {
  var blob = new Blob(recordedBlobs, {type: 'video/webm'});
  var url = window.URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;
  a.download = 'test.webm';
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
}
<commit_msg>Compatibility with Android Firefox 47.0 (#808)<commit_after>/*
*  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
*
*  Use of this source code is governed by a BSD-style license
*  that can be found in the LICENSE file in the root of the source
*  tree.
*/

'use strict';

/* globals MediaRecorder */

// This code is adapted from
// https://rawgit.com/Miguelao/demos/master/mediarecorder.html

'use strict';

/* globals MediaRecorder */

var mediaSource = new MediaSource();
mediaSource.addEventListener('sourceopen', handleSourceOpen, false);
var mediaRecorder;
var recordedBlobs;
var sourceBuffer;

var gumVideo = document.querySelector('video#gum');
var recordedVideo = document.querySelector('video#recorded');

var recordButton = document.querySelector('button#record');
var playButton = document.querySelector('button#play');
var downloadButton = document.querySelector('button#download');
recordButton.onclick = toggleRecording;
playButton.onclick = play;
downloadButton.onclick = download;

// window.isSecureContext could be used for Chrome
var isSecureOrigin = location.protocol === 'https:' ||
location.host === 'localhost';
if (!isSecureOrigin) {
  alert('getUserMedia() must be run from a secure origin: HTTPS or localhost.' +
    '\n\nChanging protocol to HTTPS');
  location.protocol = 'HTTPS';
}

// Use old-style gUM to avoid requirement to enable the
// Enable experimental Web Platform features flag in Chrome 49

var constraints = {
  audio: true,
  video: true
};

function handleSuccess(stream) {
  console.log('getUserMedia() got stream: ', stream);
  window.stream = stream;
  if (window.URL) {
    gumVideo.src = window.URL.createObjectURL(stream);
  } else {
    gumVideo.src = stream;
  }
}

function handleError(error) {
  console.log('navigator.getUserMedia error: ', error);
}

navigator.mediaDevices.getUserMedia(constraints).
    then(handleSuccess).catch(handleError);

function handleSourceOpen(event) {
  console.log('MediaSource opened');
  sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vp8"');
  console.log('Source buffer: ', sourceBuffer);
}

recordedVideo.addEventListener('error', function(ev) {
  console.error('MediaRecording.recordedMedia.error()');
  alert('Your browser can not play\n\n' + recordedVideo.src
    + '\n\n media clip. event: ' + JSON.stringify(ev));
}, true);

function handleDataAvailable(event) {
  if (event.data && event.data.size > 0) {
    recordedBlobs.push(event.data);
  }
}

function handleStop(event) {
  console.log('Recorder stopped: ', event);
}

function toggleRecording() {
  if (recordButton.textContent === 'Start Recording') {
    startRecording();
  } else {
    stopRecording();
    recordButton.textContent = 'Start Recording';
    playButton.disabled = false;
    downloadButton.disabled = false;
  }
}

// The nested try blocks will be simplified when Chrome 47 moves to Stable
function startRecording() {
  recordedBlobs = [];
  var options = {mimeType: 'video/webm;codecs=vp9'};
  if (!MediaRecorder.isTypeSupported(options.mimeType)) {
    console.log(options.mimeType + ' is not Supported');
    options = {mimeType: 'video/webm;codecs=vp8'};
    if (!MediaRecorder.isTypeSupported(options.mimeType)) {
      console.log(options.mimeType + ' is not Supported');
      options = {mimeType: 'video/webm'};
      if (!MediaRecorder.isTypeSupported(options.mimeType)) {
        console.log(options.mimeType + ' is not Supported');
        options = {mimeType: ''};
      }
    }
  }
  try {
    mediaRecorder = new MediaRecorder(window.stream, options);
  } catch (e) {
    console.error('Exception while creating MediaRecorder: ' + e);
    alert('Exception while creating MediaRecorder: '
      + e + '. mimeType: ' + options.mimeType);
    return;
  }
  console.log('Created MediaRecorder', mediaRecorder, 'with options', options);
  recordButton.textContent = 'Stop Recording';
  playButton.disabled = true;
  downloadButton.disabled = true;
  mediaRecorder.onstop = handleStop;
  mediaRecorder.ondataavailable = handleDataAvailable;
  mediaRecorder.start(10); // collect 10ms of data
  console.log('MediaRecorder started', mediaRecorder);
}

function stopRecording() {
  mediaRecorder.stop();
  console.log('Recorded Blobs: ', recordedBlobs);
  recordedVideo.controls = true;
}

function play() {
  var superBuffer = new Blob(recordedBlobs, {type: 'video/webm'});
  recordedVideo.src = window.URL.createObjectURL(superBuffer);
}

function download() {
  var blob = new Blob(recordedBlobs, {type: 'video/webm'});
  var url = window.URL.createObjectURL(blob);
  var a = document.createElement('a');
  a.style.display = 'none';
  a.href = url;
  a.download = 'test.webm';
  document.body.appendChild(a);
  a.click();
  setTimeout(function() {
    document.body.removeChild(a);
    window.URL.revokeObjectURL(url);
  }, 100);
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

CLASS({
  package: 'foam.apps.ctm',
  name: 'TaskManagerDetailView',
  extendsModel: 'foam.ui.DetailView',

  requires: [
    'foam.apps.ctm.Task',
    'foam.apps.ctm.TaskPieGraph',
    'foam.ui.ActionButton'
  ],

  properties: [
    {
      model_: 'IntProperty',
      name: 'numHistoryItems',
      defaultValue: 64
    },
    {
      model_: 'BooleanProperty',
      name: 'showActions',
      defaultValue: false,
      preSet: function() { return false; }
    }
  ],

  templates: [
    function toHTML() {/*
      <task-manager>
        <tm-header>
          Search:$$search
        </tm-header>
        <tm-body>
          $$tasks{
            editColumnsEnabled: true,
            properties$: this.data.tableColumns$
          }
        </tm-body>
        <tm-footer>
          <stats>
            <global-stats>
              $$tasks{ model_: this.TaskPieGraph, property: this.Task.MEMORY }
              $$tasks{ model_: this.TaskPieGraph, property: this.Task.CPU }
              $$tasks{ model_: this.TaskPieGraph, property: this.Task.NETWORK }
            </global-stats>
            <local-stats id="<%= this.setClass('hidden', function() { return !this.data || !this.data.selection; }.bind(this)) %>">
              $$memory{ width: 100, height: 50 }
              $$cpu{ width: 100, height: 50 }
              $$network{ width: 100, height: 50 }
            </local-stats>
          </stats>
          <actions>
            Stats for nerds
            $$kill
          </actions>
        </tm-footer>
      </task-manager>
    */},
    function CSS() {/*
      body, task-manager {
        width: 100%;
        height: 100%;
      }
      task-manager {
        display: flex;
        flex-direction: column;
      }
      task-manager .tableView:focus { outline: none; }
      task-manager tm-header,
      task-manager tm-body,
      task-manager tm-footer {
        display: block;
      }
      task-manager tm-header,
      task-manager tm-body,
      task-manager tm-footer {
        padding: 20px;
      }
      task-manager tm-footer global-stats,
      task-manager tm-footer local-stats,
      task-manager tm-footer actions {
        padding: 5px;
      }
      task-manager tm-header, task-manager tm-footer {
        flex-grow: 0;
        flex-shrink: 0;
      }
      task-manager tm-header label {
        padding: 0px 5px 0px 0px;
      }
      task-manager tm-body {
        flex-grow: 1;
        overflow: hidden;
      }
      task-manager tm-footer stats,
      task-manager tm-footer stats global-stats,
      task-manager tm-footer stats local-stats {
        display: flex;
        justify-content: space-around;
        align-items: center;
      }
      task-manager tm-footer stats {
        flex-wrap: wrap;
      }
      task-manager tm-footer stats global-stats,
      task-manager tm-footer stats local-stats {
        flex-grow: 1;
      }
      task-manager tm-footer stats local-stats.hidden {
        visibility: hidden;
      }
      task-manager tm-footer actions {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      task-manager img {
        margin-right: 4px;
        vertical-align: text-bottom;
      }
    */}
  ]
});
<commit_msg>Improved CSS in TaskManager.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 */

CLASS({
  package: 'foam.apps.ctm',
  name: 'TaskManagerDetailView',
  extendsModel: 'foam.ui.DetailView',

  requires: [
    'foam.apps.ctm.Task',
    'foam.apps.ctm.TaskPieGraph',
    'foam.ui.ActionButton'
  ],

  properties: [
    {
      model_: 'IntProperty',
      name: 'numHistoryItems',
      defaultValue: 64
    },
    {
      model_: 'BooleanProperty',
      name: 'showActions',
      defaultValue: false,
      preSet: function() { return false; }
    }
  ],

  templates: [
    function toHTML() {/*
      <task-manager>
        <tm-header>
          Search:$$search
        </tm-header>
        <tm-body>
          $$tasks{
            editColumnsEnabled: true,
            properties$: this.data.tableColumns$
          }
        </tm-body>
        <tm-footer>
          <stats>
            <global-stats>
              $$tasks{ model_: this.TaskPieGraph, property: this.Task.MEMORY }
              $$tasks{ model_: this.TaskPieGraph, property: this.Task.CPU }
              $$tasks{ model_: this.TaskPieGraph, property: this.Task.NETWORK }
            </global-stats>
            <local-stats id="<%= this.setClass('hidden', function() { return !this.data || !this.data.selection; }.bind(this)) %>">
              $$memory{ width: 100, height: 50 }
              $$cpu{ width: 100, height: 50 }
              $$network{ width: 100, height: 50 }
            </local-stats>
          </stats>
          <actions>
            Stats for nerds
            $$kill
          </actions>
        </tm-footer>
      </task-manager>
    */},
    function CSS() {/*
      body, task-manager {
        width: 100%;
        height: 100%;
      }
      task-manager {
        display: flex;
        flex-direction: column;
      }
      task-manager .tableView:focus { outline: none; }
      task-manager tm-header,
      task-manager tm-body,
      task-manager tm-footer {
        display: block;
      }
      task-manager tm-header,
      task-manager tm-body,
      task-manager tm-footer {
        padding: 20px;
      }
      task-manager tm-footer global-stats,
      task-manager tm-footer local-stats,
      task-manager tm-footer actions {
        padding: 5px;
      }
      task-manager tm-header {
        padding-bottom: 0;
      }
      task-manager tm-header, task-manager tm-footer {
        flex-grow: 0;
        flex-shrink: 0;
      }
      task-manager tm-header label {
        padding: 0px 5px 0px 0px;
      }
      task-manager tm-body {
        flex-grow: 1;
        overflow: hidden;
      }
      task-manager tm-footer stats,
      task-manager tm-footer stats global-stats,
      task-manager tm-footer stats local-stats {
        display: flex;
        justify-content: space-around;
        align-items: center;
      }
      task-manager tm-footer stats {
        flex-wrap: wrap;
      }
      task-manager tm-footer stats global-stats,
      task-manager tm-footer stats local-stats {
        flex-grow: 1;
      }
      task-manager tm-footer stats local-stats.hidden {
        visibility: hidden;
      }
      task-manager tm-footer actions {
        display: flex;
        justify-content: space-between;
        align-items: center;
      }
      task-manager img {
        margin-right: 4px;
        vertical-align: text-bottom;
      }
    */}
  ]
});
<|endoftext|>


<commit_before>/**
 * In development we assume that the code generated is going to be consumed by
 * webpack dev server and we are bundling into a single js file.
 */

import webpack from 'webpack';
import webpackConfigBase from './webpack.base.babel';
import HtmlWebpackPlugin from 'html-webpack-plugin';

const plugins = [
  new HtmlWebpackPlugin({
    template: `index.html`
  }),
  new webpack.EnvironmentPlugin([
    `CISCOSPARK_ACCESS_TOKEN`,
    `MESSAGE_DEMO_CLIENT_ID`,
    `MESSAGE_DEMO_CLIENT_SECRET`,
    `SPACE_ID`,
    `TO_PERSON_EMAIL`,
    `TO_PERSON_ID`
  ])
];

export default webpackConfigBase({
  entry: `./index.js`,
  plugins,
  devtool: `source-map`,
  devServer: {
    host: `0.0.0.0`,
    port: 8000,
    stats: {
      colors: true,
      hash: false,
      version: false,
      timings: false,
      assets: true,
      chunks: false,
      modules: false,
      reasons: false,
      children: false,
      source: false,
      errors: true,
      errorDetails: true,
      warnings: true,
      publicPath: false
    },
    headers: {
      'Content-Security-Policy': `script-src 'self' 'unsafe-inline' code.s4d.io; style-src 'self' 'unsafe-inline' code.s4d.io; media-src 'self' code.s4d.io *.clouddrive.com data: blob:; font-src 'self' code.s4d.io; img-src 'self' code.s4d.io *.clouddrive.com data: blob: *.rackcdn.com; connect-src 'self' localhost ws://localhost:8000 wss://*.wbx.com ws://*.wbx.com *.wbx2.com *.webex.com code.s4d.io *.ciscospark.com;`
    }
  }
});
<commit_msg>chore: add clouddrive and wbx2 to csp<commit_after>/**
 * In development we assume that the code generated is going to be consumed by
 * webpack dev server and we are bundling into a single js file.
 */

import webpack from 'webpack';
import webpackConfigBase from './webpack.base.babel';
import HtmlWebpackPlugin from 'html-webpack-plugin';

const plugins = [
  new HtmlWebpackPlugin({
    template: `index.html`
  }),
  new webpack.EnvironmentPlugin([
    `CISCOSPARK_ACCESS_TOKEN`,
    `MESSAGE_DEMO_CLIENT_ID`,
    `MESSAGE_DEMO_CLIENT_SECRET`,
    `SPACE_ID`,
    `TO_PERSON_EMAIL`,
    `TO_PERSON_ID`
  ])
];

export default webpackConfigBase({
  entry: `./index.js`,
  plugins,
  devtool: `source-map`,
  devServer: {
    host: `0.0.0.0`,
    port: 8000,
    stats: {
      colors: true,
      hash: false,
      version: false,
      timings: false,
      assets: true,
      chunks: false,
      modules: false,
      reasons: false,
      children: false,
      source: false,
      errors: true,
      errorDetails: true,
      warnings: true,
      publicPath: false
    },
    headers: {
      'Content-Security-Policy': `script-src 'self' 'unsafe-inline' code.s4d.io; style-src 'self' 'unsafe-inline' code.s4d.io; media-src 'self' code.s4d.io *.clouddrive.com data: blob:; font-src 'self' code.s4d.io; img-src 'self' code.s4d.io *.clouddrive.com data: blob: *.rackcdn.com; connect-src 'self' localhost ws://localhost:8000 wss://*.wbx.com wss://*.wbx2.com ws://*.wbx.com *.wbx2.com *.webex.com code.s4d.io *.ciscospark.com https://*.clouddrive.com/;`
    }
  }
});
<|endoftext|>


<commit_before>/*
    Initializes the TinyMCE editor.
*/

(function($, tinyMCE) {

    // Initialize the editors.
    tinyMCE.init({
        mode: "specific_textareas",
        editor_selector : "html",
        theme: "advanced",
        plugins: "table, advimage, inlinepopups, paste",
        paste_auto_cleanup_on_paste: true,
        paste_remove_spans: true,
        paste_remove_styles: true,
        theme_advanced_buttons1: "code,|,formatselect,styleselect,|,bullist,numlist,table,|,bold,italic,|,sub,sup,|,link,unlink,image",
        theme_advanced_buttons2: "",
        theme_advanced_buttons3: "",
        theme_advanced_resizing: true,
        theme_advanced_path: false,
        theme_advanced_statusbar_location: "bottom",
        width: "700px",
        height: "350px",
        dialog_type: "modal",
        external_link_list_url: "{% url admin:tinymce_link_list %}",
        theme_advanced_blockformats: "h2,h3,p",
        content_css: "{{settings.TINYMCE_CONTENT_CSS|escapejs}}",
        extended_valid_elements : "iframe[src|width|height|name|align]",  // Permit embedded iframes.
        convert_urls: false,
        button_tile_map: true,  // Client-side optimization.
        file_browser_callback: $.filebrowser.createCallback("{% url admin:media_file_changelist %}?pop=1")
    });

}(django.jQuery, tinyMCE));<commit_msg>Added in hr control<commit_after>/*
    Initializes the TinyMCE editor.
*/

(function($, tinyMCE) {

    // Initialize the editors.
    tinyMCE.init({
        mode: "specific_textareas",
        editor_selector : "html",
        theme: "advanced",
        plugins: "table, advimage, inlinepopups, paste",
        paste_auto_cleanup_on_paste: true,
        paste_remove_spans: true,
        paste_remove_styles: true,
        theme_advanced_buttons1: "code,|,formatselect,styleselect,|,bullist,numlist,table,hr,|,bold,italic,|,sub,sup,|,link,unlink,image",
        theme_advanced_buttons2: "",
        theme_advanced_buttons3: "",
        theme_advanced_resizing: true,
        theme_advanced_path: false,
        theme_advanced_statusbar_location: "bottom",
        width: "700px",
        height: "350px",
        dialog_type: "modal",
        external_link_list_url: "{% url admin:tinymce_link_list %}",
        theme_advanced_blockformats: "h2,h3,p",
        content_css: "{{settings.TINYMCE_CONTENT_CSS|escapejs}}",
        extended_valid_elements : "iframe[src|width|height|name|align]",  // Permit embedded iframes.
        convert_urls: false,
        button_tile_map: true,  // Client-side optimization.
        file_browser_callback: $.filebrowser.createCallback("{% url admin:media_file_changelist %}?pop=1")
    });

}(django.jQuery, tinyMCE));<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { parse } from 'date-fns';

import './action.scss';

class Action extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const action = this.props.action;
    const campaign = this.props.campaign;

    return (

{action.name}

ID

{action.id}

CALLPOWER CAMPAIGN ID

          {action.callpower_campaign_id ? (

{action.callpower_campaign_id}

          ) : (

-

          )}

POST TYPE

{action.post_type}

NOUN

{action.noun}

VERB

{action.verb}

REPORTBACK

              {action.reportback === true ? (

Yes

              ) : (

No

              )}

CIVIC ACTION

              {action.civic_action === true ? (

Yes

              ) : (

No

              )}

SCHOLARSHIP

              {action.scholarship_entry === true ? (

Yes

              ) : (

No

              )}

ANONYMOUS POST

              {action.anonymous === true ? (

Yes

              ) : (

No

              )}

        {parse(campaign.start_date) > Date.now() ? (

            <a
              className="button -secondary"
              href={`${campaign.id}/actions/${action.id}/edit`}
            >
              Edit Action

            <button
              className="button delete -tertiary"
              onClick={e => this.props.deleteAction(action.id, e)}
            >
              Delete Action
            </button>

        ) : null}

    );
  }
}

Action.propTypes = {
  action: PropTypes.object.isRequired,
};

export default Action;
<commit_msg>Display "edit" button on actions for admins.<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { parse } from 'date-fns';

import './action.scss';

class Action extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const action = this.props.action;
    const campaign = this.props.campaign;
    const campaignInProgress = parse(campaign.start_date) > Date.now();
    const isAdmin = window.AUTH.role == 'admin';

    return (

{action.name}

ID

{action.id}

CALLPOWER CAMPAIGN ID

          {action.callpower_campaign_id ? (

{action.callpower_campaign_id}

          ) : (

-

          )}

POST TYPE

{action.post_type}

NOUN

{action.noun}

VERB

{action.verb}

REPORTBACK

              {action.reportback === true ? (

Yes

              ) : (

No

              )}

CIVIC ACTION

              {action.civic_action === true ? (

Yes

              ) : (

No

              )}

SCHOLARSHIP

              {action.scholarship_entry === true ? (

Yes

              ) : (

No

              )}

ANONYMOUS POST

              {action.anonymous === true ? (

Yes

              ) : (

No

              )}

        {campaignInProgress && !isAdmin ? null : (

            <a
              className="button -secondary"
              href={`${campaign.id}/actions/${action.id}/edit`}
            >
              Edit Action

            <button
              className="button delete -tertiary"
              onClick={e => this.props.deleteAction(action.id, e)}
            >
              Delete Action
            </button>

        )}

    );
  }
}

Action.propTypes = {
  action: PropTypes.object.isRequired,
};

export default Action;
<|endoftext|>


<commit_before>// error constants
SENT_BLANK_NAME  = 1;
ALREADY_HAVE_RESPONSES = 2;

function Player(name, ID, channel) {
    var that = this;
    this.name    = name;
    // this.picture = picture;
    this.ID      = ID;
    this.channel = channel;

    this.isOut   = false;
    this.isGone  = false;

    this.score = 0;

    this.getScore = function() {
        return that.score;
    };

    this.incrementScore = function() {
        that.score++;
    };

    this.getID = function() {
        return that.ID;
    };

    this.didGetOut = function(){
        this.isOut = true;
    }

    this.didLeave = function(){
        this.isGone = true;
    }

}

function Response(response, responseID, userID, channel){
    this.response   = response;
    this.responseID = responseID;
    this.userID     = userID;
    this.channel    = channel;
    this.isActive   = true;

    this.getJSON = function(){
        var thisObj = new Object();
        thisObj.response   = this.response;
        thisObj.responseID = this.responseID;
        return JSON.stringify(thisObj);
    }
}

function Game() {
    var that= this;

    this.players     = new Array();
    this.playerQueue = new Array();
    this.responses   = new Array();
    this.cues        = new Array("Things that hang", "Things that are poor", "Things that nobody wants");
    this.cues        = shuffle(this.cues); // randomize each playthrough

    this.reader  = 0;
    this.guesser = 0;

    this.isBetweenRounds = true;

    this.addPlayer = function(player){
        while(typeof this.players[player.ID] != 'undefined'){
            player.ID++;
        }
        this.players[player.ID] = player;
        player.channel.send({ type : 'didJoin', number : player.ID });
        console.log("Added player " + player.name);
    }

    this.queuePlayer = function(player){
        this.playerQueue.push(player);
        player.channel.send({ type: 'didQueue' });
        console.log("Queued player " + player.name);
    }

    this.deletePlayer = function(id){
        console.log("Deleted player " + this.players[i].name);
        this.players.splice(id, 1);
    }

    this.getNextCue = function(){
        var lastIndex = this.cues.length - 1;
        this.currentCue = this.cues[lastIndex];
        this.cues.splice(lastIndex, 1);
        return this.currentCue;
    }
}

function getPlayerIdByChannel(channel){
    for(var i = 0; i < game.players.length; i++){
        if(game.players[i].channel == channel){
            return i;
        }
    }
}

// update the list of players on screen
function updatePlayerList(){
    var tempList = game.players;
    tempList.sort();

    for(player in tempList){
        var playerHTML = '
' + player.score + ' ' + player.name + '
';
        $('#playerlist').append()
    }
}

function joinPlayer(channel, name){
    if(name === ""){
        channel.send({ type : 'error', value : SENT_BLANK_NAME });
        return;
    }

    var newID = game.players.length;
    var newPlayer = new Player(name, newID, channel);

    if(game.isBetweenRounds){
        game.addPlayer(newPlayer);
    }
    else{
        game.queuePlayer(newPlayer);
    }
}

function leavePlayer(channel){
    // find player, set them as out for the round and set didLeave = true so they're removed before next round
    var playerID = getPlayerIdByChannel(channel);
    game.players[playerID].didGetOut();
    game.players[playerID].didLeave();

    // if they haven't submitted a response yet, leave immediately
    var responseReceived = false;

    for(var i = 0; i < responses.length; i++){
        if(responses[i].userID == playerID){
            responseReceived = true;
        }
    }

    if(!responseReceived){
        game.deletePlayer(playerID);
    }

    // if they're currently reader or currently guessing, advance to the first or next guesser
    if(game.reader == playerID || game.guesser == playerID){
        nextGuesser();
    }

    return; // only one player per channel
}

function submitResponse(channel, response){
    var userID      = getPlayerIdByChannel(channel);
    var responseID  = game.responses.length;
    var newResponse = new Response(response, responseID, userID, channel);

    // check if there are already max number of responses
    if(game.responses.length >= game.players.length){
        channel.send({ type : 'error', value : ALREADY_HAVE_RESPONSES });
        return;
    }

    // check if there's already a response from this channel
    for(var i = 0; i < game.responses.length; i++){
        if(game.responses[i].channel == channel){
            // update the response
            game.responses[i].response = response;
            return;
        }
    }

    game.responses.push(newResponse);

    // if all responses are in, move to reading phase
    if(responseID == game.players.length){
        startReading();
    }
}

function getAllResponsesJSON(){
    var response = new Object();
    for(var i = 0; i < game.responses.length; i++){
        if(game.responses[i].isActive){
            response[i] = game.responses[i].getJSON();
        }
    }
    var responseJSON = JSON.stringify(response);
    return responseJSON;
}

function startReading(){
    // randomize order of responses
    game.responses = shuffle(game.responses);

    // send all responses to the reader.
    game.players[game.reader].channel.send(getAllResponsesJSON());
}

function startGuessing(){
    // send guesser all responses
    game.players[game.guesser].channel.send(getAllResponsesJSON());
}

function submitGuess(channel, guess){
    var responseGuessed = guess.guessResponseId;
    var playerGuessed   = guess.guessPlayerNumber;
    var guesserID       = getPlayerIdByChannel(channel);

    // if you're right, you get a point, a response is pulled, and you can keep guessing.
    if(game.responses[responseGuessed].userID == playerGuessed){
        channel.send({ type : 'guessResponse', value : true });
        game.players[guesserID].incrementScore();
        game.players[playerGuessed].didGetOut();
        game.responses[responseGuessed].isActive = false;
    }
    else{
        // if the person is out, you're dumb and your turn is over.
        if(game.players[playerGuessed].isOut){
            // TODO shame notification
        }
        channel.send({ type : 'guessResponse', value : false });

        // next guesser's turn
        nextGuesser();
    }
}

function nextGuesser(){
    // round is over when only reader's and your own responses are left
    if(game.responses.length == 2){
        newGrind();
    }
    else{
        var nextGuesser = game.guesser++;
        nextGuesser = nextGuesser % game.players.length;
        game.guesser = nextGuesser;

        // notify next guesser
        game.players[nextGuesser].channel.send({ type : 'guesser' });

        startGuessing();
    }
}

function startNextRound(){
    game.isBetweenRounds = false;

    // show next question
    $('#content h1').html(game.getNextCue());

    // let everyone know the round has started
    for(var i = 0; i < game.players.length; i++){
        game.players[i].channel.send({ type : 'roundStarted', cue: game.cues[currentCue] });
    }
}

// prepare for next question
function newGrind(){
    game.isBetweenRounds = true;

    // delete players who left
    for(var i = 0; i < game.players.length; i++){
        if(game.players[i].isGone){
            game.deletePlayer(i);
        }
    }

    // add players who are queued
    for(player in game.playerQueue){
        game.addPlayer(player);
    }

    // clear player queue
    game.playerQueue = [];

    // update player list
    updatePlayerList();

    // clear responses
    game.responses = [];

    // notify next reader
    game.reader++;
    game.reader = game.reader % game.players.length;

    // update all clients' user list and scores
    for(var i = 0; i < game.players.length; i++){
        game.players[i].channel.send({type : 'gameSync', players : playerJSON, reader : game.reader });
    }
}

function initReceiver(){
    var receiver = new cast.receiver.Receiver('1f96e9a0-9cf0-4e61-910e-c76f33bd42a2', ['com.bears.triviaCast'], "", 5),
        channelHandler = new cast.receiver.ChannelHandler('com.bears.triviaCast'),
        $messages = $('.messages');

    channelHandler.addChannelFactory(
        receiver.createChannelFactory('com.bears.triviaCast'));

    receiver.start();
    console.log('receiver started');

    channelHandler.addEventListener(cast.receiver.Channel.EventType.MESSAGE, onMessage.bind(this));
    channelHandler.addEventListener(cast.receiver.Channel.EventType.ERROR, onError.bind(this));

    function onMessage(event) {
        console.log('type = ' + event.message.type);
        console.log(event);

        switch(event.message.type){
            case "join":
                joinPlayer(event.target, event.message.name);
                break;
            case "leave":
                leavePlayer(event.target);
                break;
            case "submitResponse": // user submits a response
                storeResponse(event.target, event.message.response);
                break;
            case "submitGuess": // user submits a guess
                tryGuess(event.target, event.message.guess);
                break;
            case "nextRound":
                startNextRound();
                break;
            case "readerIsDone":
                nextGuesser();
                break;
            default:
                console.warn("Invalid type: " + event.message.type);
        }
    }

    function onError(event){
        console.warn('error received');
        console.log(event);
        $.post('debug.php', { msg : event.message.type }, function(data){ console.log('error logged at debug.php')});
        $messages.html(event.message.type);
    }
}

function initGame(){
    game = new Game();
}

// initialize
$(function(){
    initGame();
    initReceiver();
});

//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/array/shuffle [v1.0]
function shuffle(o){
    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};
<commit_msg>add notification for queued players.<commit_after>// error constants
SENT_BLANK_NAME  = 1;
ALREADY_HAVE_RESPONSES = 2;

function Player(name, ID, channel) {
    var that = this;
    this.name    = name;
    // this.picture = picture;
    this.ID      = ID;
    this.channel = channel;

    this.isOut   = false;
    this.isGone  = false;

    this.score = 0;

    this.getScore = function() {
        return that.score;
    };

    this.incrementScore = function() {
        that.score++;
    };

    this.getID = function() {
        return that.ID;
    };

    this.didGetOut = function(){
        this.isOut = true;
    }

    this.didLeave = function(){
        this.isGone = true;
    }

}

function Response(response, responseID, userID, channel){
    this.response   = response;
    this.responseID = responseID;
    this.userID     = userID;
    this.channel    = channel;
    this.isActive   = true;

    this.getJSON = function(){
        var thisObj = new Object();
        thisObj.response   = this.response;
        thisObj.responseID = this.responseID;
        return JSON.stringify(thisObj);
    }
}

function Game() {
    var that= this;

    this.players     = new Array();
    this.playerQueue = new Array();
    this.responses   = new Array();
    this.cues        = new Array("Things that hang", "Things that are poor", "Things that nobody wants");
    this.cues        = shuffle(this.cues); // randomize each playthrough

    this.reader  = 0;
    this.guesser = 0;

    this.isBetweenRounds = true;

    this.addPlayer = function(player){
        while(typeof this.players[player.ID] != 'undefined'){
            player.ID++;
        }
        this.players[player.ID] = player;
        player.channel.send({ type : 'didJoin', number : player.ID });
        console.log("Added player " + player.name);
    }

    this.queuePlayer = function(player){
        this.playerQueue.push(player);
        player.channel.send({ type: 'didQueue' });
        console.log("Queued player " + player.name);
    }

    this.deletePlayer = function(id){
        console.log("Deleted player " + this.players[i].name);
        this.players.splice(id, 1);
    }

    this.getNextCue = function(){
        var lastIndex = this.cues.length - 1;
        this.currentCue = this.cues[lastIndex];
        this.cues.splice(lastIndex, 1);
        return this.currentCue;
    }
}

function getPlayerIdByChannel(channel){
    for(var i = 0; i < game.players.length; i++){
        if(game.players[i].channel == channel){
            return i;
        }
    }
}

// update the list of players on screen
function updatePlayerList(){
    var tempList = game.players;
    tempList.sort();

    $('#playerlist').empty();
    for(player in tempList){
        var playerHTML = '
' + player.score + ' ' + player.name + '
';
        $('#playerlist').append()
    }

    $('#notification').slideUp();
    $('#notification').empty();

    if(game.playerQueue.length > 0){
        var notificationHTML;
        if(game.playerQueue.length == 1){
            notificationHTML = '' + game.playerQueue[0].name + '';
        }
        else{
            for(int i = 0; i < game.playerQueue.length; i++){
                notificationHTML += '' + game.playerQueue[i].name + '';

                // if there's one more player left, put oxford comma + 'and'
                if((i + 2) == game.playerQueue.length){
                    notificationHTML += ', and ';
                }

                // if there's more than one player left, just put a comma
                else if((i + 2) < game.playerQueue.length){
                    notificationHTML += ', ';
                }
            }
        }

        notificationHTML += ' will join the game after this round.';
        $('#notification').html(notificationHTML);
    }
}

function joinPlayer(channel, name){
    if(name === ""){
        channel.send({ type : 'error', value : SENT_BLANK_NAME });
        return;
    }

    var newID = game.players.length;
    var newPlayer = new Player(name, newID, channel);

    if(game.isBetweenRounds){
        game.addPlayer(newPlayer);
    }
    else{
        game.queuePlayer(newPlayer);
    }
}

function leavePlayer(channel){
    // find player, set them as out for the round and set didLeave = true so they're removed before next round
    var playerID = getPlayerIdByChannel(channel);
    game.players[playerID].didGetOut();
    game.players[playerID].didLeave();

    // if they haven't submitted a response yet, leave immediately
    var responseReceived = false;

    for(var i = 0; i < responses.length; i++){
        if(responses[i].userID == playerID){
            responseReceived = true;
        }
    }

    if(!responseReceived){
        game.deletePlayer(playerID);
    }

    // if they're currently reader or currently guessing, advance to the first or next guesser
    if(game.reader == playerID || game.guesser == playerID){
        nextGuesser();
    }

    return; // only one player per channel
}

function submitResponse(channel, response){
    var userID      = getPlayerIdByChannel(channel);
    var responseID  = game.responses.length;
    var newResponse = new Response(response, responseID, userID, channel);

    // check if there are already max number of responses
    if(game.responses.length >= game.players.length){
        channel.send({ type : 'error', value : ALREADY_HAVE_RESPONSES });
        return;
    }

    // check if there's already a response from this channel
    for(var i = 0; i < game.responses.length; i++){
        if(game.responses[i].channel == channel){
            // update the response
            game.responses[i].response = response;
            return;
        }
    }

    game.responses.push(newResponse);

    // if all responses are in, move to reading phase
    if(responseID == game.players.length){
        startReading();
    }
}

function getAllResponsesJSON(){
    var response = new Object();
    for(var i = 0; i < game.responses.length; i++){
        if(game.responses[i].isActive){
            response[i] = game.responses[i].getJSON();
        }
    }
    var responseJSON = JSON.stringify(response);
    return responseJSON;
}

function startReading(){
    // randomize order of responses
    game.responses = shuffle(game.responses);

    // send all responses to the reader.
    game.players[game.reader].channel.send(getAllResponsesJSON());
}

function startGuessing(){
    // send guesser all responses
    game.players[game.guesser].channel.send(getAllResponsesJSON());
}

function submitGuess(channel, guess){
    var responseGuessed = guess.guessResponseId;
    var playerGuessed   = guess.guessPlayerNumber;
    var guesserID       = getPlayerIdByChannel(channel);

    // if you're right, you get a point, a response is pulled, and you can keep guessing.
    if(game.responses[responseGuessed].userID == playerGuessed){
        channel.send({ type : 'guessResponse', value : true });
        game.players[guesserID].incrementScore();
        game.players[playerGuessed].didGetOut();
        game.responses[responseGuessed].isActive = false;
    }
    else{
        // if the person is out, you're dumb and your turn is over.
        if(game.players[playerGuessed].isOut){
            // TODO shame notification
        }
        channel.send({ type : 'guessResponse', value : false });

        // next guesser's turn
        nextGuesser();
    }
}

function nextGuesser(){
    // round is over when only reader's and your own responses are left
    if(game.responses.length == 2){
        newGrind();
    }
    else{
        var nextGuesser = game.guesser++;
        nextGuesser = nextGuesser % game.players.length;
        game.guesser = nextGuesser;

        // notify next guesser
        game.players[nextGuesser].channel.send({ type : 'guesser' });

        startGuessing();
    }
}

function startNextRound(){
    game.isBetweenRounds = false;

    // show next question
    $('#content h1').html(game.getNextCue());

    // let everyone know the round has started
    for(var i = 0; i < game.players.length; i++){
        game.players[i].channel.send({ type : 'roundStarted', cue: game.cues[currentCue] });
    }
}

// prepare for next question
function newGrind(){
    game.isBetweenRounds = true;

    // delete players who left
    for(var i = 0; i < game.players.length; i++){
        if(game.players[i].isGone){
            game.deletePlayer(i);
        }
    }

    // add players who are queued
    for(player in game.playerQueue){
        game.addPlayer(player);
    }

    // clear player queue
    game.playerQueue = [];

    // update player list
    updatePlayerList();

    // clear responses
    game.responses = [];

    // notify next reader
    game.reader++;
    game.reader = game.reader % game.players.length;

    // update all clients' user list and scores
    for(var i = 0; i < game.players.length; i++){
        game.players[i].channel.send({type : 'gameSync', players : playerJSON, reader : game.reader });
    }
}

function initReceiver(){
    var receiver = new cast.receiver.Receiver('1f96e9a0-9cf0-4e61-910e-c76f33bd42a2', ['com.bears.triviaCast'], "", 5),
        channelHandler = new cast.receiver.ChannelHandler('com.bears.triviaCast'),
        $messages = $('.messages');

    channelHandler.addChannelFactory(
        receiver.createChannelFactory('com.bears.triviaCast'));

    receiver.start();
    console.log('receiver started');

    channelHandler.addEventListener(cast.receiver.Channel.EventType.MESSAGE, onMessage.bind(this));
    channelHandler.addEventListener(cast.receiver.Channel.EventType.ERROR, onError.bind(this));

    function onMessage(event) {
        console.log('type = ' + event.message.type);
        console.log(event);

        switch(event.message.type){
            case "join":
                joinPlayer(event.target, event.message.name);
                break;
            case "leave":
                leavePlayer(event.target);
                break;
            case "submitResponse": // user submits a response
                storeResponse(event.target, event.message.response);
                break;
            case "submitGuess": // user submits a guess
                tryGuess(event.target, event.message.guess);
                break;
            case "nextRound":
                startNextRound();
                break;
            case "readerIsDone":
                nextGuesser();
                break;
            default:
                console.warn("Invalid type: " + event.message.type);
        }
    }

    function onError(event){
        console.warn('error received');
        console.log(event);
        $.post('debug.php', { msg : event.message.type }, function(data){ console.log('error logged at debug.php')});
        $messages.html(event.message.type);
    }
}

function initGame(){
    game = new Game();
}

// initialize
$(function(){
    initGame();
    initReceiver();
});

//+ Jonas Raoni Soares Silva
//@ http://jsfromhell.com/array/shuffle [v1.0]
function shuffle(o){
    for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
    return o;
};
<|endoftext|>


<commit_before>module.exports = {
  siteMetadata: {
    title: `Educodar - Aulas de programação grátis para jovens`,
    titleTemplate: `%s · Educodar`,
    siteUrl: `https://educodar.com.br`,
    description: `Projeto de ensino de programação básica e exposição a T.I. para estudantes de escolas de comunidades. Iniciativa sem fins lucrativos. Cursos e alimentação gratuitos.`,
    image: "/images/logo.png",
  },
  plugins: [
    {
      resolve: `gatsby-plugin-csp`,
      options: {
        disableOnDev: false,
        directives: {
          "script-src": "'self' 'unsafe-inline'",
          "style-src": "'self' 'unsafe-inline'",
        },
      },
    },
    {
      resolve: `gatsby-plugin-html-attributes`,
      options: {
        lang: `pt-br`,
      },
    },
    {
      resolve: `gatsby-plugin-layout`,
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Educodar`,
        short_name: `Educodar`,
        start_url: `/`,
        background_color: `#00BCCD`,
        theme_color: `#0078BF`,
        icon: `src/images/icon.png`,
        display: `minimal-ui`,
        scope: `/`,
      },
    },
    {
      resolve: `gatsby-plugin-netlify`,
      options: {
        headers: {
          "/*": [
            "Content-Security-Policy: base-uri 'self'; default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; object-src 'none'; form-action 'self'; font-src 'self' data:; connect-src 'self'; img-src 'self' data:;",
            "Feature-Policy: ambient-light-sensor 'self'; autoplay 'self'; camera 'none'; encrypted-media 'self'; fullscreen 'none'; geolocation 'self'; gyroscope 'self'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; speaker 'self'; sync-xhr 'self'; usb 'none'; vr 'none'",
            "Referrer-Policy: no-referrer",
            "X-Content-Type-Options: nosniff",
            "X-Frame-Options: DENY",
            "X-XSS-Protection: 1; mode=block",
          ],
        },
      },
    },
    {
      resolve: `gatsby-plugin-react-helmet`,
    },
    {
      resolve: `gatsby-plugin-robots-txt`
    },
    {
      resolve: `gatsby-plugin-sitemap`,
    },
  ],
}
<commit_msg>Prettify on gatsby-config.js<commit_after>module.exports = {
  siteMetadata: {
    title: `Educodar - Aulas de programação grátis para jovens`,
    titleTemplate: `%s · Educodar`,
    siteUrl: `https://educodar.com.br`,
    description: `Projeto de ensino de programação básica e exposição a T.I. para estudantes de escolas de comunidades. Iniciativa sem fins lucrativos. Cursos e alimentação gratuitos.`,
    image: "/images/logo.png",
  },
  plugins: [
    {
      resolve: `gatsby-plugin-csp`,
      options: {
        disableOnDev: false,
        directives: {
          "script-src": "'self' 'unsafe-inline'",
          "style-src": "'self' 'unsafe-inline'",
        },
      },
    },
    {
      resolve: `gatsby-plugin-html-attributes`,
      options: {
        lang: `pt-br`,
      },
    },
    {
      resolve: `gatsby-plugin-layout`,
    },
    {
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Educodar`,
        short_name: `Educodar`,
        start_url: `/`,
        background_color: `#00BCCD`,
        theme_color: `#0078BF`,
        icon: `src/images/icon.png`,
        display: `minimal-ui`,
        scope: `/`,
      },
    },
    {
      resolve: `gatsby-plugin-netlify`,
      options: {
        headers: {
          "/*": [
            "Content-Security-Policy: base-uri 'self'; default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; object-src 'none'; form-action 'self'; font-src 'self' data:; connect-src 'self'; img-src 'self' data:;",
            "Feature-Policy: ambient-light-sensor 'self'; autoplay 'self'; camera 'none'; encrypted-media 'self'; fullscreen 'none'; geolocation 'self'; gyroscope 'self'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; speaker 'self'; sync-xhr 'self'; usb 'none'; vr 'none'",
            "Referrer-Policy: no-referrer",
            "X-Content-Type-Options: nosniff",
            "X-Frame-Options: DENY",
            "X-XSS-Protection: 1; mode=block",
          ],
        },
      },
    },
    {
      resolve: `gatsby-plugin-react-helmet`,
    },
    {
      resolve: `gatsby-plugin-robots-txt`,
    },
    {
      resolve: `gatsby-plugin-sitemap`,
    },
  ],
}
<|endoftext|>


<commit_before>// Utilities
import React from 'react';
import PropTypes from 'prop-types';
import { map, startCase, keyBy, filter, isEmpty } from 'lodash';
import { RestApiClient } from '@dosomething/gateway';

// Components
import Post from '../Post';
import Empty from '../Empty';
import Quantity from '../Quantity';
import TextBlock from '../TextBlock';
import HistoryModal from '../HistoryModal';
import UploaderModal from '../UploaderModal';
import ModalContainer from '../ModalContainer';
import MetaInformation from '../MetaInformation';
import UserInformation from '../Users/UserInformation';

class PostGroup extends React.Component {
  getPostsByStatus(status) {
    const posts = filter(this.props.posts, post => post.status === this.props.groupType);

    return posts;
  }

  render() {
    const posts = this.getPostsByStatus(this.props.groupType);

    return (

{startCase(this.props.groupType)}

        {
          ! isEmpty(posts) ?
            map(posts, (post, key) => (<Post
              key={key}
              post={post}
              displayUser={false}
              signup={this.props.signup}
              onUpdate={this.props.onUpdate}
              onTag={this.props.onTag}
              deletePost={this.props.deletePost}
              showSiblings={false}
            />))
            :
            <Empty header={`This user has no ${this.props.groupType} posts`} />
        }

    );
  }
}

PostGroup.propTypes = {
  posts: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  signup: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  onUpdate: PropTypes.func.isRequired,
  onTag: PropTypes.func.isRequired,
  deletePost: PropTypes.func.isRequired,
  groupType: PropTypes.string.isRequired,
};

class Signup extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      signup: {},
      posts: {},
      displayHistoryModal: false,
      historyModalId: null,
      displayUploaderModal: false,
    };

    this.api = new RestApiClient(window.location.origin, {
        headers: { 'Authorization' : `Bearer ${window.AUTH}`}
    });
    this.updatePost = this.updatePost.bind(this);
    this.updateTag = this.updateTag.bind(this);
    this.updateQuantity = this.updateQuantity.bind(this);
    this.showHistory = this.showHistory.bind(this);
    this.hideHistory = this.hideHistory.bind(this);
    this.deletePost = this.deletePost.bind(this);
    this.deleteSignup = this.deleteSignup.bind(this);
    this.showUploader = this.showUploader.bind(this);
    this.hideUploader = this.hideUploader.bind(this);
    this.submitReportback = this.submitReportback.bind(this);
    this.updateSignup = this.updateSignup.bind(this);
  }

  componentDidMount() {
    this.getUserActivity(this.props.signup_id);
  }

  /**
   * Gets the user activity for the specified user and update state.
   *
   * @param {String} id
   * @return {Object}
   */
  getUserActivity(id) {
    this.api.get('api/v2/activity', {
      filter: {
        id,
      },
    }).then(json => this.setState({
      signup: json.data[0],
      posts: keyBy(json.data[0].posts.data, 'id'),
    }));
  }

  // Open the history modal of the given post
  showHistory(postId, event) {
    event.preventDefault();

    this.api.get('api/v2/events', {
      filter: {
        signup_id: this.props.signup_id,
      },
    }).then((result) => {
      this.setState((previousState) => {
        const newState = { ...previousState };

        newState.displayHistoryModal = true;
        newState.historyModalId = postId;
        newState.signupEvents = Object.values(result.data);

        return newState;
      });
    });
  }

  // Close the open history modal
  hideHistory(event) {
    if (event) {
      event.preventDefault();
    }

    this.setState({
      displayHistoryModal: false,
      historyModalId: null,
    });
  }

  // Open the uploader modal to upload a new post
  showUploader(campaign, event) {
    event.preventDefault();

    this.setState({
      displayUploaderModal: true,
      campaign,
    });
  }

  // Close the open uploader modal
  hideUploader(event) {
    if (event) {
      event.preventDefault();
    }

    this.setState({
      displayUploaderModal: false,
      campaign: null,
    });
  }

  // Updates a post status.
  updatePost(postId, fields) {
    fields.post_id = postId;

    const request = this.api.put('reviews', fields);

    request.then((result) => {
      this.setState((previousState) => {
        const newState = { ...previousState };

        newState.posts[postId].status = fields.status;

        return newState;
      });
    });
  }

  // Tag a post.
  updateTag(postId, tag) {
    const fields = {
      post_id: postId,
      tag_name: tag,
    };

    const response = this.api.post('tags', fields);

    return response.then((result) => {
      this.setState((previousState) => {
        const newState = { ...previousState };

        newState.posts[postId] = result.data;

        return newState;
      });
    });
  }

  // Update a signups quantity.
  updateQuantity(signup, newQuantity) {
    // Fields to send to /posts
    const fields = {
      northstar_id: signup.northstar_id,
      campaign_id: signup.campaign_id,
      campaign_run_id: signup.campaign_run_id,
      quantity: newQuantity,
    };

    // Make API request to Rogue to update the quantity on the backend
    const request = this.api.post('posts', fields);

    request.then((result) => {
      // Update the state
      this.setState((previousState) => {
        const newState = { ...previousState };
        newState.signup.quantity = result.quantity;

        return newState;
      });
    });

    // Close the modal
    this.hideHistory();
  }

  // Set newstate with updated quantity and why_participated.
  updateSignup(signup) {
    this.setState((previousState) => {
      const newState = { ...previousState };

      newState.signup.quantity = signup.quantity;
      newState.signup.why_participated = signup.why_participated;

      return newState;
    });
  }

  // Delete a post.
  deletePost(postId, event) {
    event.preventDefault();
    const confirmed = confirm('🚨🔥🚨 Are you sure you want to delete this? 🚨🔥🚨');

    if (confirmed) {
      // Make API request to Rogue to update the quantity on the backend
      const response = this.api.delete('posts/'.concat(postId));

      response.then((result) => {
        // Update the state
        this.setState((previousState) => {
          const newState = { ...previousState };

          // Remove the deleted post from the state
          delete (newState.posts[postId]);

          // Return the new state
          return newState;
        });
      });
    }
  }

  // Delete a signup.
  deleteSignup(signupId, event) {
    event.preventDefault();
    const confirmed = confirm('🚨🔥🚨 Are you sure you want to delete this? 🚨🔥🚨');

    if (confirmed) {
      // Make API request to Rogue to delete the signup on the backend.
      const response = this.api.delete(`api/v3/signups/${signupId}`);

      response.then((result) => {
        // Update the state.
        this.setState((previousState) => {
          const newState = { ...previousState };

          // Set the deleted signup to undefined.
          newState.signup = undefined;

          // Return the new state
          return newState;
        });
      });
    }
  }

  // Submit a new reportback on behalf of the user.
  submitReportback(reportback) {
    // Fields to send to /posts
    const fields = {
      northstar_id: reportback.northstarId,
      campaign_id: reportback.campaignId,
      campaign_run_id: reportback.campaignRunId,
      quantity: reportback.impact,
      why_participated: reportback.whyParticipated,
      caption: reportback.caption,
      source: reportback.source,
      status: reportback.status,
      file: reportback.media.dataURL,
      remote_addr: '',
    };

    // Make API request to Rogue to upload post
    const request = this.api.post('posts', fields);

    request.then((result) => {
      // Update the state
      this.setState((previousState) => {
        const newState = { ...previousState };
        const post = keyBy(result, 'id');
        const key = Number(Object.keys(post));

        newState.successfulSubmission = {
          success: {
            message: 'Thanks for the photo! It has been automatically approved.',
          },
        };

        newState.posts[key] = post[key];

        return newState;
      });
    })
      .catch(error =>
        this.setState((previousState) => {
          const newState = { ...previousState };

          newState.successfulSubmission = {
            error: {
              message: 'Oops, looks like something went wrong.',
            },
          };

          return newState;
        }),
      );
  }

  render() {
    const user = this.props.user;
    const signup = this.state.signup;
    const campaign = this.props.campaign;
    const posts = this.state.posts;

    if (! this.state.signup) {
      return <Empty header={`This signup has been deleted`} />;
    }

    return (

            <UserInformation user={user}>
              <TextBlock title="Why Statement" content={signup.why_participated} />
            </UserInformation>

            <Quantity quantity={signup.quantity} noun={campaign.reportback_info.noun} verb={campaign.reportback_info.verb} />

               this.showHistory(signup.signup_id, e)}>Edit | Show History

              <ModalContainer>
                {this.state.displayHistoryModal ?
                  <HistoryModal
                    id={this.state.historyModalId}
                    onUpdate={this.updateQuantity}
                    onClose={e => this.hideHistory(e)}
                    signup={signup}
                    campaign={campaign}
                    signupEvents={this.state.signupEvents}
                  />
                  : null}
              </ModalContainer>

               this.showUploader(signup, e)}>Upload Photo

              <ModalContainer>
                {this.state.displayUploaderModal ?
                  <UploaderModal
                    onClose={e => this.hideUploader(e)}
                    signup={signup}
                    campaign={campaign}
                    submitReportback={this.submitReportback}
                    updateSignup={this.updateSignup}
                    success={this.state.successfulSubmission}
                  />
                  : null}
              </ModalContainer>

              <button className="button delete -tertiary" onClick={e => this.deleteSignup(signup.signup_id, e)}>Delete Signup</button>

            <MetaInformation
              title="Meta"
              details={{
                'Signup ID': signup.signup_id,
                'Northstar ID': user.id,
                'Signup Source': signup.signup_source,
                'Created At': new Date(signup.created_at).toDateString(),
              }}
            />

        {
          map(['pending', 'accepted', 'rejected'], (status, key) => <PostGroup key={key} groupType={status} posts={posts} signup={signup} onUpdate={this.updatePost} onTag={this.updateTag} deletePost={this.deletePost} />)
        }

    );
  }
}

Signup.propTypes = {
  signup_id: PropTypes.number.isRequired,
  user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  campaign: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

export default Signup;
<commit_msg>removes history modal from /signups page and updates to show each post's individual quantity<commit_after>// Utilities
import React from 'react';
import PropTypes from 'prop-types';
import { map, startCase, keyBy, filter, isEmpty } from 'lodash';
import { RestApiClient } from '@dosomething/gateway';

// Components
import Post from '../Post';
import Empty from '../Empty';
import Quantity from '../Quantity';
import TextBlock from '../TextBlock';
// import HistoryModal from '../HistoryModal';
import UploaderModal from '../UploaderModal';
import ModalContainer from '../ModalContainer';
import MetaInformation from '../MetaInformation';
import UserInformation from '../Users/UserInformation';

class PostGroup extends React.Component {
  getPostsByStatus(status) {
    const posts = filter(this.props.posts, post => post.status === this.props.groupType);

    return posts;
  }

  render() {
    const posts = this.getPostsByStatus(this.props.groupType);

    return (

{startCase(this.props.groupType)}

        {
          ! isEmpty(posts) ?
            map(posts, (post, key) => (<Post
              key={key}
              post={post}
              displayUser={false}
              signup={this.props.signup}
              onUpdate={this.props.onUpdate}
              onTag={this.props.onTag}
              deletePost={this.props.deletePost}
              showSiblings={false}
              campaign={this.props.campaign}
              showQuantity
            />))
            :
            <Empty header={`This user has no ${this.props.groupType} posts`} />
        }

    );
  }
}

PostGroup.propTypes = {
  posts: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  signup: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  onUpdate: PropTypes.func.isRequired,
  onTag: PropTypes.func.isRequired,
  deletePost: PropTypes.func.isRequired,
  groupType: PropTypes.string.isRequired,
};

class Signup extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      signup: {},
      posts: {},
      displayHistoryModal: false,
      historyModalId: null,
      displayUploaderModal: false,
    };

    this.api = new RestApiClient(window.location.origin, {
        headers: { 'Authorization' : `Bearer ${window.AUTH}`}
    });
    this.updatePost = this.updatePost.bind(this);
    this.updateTag = this.updateTag.bind(this);
    this.updateQuantity = this.updateQuantity.bind(this);
    // this.showHistory = this.showHistory.bind(this);
    // this.hideHistory = this.hideHistory.bind(this);
    this.deletePost = this.deletePost.bind(this);
    this.deleteSignup = this.deleteSignup.bind(this);
    this.showUploader = this.showUploader.bind(this);
    this.hideUploader = this.hideUploader.bind(this);
    this.submitReportback = this.submitReportback.bind(this);
    this.updateSignup = this.updateSignup.bind(this);
  }

  componentDidMount() {
    this.getUserActivity(this.props.signup_id);
  }

  /**
   * Gets the user activity for the specified user and update state.
   *
   * @param {String} id
   * @return {Object}
   */
  getUserActivity(id) {
    this.api.get('api/v2/activity', {
      filter: {
        id,
      },
    }).then(json => this.setState({
      signup: json.data[0],
      posts: keyBy(json.data[0].posts.data, 'id'),
    }));
  }

  // Open the history modal of the given post
  // showHistory(postId, event) {
  //   event.preventDefault();

  //   this.api.get('api/v2/events', {
  //     filter: {
  //       signup_id: this.props.signup_id,
  //     },
  //   }).then((result) => {
  //     this.setState((previousState) => {
  //       const newState = { ...previousState };

  //       newState.displayHistoryModal = true;
  //       newState.historyModalId = postId;
  //       newState.signupEvents = Object.values(result.data);

  //       return newState;
  //     });
  //   });
  // }

  // Close the open history modal
  // hideHistory(event) {
  //   if (event) {
  //     event.preventDefault();
  //   }

  //   this.setState({
  //     displayHistoryModal: false,
  //     historyModalId: null,
  //   });
  // }

  // Open the uploader modal to upload a new post
  showUploader(campaign, event) {
    event.preventDefault();

    this.setState({
      displayUploaderModal: true,
      campaign,
    });
  }

  // Close the open uploader modal
  hideUploader(event) {
    if (event) {
      event.preventDefault();
    }

    this.setState({
      displayUploaderModal: false,
      campaign: null,
    });
  }

  // Updates a post status.
  updatePost(postId, fields) {
    fields.post_id = postId;

    const request = this.api.put('reviews', fields);

    request.then((result) => {
      this.setState((previousState) => {
        const newState = { ...previousState };

        newState.posts[postId].status = fields.status;

        return newState;
      });
    });
  }

  // Tag a post.
  updateTag(postId, tag) {
    const fields = {
      post_id: postId,
      tag_name: tag,
    };

    const response = this.api.post('tags', fields);

    return response.then((result) => {
      this.setState((previousState) => {
        const newState = { ...previousState };

        newState.posts[postId] = result.data;

        return newState;
      });
    });
  }

  // Update a signups quantity.
  updateQuantity(signup, newQuantity) {
    // Fields to send to /posts
    const fields = {
      northstar_id: signup.northstar_id,
      campaign_id: signup.campaign_id,
      campaign_run_id: signup.campaign_run_id,
      quantity: newQuantity,
    };

    // Make API request to Rogue to update the quantity on the backend
    const request = this.api.post('posts', fields);

    request.then((result) => {
      // Update the state
      this.setState((previousState) => {
        const newState = { ...previousState };
        newState.signup.quantity = result.quantity;

        return newState;
      });
    });

    // Close the modal
    this.hideHistory();
  }

  // Set newstate with updated quantity and why_participated.
  updateSignup(signup) {
    this.setState((previousState) => {
      const newState = { ...previousState };

      newState.signup.quantity = signup.quantity;
      newState.signup.why_participated = signup.why_participated;

      return newState;
    });
  }

  // Delete a post.
  deletePost(postId, event) {
    event.preventDefault();
    const confirmed = confirm('🚨🔥🚨 Are you sure you want to delete this? 🚨🔥🚨');

    if (confirmed) {
      // Make API request to Rogue to update the quantity on the backend
      const response = this.api.delete('posts/'.concat(postId));

      response.then((result) => {
        // Update the state
        this.setState((previousState) => {
          const newState = { ...previousState };

          // Remove the deleted post from the state
          delete (newState.posts[postId]);

          // Return the new state
          return newState;
        });
      });
    }
  }

  // Delete a signup.
  deleteSignup(signupId, event) {
    event.preventDefault();
    const confirmed = confirm('🚨🔥🚨 Are you sure you want to delete this? 🚨🔥🚨');

    if (confirmed) {
      // Make API request to Rogue to delete the signup on the backend.
      const response = this.api.delete(`api/v3/signups/${signupId}`);

      response.then((result) => {
        // Update the state.
        this.setState((previousState) => {
          const newState = { ...previousState };

          // Set the deleted signup to undefined.
          newState.signup = undefined;

          // Return the new state
          return newState;
        });
      });
    }
  }

  // Submit a new reportback on behalf of the user.
  submitReportback(reportback) {
    // Fields to send to /posts
    const fields = {
      northstar_id: reportback.northstarId,
      campaign_id: reportback.campaignId,
      campaign_run_id: reportback.campaignRunId,
      quantity: reportback.impact,
      why_participated: reportback.whyParticipated,
      caption: reportback.caption,
      source: reportback.source,
      status: reportback.status,
      file: reportback.media.dataURL,
      remote_addr: '',
    };

    // Make API request to Rogue to upload post
    const request = this.api.post('posts', fields);

    request.then((result) => {
      // Update the state
      this.setState((previousState) => {
        const newState = { ...previousState };
        const post = keyBy(result, 'id');
        const key = Number(Object.keys(post));

        newState.successfulSubmission = {
          success: {
            message: 'Thanks for the photo! It has been automatically approved.',
          },
        };

        newState.posts[key] = post[key];

        return newState;
      });
    })
      .catch(error =>
        this.setState((previousState) => {
          const newState = { ...previousState };

          newState.successfulSubmission = {
            error: {
              message: 'Oops, looks like something went wrong.',
            },
          };

          return newState;
        }),
      );
  }

  render() {
    const user = this.props.user;
    const signup = this.state.signup;
    const campaign = this.props.campaign;
    const posts = this.state.posts;

    if (! this.state.signup) {
      return <Empty header={`This signup has been deleted`} />;
    }

    return (

            <UserInformation user={user}>
              <TextBlock title="Why Statement" content={signup.why_participated} />
            </UserInformation>

            <Quantity quantity={signup.quantity} noun={campaign.reportback_info.noun} verb={campaign.reportback_info.verb} />
            {/*

               this.showHistory(signup.signup_id, e)}>Edit | Show History

              <ModalContainer>
                {this.state.displayHistoryModal ?
                  <HistoryModal
                    id={this.state.historyModalId}
                    onUpdate={this.updateQuantity}
                    onClose={e => this.hideHistory(e)}
                    signup={signup}
                    campaign={campaign}
                    signupEvents={this.state.signupEvents}
                  />
                  : null}
              </ModalContainer>

            */}

               this.showUploader(signup, e)}>Upload Photo

              <ModalContainer>
                {this.state.displayUploaderModal ?
                  <UploaderModal
                    onClose={e => this.hideUploader(e)}
                    signup={signup}
                    campaign={campaign}
                    submitReportback={this.submitReportback}
                    updateSignup={this.updateSignup}
                    success={this.state.successfulSubmission}
                  />
                  : null}
              </ModalContainer>

              <button className="button delete -tertiary" onClick={e => this.deleteSignup(signup.signup_id, e)}>Delete Signup</button>

            <MetaInformation
              title="Meta"
              details={{
                'Signup ID': signup.signup_id,
                'Northstar ID': user.id,
                'Signup Source': signup.signup_source,
                'Created At': new Date(signup.created_at).toDateString(),
              }}
            />

        {
          map(['pending', 'accepted', 'rejected'], (status, key) => <PostGroup key={key} groupType={status} posts={posts} signup={signup} onUpdate={this.updatePost} onTag={this.updateTag} deletePost={this.deletePost} campaign={campaign} />)
        }

    );
  }
}

Signup.propTypes = {
  signup_id: PropTypes.number.isRequired,
  user: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
  campaign: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types
};

export default Signup;
<|endoftext|>


<commit_before>'use strict';

var StatsWriterPlugin = require('./statsWriterPlugin');

/**
 * @private
 * @function
 * @param {String} filename
 * @returns {StatsWriterPlugin}
 */
function statsWriterPluginFactory(filename) {
    return new StatsWriterPlugin(filename);
}

/**
 * @private
 * @module webpack-glob/lib/statsWriterPluginFactory
 */
module.exports = statsWriterPluginFactory;
<commit_msg>docs(statsWriterPluginFactory): updates `jsdoc`<commit_after>'use strict';

var StatsWriterPlugin = require('./statsWriterPlugin');

/**
 * @function
 * @alias statsWriterPluginFactory
 * @param {String} filename
 * @returns {StatsWriterPlugin}
 */
function statsWriterPluginFactory(filename) {
    return new StatsWriterPlugin(filename);
}

/**
 * @private
 * @module webpack-glob/lib/statsWriterPluginFactory
 * @returns {statsWriterPluginFactory}
 */
module.exports = statsWriterPluginFactory;
<|endoftext|>