<commit_before>import { Server }     from 'http';
import Koa            from 'koa';
import KoaRouter      from 'koa-router';
import Stream         from './stream';
import controller     from './controller';
import { middleware } from './monitor';
import monitor        from './monitor';
import config         from './config';
import Influx         from 'influxdb-nodejs';

const app    = new Koa();
const router = new KoaRouter();

router.use(middleware.routeAccess(router));

router.get('/',                                          controller.home.index);
router.get('/BikePoint',                                 controller.bikepoint.index);
router.io ('/BikePoint',                                 controller.bikepoint.fireHose);
router.get('/BikePoint/:bikePoint',                      controller.bikepoint.get);
router.io ('/BikePoint/:bikePoint',                      controller.bikepoint.streamSingle);
router.get('/BikePoint/around/:lon/:lat/:radius',        controller.bikepoint.around);
router.get('/BikePoint/box/:swlon/:swlat/:nelon/:nelat', controller.bikepoint.box);
router.get('/BikePoint/search/:searchstring',            controller.bikepoint.search);
router.get('/Occupancy/CarPark',                         controller.carpark.index);
router.io ('/Occupancy/CarPark',                         controller.carpark.fireHose);
router.get('/Occupancy/CarPark/:carPark',                controller.carpark.get);
router.io ('/Occupancy/CarPark/:carPark',                controller.carpark.streamSingle);
router.get('/StopPoint',                                 controller.stoppoint.index);
router.io ('/StopPoint',                                 controller.stoppoint.streamIndex);
router.get('/StopPoint/Departures',                      controller.departures.index);
router.get('/StopPoint/:stopPoint',                      controller.stoppoint.get);
router.get('/StopPoint/around/:lon/:lat/:radius',        controller.stoppoint.around);
router.get('/StopPoint/box/:swlon/:swlat/:nelon/:nelat', controller.stoppoint.box);
router.get('/StopPoint/search/:searchstring',            controller.stoppoint.search);
router.io ('/StopPoint/Departures',                      controller.departures.fireHose);
router.get('/StopPoint/Departures/:stopPoint',           controller.departures.get);
router.io ('/StopPoint/Departures/:stopPoint',           controller.departures.streamSingle);
router.get('/StopPoint/Departures/:stopPoint/:limit',    controller.departures.load);
router.get('/Journey/:from/to/:to',                      controller.journey.plan);
router.get('/Weather',                                   controller.weather.current);
router.io ('/Weather',                                   controller.weather.streamSingle);
router.get('/Weather/AirQuality',                        controller.airquality.index);
router.io ('/Weather/AirQuality',                        controller.airquality.fireHose);
router.get('/Weather/AirQuality/:weatherStation',        controller.airquality.get);
router.io ('/Weather/AirQuality/:weatherStation',        controller.airquality.streamSingle);

app.use(monitor())
   .use(middleware.responseTime())
   .use(async (ctx, next) => {
       await next();
       ctx.type = 'application/json';
   })
   .use(router.routes())
   .use(router.allowedMethods());

const server = Server(app.callback());
Stream.bind(server, router);

const PORT = config('SERVER_PORT', true);
if (PORT) {
    server.listen(PORT);
}

var influxdb = false;

const streamCountToInflux = () => {
    influxdb.write('streamConnections')
        .field({
            departures: controller.departures.streamCount(),
            bikepoint:  controller.bikepoint.streamCount(),
            carpark:    controller.carpark.streamCount(),
            weather:    controller.weather.streamCount(),
            airquality: controller.airquality.streamCount()
        })
        .then();
    return;
};

if (config('INFLUXDB')) {
    influxdb = new Influx(config('INFLUXDB') + config('NAME_VERSION'));
    setInterval(streamCountToInflux, 10000);
}

export default server;
<commit_msg>only listen on localhost<commit_after>import { Server }     from 'http';
import Koa            from 'koa';
import KoaRouter      from 'koa-router';
import Stream         from './stream';
import controller     from './controller';
import { middleware } from './monitor';
import monitor        from './monitor';
import config         from './config';
import Influx         from 'influxdb-nodejs';

const app    = new Koa();
const router = new KoaRouter();

router.use(middleware.routeAccess(router));

router.get('/',                                          controller.home.index);
router.get('/BikePoint',                                 controller.bikepoint.index);
router.io ('/BikePoint',                                 controller.bikepoint.fireHose);
router.get('/BikePoint/:bikePoint',                      controller.bikepoint.get);
router.io ('/BikePoint/:bikePoint',                      controller.bikepoint.streamSingle);
router.get('/BikePoint/around/:lon/:lat/:radius',        controller.bikepoint.around);
router.get('/BikePoint/box/:swlon/:swlat/:nelon/:nelat', controller.bikepoint.box);
router.get('/BikePoint/search/:searchstring',            controller.bikepoint.search);
router.get('/Occupancy/CarPark',                         controller.carpark.index);
router.io ('/Occupancy/CarPark',                         controller.carpark.fireHose);
router.get('/Occupancy/CarPark/:carPark',                controller.carpark.get);
router.io ('/Occupancy/CarPark/:carPark',                controller.carpark.streamSingle);
router.get('/StopPoint',                                 controller.stoppoint.index);
router.io ('/StopPoint',                                 controller.stoppoint.streamIndex);
router.get('/StopPoint/Departures',                      controller.departures.index);
router.get('/StopPoint/:stopPoint',                      controller.stoppoint.get);
router.get('/StopPoint/around/:lon/:lat/:radius',        controller.stoppoint.around);
router.get('/StopPoint/box/:swlon/:swlat/:nelon/:nelat', controller.stoppoint.box);
router.get('/StopPoint/search/:searchstring',            controller.stoppoint.search);
router.io ('/StopPoint/Departures',                      controller.departures.fireHose);
router.get('/StopPoint/Departures/:stopPoint',           controller.departures.get);
router.io ('/StopPoint/Departures/:stopPoint',           controller.departures.streamSingle);
router.get('/StopPoint/Departures/:stopPoint/:limit',    controller.departures.load);
router.get('/Journey/:from/to/:to',                      controller.journey.plan);
router.get('/Weather',                                   controller.weather.current);
router.io ('/Weather',                                   controller.weather.streamSingle);
router.get('/Weather/AirQuality',                        controller.airquality.index);
router.io ('/Weather/AirQuality',                        controller.airquality.fireHose);
router.get('/Weather/AirQuality/:weatherStation',        controller.airquality.get);
router.io ('/Weather/AirQuality/:weatherStation',        controller.airquality.streamSingle);

app.use(monitor())
   .use(middleware.responseTime())
   .use(async (ctx, next) => {
       await next();
       ctx.type = 'application/json';
   })
   .use(router.routes())
   .use(router.allowedMethods());

const server = Server(app.callback());
Stream.bind(server, router);

const PORT = config('SERVER_PORT', true);
if (PORT) {
    server.listen(PORT, 'localhost');
}

var influxdb = false;

const streamCountToInflux = () => {
    influxdb.write('streamConnections')
        .field({
            departures: controller.departures.streamCount(),
            bikepoint:  controller.bikepoint.streamCount(),
            carpark:    controller.carpark.streamCount(),
            weather:    controller.weather.streamCount(),
            airquality: controller.airquality.streamCount()
        })
        .then();
    return;
};

if (config('INFLUXDB')) {
    influxdb = new Influx(config('INFLUXDB') + config('NAME_VERSION'));
    setInterval(streamCountToInflux, 10000);
}

export default server;
<|endoftext|>


<commit_before>/** @namespace H5P */
H5P.InteractiveVideoInteraction = (function ($, EventDispatcher) {

  /**
   * Keeps control of interactions in the interactive video.
   *
   * @class
   * @param {Object} parameters describes action behavior
   * @param {H5P.InteractiveVideo} player instance
   */
  function Interaction(parameters, player, previousState) {
    var self = this;
    // Initialize event inheritance
    EventDispatcher.call(self);

    var instance, $interaction, $label, $continueButton;
    var action = parameters.action;
    if (previousState) {
      action.userDatas = {
        state: previousState
      };
    }

    // Find library name and title
    var library = action.library.split(' ')[0];
    var title = (action.params.contentName !== undefined ? action.params.contentName : player.l10n.interaction);

    // Detect custom html class for interaction.
    var classes = parameters.className;
    if (classes === undefined) {
      var classParts = action.library.split(' ')[0].toLowerCase().split('.');
      classes = classParts[0] + '-' + classParts[1] + '-interaction';
    }

    /**
     * Display the current interaction as a button on top of the video.
     *
     * @private
     */
    var createButton = function () {
      $interaction = $('<div/>', {
        tabIndex: 0,
        role: 'button',
        'class': 'h5p-interaction ' + classes + ' h5p-hidden',
        css: {
          left: parameters.x + '%',
          top: parameters.y + '%'
        },
        on: {
          click: function () {
            if (!self.dialogDisabled && library !== 'H5P.Nil') {
              openDialog();
            }
          },
          keypress: function (event) {
            if ((event.charCode || event.keyCode) === 32) { // Space
              if (!self.dialogDisabled && library !== 'H5P.Nil') {
                openDialog();
              }
            }
          }
        }
      });
      $('<div/>', {
        'class': 'h5p-interaction-button'
      }).appendTo($interaction);

      // Check to see if we should add label
      if (library === 'H5P.Nil' || (parameters.label && $converter.html(parameters.label).text().length)) {
        $label = $('<div/>', {
          'class': 'h5p-interaction-label',
          html: parameters.label
        }).appendTo($interaction);
      }

      self.trigger('display', $interaction);
      setTimeout(function () {
        if ($interaction) {
          // Transition in
          $interaction.removeClass('h5p-hidden');
        }
      }, 0);
    };

    /**
     * Opens button dialog.
     *
     * @private
     */
    var openDialog = function () {
      // Create wrapper for dialog content
      var $dialogContent = $('<div/>', {
        'class': 'h5p-dialog-interaction'
      });

      // Add new instance to dialog and open
      instance = H5P.newRunnable(action, player.contentId, $dialogContent, undefined, {parent: player});
      player.dialog.open($dialogContent);

      if (instance.getCurrentState instanceof Function ||
          typeof instance.getCurrentState === 'function') {
        // Keep track of the last state when closing the popup.
        player.dialog.once('close', function () {
          action.userDatas = {
            state: instance.getCurrentState()
          };
        });
      }

      if (library === 'H5P.Image') {
        // Special case for fitting images
        var max = player.dialog.getMaxSize($interaction);

        var $img = $dialogContent.find('img');
        if (action.params.file.width && action.params.file.height) {
          // Use the image size info that is stored
          resizeImage($img, max, {
            width: action.params.file.width,
            height: action.params.file.height
          });
        }
        else {
          // Wait for image to load
          $img.load(function () {
            if ($img.is(':visible')) {
              resizeImage($img, max, {
                width: this.width,
                height: this.height
              });
            }
          });
          player.dialog.position($interaction);
        }
      }
      else if (!(library === 'H5P.Summary' || library === 'H5P.Blanks')) {
        // Only Summary and Blanks uses the dialog that covers the entire video
        player.dialog.position($interaction);
      }

      if (library === 'H5P.Summary') {
        // Scroll summary to bottom if the task changes size
        var lastHeight = 0;
        H5P.on(instance, 'resize', function () {
          var height = $dialogContent.height();
          if (lastHeight > height + 10 || lastHeight < height - 10)  {
            setTimeout(function () {
              player.dialog.scroll(height, 300);
            }, 500);
          }
          lastHeight = height;
        });
      }

     processInstance($dialogContent, instance);
    };

    /**
     * Resize the image so that it fits the available dialog space.
     *
     * @private
     * @param {jQuery} $img
     * @param {Object} max width,height in em
     * @param {Object} size width,height in px
     */
    var resizeImage = function ($img, max, size) {
      var fontSize = 16;
      size.width /= fontSize;
      size.height /= fontSize;

      if (size.height > max.height) {
        size.width = size.width * max.height / size.height;
        size.height = max.height;
      }
      if (size.width > max.width) {
        size.height = size.height * max.width / size.width;
        size.width = max.width;
      }

      var fontSizeRatio = 16 / Number($img.css('fontSize').replace('px',''));
      $img.css({
        width: (size.width * fontSizeRatio) + 'em',
        height: (size.height * fontSizeRatio) + 'em'
      });

      // Set dialog size and position
      player.dialog.position($interaction, size);
    };

    /**
     * Display the current interaction as a poster on top of the video.
     *
     * @private
     */
    var createPoster = function () {
      $interaction = $('<div/>', {
        'class': 'h5p-interaction h5p-poster ' + classes + '',
        css: {
          left: parameters.x + '%',
          top: parameters.y + '%',
          width: (parameters.width ? parameters.width : 10) + 'em',
          height: (parameters.height ? parameters.height : 10) + 'em'
        }
      });
      $inner = $('<div/>', {
        'class': 'h5p-interaction-inner'
      }).appendTo($interaction);
      instance = H5P.newRunnable(action, player.contentId, $inner, undefined, {parent: player});

      // Trigger event listeners
      self.trigger('display', $interaction);

      processInstance($inner, instance);
    };

    var processInstance = function ($target, instance) {
      // Resize on next tick
      setTimeout(function () {
        H5P.trigger(instance, 'resize');
      }, 0);
      H5P.on(instance, 'xAPI', function (event) {
        if (event.getVerb() !== 'completed' ||
            !event.getMaxScore() ||
            event.getScore() === null) {
          return;
        }
        self.score = event.getScore();
        self.maxScore = event.getMaxScore();
        self.trigger(event);
        adaptivity($target);
      });
    };

    /**
     * Makes it easy to create buttons.
     *
     * @private
     * @param {jQuery} $container Where to append the button
     * @param {string} label Html
     * @param {function} handler What to do when clicked
     * @returns {jQuery}
     */
    var addButton = function ($container, label, handler) {
      return H5P.JoubelUI.createButton({
        tabIndex: 0,
        role: 'button',
        html: label,
        on: {
          click: function () {
            handler();
          },
          keypress: function (event) {
            if ((event.charCode || event.keyCode) === 32) {
              handler(); // Buttons must react to space
            }
          }
        },
        appendTo: $container
      });
    };

    /**
     * Adds adaptivity or continue button to exercies.
     *
     * @private
     * @param {jQuery} $target
     */
    var adaptivity = function ($target) {
      var adaptivity;
      if (parameters.adaptivity) {
        var fullScore = self.score >= self.maxScore;

        // Determine adaptivity
        adaptivity = (fullScore ? parameters.adaptivity.correct : parameters.adaptivity.wrong);
      }

      if (!adaptivity || adaptivity.seekTo === undefined) {
        // Add continue button if no adaptivity
        if (!$continueButton) {
          // Try to find suitable container
          var $container = $target.find('.h5p-show-solution-container'); // MC
          if (!$container.length) {
            $container = $target.find('.h5p-button-bar'); // B
          }
          if (!$container.length) {
            $container = $target.find('.h5p-drag-button-bar'); // DW
          }
          if (!$container.length) {
            $container = $target.find('.h5p-sc-feedback'); // SC
          }
          if (!$container.length) {
            $container = $target.find('.h5p-inner:first'); // DD
          }
          if ($container.length) {
            $continueButton = addButton($container, player.l10n.defaultAdaptivitySeekLabel, function () {
              if (self.isButton()) {
                // Close dialog
                player.dialog.close();
              }
              else {
                // Remove interaction posters
                $interaction.remove();
              }

              // Remove continue button
              $continueButton.remove();
              $continueButton = undefined;

              player.play();
            });
          }
        }

        return;
      }

      // Stop playback
      player.pause();

      if (!adaptivity.allowOptOut) {
        // Make sure only the interaction is useable.
        if (self.isButton()) {
          player.dialog.disableOverlay = true;
          player.dialog.hideCloseButton();
        }
        else {
          $interaction.css('zIndex', 52);
          player.dialog.openOverlay();
        }
      }

      // Replace interaction with adaptivity screen
      $target.html(adaptivity.message);
      //instance = ''; ??

      // Buttons wrapper
      var $buttonWrapper = $('<div/>', {
        'class': ''
      });

      // Add continue button
      addButton($buttonWrapper, (adaptivity.seekLabel ? adaptivity.seekLabel : player.l10n.defaultAdaptivitySeekLabel), function () {
        if (self.isButton()) {
          player.dialog.close();
        }
        if (!adaptivity.allowOptOut) {
          if (!self.isButton()) {
            player.dialog.closeOverlay();
            $interaction.css('zIndex', '');
          }
        }

        self.remove();
        player.seek(adaptivity.seekTo);
        player.play();
      });

      $buttonWrapper.appendTo($target);
    };

    /**
     * Extract the current state of interactivity for serialization.
     *
     * @public
     * @returns {object}
     */
    self.getCurrentState = function () {
      if (instance && (instance.getCurrentState instanceof Function ||
                       typeof instance.getCurrentState === 'function')) {
        return instance.getCurrentState();
      }
    };

    /**
     * Checks to see if the interaction should pause the video.
     *
     * @public
     * @returns {Boolean}
     */
    self.pause = function () {
      return parameters.pause;
    };

    /**
     * Check to see if interaction should be displayed as button.
     *
     * @public
     * @returns {Boolean}
     */
    self.isButton = function () {
      return parameters.displayAsButton === undefined || parameters.displayAsButton || library === 'H5P.Nil';
    };

    self.isMainSummary = function() {
      return parameters.mainSummary === true;
    };

    /**
     * Create dot for displaying above the video timeline.
     * Append to given container.
     *
     * @public
     * @param {jQuery} $container
     */
    self.addDot = function ($container) {
      if (library === 'H5P.Nil') {
        return; // Skip "sub titles"
      }

      // One could also set width using ((parameters.duration.to - parameters.duration.from + 1) * player.oneSecondInPercentage)
      $('<div/>', {
        'class': 'h5p-seekbar-interaction ' + classes,
        title: title,
        css: {
          left: (parameters.duration.from * player.oneSecondInPercentage) + '%'
        }
      }).appendTo($container);
    };

    /**
     * Display or remove the interaction depending on the video time.
     *
     * @public
     * @param {Number} second video time
     * @returns {jQuery} interaction button or container
     */
    self.toggle = function (second) {
      if (second < parameters.duration.from || second > parameters.duration.to) {
        if ($interaction) {
          // Remove interaction from display
          self.remove();
        }
        return;
      }

      if ($interaction) {
        return; // Interaction already on display
      }

      if (self.isButton()) {
        createButton();
      }
      else {
        createPoster();
      }

      return $interaction;
    };

    /**
     * Position label to the left or right of the action button.
     *
     * @public
     * @param {Number} width Size of the container
     */
    self.positionLabel = function (width) {
      if (!$interaction || !self.isButton() || !$label || library === 'H5P.Nil') {
        return;
      }

      $label.removeClass('h5p-left-label');
      if (parseInt($interaction.css('left')) + $label.position().left + $label.outerWidth() > width) {
        $label.addClass('h5p-left-label');
      }
    };

    /**
     * Update element position.
     *
     * @public
     * @param {Number} x left
     * @param {Number} y top
     */
    self.setPosition = function (x, y) {
      parameters.x = x;
      parameters.y = y;
    };

    /**
     * Update element size.
     *
     * @public
     * @param {Number} width in ems
     * @param {Number} height in ems
     */
    self.setSize = function (width, height) {
      parameters.width = width;
      parameters.height = height;

      if (library === 'H5P.DragQuestion') {
        // Re-create element to set new size
        self.remove(true);
        self.toggle(parameters.from);
      }

      H5P.trigger(instance, 'resize');
    };

    /**
     * Removes interaction from display.
     *
     * @public
     * @param {Boolean} [updateSize] Used when re-creating element
     */
    self.remove = function (updateSize) {
      if ($interaction) {
        if (updateSize && library === 'H5P.DragQuestion') {
          // Update size
          var size = action.params.question.settings.size;
          var fontSize = Number($interaction.css('fontSize').replace('px', ''));
          if (self.isButton()) {
            // Update element size with drag question parameters
            parameters.width = size.width / fontSize;
            parameters.height = size.height / fontSize;
          }
          else {
            // Update drag question parameters with size set on element
            size.width = Math.round(parameters.width * fontSize);
            size.height = Math.round(parameters.height * fontSize);
          }
        }

        $interaction.remove();
        $interaction = undefined;
      }
    };

    /**
     * @public
     * @returns {String}
     */
    self.getLibraryName = function () {
      return library;
    };

    /**
     * Collect copyright information for the interaction.
     *
     * @public
     * @returns {H5P.ContentCopyrights}
     */
    self.getCopyrights = function () {
      var instance = H5P.newRunnable(action, player.contentId);

      if (instance !== undefined && instance.getCopyrights !== undefined) {
        var interactionCopyrights = instance.getCopyrights();
        if (interactionCopyrights !== undefined) {
          interactionCopyrights.setLabel(title + ' ' + humanizeTime(parameters.duration.from) + ' - ' + humanizeTime(parameters.duration.to));
          return interactionCopyrights;
        }
      }
    };
  }

  // Extends the event dispatcher
  Interaction.prototype = Object.create(EventDispatcher.prototype);
  Interaction.prototype.constructor = Interaction;

  // Tool for converting
  var $converter = $('<div/>');

  return Interaction;
})(H5P.jQuery, H5P.EventDispatcher);
<commit_msg>Added continue button for Single Choice Set to proper $target.<commit_after>/** @namespace H5P */
H5P.InteractiveVideoInteraction = (function ($, EventDispatcher) {

  /**
   * Keeps control of interactions in the interactive video.
   *
   * @class
   * @param {Object} parameters describes action behavior
   * @param {H5P.InteractiveVideo} player instance
   */
  function Interaction(parameters, player, previousState) {
    var self = this;
    // Initialize event inheritance
    EventDispatcher.call(self);

    var instance, $interaction, $label, $continueButton;
    var action = parameters.action;
    if (previousState) {
      action.userDatas = {
        state: previousState
      };
    }

    // Find library name and title
    var library = action.library.split(' ')[0];
    var title = (action.params.contentName !== undefined ? action.params.contentName : player.l10n.interaction);

    // Detect custom html class for interaction.
    var classes = parameters.className;
    if (classes === undefined) {
      var classParts = action.library.split(' ')[0].toLowerCase().split('.');
      classes = classParts[0] + '-' + classParts[1] + '-interaction';
    }

    /**
     * Display the current interaction as a button on top of the video.
     *
     * @private
     */
    var createButton = function () {
      $interaction = $('<div/>', {
        tabIndex: 0,
        role: 'button',
        'class': 'h5p-interaction ' + classes + ' h5p-hidden',
        css: {
          left: parameters.x + '%',
          top: parameters.y + '%'
        },
        on: {
          click: function () {
            if (!self.dialogDisabled && library !== 'H5P.Nil') {
              openDialog();
            }
          },
          keypress: function (event) {
            if ((event.charCode || event.keyCode) === 32) { // Space
              if (!self.dialogDisabled && library !== 'H5P.Nil') {
                openDialog();
              }
            }
          }
        }
      });
      $('<div/>', {
        'class': 'h5p-interaction-button'
      }).appendTo($interaction);

      // Check to see if we should add label
      if (library === 'H5P.Nil' || (parameters.label && $converter.html(parameters.label).text().length)) {
        $label = $('<div/>', {
          'class': 'h5p-interaction-label',
          html: parameters.label
        }).appendTo($interaction);
      }

      self.trigger('display', $interaction);
      setTimeout(function () {
        if ($interaction) {
          // Transition in
          $interaction.removeClass('h5p-hidden');
        }
      }, 0);
    };

    /**
     * Opens button dialog.
     *
     * @private
     */
    var openDialog = function () {
      // Create wrapper for dialog content
      var $dialogContent = $('<div/>', {
        'class': 'h5p-dialog-interaction'
      });

      // Add new instance to dialog and open
      instance = H5P.newRunnable(action, player.contentId, $dialogContent, undefined, {parent: player});
      player.dialog.open($dialogContent);

      if (instance.getCurrentState instanceof Function ||
          typeof instance.getCurrentState === 'function') {
        // Keep track of the last state when closing the popup.
        player.dialog.once('close', function () {
          action.userDatas = {
            state: instance.getCurrentState()
          };
        });
      }

      if (library === 'H5P.Image') {
        // Special case for fitting images
        var max = player.dialog.getMaxSize($interaction);

        var $img = $dialogContent.find('img');
        if (action.params.file.width && action.params.file.height) {
          // Use the image size info that is stored
          resizeImage($img, max, {
            width: action.params.file.width,
            height: action.params.file.height
          });
        }
        else {
          // Wait for image to load
          $img.load(function () {
            if ($img.is(':visible')) {
              resizeImage($img, max, {
                width: this.width,
                height: this.height
              });
            }
          });
          player.dialog.position($interaction);
        }
      }
      else if (!(library === 'H5P.Summary' || library === 'H5P.Blanks')) {
        // Only Summary and Blanks uses the dialog that covers the entire video
        player.dialog.position($interaction);
      }

      if (library === 'H5P.Summary') {
        // Scroll summary to bottom if the task changes size
        var lastHeight = 0;
        H5P.on(instance, 'resize', function () {
          var height = $dialogContent.height();
          if (lastHeight > height + 10 || lastHeight < height - 10)  {
            setTimeout(function () {
              player.dialog.scroll(height, 300);
            }, 500);
          }
          lastHeight = height;
        });
      }

     processInstance($dialogContent, instance);
    };

    /**
     * Resize the image so that it fits the available dialog space.
     *
     * @private
     * @param {jQuery} $img
     * @param {Object} max width,height in em
     * @param {Object} size width,height in px
     */
    var resizeImage = function ($img, max, size) {
      var fontSize = 16;
      size.width /= fontSize;
      size.height /= fontSize;

      if (size.height > max.height) {
        size.width = size.width * max.height / size.height;
        size.height = max.height;
      }
      if (size.width > max.width) {
        size.height = size.height * max.width / size.width;
        size.width = max.width;
      }

      var fontSizeRatio = 16 / Number($img.css('fontSize').replace('px',''));
      $img.css({
        width: (size.width * fontSizeRatio) + 'em',
        height: (size.height * fontSizeRatio) + 'em'
      });

      // Set dialog size and position
      player.dialog.position($interaction, size);
    };

    /**
     * Display the current interaction as a poster on top of the video.
     *
     * @private
     */
    var createPoster = function () {
      $interaction = $('<div/>', {
        'class': 'h5p-interaction h5p-poster ' + classes + '',
        css: {
          left: parameters.x + '%',
          top: parameters.y + '%',
          width: (parameters.width ? parameters.width : 10) + 'em',
          height: (parameters.height ? parameters.height : 10) + 'em'
        }
      });
      $inner = $('<div/>', {
        'class': 'h5p-interaction-inner'
      }).appendTo($interaction);
      instance = H5P.newRunnable(action, player.contentId, $inner, undefined, {parent: player});

      // Trigger event listeners
      self.trigger('display', $interaction);

      processInstance($inner, instance);
    };

    var processInstance = function ($target, instance) {
      // Resize on next tick
      setTimeout(function () {
        H5P.trigger(instance, 'resize');
      }, 0);
      H5P.on(instance, 'xAPI', function (event) {
        if (event.getVerb() !== 'completed' ||
            !event.getMaxScore() ||
            event.getScore() === null) {
          return;
        }
        self.score = event.getScore();
        self.maxScore = event.getMaxScore();
        self.trigger(event);
        adaptivity($target);
      });
    };

    /**
     * Makes it easy to create buttons.
     *
     * @private
     * @param {jQuery} $container Where to append the button
     * @param {string} label Html
     * @param {function} handler What to do when clicked
     * @returns {jQuery}
     */
    var addButton = function ($container, label, handler) {
      return H5P.JoubelUI.createButton({
        tabIndex: 0,
        role: 'button',
        html: label,
        on: {
          click: function () {
            handler();
          },
          keypress: function (event) {
            if ((event.charCode || event.keyCode) === 32) {
              handler(); // Buttons must react to space
            }
          }
        },
        appendTo: $container
      });
    };

    /**
     * Adds adaptivity or continue button to exercies.
     *
     * @private
     * @param {jQuery} $target
     */
    var adaptivity = function ($target) {
      debugger;
      var adaptivity;
      if (parameters.adaptivity) {
        var fullScore = self.score >= self.maxScore;

        // Determine adaptivity
        adaptivity = (fullScore ? parameters.adaptivity.correct : parameters.adaptivity.wrong);
      }

      if (!adaptivity || adaptivity.seekTo === undefined) {
        // Add continue button if no adaptivity
        if (!$continueButton) {
          // Try to find suitable container
          var $container = $target.find('.h5p-show-solution-container'); // MC
          if (!$container.length) {
            $container = $target.find('.h5p-button-bar'); // B
          }
          if (!$container.length) {
            $container = $target.find('.h5p-drag-button-bar'); // DW
          }
          if (!$container.length) {
            $container = $target.find('.h5p-sc-set-results'); // SC
          }
          if (!$container.length) {
            $container = $target.find('.h5p-inner:first'); // DD
          }
          if ($container.length) {
            $continueButton = addButton($container, player.l10n.defaultAdaptivitySeekLabel, function () {
              if (self.isButton()) {
                // Close dialog
                player.dialog.close();
              }
              else {
                // Remove interaction posters
                $interaction.remove();
              }

              // Remove continue button
              $continueButton.remove();
              $continueButton = undefined;

              player.play();
            });
          }
        }

        return;
      }

      // Stop playback
      player.pause();

      if (!adaptivity.allowOptOut) {
        // Make sure only the interaction is useable.
        if (self.isButton()) {
          player.dialog.disableOverlay = true;
          player.dialog.hideCloseButton();
        }
        else {
          $interaction.css('zIndex', 52);
          player.dialog.openOverlay();
        }
      }

      // Replace interaction with adaptivity screen
      $target.html(adaptivity.message);
      //instance = ''; ??

      // Buttons wrapper
      var $buttonWrapper = $('<div/>', {
        'class': ''
      });

      // Add continue button
      addButton($buttonWrapper, (adaptivity.seekLabel ? adaptivity.seekLabel : player.l10n.defaultAdaptivitySeekLabel), function () {
        if (self.isButton()) {
          player.dialog.close();
        }
        if (!adaptivity.allowOptOut) {
          if (!self.isButton()) {
            player.dialog.closeOverlay();
            $interaction.css('zIndex', '');
          }
        }

        self.remove();
        player.seek(adaptivity.seekTo);
        player.play();
      });

      $buttonWrapper.appendTo($target);
    };

    /**
     * Extract the current state of interactivity for serialization.
     *
     * @public
     * @returns {object}
     */
    self.getCurrentState = function () {
      if (instance && (instance.getCurrentState instanceof Function ||
                       typeof instance.getCurrentState === 'function')) {
        return instance.getCurrentState();
      }
    };

    /**
     * Checks to see if the interaction should pause the video.
     *
     * @public
     * @returns {Boolean}
     */
    self.pause = function () {
      return parameters.pause;
    };

    /**
     * Check to see if interaction should be displayed as button.
     *
     * @public
     * @returns {Boolean}
     */
    self.isButton = function () {
      return parameters.displayAsButton === undefined || parameters.displayAsButton || library === 'H5P.Nil';
    };

    self.isMainSummary = function() {
      return parameters.mainSummary === true;
    };

    /**
     * Create dot for displaying above the video timeline.
     * Append to given container.
     *
     * @public
     * @param {jQuery} $container
     */
    self.addDot = function ($container) {
      if (library === 'H5P.Nil') {
        return; // Skip "sub titles"
      }

      // One could also set width using ((parameters.duration.to - parameters.duration.from + 1) * player.oneSecondInPercentage)
      $('<div/>', {
        'class': 'h5p-seekbar-interaction ' + classes,
        title: title,
        css: {
          left: (parameters.duration.from * player.oneSecondInPercentage) + '%'
        }
      }).appendTo($container);
    };

    /**
     * Display or remove the interaction depending on the video time.
     *
     * @public
     * @param {Number} second video time
     * @returns {jQuery} interaction button or container
     */
    self.toggle = function (second) {
      if (second < parameters.duration.from || second > parameters.duration.to) {
        if ($interaction) {
          // Remove interaction from display
          self.remove();
        }
        return;
      }

      if ($interaction) {
        return; // Interaction already on display
      }

      if (self.isButton()) {
        createButton();
      }
      else {
        createPoster();
      }

      return $interaction;
    };

    /**
     * Position label to the left or right of the action button.
     *
     * @public
     * @param {Number} width Size of the container
     */
    self.positionLabel = function (width) {
      if (!$interaction || !self.isButton() || !$label || library === 'H5P.Nil') {
        return;
      }

      $label.removeClass('h5p-left-label');
      if (parseInt($interaction.css('left')) + $label.position().left + $label.outerWidth() > width) {
        $label.addClass('h5p-left-label');
      }
    };

    /**
     * Update element position.
     *
     * @public
     * @param {Number} x left
     * @param {Number} y top
     */
    self.setPosition = function (x, y) {
      parameters.x = x;
      parameters.y = y;
    };

    /**
     * Update element size.
     *
     * @public
     * @param {Number} width in ems
     * @param {Number} height in ems
     */
    self.setSize = function (width, height) {
      parameters.width = width;
      parameters.height = height;

      if (library === 'H5P.DragQuestion') {
        // Re-create element to set new size
        self.remove(true);
        self.toggle(parameters.from);
      }

      H5P.trigger(instance, 'resize');
    };

    /**
     * Removes interaction from display.
     *
     * @public
     * @param {Boolean} [updateSize] Used when re-creating element
     */
    self.remove = function (updateSize) {
      if ($interaction) {
        if (updateSize && library === 'H5P.DragQuestion') {
          // Update size
          var size = action.params.question.settings.size;
          var fontSize = Number($interaction.css('fontSize').replace('px', ''));
          if (self.isButton()) {
            // Update element size with drag question parameters
            parameters.width = size.width / fontSize;
            parameters.height = size.height / fontSize;
          }
          else {
            // Update drag question parameters with size set on element
            size.width = Math.round(parameters.width * fontSize);
            size.height = Math.round(parameters.height * fontSize);
          }
        }

        $interaction.remove();
        $interaction = undefined;
      }
    };

    /**
     * @public
     * @returns {String}
     */
    self.getLibraryName = function () {
      return library;
    };

    /**
     * Collect copyright information for the interaction.
     *
     * @public
     * @returns {H5P.ContentCopyrights}
     */
    self.getCopyrights = function () {
      var instance = H5P.newRunnable(action, player.contentId);

      if (instance !== undefined && instance.getCopyrights !== undefined) {
        var interactionCopyrights = instance.getCopyrights();
        if (interactionCopyrights !== undefined) {
          interactionCopyrights.setLabel(title + ' ' + humanizeTime(parameters.duration.from) + ' - ' + humanizeTime(parameters.duration.to));
          return interactionCopyrights;
        }
      }
    };
  }

  // Extends the event dispatcher
  Interaction.prototype = Object.create(EventDispatcher.prototype);
  Interaction.prototype.constructor = Interaction;

  // Tool for converting
  var $converter = $('<div/>');

  return Interaction;
})(H5P.jQuery, H5P.EventDispatcher);
<|endoftext|>


<commit_before>import React, { useCallback, useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { isEqual, isNil, isNull } from "lodash";
import * as Events from "./Events";
import usePrevious from "./usePrevValue";

if (!global._babelPolyfill) {
  require("babel-polyfill");
}

const DEFAULT_ANIMATION_TIMER = 1000;
const DEFAULT_ANIMATION = "ease-in-out";
const DEFAULT_CONTAINER_HEIGHT = "100vh";
const DEFAULT_CONTAINER_WIDTH = "100vw";
const DEFAULT_COMPONENT_INDEX = 0;
const DEFAULT_COMPONENTS_TO_RENDER_LENGTH = 0;

const ANIMATION_TIMER_BUFFER = 200;
const KEY_UP = 38;
const KEY_DOWN = 40;
const DISABLED_CLASS_NAME = "rps-scroll--disabled";

let previousTouchMove = null;
let isScrolling = false;
let isMounted = false;
let isBodyScrollEnabled = true;
let isTransitionAfterComponentsToRenderChanged = false;
const containers = [];

const ReactPageScroller = ({
  animationTimer,
  blockScrollDown,
  blockScrollUp,
  children,
  containerHeight,
  containerWidth,
  customPageNumber,
  handleScrollUnavailable,
  pageOnChange,
  renderAllPagesOnFirstRender,
  transitionTimingFunction,
}) => {
  const [componentIndex, setComponentIndex] = useState(DEFAULT_COMPONENT_INDEX);
  const [componentsToRenderLength, setComponentsToRenderLength] = useState(
    DEFAULT_COMPONENTS_TO_RENDER_LENGTH,
  );
  const prevComponentIndex = usePrevious(componentIndex);
  const pageContainer = useRef(null);

  const addNextComponent = useCallback(
    componentsToRenderOnMountLength => {
      let tempComponentsToRenderLength = 0;

      if (!isNil(componentsToRenderOnMountLength)) {
        tempComponentsToRenderLength = componentsToRenderOnMountLength;
      }

      tempComponentsToRenderLength = Math.max(
        tempComponentsToRenderLength,
        componentsToRenderLength,
      );

      if (tempComponentsToRenderLength <= componentIndex + 1) {
        if (!isNil(children[componentIndex + 1])) {
          tempComponentsToRenderLength++;
        }
      }

      setComponentsToRenderLength(tempComponentsToRenderLength);
    },
    [children, componentIndex, componentsToRenderLength],
  );

  const checkRenderOnMount = useCallback(() => {
    if (renderAllPagesOnFirstRender) {
      setComponentsToRenderLength(React.Children.count(children));
    } else if (!isNil(children[DEFAULT_COMPONENT_INDEX + 1])) {
      addNextComponent(DEFAULT_COMPONENTS_TO_RENDER_LENGTH + 1);
    }
  }, [addNextComponent, children, renderAllPagesOnFirstRender]);

  const disableScroll = useCallback(() => {
    if (isBodyScrollEnabled) {
      isBodyScrollEnabled = false;
      window.scrollTo({
        left: 0,
        top: 0,
        behavior: "smooth",
      });
      document.body.classList.add(DISABLED_CLASS_NAME);
      document.documentElement.classList.add(DISABLED_CLASS_NAME);
    }
  }, []);

  const enableDocumentScroll = useCallback(() => {
    if (!isBodyScrollEnabled) {
      isBodyScrollEnabled = true;
      document.body.classList.remove(DISABLED_CLASS_NAME);
      document.documentElement.classList.remove(DISABLED_CLASS_NAME);
    }
  }, []);

  const setRenderComponents = useCallback(() => {
    const newComponentsToRender = [];

    let i = 0;

    while (i < componentsToRenderLength && !isNil(children[i])) {
      containers[i] = true;
      newComponentsToRender.push(

          {React.cloneElement(children[i], { ...children[i].props })}

,
      );
      i++;
    }

    return newComponentsToRender;
  }, [children, componentsToRenderLength]);

  const scrollWindowDown = useCallback(() => {
    if (!isScrolling && !blockScrollDown) {
      if (!isNil(containers[componentIndex + 1])) {
        disableScroll();
        isScrolling = true;
        pageContainer.current.style.transform = `translate3d(0, ${(componentIndex +
          1) *
          -100}%, 0)`;

        setTimeout(() => {
          if (isMounted) {
            setComponentIndex(prevState => prevState + 1);
          }
        }, animationTimer + ANIMATION_TIMER_BUFFER);
      } else {
        enableDocumentScroll();
        if (handleScrollUnavailable) {
          handleScrollUnavailable();
        }
      }
    }
  }, [
    animationTimer,
    blockScrollDown,
    componentIndex,
    disableScroll,
    enableDocumentScroll,
    handleScrollUnavailable,
  ]);

  const scrollWindowUp = useCallback(() => {
    if (!isScrolling && !blockScrollUp) {
      if (!isNil(containers[componentIndex - 1])) {
        disableScroll();
        isScrolling = true;
        pageContainer.current.style.transform = `translate3d(0, ${(componentIndex -
          1) *
          -100}%, 0)`;

        setTimeout(() => {
          if (isMounted) {
            setComponentIndex(prevState => prevState - 1);
          }
        }, animationTimer + ANIMATION_TIMER_BUFFER);
      } else {
        enableDocumentScroll();
        if (handleScrollUnavailable) {
          handleScrollUnavailable();
        }
      }
    }
  }, [
    animationTimer,
    blockScrollUp,
    componentIndex,
    disableScroll,
    enableDocumentScroll,
    handleScrollUnavailable,
  ]);

  const touchMove = useCallback(
    event => {
      if (!isNull(previousTouchMove)) {
        if (event.touches[0].clientY > previousTouchMove) {
          scrollWindowUp();
        } else {
          scrollWindowDown();
        }
      } else {
        previousTouchMove = event.touches[0].clientY;
      }
    },
    [scrollWindowDown, scrollWindowUp],
  );

  const wheelScroll = useCallback(
    event => {
      if (event.deltaY < 0) {
        scrollWindowUp();
      } else {
        scrollWindowDown();
      }
    },
    [scrollWindowDown, scrollWindowUp],
  );

  const keyPress = useCallback(
    event => {
      if (isEqual(event.keyCode, KEY_UP)) {
        scrollWindowUp();
      }
      if (isEqual(event.keyCode, KEY_DOWN)) {
        scrollWindowDown();
      }
    },
    [scrollWindowDown, scrollWindowUp],
  );

  useEffect(() => {
    pageContainer.current.addEventListener(Events.TOUCHMOVE, touchMove);
    pageContainer.current.addEventListener(Events.KEYDOWN, keyPress);
    return () => {
      pageContainer.current.removeEventListener(Events.TOUCHMOVE, touchMove);
      pageContainer.current.removeEventListener(Events.KEYDOWN, keyPress);
    };
  }, [touchMove, keyPress]);

  useEffect(() => {
    isMounted = true;

    checkRenderOnMount();
    return () => {
      isMounted = false;
    };
  }, []);

  useEffect(() => {
    isScrolling = false;
    previousTouchMove = null;
    if (componentIndex > prevComponentIndex) {
      addNextComponent();
    }
  }, [addNextComponent, componentIndex, prevComponentIndex]);

  useEffect(() => {
    if (pageOnChange) {
      pageOnChange(componentIndex);
    }
  }, [pageOnChange, componentIndex]);

  useEffect(() => {
    if (
      !isNil(customPageNumber) &&
      !isEqual(customPageNumber, componentIndex)
    ) {
      let newComponentsToRenderLength = componentsToRenderLength;

      if (!isEqual(componentIndex, customPageNumber)) {
        if (!isNil(containers[customPageNumber]) && !isScrolling) {
          isScrolling = true;
          // eslint-disable-next-line max-len
          pageContainer.current.style.transform = `translate3d(0, ${customPageNumber *
            -100}%, 0)`;

          if (
            isNil(containers[customPageNumber]) &&
            !isNil(children[customPageNumber])
          ) {
            newComponentsToRenderLength++;
          }

          setTimeout(() => {
            setComponentIndex(customPageNumber);
            setComponentsToRenderLength(newComponentsToRenderLength);
          }, animationTimer + ANIMATION_TIMER_BUFFER);
        } else if (!isScrolling && !isNil(children[customPageNumber])) {
          for (let i = componentsToRenderLength; i <= customPageNumber; i++) {
            newComponentsToRenderLength++;
          }

          if (!isNil(children[customPageNumber])) {
            newComponentsToRenderLength++;
          }

          isScrolling = true;
          isTransitionAfterComponentsToRenderChanged = true;
          setComponentsToRenderLength(newComponentsToRenderLength);
        }
      }
    }
  }, [customPageNumber]);

  useEffect(() => {
    if (isTransitionAfterComponentsToRenderChanged) {
      isTransitionAfterComponentsToRenderChanged = false;

      pageContainer.current.style.transform = `translate3d(0, ${customPageNumber *
        -100}%, 0)`;

      setTimeout(() => {
        setComponentIndex(customPageNumber);
      }, animationTimer + ANIMATION_TIMER_BUFFER);
    }
  }, [animationTimer, componentsToRenderLength, customPageNumber]);

  return (

      <div
        ref={pageContainer}
        onWheel={wheelScroll}
        style={{
          height: "100%",
          width: "100%",
          transition: `transform ${animationTimer}ms ${transitionTimingFunction}`,
          outline: "none"
        }}
        tabIndex={0}
      >
        {setRenderComponents()}

  );
};

ReactPageScroller.propTypes = {
  animationTimer: PropTypes.number,
  blockScrollDown: PropTypes.bool,
  blockScrollUp: PropTypes.bool,
  children: PropTypes.any,
  containerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  containerWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  customPageNumber: PropTypes.number,
  handleScrollUnavailable: PropTypes.func,
  pageOnChange: PropTypes.func,
  renderAllPagesOnFirstRender: PropTypes.bool,
  transitionTimingFunction: PropTypes.string,
};

ReactPageScroller.defaultProps = {
  animationTimer: DEFAULT_ANIMATION_TIMER,
  transitionTimingFunction: DEFAULT_ANIMATION,
  containerHeight: DEFAULT_CONTAINER_HEIGHT,
  containerWidth: DEFAULT_CONTAINER_WIDTH,
  blockScrollUp: false,
  blockScrollDown: false,
};

export default ReactPageScroller;
<commit_msg>Update index.js<commit_after>import React, { useCallback, useEffect, useRef, useState } from "react";
import PropTypes from "prop-types";
import { isEqual, isNil, isNull } from "lodash";
import * as Events from "./Events";
import usePrevious from "./usePrevValue";

if (!global._babelPolyfill) {
  require("babel-polyfill");
}

const DEFAULT_ANIMATION_TIMER = 1000;
const DEFAULT_ANIMATION = "ease-in-out";
const DEFAULT_CONTAINER_HEIGHT = "100vh";
const DEFAULT_CONTAINER_WIDTH = "100vw";
const DEFAULT_COMPONENT_INDEX = 0;
const DEFAULT_COMPONENTS_TO_RENDER_LENGTH = 0;

const DEFAULT_ANIMATION_TIMER_BUFFER = 200;
const KEY_UP = 38;
const KEY_DOWN = 40;
const DISABLED_CLASS_NAME = "rps-scroll--disabled";

let previousTouchMove = null;
let isScrolling = false;
let isMounted = false;
let isBodyScrollEnabled = true;
let isTransitionAfterComponentsToRenderChanged = false;
const containers = [];

const ReactPageScroller = ({
  animationTimer,
  animationTimerBuffer,
  blockScrollDown,
  blockScrollUp,
  children,
  containerHeight,
  containerWidth,
  customPageNumber,
  handleScrollUnavailable,
  pageOnChange,
  renderAllPagesOnFirstRender,
  transitionTimingFunction,
}) => {
  const [componentIndex, setComponentIndex] = useState(DEFAULT_COMPONENT_INDEX);
  const [componentsToRenderLength, setComponentsToRenderLength] = useState(
    DEFAULT_COMPONENTS_TO_RENDER_LENGTH,
  );
  const prevComponentIndex = usePrevious(componentIndex);
  const pageContainer = useRef(null);

  const addNextComponent = useCallback(
    componentsToRenderOnMountLength => {
      let tempComponentsToRenderLength = 0;

      if (!isNil(componentsToRenderOnMountLength)) {
        tempComponentsToRenderLength = componentsToRenderOnMountLength;
      }

      tempComponentsToRenderLength = Math.max(
        tempComponentsToRenderLength,
        componentsToRenderLength,
      );

      if (tempComponentsToRenderLength <= componentIndex + 1) {
        if (!isNil(children[componentIndex + 1])) {
          tempComponentsToRenderLength++;
        }
      }

      setComponentsToRenderLength(tempComponentsToRenderLength);
    },
    [children, componentIndex, componentsToRenderLength],
  );

  const checkRenderOnMount = useCallback(() => {
    if (renderAllPagesOnFirstRender) {
      setComponentsToRenderLength(React.Children.count(children));
    } else if (!isNil(children[DEFAULT_COMPONENT_INDEX + 1])) {
      addNextComponent(DEFAULT_COMPONENTS_TO_RENDER_LENGTH + 1);
    }
  }, [addNextComponent, children, renderAllPagesOnFirstRender]);

  const disableScroll = useCallback(() => {
    if (isBodyScrollEnabled) {
      isBodyScrollEnabled = false;
      window.scrollTo({
        left: 0,
        top: 0,
        behavior: "smooth",
      });
      document.body.classList.add(DISABLED_CLASS_NAME);
      document.documentElement.classList.add(DISABLED_CLASS_NAME);
    }
  }, []);

  const enableDocumentScroll = useCallback(() => {
    if (!isBodyScrollEnabled) {
      isBodyScrollEnabled = true;
      document.body.classList.remove(DISABLED_CLASS_NAME);
      document.documentElement.classList.remove(DISABLED_CLASS_NAME);
    }
  }, []);

  const setRenderComponents = useCallback(() => {
    const newComponentsToRender = [];

    let i = 0;

    while (i < componentsToRenderLength && !isNil(children[i])) {
      containers[i] = true;
      newComponentsToRender.push(

          {React.cloneElement(children[i], { ...children[i].props })}

,
      );
      i++;
    }

    return newComponentsToRender;
  }, [children, componentsToRenderLength]);

  const scrollWindowDown = useCallback(() => {
    if (!isScrolling && !blockScrollDown) {
      if (!isNil(containers[componentIndex + 1])) {
        disableScroll();
        isScrolling = true;
        pageContainer.current.style.transform = `translate3d(0, ${(componentIndex +
          1) *
          -100}%, 0)`;

        setTimeout(() => {
          if (isMounted) {
            setComponentIndex(prevState => prevState + 1);
          }
        }, animationTimer + animationTimerBuffer);
      } else {
        enableDocumentScroll();
        if (handleScrollUnavailable) {
          handleScrollUnavailable();
        }
      }
    }
  }, [
    animationTimer,
    animationTimerBuffer,
    blockScrollDown,
    componentIndex,
    disableScroll,
    enableDocumentScroll,
    handleScrollUnavailable,
  ]);

  const scrollWindowUp = useCallback(() => {
    if (!isScrolling && !blockScrollUp) {
      if (!isNil(containers[componentIndex - 1])) {
        disableScroll();
        isScrolling = true;
        pageContainer.current.style.transform = `translate3d(0, ${(componentIndex -
          1) *
          -100}%, 0)`;

        setTimeout(() => {
          if (isMounted) {
            setComponentIndex(prevState => prevState - 1);
          }
        }, animationTimer + animationTimerBuffer);
      } else {
        enableDocumentScroll();
        if (handleScrollUnavailable) {
          handleScrollUnavailable();
        }
      }
    }
  }, [
    animationTimer,
    animationTimerBuffer,
    blockScrollUp,
    componentIndex,
    disableScroll,
    enableDocumentScroll,
    handleScrollUnavailable,
  ]);

  const touchMove = useCallback(
    event => {
      if (!isNull(previousTouchMove)) {
        if (event.touches[0].clientY > previousTouchMove) {
          scrollWindowUp();
        } else {
          scrollWindowDown();
        }
      } else {
        previousTouchMove = event.touches[0].clientY;
      }
    },
    [scrollWindowDown, scrollWindowUp],
  );

  const wheelScroll = useCallback(
    event => {
      if (event.deltaY < 0) {
        scrollWindowUp();
      } else {
        scrollWindowDown();
      }
    },
    [scrollWindowDown, scrollWindowUp],
  );

  const keyPress = useCallback(
    event => {
      if (isEqual(event.keyCode, KEY_UP)) {
        scrollWindowUp();
      }
      if (isEqual(event.keyCode, KEY_DOWN)) {
        scrollWindowDown();
      }
    },
    [scrollWindowDown, scrollWindowUp],
  );

  useEffect(() => {
    pageContainer.current.addEventListener(Events.TOUCHMOVE, touchMove);
    pageContainer.current.addEventListener(Events.KEYDOWN, keyPress);
    return () => {
      pageContainer.current.removeEventListener(Events.TOUCHMOVE, touchMove);
      pageContainer.current.removeEventListener(Events.KEYDOWN, keyPress);
    };
  }, [touchMove, keyPress]);

  useEffect(() => {
    isMounted = true;

    checkRenderOnMount();
    return () => {
      isMounted = false;
    };
  }, []);

  useEffect(() => {
    isScrolling = false;
    previousTouchMove = null;
    if (componentIndex > prevComponentIndex) {
      addNextComponent();
    }
  }, [addNextComponent, componentIndex, prevComponentIndex]);

  useEffect(() => {
    if (pageOnChange) {
      pageOnChange(componentIndex);
    }
  }, [pageOnChange, componentIndex]);

  useEffect(() => {
    if (
      !isNil(customPageNumber) &&
      !isEqual(customPageNumber, componentIndex)
    ) {
      let newComponentsToRenderLength = componentsToRenderLength;

      if (!isEqual(componentIndex, customPageNumber)) {
        if (!isNil(containers[customPageNumber]) && !isScrolling) {
          isScrolling = true;
          // eslint-disable-next-line max-len
          pageContainer.current.style.transform = `translate3d(0, ${customPageNumber *
            -100}%, 0)`;

          if (
            isNil(containers[customPageNumber]) &&
            !isNil(children[customPageNumber])
          ) {
            newComponentsToRenderLength++;
          }

          setTimeout(() => {
            setComponentIndex(customPageNumber);
            setComponentsToRenderLength(newComponentsToRenderLength);
          }, animationTimer + animationTimerBuffer);
        } else if (!isScrolling && !isNil(children[customPageNumber])) {
          for (let i = componentsToRenderLength; i <= customPageNumber; i++) {
            newComponentsToRenderLength++;
          }

          if (!isNil(children[customPageNumber])) {
            newComponentsToRenderLength++;
          }

          isScrolling = true;
          isTransitionAfterComponentsToRenderChanged = true;
          setComponentsToRenderLength(newComponentsToRenderLength);
        }
      }
    }
  }, [customPageNumber]);

  useEffect(() => {
    if (isTransitionAfterComponentsToRenderChanged) {
      isTransitionAfterComponentsToRenderChanged = false;

      pageContainer.current.style.transform = `translate3d(0, ${customPageNumber *
        -100}%, 0)`;

      setTimeout(() => {
        setComponentIndex(customPageNumber);
      }, animationTimer + animationTimerBuffer);
    }
  }, [
    animationTimer,
    animationTimerBuffer,
    componentsToRenderLength,
    customPageNumber,
  ]);

  return (

      <div
        ref={pageContainer}
        onWheel={wheelScroll}
        style={{
          height: "100%",
          width: "100%",
          transition: `transform ${animationTimer}ms ${transitionTimingFunction}`,
          outline: "none",
        }}
        tabIndex={0}
      >
        {setRenderComponents()}

  );
};

ReactPageScroller.propTypes = {
  animationTimer: PropTypes.number,
  animationTimerBuffer: PropTypes.number,
  blockScrollDown: PropTypes.bool,
  blockScrollUp: PropTypes.bool,
  children: PropTypes.any,
  containerHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  containerWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  customPageNumber: PropTypes.number,
  handleScrollUnavailable: PropTypes.func,
  pageOnChange: PropTypes.func,
  renderAllPagesOnFirstRender: PropTypes.bool,
  transitionTimingFunction: PropTypes.string,
};

ReactPageScroller.defaultProps = {
  animationTimer: DEFAULT_ANIMATION_TIMER,
  animationTimerBuffer: DEFAULT_ANIMATION_TIMER_BUFFER,
  transitionTimingFunction: DEFAULT_ANIMATION,
  containerHeight: DEFAULT_CONTAINER_HEIGHT,
  containerWidth: DEFAULT_CONTAINER_WIDTH,
  blockScrollUp: false,
  blockScrollDown: false,
};

export default ReactPageScroller;
<|endoftext|>


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

import config from './config'
import sdk from './sdk'
import SocialUser from './SocialUser'
import { cleanLocation, omit } from './utils'
import cancelable from './cancelable'
export { default as OldSocialLogin } from './component'

// // Enable Promises cancellation
// Promise.config({
//   cancellation: true
// })

/**
 * React Higher Order Component handling social login for multiple providers.
 * @param {Element} WrappedComponent
 * @constructor
 */
const SocialLogin = (WrappedComponent) => class SocialLogin extends Component {
  static propTypes = {
    appId: PropTypes.string.isRequired,
    autoCleanUri: PropTypes.bool,
    autoLogin: PropTypes.bool,
    gatekeeper: PropTypes.string,
    getInstance: PropTypes.func,
    onLoginFailure: PropTypes.func,
    onLoginSuccess: PropTypes.func,
    onLogoutFailure: PropTypes.func,
    onLogoutSuccess: PropTypes.func,
    onInternetFailure: PropTypes.func,
    provider: PropTypes.oneOf(config.providers).isRequired,
    redirect: (props, propName, componentName) => {
      if (props.provider === 'instagram' && (!props[propName] || typeof props[propName] !== 'string')) {
        return new Error(`Missing required \`${propName}\` prop of type \`string\` on ${componentName}.`)
      }
    },
    scope: PropTypes.oneOfType([
      PropTypes.array,
      PropTypes.string
    ])
  }

  constructor (props) {
    super(props)

    this.isStateless = !WrappedComponent.prototype.render

    this.state = {
      isLoaded: false,
      isConnected: false,
      isFetching: false
    }

    // Load required SDK
    this.sdk = sdk[props.provider]
    this.accessToken = null
    this.fetchProvider = props.provider === 'instagram' || props.provider === 'github'
    this.loadPromise = Promise.resolve()
    this.node = null
    this.onLoginSuccess = this.onLoginSuccess.bind(this)
    this.onLoginFailure = this.onLoginFailure.bind(this)
    this.onLogoutFailure = this.onLogoutFailure.bind(this)
    this.onLogoutSuccess = this.onLogoutSuccess.bind(this)
    this.login = this.login.bind(this)
    this.logout = this.logout.bind(this)
    this.setInstance = this.setInstance.bind(this)
  }

  /**
   * Loads SDK on componentDidMount and handles auto login.
   */
  componentDidMount () {
    const { appId, autoCleanUri, autoLogin, gatekeeper, redirect, scope, version } = this.props
    this.loadPromise = cancelable(this.sdk.load({ appId, redirect, gatekeeper, scope, version })
      .then((accessToken) => {
        if (autoCleanUri) {
          cleanLocation()
        }

        if (accessToken) {
          this.accessToken = accessToken
        }

        this.setState((prevState) => ({
          ...prevState,
          isLoaded: true
        }), () => {
          if (typeof this.props.onLoaded === 'function') {
            this.props.onLoaded()
          }
          if (autoLogin || this.accessToken) {
            if (this.fetchProvider && !this.accessToken) {
              this.sdk.login(appId, redirect)
                .catch(this.onLoginFailure)
            } else {
              this.sdk.checkLogin(true)
                .then(this.onLoginSuccess, this.onLoginFailure)
            }
          }
        })

        return null
      }, this.onLoginFailure))
  }

  componentWillReceiveProps (nextProps) {
    const { appId, gatekeeper, provider } = this.props

    if (provider === 'github' && !gatekeeper && appId !== nextProps.appId) {
      this.setState(() => ({
        isLoaded: false,
        isFetching: false,
        isConnected: false
      }), () => {
        this.sdk.load(nextProps.appId).then(() => {
          this.setState((prevState) => ({
            ...prevState,
            isLoaded: true
          }))
        }, this.onLoginFailure)
      })
    }
  }

  componentWillUnmount () {
    this.loadPromise.cancel()
    this.node = null
  }

  setInstance (node) {
    this.node = node

    if (typeof this.props.getInstance === 'function') {
      this.props.getInstance(node)
    }
  }

  /**
   * Triggers login process.
   */
  login () {
    if (!navigator.onLine && this.props.onInternetFailure) {
      let shouldSkip = this.props.onInternetFailure()
      if (shouldSkip === false) {
        return
      }
    }
    if (this.state.isLoaded && !this.state.isConnected && !this.state.isFetching) {
      this.setState((prevState) => ({
        ...prevState,
        isFetching: true
      }), () => {
        this.sdk.login().then(this.onLoginSuccess, this.onLoginFailure)
          .catch(this.onLoginFailure)
      })
    } else if (this.state.isLoaded && this.state.isConnected) {
      this.props.onLoginFailure('User already connected')
    } else if (this.state.isLoaded && this.state.isFetching) {
      this.props.onLoginFailure('Fetching user')
    } else if (!this.state.isLoaded) {
      this.props.onLoginFailure('SDK not loaded')
    } else {
      this.props.onLoginFailure('Unknown error')
    }
  }

  /**
   * Create SocialUser on login success and transmit it to onLoginSuccess prop.
   * @param {Object} response
   */
  onLoginSuccess (response) {
    const { onLoginSuccess, provider } = this.props
    const user = new SocialUser(provider)
    const socialUserData = this.sdk.generateUser(response)

    user.profile = socialUserData.profile
    user.token = socialUserData.token

    // Here we check that node is not null,
    // so we can update state before
    // triggering login success function
    if (this.node) {
      this.setState((prevState) => ({
        ...prevState,
        isFetching: false,
        isConnected: true
      }), () => {
        if (typeof onLoginSuccess === 'function') {
          onLoginSuccess(user)
        }
      })
    } else {
      if (typeof onLoginSuccess === 'function') {
        onLoginSuccess(user)
      }
    }
  }

  /**
   * Handles login failure.
   * @param err
   */
  onLoginFailure (err) {
    const { onLoginFailure } = this.props

    this.setState((prevState) => ({
      ...prevState,
      isFetching: false,
      isConnected: false
    }), () => {
      if (typeof onLoginFailure === 'function') {
        onLoginFailure(err)
      }
    })
  }

  /**
   * Triggers logout process.
   */
  logout () {
    if (this.state.isLoaded && this.state.isConnected) {
      this.sdk.logout().then(this.onLogoutSuccess, this.onLogoutFailure)
    } else if (this.state.isLoaded && !this.state.isConnected) {
      this.props.onLoginFailure('User not connected')
    } else {
      this.props.onLoginFailure('SDK not loaded')
    }
  }

  /**
   * Handles logout success
   */
  onLogoutSuccess () {
    const { onLogoutSuccess } = this.props

    if (this.node) {
      this.setState((prevState) => ({
        ...prevState,
        isConnected: false
      }), () => {
        if (typeof onLogoutSuccess === 'function') {
          onLogoutSuccess()
        }
      })
    } else {
      if (typeof onLogoutSuccess === 'function') {
        onLogoutSuccess()
      }
    }
  }

  /**
   * Handles logout failure.
   * @param err
   */
  onLogoutFailure (err) {
    if (typeof this.props.onLoginFailure === 'function') {
      this.props.onLoginFailure(err)
    }
  }

  render () {
    // Don’t forward unneeded props
    const originalProps = omit(this.props, [
      'appId',
      'scope',
      'autoCleanUri',
      'autoLogin',
      'gatekeeper',
      'getInstance',
      'onLoginFailure',
      'onLoginSuccess',
      'onLogoutFailure',
      'onLogoutSuccess',
      'provider',
      'redirect',
      'onInternetFailure',
      'ref'
    ])
    let additionnalProps = {}

    if (this.props.onLogoutFailure || this.props.onLogoutSuccess) {
      additionnalProps = {
        triggerLogout: this.logout
      }
    }

    if (!this.isStateless) {
      additionnalProps = {
        ...additionnalProps,
        ref: this.setInstance
      }
    }

    return (
      this.state.isLoaded
        ? <WrappedComponent triggerLogin={this.login} {...additionnalProps} {...originalProps} />
        : null
    )
  }
}

export default SocialLogin
<commit_msg>Added support for optional LoaderComponent <commit_after>
import PropTypes from 'prop-types'
import React, { Component } from 'react'

import config from './config'
import sdk from './sdk'
import SocialUser from './SocialUser'
import { cleanLocation, omit } from './utils'
import cancelable from './cancelable'
export { default as OldSocialLogin } from './component'

// // Enable Promises cancellation
// Promise.config({
//   cancellation: true
// })

/**
 * React Higher Order Component handling social login for multiple providers.
 * @param {Element} WrappedComponent
 * @param {Element} LoaderComponent
 * @constructor
 */
const SocialLogin = (WrappedComponent, LoaderComponent) => class SocialLogin extends Component {
  static propTypes = {
    appId: PropTypes.string.isRequired,
    autoCleanUri: PropTypes.bool,
    autoLogin: PropTypes.bool,
    gatekeeper: PropTypes.string,
    getInstance: PropTypes.func,
    onLoginFailure: PropTypes.func,
    onLoginSuccess: PropTypes.func,
    onLogoutFailure: PropTypes.func,
    onLogoutSuccess: PropTypes.func,
    onInternetFailure: PropTypes.func,
    provider: PropTypes.oneOf(config.providers).isRequired,
    redirect: (props, propName, componentName) => {
      if (props.provider === 'instagram' && (!props[propName] || typeof props[propName] !== 'string')) {
        return new Error(`Missing required \`${propName}\` prop of type \`string\` on ${componentName}.`)
      }
    },
    scope: PropTypes.oneOfType([
      PropTypes.array,
      PropTypes.string
    ])
  }

  constructor (props) {
    super(props)

    this.isStateless = !WrappedComponent.prototype.render

    this.state = {
      isLoaded: false,
      isConnected: false,
      isFetching: false
    }

    // Load required SDK
    this.sdk = sdk[props.provider]
    this.accessToken = null
    this.fetchProvider = props.provider === 'instagram' || props.provider === 'github'
    this.loadPromise = Promise.resolve()
    this.node = null
    this.onLoginSuccess = this.onLoginSuccess.bind(this)
    this.onLoginFailure = this.onLoginFailure.bind(this)
    this.onLogoutFailure = this.onLogoutFailure.bind(this)
    this.onLogoutSuccess = this.onLogoutSuccess.bind(this)
    this.login = this.login.bind(this)
    this.logout = this.logout.bind(this)
    this.setInstance = this.setInstance.bind(this)
  }

  /**
   * Loads SDK on componentDidMount and handles auto login.
   */
  componentDidMount () {
    const { appId, autoCleanUri, autoLogin, gatekeeper, redirect, scope, version } = this.props
    this.loadPromise = cancelable(this.sdk.load({ appId, redirect, gatekeeper, scope, version })
      .then((accessToken) => {
        if (autoCleanUri) {
          cleanLocation()
        }

        if (accessToken) {
          this.accessToken = accessToken
        }

        this.setState((prevState) => ({
          ...prevState,
          isLoaded: true
        }), () => {
          if (typeof this.props.onLoaded === 'function') {
            this.props.onLoaded()
          }
          if (autoLogin || this.accessToken) {
            if (this.fetchProvider && !this.accessToken) {
              this.sdk.login(appId, redirect)
                .catch(this.onLoginFailure)
            } else {
              this.sdk.checkLogin(true)
                .then(this.onLoginSuccess, this.onLoginFailure)
            }
          }
        })

        return null
      }, this.onLoginFailure))
  }

  componentWillReceiveProps (nextProps) {
    const { appId, gatekeeper, provider } = this.props

    if (provider === 'github' && !gatekeeper && appId !== nextProps.appId) {
      this.setState(() => ({
        isLoaded: false,
        isFetching: false,
        isConnected: false
      }), () => {
        this.sdk.load(nextProps.appId).then(() => {
          this.setState((prevState) => ({
            ...prevState,
            isLoaded: true
          }))
        }, this.onLoginFailure)
      })
    }
  }

  componentWillUnmount () {
    this.loadPromise.cancel()
    this.node = null
  }

  setInstance (node) {
    this.node = node

    if (typeof this.props.getInstance === 'function') {
      this.props.getInstance(node)
    }
  }

  /**
   * Triggers login process.
   */
  login () {
    if (!navigator.onLine && this.props.onInternetFailure) {
      let shouldSkip = this.props.onInternetFailure()
      if (shouldSkip === false) {
        return
      }
    }
    if (this.state.isLoaded && !this.state.isConnected && !this.state.isFetching) {
      this.setState((prevState) => ({
        ...prevState,
        isFetching: true
      }), () => {
        this.sdk.login().then(this.onLoginSuccess, this.onLoginFailure)
          .catch(this.onLoginFailure)
      })
    } else if (this.state.isLoaded && this.state.isConnected) {
      this.props.onLoginFailure('User already connected')
    } else if (this.state.isLoaded && this.state.isFetching) {
      this.props.onLoginFailure('Fetching user')
    } else if (!this.state.isLoaded) {
      this.props.onLoginFailure('SDK not loaded')
    } else {
      this.props.onLoginFailure('Unknown error')
    }
  }

  /**
   * Create SocialUser on login success and transmit it to onLoginSuccess prop.
   * @param {Object} response
   */
  onLoginSuccess (response) {
    const { onLoginSuccess, provider } = this.props
    const user = new SocialUser(provider)
    const socialUserData = this.sdk.generateUser(response)

    user.profile = socialUserData.profile
    user.token = socialUserData.token

    // Here we check that node is not null,
    // so we can update state before
    // triggering login success function
    if (this.node) {
      this.setState((prevState) => ({
        ...prevState,
        isFetching: false,
        isConnected: true
      }), () => {
        if (typeof onLoginSuccess === 'function') {
          onLoginSuccess(user)
        }
      })
    } else {
      if (typeof onLoginSuccess === 'function') {
        onLoginSuccess(user)
      }
    }
  }

  /**
   * Handles login failure.
   * @param err
   */
  onLoginFailure (err) {
    const { onLoginFailure } = this.props

    this.setState((prevState) => ({
      ...prevState,
      isFetching: false,
      isConnected: false
    }), () => {
      if (typeof onLoginFailure === 'function') {
        onLoginFailure(err)
      }
    })
  }

  /**
   * Triggers logout process.
   */
  logout () {
    if (this.state.isLoaded && this.state.isConnected) {
      this.sdk.logout().then(this.onLogoutSuccess, this.onLogoutFailure)
    } else if (this.state.isLoaded && !this.state.isConnected) {
      this.props.onLoginFailure('User not connected')
    } else {
      this.props.onLoginFailure('SDK not loaded')
    }
  }

  /**
   * Handles logout success
   */
  onLogoutSuccess () {
    const { onLogoutSuccess } = this.props

    if (this.node) {
      this.setState((prevState) => ({
        ...prevState,
        isConnected: false
      }), () => {
        if (typeof onLogoutSuccess === 'function') {
          onLogoutSuccess()
        }
      })
    } else {
      if (typeof onLogoutSuccess === 'function') {
        onLogoutSuccess()
      }
    }
  }

  /**
   * Handles logout failure.
   * @param err
   */
  onLogoutFailure (err) {
    if (typeof this.props.onLoginFailure === 'function') {
      this.props.onLoginFailure(err)
    }
  }

  render () {
    // Don’t forward unneeded props
    const originalProps = omit(this.props, [
      'appId',
      'scope',
      'autoCleanUri',
      'autoLogin',
      'gatekeeper',
      'getInstance',
      'onLoginFailure',
      'onLoginSuccess',
      'onLogoutFailure',
      'onLogoutSuccess',
      'provider',
      'redirect',
      'onInternetFailure',
      'ref'
    ])
    let additionnalProps = {}

    if (this.props.onLogoutFailure || this.props.onLogoutSuccess) {
      additionnalProps = {
        triggerLogout: this.logout
      }
    }

    if (!this.isStateless) {
      additionnalProps = {
        ...additionnalProps,
        ref: this.setInstance
      }
    }

    return (
      this.state.isLoaded
        ? <WrappedComponent triggerLogin={this.login} {...additionnalProps} {...originalProps} />
        : (LoaderComponent ? <LoaderComponent /> : null)
    )
  }
}

export default SocialLogin
<|endoftext|>


<commit_before>import path from 'path';
import lodash from 'lodash';
import minimatch from 'minimatch';
import check from './check';
import { readJSON } from './utils';

import {
  defaultOptions,
  availableParsers,
  availableDetectors,
  availableSpecials,
} from './constants';

function isIgnored(ignoreMatches, dependency) {
  const match = lodash.partial(minimatch, dependency);
  return ignoreMatches.some(match);
}

function hasBin(rootDir, dependency) {
  try {
    const metadata = readJSON(path.join(rootDir, 'node_modules', dependency, 'package.json'));
    return {}.hasOwnProperty.call(metadata, 'bin');
  } catch (error) {
    return false;
  }
}

function filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, dependencies) {
  return lodash(dependencies)
    .keys()
    .reject(dep =>
      (isIgnored(ignoreMatches, dep)) ||
      (ignoreBinPackage && hasBin(rootDir, dep)))
    .value();
}

export default function depcheck(rootDir, options, callback) {
  const getOption = key =>
    (lodash.isUndefined(options[key]) ? defaultOptions[key] : options[key]);

  const withoutDev = getOption('withoutDev');
  const ignoreBinPackage = getOption('ignoreBinPackage');
  const ignoreMatches = getOption('ignoreMatches');
  const ignoreDirs = lodash.union(defaultOptions.ignoreDirs, options.ignoreDirs);

  const detectors = getOption('detectors');
  const parsers = lodash(getOption('parsers'))
    .mapValues(value => (lodash.isArray(value) ? value : [value]))
    .merge({ '*': getOption('specials') })
    .value();

  const metadata = options.package || readJSON(path.join(rootDir, 'package.json'));
  const dependencies = metadata.dependencies || {};
  const devDependencies = !withoutDev && metadata.devDependencies ? metadata.devDependencies : {};
  const peerDeps = Object.keys(metadata.peerDependencies || {});
  const optionalDeps = Object.keys(metadata.optionalDependencies || {});
  const deps = filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, dependencies);
  const devDeps = filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, devDependencies);

  return check({
    rootDir,
    ignoreDirs,
    deps,
    devDeps,
    peerDeps,
    optionalDeps,
    parsers,
    detectors,
  })
  .then((results) => {
    const postProcessedResults = results;

    postProcessedResults.missing = lodash.pick(
      results.missing,
      filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, results.missing),
    );

    return postProcessedResults;
  })
  .then(callback);
}

depcheck.parser = availableParsers;
depcheck.detector = availableDetectors;
depcheck.special = availableSpecials;
<commit_msg>Make the post process function pure.<commit_after>import path from 'path';
import lodash from 'lodash';
import minimatch from 'minimatch';
import check from './check';
import { readJSON } from './utils';

import {
  defaultOptions,
  availableParsers,
  availableDetectors,
  availableSpecials,
} from './constants';

function isIgnored(ignoreMatches, dependency) {
  const match = lodash.partial(minimatch, dependency);
  return ignoreMatches.some(match);
}

function hasBin(rootDir, dependency) {
  try {
    const metadata = readJSON(path.join(rootDir, 'node_modules', dependency, 'package.json'));
    return {}.hasOwnProperty.call(metadata, 'bin');
  } catch (error) {
    return false;
  }
}

function filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, dependencies) {
  return lodash(dependencies)
    .keys()
    .reject(dep =>
      (isIgnored(ignoreMatches, dep)) ||
      (ignoreBinPackage && hasBin(rootDir, dep)))
    .value();
}

export default function depcheck(rootDir, options, callback) {
  const getOption = key =>
    (lodash.isUndefined(options[key]) ? defaultOptions[key] : options[key]);

  const withoutDev = getOption('withoutDev');
  const ignoreBinPackage = getOption('ignoreBinPackage');
  const ignoreMatches = getOption('ignoreMatches');
  const ignoreDirs = lodash.union(defaultOptions.ignoreDirs, options.ignoreDirs);

  const detectors = getOption('detectors');
  const parsers = lodash(getOption('parsers'))
    .mapValues(value => (lodash.isArray(value) ? value : [value]))
    .merge({ '*': getOption('specials') })
    .value();

  const metadata = options.package || readJSON(path.join(rootDir, 'package.json'));
  const dependencies = metadata.dependencies || {};
  const devDependencies = !withoutDev && metadata.devDependencies ? metadata.devDependencies : {};
  const peerDeps = Object.keys(metadata.peerDependencies || {});
  const optionalDeps = Object.keys(metadata.optionalDependencies || {});
  const deps = filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, dependencies);
  const devDeps = filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, devDependencies);

  return check({
    rootDir,
    ignoreDirs,
    deps,
    devDeps,
    peerDeps,
    optionalDeps,
    parsers,
    detectors,
  })
  .then(results => Object.assign(results, {
    missing: lodash.pick(
      results.missing,
      filterDependencies(rootDir, ignoreBinPackage, ignoreMatches, results.missing),
    ),
  }))
  .then(callback);
}

depcheck.parser = availableParsers;
depcheck.detector = availableDetectors;
depcheck.special = availableSpecials;
<|endoftext|>


<commit_before>'use strict'

const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const chalk = require('chalk')
const fs = require('fs')
const path = require('path')

const errorTypes = {
  HALT: 'HALT',
}

class ServerlessPlugin {
  constructor(serverless, options) {
    this.serverless = serverless
    this.serverless.service.provider.environment =
      this.serverless.service.provider.environment || {}
    this.config =
      this.serverless.service.custom && this.serverless.service.custom['dotenv']
    this.logging =
      this.config && typeof this.config.logging !== 'undefined'
        ? this.config.logging
        : true
    this.required = (this.config && this.config.required) || {}
    this.variableExpansion = !(
      (this.config && this.config.variableExpansion) === false
    )

    if (this.config && this.config.dotenvParser) {
      this.dotenvParserPath = path.join(
        serverless.config.servicePath,
        this.config.dotenvParser,
      )
    }

    this.loadEnv(this.getEnvironment(options))
  }

  log(...args) {
    if (this.logging) {
      this.serverless.cli.log(...args)
    }
  }

  /**
   * @param {Object} options
   * @returns {string}
   */
  getEnvironment(options) {
    return process.env.NODE_ENV || options.env || options.stage || 'development'
  }

  /**
   * @param {string} env
   * @returns {string[]}
   */
  resolveEnvFileNames(env) {
    const basePath = (this.config && this.config.basePath) || ''

    if (this.config && this.config.path) {
      if (basePath) {
        this.log('DOTENV (WARNING): if "path" is set, "basePath" is ignored.')
      }

      if (Array.isArray(this.config.path)) {
        return this.config.path
      }
      return [this.config.path]
    }

    // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
    const dotenvFiles = [
      `.env.${env}.local`,
      `.env.${env}`,
      // Don't include `.env.local` for `test` environment
      // since normally you expect tests to produce the same
      // results for everyone
      env !== 'test' && `.env.local`,
      `.env`,
    ]

    const filesNames = dotenvFiles.map((file) => basePath + file)

    return filesNames.filter((fileName) => fs.existsSync(fileName))
  }

  /**
   * @param {string[]} envFileNames
   * @returns {Object}
   */
  callDotenvParser(envFileNames) {
    try {
      return require(this.dotenvParserPath)({ dotenv, paths: envFileNames })
    } catch (err) {
      throw Object.assign(err, { type: errorTypes.HALT })
    }
  }

  /**
   * @param {string[]} envFileNames
   * @returns {Object}
   */
  parseEnvFiles(envFileNames) {
    const envVarsArray = envFileNames.map((fileName) => {
      const parsed = dotenv.config({ path: fileName })
      return this.variableExpansion
        ? dotenvExpand(parsed).parsed
        : parsed.parsed
    })

    return envVarsArray.reduce((acc, curr) => ({ ...acc, ...curr }), {})
  }

  /**
   * @param {Object} envVars
   */
  setProviderEnv(envVars) {
    const include = (this.config && this.config.include) || []
    const exclude = (this.config && this.config.exclude) || []

    if (include.length > 0) {
      if (exclude.length > 0) {
        this.log('DOTENV (WARNING): if "include" is set, "exclude" is ignored.')
      }

      Object.keys(envVars)
        .filter((key) => !include.includes(key))
        .forEach((key) => {
          delete envVars[key]
        })
    } else if (exclude.length > 0) {
      Object.keys(envVars)
        .filter((key) => exclude.includes(key))
        .forEach((key) => {
          delete envVars[key]
        })
    }

    Object.keys(envVars).forEach((key) => {
      this.log('\t - ' + key)
      this.serverless.service.provider.environment[key] = envVars[key]
    })
  }

  /**
   * @param {string[]} envFileNames
   */
  validateEnvFileNames(envFileNames) {
    if (envFileNames.length > 0) {
      this.log(
        'DOTENV: Loading environment variables from ' +
          [...envFileNames].reverse().join(', ') +
          ':',
      )
    } else {
      const errorMsg = 'DOTENV: Could not find .env file.'
      this.log(errorMsg)

      if (this.required.file === true) {
        throw Object.assign(new Error(errorMsg), { type: errorTypes.HALT })
      }
    }
  }

  /**
   * @param {string[]} envFileNames
   */
  validateEnvVars(envVars) {
    if (!envVars) {
      throw Object.assign(
        new Error(
          'Unexpected env var object (expected an object but is falsy). Did you forget to return an object in your dotenv parser?',
        ),
        { type: errorTypes.HALT },
      )
    }

    const missingRequiredEnvVars = (this.required.env || []).filter(
      (envVarName) => !envVars[envVarName] && !process.env[envVarName],
    )

    if (missingRequiredEnvVars.length > 0) {
      throw Object.assign(
        new Error(
          `Missing the following required environment variables: ${missingRequiredEnvVars.join(
            ',',
          )}`,
        ),
        { type: errorTypes.HALT },
      )
    }
  }

  /**
   * @param {string} env
   */
  loadEnv(env) {
    const envFileNames = this.resolveEnvFileNames(env)
    try {
      this.validateEnvFileNames(envFileNames)

      const envVars = this.dotenvParserPath
        ? this.callDotenvParser(envFileNames)
        : this.parseEnvFiles(envFileNames)

      this.validateEnvVars(envVars)
      this.setProviderEnv(envVars)
    } catch (e) {
      if (e.type === errorTypes.HALT) {
        throw e
      }

      console.error(
        chalk.red(
          '\n Serverless Plugin Error --------------------------------------\n',
        ),
      )
      console.error(chalk.red('  ' + e.message))
    }
  }
}

module.exports = ServerlessPlugin
<commit_msg>refactor: consolidate configs for easy default configuration. (#136)<commit_after>'use strict'

const dotenv = require('dotenv')
const dotenvExpand = require('dotenv-expand')
const chalk = require('chalk')
const fs = require('fs')
const path = require('path')

const errorTypes = {
  HALT: 'HALT',
}

class ServerlessPlugin {
  constructor(serverless, options) {
    this.serverless = serverless
    this.serverless.service.provider.environment =
      this.serverless.service.provider.environment || {}

    this.config = Object.assign(
      {
        logging: true,
        required: {},
        variableExpansion: true,
      },
      (this.serverless.service.custom &&
        this.serverless.service.custom['dotenv']) ||
        {},
    )

    if (this.config.dotenvParser) {
      this.config.dotenvParserPath = path.join(
        serverless.config.servicePath,
        this.config.dotenvParser,
      )
    }

    this.loadEnv(this.getEnvironment(options))
  }

  log(...args) {
    if (this.config.logging) {
      this.serverless.cli.log(...args)
    }
  }

  /**
   * @param {Object} options
   * @returns {string}
   */
  getEnvironment(options) {
    return process.env.NODE_ENV || options.env || options.stage || 'development'
  }

  /**
   * @param {string} env
   * @returns {string[]}
   */
  resolveEnvFileNames(env) {
    const basePath = (this.config && this.config.basePath) || ''

    if (this.config && this.config.path) {
      if (basePath) {
        this.log('DOTENV (WARNING): if "path" is set, "basePath" is ignored.')
      }

      if (Array.isArray(this.config.path)) {
        return this.config.path
      }
      return [this.config.path]
    }

    // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
    const dotenvFiles = [
      `.env.${env}.local`,
      `.env.${env}`,
      // Don't include `.env.local` for `test` environment
      // since normally you expect tests to produce the same
      // results for everyone
      env !== 'test' && `.env.local`,
      `.env`,
    ]

    const filesNames = dotenvFiles.map((file) => basePath + file)

    return filesNames.filter((fileName) => fs.existsSync(fileName))
  }

  /**
   * @param {string[]} envFileNames
   * @returns {Object}
   */
  callDotenvParser(envFileNames) {
    try {
      return require(this.config.dotenvParserPath)({
        dotenv,
        paths: envFileNames,
      })
    } catch (err) {
      throw Object.assign(err, { type: errorTypes.HALT })
    }
  }

  /**
   * @param {string[]} envFileNames
   * @returns {Object}
   */
  parseEnvFiles(envFileNames) {
    const envVarsArray = envFileNames.map((fileName) => {
      const parsed = dotenv.config({ path: fileName })
      return this.config.variableExpansion
        ? dotenvExpand(parsed).parsed
        : parsed.parsed
    })

    return envVarsArray.reduce((acc, curr) => ({ ...acc, ...curr }), {})
  }

  /**
   * @param {Object} envVars
   */
  setProviderEnv(envVars) {
    const include = (this.config && this.config.include) || []
    const exclude = (this.config && this.config.exclude) || []

    if (include.length > 0) {
      if (exclude.length > 0) {
        this.log('DOTENV (WARNING): if "include" is set, "exclude" is ignored.')
      }

      Object.keys(envVars)
        .filter((key) => !include.includes(key))
        .forEach((key) => {
          delete envVars[key]
        })
    } else if (exclude.length > 0) {
      Object.keys(envVars)
        .filter((key) => exclude.includes(key))
        .forEach((key) => {
          delete envVars[key]
        })
    }

    Object.keys(envVars).forEach((key) => {
      this.log('\t - ' + key)
      this.serverless.service.provider.environment[key] = envVars[key]
    })
  }

  /**
   * @param {string[]} envFileNames
   */
  validateEnvFileNames(envFileNames) {
    if (envFileNames.length > 0) {
      this.log(
        'DOTENV: Loading environment variables from ' +
          [...envFileNames].reverse().join(', ') +
          ':',
      )
    } else {
      const errorMsg = 'DOTENV: Could not find .env file.'
      this.log(errorMsg)

      if (this.config.required.file === true) {
        throw Object.assign(new Error(errorMsg), { type: errorTypes.HALT })
      }
    }
  }

  /**
   * @param {string[]} envFileNames
   */
  validateEnvVars(envVars) {
    if (!envVars) {
      throw Object.assign(
        new Error(
          'Unexpected env var object (expected an object but is falsy). Did you forget to return an object in your dotenv parser?',
        ),
        { type: errorTypes.HALT },
      )
    }

    const missingRequiredEnvVars = (this.config.required.env || []).filter(
      (envVarName) => !envVars[envVarName] && !process.env[envVarName],
    )

    if (missingRequiredEnvVars.length > 0) {
      throw Object.assign(
        new Error(
          `Missing the following required environment variables: ${missingRequiredEnvVars.join(
            ',',
          )}`,
        ),
        { type: errorTypes.HALT },
      )
    }
  }

  /**
   * @param {string} env
   */
  loadEnv(env) {
    const envFileNames = this.resolveEnvFileNames(env)
    try {
      this.validateEnvFileNames(envFileNames)

      const envVars = this.config.dotenvParserPath
        ? this.callDotenvParser(envFileNames)
        : this.parseEnvFiles(envFileNames)

      this.validateEnvVars(envVars)
      this.setProviderEnv(envVars)
    } catch (e) {
      if (e.type === errorTypes.HALT) {
        throw e
      }

      console.error(
        chalk.red(
          '\n Serverless Plugin Error --------------------------------------\n',
        ),
      )
      console.error(chalk.red('  ' + e.message))
    }
  }
}

module.exports = ServerlessPlugin
<|endoftext|>


<commit_before>const babel = require('babel-core')
const resolve = require('resolve-from')
const fspath = require('path')
const pkgup = require('pkg-up')
const pkgpath = fspath.dirname(pkgup.sync())
const _ = require('lodash')
// const Graph = require('graph').Graph

function findDeps(fullFilePath) {
	const tfmd = babel.transformFileSync(fullFilePath, {
		sourceType: 'module',
		extends: '/Users/hawken/gobbldygook/.babelrc',
	})
	let dirname = fspath.dirname(fullFilePath)

	let references = []
	babel.traverse(tfmd.ast, {
		enter(path) {
			if (
				path.node.type === 'CallExpression' &&
				path.node.callee.name === 'require'
			) {
				let refs = path.node.arguments
					.map(a => a.value)
					.map(id => resolve(dirname, id))
				references = references.concat(refs)
			}
		},
	})

	return references
}

function shortenPath(path) {
	return fspath.relative(pkgpath, path)
}

function prettyifyResults(res) {
	let pairs = _.toPairs(res)
	let edited = _.map(pairs, ([key, val]) => {
		key = shortenPath(key)
		val = val.map(shortenPath)
		return [key, val]
	})
	return _.fromPairs(_.sortBy(edited, ([k]) => k))
}

function main() {
	let seen = {}

	let toTraverse = process.argv.slice(2)
	let p

	while ((p = toTraverse.pop())) {
		let fullPath = fspath.resolve(p)
		// console.log(fullPath)

		if (seen.hasOwnProperty(fullPath)) {
			continue
		}

		seen[fullPath] = findDeps(fullPath)
		seen[fullPath] = _.reject(seen[fullPath], p => _.includes(p, 'node_modules'))
		toTraverse = toTraverse.concat(seen[fullPath])
	}

	// console.log(seen)
	let nice = prettyifyResults(seen)
	console.log(nice)
	// console.log(new Graph(nice))
}

main()
<commit_msg>remove an unused script<commit_after><|endoftext|>