<commit_before>/*
 * Leaflet-IIIF 0.0.9
 * IIIF Viewer for Leaflet
 * by Jack Reed, @mejackreed
 */

L.TileLayer.Iiif = L.TileLayer.extend({
  options: {
    continuousWorld: true,
    tileSize: 256,
    updateWhenIdle: true,
    tileFormat: 'jpg'
  },

  initialize: function(url, options) {
    if (options.maxZoom) {
      this._customMaxZoom = true;
    }

    // Check for explicit tileSize set
    if (options.tileSize) {
      this._explicitTileSize = true;
    }

    options = L.setOptions(this, options);
    this._infoDeferred = new $.Deferred();
    this._infoUrl = url;
    this._baseUrl = this._templateUrl();
    this._getInfo();
  },
  getTileUrl: function(coords) {
    var _this = this,
      x = coords.x,
      y = (coords.y),
      zoom = _this._map.getZoom(),
      scale = Math.pow(2, _this.maxNativeZoom - zoom),
      tileBaseSize = _this.options.tileSize * scale,
      minx = (x * tileBaseSize),
      miny = (y * tileBaseSize),
      maxx = Math.min(minx + tileBaseSize, _this.x),
      maxy = Math.min(miny + tileBaseSize, _this.y);

    var xDiff = (maxx - minx);
    var yDiff = (maxy - miny);

    return L.Util.template(this._baseUrl, L.extend({
      format: _this.options.tileFormat,
      quality: _this.quality,
      region: [minx, miny, xDiff, yDiff].join(','),
      rotation: 0,
      size: _this._iiifSizeParam(Math.ceil(xDiff / scale), Math.ceil(yDiff / scale))
    }, this.options));
  },
  /**
  * Returns a IIIF size parameter based off of the max dimension of
  * a tile
  * @param {Number} x - The width of a tile
  * @param {Number} y - The height of a tile
  * @returns {String}
  */
  _iiifSizeParam: function(x, y) {
    if (x >= y) {
      return x + ',';
    } else {
      return ',' + y;
    }
  },
  onAdd: function(map) {
    var _this = this;

    // Wait for deferred to complete
    $.when(_this._infoDeferred).done(function() {

      // Find best zoom level and center map
      var initialZoom = _this._getInitialZoom(map.getSize()),
        imageSize = _this._imageSizes[initialZoom],
        sw = map.options.crs.pointToLatLng(L.point(0, imageSize.y), initialZoom),
        ne = map.options.crs.pointToLatLng(L.point(imageSize.x, 0), initialZoom),
        bounds = L.latLngBounds(sw, ne);

      map.fitBounds(bounds, true);

      // Set maxZoom for map
      map._layersMaxZoom = _this.maxZoom;

      // Call add TileLayer
      L.TileLayer.prototype.onAdd.call(_this, map);

      // Reset tile sizes to handle non 256x256 IIIF tiles
      _this.on('tileload', function(tile, url) {

        var height = tile.tile.naturalHeight,
          width = tile.tile.naturalWidth;

        // No need to resize if tile is 256 x 256
        if (height === 256 && width === 256) return;

        tile.tile.style.width = width + 'px';
        tile.tile.style.height = height + 'px';

      });
    });
  },
  _getInfo: function() {
    var _this = this;

    // Look for a way to do this without jQuery
    $.getJSON(_this._infoUrl)
      .done(function(data) {
        _this.y = data.height;
        _this.x = data.width;

        var profile,
          tierSizes = [],
          imageSizes = [],
          scale,
          width_,
          height_,
          tilesX_,
          tilesY_;

        // Set quality based off of IIIF version
        if (data.profile instanceof Array) {
          profile = data.profile[0];
        }else {
          profile = data.profile;
        }
        switch (true) {
          case /^http:\/\/library.stanford.edu\/iiif\/image-api\/1.1\/compliance.html.*$/.test(profile):
            _this.quality = 'native';
            break;
          case /^http:\/\/iiif.io\/api\/image\/2.*$/.test(profile):
            _this.quality = 'default';
            break;
        }

        // Unless an explicit tileSize is set, use a preferred tileSize
        if (!_this.explicitTileSize) {
          if (data.tiles) {
            // Image API 2.0 Case
            _this.options.tileSize = data.tiles[0].width;
          } else if (data.tile_width){
            // Image API 1.1 Case
            _this.options.tileSize = data.tile_width;
          }
        }

        ceilLog2 = function(x) {
          return Math.ceil(Math.log(x) / Math.LN2);
        };

        // Calculates maximum native zoom for the layer
        _this.maxNativeZoom = Math.max(ceilLog2(_this.x / _this.options.tileSize),
          ceilLog2(_this.y / _this.options.tileSize));

        // Enable zooming further than native if maxZoom option supplied
        if (_this._customMaxZoom && _this.options.maxZoom > _this.maxNativeZoom) {
          _this.maxZoom = _this.options.maxZoom;
        }
        else {
          _this.maxZoom = _this.maxNativeZoom;
        }

        for (var i = 0; i <= _this.maxZoom; i++) {
          scale = Math.pow(2, _this.maxNativeZoom - i);
          width_ = Math.ceil(_this.x / scale);
          height_ = Math.ceil(_this.y / scale);
          tilesX_ = Math.ceil(width_ / _this.options.tileSize);
          tilesY_ = Math.ceil(height_ / _this.options.tileSize);
          tierSizes.push([tilesX_, tilesY_]);
          imageSizes.push(L.point(width_,height_));
        }

        _this._tierSizes = tierSizes;
        _this._imageSizes = imageSizes;

        // Resolved Deferred to initiate tilelayer load
        _this._infoDeferred.resolve();
      });
  },
  _infoToBaseUrl: function() {
    return this._infoUrl.replace('info.json', '');
  },
  _templateUrl: function() {
    return this._infoToBaseUrl() + '{region}/{size}/{rotation}/{quality}.{format}';
  },
  _tileShouldBeLoaded: function(coords) {
    var _this = this,
      zoom = _this._map.getZoom(),
      sizes = _this._tierSizes[zoom],
      x = coords.x,
      y = (coords.y);

    if (!sizes) return false;
    if (x < 0 || sizes[0] <= x || y < 0 || sizes[1] <= y) {
      return false;
    }else {
      return true;
    }
  },
  _getInitialZoom: function (mapSize) {
    var _this = this,
      tolerance = 0.8,
      imageSize;

    for (var i = _this.maxNativeZoom; i >= 0; i--) {
      imageSize = this._imageSizes[i];
      if (imageSize.x * tolerance < mapSize.x && imageSize.y * tolerance < mapSize.y) {
        return i;
      }
    }
    // return a default zoom
    return 2;
  }
});

L.tileLayer.iiif = function(url, options) {
  return new L.TileLayer.Iiif(url, options);
};
<commit_msg>Check for options being passed in on instantiation<commit_after>/*
 * Leaflet-IIIF 0.0.9
 * IIIF Viewer for Leaflet
 * by Jack Reed, @mejackreed
 */

L.TileLayer.Iiif = L.TileLayer.extend({
  options: {
    continuousWorld: true,
    tileSize: 256,
    updateWhenIdle: true,
    tileFormat: 'jpg'
  },

  initialize: function(url, options) {
    options = typeof options !== 'undefined' ? options : {};

    if (options.maxZoom) {
      this._customMaxZoom = true;
    }

    // Check for explicit tileSize set
    if (options.tileSize) {
      this._explicitTileSize = true;
    }

    options = L.setOptions(this, options);
    this._infoDeferred = new $.Deferred();
    this._infoUrl = url;
    this._baseUrl = this._templateUrl();
    this._getInfo();
  },
  getTileUrl: function(coords) {
    var _this = this,
      x = coords.x,
      y = (coords.y),
      zoom = _this._map.getZoom(),
      scale = Math.pow(2, _this.maxNativeZoom - zoom),
      tileBaseSize = _this.options.tileSize * scale,
      minx = (x * tileBaseSize),
      miny = (y * tileBaseSize),
      maxx = Math.min(minx + tileBaseSize, _this.x),
      maxy = Math.min(miny + tileBaseSize, _this.y);

    var xDiff = (maxx - minx);
    var yDiff = (maxy - miny);

    return L.Util.template(this._baseUrl, L.extend({
      format: _this.options.tileFormat,
      quality: _this.quality,
      region: [minx, miny, xDiff, yDiff].join(','),
      rotation: 0,
      size: _this._iiifSizeParam(Math.ceil(xDiff / scale), Math.ceil(yDiff / scale))
    }, this.options));
  },
  /**
  * Returns a IIIF size parameter based off of the max dimension of
  * a tile
  * @param {Number} x - The width of a tile
  * @param {Number} y - The height of a tile
  * @returns {String}
  */
  _iiifSizeParam: function(x, y) {
    if (x >= y) {
      return x + ',';
    } else {
      return ',' + y;
    }
  },
  onAdd: function(map) {
    var _this = this;

    // Wait for deferred to complete
    $.when(_this._infoDeferred).done(function() {

      // Find best zoom level and center map
      var initialZoom = _this._getInitialZoom(map.getSize()),
        imageSize = _this._imageSizes[initialZoom],
        sw = map.options.crs.pointToLatLng(L.point(0, imageSize.y), initialZoom),
        ne = map.options.crs.pointToLatLng(L.point(imageSize.x, 0), initialZoom),
        bounds = L.latLngBounds(sw, ne);

      map.fitBounds(bounds, true);

      // Set maxZoom for map
      map._layersMaxZoom = _this.maxZoom;

      // Call add TileLayer
      L.TileLayer.prototype.onAdd.call(_this, map);

      // Reset tile sizes to handle non 256x256 IIIF tiles
      _this.on('tileload', function(tile, url) {

        var height = tile.tile.naturalHeight,
          width = tile.tile.naturalWidth;

        // No need to resize if tile is 256 x 256
        if (height === 256 && width === 256) return;

        tile.tile.style.width = width + 'px';
        tile.tile.style.height = height + 'px';

      });
    });
  },
  _getInfo: function() {
    var _this = this;

    // Look for a way to do this without jQuery
    $.getJSON(_this._infoUrl)
      .done(function(data) {
        _this.y = data.height;
        _this.x = data.width;

        var profile,
          tierSizes = [],
          imageSizes = [],
          scale,
          width_,
          height_,
          tilesX_,
          tilesY_;

        // Set quality based off of IIIF version
        if (data.profile instanceof Array) {
          profile = data.profile[0];
        }else {
          profile = data.profile;
        }
        switch (true) {
          case /^http:\/\/library.stanford.edu\/iiif\/image-api\/1.1\/compliance.html.*$/.test(profile):
            _this.quality = 'native';
            break;
          case /^http:\/\/iiif.io\/api\/image\/2.*$/.test(profile):
            _this.quality = 'default';
            break;
        }

        // Unless an explicit tileSize is set, use a preferred tileSize
        if (!_this.explicitTileSize) {
          if (data.tiles) {
            // Image API 2.0 Case
            _this.options.tileSize = data.tiles[0].width;
          } else if (data.tile_width){
            // Image API 1.1 Case
            _this.options.tileSize = data.tile_width;
          }
        }

        ceilLog2 = function(x) {
          return Math.ceil(Math.log(x) / Math.LN2);
        };

        // Calculates maximum native zoom for the layer
        _this.maxNativeZoom = Math.max(ceilLog2(_this.x / _this.options.tileSize),
          ceilLog2(_this.y / _this.options.tileSize));

        // Enable zooming further than native if maxZoom option supplied
        if (_this._customMaxZoom && _this.options.maxZoom > _this.maxNativeZoom) {
          _this.maxZoom = _this.options.maxZoom;
        }
        else {
          _this.maxZoom = _this.maxNativeZoom;
        }

        for (var i = 0; i <= _this.maxZoom; i++) {
          scale = Math.pow(2, _this.maxNativeZoom - i);
          width_ = Math.ceil(_this.x / scale);
          height_ = Math.ceil(_this.y / scale);
          tilesX_ = Math.ceil(width_ / _this.options.tileSize);
          tilesY_ = Math.ceil(height_ / _this.options.tileSize);
          tierSizes.push([tilesX_, tilesY_]);
          imageSizes.push(L.point(width_,height_));
        }

        _this._tierSizes = tierSizes;
        _this._imageSizes = imageSizes;

        // Resolved Deferred to initiate tilelayer load
        _this._infoDeferred.resolve();
      });
  },
  _infoToBaseUrl: function() {
    return this._infoUrl.replace('info.json', '');
  },
  _templateUrl: function() {
    return this._infoToBaseUrl() + '{region}/{size}/{rotation}/{quality}.{format}';
  },
  _tileShouldBeLoaded: function(coords) {
    var _this = this,
      zoom = _this._map.getZoom(),
      sizes = _this._tierSizes[zoom],
      x = coords.x,
      y = (coords.y);

    if (!sizes) return false;
    if (x < 0 || sizes[0] <= x || y < 0 || sizes[1] <= y) {
      return false;
    }else {
      return true;
    }
  },
  _getInitialZoom: function (mapSize) {
    var _this = this,
      tolerance = 0.8,
      imageSize;

    for (var i = _this.maxNativeZoom; i >= 0; i--) {
      imageSize = this._imageSizes[i];
      if (imageSize.x * tolerance < mapSize.x && imageSize.y * tolerance < mapSize.y) {
        return i;
      }
    }
    // return a default zoom
    return 2;
  }
});

L.tileLayer.iiif = function(url, options) {
  return new L.TileLayer.Iiif(url, options);
};
<|endoftext|>


<commit_before>// Copyright 2016 Google LLC
//
// 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.

'use strict';

// [START functions_slack_setup]
const {google} = require('googleapis');
const {verifyRequestSignature} = require('@slack/events-api');

// Get a reference to the Knowledge Graph Search component
const kgsearch = google.kgsearch('v1');
// [END functions_slack_setup]

// [START functions_slack_format]
/**
 * Format the Knowledge Graph API response into a richly formatted Slack message.
 *
 * @param {string} query The user's search query.
 * @param {object} response The response from the Knowledge Graph API.
 * @returns {object} The formatted message.
 */
const formatSlackMessage = (query, response) => {
  let entity;

  // Extract the first entity from the result list, if any
  if (
    response &&
    response.data &&
    response.data.itemListElement &&
    response.data.itemListElement.length > 0
  ) {
    entity = response.data.itemListElement[0].result;
  }

  // Prepare a rich Slack message
  // See https://api.slack.com/docs/message-formatting
  const slackMessage = {
    response_type: 'in_channel',
    text: `Query: ${query}`,
    attachments: [],
  };

  if (entity) {
    const attachment = {
      color: '#3367d6',
    };
    if (entity.name) {
      attachment.title = entity.name;
      if (entity.description) {
        attachment.title = `${attachment.title}: ${entity.description}`;
      }
    }
    if (entity.detailedDescription) {
      if (entity.detailedDescription.url) {
        attachment.title_link = entity.detailedDescription.url;
      }
      if (entity.detailedDescription.articleBody) {
        attachment.text = entity.detailedDescription.articleBody;
      }
    }
    if (entity.image && entity.image.contentUrl) {
      attachment.image_url = entity.image.contentUrl;
    }
    slackMessage.attachments.push(attachment);
  } else {
    slackMessage.attachments.push({
      text: 'No results match your query...',
    });
  }

  return slackMessage;
};
// [END functions_slack_format]

// [START functions_verify_webhook]
/**
 * Verify that the webhook request came from Slack.
 *
 * @param {object} req Cloud Function request object.
 * @param {string} req.headers Headers Slack SDK uses to authenticate request.
 * @param {string} req.rawBody Raw body of webhook request to check signature against.
 */
const verifyWebhook = req => {
  const signature = {
    signingSecret: process.env.SLACK_SECRET,
    requestSignature: req.headers['x-slack-signature'],
    requestTimestamp: req.headers['x-slack-request-timestamp'],
    body: req.rawBody,
  };

  if (!verifyRequestSignature(signature)) {
    const error = new Error('Invalid credentials');
    error.code = 401;
    throw error;
  }
};
// [END functions_verify_webhook]

// [START functions_slack_request]
/**
 * Send the user's search query to the Knowledge Graph API.
 *
 * @param {string} query The user's search query.
 */
const makeSearchRequest = query => {
  return new Promise((resolve, reject) => {
    kgsearch.entities.search(
      {
        auth: process.env.KG_API_KEY,
        query: query,
        limit: 1,
      },
      (err, response) => {
        console.log(err);
        if (err) {
          reject(err);
          return;
        }

        // Return a formatted message
        resolve(formatSlackMessage(query, response));
      }
    );
  });
};
// [END functions_slack_request]

// [START functions_slack_search]
/**
 * Receive a Slash Command request from Slack.
 *
 * Trigger this function by creating a Slack slash command with this URL:
 * https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/kgSearch
 *
 * @param {object} req Cloud Function request object.
 * @param {object} req.body The request payload.
 * @param {string} req.rawBody Raw request payload used to validate Slack's message signature.
 * @param {string} req.body.text The user's search query.
 * @param {object} res Cloud Function response object.
 */
exports.kgSearch = async (req, res) => {
  try {
    if (req.method !== 'POST') {
      const error = new Error('Only POST requests are accepted');
      error.code = 405;
      throw error;
    }

    // Verify that this request came from Slack
    verifyWebhook(req);

    // Make the request to the Knowledge Graph Search API
    const response = await makeSearchRequest(req.body.text);

    // Send the formatted message back to Slack
    res.json(response);

    return Promise.resolve();
  } catch (err) {
    console.error(err);
    res.status(err.code || 500).send(err);
    return Promise.reject(err);
  }
};
// [END functions_slack_search]
<commit_msg>chore(functions): remove unused exception handler (#2160)<commit_after>// Copyright 2016 Google LLC
//
// 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.

'use strict';

// [START functions_slack_setup]
const {google} = require('googleapis');
const {verifyRequestSignature} = require('@slack/events-api');

// Get a reference to the Knowledge Graph Search component
const kgsearch = google.kgsearch('v1');
// [END functions_slack_setup]

// [START functions_slack_format]
/**
 * Format the Knowledge Graph API response into a richly formatted Slack message.
 *
 * @param {string} query The user's search query.
 * @param {object} response The response from the Knowledge Graph API.
 * @returns {object} The formatted message.
 */
const formatSlackMessage = (query, response) => {
  let entity;

  // Extract the first entity from the result list, if any
  if (
    response &&
    response.data &&
    response.data.itemListElement &&
    response.data.itemListElement.length > 0
  ) {
    entity = response.data.itemListElement[0].result;
  }

  // Prepare a rich Slack message
  // See https://api.slack.com/docs/message-formatting
  const slackMessage = {
    response_type: 'in_channel',
    text: `Query: ${query}`,
    attachments: [],
  };

  if (entity) {
    const attachment = {
      color: '#3367d6',
    };
    if (entity.name) {
      attachment.title = entity.name;
      if (entity.description) {
        attachment.title = `${attachment.title}: ${entity.description}`;
      }
    }
    if (entity.detailedDescription) {
      if (entity.detailedDescription.url) {
        attachment.title_link = entity.detailedDescription.url;
      }
      if (entity.detailedDescription.articleBody) {
        attachment.text = entity.detailedDescription.articleBody;
      }
    }
    if (entity.image && entity.image.contentUrl) {
      attachment.image_url = entity.image.contentUrl;
    }
    slackMessage.attachments.push(attachment);
  } else {
    slackMessage.attachments.push({
      text: 'No results match your query...',
    });
  }

  return slackMessage;
};
// [END functions_slack_format]

// [START functions_verify_webhook]
/**
 * Verify that the webhook request came from Slack.
 *
 * @param {object} req Cloud Function request object.
 * @param {string} req.headers Headers Slack SDK uses to authenticate request.
 * @param {string} req.rawBody Raw body of webhook request to check signature against.
 */
const verifyWebhook = req => {
  const signature = {
    signingSecret: process.env.SLACK_SECRET,
    requestSignature: req.headers['x-slack-signature'],
    requestTimestamp: req.headers['x-slack-request-timestamp'],
    body: req.rawBody,
  };

  // This method throws an exception if an incoming request is invalid.
  verifyRequestSignature(signature);
};
// [END functions_verify_webhook]

// [START functions_slack_request]
/**
 * Send the user's search query to the Knowledge Graph API.
 *
 * @param {string} query The user's search query.
 */
const makeSearchRequest = query => {
  return new Promise((resolve, reject) => {
    kgsearch.entities.search(
      {
        auth: process.env.KG_API_KEY,
        query: query,
        limit: 1,
      },
      (err, response) => {
        console.log(err);
        if (err) {
          reject(err);
          return;
        }

        // Return a formatted message
        resolve(formatSlackMessage(query, response));
      }
    );
  });
};
// [END functions_slack_request]

// [START functions_slack_search]
/**
 * Receive a Slash Command request from Slack.
 *
 * Trigger this function by creating a Slack slash command with this URL:
 * https://[YOUR_REGION]-[YOUR_PROJECT_ID].cloudfunctions.net/kgSearch
 *
 * @param {object} req Cloud Function request object.
 * @param {object} req.body The request payload.
 * @param {string} req.rawBody Raw request payload used to validate Slack's message signature.
 * @param {string} req.body.text The user's search query.
 * @param {object} res Cloud Function response object.
 */
exports.kgSearch = async (req, res) => {
  try {
    if (req.method !== 'POST') {
      const error = new Error('Only POST requests are accepted');
      error.code = 405;
      throw error;
    }

    // Verify that this request came from Slack
    verifyWebhook(req);

    // Make the request to the Knowledge Graph Search API
    const response = await makeSearchRequest(req.body.text);

    // Send the formatted message back to Slack
    res.json(response);

    return Promise.resolve();
  } catch (err) {
    console.error(err);
    res.status(err.code || 500).send(err);
    return Promise.reject(err);
  }
};
// [END functions_slack_search]
<|endoftext|>


<commit_before>// @flow

import Animated, { Easing } from 'react-native-reanimated';
import { State as GestureState } from 'react-native-gesture-handler';
import { Platform } from 'react-native';

/* eslint-disable import/no-named-as-default-member */
const {
  Clock,
  Value,
  block,
  cond,
  not,
  and,
  or,
  greaterThan,
  lessThan,
  eq,
  sub,
  set,
  max,
  startClock,
  stopClock,
  clockRunning,
  timing,
} = Animated;
/* eslint-enable import/no-named-as-default-member */

function clamp(value: Value, minValue: Value, maxValue: Value): Value {
  return cond(
    greaterThan(value, maxValue),
    maxValue,
    cond(greaterThan(minValue, value), minValue, value),
  );
}

function delta(value: Value) {
  const prevValue = new Value(0);
  const deltaValue = new Value(0);
  return [
    set(deltaValue, cond(eq(prevValue, 0), 0, sub(value, prevValue))),
    set(prevValue, value),
    deltaValue,
  ];
}

function gestureJustStarted(state: Value) {
  const prevValue = new Value(-1);
  return cond(eq(prevValue, state), 0, [
    set(prevValue, state),
    eq(state, GestureState.ACTIVE),
  ]);
}

function gestureJustEnded(state: Value) {
  const prevValue = new Value(-1);
  return cond(eq(prevValue, state), 0, [
    set(prevValue, state),
    eq(state, GestureState.END),
  ]);
}

const defaultTimingConfig = {
  duration: 250,
  easing: Easing.out(Easing.ease),
};

type TimingConfig = $Shape<typeof defaultTimingConfig>;
function runTiming(
  clock: Clock,
  initialValue: Value | number,
  finalValue: Value | number,
  startStopClock: boolean = true,
  config: TimingConfig = defaultTimingConfig,
): Value {
  const state = {
    finished: new Value(0),
    position: new Value(0),
    frameTime: new Value(0),
    time: new Value(0),
  };
  const timingConfig = {
    ...defaultTimingConfig,
    ...config,
    toValue: new Value(0),
  };
  return [
    cond(not(clockRunning(clock)), [
      set(state.finished, 0),
      set(state.frameTime, 0),
      set(state.time, 0),
      set(state.position, initialValue),
      set(timingConfig.toValue, finalValue),
      startStopClock && startClock(clock),
    ]),
    timing(clock, state, timingConfig),
    cond(state.finished, startStopClock && stopClock(clock)),
    state.position,
  ];
}

// You provide a node that performs a "ratchet",
// and this function will call it as keyboard height increases
function ratchetAlongWithKeyboardHeight(
  keyboardHeight: Animated.Node,
  ratchetFunction: Animated.Node,
) {
  const prevKeyboardHeightValue = new Value(-1);
  const whenToUpdate = Platform.select({
    // In certain situations, iOS will send multiple keyboardShows in rapid
    // succession with increasing height values. Only the final value has any
    // semblance of reality. I've encountered this when using the native
    // password management integration
    ios: greaterThan(keyboardHeight, max(prevKeyboardHeightValue, 0)),
    // Android's keyboard can resize due to user interaction sometimes. In these
    // cases it can get quite big, in which case we don't want to update
    defaut: and(eq(prevKeyboardHeightValue, 0), greaterThan(keyboardHeight, 0)),
  });
  const whenToReset = and(
    eq(keyboardHeight, 0),
    greaterThan(prevKeyboardHeightValue, 0),
  );
  return block([
    cond(
      lessThan(prevKeyboardHeightValue, 0),
      set(prevKeyboardHeightValue, keyboardHeight),
    ),
    cond(or(whenToUpdate, whenToReset), ratchetFunction),
    set(prevKeyboardHeightValue, keyboardHeight),
  ]);
}

export {
  clamp,
  delta,
  gestureJustStarted,
  gestureJustEnded,
  runTiming,
  ratchetAlongWithKeyboardHeight,
};
<commit_msg>[native] Fix ratchetAlongWithKeyboardHeight on Android<commit_after>// @flow

import Animated, { Easing } from 'react-native-reanimated';
import { State as GestureState } from 'react-native-gesture-handler';
import { Platform } from 'react-native';

/* eslint-disable import/no-named-as-default-member */
const {
  Clock,
  Value,
  block,
  cond,
  not,
  and,
  or,
  greaterThan,
  lessThan,
  eq,
  sub,
  set,
  max,
  startClock,
  stopClock,
  clockRunning,
  timing,
} = Animated;
/* eslint-enable import/no-named-as-default-member */

function clamp(value: Value, minValue: Value, maxValue: Value): Value {
  return cond(
    greaterThan(value, maxValue),
    maxValue,
    cond(greaterThan(minValue, value), minValue, value),
  );
}

function delta(value: Value) {
  const prevValue = new Value(0);
  const deltaValue = new Value(0);
  return [
    set(deltaValue, cond(eq(prevValue, 0), 0, sub(value, prevValue))),
    set(prevValue, value),
    deltaValue,
  ];
}

function gestureJustStarted(state: Value) {
  const prevValue = new Value(-1);
  return cond(eq(prevValue, state), 0, [
    set(prevValue, state),
    eq(state, GestureState.ACTIVE),
  ]);
}

function gestureJustEnded(state: Value) {
  const prevValue = new Value(-1);
  return cond(eq(prevValue, state), 0, [
    set(prevValue, state),
    eq(state, GestureState.END),
  ]);
}

const defaultTimingConfig = {
  duration: 250,
  easing: Easing.out(Easing.ease),
};

type TimingConfig = $Shape<typeof defaultTimingConfig>;
function runTiming(
  clock: Clock,
  initialValue: Value | number,
  finalValue: Value | number,
  startStopClock: boolean = true,
  config: TimingConfig = defaultTimingConfig,
): Value {
  const state = {
    finished: new Value(0),
    position: new Value(0),
    frameTime: new Value(0),
    time: new Value(0),
  };
  const timingConfig = {
    ...defaultTimingConfig,
    ...config,
    toValue: new Value(0),
  };
  return [
    cond(not(clockRunning(clock)), [
      set(state.finished, 0),
      set(state.frameTime, 0),
      set(state.time, 0),
      set(state.position, initialValue),
      set(timingConfig.toValue, finalValue),
      startStopClock && startClock(clock),
    ]),
    timing(clock, state, timingConfig),
    cond(state.finished, startStopClock && stopClock(clock)),
    state.position,
  ];
}

// You provide a node that performs a "ratchet",
// and this function will call it as keyboard height increases
function ratchetAlongWithKeyboardHeight(
  keyboardHeight: Animated.Node,
  ratchetFunction: Animated.Node,
) {
  const prevKeyboardHeightValue = new Value(-1);
  const whenToUpdate = Platform.select({
    // In certain situations, iOS will send multiple keyboardShows in rapid
    // succession with increasing height values. Only the final value has any
    // semblance of reality. I've encountered this when using the native
    // password management integration
    ios: greaterThan(keyboardHeight, max(prevKeyboardHeightValue, 0)),
    // Android's keyboard can resize due to user interaction sometimes. In these
    // cases it can get quite big, in which case we don't want to update
    default: and(
      eq(prevKeyboardHeightValue, 0),
      greaterThan(keyboardHeight, 0),
    ),
  });
  const whenToReset = and(
    eq(keyboardHeight, 0),
    greaterThan(prevKeyboardHeightValue, 0),
  );
  return block([
    cond(
      lessThan(prevKeyboardHeightValue, 0),
      set(prevKeyboardHeightValue, keyboardHeight),
    ),
    cond(or(whenToUpdate, whenToReset), ratchetFunction),
    set(prevKeyboardHeightValue, keyboardHeight),
  ]);
}

export {
  clamp,
  delta,
  gestureJustStarted,
  gestureJustEnded,
  runTiming,
  ratchetAlongWithKeyboardHeight,
};
<|endoftext|>


<commit_before>define(['jquery', 'base/js/dialog', 'base/js/namespace'],
function ($, dialog, Jupyter) {
    'use strict';

    var NS = 'anaconda-cloud',
      THUMBNAIL_MIN_DIM = 48,
      LOGO_URL = 'http://binstar-static-prod.s3.amazonaws.com' +
        '/latest/img/AnacondaCloud_logo_green.png',
      credentials = {};

    function api(route){
      // reusable API mapping, handles
      return Jupyter.notebook.base_url + NS + '/' + route;
    }

    function metadata(key, value){
        // wrapper for metadata, ensuring the top-level namespace
        var md = Jupyter.notebook.metadata;

        // ensure top-level namespace is initialized
        md[NS] = md[NS] || {};

        if (!arguments.length) {
          return md[NS];
        } else if(arguments.length === 1){
          return md[NS][key];
        } else{
          md[NS][key] = value;
        }
    }

    function publishNotebook(){
      /*
        Select the current environment for the running kernel.
        This is dependent on behavior of 'nb_conda_kernels' 2.0.0:
        - <normal name, like ir, python3, etc>  # server env
        - conda-env-<env name>-<lang key, py or ir>
        - conda-root-<lang key>
        */
        var kernel_name = IPython.notebook.kernel.name,
            kernel_env_re = /^conda-(env-)?(.+?)(-[^\-]*)?$/,
            match = kernel_env_re.exec(kernel_name),
            env_name;

        if(match) {
            env_name = m[2];
        }

        metadata('environment', metadata('attach-environment') ?
            env_name :
            null);
        Jupyter.notebook.save_notebook().then(uploadNotebook);
    }

    function uploadNotebook() {
        var interval;

        $.ajax({
              url: api('publish'),
              method: 'POST',
              dataType: 'json',
              contentType: 'application/json; charset=utf-8',
              processData: false,
              data: JSON.stringify({
                  name: Jupyter.notebook.notebook_name,
                  content: Jupyter.notebook.toJSON()
              })
          })
          .done(function(data) {
              Jupyter.notification_area.get_widget('notebook')
                  .set_message(Jupyter.notebook.notebook_name +
                      ' was published',
                      4000);
              metadata('url', data.url);
              Jupyter.notebook.save_notebook();
              updateVisitLink();
          })
          .fail(showError)
          .always(function(data, textStatus) {
              clearInterval(interval);
          });
        interval = uploadingNotification();
    }

    function visitNotebook() {
        var url = metadata('url');
        if (url) {
            var _window = window.open(url, '_blank');
            if (_window) {
                _window.focus();
            }
        }
    }

    function uploadingNotification() {
        var index = 0,
            pattern = ['-', '\\', '|', '/'],
            _updateString = function(i) {
                Jupyter.notification_area.
                    get_widget('notebook').
                    warning('Uploading ' + pattern[i]);
            }
        _updateString(index);
        return setInterval(function() {
            index+=1;
            if (index > 3) { index = 0 };
            _updateString(index);
        }, 250);
    }

    function updateVisitLink(){
        if (!metadata('url')) {
            $('#visit_notebook').addClass('hide');
        } else {
            $('#visit_notebook').removeClass('hide');
        }
    }

    function thumbnailable(){
        // find good candidate thumbnails
        return $('#notebook')
          .find('img, canvas')
          .filter(function(){
            return this.clientWidth > THUMBNAIL_MIN_DIM &&
              this.clientHeight > THUMBNAIL_MIN_DIM;
          });
    }

    function configureUpload(){
        var url,
            body,
            form,
            name,
            modal,
            attach,
            select,
            summary,
            adminUrl,
            thumbnail,
            thumbnails;

        Jupyter.notification_area.get_widget('notebook')
            .set_message('Connecting to Anaconda Cloud', 2000);

        $.ajax({
            url: api('login'),
            method: 'GET',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
        }).done(function(data) {
            body = $('
');
            name = Jupyter.notebook.notebook_name.replace(/.ipynb$/, '');

            // avoid any surprises with user events firing in the background
            Jupyter.notebook.keyboard_manager.register_events(body);

            url = metadata('url') || '';
            adminUrl = !url ? '' :
              url.replace('https://notebooks.', 'https://') + '/settings/admin';

            // actually build the form
            form = $('
', {
                'class': 'form-horizontal',
                'role': 'form'
            }).appendTo(body);

            // provide prior publishing information
            if(url){
              $('
').append(
                  $('')
                      .text('This notebook was previously published as '),
                  $('', {href: url, target: '_blank'}).text(url)
              )
              .css({'border-left': 'solid 4px #43B02A'})
              .appendTo(body);
            }

            // warning about published status
            $('
')
                .text('Initially, all published notebooks will be ').append(
                  $('', {'class': 'label label-default'})
                    .text('public'),
                  $('').text('. ')
                )
            .appendTo(body);

            // call to action to change settings
            if(adminUrl){
                $('
').append(
                    $('').text('Make '),
                    $('').text(name),
                    $('').text(' '),
                    $('', {'class': 'label label-primary'})
                        .text('private'),
                    $('').text(' or '),
                    $('', {'class': 'label label-success'})
                        .text('authenticated'),
                    $('').text(' on the '),
                    $('', {href: adminUrl, target: '_blank'})
                        .text('Anaconda Cloud settings page'),
                    $('').text('.'))
                .appendTo(body);
            }

            // organization picker, if needed
            if (data.organizations && data.organizations.length) {
                select = $('<select/>', {
                        id: 'select-organization',
                        'class': 'form-control'
                    })

                $('
', {'class': 'form-group'}).append(
                    $('', {'class': 'control-label col-sm-3'})
                        .text('User/Organization'),
                    $('
', {'class': 'col-sm-9'}).append(select))
                .appendTo(form);

                [data.user].concat(data.organizations)
                    .forEach(function(org) {
                        $('<option/>', {value: org.login})
                            .text(org.login + ' (' + org.name + ')')
                            .appendTo(select);
                    });

                if (metadata('organization')) {
                    select.val(metadata('organization'));
                }

                select.on('input', function() {
                    metadata('organization', select.val());
                });
            }

            summary = $('<input/>', {
                'class': 'form-control',
                type: 'text',
                id: 'anaconda-summary',
                placeholder: 'A brief summary'
            })
            .val(metadata('summary'))
            .on('input', function(){
                metadata('summary', summary.val())
            });

            $('
', {'class': 'form-group'}).append(
                $('', {'class': 'control-label col-sm-3'})
                    .text('Summary'),
                $('
', {'class': 'col-sm-9'}).append(summary))
            .appendTo(form);

            // whether to include the environment in metadata
            attach = $('<input/>', {type: 'checkbox'}).prop({
                checked: metadata('attach-environment')})
                .on("change", function(){
                  metadata('attach-environment', attach.prop('checked'));
                });

            $('
', {'class': 'form-group'}).append(
                $('', {'class': 'control-label col-sm-3'})
                    .text('Environment'),
                $('
', {'class': 'col-sm-9'}).append(
                    $('
', {'class': 'checkbox'}).append(
                        $('').append(attach,
                            $('').text('Attach conda environment')))))
            .appendTo(form);

            // thumbnails
            thumbnail = $('', {src: metadata('thumbnail'), height: 72});

            thumbnails = [];

            thumbnailable().map(function(){
                var canvas,
                  ctx,
                  uri;

                try{
                    switch(this.tagName.toLowerCase()){
                      case 'canvas':
                        canvas = this;
                        uri = canvas.toDataURL();
                        break;
                      case 'img':
                        canvas = $('<canvas/>').css({display: 'none'});
                        ctx = canvas[0].getContext('2d');
                        ctx.canvas.width = this.clientWidth;
                        ctx.canvas.height = this.clientHeight;
                        ctx.drawImage(this, 0, 0);
                        uri = canvas[0].toDataURL();
                        canvas.remove();
                        break;
                      default:
                        return;
                    }
                }catch (err){
                    console.log("couldn't snapshot", this, "perhaps execute the cell, perhaps?", err);
                    return;
                }

                thumbnails.push($('
', {value: uri}).append(
                    $('')
                        .on('click', function(){
                            metadata('thumbnail', uri);
                            thumbnail.attr({src: uri})
                        })
                        .append($('', {src: uri, height: 100}))));
            });

            if(thumbnails.length){
              $('
', {'class': 'form-group'}).append(
                  $('', {'class': 'control-label col-sm-3'})
                      .text('Thumbnail'),
                  $('
', {'class': 'col-sm-9'}).append(
                      $('
', {'class': 'dropdown'}).append(
                          $('<button/>', {
                              'class': 'btn btn-default dropdown-toggle',
                              'type': 'button',
                              'data-toggle': 'dropdown'
                          }).append(thumbnail),
                          $('
', {'class': 'dropdown-menu'})
                            .append(thumbnails)
                  )))
              .appendTo(form);
            }

            $('
', {'class': 'form-group'}).append(
                $('
', {'class': 'col-sm-9 col-sm-offset-3'}).append(
                    $('<button/>', {'class': 'btn btn-lg btn-success'})
                      .on('click', function(){
                        modal.modal('hide');
                        publishNotebook();
                      })
                      .append(
                        $('', {'class': 'fa fa-cloud-upload'}),
                        $('').text(' Publish')
                      )))
            .appendTo(form);

            // revert notebook CSS customizations to bootstrap default
            form.find('.control-label label')
                .css({'font-weight': 'bold'});
            form.find('.form-control')
                .css({margin: 0});
            form.find('.col-sm-3, .col-sm-9')
                .css({'padding-left': '15px', 'padding-right': '15px'});

            modal = dialog.modal({
                body: body,
                buttons: {
                    'Close': {}
                }
            });

            modal.find('.modal-title').append(
                $('').text('Publish '),
                $('')
                  .text(name),
                $('').text(' to '),
                $('', {href: 'https://anaconda.org', target: '_blank'})
                  .append($('', {src: LOGO_URL, height: 32}))
            );

        }).fail(showError);
    }

    function showError(jqXHR, textStatus, label) {
        var notif, title, body, showLoginForm=false;

        console.log(arguments)

        switch(jqXHR.status){
          case 401:
            title = 'Unauthorized';
            showLoginForm = true;
            break;
          case 500:
            title = label;
            notif = [
              $('
').text('An error ocurred in the Notebook Server'),
              $('
').text('You may need to re-install nb_anacondacloud')
            ];
            break;
          default:
            title = 'Error: ' + label;
            notif = [
              $('
').text('An error ocurred in the Notebook Server'),
            ]
        }

        Jupyter.notification_area.get_widget('notebook').
            danger(title, 4000);

        if (showLoginForm) {
            promptLogin();
        } else {
            body = $('
').append(notif);

            dialog.modal({
                title: title,
                body: body,
                buttons : {
                    OK: {}
                }
            });
        }
    }

    function promptLogin(withError) {
        var body,
            form,
            username,
            password,
            modal;
        body = $('
');

        // avoid any surprises with user events firing in the background
        Jupyter.notebook.keyboard_manager.register_events(body);
        if (withError) {
            $('
', {'class': 'alert alert-danger', 'role': 'alert'})
                .html(
                    'Invalid username or password.
' +
                    'Did you forget your ' +
                    'username or ' +
                    'password?'
                )
                .appendTo(body);
        }

        // warning about SSL
        $('
', {'class': 'alert alert-warning', 'role': 'alert'})
            .html('If you are not under a secure connection we strongly recommend to ' +
                  'login through the command line with:
anaconda login
')
            .appendTo(body);

        // actually build the form
        form = $('
', {
            'class': 'form-horizontal',
            'role': 'form'
        }).appendTo(body);

        username = $('<input/>', {
            'class': 'form-control',
            type: 'text',
            id: 'anaconda-username',
            placeholder: 'username'
        }).on('input', function() {
            credentials['username'] = username.val();
        });

        password = $('<input/>', {
            'class': 'form-control',
            type: 'password',
            id: 'anaconda-password'
        }).on('input', function() {
            credentials['password'] = password.val();
        });

        $('
', {'class': 'form-group'}).append(
            $('', {'for': 'anaconda-username'})
                .text('Username'),
            $('
').append(username))
        .appendTo(form);

        $('
', {'class': 'form-group'}).append(
            $('', {'for': 'anaconda-password'})
                .text('Password'),
            $('
').append(password))
        .appendTo(form);

        $('
', {'class': 'form-group'}).append(
            $('').html(
                'If you don\'t have an account you sign up in ' +
                'Anaconda.org.'
            )
        ).appendTo(form);

        modal = dialog.modal({
            body: body,
            buttons: {
                'OK': {class: 'btn-primary', click: loginIntoAnaconda},
                'Close': {}
            }
        });

        modal.find('.modal-title').append(
            $('').text('Sign in to '),
            $('', {'href': 'https://anaconda.org', 'target': '_blank'})
              .append($('', {'src': LOGO_URL, 'height': 32}))
        );
    }

    function loginIntoAnaconda() {
        $.ajax({
            url: api('login'),
            method: 'POST',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(credentials)
        }).done(function(data) {
            console.log(data);
            Jupyter.notification_area.get_widget('notebook').
                set_message('Welcome ' + data['username'], 2000);
            configureUpload();
        }).fail(function(jqXHR, textStatus, label) {
            Jupyter.notification_area.get_widget('notebook').
                danger('Unauthorized: Please verify your credentials', 4000);
            promptLogin(true);
        }).always(function() {
            credentials = {};
        });
    }

    function updateToolbar() {
        if (!Jupyter.toolbar) {
            $([Jupyter.events])
              .on('app_initialized.NotebookApp', updateToolbar);
            return;
        }
        if ($('#publish_notebook').length === 0) {
            Jupyter.toolbar.add_buttons_group([{
                label: 'Publish your notebook into Anaconda.org',
                icon: 'fa-cloud-upload',
                callback: configureUpload,
                id: 'publish_notebook'
            }, {
                label: 'Visit your notebook',
                icon: 'fa-cloud',
                callback: visitNotebook,
                id: 'visit_notebook'
            }]);
        }
        updateVisitLink();
    }

    function load_ipython_extension() {
        updateToolbar();
    }

    // export new and old API entrypoints
    return {
      load_ipython_extension: load_ipython_extension
    };
});
<commit_msg>fix up dangling IPython reference in js<commit_after>define(['jquery', 'base/js/dialog', 'base/js/namespace'],
function ($, dialog, Jupyter) {
    'use strict';

    var NS = 'anaconda-cloud',
      THUMBNAIL_MIN_DIM = 48,
      LOGO_URL = 'http://binstar-static-prod.s3.amazonaws.com' +
        '/latest/img/AnacondaCloud_logo_green.png',
      credentials = {};

    function api(route){
      // reusable API mapping, handles
      return Jupyter.notebook.base_url + NS + '/' + route;
    }

    function metadata(key, value){
        // wrapper for metadata, ensuring the top-level namespace
        var md = Jupyter.notebook.metadata;

        // ensure top-level namespace is initialized
        md[NS] = md[NS] || {};

        if (!arguments.length) {
          return md[NS];
        } else if(arguments.length === 1){
          return md[NS][key];
        } else{
          md[NS][key] = value;
        }
    }

    function publishNotebook(){
      /*
        Select the current environment for the running kernel.
        This is dependent on behavior of 'nb_conda_kernels' 2.0.0:
        - <normal name, like ir, python3, etc>  # server env
        - conda-env-<env name>-<lang key, py or ir>
        - conda-root-<lang key>
        */
        var kernel_name = Jupyter.notebook.kernel.name,
            kernel_env_re = /^conda-(env-)?(.+?)(-[^\-]*)?$/,
            match = kernel_env_re.exec(kernel_name),
            env_name;

        if(match) {
            env_name = m[2];
        }

        metadata('environment', metadata('attach-environment') ?
            env_name :
            null);
        Jupyter.notebook.save_notebook().then(uploadNotebook);
    }

    function uploadNotebook() {
        var interval;

        $.ajax({
              url: api('publish'),
              method: 'POST',
              dataType: 'json',
              contentType: 'application/json; charset=utf-8',
              processData: false,
              data: JSON.stringify({
                  name: Jupyter.notebook.notebook_name,
                  content: Jupyter.notebook.toJSON()
              })
          })
          .done(function(data) {
              Jupyter.notification_area.get_widget('notebook')
                  .set_message(Jupyter.notebook.notebook_name +
                      ' was published',
                      4000);
              metadata('url', data.url);
              Jupyter.notebook.save_notebook();
              updateVisitLink();
          })
          .fail(showError)
          .always(function(data, textStatus) {
              clearInterval(interval);
          });
        interval = uploadingNotification();
    }

    function visitNotebook() {
        var url = metadata('url');
        if (url) {
            var _window = window.open(url, '_blank');
            if (_window) {
                _window.focus();
            }
        }
    }

    function uploadingNotification() {
        var index = 0,
            pattern = ['-', '\\', '|', '/'],
            _updateString = function(i) {
                Jupyter.notification_area.
                    get_widget('notebook').
                    warning('Uploading ' + pattern[i]);
            }
        _updateString(index);
        return setInterval(function() {
            index+=1;
            if (index > 3) { index = 0 };
            _updateString(index);
        }, 250);
    }

    function updateVisitLink(){
        if (!metadata('url')) {
            $('#visit_notebook').addClass('hide');
        } else {
            $('#visit_notebook').removeClass('hide');
        }
    }

    function thumbnailable(){
        // find good candidate thumbnails
        return $('#notebook')
          .find('img, canvas')
          .filter(function(){
            return this.clientWidth > THUMBNAIL_MIN_DIM &&
              this.clientHeight > THUMBNAIL_MIN_DIM;
          });
    }

    function configureUpload(){
        var url,
            body,
            form,
            name,
            modal,
            attach,
            select,
            summary,
            adminUrl,
            thumbnail,
            thumbnails;

        Jupyter.notification_area.get_widget('notebook')
            .set_message('Connecting to Anaconda Cloud', 2000);

        $.ajax({
            url: api('login'),
            method: 'GET',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
        }).done(function(data) {
            body = $('
');
            name = Jupyter.notebook.notebook_name.replace(/.ipynb$/, '');

            // avoid any surprises with user events firing in the background
            Jupyter.notebook.keyboard_manager.register_events(body);

            url = metadata('url') || '';
            adminUrl = !url ? '' :
              url.replace('https://notebooks.', 'https://') + '/settings/admin';

            // actually build the form
            form = $('
', {
                'class': 'form-horizontal',
                'role': 'form'
            }).appendTo(body);

            // provide prior publishing information
            if(url){
              $('
').append(
                  $('')
                      .text('This notebook was previously published as '),
                  $('', {href: url, target: '_blank'}).text(url)
              )
              .css({'border-left': 'solid 4px #43B02A'})
              .appendTo(body);
            }

            // warning about published status
            $('
')
                .text('Initially, all published notebooks will be ').append(
                  $('', {'class': 'label label-default'})
                    .text('public'),
                  $('').text('. ')
                )
            .appendTo(body);

            // call to action to change settings
            if(adminUrl){
                $('
').append(
                    $('').text('Make '),
                    $('').text(name),
                    $('').text(' '),
                    $('', {'class': 'label label-primary'})
                        .text('private'),
                    $('').text(' or '),
                    $('', {'class': 'label label-success'})
                        .text('authenticated'),
                    $('').text(' on the '),
                    $('', {href: adminUrl, target: '_blank'})
                        .text('Anaconda Cloud settings page'),
                    $('').text('.'))
                .appendTo(body);
            }

            // organization picker, if needed
            if (data.organizations && data.organizations.length) {
                select = $('<select/>', {
                        id: 'select-organization',
                        'class': 'form-control'
                    })

                $('
', {'class': 'form-group'}).append(
                    $('', {'class': 'control-label col-sm-3'})
                        .text('User/Organization'),
                    $('
', {'class': 'col-sm-9'}).append(select))
                .appendTo(form);

                [data.user].concat(data.organizations)
                    .forEach(function(org) {
                        $('<option/>', {value: org.login})
                            .text(org.login + ' (' + org.name + ')')
                            .appendTo(select);
                    });

                if (metadata('organization')) {
                    select.val(metadata('organization'));
                }

                select.on('input', function() {
                    metadata('organization', select.val());
                });
            }

            summary = $('<input/>', {
                'class': 'form-control',
                type: 'text',
                id: 'anaconda-summary',
                placeholder: 'A brief summary'
            })
            .val(metadata('summary'))
            .on('input', function(){
                metadata('summary', summary.val())
            });

            $('
', {'class': 'form-group'}).append(
                $('', {'class': 'control-label col-sm-3'})
                    .text('Summary'),
                $('
', {'class': 'col-sm-9'}).append(summary))
            .appendTo(form);

            // whether to include the environment in metadata
            attach = $('<input/>', {type: 'checkbox'}).prop({
                checked: metadata('attach-environment')})
                .on("change", function(){
                  metadata('attach-environment', attach.prop('checked'));
                });

            $('
', {'class': 'form-group'}).append(
                $('', {'class': 'control-label col-sm-3'})
                    .text('Environment'),
                $('
', {'class': 'col-sm-9'}).append(
                    $('
', {'class': 'checkbox'}).append(
                        $('').append(attach,
                            $('').text('Attach conda environment')))))
            .appendTo(form);

            // thumbnails
            thumbnail = $('', {src: metadata('thumbnail'), height: 72});

            thumbnails = [];

            thumbnailable().map(function(){
                var canvas,
                  ctx,
                  uri;

                try{
                    switch(this.tagName.toLowerCase()){
                      case 'canvas':
                        canvas = this;
                        uri = canvas.toDataURL();
                        break;
                      case 'img':
                        canvas = $('<canvas/>').css({display: 'none'});
                        ctx = canvas[0].getContext('2d');
                        ctx.canvas.width = this.clientWidth;
                        ctx.canvas.height = this.clientHeight;
                        ctx.drawImage(this, 0, 0);
                        uri = canvas[0].toDataURL();
                        canvas.remove();
                        break;
                      default:
                        return;
                    }
                }catch (err){
                    console.log("couldn't snapshot", this, "perhaps execute the cell, perhaps?", err);
                    return;
                }

                thumbnails.push($('
', {value: uri}).append(
                    $('')
                        .on('click', function(){
                            metadata('thumbnail', uri);
                            thumbnail.attr({src: uri})
                        })
                        .append($('', {src: uri, height: 100}))));
            });

            if(thumbnails.length){
              $('
', {'class': 'form-group'}).append(
                  $('', {'class': 'control-label col-sm-3'})
                      .text('Thumbnail'),
                  $('
', {'class': 'col-sm-9'}).append(
                      $('
', {'class': 'dropdown'}).append(
                          $('<button/>', {
                              'class': 'btn btn-default dropdown-toggle',
                              'type': 'button',
                              'data-toggle': 'dropdown'
                          }).append(thumbnail),
                          $('
', {'class': 'dropdown-menu'})
                            .append(thumbnails)
                  )))
              .appendTo(form);
            }

            $('
', {'class': 'form-group'}).append(
                $('
', {'class': 'col-sm-9 col-sm-offset-3'}).append(
                    $('<button/>', {'class': 'btn btn-lg btn-success'})
                      .on('click', function(){
                        modal.modal('hide');
                        publishNotebook();
                      })
                      .append(
                        $('', {'class': 'fa fa-cloud-upload'}),
                        $('').text(' Publish')
                      )))
            .appendTo(form);

            // revert notebook CSS customizations to bootstrap default
            form.find('.control-label label')
                .css({'font-weight': 'bold'});
            form.find('.form-control')
                .css({margin: 0});
            form.find('.col-sm-3, .col-sm-9')
                .css({'padding-left': '15px', 'padding-right': '15px'});

            modal = dialog.modal({
                body: body,
                buttons: {
                    'Close': {}
                }
            });

            modal.find('.modal-title').append(
                $('').text('Publish '),
                $('')
                  .text(name),
                $('').text(' to '),
                $('', {href: 'https://anaconda.org', target: '_blank'})
                  .append($('', {src: LOGO_URL, height: 32}))
            );

        }).fail(showError);
    }

    function showError(jqXHR, textStatus, label) {
        var notif, title, body, showLoginForm=false;

        console.log(arguments)

        switch(jqXHR.status){
          case 401:
            title = 'Unauthorized';
            showLoginForm = true;
            break;
          case 500:
            title = label;
            notif = [
              $('
').text('An error ocurred in the Notebook Server'),
              $('
').text('You may need to re-install nb_anacondacloud')
            ];
            break;
          default:
            title = 'Error: ' + label;
            notif = [
              $('
').text('An error ocurred in the Notebook Server'),
            ]
        }

        Jupyter.notification_area.get_widget('notebook').
            danger(title, 4000);

        if (showLoginForm) {
            promptLogin();
        } else {
            body = $('
').append(notif);

            dialog.modal({
                title: title,
                body: body,
                buttons : {
                    OK: {}
                }
            });
        }
    }

    function promptLogin(withError) {
        var body,
            form,
            username,
            password,
            modal;
        body = $('
');

        // avoid any surprises with user events firing in the background
        Jupyter.notebook.keyboard_manager.register_events(body);
        if (withError) {
            $('
', {'class': 'alert alert-danger', 'role': 'alert'})
                .html(
                    'Invalid username or password.
' +
                    'Did you forget your ' +
                    'username or ' +
                    'password?'
                )
                .appendTo(body);
        }

        // warning about SSL
        $('
', {'class': 'alert alert-warning', 'role': 'alert'})
            .html('If you are not under a secure connection we strongly recommend to ' +
                  'login through the command line with:
anaconda login
')
            .appendTo(body);

        // actually build the form
        form = $('
', {
            'class': 'form-horizontal',
            'role': 'form'
        }).appendTo(body);

        username = $('<input/>', {
            'class': 'form-control',
            type: 'text',
            id: 'anaconda-username',
            placeholder: 'username'
        }).on('input', function() {
            credentials['username'] = username.val();
        });

        password = $('<input/>', {
            'class': 'form-control',
            type: 'password',
            id: 'anaconda-password'
        }).on('input', function() {
            credentials['password'] = password.val();
        });

        $('
', {'class': 'form-group'}).append(
            $('', {'for': 'anaconda-username'})
                .text('Username'),
            $('
').append(username))
        .appendTo(form);

        $('
', {'class': 'form-group'}).append(
            $('', {'for': 'anaconda-password'})
                .text('Password'),
            $('
').append(password))
        .appendTo(form);

        $('
', {'class': 'form-group'}).append(
            $('').html(
                'If you don\'t have an account you sign up in ' +
                'Anaconda.org.'
            )
        ).appendTo(form);

        modal = dialog.modal({
            body: body,
            buttons: {
                'OK': {class: 'btn-primary', click: loginIntoAnaconda},
                'Close': {}
            }
        });

        modal.find('.modal-title').append(
            $('').text('Sign in to '),
            $('', {'href': 'https://anaconda.org', 'target': '_blank'})
              .append($('', {'src': LOGO_URL, 'height': 32}))
        );
    }

    function loginIntoAnaconda() {
        $.ajax({
            url: api('login'),
            method: 'POST',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(credentials)
        }).done(function(data) {
            console.log(data);
            Jupyter.notification_area.get_widget('notebook').
                set_message('Welcome ' + data['username'], 2000);
            configureUpload();
        }).fail(function(jqXHR, textStatus, label) {
            Jupyter.notification_area.get_widget('notebook').
                danger('Unauthorized: Please verify your credentials', 4000);
            promptLogin(true);
        }).always(function() {
            credentials = {};
        });
    }

    function updateToolbar() {
        if (!Jupyter.toolbar) {
            $([Jupyter.events])
              .on('app_initialized.NotebookApp', updateToolbar);
            return;
        }
        if ($('#publish_notebook').length === 0) {
            Jupyter.toolbar.add_buttons_group([{
                label: 'Publish your notebook into Anaconda.org',
                icon: 'fa-cloud-upload',
                callback: configureUpload,
                id: 'publish_notebook'
            }, {
                label: 'Visit your notebook',
                icon: 'fa-cloud',
                callback: visitNotebook,
                id: 'visit_notebook'
            }]);
        }
        updateVisitLink();
    }

    function load_ipython_extension() {
        updateToolbar();
    }

    // export new and old API entrypoints
    return {
      load_ipython_extension: load_ipython_extension
    };
});
<|endoftext|>


<commit_before>function Muppet(age, hobby) {
  this.age = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(FILL_ME_IN);
    expect(this.swedishChef.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual(FILL_ME_IN);
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

//no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
//note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
//however, it does do something interesting -- it makes this work:
//  var g = new Gonzo(...);
//  g instanceOf Muppet //true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(FILL_ME_IN);
    expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual(FILL_ME_IN);
  });
});
<commit_msg>fix Inheritance<commit_after>function Muppet(age, hobby) {
  this.age = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual("Mmmm soup!");
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(2);
    expect(this.swedishChef.hobby).toEqual("cooking");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual("chillin");
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

//no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
//note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
//however, it does do something interesting -- it makes this work:
//  var g = new Gonzo(...);
//  g instanceOf Muppet //true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual('eat a tire');
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(3);
    expect(this.gonzo.hobby).toEqual("daredevil performer");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual('eat a tire');
  });
});
<|endoftext|>


<commit_before>function Muppet(age, hobby) {
  this.age = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual("Mmmm soup!");
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(2);
    expect(this.swedishChef.hobby).toEqual("cooking");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual("chillin");
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

//no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
//note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
//however, it does do something interesting -- it makes this work:
//  var g = new Gonzo(...);
//  g instanceOf Muppet //true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual(FILL_ME_IN);
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(FILL_ME_IN);
    expect(this.gonzo.hobby).toEqual(FILL_ME_IN);
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual(FILL_ME_IN);
  });
});
<commit_msg>Corrected "Crockfords inheritance improvement"<commit_after>function Muppet(age, hobby) {
  this.age = age;
  this.hobby = hobby;

  this.answerNanny = function(){
	return "Everything's cool!";
  }
}

function SwedishChef(age, hobby, mood) {
  Muppet.call(this, age, hobby);
  this.mood = mood;

  this.cook = function() {
    return "Mmmm soup!";
  }
}

SwedishChef.prototype = new Muppet();

describe("About inheritance", function() {
  beforeEach(function(){
    this.muppet = new Muppet(2, "coding");
  	this.swedishChef = new SwedishChef(2, "cooking", "chillin");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.swedishChef.cook()).toEqual("Mmmm soup!");
  });

  it("should be able to call a method on the base object", function() {
    expect(this.swedishChef.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.swedishChef.age).toEqual(2);
    expect(this.swedishChef.hobby).toEqual("cooking");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.swedishChef.mood).toEqual("chillin");
  });
});

// http://javascript.crockford.com/prototypal.html
Object.prototype.beget = function () {
  function F() {}
  F.prototype = this;
  return new F();
}

function Gonzo(age, hobby, trick) {
  Muppet.call(this, age, hobby);
  this.trick = trick;

  this.doTrick = function() {
    return this.trick;
  }
}

//no longer need to call the Muppet (base type) constructor
Gonzo.prototype = Muppet.prototype.beget();
//note: if you're wondering how this line affects the below tests, the answer is that it doesn't.
//however, it does do something interesting -- it makes this work:
//  var g = new Gonzo(...);
//  g instanceOf Muppet //true

describe("About Crockford's inheritance improvement", function() {
  beforeEach(function(){
    this.gonzo = new Gonzo(3, "daredevil performer", "eat a tire");
  });

  it("should be able to call a method on the derived object", function() {
    expect(this.gonzo.doTrick()).toEqual('eat a tire');
  });

  it("should be able to call a method on the base object", function() {
    expect(this.gonzo.answerNanny()).toEqual("Everything's cool!");
  });

  it("should set constructor parameters on the base object", function() {
    expect(this.gonzo.age).toEqual(3);
    expect(this.gonzo.hobby).toEqual("daredevil performer");
  });

  it("should set constructor parameters on the derived object", function() {
    expect(this.gonzo.trick).toEqual('eat a tire');
  });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module for registering apps that can be createed
 * @module kbox.create
 */

// Intrinsic modules.
var path = require('path');

// Npm modules
var _ = require('lodash');
var chalk = require('chalk');
var VError = require('verror');
var fs = require('fs-extra');

// Kbox modules
var core = require('./core.js');
var util = require('./util.js');
var appEngine = require('./app.js');
var Promise = require('./promise.js');

// The create sauce
module.exports = function() {

  // Instantiate our app singleton
  var apps = {};
  // To inquire is better to inquisite
  var inquirer = require('inquirer');

  /**
   * Adds a type of app that you can create and options, interactive inquires
   * and other fun stuff too that app.
   * @static
   * @method
   * @arg {string} appName - What app to attach the data to.
   * @arg {object} data - A task or option to add to the app.
   * @example
   * // This will create a command called `kbox create drupal`.
   * kbox.create.add('drupal', {
   *   task: {
   *     name: 'Drupal',
   *     module: 'kalabox-app-drupal',
   *     description: 'Creates a Drupal app.'
   *   }
   * });
   *
   * // This will add a command line option called `name` to `kbox create drupal`.
   * // It will also add interactive step asking for the name to be inputted if
   * // it is not passed in to the CLI. The result of this will end up at the
   * // top level of the app's kalabox.json in the appName key.
   * kbox.create.add('drupal', {
   *   option: {
   *     name: 'name',
   *     weight: -99,
   *     task: {
   *       kind: 'string',
   *       description: 'The name of your app.',
   *     },
   *     inquire: {
   *       type: 'input',
   *       message: 'What will this app be called',
   *       validate: function(value) {
   *         // @todo some actual validation here
   *         return true;
   *       },
   *       filter: function(value) {
   *         return _.kebabCase(value);
   *       },
   *       default: 'My Drupal App'
   *     },
   *     conf: {
   *       type: 'global',
   *       key: 'appName'
   *     }
   *   }
   * });
   *
   * // This add's an interactive step to `kbox drupal create` asking the
   * // user to select a drupal version.
   * kbox.create.add('drupal', {
   *   option: {
   *     name: 'drupal-version',
   *     weight: -98,
   *     inquire: {
   *       type: 'list',
   *       message: 'What major version of drupal',
   *       default: '7',
   *       choices: Object.keys(drupalMatrix)
   *     }
   *   }
   * });
   */
  var add = function(appName, data) {
    var optionDefaults = {
      name: null,
      task: null,
      weight: 0,
      inquire: null,
      conf: {
        type: null,
        key: null,
        plugin: null
      }
    };

    if (!apps[appName]) {
      create(appName);
    }
    if (data.task) {
      apps[appName].task = data.task;
    }
    if (data.option) {
      var merge = _.merge({}, optionDefaults, data.option);
      apps[appName].options.push(merge);
    }
  };

  /*
   * Adds a new app plugin to the mix
   */
  var create = function(appName) {
    var appDefaults = {
      name : null,
      task: {
        name: null,
        module: null,
        description: null
      },
      options: []
    };
    apps[appName] = appDefaults;
    apps[appName].name = appName;
  };

  /*
   * Removes an app plugin from the mix
   */
  var remove = function(appName) {
    if (apps[appName]) {
      delete apps[appName];
    }
  };

  /*
   * Grabs an app plugin, or returns false
   */
  var get = function(appName) {
    if (appName) {
      var options = _.sortBy(apps[appName].options, 'weight');
      apps[appName].options = options;
      return apps[appName];
    }
    return false;
  };

  /*
   * ALL THE APP PLUGINZZZ
   */
  var getAll = function() {
    return apps;
  };

  /**
   * Assembles create.add metadata and builds a task to create a certain kind
   * of app.
   * @static
   * @method
   * @arg {object} task - A kbox task object.
   * @arg {string} appName - The name of the kind of app to be created.
   * @example
   * // Task to create kalabox apps
   * kbox.tasks.add(function(task) {
   *  kbox.create.buildTask(task, 'frontpage98');
   * });
   */
  var buildTask = function(task, appName) {

    // Make sure we are going to add a task with a real app
    if (!get(appName)) {
      // todo: something useful besides silentfail
      // todo: never forget how funny this was/is/will alway be
      console.log('F!');
    }
    else {

      // Get the app plugin metadata
      var app = get(appName);

      // Create a task path for that app plugin
      task.path = ['create', appName];
      task.category = 'global';
      // And a description
      task.description = app.task.description;

      // Generate a list of our inquiries and task options
      var questions = [];
      app.options.forEach(function(opt) {
        if (opt.inquire) {
          opt.inquire.name = opt.name;
          questions.push(opt.inquire);
        }
        if (opt.task) {
          opt.task.name = opt.name;
          task.options.push(opt.task);
        }
      });

      // Add in some global options that every app gets
      // Lets the user install their app with its local dockerfilez
      task.options.push({
        name: 'build-local',
        kind: 'boolean',
        description: 'Build images locally instead of pulling them remotely.'
      });

      // Put this app someplace else
      task.options.push({
        name: 'dir',
        kind: 'string',
        description: 'Creates the app in this directory. Defaults to CWD.',
      });

      // The func that this app create task runs
      task.func = function(done) {

        // Grab the CLI options that are available
        var options = this.options;

        // Filter out interactive questions based on passed in options
        questions = _.filter(questions, function(question) {

          var option = options[question.name];

          if (question.filter) {
            options[question.name] = question.filter(options[question.name]);
          }

          if (option === false || option === undefined) {
            return true;
          }

          else {
            return !_.includes(Object.keys(options), question.name);
          }
        });

        // Launch the inquiry
        inquirer.prompt(questions, function(answers) {

          // Create the app
          return createApp(app, _.merge({}, options, answers))

          // Return.
          .nodeify(done);

        });
      };
    }
  };

  var createApp = function(app, results, callback) {

    // Create some globally vars for use in and-thens
    var srcRoot = core.deps.lookup('config').srcRoot;
    var dir = (results.dir) ? results.dir : process.cwd();
    var source = path.join(srcRoot, 'node_modules', app.task.module, 'app');
    var dest = path.join(dir, results.name);

    // Things are happening!!!
    console.log(chalk.yellow('Building your app...'));

    // Make sure app does not already exist.
    return Promise.try(function() {
      // Try to open the dir.
      return Promise.fromNode(function(cb) {
        fs.open(dest, 'r', cb);
      })
      // If the dir was opened, close it and throw an error.
      .then(function(fd) {
        return Promise.fromNode(function(cb) {
          fs.close(fd, cb);
        })
        .then(function() {
          throw new Error('App already exists: ' + dest);
        });
      })
      // Ignore file doesn't exist errors.
      .catch(function(err) {
        if (err.code !== 'ENOENT') {
          throw err;
        }
      });
    })

    // Emit pre create event.
    .then(function() {
      return core.events.emit('pre-create-app', app);
    })

    // Copy our app skeleton over
    .then(function() {
      return Promise.fromNode(function(cb) {
        fs.copy(source, dest, cb);
      });
    })

    // Instantiate the new app
    .then(function() {
      return instantiateApp(app, results);
    })

    // Install the new app
    .then(function() {

      // Get app config from new app
      var config = core.config.getAppConfig(null, dest);

      // Create a new app object
      return appEngine.create(results.name, config).bind({})

      // Register the new app object so we can
      // correctly detech an app context
      .then(function(loadedApp) {
        this.loadedApp = loadedApp;
        return core.deps.register('appConfig', loadedApp.config);
      })
      .then(function() {
        return core.deps.register('app', this.loadedApp);
      })
      // Load up all the apps plugins
      .then(function() {
        return appEngine.loadPlugins(this.loadedApp);
      })
      // Install the SOB
      .then(function() {
        return appEngine.install(this.loadedApp);
      });
    })

    // Return.
    .nodeify(callback);

  };

  var instantiateApp = function(app, results, callback) {
    // Figure out where our app skeleton lives
    var sourcePath = path.join(
      core.deps.lookup('config').srcRoot,
      'node_modules',
      app.task.module,
      'app'
    );

    // Load up the source kalabox/package sons of j
    var sourceKbFile = path.join(sourcePath, 'kalabox.json');
    var sourcePkgFile = path.join(sourcePath, 'package.json');
    var kalaboxJson = require(sourceKbFile);
    var pkgJson = require(sourcePkgFile);

    // Customize kalabox.json with options and answers
    app.options.forEach(function(opt) {
      if (opt.conf) {
        if (opt.conf.type === 'global') {
          kalaboxJson[opt.conf.key] = results[opt.name];
        }
        else if (opt.conf.type === 'plugin') {
          var plugin = opt.conf.plugin;
          var key = opt.conf.key;
          if (!kalaboxJson.pluginConf[plugin]) {
            kalaboxJson.pluginConf[plugin] = {};
          }
          if (kalaboxJson.pluginConf[plugin][key]) {
            kalaboxJson.pluginConf[plugin][key] = results[opt.name];
          }
          else {
            kalaboxJson.pluginConf[plugin][key] = results[opt.name];
          }
        }
      }
    });

    // Customize pkg.json with our app name
    pkgJson.name = results.name;

    return core.events.emit('pre-create-instantiate', kalaboxJson)

    // Update source files with our new stuff and then
    // npm install all the goodies
    .then(function() {
      var dir = (results.dir) ? results.dir : process.cwd();
      var appPath = path.join(dir, results.name);
      var destKbFile = path.join(appPath, 'kalabox.json');
      var destPkgFile = path.join(appPath, 'package.json');
      fs.writeFileSync(destKbFile, JSON.stringify(kalaboxJson, null, 2));
      fs.writeFileSync(destPkgFile, JSON.stringify(pkgJson, null, 2));

      // Install node deps
      return util.npm.installPackages(appPath);
    })

    // This is probably not very helpful but symmetry and all
    .then(function() {
      return core.events.emit('post-create-instantiate');
    })

    // Return.
    .nodeify(callback);
  };

  return {
    add: add,
    get: get,
    getAll: getAll,
    buildTask: buildTask
  };

};
<commit_msg>#317: Check for engine status before we create an app want to prompt provision before app is registered<commit_after>'use strict';

/**
 * Module for registering apps that can be createed
 * @module kbox.create
 */

// Intrinsic modules.
var path = require('path');

// Npm modules
var _ = require('lodash');
var chalk = require('chalk');
var VError = require('verror');
var fs = require('fs-extra');

// Kbox modules
var core = require('./core.js');
var engine = require('./engine.js');
var util = require('./util.js');
var appEngine = require('./app.js');
var Promise = require('./promise.js');

// The create sauce
module.exports = function() {

  // Instantiate our app singleton
  var apps = {};
  // To inquire is better to inquisite
  var inquirer = require('inquirer');

  /**
   * Adds a type of app that you can create and options, interactive inquires
   * and other fun stuff too that app.
   * @static
   * @method
   * @arg {string} appName - What app to attach the data to.
   * @arg {object} data - A task or option to add to the app.
   * @example
   * // This will create a command called `kbox create drupal`.
   * kbox.create.add('drupal', {
   *   task: {
   *     name: 'Drupal',
   *     module: 'kalabox-app-drupal',
   *     description: 'Creates a Drupal app.'
   *   }
   * });
   *
   * // This will add a command line option called `name` to `kbox create drupal`.
   * // It will also add interactive step asking for the name to be inputted if
   * // it is not passed in to the CLI. The result of this will end up at the
   * // top level of the app's kalabox.json in the appName key.
   * kbox.create.add('drupal', {
   *   option: {
   *     name: 'name',
   *     weight: -99,
   *     task: {
   *       kind: 'string',
   *       description: 'The name of your app.',
   *     },
   *     inquire: {
   *       type: 'input',
   *       message: 'What will this app be called',
   *       validate: function(value) {
   *         // @todo some actual validation here
   *         return true;
   *       },
   *       filter: function(value) {
   *         return _.kebabCase(value);
   *       },
   *       default: 'My Drupal App'
   *     },
   *     conf: {
   *       type: 'global',
   *       key: 'appName'
   *     }
   *   }
   * });
   *
   * // This add's an interactive step to `kbox drupal create` asking the
   * // user to select a drupal version.
   * kbox.create.add('drupal', {
   *   option: {
   *     name: 'drupal-version',
   *     weight: -98,
   *     inquire: {
   *       type: 'list',
   *       message: 'What major version of drupal',
   *       default: '7',
   *       choices: Object.keys(drupalMatrix)
   *     }
   *   }
   * });
   */
  var add = function(appName, data) {
    var optionDefaults = {
      name: null,
      task: null,
      weight: 0,
      inquire: null,
      conf: {
        type: null,
        key: null,
        plugin: null
      }
    };

    if (!apps[appName]) {
      create(appName);
    }
    if (data.task) {
      apps[appName].task = data.task;
    }
    if (data.option) {
      var merge = _.merge({}, optionDefaults, data.option);
      apps[appName].options.push(merge);
    }
  };

  /*
   * Adds a new app plugin to the mix
   */
  var create = function(appName) {
    var appDefaults = {
      name : null,
      task: {
        name: null,
        module: null,
        description: null
      },
      options: []
    };
    apps[appName] = appDefaults;
    apps[appName].name = appName;
  };

  /*
   * Removes an app plugin from the mix
   */
  var remove = function(appName) {
    if (apps[appName]) {
      delete apps[appName];
    }
  };

  /*
   * Grabs an app plugin, or returns false
   */
  var get = function(appName) {
    if (appName) {
      var options = _.sortBy(apps[appName].options, 'weight');
      apps[appName].options = options;
      return apps[appName];
    }
    return false;
  };

  /*
   * ALL THE APP PLUGINZZZ
   */
  var getAll = function() {
    return apps;
  };

  /**
   * Assembles create.add metadata and builds a task to create a certain kind
   * of app.
   * @static
   * @method
   * @arg {object} task - A kbox task object.
   * @arg {string} appName - The name of the kind of app to be created.
   * @example
   * // Task to create kalabox apps
   * kbox.tasks.add(function(task) {
   *  kbox.create.buildTask(task, 'frontpage98');
   * });
   */
  var buildTask = function(task, appName) {

    // Make sure we are going to add a task with a real app
    if (!get(appName)) {
      // todo: something useful besides silentfail
      // todo: never forget how funny this was/is/will alway be
      console.log('F!');
    }
    else {

      // Get the app plugin metadata
      var app = get(appName);

      // Create a task path for that app plugin
      task.path = ['create', appName];
      task.category = 'global';
      // And a description
      task.description = app.task.description;

      // Generate a list of our inquiries and task options
      var questions = [];
      app.options.forEach(function(opt) {
        if (opt.inquire) {
          opt.inquire.name = opt.name;
          questions.push(opt.inquire);
        }
        if (opt.task) {
          opt.task.name = opt.name;
          task.options.push(opt.task);
        }
      });

      // Add in some global options that every app gets
      // Lets the user install their app with its local dockerfilez
      task.options.push({
        name: 'build-local',
        kind: 'boolean',
        description: 'Build images locally instead of pulling them remotely.'
      });

      // Put this app someplace else
      task.options.push({
        name: 'dir',
        kind: 'string',
        description: 'Creates the app in this directory. Defaults to CWD.',
      });

      // The func that this app create task runs
      task.func = function(done) {

        // Grab the CLI options that are available
        var options = this.options;

        // Filter out interactive questions based on passed in options
        questions = _.filter(questions, function(question) {

          var option = options[question.name];

          if (question.filter) {
            options[question.name] = question.filter(options[question.name]);
          }

          if (option === false || option === undefined) {
            return true;
          }

          else {
            return !_.includes(Object.keys(options), question.name);
          }
        });

        // Launch the inquiry
        inquirer.prompt(questions, function(answers) {

          // Create the app
          return createApp(app, _.merge({}, options, answers))

          // Return.
          .nodeify(done);

        });
      };
    }
  };

  var createApp = function(app, results, callback) {

    // Create some globally vars for use in and-thens
    var srcRoot = core.deps.lookup('config').srcRoot;
    var dir = (results.dir) ? results.dir : process.cwd();
    var source = path.join(srcRoot, 'node_modules', app.task.module, 'app');
    var dest = path.join(dir, results.name);

    // Things are happening!!!
    console.log(chalk.yellow('Building your app...'));

    // Make sure app does not already exist.
    return Promise.try(function() {
      // Try to open the dir.
      return Promise.fromNode(function(cb) {
        fs.open(dest, 'r', cb);
      })
      // If the dir was opened, close it and throw an error.
      .then(function(fd) {
        return Promise.fromNode(function(cb) {
          fs.close(fd, cb);
        })
        .then(function() {
          throw new Error('App already exists: ' + dest);
        });
      })
      // Ignore file doesn't exist errors.
      .catch(function(err) {
        if (err.code !== 'ENOENT') {
          throw err;
        }
      });
    })

    // Emit pre create event.
    .then(function() {
      return core.events.emit('pre-create-app', app);
    })

    // Copy our app skeleton over
    .then(function() {
      return Promise.fromNode(function(cb) {
        fs.copy(source, dest, cb);
      });
    })

    // Instantiate the new app
    .then(function() {
      return instantiateApp(app, results);
    })

    // Check engine status to prompt install request
    .then(function() {
      return engine.up();
    })

    // Install the new app
    .then(function() {

      // Get app config from new app
      var config = core.config.getAppConfig(null, dest);

      // Create a new app object
      return appEngine.create(results.name, config).bind({})

      // Register the new app object so we can
      // correctly detech an app context
      .then(function(loadedApp) {
        this.loadedApp = loadedApp;
        return core.deps.register('appConfig', loadedApp.config);
      })
      .then(function() {
        return core.deps.register('app', this.loadedApp);
      })
      // Load up all the apps plugins
      .then(function() {
        return appEngine.loadPlugins(this.loadedApp);
      })
      // Install the SOB
      .then(function() {
        return appEngine.install(this.loadedApp);
      });
    })

    // Return.
    .nodeify(callback);

  };

  var instantiateApp = function(app, results, callback) {
    // Figure out where our app skeleton lives
    var sourcePath = path.join(
      core.deps.lookup('config').srcRoot,
      'node_modules',
      app.task.module,
      'app'
    );

    // Load up the source kalabox/package sons of j
    var sourceKbFile = path.join(sourcePath, 'kalabox.json');
    var sourcePkgFile = path.join(sourcePath, 'package.json');
    var kalaboxJson = require(sourceKbFile);
    var pkgJson = require(sourcePkgFile);

    // Customize kalabox.json with options and answers
    app.options.forEach(function(opt) {
      if (opt.conf) {
        if (opt.conf.type === 'global') {
          kalaboxJson[opt.conf.key] = results[opt.name];
        }
        else if (opt.conf.type === 'plugin') {
          var plugin = opt.conf.plugin;
          var key = opt.conf.key;
          if (!kalaboxJson.pluginConf[plugin]) {
            kalaboxJson.pluginConf[plugin] = {};
          }
          if (kalaboxJson.pluginConf[plugin][key]) {
            kalaboxJson.pluginConf[plugin][key] = results[opt.name];
          }
          else {
            kalaboxJson.pluginConf[plugin][key] = results[opt.name];
          }
        }
      }
    });

    // Customize pkg.json with our app name
    pkgJson.name = results.name;

    return core.events.emit('pre-create-instantiate', kalaboxJson)

    // Update source files with our new stuff and then
    // npm install all the goodies
    .then(function() {
      var dir = (results.dir) ? results.dir : process.cwd();
      var appPath = path.join(dir, results.name);
      var destKbFile = path.join(appPath, 'kalabox.json');
      var destPkgFile = path.join(appPath, 'package.json');
      fs.writeFileSync(destKbFile, JSON.stringify(kalaboxJson, null, 2));
      fs.writeFileSync(destPkgFile, JSON.stringify(pkgJson, null, 2));

      // Install node deps
      return util.npm.installPackages(appPath);
    })

    // This is probably not very helpful but symmetry and all
    .then(function() {
      return core.events.emit('post-create-instantiate');
    })

    // Return.
    .nodeify(callback);
  };

  return {
    add: add,
    get: get,
    getAll: getAll,
    buildTask: buildTask
  };

};
<|endoftext|>