<commit_before>/*jshint node:true*/
'use strict';
var Q = require('q'),
	async = require('async'),
	fetchDropletCmd = require('./fetch-droplet');

/**
 *
 * @param {Speedboat} speedboat
 * @returns {Function}
 */
module.exports = function (speedboat) {

	var fetchDroplet = fetchDropletCmd(speedboat);

	return function provision (options) {

		var deferred = Q.defer(),
			promise = deferred.promise,
			destination = '/opt/' + options.configObject.destination;

		var dropletName = [options.subdomain, '.', options.configObject.hostname].join('');
		fetchDroplet(dropletName).then(function (droplet) {

			if (!droplet) {
				throw new Error("Unable to retrieve droplet at " + dropletName);
			}

			// We need the droplet in the next step
			return droplet;

		}).then(function (droplet) {

			async.series([

				// The app should already be stopped, but no way for us
				// to check that since we don't know what is involved in
				// that process, instead, just march forward...

				// Clear out any previous project files and recreate dir
				speedboat.plot(droplet.id, [
					'rm -rf ' + destination + '; ',
					'mkdir ' + destination
				].join(' ')),

				// Copy files from temp location to destination
				speedboat.plot(droplet.id, [
					'cp -rf /opt/' + options.configObject.temp + ' ' + destination
				].join(' ')),

				// run the deploy step of the scripts block (if it exists)
				speedboat.plot(droplet.id, [
					'cd ' + destination + '; ',
					'npm run-script deploy'
				].join(' '))

			], function (err, results) {
				if (err) {
					return deferred.reject(err);
				}
				deferred.resolve(results);
			});

		}).fail(function (err) {
			deferred.reject(err);
		})
		.done();

		return promise;
	};
};<commit_msg>More shell command fixing<commit_after>/*jshint node:true*/
'use strict';
var Q = require('q'),
	async = require('async'),
	fetchDropletCmd = require('./fetch-droplet');

/**
 *
 * @param {Speedboat} speedboat
 * @returns {Function}
 */
module.exports = function (speedboat) {

	var fetchDroplet = fetchDropletCmd(speedboat);

	return function provision (options) {

		var deferred = Q.defer(),
			promise = deferred.promise,
			destination = '/opt/' + options.configObject.destination;

		var dropletName = [options.subdomain, '.', options.configObject.hostname].join('');
		fetchDroplet(dropletName).then(function (droplet) {

			if (!droplet) {
				throw new Error("Unable to retrieve droplet at " + dropletName);
			}

			// We need the droplet in the next step
			return droplet;

		}).then(function (droplet) {

			async.series([

				// The app should already be stopped, but no way for us
				// to check that since we don't know what is involved in
				// that process, instead, just march forward...

				// Clear out any previous project files and recreate dir
				speedboat.plot(droplet.id, [
					'rm -rf ' + destination
				].join(' ')),

				// Copy files from temp location to destination
				speedboat.plot(droplet.id, [
					'cp -ra /opt/' + options.configObject.temp + '/. ' + destination
				].join(' ')),

				// run the deploy step of the scripts block (if it exists)
				speedboat.plot(droplet.id, [
					'cd ' + destination + '; ',
					'npm run-script deploy'
				].join(' '))

			], function (err, results) {
				if (err) {
					return deferred.reject(err);
				}
				deferred.resolve(results);
			});

		}).fail(function (err) {
			deferred.reject(err);
		})
		.done();

		return promise;
	};
};<|endoftext|>


<commit_before>// 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
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

import {
  browsers,
  commentFlow,
  disableExperimentalPlugins,
  disableSourceMaps,
  jsx,
  minify,
  node,
  platforms,
  react,
  removeFlow,
  typeScript,
  watchBuild,
} from '../flags'
import {CONFIGS_DIR, setTargetDir} from '../paths'
import {join} from 'path'
import {exitOnError, runCommandSync} from '../system'
import {exitOnPackageNotFound} from '../util'

const PRESET_LOCATION = join(CONFIGS_DIR, 'babel-preset', 'index.js')
const BASIC_ARGS = [
  '"src"',
  '-d "build"',
  '--ignore "**/__tests__"',
  `--presets="${PRESET_LOCATION}"`,
]

function builder(yargs) {
  browsers(yargs)
  commentFlow(yargs)
  disableExperimentalPlugins(yargs)
  disableSourceMaps(yargs)
  jsx(yargs)
  minify(yargs)
  node(yargs)
  platforms(yargs)
  react(yargs)
  removeFlow(yargs)
  typeScript(yargs)
  watchBuild(yargs)
}

function handler(args) {
  setTargetDir(args.dir)
  exitOnPackageNotFound()

  let {disableSourceMaps, watch} = args
  let babelArgs = [...BASIC_ARGS]

  if (!disableSourceMaps)
    babelArgs.push('--source-maps inline')

  if (watch)
    babelArgs.push('--watch')

  exitOnError(runCommandSync('rimraf', {args: ['"build"']}))
  exitOnError(runCommandSync('babel', {args: babelArgs, env: args}))
}

export default {
  command: 'build [dir]',
  description: 'Build the project.',
  builder,
  handler,
}
<commit_msg>Update build command to run webpack too<commit_after>// 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
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

import {
  browsers,
  commentFlow,
  disableExperimentalPlugins,
  disableSourceMaps,
  jsx,
  minify,
  node,
  platforms,
  react,
  removeFlow,
  typeScript,
  watchBuild,
} from '../flags'
import {CONFIGS_DIR, setTargetDir} from '../paths'
import {getSettings} from '../settings'
import {join} from 'path'
import {exitOnError, runCommandSync} from '../system'
import {exitOnPackageNotFound} from '../util'

const PRESET_LOCATION = join(CONFIGS_DIR, 'babel-preset', 'index.js')
const BASIC_ARGS = [
  '"src"',
  '-d "build"',
  '--ignore "**/__tests__"',
  `--presets="${PRESET_LOCATION}"`,
]

function builder(yargs) {
  browsers(yargs)
  commentFlow(yargs)
  disableExperimentalPlugins(yargs)
  disableSourceMaps(yargs)
  jsx(yargs)
  minify(yargs)
  node(yargs)
  platforms(yargs)
  react(yargs)
  removeFlow(yargs)
  typeScript(yargs)
  watchBuild(yargs)
}

function handler(args) {
  setTargetDir(args.dir)
  exitOnPackageNotFound()

  exitOnError(runCommandSync('rimraf', {args: ['"build"']}))

  let {bundle} = getSettings()
  if (bundle)
    runWebpack(args)
  else
    runBabel(args)
}

function runBabel(args) {
  let {disableSourceMaps, watch} = args
  let babelArgs = [...BASIC_ARGS]

  if (!disableSourceMaps)
    babelArgs.push('--source-maps')

  if (watch)
    babelArgs.push('--watch')

  exitOnError(runCommandSync('babel', {
    args: babelArgs,
    env: args
  }))
}

function runWebpack(args) {
  exitOnError(runCommandSync('webpack', {env: args}))
}

export default {
  command: 'build [dir]',
  description: 'Build the project.',
  builder,
  handler,
}
<|endoftext|>


<commit_before>const newContributor = require("fs").readFileSync("./src/templates/newContributor.md", "utf8");

exports.run = (client, comment, issue, repository) => {
  const commenter = comment.user.login;
  const repoName = repository.name;
  const repoOwner = repository.owner.login;
  if (issue.assignees && issue.assignees.find(assignee => assignee.login === commenter)) return client.newComment(issue, repository, "**ERROR:** You have already claimed this issue.");
  client.repos.checkCollaborator({owner: repoOwner, repo: repoName, username: commenter})
  .then((response) => {
    if (response.meta.status !== "204 No Content") return client.newComment(issue, repository, "**ERROR:** Unexpected response from GitHub API.");
    exports.claimIssue(client, comment, issue, repository);
  }, (response) => {
    if (response.headers.status !== "404 Not Found") return client.newComment(issue, repository, "**ERROR:** Unexpected response from GitHub API.");
    exports.addCollaborator(client, comment, issue, repository);
  });
};

exports.addCollaborator = (client, comment, issue, repository) => {
  const commenter = comment.user.login;
  const repoName = repository.name;
  const repoOwner = repository.owner.login;
  if (!client.cfg.addCollabPermission) return client.newComment(issue, repository, "**ERROR:** No new collaborator permission was specified in `src/config.js`.");
  client.repos.addCollaborator({owner: repoOwner, repo: repoName, username: commenter, permission: client.cfg.addCollabPermission})
  .then(exports.claimIssue(client, comment, issue, repository, true));
};

exports.claimIssue = (client, comment, issue, repository, newContrib) => {
  const commenter = comment.user.login;
  const issueNumber = issue.number;
  const repoName = repository.name;
  const repoOwner = repository.owner.login;
  client.issues.addAssigneesToIssue({owner: repoOwner, repo: repoName, number: issueNumber, assignees: [commenter]})
  .then(() => {
    if (newContrib) client.newComment(issue, repository, newContributor.replace("[commenter]", commenter));
    if (!client.cfg.inProgressLabel || issue.labels.find(label => label.name === client.cfg.inProgressLabel || issue.pull_request)) return;
    client.issues.addLabels({owner: repoOwner, repo: repoName, number: issueNumber, labels: [client.cfg.inProgressLabel]});
  });
};

exports.name = "claim";
exports.aliases = require("../config.js").claimCommands;
exports.args = false;
<commit_msg>issues: Use new contributor template bound to client.<commit_after>exports.run = (client, comment, issue, repository) => {
  const commenter = comment.user.login;
  const repoName = repository.name;
  const repoOwner = repository.owner.login;
  if (issue.assignees && issue.assignees.find(assignee => assignee.login === commenter)) return client.newComment(issue, repository, "**ERROR:** You have already claimed this issue.");
  client.repos.checkCollaborator({owner: repoOwner, repo: repoName, username: commenter})
  .then((response) => {
    if (response.meta.status !== "204 No Content") return client.newComment(issue, repository, "**ERROR:** Unexpected response from GitHub API.");
    exports.claimIssue(client, comment, issue, repository);
  }, (response) => {
    if (response.headers.status !== "404 Not Found") return client.newComment(issue, repository, "**ERROR:** Unexpected response from GitHub API.");
    exports.addCollaborator(client, comment, issue, repository);
  });
};

exports.addCollaborator = (client, comment, issue, repository) => {
  const commenter = comment.user.login;
  const repoName = repository.name;
  const repoOwner = repository.owner.login;
  if (!client.cfg.addCollabPermission) return client.newComment(issue, repository, "**ERROR:** No new collaborator permission was specified in `src/config.js`.");
  client.repos.addCollaborator({owner: repoOwner, repo: repoName, username: commenter, permission: client.cfg.addCollabPermission})
  .then(exports.claimIssue(client, comment, issue, repository, true));
};

exports.claimIssue = (client, comment, issue, repository, newContrib) => {
  const commenter = comment.user.login;
  const issueNumber = issue.number;
  const repoName = repository.name;
  const repoOwner = repository.owner.login;
  client.issues.addAssigneesToIssue({owner: repoOwner, repo: repoName, number: issueNumber, assignees: [commenter]})
  .then(() => {
    if (newContrib) client.newComment(issue, repository, client.templates.get("newContributor").replace("[commenter]", commenter));
    if (!client.cfg.inProgressLabel || issue.labels.find(label => label.name === client.cfg.inProgressLabel || issue.pull_request)) return;
    client.issues.addLabels({owner: repoOwner, repo: repoName, number: issueNumber, labels: [client.cfg.inProgressLabel]});
  });
};

exports.name = "claim";
exports.aliases = require("../config.js").claimCommands;
exports.args = false;
<|endoftext|>


<commit_before>import { utils } from './utils';
import path from 'path';
import request from 'request';
import requestDebug from 'request-debug';
import _ from 'lodash';
import basicMethods from './basicMethods';

export default class ClassyResource {
  constructor(Classy, urlData) {
    /** Public properties */
    this.basePath = Classy.basePath;
    this.baseUrl = Classy.baseUrl;
    this.path = utils.makeURLInterpolator(this.path || '');

    /** Private properties */
    this._classy = Classy;
    this._urlData = urlData;

    /** Request debug setting */
    if (this._classy.requestDebug) {
      requestDebug(request, function (type, data, r) {
        if (r.headers['x-no-log']) {
          return;
        } else {
          console.log(data);
        }
      });
    }

    /** Add basic methods */
    if (urlData.basic) {
      this._addBasicMethods(urlData.basic);
    }

    /** Add list methods */
    if (urlData.lists) {
      this._addListMethods(urlData.lists);
    }

    /** Add create methods */
    if (urlData.creates) {
      this._addCreateMethods(urlData.creates);
    }

    /** Add custom methods */
    if (urlData.custom && urlData.custom.methods) {
      this._addCustomMethods(urlData.custom.methods);
    }
  }

  createMethod(spec) {
    const PARAM_REGEX = /\{(.*?)\}/g;

    /** Get parameterized request URL and method */
    const specPath = (!_.isUndefined(spec.path) ? spec.path : '');
    const fullPath = this._urlData.path + specPath;
    const commandPath = utils.makeURLInterpolator(fullPath);
    const urlParams = utils.getRegexMatches(fullPath, PARAM_REGEX);
    const requestMethod = _.get(spec, 'method', 'GET').toUpperCase();

    /** Return resource method */
    return (...args) => {

      /** Extract data from function arguments */
      const _this = this;
      const urlData = this._populateUrlParams(urlParams, args);
      const data = utils.getDataFromArgs(args);

      /** Error if not all required params are satisfied */
      for (let i = 0; i < urlParams.length; i++) {
        const arg = args[0];
        const param = urlParams[i];

        if (!arg) {
          throw new Error(
            'Classy: Argument "' + urlParams[i] + '" required, but got: ' + arg +
            ' (on API request to ' + requestMethod + ' ' + commandPath(urlData) + ')'
          );
        }
      }

      /** Create full request path with resolved params */
      const resolvedPath = commandPath(urlData);
      const isAuthRequest = utils.isAuthRequest(resolvedPath);
      const requestPath = this._createFullPath(resolvedPath, isAuthRequest);

      /** Populate form data for authorization requests */
      let form = false;
      if (isAuthRequest) {
        form = this._generateAuthForm(args);
      }

      /**
       * Token stuff & requests need to be synchronous with refresh
       */
      return this._chooseToken(form, data).then((response) => {
        const DEFAULT_REQUEST_HEADERS = {
          Accept: 'application/json'
        };

        let requestHeaders = this._classy.headers || _.merge(DEFAULT_REQUEST_HEADERS, spec.headers);

        if (requestMethod !== 'GET' && !isAuthRequest) {
          requestHeaders['Content-Type'] = 'application/json';
        }

        if (!response) {
          return this._makeRequest(requestPath, requestMethod, requestHeaders, form, data);
        }

        if (response === 'app') {
          return this._refreshAppToken().then((response) => {
            const appToken = _.get(this._classy.appToken, 'access_token', null);
            delete data.token;

            requestHeaders.Authorization = 'Bearer ' + appToken;

            return this._makeRequest(requestPath, requestMethod, requestHeaders, form, data);
          }, (error) => {
            console.error('App token refresh failed: ', error);
          });
        }

        if (response === 'member') {
          const memberToken = _.get(data, 'token.access_token', false);
          delete data.token;

          requestHeaders.Authorization = 'Bearer ' + memberToken;

          return this._makeRequest(requestPath, requestMethod, requestHeaders, form, data);
        }

      }, (error) => {
        throw new Error('No token defined. Expected memberToken object or `token: \'app\'`');
      });

    };
  }

  /**
   *
   */
  _chooseToken(form, data) {
    const dataToken = _.get(data, 'token', false);
    const clientCredentialsRequest = _.get(form, 'grant_type', false) === 'client_credentials';

    const promise = new Promise((resolve, reject) => {
      /** Unless it's a `client_credentials` request, pick a token */
      if (clientCredentialsRequest) {
        resolve(false);
      } else {
        if (!dataToken) {
          reject('No token defined. Expected memberToken object or `token: \'app\'`');
        } else if (dataToken === 'app') {
          resolve('app');
        } else {
          resolve('member');
        }

      }
    });

    return promise;
  }

  /**
   * If this._classy.appToken exists, just resolve,
   * if not, refresh and then resolve.
   * @return {[type]} [description]
   */
  _refreshAppToken() {
    const promise = new Promise((resolve, reject) => {
      if (!this._classy.appToken) {
        this._classy.getAppToken().then((response) => {
          this._classy.setAppToken(response);
          resolve(response);
        }, (error) => {
          reject(error);
        });
      } else {
        resolve(this._classy.appToken);
      }
    });

    return promise;
  }

  /**
   * Adds basic methods to the resource based on
   * the passed array.
   *
   * @param {array} basics A list of basic methods to add
   */
  _addBasicMethods(basics) {
    const _this = this;
    _.each(basics, (method) => {
      _this[method] = _this.createMethod(basicMethods[method]);
    });
  }

  /**
   * Adds list methods to the resource based on
   * the passed array. Lists are resources associated
   * with the called resource, e.g. campaigns.listFaqs(#)
   *
   * @param {array} lists A list of list methods to add
   */
  _addListMethods(lists) {
    const _this = this;

    _.each(lists, (method) => {
      const methodName = _.upperFirst(_.camelCase(method));

      _this['list' + methodName] = _this.createMethod({
        method: 'GET',
        path: '/{id}/' + method
      });
    });
  }

  /**
   * Adds list methods to the resource based on
   * the passed array. Lists are resources associated
   * with the called resource, e.g. campaigns.listFaqs(#)
   *
   * @param {array} lists A list of list methods to add
   */
  _addCreateMethods(lists) {
    const _this = this;

    _.each(lists, (method) => {
      const uppercaseMethod = _.upperFirst(_.camelCase(method));
      const methodName = uppercaseMethod.substr(0, uppercaseMethod.length - 1);

      _this['create' + methodName] = _this.createMethod({
        method: 'POST',
        path: '/{id}/' + method
      });
    });
  }

  _addCustomMethods(methods) {
    const _this = this;

    _.each(methods, (params, methodName) => {
      _this[methodName] = _this.createMethod(params);
    });
  }

  /**
   * Create a full path based on the populated
   * URI and the basePath defined when the Classy
   * instance was initialized. Returns something like
   * /2.0/campaigns/23456 or /oauth2/auth.
   *
   * @param  {string}  resolvedPath  The populated URI
   * @param  {Boolean} isAuthRequest Determines whether to append basePath
   * @return {string}                The full URI for the upcoming request
   */
  _createFullPath(resolvedPath, isAuthRequest) {
    const fullPath = path.join(
        isAuthRequest ? '' : this.basePath,
        resolvedPath
      ).replace(/\\/g, '/');

    const normalizedPath = path.normalize(fullPath);

    return normalizedPath;
  }

  /**
   * Maps URL params from the spec to arguments
   * used in the actual resource call.
   *
   * @param  {array} params URL params from the spec
   * @param  {array} args   Arguments used in the resource call
   * @return {object}        A key/value mapping between params and args
   */
  _populateUrlParams(params, args) {
    const urlData = {};

    for (let i in params) {
      if (!_.isUndefined(args[i])) {
        urlData[params[i]] = args[i];
      }
    }

    return urlData;
  }

  /**
   * Handles authentication requests by adding
   * the appropriate x-www-form-urlencoded data
   * to the request. Camel cased keys will be
   * converted to snake case.
   *
   * @param  {array} args
   * @return {object}      A form object for the request
   */
  _generateAuthForm(args) {
    const form = _.mapKeys(
      args[0],
      (value, key) => _.snakeCase(key).replace(/(\d)_/g, '$1')
    );
    const grantType = utils.generateOauthGrantType(args[0]);

    form.grant_type = grantType;
    form.client_id = form.client_id || this._classy.clientId;
    form.client_secret = form.client_secret || this._classy.clientSecret;

    return form;
  }

  /**
   * Makes a request to Classy's API.
   *
   * @param  {string} path    Request URI
   * @param  {string} method  Request method
   * @param  {object} headers Request headers
   * @param  {object} form    Request form (optional)
   * @return {promise}         Promise based on API request
   */
  _makeRequest(path, method, headers, form, data) {
    const _this = this;

    if (data.noLog) {
      headers['x-no-log'] = true;
      delete data.noLog;
    }

    const promise = new Promise((resolve, reject) => {
      const requestParams = {
        baseUrl: this.baseUrl,
        uri: path,
        method: method,
        headers: headers,
        rejectUnauthorized: false,
        form: form
      };

      if (method === 'GET') {
        requestParams.qs = data;
      } else {
        requestParams.body = JSON.stringify(data);
      }

      request(requestParams, (err, response, body) => {
        if (err || !/^2/.test('' + response.statusCode)) {
          let error;
          if (err && err instanceof Error) {
            error = err;
          } else if (err && !(err instanceof Error)) {
            error = new Error(err);
          } else {
            error = new Error(JSON.stringify(response));
            error.statusCode = response.statusCode;
          }

          reject(error);
        } else {
          body = JSON.parse(body);
          resolve(body);
        }
      });

    });

    return promise;
  }
}
<commit_msg>Update content-type header requirements from blacklist to whitelist<commit_after>import { utils } from './utils';
import path from 'path';
import request from 'request';
import requestDebug from 'request-debug';
import _ from 'lodash';
import basicMethods from './basicMethods';

export default class ClassyResource {
  constructor(Classy, urlData) {
    /** Public properties */
    this.basePath = Classy.basePath;
    this.baseUrl = Classy.baseUrl;
    this.path = utils.makeURLInterpolator(this.path || '');

    /** Private properties */
    this._classy = Classy;
    this._urlData = urlData;

    /** Request debug setting */
    if (this._classy.requestDebug) {
      requestDebug(request, function (type, data, r) {
        if (r.headers['x-no-log']) {
          return;
        } else {
          console.log(data);
        }
      });
    }

    /** Add basic methods */
    if (urlData.basic) {
      this._addBasicMethods(urlData.basic);
    }

    /** Add list methods */
    if (urlData.lists) {
      this._addListMethods(urlData.lists);
    }

    /** Add create methods */
    if (urlData.creates) {
      this._addCreateMethods(urlData.creates);
    }

    /** Add custom methods */
    if (urlData.custom && urlData.custom.methods) {
      this._addCustomMethods(urlData.custom.methods);
    }
  }

  createMethod(spec) {
    const PARAM_REGEX = /\{(.*?)\}/g;

    /** Get parameterized request URL and method */
    const specPath = (!_.isUndefined(spec.path) ? spec.path : '');
    const fullPath = this._urlData.path + specPath;
    const commandPath = utils.makeURLInterpolator(fullPath);
    const urlParams = utils.getRegexMatches(fullPath, PARAM_REGEX);
    const requestMethod = _.get(spec, 'method', 'GET').toUpperCase();

    /** Return resource method */
    return (...args) => {

      /** Extract data from function arguments */
      const _this = this;
      const urlData = this._populateUrlParams(urlParams, args);
      const data = utils.getDataFromArgs(args);

      /** Error if not all required params are satisfied */
      for (let i = 0; i < urlParams.length; i++) {
        const arg = args[0];
        const param = urlParams[i];

        if (!arg) {
          throw new Error(
            'Classy: Argument "' + urlParams[i] + '" required, but got: ' + arg +
            ' (on API request to ' + requestMethod + ' ' + commandPath(urlData) + ')'
          );
        }
      }

      /** Create full request path with resolved params */
      const resolvedPath = commandPath(urlData);
      const isAuthRequest = utils.isAuthRequest(resolvedPath);
      const requestPath = this._createFullPath(resolvedPath, isAuthRequest);

      /** Populate form data for authorization requests */
      let form = false;
      if (isAuthRequest) {
        form = this._generateAuthForm(args);
      }

      /**
       * Token stuff & requests need to be synchronous with refresh
       */
      return this._chooseToken(form, data).then((response) => {
        const DEFAULT_REQUEST_HEADERS = {
          Accept: 'application/json'
        };

        let requestHeaders = this._classy.headers || _.merge(DEFAULT_REQUEST_HEADERS, spec.headers);

        if (requestMethod == 'POST'
          || requestMethod == 'PUT'
          || requestMethod == 'PATCH'
          && !isAuthRequest) {
          requestHeaders['Content-Type'] = 'application/json';
        }

        if (!response) {
          return this._makeRequest(requestPath, requestMethod, requestHeaders, form, data);
        }

        if (response === 'app') {
          return this._refreshAppToken().then((response) => {
            const appToken = _.get(this._classy.appToken, 'access_token', null);
            delete data.token;

            requestHeaders.Authorization = 'Bearer ' + appToken;

            return this._makeRequest(requestPath, requestMethod, requestHeaders, form, data);
          }, (error) => {
            console.error('App token refresh failed: ', error);
          });
        }

        if (response === 'member') {
          const memberToken = _.get(data, 'token.access_token', false);
          delete data.token;

          requestHeaders.Authorization = 'Bearer ' + memberToken;

          return this._makeRequest(requestPath, requestMethod, requestHeaders, form, data);
        }

      }, (error) => {
        throw new Error('No token defined. Expected memberToken object or `token: \'app\'`');
      });

    };
  }

  /**
   *
   */
  _chooseToken(form, data) {
    const dataToken = _.get(data, 'token', false);
    const clientCredentialsRequest = _.get(form, 'grant_type', false) === 'client_credentials';

    const promise = new Promise((resolve, reject) => {
      /** Unless it's a `client_credentials` request, pick a token */
      if (clientCredentialsRequest) {
        resolve(false);
      } else {
        if (!dataToken) {
          reject('No token defined. Expected memberToken object or `token: \'app\'`');
        } else if (dataToken === 'app') {
          resolve('app');
        } else {
          resolve('member');
        }

      }
    });

    return promise;
  }

  /**
   * If this._classy.appToken exists, just resolve,
   * if not, refresh and then resolve.
   * @return {[type]} [description]
   */
  _refreshAppToken() {
    const promise = new Promise((resolve, reject) => {
      if (!this._classy.appToken) {
        this._classy.getAppToken().then((response) => {
          this._classy.setAppToken(response);
          resolve(response);
        }, (error) => {
          reject(error);
        });
      } else {
        resolve(this._classy.appToken);
      }
    });

    return promise;
  }

  /**
   * Adds basic methods to the resource based on
   * the passed array.
   *
   * @param {array} basics A list of basic methods to add
   */
  _addBasicMethods(basics) {
    const _this = this;
    _.each(basics, (method) => {
      _this[method] = _this.createMethod(basicMethods[method]);
    });
  }

  /**
   * Adds list methods to the resource based on
   * the passed array. Lists are resources associated
   * with the called resource, e.g. campaigns.listFaqs(#)
   *
   * @param {array} lists A list of list methods to add
   */
  _addListMethods(lists) {
    const _this = this;

    _.each(lists, (method) => {
      const methodName = _.upperFirst(_.camelCase(method));

      _this['list' + methodName] = _this.createMethod({
        method: 'GET',
        path: '/{id}/' + method
      });
    });
  }

  /**
   * Adds list methods to the resource based on
   * the passed array. Lists are resources associated
   * with the called resource, e.g. campaigns.listFaqs(#)
   *
   * @param {array} lists A list of list methods to add
   */
  _addCreateMethods(lists) {
    const _this = this;

    _.each(lists, (method) => {
      const uppercaseMethod = _.upperFirst(_.camelCase(method));
      const methodName = uppercaseMethod.substr(0, uppercaseMethod.length - 1);

      _this['create' + methodName] = _this.createMethod({
        method: 'POST',
        path: '/{id}/' + method
      });
    });
  }

  _addCustomMethods(methods) {
    const _this = this;

    _.each(methods, (params, methodName) => {
      _this[methodName] = _this.createMethod(params);
    });
  }

  /**
   * Create a full path based on the populated
   * URI and the basePath defined when the Classy
   * instance was initialized. Returns something like
   * /2.0/campaigns/23456 or /oauth2/auth.
   *
   * @param  {string}  resolvedPath  The populated URI
   * @param  {Boolean} isAuthRequest Determines whether to append basePath
   * @return {string}                The full URI for the upcoming request
   */
  _createFullPath(resolvedPath, isAuthRequest) {
    const fullPath = path.join(
        isAuthRequest ? '' : this.basePath,
        resolvedPath
      ).replace(/\\/g, '/');

    const normalizedPath = path.normalize(fullPath);

    return normalizedPath;
  }

  /**
   * Maps URL params from the spec to arguments
   * used in the actual resource call.
   *
   * @param  {array} params URL params from the spec
   * @param  {array} args   Arguments used in the resource call
   * @return {object}        A key/value mapping between params and args
   */
  _populateUrlParams(params, args) {
    const urlData = {};

    for (let i in params) {
      if (!_.isUndefined(args[i])) {
        urlData[params[i]] = args[i];
      }
    }

    return urlData;
  }

  /**
   * Handles authentication requests by adding
   * the appropriate x-www-form-urlencoded data
   * to the request. Camel cased keys will be
   * converted to snake case.
   *
   * @param  {array} args
   * @return {object}      A form object for the request
   */
  _generateAuthForm(args) {
    const form = _.mapKeys(
      args[0],
      (value, key) => _.snakeCase(key).replace(/(\d)_/g, '$1')
    );
    const grantType = utils.generateOauthGrantType(args[0]);

    form.grant_type = grantType;
    form.client_id = form.client_id || this._classy.clientId;
    form.client_secret = form.client_secret || this._classy.clientSecret;

    return form;
  }

  /**
   * Makes a request to Classy's API.
   *
   * @param  {string} path    Request URI
   * @param  {string} method  Request method
   * @param  {object} headers Request headers
   * @param  {object} form    Request form (optional)
   * @return {promise}         Promise based on API request
   */
  _makeRequest(path, method, headers, form, data) {
    const _this = this;

    if (data.noLog) {
      headers['x-no-log'] = true;
      delete data.noLog;
    }

    const promise = new Promise((resolve, reject) => {
      const requestParams = {
        baseUrl: this.baseUrl,
        uri: path,
        method: method,
        headers: headers,
        rejectUnauthorized: false,
        form: form
      };

      if (method === 'GET') {
        requestParams.qs = data;
      } else {
        requestParams.body = JSON.stringify(data);
      }

      request(requestParams, (err, response, body) => {
        if (err || !/^2/.test('' + response.statusCode)) {
          let error;
          if (err && err instanceof Error) {
            error = err;
          } else if (err && !(err instanceof Error)) {
            error = new Error(err);
          } else {
            error = new Error(JSON.stringify(response));
            error.statusCode = response.statusCode;
          }

          reject(error);
        } else {
          body = JSON.parse(body);
          resolve(body);
        }
      });

    });

    return promise;
  }
}
<|endoftext|>


<commit_before>var globals = require('../globals'),
  util = require('../util'),
  setter = util.setter,
  schema = require('../schema/schema'),
  time = require('./time');

module.exports = vllayout;
var CHARACTER_WIDTH = 6, SMALL_BAND_CARDINALITY = 20;

function vllayout(encoding, stats) {
  var layout = box(encoding, stats);
  layout = offset(encoding, stats, layout);
  layout.characterWidth = CHARACTER_WIDTH;
  return layout;
}

function getCardinality(encoding, encType, stats) {
  var field = encoding.fieldName(encType);
  if (encoding.bin(encType)) {
    var bins = util.getbins(stats[field]);
    return (bins.stop - bins.start) / bins.step;
  }
  if (encoding.isType(encType, T)) {
    return time.cardinality(encoding, encType, stats);
  }
  return stats[field].cardinality;
}

/*
  HACK to set chart size
  NOTE: this fails for plots driven by derived values (e.g., aggregates)
  One solution is to update Vega to support auto-sizing
  In the meantime, auto-padding (mostly) does the trick
 */
function box(encoding, stats) {
  var hasRow = encoding.has(ROW),
      hasCol = encoding.has(COL),
      marktype = encoding.marktype();

  var cellWidth, cellHeight, cellPadding = encoding.config('cellPadding'),
    xUseSmallBand = false, yUseSmallBand = false;

  // set cellWidth
  if (encoding.has(X)) {
    if (encoding.isOrdinalScale(X)) {
      // for ordinal, hasCol or not doesn't matter -- we scale based on cardinality
      var xCardinality = getCardinality(encoding, X, stats);
      if (xCardinality >= SMALL_BAND_CARDINALITY) {
        xUseSmallBand = true;
      }
      cellWidth = (xCardinality + encoding.band(X).padding) * encoding.bandSize(X, xUseSmallBand);
    } else {
      cellWidth = hasCol ? encoding.enc(COL).width :  encoding.config("singleWidth");
    }
  } else {
    if (marktype === TEXT) {
      cellWidth = encoding.config('textCellWidth');
    } else {
      cellWidth = encoding.bandSize(X);
    }
  }

  // set cellHeight
  if (encoding.has(Y)) {
    if (encoding.isOrdinalScale(Y)) {
      // for ordinal, hasCol or not doesn't matter -- we scale based on cardinality
      var yCardinality = getCardinality(encoding, Y, stats);
      if (yCardinality >= SMALL_BAND_CARDINALITY) {
        yUseSmallBand = true;
      }
      cellHeight = (yCardinality + encoding.band(Y).padding) * encoding.bandSize(Y, yUseSmallBand);
    } else {
      cellHeight = hasRow ? encoding.enc(ROW).height :  encoding.config("singleHeight");
    }
  } else {
    cellHeight = encoding.bandSize(Y);
  }

  // Cell bands use rangeBands(). There are n-1 padding.  Outerpadding = 0 for cells

  var width = cellWidth, height = cellHeight;
  if (hasCol) {
    var colCardinality = getCardinality(encoding, COL, stats);
    width = cellWidth * ((1 + cellPadding) * (colCardinality - 1) + 1);
  }
  if (hasRow) {
    var rowCardinality = getCardinality(encoding, ROW, stats);
    height = cellHeight * ((1 + cellPadding) * (rowCardinality - 1) + 1);
  }

  return {
    cellWidth: cellWidth,
    cellHeight: cellHeight,
    width: width,
    height: height,
    x: {useSmallBand: xUseSmallBand},
    y: {useSmallBand: yUseSmallBand}
  };
}

function offset(encoding, stats, layout) {
  [X, Y].forEach(function (x) {
    var maxLength;
    if (encoding.isOrdinalScale(x) || encoding.isType(x, T)) {
      maxLength = stats[encoding.fieldName(x)].maxlength;
    } else if (encoding.aggr(x) === 'count') {
      //assign default value for count as it won't have stats
      maxLength =  4;
    } else if (encoding.isType(x, Q)) {
      //assume that default formating is always shorter than 7
      maxLength = Math.min(stats[encoding.fieldName(x)].maxlength, 7);
    }
    setter(layout,[x, 'axisTitleOffset'], CHARACTER_WIDTH *  maxLength + 20);
  });
  return layout;
}
<commit_msg>minor layout follow up for #191<commit_after>var globals = require('../globals'),
  util = require('../util'),
  setter = util.setter,
  schema = require('../schema/schema'),
  time = require('./time');

module.exports = vllayout;
var CHARACTER_WIDTH = 6, SMALL_BAND_CARDINALITY = 20;

function vllayout(encoding, stats) {
  var layout = box(encoding, stats);
  layout = offset(encoding, stats, layout);
  layout.characterWidth = CHARACTER_WIDTH;
  return layout;
}

function getCardinality(encoding, encType, stats) {
  var field = encoding.fieldName(encType);
  if (encoding.bin(encType)) {
    var bins = util.getbins(stats[field]);
    return (bins.stop - bins.start) / bins.step;
  }
  if (encoding.isType(encType, T)) {
    return time.cardinality(encoding, encType, stats);
  }
  return stats[field].cardinality;
}

/*
  HACK to set chart size
  NOTE: this fails for plots driven by derived values (e.g., aggregates)
  One solution is to update Vega to support auto-sizing
  In the meantime, auto-padding (mostly) does the trick
 */
function box(encoding, stats) {
  var hasRow = encoding.has(ROW),
      hasCol = encoding.has(COL),
      marktype = encoding.marktype();

  var cellWidth, cellHeight, cellPadding = encoding.config('cellPadding'),
    xUseSmallBand = false, yUseSmallBand = false;

  // set cellWidth
  if (encoding.has(X)) {
    if (encoding.isOrdinalScale(X)) {
      // for ordinal, hasCol or not doesn't matter -- we scale based on cardinality
      var xCardinality = getCardinality(encoding, X, stats);
      if (xCardinality >= SMALL_BAND_CARDINALITY) {
        xUseSmallBand = true;
      }
      cellWidth = (xCardinality + encoding.band(X).padding) * encoding.bandSize(X, xUseSmallBand);
    } else {
      cellWidth = hasCol ? encoding.enc(COL).width :  encoding.config("singleWidth");
    }
  } else {
    if (marktype === TEXT) {
      cellWidth = encoding.config('textCellWidth');
    } else {
      cellWidth = encoding.bandSize(X);
    }
  }

  // set cellHeight
  if (encoding.has(Y)) {
    if (encoding.isOrdinalScale(Y)) {
      // for ordinal, hasCol or not doesn't matter -- we scale based on cardinality
      var yCardinality = getCardinality(encoding, Y, stats);
      if (yCardinality >= SMALL_BAND_CARDINALITY) {
        yUseSmallBand = true;
      }
      cellHeight = (yCardinality + encoding.band(Y).padding) * encoding.bandSize(Y, yUseSmallBand);
    } else {
      cellHeight = hasRow ? encoding.enc(ROW).height :  encoding.config("singleHeight");
    }
  } else {
    cellHeight = encoding.bandSize(Y);
  }

  // Cell bands use rangeBands(). There are n-1 padding.  Outerpadding = 0 for cells

  var width = cellWidth, height = cellHeight;
  if (hasCol) {
    var colCardinality = getCardinality(encoding, COL, stats);
    width = cellWidth * ((1 + cellPadding) * (colCardinality - 1) + 1);
  }
  if (hasRow) {
    var rowCardinality = getCardinality(encoding, ROW, stats);
    height = cellHeight * ((1 + cellPadding) * (rowCardinality - 1) + 1);
  }

  return {
    cellWidth: cellWidth,
    cellHeight: cellHeight,
    width: width,
    height: height,
    x: {useSmallBand: xUseSmallBand},
    y: {useSmallBand: yUseSmallBand}
  };
}

function offset(encoding, stats, layout) {
  [X, Y].forEach(function (x) {
    var maxLength;
    if (encoding.isOrdinalScale(x) || encoding.isType(x, T)) {
      maxLength = stats[encoding.fieldName(x)].maxlength;
    } else if (encoding.aggr(x) === 'count') {
      //assign default value for count as it won't have stats
      maxLength =  4;
    } else if (encoding.isType(x, Q)) {
      if (x===X) {
        maxLength = 3;
      } else { // Y
        //assume that default formating is always shorter than 7
        maxLength = Math.min(stats[encoding.fieldName(x)].maxlength, 7);
      }
    }
    setter(layout,[x, 'axisTitleOffset'], CHARACTER_WIDTH *  maxLength + 20);
  });
  return layout;
}
<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import { Link, IndexLink } from 'react-router';

const BarLink = function(props) {
  const classes = "nav-icon glyphicon glyphicon-" + props.glyph
  return (

      <Link to={props.uri}>
        {props.text}

      </Link>

  )
}

const App = (props) => {
  const spacer = (|)
  return (

              <BarLink uri="/" text="Home" glyph="home" />
              <BarLink uri="/view" text="View Polls" glyph="stats" />
              <BarLink uri="/create" text="Create" glyph="plus" />
              <BarLink uri="/login" text="Login" glyph="user" />

        {props.children}

  );
};

App.propTypes = {
  children: PropTypes.element
};

export default App;
<commit_msg>Changed app into a React Class<commit_after>import React, { PropTypes } from 'react';
import { Link, IndexLink } from 'react-router';

const BarLink = function(props) {
  const classes = "nav-icon glyphicon glyphicon-" + props.glyph
  return (

      <Link to={props.uri}>
        {props.text}

      </Link>

  )
}

const App = React.createClass({
  render: function(){
    const spacer = (|)
    return (

                <BarLink uri="/" text="Home" glyph="home" />
                <BarLink uri="/view" text="View Polls" glyph="stats" />
                <BarLink uri="/create" text="Create" glyph="plus" />
                <BarLink uri="/login" text="Login" glyph="user" />

          {this.props.children}

    );
  }
});

App.propTypes = {
  children: PropTypes.element
};

export default App;
<|endoftext|>


<commit_before>import {h, render, Component} from 'preact'
import hash from 'string-hash'
import * as doclist from '../doclist'
import GitHub, {extractGistInfo} from '../github'
import History from '../history'
import * as outline from '../outline'
import * as storage from '../storage'

import Toolbar, {ToolbarButton, ToolbarSeparator} from './Toolbar'
import MenuPanel from './MenuPanel'
import DocumentView from './DocumentView'
import BusyScreen from './BusyScreen'

export default class App extends Component {
    history = new History()

    state = {
        busy: [],
        oldContentHash: null,
        user: null,
        showMenu: true,
        currentIndex: 0,
        docs: [],
        selectedIds: []
    }

    constructor() {
        super()

        this.recordHistory()
    }

    componentDidMount() {
        // Auto login

        try {
            let credentials = JSON.parse(atob(storage.get('credentials')))
            this.login(credentials)
        } catch(_) {
            storage.set('credentials', null)
        }

        // Events

        document.addEventListener('keydown', evt => {
            if (this.state.busy.length > 0) return

            if (evt.keyCode === 83 && evt.ctrlKey) {
                // Ctrl+S
                // Push

                evt.preventDefault()
                this.pushClick()
            } else if (evt.keyCode === 79 && evt.ctrlKey) {
                // Ctrl+O
                // Open menu panel

                evt.preventDefault()
                this.showMenu()
            } else if (evt.keyCode === 78 && evt.ctrlKey) {
                // Ctrl+N
                // Add new document

                evt.preventDefault()
                this.addNewDoc()
            } else if ((evt.keyCode === 90 || evt.keyCode === 89) && evt.ctrlKey) {
                // Ctrl+(Shift)+Z/Y
                // Undo/Redo

                evt.preventDefault()

                let step = evt.keyCode === 90 ? -1 : 1
                if (evt.shiftKey) step = -step

                this.stepInHistory(step)
            }
        })

        window.addEventListener('beforeunload', evt => {
            if (this.state.oldContentHash === this.getContentHash()) return

            let message = 'You have made unsaved changes. Do you really want to leave?'
            evt.returnValue = message
        })
    }

    getContentHash() {
        return hash(doclist.stringify(this.state.docs))
    }

    pull = () => {
        return this.client.getGist(this.gistId).then(gist => {
            let filename = Object.keys(gist.files)[0]

            return this.client.getGistFileContent(gist, filename).then(content => {
                return {gist, filename, content}
            })
        })
        .then(({gist, filename, content}) => {
            this.gistFilename = filename

            this.setState({
                user: {
                    avatar: gist.owner.avatar_url,
                    name: gist.owner.login
                }
            })

            let docs = doclist.parse(content)

            this.updateDocs({docs})
            this.updateCurrentIndex({currentIndex: Math.min(docs.length - 1, this.state.currentIndex)})
            this.setState({oldContentHash: this.getContentHash()})
        })
    }

    push = () => {
        if (this.state.oldContentHash === this.getContentHash()) return

        return this.client.editGist(this.gistId, {
            files: {
                [this.gistFilename]: {
                    content: doclist.stringify(this.state.docs)
                }
            }
        }).then(() => {
            this.setState({oldContentHash: this.getContentHash()})
        })
    }

    login = ({gistUrl, accessToken}) => {
        this.startBusy()

        storage.set('credentials', btoa(JSON.stringify({gistUrl, accessToken})))

        return Promise.resolve().then(() => {
            let {id, user, host} = extractGistInfo(gistUrl)
            this.client = new GitHub({host, user, pass: accessToken})
            this.gistId = id
            this.gistUrl = gistUrl
        }).then(() => {
            return this.pull()
        }).then(() => {
            this.updateCurrentIndex({currentIndex: 0})
            this.history.clear()
            this.recordHistory()
        }).catch(err => {
            alert(`Login failed.\n\n${err}`)
            this.logout()
        })
        .then(() => this.endBusy())
    }

    logout = () => {
        storage.set('credentials', null)

        this.client = null
        this.setState({user: null, docs: []})
    }

    pullClick = () => {
        this.startBusy('pull')

        return this.pull().catch(err => {
            alert(`Loading of gist failed.\n\n${err}`)
        })
        .then(() => this.endBusy())
    }

    pushClick = () => {
        this.startBusy('push')

        return this.push().catch(err => {
            alert(`Updating gist failed.\n\n${err}`)
        }).then(() => {
            this.endBusy()
        })
    }

    startBusy = type => {
        this.setState(s => ({busy: (s.busy.push(type), s.busy)}))
    }

    endBusy = () => {
        this.setState(s => ({busy: (s.busy.pop(), s.busy)}))
    }

    getCurrentDoc = () => {
        let {docs, currentIndex} = this.state
        return docs[currentIndex]
    }

    recordHistory = () => {
        this.history.push({...this.state})
    }

    stepInHistory = step => {
        let entry = this.history.step(step)
        if (entry == null) return

        let {currentIndex, docs, selectedIds} = entry
        this.setState({currentIndex, docs, selectedIds})
    }

    undo = () => this.stepInHistory(-1)
    redo = () => this.stepInHistory(1)

    showMenu = () => {
        this.setState({showMenu: true})
    }

    hideMenu = () => {
        this.setState({showMenu: false})
    }

    updateDocs = ({docs}) => {
        if (docs === this.state.docs) return

        this.setState({docs})
        this.recordHistory()
    }

    permutateDocs = ({permutation}) => {
        if (permutation.every((x, i) => x === i)) return

        this.updateDocs({docs: permutation.map(i => this.state.docs[i])})
        this.updateCurrentIndex({currentIndex: permutation.indexOf(this.state.currentIndex)})
    }

    updateCurrentIndex = ({currentIndex}) => {
        if (currentIndex === this.state.currentIndex) return

        let currentDoc = this.state.docs[currentIndex]

        this.setState({
            currentIndex,
            selectedIds: currentDoc == null ? [] : currentDoc.list.slice(0, 1).map(item => item.id)
        })

        this.recordHistory()
    }

    updateSelectedIds = ({selectedIds}) => {
        this.setState({selectedIds})
    }

    updateDoc = ({doc}) => {
        let {docs, currentIndex} = this.state
        this.updateDocs({docs: doclist.update(docs, currentIndex, doc)})
    }

    removeDoc = ({index}) => {
        let {docs, currentIndex} = this.state
        let result = confirm('Do you really want to remove this document?')
        if (!result) return

        this.updateDocs({docs: doclist.remove(docs, index)})
        this.updateCurrentIndex({
            currentIndex: index === currentIndex
                ? Math.max(0, currentIndex - 1)
                : Math.min(currentIndex, docs.length - 2)
        })
    }

    addNewDoc = () => {
        let {docs} = this.state
        let newDocs = doclist.append(docs, '')

        this.updateDocs({docs: newDocs})
        this.updateCurrentIndex({currentIndex: newDocs.length - 1})
        this.hideMenu()

        this.setState({}, () => {
            document.querySelector('.document-view-header h1 input').focus()
        })
    }

    handleDocumentClick = ({index}) => {
        this.updateCurrentIndex({currentIndex: index})
        this.hideMenu()

        this.setState({}, () => {
            document.querySelector('.document-view .outline-view').focus()
        })
    }

    handleOpenGist = () => {
        let a = render((

        ), document.body)

        a.click()
        a.remove()
    }

    render() {
        let doc = this.getCurrentDoc()

        return

            <MenuPanel
                user={this.state.user}
                show={this.state.showMenu}
                docs={this.state.docs}
                currentIndex={this.state.currentIndex}

                onNewDocumentClick={this.addNewDoc}
                onDocumentClick={this.handleDocumentClick}
                onDocumentRemove={this.removeDoc}
                onPermutation={this.permutateDocs}
                onOpenGistClick={this.handleOpenGist}
                onLogin={this.login}
                onLogout={this.logout}
            />

            <DocumentView
                disabled={doc == null || this.state.user == null}
                doc={doc || {title: '', list: []}}
                selectedIds={this.state.selectedIds}

                headerToolbar={
                    <Toolbar disabled={this.state.user == null}>
                        <ToolbarButton
                            key="pull"
                            type={this.state.busy.includes('pull') && 'sync'}
                            icon={`./img/${this.state.busy.includes('pull') ? 'sync' : 'down'}.svg`}
                            tooltip="Pull"
                            onClick={this.pullClick}
                        />
                        {this.state.oldContentHash !== this.getContentHash() && <ToolbarButton
                            key="push"
                            type={this.state.busy.includes('push') && 'sync'}
                            icon={`./img/${this.state.busy.includes('push') ? 'sync' : 'up'}.svg`}
                            tooltip="Push"
                            onClick={this.pushClick}
                        />}

                        <ToolbarSeparator/>

                        <ToolbarButton
                            icon="./img/undo.svg"
                            tooltip="Undo"
                            disabled={!this.history.isUndoable()}
                            onClick={this.undo}
                        />
                        <ToolbarButton
                            icon="./img/redo.svg"
                            tooltip="Redo"
                            disabled={!this.history.isRedoable()}
                            onClick={this.redo}
                        />
                    </Toolbar>
                }

                onMenuButtonClick={this.showMenu}
                onChange={this.updateDoc}
                onSelectionChange={this.updateSelectedIds}
                onUndo={this.undo}
                onRedo={this.redo}
            />

            <BusyScreen show={this.state.busy.length > 0}/>

    }
}
<commit_msg>Fix non-responsiveness when pushing with no changes<commit_after>import {h, render, Component} from 'preact'
import hash from 'string-hash'
import * as doclist from '../doclist'
import GitHub, {extractGistInfo} from '../github'
import History from '../history'
import * as outline from '../outline'
import * as storage from '../storage'

import Toolbar, {ToolbarButton, ToolbarSeparator} from './Toolbar'
import MenuPanel from './MenuPanel'
import DocumentView from './DocumentView'
import BusyScreen from './BusyScreen'

export default class App extends Component {
    history = new History()

    state = {
        busy: [],
        oldContentHash: null,
        user: null,
        showMenu: true,
        currentIndex: 0,
        docs: [],
        selectedIds: []
    }

    constructor() {
        super()

        this.recordHistory()
    }

    componentDidMount() {
        // Auto login

        try {
            let credentials = JSON.parse(atob(storage.get('credentials')))
            this.login(credentials)
        } catch(_) {
            storage.set('credentials', null)
        }

        // Events

        document.addEventListener('keydown', evt => {
            if (this.state.busy.length > 0) return

            if (evt.keyCode === 83 && evt.ctrlKey) {
                // Ctrl+S
                // Push

                evt.preventDefault()
                this.pushClick()
            } else if (evt.keyCode === 79 && evt.ctrlKey) {
                // Ctrl+O
                // Open menu panel

                evt.preventDefault()
                this.showMenu()
            } else if (evt.keyCode === 78 && evt.ctrlKey) {
                // Ctrl+N
                // Add new document

                evt.preventDefault()
                this.addNewDoc()
            } else if ((evt.keyCode === 90 || evt.keyCode === 89) && evt.ctrlKey) {
                // Ctrl+(Shift)+Z/Y
                // Undo/Redo

                evt.preventDefault()

                let step = evt.keyCode === 90 ? -1 : 1
                if (evt.shiftKey) step = -step

                this.stepInHistory(step)
            }
        })

        window.addEventListener('beforeunload', evt => {
            if (this.state.oldContentHash === this.getContentHash()) return

            let message = 'You have made unsaved changes. Do you really want to leave?'
            evt.returnValue = message
        })
    }

    getContentHash() {
        return hash(doclist.stringify(this.state.docs))
    }

    pull = () => {
        return this.client.getGist(this.gistId).then(gist => {
            let filename = Object.keys(gist.files)[0]

            return this.client.getGistFileContent(gist, filename).then(content => {
                return {gist, filename, content}
            })
        })
        .then(({gist, filename, content}) => {
            this.gistFilename = filename

            this.setState({
                user: {
                    avatar: gist.owner.avatar_url,
                    name: gist.owner.login
                }
            })

            let docs = doclist.parse(content)

            this.updateDocs({docs})
            this.updateCurrentIndex({currentIndex: Math.min(docs.length - 1, this.state.currentIndex)})
            this.setState({oldContentHash: this.getContentHash()})
        })
    }

    push = () => {
        if (this.state.oldContentHash === this.getContentHash())
            return Promise.resolve()

        return this.client.editGist(this.gistId, {
            files: {
                [this.gistFilename]: {
                    content: doclist.stringify(this.state.docs)
                }
            }
        }).then(() => {
            this.setState({oldContentHash: this.getContentHash()})
        })
    }

    login = ({gistUrl, accessToken}) => {
        this.startBusy()

        storage.set('credentials', btoa(JSON.stringify({gistUrl, accessToken})))

        return Promise.resolve().then(() => {
            let {id, user, host} = extractGistInfo(gistUrl)
            this.client = new GitHub({host, user, pass: accessToken})
            this.gistId = id
            this.gistUrl = gistUrl
        }).then(() => {
            return this.pull()
        }).then(() => {
            this.updateCurrentIndex({currentIndex: 0})
            this.history.clear()
            this.recordHistory()
        }).catch(err => {
            alert(`Login failed.\n\n${err}`)
            this.logout()
        })
        .then(() => this.endBusy())
    }

    logout = () => {
        storage.set('credentials', null)

        this.client = null
        this.setState({user: null, docs: []})
    }

    pullClick = () => {
        this.startBusy('pull')

        return this.pull().catch(err => {
            alert(`Loading of gist failed.\n\n${err}`)
        })
        .then(() => this.endBusy())
    }

    pushClick = () => {
        this.startBusy('push')

        return this.push().catch(err => {
            alert(`Updating gist failed.\n\n${err}`)
        }).then(() => {
            this.endBusy()
        })
    }

    startBusy = type => {
        this.setState(s => ({busy: (s.busy.push(type), s.busy)}))
    }

    endBusy = () => {
        this.setState(s => ({busy: (s.busy.pop(), s.busy)}))
    }

    getCurrentDoc = () => {
        let {docs, currentIndex} = this.state
        return docs[currentIndex]
    }

    recordHistory = () => {
        this.history.push({...this.state})
    }

    stepInHistory = step => {
        let entry = this.history.step(step)
        if (entry == null) return

        let {currentIndex, docs, selectedIds} = entry
        this.setState({currentIndex, docs, selectedIds})
    }

    undo = () => this.stepInHistory(-1)
    redo = () => this.stepInHistory(1)

    showMenu = () => {
        this.setState({showMenu: true})
    }

    hideMenu = () => {
        this.setState({showMenu: false})
    }

    updateDocs = ({docs}) => {
        if (docs === this.state.docs) return

        this.setState({docs})
        this.recordHistory()
    }

    permutateDocs = ({permutation}) => {
        if (permutation.every((x, i) => x === i)) return

        this.updateDocs({docs: permutation.map(i => this.state.docs[i])})
        this.updateCurrentIndex({currentIndex: permutation.indexOf(this.state.currentIndex)})
    }

    updateCurrentIndex = ({currentIndex}) => {
        if (currentIndex === this.state.currentIndex) return

        let currentDoc = this.state.docs[currentIndex]

        this.setState({
            currentIndex,
            selectedIds: currentDoc == null ? [] : currentDoc.list.slice(0, 1).map(item => item.id)
        })

        this.recordHistory()
    }

    updateSelectedIds = ({selectedIds}) => {
        this.setState({selectedIds})
    }

    updateDoc = ({doc}) => {
        let {docs, currentIndex} = this.state
        this.updateDocs({docs: doclist.update(docs, currentIndex, doc)})
    }

    removeDoc = ({index}) => {
        let {docs, currentIndex} = this.state
        let result = confirm('Do you really want to remove this document?')
        if (!result) return

        this.updateDocs({docs: doclist.remove(docs, index)})
        this.updateCurrentIndex({
            currentIndex: index === currentIndex
                ? Math.max(0, currentIndex - 1)
                : Math.min(currentIndex, docs.length - 2)
        })
    }

    addNewDoc = () => {
        let {docs} = this.state
        let newDocs = doclist.append(docs, '')

        this.updateDocs({docs: newDocs})
        this.updateCurrentIndex({currentIndex: newDocs.length - 1})
        this.hideMenu()

        this.setState({}, () => {
            document.querySelector('.document-view-header h1 input').focus()
        })
    }

    handleDocumentClick = ({index}) => {
        this.updateCurrentIndex({currentIndex: index})
        this.hideMenu()

        this.setState({}, () => {
            document.querySelector('.document-view .outline-view').focus()
        })
    }

    handleOpenGist = () => {
        let a = render((

        ), document.body)

        a.click()
        a.remove()
    }

    render() {
        let doc = this.getCurrentDoc()

        return

            <MenuPanel
                user={this.state.user}
                show={this.state.showMenu}
                docs={this.state.docs}
                currentIndex={this.state.currentIndex}

                onNewDocumentClick={this.addNewDoc}
                onDocumentClick={this.handleDocumentClick}
                onDocumentRemove={this.removeDoc}
                onPermutation={this.permutateDocs}
                onOpenGistClick={this.handleOpenGist}
                onLogin={this.login}
                onLogout={this.logout}
            />

            <DocumentView
                disabled={doc == null || this.state.user == null}
                doc={doc || {title: '', list: []}}
                selectedIds={this.state.selectedIds}

                headerToolbar={
                    <Toolbar disabled={this.state.user == null}>
                        <ToolbarButton
                            key="pull"
                            type={this.state.busy.includes('pull') && 'sync'}
                            icon={`./img/${this.state.busy.includes('pull') ? 'sync' : 'down'}.svg`}
                            tooltip="Pull"
                            onClick={this.pullClick}
                        />
                        {this.state.oldContentHash !== this.getContentHash() && <ToolbarButton
                            key="push"
                            type={this.state.busy.includes('push') && 'sync'}
                            icon={`./img/${this.state.busy.includes('push') ? 'sync' : 'up'}.svg`}
                            tooltip="Push"
                            onClick={this.pushClick}
                        />}

                        <ToolbarSeparator/>

                        <ToolbarButton
                            icon="./img/undo.svg"
                            tooltip="Undo"
                            disabled={!this.history.isUndoable()}
                            onClick={this.undo}
                        />
                        <ToolbarButton
                            icon="./img/redo.svg"
                            tooltip="Redo"
                            disabled={!this.history.isRedoable()}
                            onClick={this.redo}
                        />
                    </Toolbar>
                }

                onMenuButtonClick={this.showMenu}
                onChange={this.updateDoc}
                onSelectionChange={this.updateSelectedIds}
                onUndo={this.undo}
                onRedo={this.redo}
            />

            <BusyScreen show={this.state.busy.length > 0}/>

    }
}
<|endoftext|>


<commit_before>var _ = require('underscore');
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var path = require('path');
var assign = require('object-assign');
var docker = require('./Docker');
var metrics = require('./Metrics');
var registry = require('./Registry');
var LogStore = require('./LogStore');
var bugsnag = require('bugsnag-js');

var _placeholders = {};
var _containers = {};
var _progress = {};
var _muted = {};
var _blocked = {};
var _error = null;

var ContainerStore = assign(Object.create(EventEmitter.prototype), {
  CLIENT_CONTAINER_EVENT: 'client_container_event',
  SERVER_CONTAINER_EVENT: 'server_container_event',
  SERVER_PROGRESS_EVENT: 'server_progress_event',
  SERVER_ERROR_EVENT: 'server_error_event',
  _pullImage: function (repository, tag, callback, progressCallback, blockedCallback) {
    registry.layers(repository, tag, (err, layerSizes) => {

      // TODO: Support v2 registry API
      // TODO: clean this up- It's messy to work with pulls from both the v1 and v2 registry APIs
      // Use the per-layer pull progress % to update the total progress.
      docker.client().listImages({all: 1}, (err, images) => {
        images = images || [];
        var existingIds = new Set(images.map(function (image) {
          return image.Id.slice(0, 12);
        }));
        var layersToDownload = layerSizes.filter(function (layerSize) {
          return !existingIds.has(layerSize.Id);
        });

        var totalBytes = layersToDownload.map(function (s) { return s.size; }).reduce(function (pv, sv) { return pv + sv; }, 0);
        docker.client().pull(repository + ':' + tag, (err, stream) => {
          if (err) {
            callback(err);
            return;
          }
          stream.setEncoding('utf8');

          var layerProgress = layersToDownload.reduce(function (r, layer) {
            if (_.findWhere(images, {Id: layer.Id})) {
              r[layer.Id] = 100;
            } else {
              r[layer.Id] = 0;
            }
            return r;
          }, {});

          stream.on('data', str => {
            var data = JSON.parse(str);
            console.log(data);

            if (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1) {
              blockedCallback();
              return;
            }

            if (data.status === 'Already exists') {
              layerProgress[data.id] = 1;
            } else if (data.status === 'Downloading') {
              var current = data.progressDetail.current;
              var total = data.progressDetail.total;
              var layerFraction = current / total;
              layerProgress[data.id] = layerFraction;
            }

            var chunks = layersToDownload.map(function (s) {
              return layerProgress[s.Id] * s.size;
            });

            var totalReceived = chunks.reduce(function (pv, sv) {
              return pv + sv;
            }, 0);

            var totalProgress = totalReceived / totalBytes;
            progressCallback(totalProgress);
          });
          stream.on('end', function () {
            callback();
          });
        });
      });
    });
  },
  _createContainer: function (name, containerData, callback) {
    var existing = docker.client().getContainer(name);
    var self = this;
    if (!containerData.name && containerData.Name) {
      containerData.name = containerData.Name;
    } else if (!containerData.name) {
      containerData.name = name;
    }
    if (containerData.Config && containerData.Config.Image) {
      containerData.Image = containerData.Config.Image;
    }
    existing.kill(function () {
      existing.remove(function () {
        docker.client().getImage(containerData.Image).inspect(function (err, data) {
          if (err) {
            callback(err);
            return;
          }
          var binds = containerData.Binds || [];
          if (data.Config.Volumes) {
            _.each(data.Config.Volumes, function (value, key) {
              var existingBind = _.find(binds, b => {
                return b.indexOf(':' + key) !== -1;
              });
              if (!existingBind) {
                binds.push(path.join(process.env.HOME, 'Kitematic', containerData.name, key)+ ':' + key);
              }
            });
          }
          docker.client().createContainer(containerData, function (err, container) {
            if (err) {
              callback(err, null);
              return;
            }
            if (containerData.State && !containerData.State.Running) {
              self.fetchContainer(containerData.name, callback);
            } else {
              var startopts = {
                Binds: binds
              };
              if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
                startopts.PortBindings = containerData.NetworkSettings.Ports;
              } else{
                startopts.PublishAllPorts = true;
              }
              container.start(startopts, function (err) {
                if (err) {
                  callback(err);
                  return;
                }
                self.fetchContainer(containerData.name, callback);
              });
            }
          });
        });
      });
    });
  },
  _generateName: function (repository) {
    var base = _.last(repository.split('/'));
    var count = 1;
    var name = base;
    while (true) {
      if (!this.containers()[name]) {
        return name;
      } else {
        count++;
        name = base + '-' + count;
      }
    }
  },
  _resumePulling: function (callback) {
    var downloading = _.filter(_.values(this.containers()), function (container) {
      return container.State.Downloading;
    });

    // Recover any pulls that were happening
    var self = this;
    downloading.forEach(function (container) {
      _progress[container.Name] = 99;
      docker.client().pull(container.Config.Image, function (err, stream) {
        if (err) {
          callback(err);
          return;
        }
        stream.setEncoding('utf8');
        stream.on('data', function () {});
        stream.on('end', function () {
          delete _placeholders[container.Name];
          localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
          self._createContainer(container.Name, {Image: container.Config.Image}, err => {
            if (err) {
              callback(err);
              return;
            }
            self.emit(self.SERVER_PROGRESS_EVENT, container.Name);
            self.emit(self.CLIENT_CONTAINER_EVENT, container.Name);
          });
        });
      });
    });
  },
  _startListeningToEvents: function (callback) {
    docker.client().getEvents((err, stream) => {
      if (err) {
        callback(err);
        return;
      }
      if (stream) {
        stream.setEncoding('utf8');
        stream.on('data', this._dockerEvent.bind(this));
      }
    });
  },
  _dockerEvent: function (json) {
    var data = JSON.parse(json);
    console.log(data);

    // If the event is delete, remove the container
    if (data.status === 'destroy') {
      var container = _.findWhere(_.values(_containers), {Id: data.id});
      if (container) {
        delete _containers[container.Name];
        if (!_muted[container.Name]) {
          this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status);
        }
      } else {
        this.emit(this.SERVER_CONTAINER_EVENT, data.status);
      }
    } else {
      this.fetchContainer(data.id, err => {
        if (err) {
          return;
        }
        var container = _.findWhere(_.values(_containers), {Id: data.id});
        if (!container || _muted[container.Name]) {
          return;
        }
        this.emit(this.SERVER_CONTAINER_EVENT, container ? container.Name : null, data.status);
      });
    }
  },
  init: function (callback) {
    // TODO: Load cached data from db on loading
    this.fetchAllContainers(err => {
      if (err) {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to init', err);
        callback(err);
        return;
      } else {
        callback();
      }
      var placeholderData = JSON.parse(localStorage.getItem('store.placeholders'));
      if (placeholderData) {
        _placeholders = _.omit(placeholderData, _.keys(_containers));
        localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
      }
      this.emit(this.CLIENT_CONTAINER_EVENT);
      this._resumePulling(err => {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to resume pulling', err);
      });
      this._startListeningToEvents(err => {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to listen to events', err);
      });
    });
  },
  fetchContainer: function (id, callback) {
    docker.client().getContainer(id).inspect((err, container) => {
      if (err) {
        callback(err);
      } else {
        if (container.Config.Image === container.Image.slice(0, 12) || container.Config.Image === container.Image) {
          callback();
          return;
        }
        // Fix leading slash in container names
        container.Name = container.Name.replace('/', '');
        _containers[container.Name] = container;
        callback(null, container);
      }
    });
  },
  fetchAllContainers: function (callback) {
    docker.client().listContainers({all: true}, (err, containers) => {
      if (err) {
        callback(err);
        return;
      }
      var names = new Set(_.map(containers, container => container.Names[0].replace('/', '')));
      _.each(_.keys(_containers), name => {
        if (!names.has(name)) {
          delete _containers[name];
        }
      });
      async.each(containers, (container, callback) => {
        this.fetchContainer(container.Id, function (err) {
          callback(err);
        });
      }, function (err) {
        callback(err);
      });
    });
  },
  create: function (repository, tag, callback) {
    tag = tag || 'latest';
    var imageName = repository + ':' + tag;
    var containerName = this._generateName(repository);

    _placeholders[containerName] = {
      Name: containerName,
      Image: imageName,
      Config: {
        Image: imageName,
      },
      State: {
        Downloading: true
      }
    };
    localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
    this.emit(this.CLIENT_CONTAINER_EVENT, containerName, 'create');

    _muted[containerName] = true;
    _progress[containerName] = 0;
    this._pullImage(repository, tag, err => {
      if (err) {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to create container', err);
        return;
      }
      _error = null;
      _blocked[containerName] = false;
      if (!_placeholders[containerName]) {
        return;
      }
      delete _placeholders[containerName];
      localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
      this._createContainer(containerName, {Image: imageName}, err => {
        if (err) {
          console.log(err);
          _error = err;
          this.emit(this.SERVER_ERROR_EVENT, err);
          return;
        }
        _error = null;
        metrics.track('Container Finished Creating');
        delete _progress[containerName];
        _muted[containerName] = false;
        this.emit(this.CLIENT_CONTAINER_EVENT, containerName);
      });
    }, progress => {
      _blocked[containerName] = false;
      _progress[containerName] = progress;
      this.emit(this.SERVER_PROGRESS_EVENT, containerName);
    }, () => {
      _blocked[containerName] = true;
      this.emit(this.SERVER_PROGRESS_EVENT, containerName);
    });
    callback(null, containerName);
  },
  updateContainer: function (name, data, callback) {
    _muted[name] = true;
    if (!data.name) {
      data.name = data.Name;
    }
    if (name !== data.name) {
      LogStore.rename(name, data.name);
    }
    var fullData = assign(_containers[name], data);
    this._createContainer(name, fullData, function () {
      _muted[name] = false;
      this.emit(this.CLIENT_CONTAINER_EVENT, name);
      callback();
    }.bind(this));
  },
  rename: function (name, newName, callback) {
    LogStore.rename(name, newName);
    docker.client().getContainer(name).rename({name: newName}, err => {
      if (err && err.statusCode !== 204) {
        callback(err);
        return;
      }
      this.fetchAllContainers(err => {
        this.emit(this.CLIENT_CONTAINER_EVENT);
        callback(err);
      });
    });
  },
  restart: function (name, callback) {
    var container = docker.client().getContainer(name);
    container.restart(function (err) {
      callback(err);
    });
  },
  remove: function (name, callback) {
    if (_placeholders[name]) {
      delete _placeholders[name];
      localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
      this.emit(this.CLIENT_CONTAINER_EVENT, name, 'destroy');
      callback();
      return;
    }
    var container = docker.client().getContainer(name);
    if (_containers[name].State.Paused) {
      container.unpause(function (err) {
        if (err) {
          callback(err);
          return;
        } else {
          container.kill(function (err) {
            if (err) {
              callback(err);
              return;
            } else {
              container.remove(function (err) {
                if (err) {
                  callback(err);
                  return;
                }
              });
            }
          });
        }
      });
    } else {
      container.kill(function (err) {
        if (err) {
          callback(err);
          return;
        } else {
          container.remove(function (err) {
            callback(err);
          });
        }
      });
    }
  },
  containers: function() {
    return _.extend(_.clone(_containers), _placeholders);
  },
  container: function (name) {
    return this.containers()[name];
  },
  sorted: function () {
    return _.values(this.containers()).sort(function (a, b) {
      if (a.State.Downloading && !b.State.Downloading) {
        return -1;
      } else if (!a.State.Downloading && b.State.Downloading) {
        return 1;
      } else {
        if (a.State.Running && !b.State.Running) {
          return -1;
        } else if (!a.State.Running && b.State.Running) {
          return 1;
        } else {
          return a.Name.localeCompare(b.Name);
        }
      }
    });
  },
  progress: function (name) {
    return _progress[name];
  },
  blocked: function (name) {
    return !!_blocked[name];
  },
  error: function () {
    return _error;
  }
});

module.exports = ContainerStore;
<commit_msg>Fix status check<commit_after>var _ = require('underscore');
var EventEmitter = require('events').EventEmitter;
var async = require('async');
var path = require('path');
var assign = require('object-assign');
var docker = require('./Docker');
var metrics = require('./Metrics');
var registry = require('./Registry');
var LogStore = require('./LogStore');
var bugsnag = require('bugsnag-js');

var _placeholders = {};
var _containers = {};
var _progress = {};
var _muted = {};
var _blocked = {};
var _error = null;

var ContainerStore = assign(Object.create(EventEmitter.prototype), {
  CLIENT_CONTAINER_EVENT: 'client_container_event',
  SERVER_CONTAINER_EVENT: 'server_container_event',
  SERVER_PROGRESS_EVENT: 'server_progress_event',
  SERVER_ERROR_EVENT: 'server_error_event',
  _pullImage: function (repository, tag, callback, progressCallback, blockedCallback) {
    registry.layers(repository, tag, (err, layerSizes) => {

      // TODO: Support v2 registry API
      // TODO: clean this up- It's messy to work with pulls from both the v1 and v2 registry APIs
      // Use the per-layer pull progress % to update the total progress.
      docker.client().listImages({all: 1}, (err, images) => {
        images = images || [];
        var existingIds = new Set(images.map(function (image) {
          return image.Id.slice(0, 12);
        }));
        var layersToDownload = layerSizes.filter(function (layerSize) {
          return !existingIds.has(layerSize.Id);
        });

        var totalBytes = layersToDownload.map(function (s) { return s.size; }).reduce(function (pv, sv) { return pv + sv; }, 0);
        docker.client().pull(repository + ':' + tag, (err, stream) => {
          if (err) {
            callback(err);
            return;
          }
          stream.setEncoding('utf8');

          var layerProgress = layersToDownload.reduce(function (r, layer) {
            if (_.findWhere(images, {Id: layer.Id})) {
              r[layer.Id] = 100;
            } else {
              r[layer.Id] = 0;
            }
            return r;
          }, {});

          stream.on('data', str => {
            var data = JSON.parse(str);
            console.log(data);

            if (data.status && (data.status === 'Pulling dependent layers' || data.status.indexOf('already being pulled by another client') !== -1)) {
              blockedCallback();
              return;
            }

            if (data.status === 'Already exists') {
              layerProgress[data.id] = 1;
            } else if (data.status === 'Downloading') {
              var current = data.progressDetail.current;
              var total = data.progressDetail.total;
              var layerFraction = current / total;
              layerProgress[data.id] = layerFraction;
            }

            var chunks = layersToDownload.map(function (s) {
              return layerProgress[s.Id] * s.size;
            });

            var totalReceived = chunks.reduce(function (pv, sv) {
              return pv + sv;
            }, 0);

            var totalProgress = totalReceived / totalBytes;
            progressCallback(totalProgress);
          });
          stream.on('end', function () {
            callback();
          });
        });
      });
    });
  },
  _createContainer: function (name, containerData, callback) {
    var existing = docker.client().getContainer(name);
    var self = this;
    if (!containerData.name && containerData.Name) {
      containerData.name = containerData.Name;
    } else if (!containerData.name) {
      containerData.name = name;
    }
    if (containerData.Config && containerData.Config.Image) {
      containerData.Image = containerData.Config.Image;
    }
    existing.kill(function () {
      existing.remove(function () {
        docker.client().getImage(containerData.Image).inspect(function (err, data) {
          if (err) {
            callback(err);
            return;
          }
          var binds = containerData.Binds || [];
          if (data.Config.Volumes) {
            _.each(data.Config.Volumes, function (value, key) {
              var existingBind = _.find(binds, b => {
                return b.indexOf(':' + key) !== -1;
              });
              if (!existingBind) {
                binds.push(path.join(process.env.HOME, 'Kitematic', containerData.name, key)+ ':' + key);
              }
            });
          }
          docker.client().createContainer(containerData, function (err, container) {
            if (err) {
              callback(err, null);
              return;
            }
            if (containerData.State && !containerData.State.Running) {
              self.fetchContainer(containerData.name, callback);
            } else {
              var startopts = {
                Binds: binds
              };
              if (containerData.NetworkSettings && containerData.NetworkSettings.Ports) {
                startopts.PortBindings = containerData.NetworkSettings.Ports;
              } else{
                startopts.PublishAllPorts = true;
              }
              container.start(startopts, function (err) {
                if (err) {
                  callback(err);
                  return;
                }
                self.fetchContainer(containerData.name, callback);
              });
            }
          });
        });
      });
    });
  },
  _generateName: function (repository) {
    var base = _.last(repository.split('/'));
    var count = 1;
    var name = base;
    while (true) {
      if (!this.containers()[name]) {
        return name;
      } else {
        count++;
        name = base + '-' + count;
      }
    }
  },
  _resumePulling: function (callback) {
    var downloading = _.filter(_.values(this.containers()), function (container) {
      return container.State.Downloading;
    });

    // Recover any pulls that were happening
    var self = this;
    downloading.forEach(function (container) {
      _progress[container.Name] = 99;
      docker.client().pull(container.Config.Image, function (err, stream) {
        if (err) {
          callback(err);
          return;
        }
        stream.setEncoding('utf8');
        stream.on('data', function () {});
        stream.on('end', function () {
          delete _placeholders[container.Name];
          localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
          self._createContainer(container.Name, {Image: container.Config.Image}, err => {
            if (err) {
              callback(err);
              return;
            }
            self.emit(self.SERVER_PROGRESS_EVENT, container.Name);
            self.emit(self.CLIENT_CONTAINER_EVENT, container.Name);
          });
        });
      });
    });
  },
  _startListeningToEvents: function (callback) {
    docker.client().getEvents((err, stream) => {
      if (err) {
        callback(err);
        return;
      }
      if (stream) {
        stream.setEncoding('utf8');
        stream.on('data', this._dockerEvent.bind(this));
      }
    });
  },
  _dockerEvent: function (json) {
    var data = JSON.parse(json);
    console.log(data);

    // If the event is delete, remove the container
    if (data.status === 'destroy') {
      var container = _.findWhere(_.values(_containers), {Id: data.id});
      if (container) {
        delete _containers[container.Name];
        if (!_muted[container.Name]) {
          this.emit(this.SERVER_CONTAINER_EVENT, container.Name, data.status);
        }
      } else {
        this.emit(this.SERVER_CONTAINER_EVENT, data.status);
      }
    } else {
      this.fetchContainer(data.id, err => {
        if (err) {
          return;
        }
        var container = _.findWhere(_.values(_containers), {Id: data.id});
        if (!container || _muted[container.Name]) {
          return;
        }
        this.emit(this.SERVER_CONTAINER_EVENT, container ? container.Name : null, data.status);
      });
    }
  },
  init: function (callback) {
    // TODO: Load cached data from db on loading
    this.fetchAllContainers(err => {
      if (err) {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to init', err);
        callback(err);
        return;
      } else {
        callback();
      }
      var placeholderData = JSON.parse(localStorage.getItem('store.placeholders'));
      if (placeholderData) {
        _placeholders = _.omit(placeholderData, _.keys(_containers));
        localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
      }
      this.emit(this.CLIENT_CONTAINER_EVENT);
      this._resumePulling(err => {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to resume pulling', err);
      });
      this._startListeningToEvents(err => {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to listen to events', err);
      });
    });
  },
  fetchContainer: function (id, callback) {
    docker.client().getContainer(id).inspect((err, container) => {
      if (err) {
        callback(err);
      } else {
        if (container.Config.Image === container.Image.slice(0, 12) || container.Config.Image === container.Image) {
          callback();
          return;
        }
        // Fix leading slash in container names
        container.Name = container.Name.replace('/', '');
        _containers[container.Name] = container;
        callback(null, container);
      }
    });
  },
  fetchAllContainers: function (callback) {
    docker.client().listContainers({all: true}, (err, containers) => {
      if (err) {
        callback(err);
        return;
      }
      var names = new Set(_.map(containers, container => container.Names[0].replace('/', '')));
      _.each(_.keys(_containers), name => {
        if (!names.has(name)) {
          delete _containers[name];
        }
      });
      async.each(containers, (container, callback) => {
        this.fetchContainer(container.Id, function (err) {
          callback(err);
        });
      }, function (err) {
        callback(err);
      });
    });
  },
  create: function (repository, tag, callback) {
    tag = tag || 'latest';
    var imageName = repository + ':' + tag;
    var containerName = this._generateName(repository);

    _placeholders[containerName] = {
      Name: containerName,
      Image: imageName,
      Config: {
        Image: imageName,
      },
      State: {
        Downloading: true
      }
    };
    localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
    this.emit(this.CLIENT_CONTAINER_EVENT, containerName, 'create');

    _muted[containerName] = true;
    _progress[containerName] = 0;
    this._pullImage(repository, tag, err => {
      if (err) {
        _error = err;
        this.emit(this.SERVER_ERROR_EVENT, err);
        bugsnag.notify(err, 'Container Store failed to create container', err);
        return;
      }
      _error = null;
      _blocked[containerName] = false;
      if (!_placeholders[containerName]) {
        return;
      }
      delete _placeholders[containerName];
      localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
      this._createContainer(containerName, {Image: imageName}, err => {
        if (err) {
          console.log(err);
          _error = err;
          this.emit(this.SERVER_ERROR_EVENT, err);
          return;
        }
        _error = null;
        metrics.track('Container Finished Creating');
        delete _progress[containerName];
        _muted[containerName] = false;
        this.emit(this.CLIENT_CONTAINER_EVENT, containerName);
      });
    }, progress => {
      _blocked[containerName] = false;
      _progress[containerName] = progress;
      this.emit(this.SERVER_PROGRESS_EVENT, containerName);
    }, () => {
      _blocked[containerName] = true;
      this.emit(this.SERVER_PROGRESS_EVENT, containerName);
    });
    callback(null, containerName);
  },
  updateContainer: function (name, data, callback) {
    _muted[name] = true;
    if (!data.name) {
      data.name = data.Name;
    }
    if (name !== data.name) {
      LogStore.rename(name, data.name);
    }
    var fullData = assign(_containers[name], data);
    this._createContainer(name, fullData, function () {
      _muted[name] = false;
      this.emit(this.CLIENT_CONTAINER_EVENT, name);
      callback();
    }.bind(this));
  },
  rename: function (name, newName, callback) {
    LogStore.rename(name, newName);
    docker.client().getContainer(name).rename({name: newName}, err => {
      if (err && err.statusCode !== 204) {
        callback(err);
        return;
      }
      this.fetchAllContainers(err => {
        this.emit(this.CLIENT_CONTAINER_EVENT);
        callback(err);
      });
    });
  },
  restart: function (name, callback) {
    var container = docker.client().getContainer(name);
    container.restart(function (err) {
      callback(err);
    });
  },
  remove: function (name, callback) {
    if (_placeholders[name]) {
      delete _placeholders[name];
      localStorage.setItem('store.placeholders', JSON.stringify(_placeholders));
      this.emit(this.CLIENT_CONTAINER_EVENT, name, 'destroy');
      callback();
      return;
    }
    var container = docker.client().getContainer(name);
    if (_containers[name].State.Paused) {
      container.unpause(function (err) {
        if (err) {
          callback(err);
          return;
        } else {
          container.kill(function (err) {
            if (err) {
              callback(err);
              return;
            } else {
              container.remove(function (err) {
                if (err) {
                  callback(err);
                  return;
                }
              });
            }
          });
        }
      });
    } else {
      container.kill(function (err) {
        if (err) {
          callback(err);
          return;
        } else {
          container.remove(function (err) {
            callback(err);
          });
        }
      });
    }
  },
  containers: function() {
    return _.extend(_.clone(_containers), _placeholders);
  },
  container: function (name) {
    return this.containers()[name];
  },
  sorted: function () {
    return _.values(this.containers()).sort(function (a, b) {
      if (a.State.Downloading && !b.State.Downloading) {
        return -1;
      } else if (!a.State.Downloading && b.State.Downloading) {
        return 1;
      } else {
        if (a.State.Running && !b.State.Running) {
          return -1;
        } else if (!a.State.Running && b.State.Running) {
          return 1;
        } else {
          return a.Name.localeCompare(b.Name);
        }
      }
    });
  },
  progress: function (name) {
    return _progress[name];
  },
  blocked: function (name) {
    return !!_blocked[name];
  },
  error: function () {
    return _error;
  }
});

module.exports = ContainerStore;
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { playbackStart, playbackStop, recordingStart, recordingStop } from '../actions';
import { MICROPHONE_STATE } from '../constants';
import Button from './Button';
import Layer from './Layer';
import './App.css';

class App extends React.Component {
  render() {
    const { microphoneState } = this.props;

    switch (microphoneState) {
      case MICROPHONE_STATE.DISABLED:
        return (

Sorry...

              I can't access your microphone.

        );
      case MICROPHONE_STATE.REQUESTED_PERMISSION:
        return (

Hi.

              Please allow this site to use your microphone.

(look up)

        );
      case MICROPHONE_STATE.ENABLED:
        return this.renderRecorder();
      default:
    }

    return null;
  }

  renderRecorder() {
    const { isCapturing, isPlaying, isRecording, layers } = this.props;
    const { onStartPlaying, onStopPlaying, onStartRecording, onStopRecording } = this.props;

    return (

          <Button
            hasMargin
            isDown={isRecording}
            onClick={isRecording ? onStopRecording : onStartRecording}
          >
            Record
          </Button>
          {layers.length ?
            <Button
              isDown={isPlaying}
              onClick={isPlaying ? onStopPlaying : onStartPlaying}
            >
              Play
            </Button>
            :
            null
          }

        {isCapturing ?
...
 : null}
        {layers.map((layer) => (
          <Layer layer={layer} />
        ))}

    );
  }
}

function mapStateToProps(state) {
  return state;
}

function mapDispatchToProps(dispatch) {
  return {
    onStartPlaying: function () {
      dispatch(playbackStart());
    },
    onStopPlaying: function () {
      dispatch(playbackStop());
    },
    onStartRecording: function () {
      dispatch(recordingStart());
    },
    onStopRecording: function () {
      dispatch(recordingStop());
    }
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App);
<commit_msg>specify state in App mapStateToProps and map actions directly<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { playbackStart, playbackStop, recordingStart, recordingStop } from '../actions';
import { MICROPHONE_STATE } from '../constants';
import Button from './Button';
import Layer from './Layer';
import './App.css';

class App extends React.Component {
  render() {
    const { microphoneState } = this.props;

    switch (microphoneState) {
      case MICROPHONE_STATE.DISABLED:
        return (

Sorry...

              I can't access your microphone.

        );
      case MICROPHONE_STATE.REQUESTED_PERMISSION:
        return (

Hi.

              Please allow this site to use your microphone.

(look up)

        );
      case MICROPHONE_STATE.ENABLED:
        return this.renderRecorder();
      default:
    }

    return null;
  }

  renderRecorder() {
    const { isCapturing, isPlaying, isRecording, layers } = this.props;
    const { playbackStart, playbackStop, recordingStart, recordingStop } = this.props;

    return (

          <Button
            hasMargin
            isDown={isRecording}
            onClick={isRecording ? recordingStop : recordingStart}
          >
            Record
          </Button>
          {layers.length ?
            <Button
              isDown={isPlaying}
              onClick={isPlaying ? playbackStop : playbackStart}
            >
              Play
            </Button>
            :
            null
          }

        {isCapturing ?
...
 : null}
        {layers.map((layer) => (
          <Layer layer={layer} />
        ))}

    );
  }
}

function mapStateToProps(state) {
  return {
    isCapturing: state.isCapturing,
    isPlaying: state.isPlaying,
    isRecording: state.isRecording,
    layers: state.layers,
    microphoneState: state.microphoneState
  };
}

export default connect(mapStateToProps, {
  playbackStart,
  playbackStop,
  recordingStart,
  recordingStop
})(App);
<|endoftext|>